| Index: trunk/src/extensions/renderer/resources/event.js
|
| ===================================================================
|
| --- trunk/src/extensions/renderer/resources/event.js (revision 274563)
|
| +++ trunk/src/extensions/renderer/resources/event.js (working copy)
|
| @@ -1,528 +0,0 @@
|
| -// Copyright 2014 The Chromium 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 eventNatives = requireNative('event_natives');
|
| - var handleUncaughtException = require('uncaught_exception_handler').handle;
|
| - var logging = requireNative('logging');
|
| - var schemaRegistry = requireNative('schema_registry');
|
| - var sendRequest = require('sendRequest').sendRequest;
|
| - var utils = require('utils');
|
| - var validate = require('schemaUtils').validate;
|
| - var unloadEvent = require('unload_event');
|
| -
|
| - // Schemas for the rule-style functions on the events API that
|
| - // only need to be generated occasionally, so populate them lazily.
|
| - var ruleFunctionSchemas = {
|
| - // These values are set lazily:
|
| - // addRules: {},
|
| - // getRules: {},
|
| - // removeRules: {}
|
| - };
|
| -
|
| - // This function ensures that |ruleFunctionSchemas| is populated.
|
| - function ensureRuleSchemasLoaded() {
|
| - if (ruleFunctionSchemas.addRules)
|
| - return;
|
| - var eventsSchema = schemaRegistry.GetSchema("events");
|
| - var eventType = utils.lookup(eventsSchema.types, 'id', 'events.Event');
|
| -
|
| - ruleFunctionSchemas.addRules =
|
| - utils.lookup(eventType.functions, 'name', 'addRules');
|
| - ruleFunctionSchemas.getRules =
|
| - utils.lookup(eventType.functions, 'name', 'getRules');
|
| - ruleFunctionSchemas.removeRules =
|
| - utils.lookup(eventType.functions, 'name', 'removeRules');
|
| - }
|
| -
|
| - // A map of event names to the event object that is registered to that name.
|
| - var attachedNamedEvents = {};
|
| -
|
| - // An array of all attached event objects, used for detaching on unload.
|
| - var allAttachedEvents = [];
|
| -
|
| - // A map of functions that massage event arguments before they are dispatched.
|
| - // Key is event name, value is function.
|
| - var eventArgumentMassagers = {};
|
| -
|
| - // An attachment strategy for events that aren't attached to the browser.
|
| - // This applies to events with the "unmanaged" option and events without
|
| - // names.
|
| - var NullAttachmentStrategy = function(event) {
|
| - this.event_ = event;
|
| - };
|
| - NullAttachmentStrategy.prototype.onAddedListener =
|
| - function(listener) {
|
| - };
|
| - NullAttachmentStrategy.prototype.onRemovedListener =
|
| - function(listener) {
|
| - };
|
| - NullAttachmentStrategy.prototype.detach = function(manual) {
|
| - };
|
| - NullAttachmentStrategy.prototype.getListenersByIDs = function(ids) {
|
| - // |ids| is for filtered events only.
|
| - return this.event_.listeners;
|
| - };
|
| -
|
| - // 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)
|
| - eventNatives.AttachEvent(this.event_.eventName);
|
| - };
|
| -
|
| - UnfilteredAttachmentStrategy.prototype.onRemovedListener =
|
| - function(listener) {
|
| - if (this.event_.listeners.length == 0)
|
| - this.detach(true);
|
| - };
|
| -
|
| - UnfilteredAttachmentStrategy.prototype.detach = function(manual) {
|
| - eventNatives.DetachEvent(this.event_.eventName, manual);
|
| - };
|
| -
|
| - UnfilteredAttachmentStrategy.prototype.getListenersByIDs = function(ids) {
|
| - // |ids| is for filtered events only.
|
| - return this.event_.listeners;
|
| - };
|
| -
|
| - var FilteredAttachmentStrategy = function(event) {
|
| - this.event_ = event;
|
| - this.listenerMap_ = {};
|
| - };
|
| -
|
| - FilteredAttachmentStrategy.idToEventMap = {};
|
| -
|
| - FilteredAttachmentStrategy.prototype.onAddedListener = function(listener) {
|
| - var id = eventNatives.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];
|
| - eventNatives.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++)
|
| - $Array.push(result, this.listenerMap_[ids[i]]);
|
| - return result;
|
| - };
|
| -
|
| - function parseEventOptions(opt_eventOptions) {
|
| - function merge(dest, src) {
|
| - for (var k in src) {
|
| - if (!$Object.hasOwnProperty(dest, k)) {
|
| - dest[k] = src[k];
|
| - }
|
| - }
|
| - }
|
| -
|
| - var options = opt_eventOptions || {};
|
| - merge(options, {
|
| - // Event supports adding listeners with filters ("filtered events"), for
|
| - // example as used in the webNavigation API.
|
| - //
|
| - // event.addListener(listener, [filter1, filter2]);
|
| - supportsFilters: false,
|
| -
|
| - // Events supports vanilla events. Most APIs use these.
|
| - //
|
| - // event.addListener(listener);
|
| - supportsListeners: true,
|
| -
|
| - // Event supports adding rules ("declarative events") rather than
|
| - // listeners, for example as used in the declarativeWebRequest API.
|
| - //
|
| - // event.addRules([rule1, rule2]);
|
| - supportsRules: false,
|
| -
|
| - // Event is unmanaged in that the browser has no knowledge of its
|
| - // existence; it's never invoked, doesn't keep the renderer alive, and
|
| - // the bindings system has no knowledge of it.
|
| - //
|
| - // Both events created by user code (new chrome.Event()) and messaging
|
| - // events are unmanaged, though in the latter case the browser *does*
|
| - // interact indirectly with them via IPCs written by hand.
|
| - unmanaged: false,
|
| - });
|
| - return options;
|
| - };
|
| -
|
| - // 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
|
| - // opt_eventName is required for events that support rules.
|
| - //
|
| - // Example:
|
| - // var Event = require('event_bindings').Event;
|
| - // chrome.tabs.onChanged = new Event("tab-changed");
|
| - // chrome.tabs.onChanged.addListener(function(data) { alert(data); });
|
| - // Event.dispatch("tab-changed", "hi");
|
| - // will result in an alert dialog that says 'hi'.
|
| - //
|
| - // If opt_eventOptions exists, it is a dictionary that contains the boolean
|
| - // entries "supportsListeners" and "supportsRules".
|
| - // If opt_webViewInstanceId exists, it is an integer uniquely identifying a
|
| - // <webview> tag within the embedder. If it does not exist, then this is an
|
| - // extension event rather than a <webview> event.
|
| - var EventImpl = function(opt_eventName, opt_argSchemas, opt_eventOptions,
|
| - opt_webViewInstanceId) {
|
| - this.eventName = opt_eventName;
|
| - this.argSchemas = opt_argSchemas;
|
| - this.listeners = [];
|
| - this.eventOptions = parseEventOptions(opt_eventOptions);
|
| - this.webViewInstanceId = opt_webViewInstanceId || 0;
|
| -
|
| - if (!this.eventName) {
|
| - if (this.eventOptions.supportsRules)
|
| - throw new Error("Events that support rules require an event name.");
|
| - // Events without names cannot be managed by the browser by definition
|
| - // (the browser has no way of identifying them).
|
| - this.eventOptions.unmanaged = true;
|
| - }
|
| -
|
| - // Track whether the event has been destroyed to help track down the cause
|
| - // of http://crbug.com/258526.
|
| - // This variable will eventually hold the stack trace of the destroy call.
|
| - // TODO(kalman): Delete this and replace with more sound logic that catches
|
| - // when events are used without being *attached*.
|
| - this.destroyed = null;
|
| -
|
| - if (this.eventOptions.unmanaged)
|
| - this.attachmentStrategy = new NullAttachmentStrategy(this);
|
| - else if (this.eventOptions.supportsFilters)
|
| - this.attachmentStrategy = new FilteredAttachmentStrategy(this);
|
| - else
|
| - this.attachmentStrategy = new UnfilteredAttachmentStrategy(this);
|
| - };
|
| -
|
| - // callback is a function(args, dispatch). args are the args we receive from
|
| - // dispatchEvent(), and dispatch is a function(args) that dispatches args to
|
| - // its listeners.
|
| - function registerArgumentMassager(name, callback) {
|
| - if (eventArgumentMassagers[name])
|
| - throw new Error("Massager already registered for event: " + name);
|
| - eventArgumentMassagers[name] = callback;
|
| - }
|
| -
|
| - // Dispatches a named event with the given argument array. The args array is
|
| - // the list of arguments that will be sent to the event callback.
|
| - function dispatchEvent(name, args, filteringInfo) {
|
| - var listenerIDs = [];
|
| -
|
| - if (filteringInfo)
|
| - listenerIDs = eventNatives.MatchAgainstEventFilter(name, filteringInfo);
|
| -
|
| - var event = attachedNamedEvents[name];
|
| - if (!event)
|
| - return;
|
| -
|
| - var dispatchArgs = function(args) {
|
| - var result = event.dispatch_(args, listenerIDs);
|
| - if (result)
|
| - logging.DCHECK(!result.validationErrors, result.validationErrors);
|
| - return result;
|
| - };
|
| -
|
| - if (eventArgumentMassagers[name])
|
| - eventArgumentMassagers[name](args, dispatchArgs);
|
| - else
|
| - dispatchArgs(args);
|
| - }
|
| -
|
| - // Registers a callback to be called when this event is dispatched.
|
| - EventImpl.prototype.addListener = function(cb, filters) {
|
| - if (!this.eventOptions.supportsListeners)
|
| - throw new Error("This event does not support listeners.");
|
| - if (this.eventOptions.maxListeners &&
|
| - this.getListenerCount_() >= this.eventOptions.maxListeners) {
|
| - throw new Error("Too many listeners for " + this.eventName);
|
| - }
|
| - 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.");
|
| - if (filters.serviceType &&
|
| - !(typeof filters.serviceType === 'string')) {
|
| - throw new Error("filters.serviceType should be a string.")
|
| - }
|
| - }
|
| - var listener = {callback: cb, filters: filters};
|
| - this.attach_(listener);
|
| - $Array.push(this.listeners, listener);
|
| - };
|
| -
|
| - EventImpl.prototype.attach_ = function(listener) {
|
| - this.attachmentStrategy.onAddedListener(listener);
|
| -
|
| - if (this.listeners.length == 0) {
|
| - allAttachedEvents[allAttachedEvents.length] = this;
|
| - if (this.eventName) {
|
| - if (attachedNamedEvents[this.eventName]) {
|
| - throw new Error("Event '" + this.eventName +
|
| - "' is already attached.");
|
| - }
|
| - attachedNamedEvents[this.eventName] = this;
|
| - }
|
| - }
|
| - };
|
| -
|
| - // Unregisters a callback.
|
| - EventImpl.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;
|
| -
|
| - var removedListener = $Array.splice(this.listeners, idx, 1)[0];
|
| - this.attachmentStrategy.onRemovedListener(removedListener);
|
| -
|
| - if (this.listeners.length == 0) {
|
| - var i = $Array.indexOf(allAttachedEvents, this);
|
| - if (i >= 0)
|
| - delete allAttachedEvents[i];
|
| - if (this.eventName) {
|
| - if (!attachedNamedEvents[this.eventName]) {
|
| - throw new Error(
|
| - "Event '" + this.eventName + "' is not attached.");
|
| - }
|
| - delete attachedNamedEvents[this.eventName];
|
| - }
|
| - }
|
| - };
|
| -
|
| - // Test if the given callback is registered for this event.
|
| - EventImpl.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.
|
| - EventImpl.prototype.hasListeners = function() {
|
| - return this.getListenerCount_() > 0;
|
| - };
|
| -
|
| - // Returns the number of listeners on this event.
|
| - EventImpl.prototype.getListenerCount_ = function() {
|
| - if (!this.eventOptions.supportsListeners)
|
| - throw new Error("This event does not support listeners.");
|
| - return this.listeners.length;
|
| - };
|
| -
|
| - // Returns the index of the given callback if registered, or -1 if not
|
| - // found.
|
| - EventImpl.prototype.findListener_ = function(cb) {
|
| - for (var i = 0; i < this.listeners.length; i++) {
|
| - if (this.listeners[i].callback == cb) {
|
| - return i;
|
| - }
|
| - }
|
| -
|
| - return -1;
|
| - };
|
| -
|
| - EventImpl.prototype.dispatch_ = function(args, listenerIDs) {
|
| - if (this.destroyed) {
|
| - throw new Error(this.eventName + ' was already destroyed at: ' +
|
| - this.destroyed);
|
| - }
|
| - if (!this.eventOptions.supportsListeners)
|
| - throw new Error("This event does not support listeners.");
|
| -
|
| - if (this.argSchemas && logging.DCHECK_IS_ON()) {
|
| - try {
|
| - validate(args, this.argSchemas);
|
| - } catch (e) {
|
| - e.message += ' in ' + this.eventName;
|
| - throw e;
|
| - }
|
| - }
|
| -
|
| - // Make a copy of the listeners in case the listener list is modified
|
| - // while dispatching the event.
|
| - var listeners = $Array.slice(
|
| - this.attachmentStrategy.getListenersByIDs(listenerIDs));
|
| -
|
| - var results = [];
|
| - for (var i = 0; i < listeners.length; i++) {
|
| - try {
|
| - var result = this.wrapper.dispatchToListener(listeners[i].callback,
|
| - args);
|
| - if (result !== undefined)
|
| - $Array.push(results, result);
|
| - } catch (e) {
|
| - handleUncaughtException(
|
| - 'Error in event handler for ' +
|
| - (this.eventName ? this.eventName : '(unknown)') +
|
| - ': ' + e.message + '\nStack trace: ' + e.stack,
|
| - e);
|
| - }
|
| - }
|
| - if (results.length)
|
| - return {results: results};
|
| - }
|
| -
|
| - // Can be overridden to support custom dispatching.
|
| - EventImpl.prototype.dispatchToListener = function(callback, args) {
|
| - return $Function.apply(callback, null, args);
|
| - }
|
| -
|
| - // Dispatches this event object to all listeners, passing all supplied
|
| - // arguments to this function each listener.
|
| - EventImpl.prototype.dispatch = function(varargs) {
|
| - return this.dispatch_($Array.slice(arguments), undefined);
|
| - };
|
| -
|
| - // Detaches this event object from its name.
|
| - EventImpl.prototype.detach_ = function() {
|
| - this.attachmentStrategy.detach(false);
|
| - };
|
| -
|
| - EventImpl.prototype.destroy_ = function() {
|
| - this.listeners.length = 0;
|
| - this.detach_();
|
| - this.destroyed = new Error().stack;
|
| - };
|
| -
|
| - EventImpl.prototype.addRules = function(rules, opt_cb) {
|
| - if (!this.eventOptions.supportsRules)
|
| - throw new Error("This event does not support rules.");
|
| -
|
| - // 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);
|
| - $Array.forEach(rules, function(rule) {
|
| - validate([rule.conditions], [conditionsSchema]);
|
| - validate([rule.actions], [actionsSchema]);
|
| - });
|
| - };
|
| -
|
| - if (!this.eventOptions.conditions || !this.eventOptions.actions) {
|
| - throw new Error('Event ' + this.eventName + ' misses ' +
|
| - 'conditions or actions in the API specification.');
|
| - }
|
| -
|
| - validateRules(rules,
|
| - this.eventOptions.conditions,
|
| - this.eventOptions.actions);
|
| -
|
| - ensureRuleSchemasLoaded();
|
| - // We remove the first parameter from the validation to give the user more
|
| - // meaningful error messages.
|
| - validate([this.webViewInstanceId, rules, opt_cb],
|
| - $Array.splice(
|
| - $Array.slice(ruleFunctionSchemas.addRules.parameters), 1));
|
| - sendRequest(
|
| - "events.addRules",
|
| - [this.eventName, this.webViewInstanceId, rules, opt_cb],
|
| - ruleFunctionSchemas.addRules.parameters);
|
| - }
|
| -
|
| - EventImpl.prototype.removeRules = function(ruleIdentifiers, opt_cb) {
|
| - if (!this.eventOptions.supportsRules)
|
| - throw new Error("This event does not support rules.");
|
| - ensureRuleSchemasLoaded();
|
| - // We remove the first parameter from the validation to give the user more
|
| - // meaningful error messages.
|
| - validate([this.webViewInstanceId, ruleIdentifiers, opt_cb],
|
| - $Array.splice(
|
| - $Array.slice(ruleFunctionSchemas.removeRules.parameters), 1));
|
| - sendRequest("events.removeRules",
|
| - [this.eventName,
|
| - this.webViewInstanceId,
|
| - ruleIdentifiers,
|
| - opt_cb],
|
| - ruleFunctionSchemas.removeRules.parameters);
|
| - }
|
| -
|
| - EventImpl.prototype.getRules = function(ruleIdentifiers, cb) {
|
| - if (!this.eventOptions.supportsRules)
|
| - throw new Error("This event does not support rules.");
|
| - ensureRuleSchemasLoaded();
|
| - // We remove the first parameter from the validation to give the user more
|
| - // meaningful error messages.
|
| - validate([this.webViewInstanceId, ruleIdentifiers, cb],
|
| - $Array.splice(
|
| - $Array.slice(ruleFunctionSchemas.getRules.parameters), 1));
|
| -
|
| - sendRequest(
|
| - "events.getRules",
|
| - [this.eventName, this.webViewInstanceId, ruleIdentifiers, cb],
|
| - ruleFunctionSchemas.getRules.parameters);
|
| - }
|
| -
|
| - unloadEvent.addListener(function() {
|
| - for (var i = 0; i < allAttachedEvents.length; ++i) {
|
| - var event = allAttachedEvents[i];
|
| - if (event)
|
| - event.detach_();
|
| - }
|
| - });
|
| -
|
| - var Event = utils.expose('Event', EventImpl, { functions: [
|
| - 'addListener',
|
| - 'removeListener',
|
| - 'hasListener',
|
| - 'hasListeners',
|
| - 'dispatchToListener',
|
| - 'dispatch',
|
| - 'addRules',
|
| - 'removeRules',
|
| - 'getRules'
|
| - ] });
|
| -
|
| - // NOTE: Event is (lazily) exposed as chrome.Event from dispatcher.cc.
|
| - exports.Event = Event;
|
| -
|
| - exports.dispatchEvent = dispatchEvent;
|
| - exports.parseEventOptions = parseEventOptions;
|
| - exports.registerArgumentMassager = registerArgumentMassager;
|
|
|