• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7var binding = require('binding').Binding.create('webRequestInternal');
8var eventBindings = require('event_bindings');
9var sendRequest = require('sendRequest').sendRequest;
10var validate = require('schemaUtils').validate;
11var utils = require('utils');
12var idGeneratorNatives = requireNative('id_generator');
13
14var webRequestInternal;
15
16function 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.
30function 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.
49WebRequestEventImpl.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.
56WebRequestEventImpl.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.
66WebRequestEventImpl.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.
115WebRequestEventImpl.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
130WebRequestEventImpl.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
143WebRequestEventImpl.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
149WebRequestEventImpl.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
156WebRequestEventImpl.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
162binding.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
178var WebRequestEvent = utils.expose('WebRequestEvent',
179                                   WebRequestEventImpl,
180                                   { functions: [
181  'hasListener',
182  'hasListeners',
183  'addListener',
184  'removeListener',
185  'addRules',
186  'removeRules',
187  'getRules'
188] });
189
190webRequestInternal = binding.generate();
191exports.binding = webRequestInternal;
192exports.WebRequestEvent = WebRequestEvent;
193