Bindings layer for declarative events API

BUG=112155
TEST=no

Review URL: https://chromiumcodereview.appspot.com/9192029

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@120962 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/api/declarative/declarative_api.cc b/chrome/browser/extensions/api/declarative/declarative_api.cc
new file mode 100644
index 0000000..e3806675
--- /dev/null
+++ b/chrome/browser/extensions/api/declarative/declarative_api.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/browser/extensions/api/declarative/declarative_api.h"
+
+#include "base/values.h"
+
+namespace extensions {
+
+bool AddRulesFunction::RunImpl() {
+  // LOG(ERROR) << "AddRulesFunction called";
+
+  ListValue* rules_list = NULL;
+  EXTENSION_FUNCTION_VALIDATE(args_->GetList(1, &rules_list));
+
+  // TODO(battre): Generate unique IDs and priorities here.
+
+  result_.reset(rules_list->DeepCopy());
+  return true;
+}
+
+bool RemoveRulesFunction::RunImpl() {
+  // LOG(ERROR) << "RemoveRulesFunction called";
+  return true;
+}
+
+bool GetRulesFunction::RunImpl() {
+  // LOG(ERROR) << "GetRulesFunction called";
+  result_.reset(new ListValue());
+  return true;
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/declarative/declarative_api.h b/chrome/browser/extensions/api/declarative/declarative_api.h
new file mode 100644
index 0000000..64009465
--- /dev/null
+++ b/chrome/browser/extensions/api/declarative/declarative_api.h
@@ -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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_DECLARATIVE_API_H__
+#define CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_DECLARATIVE_API_H__
+#pragma once
+
+#include "chrome/browser/extensions/extension_function.h"
+
+namespace extensions {
+
+class AddRulesFunction : public SyncExtensionFunction {
+ public:
+  virtual bool RunImpl() OVERRIDE;
+  DECLARE_EXTENSION_FUNCTION_NAME("experimental.declarative.addRules");
+};
+
+class RemoveRulesFunction : public SyncExtensionFunction {
+ public:
+  virtual bool RunImpl() OVERRIDE;
+  DECLARE_EXTENSION_FUNCTION_NAME("experimental.declarative.removeRules");
+};
+
+class GetRulesFunction : public SyncExtensionFunction {
+ public:
+  virtual bool RunImpl() OVERRIDE;
+  DECLARE_EXTENSION_FUNCTION_NAME("experimental.declarative.getRules");
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_DECLARATIVE_API_H__
diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc
index 2a0d4093..9e4bdd57 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.cc
+++ b/chrome/browser/extensions/extension_function_dispatcher.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/bookmarks/bookmark_manager_extension_api.h"
 #include "chrome/browser/download/download_extension_api.h"
 #include "chrome/browser/extensions/api/app/app_api.h"
+#include "chrome/browser/extensions/api/declarative/declarative_api.h"
 #include "chrome/browser/extensions/api/dns/dns_api.h"
 #include "chrome/browser/extensions/api/permissions/permissions_api.h"
 #include "chrome/browser/extensions/api/serial/serial_api.h"
@@ -501,6 +502,11 @@
   // System
   RegisterFunction<extensions::GetIncognitoModeAvailabilityFunction>();
   RegisterFunction<extensions::GetUpdateStatusFunction>();
+
+  // Net
+  RegisterFunction<extensions::AddRulesFunction>();
+  RegisterFunction<extensions::RemoveRulesFunction>();
+  RegisterFunction<extensions::GetRulesFunction>();
 }
 
 void FactoryRegistry::GetAllNames(std::vector<std::string>* names) {
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 103f7b9..63864691 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -964,6 +964,8 @@
         'browser/event_disposition.h',
         'browser/extensions/api/app/app_api.cc',
         'browser/extensions/api/app/app_api.h',
+        'browser/extensions/api/declarative/declarative_api.cc',
+        'browser/extensions/api/declarative/declarative_api.h',
         'browser/extensions/api/dns/dns_api.cc',
         'browser/extensions/api/dns/dns_api.h',
         'browser/extensions/api/permissions/permissions_api.cc',
diff --git a/chrome/chrome_renderer.gypi b/chrome/chrome_renderer.gypi
index 0efa805..e2f4e26 100644
--- a/chrome/chrome_renderer.gypi
+++ b/chrome/chrome_renderer.gypi
@@ -126,6 +126,7 @@
         'renderer/resources/extensions/context_menus_custom_bindings.js',
         'renderer/resources/extensions/devtools_custom_bindings.js',
         'renderer/resources/extensions/event.js',
+        'renderer/resources/extensions/experimental.declarative_custom_bindings.js',
         'renderer/resources/extensions/experimental.socket_custom_bindings.js',
         'renderer/resources/extensions/extension_custom_bindings.js',
         'renderer/resources/extensions/file_browser_handler_custom_bindings.js',
diff --git a/chrome/common/common_resources.grd b/chrome/common/common_resources.grd
index 9b460e2..5e2a635bf 100644
--- a/chrome/common/common_resources.grd
+++ b/chrome/common/common_resources.grd
@@ -23,6 +23,7 @@
       <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_APP" file="extensions\api\experimental.app.json" type="BINDATA" />
       <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_BOOKMARKMANAGER" file="extensions\api\experimental.bookmarkManager.json" type="BINDATA" />
       <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_CLEAR" file="extensions\api\experimental.clear.json" type="BINDATA" />
+      <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_DECLARATIVE" file="extensions\api\experimental.declarative.json" type="BINDATA" />
       <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_DNS" file="extensions\api\experimental.dns.json" type="BINDATA" />
       <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_DOWNLOADS" file="extensions\api\experimental.downloads.json" type="BINDATA" />
       <include name="IDR_EXTENSION_API_JSON_EXPERIMENTAL_EXTENSIONS" file="extensions\api\experimental.extension.json" type="BINDATA" />
diff --git a/chrome/common/extensions/api/experimental.declarative.json b/chrome/common/extensions/api/experimental.declarative.json
new file mode 100644
index 0000000..f8e315b
--- /dev/null
+++ b/chrome/common/extensions/api/experimental.declarative.json
@@ -0,0 +1,128 @@
+[
+  {
+    "namespace": "experimental.declarative",
+    "nodoc": true,
+    "types": [
+      {
+        "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."
+          }
+        }
+      }
+    ],
+    "functions": [
+      {
+        "name": "addRules",
+        "type": "function",
+        "description": "Registers rules to handle events.",
+        "parameters": [
+          {
+            "name": "event",
+            "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": [
+          {
+            "name": "event",
+            "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": [
+          {
+            "name": "event",
+            "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."
+          }
+        ]
+      }
+    ]
+  }
+]
+
diff --git a/chrome/common/extensions/api/extension_api.cc b/chrome/common/extensions/api/extension_api.cc
index 226a8b99..df5de4a 100644
--- a/chrome/common/extensions/api/extension_api.cc
+++ b/chrome/common/extensions/api/extension_api.cc
@@ -88,6 +88,7 @@
     IDR_EXTENSION_API_JSON_EXPERIMENTAL_APP,
     IDR_EXTENSION_API_JSON_EXPERIMENTAL_BOOKMARKMANAGER,
     IDR_EXTENSION_API_JSON_EXPERIMENTAL_CLEAR,
+    IDR_EXTENSION_API_JSON_EXPERIMENTAL_DECLARATIVE,
     IDR_EXTENSION_API_JSON_EXPERIMENTAL_DNS,
     IDR_EXTENSION_API_JSON_EXPERIMENTAL_DOWNLOADS,
     IDR_EXTENSION_API_JSON_EXPERIMENTAL_EXTENSIONS,
diff --git a/chrome/common/extensions/api/webRequest.json b/chrome/common/extensions/api/webRequest.json
index 40ac446..52e007f9 100644
--- a/chrome/common/extensions/api/webRequest.json
+++ b/chrome/common/extensions/api/webRequest.json
@@ -75,6 +75,38 @@
             }
           }
         }
+      },
+      {
+        "nodoc": true,
+        "id": "RequestMatcher",
+        "type": "object",
+        "description": "Matches network events by various criteria. Experimental junk, do not use!",
+        "properties": {
+          "url": {
+            "type": "string",
+            "description": "Matches for a full URL",
+            "optional": true
+          },
+          "filterType": { "type": "string", "enum": ["net.RequestMatcher"] }
+        }
+      },
+      {
+        "nodoc": true,
+        "id": "CancelRequest",
+        "description": "Declarative event action that cancels a network request. Experimental junk, do not use!",
+        "type": "object",
+        "properties": {
+          "actionType": { "type": "string", "enum": ["net.CancelRequest"] }
+        }
+      },
+      {
+        "nodoc": true,
+        "id": "ModifyRequest",
+        "description": "Declarative event action that modifies a network request. Experimental junk, do not use!",
+        "type": "object",
+        "properties": {
+          "actionType": { "type": "string", "enum": ["net.ModifyRequest"] }
+        }
       }
     ],
     "functions": [
@@ -518,6 +550,16 @@
             "description": "A set of filters that restricts the events that will be sent to this listener."
           }
         ]
