| // Copyright (c) 2012 The Chromium 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 binding for the webRequestInternal API. |
| |
| var binding = require('binding').Binding.create('webRequestInternal'); |
| var eventBindings = require('event_bindings'); |
| var sendRequest = require('sendRequest').sendRequest; |
| var validate = require('schemaUtils').validate; |
| var utils = require('utils'); |
| var idGeneratorNatives = requireNative('id_generator'); |
| |
| var webRequestInternal; |
| |
| function GetUniqueSubEventName(eventName) { |
| return eventName + "/" + idGeneratorNatives.GetNextId(); |
| } |
| |
| // WebRequestEventImpl object. This is used for special webRequest events |
| // with extra parameters. Each invocation of addListener creates a new named |
| // sub-event. That sub-event is associated with the extra parameters in the |
| // browser process, so that only it is dispatched when the main event occurs |
| // matching the extra parameters. |
| // |
| // Example: |
| // chrome.webRequest.onBeforeRequest.addListener( |
| // callback, {urls: 'http://*.google.com/*'}); |
| // ^ callback will only be called for onBeforeRequests matching the filter. |
| function WebRequestEventImpl(eventName, opt_argSchemas, opt_extraArgSchemas, |
| opt_eventOptions, opt_webViewInstanceId) { |
| if (typeof eventName != 'string') |
| throw new Error('chrome.WebRequestEvent requires an event name.'); |
| |
| this.eventName = eventName; |
| this.argSchemas = opt_argSchemas; |
| this.extraArgSchemas = opt_extraArgSchemas; |
| this.webViewInstanceId = opt_webViewInstanceId || 0; |
| this.subEvents = []; |
| this.eventOptions = eventBindings.parseEventOptions(opt_eventOptions); |
| if (this.eventOptions.supportsRules) { |
| this.eventForRules = |
| new eventBindings.Event(eventName, opt_argSchemas, opt_eventOptions, |
| opt_webViewInstanceId); |
| } |
| } |
| |
| // Test if the given callback is registered for this event. |
| WebRequestEventImpl.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. |
| WebRequestEventImpl.prototype.hasListeners = function() { |
| if (!this.eventOptions.supportsListeners) |
| throw new Error('This event does not support listeners.'); |
| return this.subEvents.length > 0; |
| }; |
| |
| // Registers a callback to be called when this event is dispatched. If |
| // opt_filter is specified, then the callback is only called for events that |
| // match the given filters. If opt_extraInfo is specified, the given optional |
| // info is sent to the callback. |
| WebRequestEventImpl.prototype.addListener = |
| function(cb, opt_filter, opt_extraInfo) { |
| if (!this.eventOptions.supportsListeners) |
| throw new Error('This event does not support listeners.'); |
| // NOTE(benjhayden) New APIs should not use this subEventName trick! It does |
| // not play well with event pages. See downloads.onDeterminingFilename and |
| // ExtensionDownloadsEventRouter for an alternative approach. |
| var subEventName = GetUniqueSubEventName(this.eventName); |
| // Note: this could fail to validate, in which case we would not add the |
| // subEvent listener. |
| validate($Array.slice(arguments, 1), this.extraArgSchemas); |
| webRequestInternal.addEventListener( |
| cb, opt_filter, opt_extraInfo, this.eventName, subEventName, |
| this.webViewInstanceId); |
| |
| var subEvent = new eventBindings.Event(subEventName, this.argSchemas); |
| var subEventCallback = cb; |
| if (opt_extraInfo && opt_extraInfo.indexOf('blocking') >= 0) { |
| var eventName = this.eventName; |
| subEventCallback = function() { |
| var requestId = arguments[0].requestId; |
| try { |
| var result = $Function.apply(cb, null, arguments); |
| webRequestInternal.eventHandled( |
| eventName, subEventName, requestId, result); |
| } catch (e) { |
| webRequestInternal.eventHandled( |
| eventName, subEventName, requestId); |
| throw e; |
| } |
| }; |
| } else if (opt_extraInfo && opt_extraInfo.indexOf('asyncBlocking') >= 0) { |
| var eventName = this.eventName; |
| subEventCallback = function() { |
| var details = arguments[0]; |
| var requestId = details.requestId; |
| var handledCallback = function(response) { |
| webRequestInternal.eventHandled( |
| eventName, subEventName, requestId, response); |
| }; |
| $Function.apply(cb, null, [details, handledCallback]); |
| }; |
| } |
| $Array.push(this.subEvents, |
| {subEvent: subEvent, callback: cb, subEventCallback: subEventCallback}); |
| subEvent.addListener(subEventCallback); |
| }; |
| |
| // Unregisters a callback. |
| WebRequestEventImpl.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]; |
| e.subEvent.removeListener(e.subEventCallback); |
| if (e.subEvent.hasListeners()) { |
| console.error( |
| 'Internal error: webRequest subEvent has orphaned listeners.'); |
| } |
| $Array.splice(this.subEvents, idx, 1); |
| } |
| }; |
| |
| WebRequestEventImpl.prototype.findListener_ = function(cb) { |
| for (var i in this.subEvents) { |
| var e = this.subEvents[i]; |
| if (e.callback === cb) { |
| if (e.subEvent.hasListener(e.subEventCallback)) |
| return i; |
| console.error('Internal error: webRequest subEvent has no callback.'); |
| } |
| } |
| |
| return -1; |
| }; |
| |
| WebRequestEventImpl.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); |
| }; |
| |
| WebRequestEventImpl.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); |
| }; |
| |
| WebRequestEventImpl.prototype.getRules = function(ruleIdentifiers, cb) { |
| if (!this.eventOptions.supportsRules) |
| throw new Error('This event does not support rules.'); |
| this.eventForRules.getRules(ruleIdentifiers, cb); |
| }; |
| |
| binding.registerCustomHook(function(api) { |
| var apiFunctions = api.apiFunctions; |
| |
| apiFunctions.setHandleRequest('addEventListener', function() { |
| var args = $Array.slice(arguments); |
| sendRequest(this.name, args, this.definition.parameters, |
| {forIOThread: true}); |
| }); |
| |
| apiFunctions.setHandleRequest('eventHandled', function() { |
| var args = $Array.slice(arguments); |
| sendRequest(this.name, args, this.definition.parameters, |
| {forIOThread: true}); |
| }); |
| }); |
| |
| var WebRequestEvent = utils.expose('WebRequestEvent', |
| WebRequestEventImpl, |
| { functions: [ |
| 'hasListener', |
| 'hasListeners', |
| 'addListener', |
| 'removeListener', |
| 'addRules', |
| 'removeRules', |
| 'getRules' |
| ] }); |
| |
| webRequestInternal = binding.generate(); |
| exports.binding = webRequestInternal; |
| exports.WebRequestEvent = WebRequestEvent; |