| OLD | NEW | 
|---|
|  | (Empty) | 
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |  | 
| 2 // Use of this source code is governed by a BSD-style license that can be |  | 
| 3 // found in the LICENSE file. |  | 
| 4 |  | 
| 5   var eventNatives = requireNative('event_natives'); |  | 
| 6   var handleUncaughtException = require('uncaught_exception_handler').handle; |  | 
| 7   var logging = requireNative('logging'); |  | 
| 8   var schemaRegistry = requireNative('schema_registry'); |  | 
| 9   var sendRequest = require('sendRequest').sendRequest; |  | 
| 10   var utils = require('utils'); |  | 
| 11   var validate = require('schemaUtils').validate; |  | 
| 12   var unloadEvent = require('unload_event'); |  | 
| 13 |  | 
| 14   // Schemas for the rule-style functions on the events API that |  | 
| 15   // only need to be generated occasionally, so populate them lazily. |  | 
| 16   var ruleFunctionSchemas = { |  | 
| 17     // These values are set lazily: |  | 
| 18     // addRules: {}, |  | 
| 19     // getRules: {}, |  | 
| 20     // removeRules: {} |  | 
| 21   }; |  | 
| 22 |  | 
| 23   // This function ensures that |ruleFunctionSchemas| is populated. |  | 
| 24   function ensureRuleSchemasLoaded() { |  | 
| 25     if (ruleFunctionSchemas.addRules) |  | 
| 26       return; |  | 
| 27     var eventsSchema = schemaRegistry.GetSchema("events"); |  | 
| 28     var eventType = utils.lookup(eventsSchema.types, 'id', 'events.Event'); |  | 
| 29 |  | 
| 30     ruleFunctionSchemas.addRules = |  | 
| 31         utils.lookup(eventType.functions, 'name', 'addRules'); |  | 
| 32     ruleFunctionSchemas.getRules = |  | 
| 33         utils.lookup(eventType.functions, 'name', 'getRules'); |  | 
| 34     ruleFunctionSchemas.removeRules = |  | 
| 35         utils.lookup(eventType.functions, 'name', 'removeRules'); |  | 
| 36   } |  | 
| 37 |  | 
| 38   // A map of event names to the event object that is registered to that name. |  | 
| 39   var attachedNamedEvents = {}; |  | 
| 40 |  | 
| 41   // An array of all attached event objects, used for detaching on unload. |  | 
| 42   var allAttachedEvents = []; |  | 
| 43 |  | 
| 44   // A map of functions that massage event arguments before they are dispatched. |  | 
| 45   // Key is event name, value is function. |  | 
| 46   var eventArgumentMassagers = {}; |  | 
| 47 |  | 
| 48   // An attachment strategy for events that aren't attached to the browser. |  | 
| 49   // This applies to events with the "unmanaged" option and events without |  | 
| 50   // names. |  | 
| 51   var NullAttachmentStrategy = function(event) { |  | 
| 52     this.event_ = event; |  | 
| 53   }; |  | 
| 54   NullAttachmentStrategy.prototype.onAddedListener = |  | 
| 55       function(listener) { |  | 
| 56   }; |  | 
| 57   NullAttachmentStrategy.prototype.onRemovedListener = |  | 
| 58       function(listener) { |  | 
| 59   }; |  | 
| 60   NullAttachmentStrategy.prototype.detach = function(manual) { |  | 
| 61   }; |  | 
| 62   NullAttachmentStrategy.prototype.getListenersByIDs = function(ids) { |  | 
| 63     // |ids| is for filtered events only. |  | 
| 64     return this.event_.listeners; |  | 
| 65   }; |  | 
| 66 |  | 
| 67   // Handles adding/removing/dispatching listeners for unfiltered events. |  | 
| 68   var UnfilteredAttachmentStrategy = function(event) { |  | 
| 69     this.event_ = event; |  | 
| 70   }; |  | 
| 71 |  | 
| 72   UnfilteredAttachmentStrategy.prototype.onAddedListener = |  | 
| 73       function(listener) { |  | 
| 74     // Only attach / detach on the first / last listener removed. |  | 
| 75     if (this.event_.listeners.length == 0) |  | 
| 76       eventNatives.AttachEvent(this.event_.eventName); |  | 
| 77   }; |  | 
| 78 |  | 
| 79   UnfilteredAttachmentStrategy.prototype.onRemovedListener = |  | 
| 80       function(listener) { |  | 
| 81     if (this.event_.listeners.length == 0) |  | 
| 82       this.detach(true); |  | 
| 83   }; |  | 
| 84 |  | 
| 85   UnfilteredAttachmentStrategy.prototype.detach = function(manual) { |  | 
| 86     eventNatives.DetachEvent(this.event_.eventName, manual); |  | 
| 87   }; |  | 
| 88 |  | 
| 89   UnfilteredAttachmentStrategy.prototype.getListenersByIDs = function(ids) { |  | 
| 90     // |ids| is for filtered events only. |  | 
| 91     return this.event_.listeners; |  | 
| 92   }; |  | 
| 93 |  | 
| 94   var FilteredAttachmentStrategy = function(event) { |  | 
| 95     this.event_ = event; |  | 
| 96     this.listenerMap_ = {}; |  | 
| 97   }; |  | 
| 98 |  | 
| 99   FilteredAttachmentStrategy.idToEventMap = {}; |  | 
| 100 |  | 
| 101   FilteredAttachmentStrategy.prototype.onAddedListener = function(listener) { |  | 
| 102     var id = eventNatives.AttachFilteredEvent(this.event_.eventName, |  | 
| 103                                               listener.filters || {}); |  | 
| 104     if (id == -1) |  | 
| 105       throw new Error("Can't add listener"); |  | 
| 106     listener.id = id; |  | 
| 107     this.listenerMap_[id] = listener; |  | 
| 108     FilteredAttachmentStrategy.idToEventMap[id] = this.event_; |  | 
| 109   }; |  | 
| 110 |  | 
| 111   FilteredAttachmentStrategy.prototype.onRemovedListener = function(listener) { |  | 
| 112     this.detachListener(listener, true); |  | 
| 113   }; |  | 
| 114 |  | 
| 115   FilteredAttachmentStrategy.prototype.detachListener = |  | 
| 116       function(listener, manual) { |  | 
| 117     if (listener.id == undefined) |  | 
| 118       throw new Error("listener.id undefined - '" + listener + "'"); |  | 
| 119     var id = listener.id; |  | 
| 120     delete this.listenerMap_[id]; |  | 
| 121     delete FilteredAttachmentStrategy.idToEventMap[id]; |  | 
| 122     eventNatives.DetachFilteredEvent(id, manual); |  | 
| 123   }; |  | 
| 124 |  | 
| 125   FilteredAttachmentStrategy.prototype.detach = function(manual) { |  | 
| 126     for (var i in this.listenerMap_) |  | 
| 127       this.detachListener(this.listenerMap_[i], manual); |  | 
| 128   }; |  | 
| 129 |  | 
| 130   FilteredAttachmentStrategy.prototype.getListenersByIDs = function(ids) { |  | 
| 131     var result = []; |  | 
| 132     for (var i = 0; i < ids.length; i++) |  | 
| 133       $Array.push(result, this.listenerMap_[ids[i]]); |  | 
| 134     return result; |  | 
| 135   }; |  | 
| 136 |  | 
| 137   function parseEventOptions(opt_eventOptions) { |  | 
| 138     function merge(dest, src) { |  | 
| 139       for (var k in src) { |  | 
| 140         if (!$Object.hasOwnProperty(dest, k)) { |  | 
| 141           dest[k] = src[k]; |  | 
| 142         } |  | 
| 143       } |  | 
| 144     } |  | 
| 145 |  | 
| 146     var options = opt_eventOptions || {}; |  | 
| 147     merge(options, { |  | 
| 148       // Event supports adding listeners with filters ("filtered events"), for |  | 
| 149       // example as used in the webNavigation API. |  | 
| 150       // |  | 
| 151       // event.addListener(listener, [filter1, filter2]); |  | 
| 152       supportsFilters: false, |  | 
| 153 |  | 
| 154       // Events supports vanilla events. Most APIs use these. |  | 
| 155       // |  | 
| 156       // event.addListener(listener); |  | 
| 157       supportsListeners: true, |  | 
| 158 |  | 
| 159       // Event supports adding rules ("declarative events") rather than |  | 
| 160       // listeners, for example as used in the declarativeWebRequest API. |  | 
| 161       // |  | 
| 162       // event.addRules([rule1, rule2]); |  | 
| 163       supportsRules: false, |  | 
| 164 |  | 
| 165       // Event is unmanaged in that the browser has no knowledge of its |  | 
| 166       // existence; it's never invoked, doesn't keep the renderer alive, and |  | 
| 167       // the bindings system has no knowledge of it. |  | 
| 168       // |  | 
| 169       // Both events created by user code (new chrome.Event()) and messaging |  | 
| 170       // events are unmanaged, though in the latter case the browser *does* |  | 
| 171       // interact indirectly with them via IPCs written by hand. |  | 
| 172       unmanaged: false, |  | 
| 173     }); |  | 
| 174     return options; |  | 
| 175   }; |  | 
| 176 |  | 
| 177   // Event object.  If opt_eventName is provided, this object represents |  | 
| 178   // the unique instance of that named event, and dispatching an event |  | 
| 179   // with that name will route through this object's listeners. Note that |  | 
| 180   // opt_eventName is required for events that support rules. |  | 
| 181   // |  | 
| 182   // Example: |  | 
| 183   //   var Event = require('event_bindings').Event; |  | 
| 184   //   chrome.tabs.onChanged = new Event("tab-changed"); |  | 
| 185   //   chrome.tabs.onChanged.addListener(function(data) { alert(data); }); |  | 
| 186   //   Event.dispatch("tab-changed", "hi"); |  | 
| 187   // will result in an alert dialog that says 'hi'. |  | 
| 188   // |  | 
| 189   // If opt_eventOptions exists, it is a dictionary that contains the boolean |  | 
| 190   // entries "supportsListeners" and "supportsRules". |  | 
| 191   // If opt_webViewInstanceId exists, it is an integer uniquely identifying a |  | 
| 192   // <webview> tag within the embedder. If it does not exist, then this is an |  | 
| 193   // extension event rather than a <webview> event. |  | 
| 194   var EventImpl = function(opt_eventName, opt_argSchemas, opt_eventOptions, |  | 
| 195                            opt_webViewInstanceId) { |  | 
| 196     this.eventName = opt_eventName; |  | 
| 197     this.argSchemas = opt_argSchemas; |  | 
| 198     this.listeners = []; |  | 
| 199     this.eventOptions = parseEventOptions(opt_eventOptions); |  | 
| 200     this.webViewInstanceId = opt_webViewInstanceId || 0; |  | 
| 201 |  | 
| 202     if (!this.eventName) { |  | 
| 203       if (this.eventOptions.supportsRules) |  | 
| 204         throw new Error("Events that support rules require an event name."); |  | 
| 205       // Events without names cannot be managed by the browser by definition |  | 
| 206       // (the browser has no way of identifying them). |  | 
| 207       this.eventOptions.unmanaged = true; |  | 
| 208     } |  | 
| 209 |  | 
| 210     // Track whether the event has been destroyed to help track down the cause |  | 
| 211     // of http://crbug.com/258526. |  | 
| 212     // This variable will eventually hold the stack trace of the destroy call. |  | 
| 213     // TODO(kalman): Delete this and replace with more sound logic that catches |  | 
| 214     // when events are used without being *attached*. |  | 
| 215     this.destroyed = null; |  | 
| 216 |  | 
| 217     if (this.eventOptions.unmanaged) |  | 
| 218       this.attachmentStrategy = new NullAttachmentStrategy(this); |  | 
| 219     else if (this.eventOptions.supportsFilters) |  | 
| 220       this.attachmentStrategy = new FilteredAttachmentStrategy(this); |  | 
| 221     else |  | 
| 222       this.attachmentStrategy = new UnfilteredAttachmentStrategy(this); |  | 
| 223   }; |  | 
| 224 |  | 
| 225   // callback is a function(args, dispatch). args are the args we receive from |  | 
| 226   // dispatchEvent(), and dispatch is a function(args) that dispatches args to |  | 
| 227   // its listeners. |  | 
| 228   function registerArgumentMassager(name, callback) { |  | 
| 229     if (eventArgumentMassagers[name]) |  | 
| 230       throw new Error("Massager already registered for event: " + name); |  | 
| 231     eventArgumentMassagers[name] = callback; |  | 
| 232   } |  | 
| 233 |  | 
| 234   // Dispatches a named event with the given argument array. The args array is |  | 
| 235   // the list of arguments that will be sent to the event callback. |  | 
| 236   function dispatchEvent(name, args, filteringInfo) { |  | 
| 237     var listenerIDs = []; |  | 
| 238 |  | 
| 239     if (filteringInfo) |  | 
| 240       listenerIDs = eventNatives.MatchAgainstEventFilter(name, filteringInfo); |  | 
| 241 |  | 
| 242     var event = attachedNamedEvents[name]; |  | 
| 243     if (!event) |  | 
| 244       return; |  | 
| 245 |  | 
| 246     var dispatchArgs = function(args) { |  | 
| 247       var result = event.dispatch_(args, listenerIDs); |  | 
| 248       if (result) |  | 
| 249         logging.DCHECK(!result.validationErrors, result.validationErrors); |  | 
| 250       return result; |  | 
| 251     }; |  | 
| 252 |  | 
| 253     if (eventArgumentMassagers[name]) |  | 
| 254       eventArgumentMassagers[name](args, dispatchArgs); |  | 
| 255     else |  | 
| 256       dispatchArgs(args); |  | 
| 257   } |  | 
| 258 |  | 
| 259   // Registers a callback to be called when this event is dispatched. |  | 
| 260   EventImpl.prototype.addListener = function(cb, filters) { |  | 
| 261     if (!this.eventOptions.supportsListeners) |  | 
| 262       throw new Error("This event does not support listeners."); |  | 
| 263     if (this.eventOptions.maxListeners && |  | 
| 264         this.getListenerCount_() >= this.eventOptions.maxListeners) { |  | 
| 265       throw new Error("Too many listeners for " + this.eventName); |  | 
| 266     } |  | 
| 267     if (filters) { |  | 
| 268       if (!this.eventOptions.supportsFilters) |  | 
| 269         throw new Error("This event does not support filters."); |  | 
| 270       if (filters.url && !(filters.url instanceof Array)) |  | 
| 271         throw new Error("filters.url should be an array."); |  | 
| 272       if (filters.serviceType && |  | 
| 273           !(typeof filters.serviceType === 'string')) { |  | 
| 274         throw new Error("filters.serviceType should be a string.") |  | 
| 275       } |  | 
| 276     } |  | 
| 277     var listener = {callback: cb, filters: filters}; |  | 
| 278     this.attach_(listener); |  | 
| 279     $Array.push(this.listeners, listener); |  | 
| 280   }; |  | 
| 281 |  | 
| 282   EventImpl.prototype.attach_ = function(listener) { |  | 
| 283     this.attachmentStrategy.onAddedListener(listener); |  | 
| 284 |  | 
| 285     if (this.listeners.length == 0) { |  | 
| 286       allAttachedEvents[allAttachedEvents.length] = this; |  | 
| 287       if (this.eventName) { |  | 
| 288         if (attachedNamedEvents[this.eventName]) { |  | 
| 289           throw new Error("Event '" + this.eventName + |  | 
| 290                           "' is already attached."); |  | 
| 291         } |  | 
| 292         attachedNamedEvents[this.eventName] = this; |  | 
| 293       } |  | 
| 294     } |  | 
| 295   }; |  | 
| 296 |  | 
| 297   // Unregisters a callback. |  | 
| 298   EventImpl.prototype.removeListener = function(cb) { |  | 
| 299     if (!this.eventOptions.supportsListeners) |  | 
| 300       throw new Error("This event does not support listeners."); |  | 
| 301 |  | 
| 302     var idx = this.findListener_(cb); |  | 
| 303     if (idx == -1) |  | 
| 304       return; |  | 
| 305 |  | 
| 306     var removedListener = $Array.splice(this.listeners, idx, 1)[0]; |  | 
| 307     this.attachmentStrategy.onRemovedListener(removedListener); |  | 
| 308 |  | 
| 309     if (this.listeners.length == 0) { |  | 
| 310       var i = $Array.indexOf(allAttachedEvents, this); |  | 
| 311       if (i >= 0) |  | 
| 312         delete allAttachedEvents[i]; |  | 
| 313       if (this.eventName) { |  | 
| 314         if (!attachedNamedEvents[this.eventName]) { |  | 
| 315           throw new Error( |  | 
| 316               "Event '" + this.eventName + "' is not attached."); |  | 
| 317         } |  | 
| 318         delete attachedNamedEvents[this.eventName]; |  | 
| 319       } |  | 
| 320     } |  | 
| 321   }; |  | 
| 322 |  | 
| 323   // Test if the given callback is registered for this event. |  | 
| 324   EventImpl.prototype.hasListener = function(cb) { |  | 
| 325     if (!this.eventOptions.supportsListeners) |  | 
| 326       throw new Error("This event does not support listeners."); |  | 
| 327     return this.findListener_(cb) > -1; |  | 
| 328   }; |  | 
| 329 |  | 
| 330   // Test if any callbacks are registered for this event. |  | 
| 331   EventImpl.prototype.hasListeners = function() { |  | 
| 332     return this.getListenerCount_() > 0; |  | 
| 333   }; |  | 
| 334 |  | 
| 335   // Returns the number of listeners on this event. |  | 
| 336   EventImpl.prototype.getListenerCount_ = function() { |  | 
| 337     if (!this.eventOptions.supportsListeners) |  | 
| 338       throw new Error("This event does not support listeners."); |  | 
| 339     return this.listeners.length; |  | 
| 340   }; |  | 
| 341 |  | 
| 342   // Returns the index of the given callback if registered, or -1 if not |  | 
| 343   // found. |  | 
| 344   EventImpl.prototype.findListener_ = function(cb) { |  | 
| 345     for (var i = 0; i < this.listeners.length; i++) { |  | 
| 346       if (this.listeners[i].callback == cb) { |  | 
| 347         return i; |  | 
| 348       } |  | 
| 349     } |  | 
| 350 |  | 
| 351     return -1; |  | 
| 352   }; |  | 
| 353 |  | 
| 354   EventImpl.prototype.dispatch_ = function(args, listenerIDs) { |  | 
| 355     if (this.destroyed) { |  | 
| 356       throw new Error(this.eventName + ' was already destroyed at: ' + |  | 
| 357                       this.destroyed); |  | 
| 358     } |  | 
| 359     if (!this.eventOptions.supportsListeners) |  | 
| 360       throw new Error("This event does not support listeners."); |  | 
| 361 |  | 
| 362     if (this.argSchemas && logging.DCHECK_IS_ON()) { |  | 
| 363       try { |  | 
| 364         validate(args, this.argSchemas); |  | 
| 365       } catch (e) { |  | 
| 366         e.message += ' in ' + this.eventName; |  | 
| 367         throw e; |  | 
| 368       } |  | 
| 369     } |  | 
| 370 |  | 
| 371     // Make a copy of the listeners in case the listener list is modified |  | 
| 372     // while dispatching the event. |  | 
| 373     var listeners = $Array.slice( |  | 
| 374         this.attachmentStrategy.getListenersByIDs(listenerIDs)); |  | 
| 375 |  | 
| 376     var results = []; |  | 
| 377     for (var i = 0; i < listeners.length; i++) { |  | 
| 378       try { |  | 
| 379         var result = this.wrapper.dispatchToListener(listeners[i].callback, |  | 
| 380                                                      args); |  | 
| 381         if (result !== undefined) |  | 
| 382           $Array.push(results, result); |  | 
| 383       } catch (e) { |  | 
| 384         handleUncaughtException( |  | 
| 385           'Error in event handler for ' + |  | 
| 386               (this.eventName ? this.eventName : '(unknown)') + |  | 
| 387               ': ' + e.message + '\nStack trace: ' + e.stack, |  | 
| 388           e); |  | 
| 389       } |  | 
| 390     } |  | 
| 391     if (results.length) |  | 
| 392       return {results: results}; |  | 
| 393   } |  | 
| 394 |  | 
| 395   // Can be overridden to support custom dispatching. |  | 
| 396   EventImpl.prototype.dispatchToListener = function(callback, args) { |  | 
| 397     return $Function.apply(callback, null, args); |  | 
| 398   } |  | 
| 399 |  | 
| 400   // Dispatches this event object to all listeners, passing all supplied |  | 
| 401   // arguments to this function each listener. |  | 
| 402   EventImpl.prototype.dispatch = function(varargs) { |  | 
| 403     return this.dispatch_($Array.slice(arguments), undefined); |  | 
| 404   }; |  | 
| 405 |  | 
| 406   // Detaches this event object from its name. |  | 
| 407   EventImpl.prototype.detach_ = function() { |  | 
| 408     this.attachmentStrategy.detach(false); |  | 
| 409   }; |  | 
| 410 |  | 
| 411   EventImpl.prototype.destroy_ = function() { |  | 
| 412     this.listeners.length = 0; |  | 
| 413     this.detach_(); |  | 
| 414     this.destroyed = new Error().stack; |  | 
| 415   }; |  | 
| 416 |  | 
| 417   EventImpl.prototype.addRules = function(rules, opt_cb) { |  | 
| 418     if (!this.eventOptions.supportsRules) |  | 
| 419       throw new Error("This event does not support rules."); |  | 
| 420 |  | 
| 421     // Takes a list of JSON datatype identifiers and returns a schema fragment |  | 
| 422     // that verifies that a JSON object corresponds to an array of only these |  | 
| 423     // data types. |  | 
| 424     function buildArrayOfChoicesSchema(typesList) { |  | 
| 425       return { |  | 
| 426         'type': 'array', |  | 
| 427         'items': { |  | 
| 428           'choices': typesList.map(function(el) {return {'$ref': el};}) |  | 
| 429         } |  | 
| 430       }; |  | 
| 431     }; |  | 
| 432 |  | 
| 433     // Validate conditions and actions against specific schemas of this |  | 
| 434     // event object type. |  | 
| 435     // |rules| is an array of JSON objects that follow the Rule type of the |  | 
| 436     // declarative extension APIs. |conditions| is an array of JSON type |  | 
| 437     // identifiers that are allowed to occur in the conditions attribute of each |  | 
| 438     // rule. Likewise, |actions| is an array of JSON type identifiers that are |  | 
| 439     // allowed to occur in the actions attribute of each rule. |  | 
| 440     function validateRules(rules, conditions, actions) { |  | 
| 441       var conditionsSchema = buildArrayOfChoicesSchema(conditions); |  | 
| 442       var actionsSchema = buildArrayOfChoicesSchema(actions); |  | 
| 443       $Array.forEach(rules, function(rule) { |  | 
| 444         validate([rule.conditions], [conditionsSchema]); |  | 
| 445         validate([rule.actions], [actionsSchema]); |  | 
| 446       }); |  | 
| 447     }; |  | 
| 448 |  | 
| 449     if (!this.eventOptions.conditions || !this.eventOptions.actions) { |  | 
| 450       throw new Error('Event ' + this.eventName + ' misses ' + |  | 
| 451                       'conditions or actions in the API specification.'); |  | 
| 452     } |  | 
| 453 |  | 
| 454     validateRules(rules, |  | 
| 455                   this.eventOptions.conditions, |  | 
| 456                   this.eventOptions.actions); |  | 
| 457 |  | 
| 458     ensureRuleSchemasLoaded(); |  | 
| 459     // We remove the first parameter from the validation to give the user more |  | 
| 460     // meaningful error messages. |  | 
| 461     validate([this.webViewInstanceId, rules, opt_cb], |  | 
| 462              $Array.splice( |  | 
| 463                  $Array.slice(ruleFunctionSchemas.addRules.parameters), 1)); |  | 
| 464     sendRequest( |  | 
| 465       "events.addRules", |  | 
| 466       [this.eventName, this.webViewInstanceId, rules,  opt_cb], |  | 
| 467       ruleFunctionSchemas.addRules.parameters); |  | 
| 468   } |  | 
| 469 |  | 
| 470   EventImpl.prototype.removeRules = function(ruleIdentifiers, opt_cb) { |  | 
| 471     if (!this.eventOptions.supportsRules) |  | 
| 472       throw new Error("This event does not support rules."); |  | 
| 473     ensureRuleSchemasLoaded(); |  | 
| 474     // We remove the first parameter from the validation to give the user more |  | 
| 475     // meaningful error messages. |  | 
| 476     validate([this.webViewInstanceId, ruleIdentifiers, opt_cb], |  | 
| 477              $Array.splice( |  | 
| 478                  $Array.slice(ruleFunctionSchemas.removeRules.parameters), 1)); |  | 
| 479     sendRequest("events.removeRules", |  | 
| 480                 [this.eventName, |  | 
| 481                  this.webViewInstanceId, |  | 
| 482                  ruleIdentifiers, |  | 
| 483                  opt_cb], |  | 
| 484                 ruleFunctionSchemas.removeRules.parameters); |  | 
| 485   } |  | 
| 486 |  | 
| 487   EventImpl.prototype.getRules = function(ruleIdentifiers, cb) { |  | 
| 488     if (!this.eventOptions.supportsRules) |  | 
| 489       throw new Error("This event does not support rules."); |  | 
| 490     ensureRuleSchemasLoaded(); |  | 
| 491     // We remove the first parameter from the validation to give the user more |  | 
| 492     // meaningful error messages. |  | 
| 493     validate([this.webViewInstanceId, ruleIdentifiers, cb], |  | 
| 494              $Array.splice( |  | 
| 495                  $Array.slice(ruleFunctionSchemas.getRules.parameters), 1)); |  | 
| 496 |  | 
| 497     sendRequest( |  | 
| 498       "events.getRules", |  | 
| 499       [this.eventName, this.webViewInstanceId, ruleIdentifiers, cb], |  | 
| 500       ruleFunctionSchemas.getRules.parameters); |  | 
| 501   } |  | 
| 502 |  | 
| 503   unloadEvent.addListener(function() { |  | 
| 504     for (var i = 0; i < allAttachedEvents.length; ++i) { |  | 
| 505       var event = allAttachedEvents[i]; |  | 
| 506       if (event) |  | 
| 507         event.detach_(); |  | 
| 508     } |  | 
| 509   }); |  | 
| 510 |  | 
| 511   var Event = utils.expose('Event', EventImpl, { functions: [ |  | 
| 512     'addListener', |  | 
| 513     'removeListener', |  | 
| 514     'hasListener', |  | 
| 515     'hasListeners', |  | 
| 516     'dispatchToListener', |  | 
| 517     'dispatch', |  | 
| 518     'addRules', |  | 
| 519     'removeRules', |  | 
| 520     'getRules' |  | 
| 521   ] }); |  | 
| 522 |  | 
| 523   // NOTE: Event is (lazily) exposed as chrome.Event from dispatcher.cc. |  | 
| 524   exports.Event = Event; |  | 
| 525 |  | 
| 526   exports.dispatchEvent = dispatchEvent; |  | 
| 527   exports.parseEventOptions = parseEventOptions; |  | 
| 528   exports.registerArgumentMassager = registerArgumentMassager; |  | 
| OLD | NEW | 
|---|