+      },
+      {
+        "name": "onRequest",
+        "nodoc": true,
+        "options": {
+          "supportsListeners": false,
+          "supportsRules": true,
+          "conditions": ["RequestMatcher"],
+          "actions": ["CancelRequest", "ModifyRequest"]
+        }
       }
     ]
   }
diff --git a/chrome/common/extensions/docs/samples.json b/chrome/common/extensions/docs/samples.json
index de39af7..3afdde7e 100644
--- a/chrome/common/extensions/docs/samples.json
+++ b/chrome/common/extensions/docs/samples.json
@@ -68,7 +68,6 @@
     "chrome.experimental.devtools.console.addMessage": "experimental.devtools.console.html#method-addMessage",
     "chrome.experimental.devtools.console.getMessages": "experimental.devtools.console.html#method-getMessages",
     "chrome.experimental.devtools.console.onMessageAdded": "experimental.devtools.console.html#event-onMessageAdded",
-    "chrome.experimental.extension.onInstalled": "experimental.extension.html#event-onInstalled",
     "chrome.experimental.infobars.show": "experimental.infobars.html#method-show",
     "chrome.experimental.speechInput.isRecording": "experimental.speechInput.html#method-isRecording",
     "chrome.experimental.speechInput.onError": "experimental.speechInput.html#event-onError",
