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