Home | History | Annotate | Download | only in resources
      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 // Custom binding for the webRequestInternal API.
      6 
      7 var binding = require('binding').Binding.create('webRequestInternal');
      8 var eventBindings = require('event_bindings');
      9 var sendRequest = require('sendRequest').sendRequest;
     10 var validate = require('schemaUtils').validate;
     11 var utils = require('utils');
     12 var idGeneratorNatives = requireNative('id_generator');
     13 
     14 var webRequestInternal;
     15 
     16 function GetUniqueSubEventName(eventName) {
     17   return eventName + "/" + idGeneratorNatives.GetNextId();
     18 }
     19 
     20 // WebRequestEventImpl object. This is used for special webRequest events
     21 // with extra parameters. Each invocation of addListener creates a new named
     22 // sub-event. That sub-event is associated with the extra parameters in the
     23 // browser process, so that only it is dispatched when the main event occurs
     24 // matching the extra parameters.
     25 //
     26 // Example:
     27 //   chrome.webRequest.onBeforeRequest.addListener(
     28 //       callback, {urls: 'http://*.google.com/*'});
     29 //   ^ callback will only be called for onBeforeRequests matching the filter.
     30 function WebRequestEventImpl(eventName, opt_argSchemas, opt_extraArgSchemas,
     31                              opt_eventOptions, opt_webViewInstanceId) {
     32   if (typeof eventName != 'string')
     33     throw new Error('chrome.WebRequestEvent requires an event name.');
     34 
     35   this.eventName = eventName;
     36   this.argSchemas = opt_argSchemas;
     37   this.extraArgSchemas = opt_extraArgSchemas;
     38   this.webViewInstanceId = opt_webViewInstanceId || 0;
     39   this.subEvents = [];
     40   this.eventOptions = eventBindings.parseEventOptions(opt_eventOptions);
     41   if (this.eventOptions.supportsRules) {
     42     this.eventForRules =
     43         new eventBindings.Event(eventName, opt_argSchemas, opt_eventOptions,
     44                                 opt_webViewInstanceId);
     45   }
     46 }
     47 
     48 // Test if the given callback is registered for this event.
     49 WebRequestEventImpl.prototype.hasListener = function(cb) {
     50   if (!this.eventOptions.supportsListeners)
     51     throw new Error('This event does not support listeners.');
     52   return this.findListener_(cb) > -1;
     53 };
     54 
     55 // Test if any callbacks are registered fur thus event.
     56 WebRequestEventImpl.prototype.hasListeners = function() {
     57   if (!this.eventOptions.supportsListeners)
     58     throw new Error('This event does not support listeners.');
     59   return this.subEvents.length > 0;
     60 };
     61 
     62 // Registers a callback to be called when this event is dispatched. If
     63 // opt_filter is specified, then the callback is only called for events that
     64 // match the given filters. If opt_extraInfo is specified, the given optional
     65 // info is sent to the callback.
     66 WebRequestEventImpl.prototype.addListener =
     67     function(cb, opt_filter, opt_extraInfo) {
     68   if (!this.eventOptions.supportsListeners)
     69     throw new Error('This event does not support listeners.');
     70   // NOTE(benjhayden) New APIs should not use this subEventName trick! It does
     71   // not play well with event pages. See downloads.onDeterminingFilename and
     72   // ExtensionDownloadsEventRouter for an alternative approach.
     73   var subEventName = GetUniqueSubEventName(this.eventName);
     74   // Note: this could fail to validate, in which case we would not add the
     75   // subEvent listener.
     76   validate($Array.slice(arguments, 1), this.extraArgSchemas);
     77   webRequestInternal.addEventListener(
     78       cb, opt_filter, opt_extraInfo, this.eventName, subEventName,
     79       this.webViewInstanceId);
     80 
     81   var subEvent = new eventBindings.Event(subEventName, this.argSchemas);
     82   var subEventCallback = cb;
     83   if (opt_extraInfo && opt_extraInfo.indexOf('blocking') >= 0) {
     84     var eventName = this.eventName;
     85     subEventCallback = function() {
     86       var requestId = arguments[0].requestId;
     87       try {
     88         var result = $Function.apply(cb, null, arguments);
     89         webRequestInternal.eventHandled(
     90             eventName, subEventName, requestId, result);
     91       } catch (e) {
     92         webRequestInternal.eventHandled(
     93             eventName, subEventName, requestId);
     94         throw e;
     95       }
     96     };
     97   } else if (opt_extraInfo && opt_extraInfo.indexOf('asyncBlocking') >= 0) {
     98     var eventName = this.eventName;
     99     subEventCallback = function() {
    100       var details = arguments[0];
    101       var requestId = details.requestId;
    102       var handledCallback = function(response) {
    103         webRequestInternal.eventHandled(
    104             eventName, subEventName, requestId, response);
    105       };
    106       $Function.apply(cb, null, [details, handledCallback]);
    107     };
    108   }
    109   $Array.push(this.subEvents,
    110       {subEvent: subEvent, callback: cb, subEventCallback: subEventCallback});
    111   subEvent.addListener(subEventCallback);
    112 };
    113 
    114 // Unregisters a callback.
    115 WebRequestEventImpl.prototype.removeListener = function(cb) {
    116   if (!this.eventOptions.supportsListeners)
    117     throw new Error('This event does not support listeners.');
    118   var idx;
    119   while ((idx = this.findListener_(cb)) >= 0) {
    120     var e = this.subEvents[idx];
    121     e.subEvent.removeListener(e.subEventCallback);
    122     if (e.subEvent.hasListeners()) {
    123       console.error(
    124           'Internal error: webRequest subEvent has orphaned listeners.');
    125     }
    126     $Array.splice(this.subEvents, idx, 1);
    127   }
    128 };
    129 
    130 WebRequestEventImpl.prototype.findListener_ = function(cb) {
    131   for (var i in this.subEvents) {
    132     var e = this.subEvents[i];
    133     if (e.callback === cb) {
    134       if (e.subEvent.hasListener(e.subEventCallback))
    135         return i;
    136       console.error('Internal error: webRequest subEvent has no callback.');
    137     }
    138   }
    139 
    140   return -1;
    141 };
    142 
    143 WebRequestEventImpl.prototype.addRules = function(rules, opt_cb) {
    144   if (!this.eventOptions.supportsRules)
    145     throw new Error('This event does not support rules.');
    146   this.eventForRules.addRules(rules, opt_cb);
    147 };
    148 
    149 WebRequestEventImpl.prototype.removeRules =
    150     function(ruleIdentifiers, opt_cb) {
    151   if (!this.eventOptions.supportsRules)
    152     throw new Error('This event does not support rules.');
    153   this.eventForRules.removeRules(ruleIdentifiers, opt_cb);
    154 };
    155 
    156 WebRequestEventImpl.prototype.getRules = function(ruleIdentifiers, cb) {
    157   if (!this.eventOptions.supportsRules)
    158     throw new Error('This event does not support rules.');
    159   this.eventForRules.getRules(ruleIdentifiers, cb);
    160 };
    161 
    162 binding.registerCustomHook(function(api) {
    163   var apiFunctions = api.apiFunctions;
    164 
    165   apiFunctions.setHandleRequest('addEventListener', function() {
    166     var args = $Array.slice(arguments);
    167     sendRequest(this.name, args, this.definition.parameters,
    168                 {forIOThread: true});
    169   });
    170 
    171   apiFunctions.setHandleRequest('eventHandled', function() {
    172     var args = $Array.slice(arguments);
    173     sendRequest(this.name, args, this.definition.parameters,
    174                 {forIOThread: true});
    175   });
    176 });
    177 
    178 var WebRequestEvent = utils.expose('WebRequestEvent',
    179                                    WebRequestEventImpl,
    180                                    { functions: [
    181   'hasListener',
    182   'hasListeners',
    183   'addListener',
    184   'removeListener',
    185   'addRules',
    186   'removeRules',
    187   'getRules'
    188 ] });
    189 
    190 webRequestInternal = binding.generate();
    191 exports.binding = webRequestInternal;
    192 exports.WebRequestEvent = WebRequestEvent;
    193