diff --git a/chrome/common/extensions/docs/template/api_template.html b/chrome/common/extensions/docs/template/api_template.html
index 8fa2e449..e4144a4 100644
--- a/chrome/common/extensions/docs/template/api_template.html
+++ b/chrome/common/extensions/docs/template/api_template.html
@@ -149,7 +149,8 @@
           <li id="typesTocTemplate" jsdisplay="types && types.length > 0">
             <a href="#types">Types</a>
             <ol>
-              <li jsselect="types.sort(sortByName)">
+              <li jsselect="types.sort(sortByName)"
+                  jsdisplay="!($this.nodoc)">
                 <a jscontent="id"
                    jsvalues=".href:'#type-' + id"
                    href="#id-anchor">id</a>
@@ -514,7 +515,8 @@
             <h3 id="types">Types</h3>
 
             <!-- iterates over all types -->
-            <div jsselect="types.sort(sortByName)" class="apiItem">
+            <div jsselect="types.sort(sortByName)" jsdisplay="!($this.nodoc)"
+                 class="apiItem">
               <a jsvalues=".name:'type-' + id"></a>
               <h4 jscontent="id">type name</h4>
 
diff --git a/chrome/renderer/extensions/custom_bindings_util.cc b/chrome/renderer/extensions/custom_bindings_util.cc
index 08fb523..5d9277f 100644
--- a/chrome/renderer/extensions/custom_bindings_util.cc
+++ b/chrome/renderer/extensions/custom_bindings_util.cc
@@ -34,6 +34,7 @@
   static const char* kJavascriptFiles[] = {
     "extensions/browser_action_custom_bindings.js",
     "extensions/content_settings_custom_bindings.js",
+    "extensions/experimental.declarative_custom_bindings.js",
     "extensions/devtools_custom_bindings.js",
     "extensions/input.ime_custom_bindings.js",
     "extensions/omnibox_custom_bindings.js",
@@ -49,6 +50,7 @@
   static const int kResourceIDs[] = {
     IDR_BROWSER_ACTION_CUSTOM_BINDINGS_JS,
     IDR_CONTENT_SETTINGS_CUSTOM_BINDINGS_JS,
+    IDR_EXPERIMENTAL_DECLARATIVE_CUSTOM_BINDINGS_JS,
     IDR_DEVTOOLS_CUSTOM_BINDINGS_JS,
     IDR_INPUT_IME_CUSTOM_BINDINGS_JS,
     IDR_OMNIBOX_CUSTOM_BINDINGS_JS,
diff --git a/chrome/renderer/renderer_resources.grd b/chrome/renderer/renderer_resources.grd
index 18d5fde..ba9498f 100644
--- a/chrome/renderer/renderer_resources.grd
+++ b/chrome/renderer/renderer_resources.grd
@@ -32,6 +32,7 @@
       <include name="IDR_CONTENT_SETTINGS_CUSTOM_BINDINGS_JS" file="resources\extensions\content_settings_custom_bindings.js" type="BINDATA" />
       <include name="IDR_CONTEXT_MENUS_CUSTOM_BINDINGS_JS" file="resources\extensions\context_menus_custom_bindings.js" type="BINDATA" />
       <include name="IDR_DEVTOOLS_CUSTOM_BINDINGS_JS" file="resources\extensions\devtools_custom_bindings.js" type="BINDATA" />
+      <include name="IDR_EXPERIMENTAL_DECLARATIVE_CUSTOM_BINDINGS_JS" file="resources\extensions\experimental.declarative_custom_bindings.js" type="BINDATA" />
       <include name="IDR_EXPERIMENTAL_SOCKET_CUSTOM_BINDINGS_JS" file="resources\extensions\experimental.socket_custom_bindings.js" type="BINDATA" />
       <include name="IDR_EXTENSION_CUSTOM_BINDINGS_JS" file="resources\extensions\extension_custom_bindings.js" type="BINDATA" />
       <include name="IDR_FILE_BROWSER_HANDLER_CUSTOM_BINDINGS_JS" file="resources\extensions\file_browser_handler_custom_bindings.js" type="BINDATA" />
diff --git a/chrome/renderer/resources/extensions/event.js b/chrome/renderer/resources/extensions/event.js
index dffe201..b1b6967 100644
--- a/chrome/renderer/resources/extensions/event.js
+++ b/chrome/renderer/resources/extensions/event.js
@@ -51,16 +51,25 @@
 
   // Event object.  If opt_eventName is provided, this object represents
   // the unique instance of that named event, and dispatching an event
-  // with that name will route through this object's listeners.
+  // with that name will route through this object's listeners. Note that
+  // opt_eventName is required for events that support rules.
   //
   // Example:
   //   chrome.tabs.onChanged = new chrome.Event("tab-changed");
   //   chrome.tabs.onChanged.addListener(function(data) { alert(data); });
   //   chromeHidden.Event.dispatch("tab-changed", "hi");
   // will result in an alert dialog that says 'hi'.
-  chrome.Event = function(opt_eventName, opt_argSchemas) {
+  //
+  // If opt_eventOptions exists, it is a dictionary that contains the boolean
+  // entries "supportsListeners" and "supportsRules".
+  chrome.Event = function(opt_eventName, opt_argSchemas, opt_eventOptions) {
     this.eventName_ = opt_eventName;
     this.listeners_ = [];
+    this.eventOptions_ = opt_eventOptions ||
+        {"supportsListeners": true, "supportsRules": false};
+
+    if (this.eventOptions_.supportsRules && !opt_eventName)
+      throw new Error("Events that support rules require an event name.");
 
     // Validate event parameters if we are in debug.
     if (opt_argSchemas &&
@@ -75,6 +84,8 @@
                  exception;
         }
       };
+    } else {
+      this.validate_ = function() {}
     }
   };
 
@@ -127,6 +138,8 @@
 
   // Registers a callback to be called when this event is dispatched.
   chrome.Event.prototype.addListener = function(cb) {
+    if (!this.eventOptions_.supportsListeners)
+      throw new Error("This event does not support listeners.");
     if (this.listeners_.length == 0) {
       this.attach_();
     }
@@ -135,6 +148,8 @@
 
   // Unregisters a callback.
   chrome.Event.prototype.removeListener = function(cb) {
+    if (!this.eventOptions_.supportsListeners)
+      throw new Error("This event does not support listeners.");
     var idx = this.findListener_(cb);
     if (idx == -1) {
       return;
@@ -148,11 +163,15 @@
 
   // Test if the given callback is registered for this event.
   chrome.Event.prototype.hasListener = function(cb) {
+    if (!this.eventOptions_.supportsListeners)
+      throw new Error("This event does not support listeners.");
     return this.findListener_(cb) > -1;
   };
 
   // Test if any callbacks are registered for this event.
-  chrome.Event.prototype.hasListeners = function(cb) {
+  chrome.Event.prototype.hasListeners = function() {
+    if (!this.eventOptions_.supportsListeners)
+      throw new Error("This event does not support listeners.");
     return this.listeners_.length > 0;
   };
 
@@ -171,12 +190,12 @@
   // Dispatches this event object to all listeners, passing all supplied
   // arguments to this function each listener.
   chrome.Event.prototype.dispatch = function(varargs) {
+    if (!this.eventOptions_.supportsListeners)
+      throw new Error("This event does not support listeners.");
     var args = Array.prototype.slice.call(arguments);
-    if (this.validate_) {
-      var validationErrors = this.validate_(args);
-      if (validationErrors) {
-        return validationErrors;
-      }
+    var validationErrors = this.validate_(args);
+    if (validationErrors) {
+      return validationErrors;
     }
     for (var i = 0; i < this.listeners_.length; i++) {
       try {
@@ -227,6 +246,38 @@
     this.detach_();
   };
 
+  chrome.Event.prototype.addRules = function(rules, opt_cb) {
+    if (!this.eventOptions_.supportsRules)
+      throw new Error("This event does not support rules.");
+    if (!chrome.experimental || !chrome.experimental.declarative) {
+      throw new Error("You must have access to the experimental.declarative " +
+                      "API to support rules in events");
+    }
+    chrome.experimental.declarative.addRules(this.eventName_, rules, opt_cb);
+  }
+
+  chrome.Event.prototype.removeRules = function(ruleIdentifiers, opt_cb) {
+    if (!this.eventOptions_.supportsRules)
+      throw new Error("This event does not support rules.");
+    if (!chrome.experimental || !chrome.experimental.declarative) {
+      throw new Error("You must have access to the experimental.declarative " +
+                      "API to support rules in events");
+    }
+    chrome.experimental.declarative.removeRules(
+        this.eventName_, ruleIdentifiers, opt_cb);
+  }
+
+  chrome.Event.prototype.getRules = function(ruleIdentifiers, cb) {
+    if (!this.eventOptions_.supportsRules)
+      throw new Error("This event does not support rules.");
+    if (!chrome.experimental || !chrome.experimental.declarative) {
+      throw new Error("You must have access to the experimental.declarative " +
+                      "API to support rules in events");
+    }
+    chrome.experimental.declarative.getRules(
+        this.eventName_, ruleIdentifiers, cb);
+  }
+
   // Special load events: we don't use the DOM unload because that slows
   // down tab shutdown.  On the other hand, onUnload might not always fire,
   // since Chrome will terminate renderers on shutdown (SuddenTermination).
diff --git a/chrome/renderer/resources/extensions/experimental.declarative_custom_bindings.js b/chrome/renderer/resources/extensions/experimental.declarative_custom_bindings.js
new file mode 100644
index 0000000..029d9c3
--- /dev/null
+++ b/chrome/renderer/resources/extensions/experimental.declarative_custom_bindings.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.
+
+// Custom bindings for the declarative API.
+
+(function() {
+
+native function GetChromeHidden();
+
+var chromeHidden = GetChromeHidden();
+
+chromeHidden.registerCustomHook(
+    'experimental.declarative',
+    function(bindingsAPI) {
+  var apiFunctions = bindingsAPI.apiFunctions;
+  var sendRequest = bindingsAPI.sendRequest;
+  var apiDefinitions = bindingsAPI.apiDefinitions;
+  var cachedEventOptions = {};
+
+  function getEventOptions(qualifiedEventName) {
+    if (cachedEventOptions[qualifiedEventName])
+      return cachedEventOptions[qualifiedEventName];
+
+    // Parse qualifiedEventName into namespace and event name.
+    var lastSeparator = qualifiedEventName.lastIndexOf(".");
+    var eventName = qualifiedEventName.substr(lastSeparator + 1);
+    var namespace = qualifiedEventName.substr(0, lastSeparator);
+
+    // Lookup schema definition.
+    var filterNamespace = function(val) {return val.namespace === namespace;};
+    var apiSchema = apiDefinitions.filter(filterNamespace)[0];
+    var filterEventName = function (val) {return val.name === eventName;};
+    var eventSchema = apiSchema.events.filter(filterEventName)[0];
+
+    cachedEventOptions[qualifiedEventName] = eventSchema.options;
+    return eventSchema.options;
+  }
+
+  // Takes a list of JSON datatype identifiers and returns a schema fragment
+  // that verifies that a JSON object corresponds to an array of only these
+  // data types.
+  function buildArrayOfChoicesSchema(typesList) {
+    return {
+      "type": "array",
+      "items": {
+        "choices": typesList.map(function(el) {return {"$ref": el};})
+      }
+    };
+  }
+
+  // Validate conditions and actions against specific schemas of this
+  // event object type.
+  // |rules| is an array of JSON objects that follow the Rule type of the
+  // declarative extension APIs. |conditions| is an array of JSON type
+  // identifiers that are allowed to occur in the conditions attribute of each
+  // rule. Likewise, |actions| is an array of JSON type identifiers that are
+  // allowed to occur in the actions attribute of each rule.
+  function validateRules(rules, conditions, actions) {
+    var conditionsSchema = buildArrayOfChoicesSchema(conditions);
+    var actionsSchema = buildArrayOfChoicesSchema(actions);
+    rules.forEach(function(rule) {
+      chromeHidden.validate([rule.conditions], [conditionsSchema]);
+      chromeHidden.validate([rule.actions], [actionsSchema]);
+    })
+  }
+
+  apiFunctions.setHandleRequest("experimental.declarative.addRules",
+      function(eventName, rules, opt_callback) {
+    var eventOptions = getEventOptions(eventName);
+    if (!eventOptions.conditions || !eventOptions.actions) {
+      throw new Error("Event " + eventName + " misses conditions or " +
+                      "actions in the API specification.");
+    }
+    validateRules(rules,
+                  eventOptions.conditions,
+                  eventOptions.actions);
+    sendRequest(this.name, [eventName, rules, opt_callback],
+                this.definition.parameters);
+  });
+});
+
+})();
diff --git a/chrome/renderer/resources/extensions/schema_generated_bindings.js b/chrome/renderer/resources/extensions/schema_generated_bindings.js
index a935ff9..48fb43a 100644
--- a/chrome/renderer/resources/extensions/schema_generated_bindings.js
+++ b/chrome/renderer/resources/extensions/schema_generated_bindings.js
@@ -483,10 +483,11 @@
           var customEvent = customEvents[apiDef.namespace];
           if (customEvent) {
             module[eventDef.name] = new customEvent(
-                eventName, eventDef.parameters, eventDef.extraParameters);
+                eventName, eventDef.parameters, eventDef.extraParameters,
+                eventDef.options);
           } else {
             module[eventDef.name] = new chrome.Event(
-                eventName, eventDef.parameters);
+                eventName, eventDef.parameters, eventDef.options);
           }
         });
       }
@@ -566,6 +567,7 @@
         apiFunctions: apiFunctions,
         sendRequest: sendRequest,
         setIcon: setIcon,
+        apiDefinitions: apiDefinitions,
       }, extensionId);
     });
 
