Chromium Code Reviews| Index: chrome/renderer/resources/extensions/event.js |
| diff --git a/chrome/renderer/resources/extensions/event.js b/chrome/renderer/resources/extensions/event.js |
| index a6d636342eea978f3568208f7345f8b763559306..30c4c2d2450228928c35761c79a29a41470d896d 100644 |
| --- a/chrome/renderer/resources/extensions/event.js |
| +++ b/chrome/renderer/resources/extensions/event.js |
| @@ -5,6 +5,9 @@ |
| var eventBindingsNatives = requireNative('event_bindings'); |
| var AttachEvent = eventBindingsNatives.AttachEvent; |
| var DetachEvent = eventBindingsNatives.DetachEvent; |
| + var AttachFilteredEvent = eventBindingsNatives.AttachFilteredEvent; |
| + var DetachFilteredEvent = eventBindingsNatives.DetachFilteredEvent; |
| + var MatchAgainstEventFilter = eventBindingsNatives.MatchAgainstEventFilter; |
| var sendRequest = require('sendRequest').sendRequest; |
| var utils = require('utils'); |
| var validate = require('schemaUtils').validate; |
| @@ -75,6 +78,78 @@ |
| }; |
| })(); |
| + // Handles adding/removing/dispatching listeners for unfiltered events. |
| + var UnfilteredAttachmentStrategy = function(event) { |
| + this.event_ = event; |
| + }; |
| + |
| + UnfilteredAttachmentStrategy.prototype.onAddedListener = |
| + function(listener) { |
| + // Only attach / detach on the first / last listener removed. |
| + if (this.event_.listeners_.length == 0) |
| + AttachEvent(this.event_.eventName_); |
| + }; |
| + |
| + UnfilteredAttachmentStrategy.prototype.onRemovedListener = |
| + function(listener) { |
| + if (this.event_.listeners_.length == 0) |
| + this.detach(true); |
| + }; |
| + |
| + UnfilteredAttachmentStrategy.prototype.detach = function(manual) { |
| + var i = allAttachedEvents.indexOf(this.event_); |
|
battre
2012/06/20 09:50:46
where is this declared?
koz (OOO until 15th September)
2012/06/21 02:23:11
It was declared lower down, but I've moved above t
|
| + if (i >= 0) |
| + delete allAttachedEvents[i]; |
| + DetachEvent(this.event_.eventName_, manual); |
| + }; |
| + |
| + UnfilteredAttachmentStrategy.prototype.getListenersByIDs = function(ids) { |
| + return this.event_.listeners_; |
| + }; |
| + |
| + var FilteredAttachmentStrategy = function(event) { |
| + this.event_ = event; |
| + this.listenerMap_ = {}; |
| + }; |
| + |
| + FilteredAttachmentStrategy.idToEventMap = {}; |
| + |
| + FilteredAttachmentStrategy.prototype.onAddedListener = function(listener) { |
| + var id = AttachFilteredEvent(this.event_.eventName_, |
| + listener.filters || {}); |
| + if (id == -1) |
| + throw new Error("Can't add listener"); |
| + listener.id = id; |
| + this.listenerMap_[id] = listener; |
| + FilteredAttachmentStrategy.idToEventMap[id] = this.event_; |
| + }; |
| + |
| + FilteredAttachmentStrategy.prototype.onRemovedListener = function(listener) { |
| + this.detachListener(listener, true); |
| + }; |
| + |
| + FilteredAttachmentStrategy.prototype.detachListener = |
| + function(listener, manual) { |
| + if (listener.id == undefined) |
| + throw new Error("listener.id undefined - '" + listener + "'"); |
| + var id = listener.id; |
| + delete this.listenerMap_[id]; |
| + delete FilteredAttachmentStrategy.idToEventMap[id]; |
| + DetachFilteredEvent(id, manual); |
| + }; |
| + |
| + FilteredAttachmentStrategy.prototype.detach = function(manual) { |
| + for (var i in this.listenerMap_) |
| + this.detachListener(this.listenerMap_[i], manual); |
| + }; |
| + |
| + FilteredAttachmentStrategy.prototype.getListenersByIDs = function(ids) { |
| + var result = []; |
| + for (var i = 0; i < ids.length; i++) |
| + result.push(this.listenerMap_[ids[i]]); |
| + return result; |
| + }; |
| + |
| // 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. Note that |
| @@ -92,11 +167,20 @@ |
| this.eventName_ = opt_eventName; |
| this.listeners_ = []; |
| this.eventOptions_ = opt_eventOptions || |
| - {"supportsListeners": true, "supportsRules": false}; |
| + {supportsFilters: false, |
| + supportsListeners: true, |
| + supportsRules: false, |
| + }; |
| if (this.eventOptions_.supportsRules && !opt_eventName) |
| throw new Error("Events that support rules require an event name."); |
| + if (this.eventOptions_.supportsFilters) { |
| + this.attachmentStrategy_ = new FilteredAttachmentStrategy(this); |
| + } else { |
| + this.attachmentStrategy_ = new UnfilteredAttachmentStrategy(this); |
| + } |
| + |
| // Validate event arguments (the data that is passed to the callbacks) |
| // if we are in debug. |
| if (opt_argSchemas && |
| @@ -136,7 +220,12 @@ |
| // Dispatches a named event with the given JSON array, which is deserialized |
| // before dispatch. The JSON array is the list of arguments that will be |
| // sent with the event callback. |
| - chromeHidden.Event.dispatchJSON = function(name, args) { |
| + chromeHidden.Event.dispatchJSON = function(name, args, filteringInfo) { |
| + var listenerIDs; |
| + |
| + if (filteringInfo) { |
| + listenerIDs = MatchAgainstEventFilter(name, filteringInfo); |
|
battre
2012/06/20 09:50:46
would it make sense to change this logic such that
koz (OOO until 15th September)
2012/06/21 02:23:11
Yes, that would work, but it wouldn't save a round
battre
2012/06/21 05:35:16
SGTM
|
| + } |
| if (attachedNamedEvents[name]) { |
| if (args) { |
| // TODO(asargent): This is an antiquity. Until all callers of |
| @@ -148,8 +237,7 @@ |
| if (eventArgumentMassagers[name]) |
| eventArgumentMassagers[name](args); |
| } |
| - var result = attachedNamedEvents[name].dispatch.apply( |
| - attachedNamedEvents[name], args); |
| + var result = attachedNamedEvents[name].dispatch_(args, listenerIDs); |
| if (result && result.validationErrors) |
| return result.validationErrors; |
| } |
| @@ -170,13 +258,34 @@ |
| }; |
| // Registers a callback to be called when this event is dispatched. |
| - chrome.Event.prototype.addListener = function(cb) { |
| + chrome.Event.prototype.addListener = function(cb, filters) { |
| if (!this.eventOptions_.supportsListeners) |
| throw new Error("This event does not support listeners."); |
| + if (filters) { |
| + if (!this.eventOptions_.supportsFilters) |
| + throw new Error("This event does not support filters."); |
| + if (filters.url && !(filters.url instanceof Array)) |
| + throw new Error("filters.url should be an array"); |
| + } |
| + var listener = {callback: cb, filters: filters}; |
| + this.attach_(listener); |
| + this.listeners_.push(listener); |
| + }; |
| + |
| + chrome.Event.prototype.attach_ = function(listener) { |
| + this.attachmentStrategy_.onAddedListener(listener); |
| if (this.listeners_.length == 0) { |
| - this.attach_(); |
| + allAttachedEvents[allAttachedEvents.length] = this; |
|
battre
2012/06/20 09:50:46
Is my understanding correct, that this adds the Ev
koz (OOO until 15th September)
2012/06/21 02:23:11
Nice spot, this code is really hairy. Done.
|
| + if (!this.eventName_) |
| + return; |
| + |
| + if (attachedNamedEvents[this.eventName_]) { |
| + throw new Error("chrome.Event '" + this.eventName_ + |
| + "' is already attached."); |
| + } |
| + |
| + attachedNamedEvents[this.eventName_] = this; |
| } |
| - this.listeners_.push(cb); |
| }; |
| // Unregisters a callback. |
| @@ -188,9 +297,19 @@ |
| return; |
| } |
| - this.listeners_.splice(idx, 1); |
| + var removedListener = this.listeners_.splice(idx, 1)[0]; |
| + this.attachmentStrategy_.onRemovedListener(removedListener); |
| + |
| if (this.listeners_.length == 0) { |
| - this.detach_(true); |
| + if (!this.eventName_) |
| + return; |
| + |
| + if (!attachedNamedEvents[this.eventName_]) { |
| + throw new Error("chrome.Event '" + this.eventName_ + |
| + "' is not attached."); |
| + } |
| + |
| + delete attachedNamedEvents[this.eventName_]; |
| } |
| }; |
| @@ -212,7 +331,7 @@ |
| // found. |
| chrome.Event.prototype.findListener_ = function(cb) { |
| for (var i = 0; i < this.listeners_.length; i++) { |
| - if (this.listeners_[i] == cb) { |
| + if (this.listeners_[i].callback == cb) { |
| return i; |
| } |
| } |
| @@ -220,21 +339,21 @@ |
| return -1; |
| }; |
| - // Dispatches this event object to all listeners, passing all supplied |
| - // arguments to this function each listener. |
| - chrome.Event.prototype.dispatch = function(varargs) { |
| + chrome.Event.prototype.dispatch_ = function(args, listenerIDs) { |
| if (!this.eventOptions_.supportsListeners) |
| throw new Error("This event does not support listeners."); |
| - var args = Array.prototype.slice.call(arguments); |
| var validationErrors = this.validateEventArgs_(args); |
| if (validationErrors) { |
| console.error(validationErrors); |
| return {validationErrors: validationErrors}; |
| } |
| + |
| + var listeners = this.attachmentStrategy_.getListenersByIDs(listenerIDs); |
| + |
| var results = []; |
| - for (var i = 0; i < this.listeners_.length; i++) { |
| + for (var i = 0; i < listeners.length; i++) { |
| try { |
| - var result = this.listeners_[i].apply(null, args); |
| + var result = listeners[i].callback.apply(null, args); |
| if (result !== undefined) |
| results.push(result); |
| } catch (e) { |
| @@ -244,39 +363,17 @@ |
| } |
| if (results.length) |
| return {results: results}; |
| - }; |
| - |
| - // Attaches this event object to its name. Only one object can have a given |
| - // name. |
| - chrome.Event.prototype.attach_ = function() { |
| - AttachEvent(this.eventName_); |
| - allAttachedEvents[allAttachedEvents.length] = this; |
| - if (!this.eventName_) |
| - return; |
| - |
| - if (attachedNamedEvents[this.eventName_]) { |
| - throw new Error("chrome.Event '" + this.eventName_ + |
| - "' is already attached."); |
| - } |
| + } |
| - attachedNamedEvents[this.eventName_] = this; |
| + // Dispatches this event object to all listeners, passing all supplied |
| + // arguments to this function each listener. |
| + chrome.Event.prototype.dispatch = function(varargs) { |
| + return this.dispatch_(Array.prototype.slice.call(arguments), undefined); |
| }; |
| // Detaches this event object from its name. |
| - chrome.Event.prototype.detach_ = function(manual) { |
| - var i = allAttachedEvents.indexOf(this); |
| - if (i >= 0) |
| - delete allAttachedEvents[i]; |
| - DetachEvent(this.eventName_, manual); |
| - if (!this.eventName_) |
| - return; |
| - |
| - if (!attachedNamedEvents[this.eventName_]) { |
| - throw new Error("chrome.Event '" + this.eventName_ + |
| - "' is not attached."); |
| - } |
| - |
| - delete attachedNamedEvents[this.eventName_]; |
| + chrome.Event.prototype.detach_ = function() { |
| + this.attachmentStrategy_.detach(false); |
| }; |
| chrome.Event.prototype.destroy_ = function() { |
| @@ -363,7 +460,7 @@ |
| for (var i = 0; i < allAttachedEvents.length; ++i) { |
| var event = allAttachedEvents[i]; |
| if (event) |
| - event.detach_(false); |
| + event.detach_(); |
| } |
| }; |