diff --git a/chrome/renderer/resources/extensions/web_request_custom_bindings.js b/chrome/renderer/resources/extensions/web_request_custom_bindings.js
index 6d28188..316ac07 100644
--- a/chrome/renderer/resources/extensions/web_request_custom_bindings.js
+++ b/chrome/renderer/resources/extensions/web_request_custom_bindings.js
@@ -21,7 +21,8 @@
 //   chrome.webRequest.onBeforeRequest.addListener(
 //       callback, {urls: "http://*.google.com/*"});
 //   ^ callback will only be called for onBeforeRequests matching the filter.
-function WebRequestEvent(eventName, opt_argSchemas, opt_extraArgSchemas) {
+function WebRequestEvent(eventName, opt_argSchemas, opt_extraArgSchemas,
+                         opt_eventOptions) {
   if (typeof eventName != "string")
     throw new Error("chrome.WebRequestEvent requires an event name.");
 
@@ -29,15 +30,25 @@
   this.argSchemas_ = opt_argSchemas;
   this.extraArgSchemas_ = opt_extraArgSchemas;
   this.subEvents_ = [];
+  this.eventOptions_ = opt_eventOptions ||
+      {"supportsListeners": true, "supportsRules": false};
+
+  if (this.eventOptions_.supportsRules)
+    this.eventForRules_ =
+        new chrome.Event(eventName, opt_argSchemas, opt_eventOptions);
 };
 
 // Test if the given callback is registered for this event.
 WebRequestEvent.prototype.hasListener = function(cb) {
+  if (!this.eventOptions_.supportsListeners)
+    throw new Error("This event does not support listeners.");
   return this.findListener_(cb) > -1;
 };
 
 // Test if any callbacks are registered fur thus event.
 WebRequestEvent.prototype.hasListeners = function() {
+  if (!this.eventOptions_.supportsListeners)
+    throw new Error("This event does not support listeners.");
   return this.subEvents_.length > 0;
 };
 
@@ -47,6 +58,8 @@
 // info is sent to the callback.
 WebRequestEvent.prototype.addListener =
     function(cb, opt_filter, opt_extraInfo) {
+  if (!this.eventOptions_.supportsListeners)
+    throw new Error("This event does not support listeners.");
   var subEventName = GetUniqueSubEventName(this.eventName_);
   // Note: this could fail to validate, in which case we would not add the
   // subEvent listener.
@@ -90,6 +103,8 @@
 
 // Unregisters a callback.
 WebRequestEvent.prototype.removeListener = function(cb) {
+  if (!this.eventOptions_.supportsListeners)
+    throw new Error("This event does not support listeners.");
   var idx;
   while ((idx = this.findListener_(cb)) >= 0) {
     var e = this.subEvents_[idx];
@@ -115,9 +130,27 @@
   return -1;
 };
 
-chromeHidden.registerCustomEvent('webRequest', WebRequestEvent);
+WebRequestEvent.prototype.addRules = function(rules, opt_cb) {
+  if (!this.eventOptions_.supportsRules)
+    throw new Error("This event does not support rules.");
+  this.eventForRules_.addRules(rules, opt_cb);
+}
 
-chromeHidden.registerCustomHook('webRequest', function(api) {
+WebRequestEvent.prototype.removeRules = function(ruleIdentifiers, opt_cb) {
+  if (!this.eventOptions_.supportsRules)
+    throw new Error("This event does not support rules.");
+  this.eventForRules_.removeRules(ruleIdentifiers, opt_cb);
+}
+
+WebRequestEvent.prototype.getRules = function(ruleIdentifiers, cb) {
+  if (!this.eventOptions_.supportsRules)
+    throw new Error("This event does not support rules.");
+  this.eventForRules_.getRules(ruleIdentifiers, cb);
+}
+
+chromeHidden.registerCustomEvent("webRequest", WebRequestEvent);
+
+chromeHidden.registerCustomHook("webRequest", function(api) {
   var apiFunctions = api.apiFunctions;
   var sendRequest = api.sendRequest;