Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2011 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 #include "chrome/browser/extensions/extension_webrequest_api.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/json/json_writer.h"
     10 #include "base/metrics/histogram.h"
     11 #include "base/string_number_conversions.h"
     12 #include "base/values.h"
     13 #include "chrome/browser/extensions/extension_event_router_forwarder.h"
     14 #include "chrome/browser/extensions/extension_tab_id_map.h"
     15 #include "chrome/browser/extensions/extension_webrequest_api_constants.h"
     16 #include "chrome/browser/profiles/profile.h"
     17 #include "chrome/common/extensions/extension.h"
     18 #include "chrome/common/extensions/extension_error_utils.h"
     19 #include "chrome/common/extensions/extension_extent.h"
     20 #include "chrome/common/extensions/url_pattern.h"
     21 #include "chrome/common/url_constants.h"
     22 #include "content/browser/browser_thread.h"
     23 #include "content/browser/renderer_host/resource_dispatcher_host.h"
     24 #include "content/browser/renderer_host/resource_dispatcher_host_request_info.h"
     25 #include "net/base/net_errors.h"
     26 #include "net/url_request/url_request.h"
     27 #include "googleurl/src/gurl.h"
     28 
     29 namespace keys = extension_webrequest_api_constants;
     30 
     31 namespace {
     32 
     33 // List of all the webRequest events.
     34 static const char* const kWebRequestEvents[] = {
     35   keys::kOnBeforeRedirect,
     36   keys::kOnBeforeRequest,
     37   keys::kOnBeforeSendHeaders,
     38   keys::kOnCompleted,
     39   keys::kOnErrorOccurred,
     40   keys::kOnHeadersReceived,
     41   keys::kOnRequestSent
     42 };
     43 
     44 static const char* kResourceTypeStrings[] = {
     45   "main_frame",
     46   "sub_frame",
     47   "stylesheet",
     48   "script",
     49   "image",
     50   "object",
     51   "other",
     52 };
     53 
     54 static ResourceType::Type kResourceTypeValues[] = {
     55   ResourceType::MAIN_FRAME,
     56   ResourceType::SUB_FRAME,
     57   ResourceType::STYLESHEET,
     58   ResourceType::SCRIPT,
     59   ResourceType::IMAGE,
     60   ResourceType::OBJECT,
     61   ResourceType::LAST_TYPE,  // represents "other"
     62 };
     63 
     64 COMPILE_ASSERT(
     65     arraysize(kResourceTypeStrings) == arraysize(kResourceTypeValues),
     66     keep_resource_types_in_sync);
     67 
     68 #define ARRAYEND(array) (array + arraysize(array))
     69 
     70 static bool IsWebRequestEvent(const std::string& event_name) {
     71   return std::find(kWebRequestEvents, ARRAYEND(kWebRequestEvents),
     72                    event_name) != ARRAYEND(kWebRequestEvents);
     73 }
     74 
     75 static const char* ResourceTypeToString(ResourceType::Type type) {
     76   ResourceType::Type* iter =
     77       std::find(kResourceTypeValues, ARRAYEND(kResourceTypeValues), type);
     78   if (iter == ARRAYEND(kResourceTypeValues))
     79     return "other";
     80 
     81   return kResourceTypeStrings[iter - kResourceTypeValues];
     82 }
     83 
     84 static bool ParseResourceType(const std::string& type_str,
     85                               ResourceType::Type* type) {
     86   const char** iter =
     87       std::find(kResourceTypeStrings, ARRAYEND(kResourceTypeStrings), type_str);
     88   if (iter == ARRAYEND(kResourceTypeStrings))
     89     return false;
     90   *type = kResourceTypeValues[iter - kResourceTypeStrings];
     91   return true;
     92 }
     93 
     94 static void ExtractRequestInfo(net::URLRequest* request,
     95                                int* tab_id,
     96                                int* window_id,
     97                                ResourceType::Type* resource_type) {
     98   if (!request->GetUserData(NULL))
     99     return;
    100 
    101   ResourceDispatcherHostRequestInfo* info =
    102       ResourceDispatcherHost::InfoForRequest(request);
    103   ExtensionTabIdMap::GetInstance()->GetTabAndWindowId(
    104       info->child_id(), info->route_id(), tab_id, window_id);
    105 
    106   // Restrict the resource type to the values we care about.
    107   ResourceType::Type* iter =
    108       std::find(kResourceTypeValues, ARRAYEND(kResourceTypeValues),
    109                 info->resource_type());
    110   *resource_type = (iter != ARRAYEND(kResourceTypeValues)) ?
    111       *iter : ResourceType::LAST_TYPE;
    112 }
    113 
    114 static void AddEventListenerOnIOThread(
    115     ProfileId profile_id,
    116     const std::string& extension_id,
    117     const std::string& event_name,
    118     const std::string& sub_event_name,
    119     const ExtensionWebRequestEventRouter::RequestFilter& filter,
    120     int extra_info_spec) {
    121   ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
    122       profile_id, extension_id, event_name, sub_event_name, filter,
    123       extra_info_spec);
    124 }
    125 
    126 static void EventHandledOnIOThread(
    127     ProfileId profile_id,
    128     const std::string& extension_id,
    129     const std::string& event_name,
    130     const std::string& sub_event_name,
    131     uint64 request_id,
    132     bool cancel,
    133     const GURL& new_url) {
    134   ExtensionWebRequestEventRouter::GetInstance()->OnEventHandled(
    135       profile_id, extension_id, event_name, sub_event_name, request_id,
    136       cancel, new_url);
    137 }
    138 
    139 }  // namespace
    140 
    141 // Internal representation of the webRequest.RequestFilter type, used to
    142 // filter what network events an extension cares about.
    143 struct ExtensionWebRequestEventRouter::RequestFilter {
    144   ExtensionExtent urls;
    145   std::vector<ResourceType::Type> types;
    146   int tab_id;
    147   int window_id;
    148 
    149   RequestFilter() : tab_id(-1), window_id(-1) {}
    150   bool InitFromValue(const DictionaryValue& value);
    151 };
    152 
    153 // Internal representation of the extraInfoSpec parameter on webRequest events,
    154 // used to specify extra information to be included with network events.
    155 struct ExtensionWebRequestEventRouter::ExtraInfoSpec {
    156   enum Flags {
    157     REQUEST_LINE = 1<<0,
    158     REQUEST_HEADERS = 1<<1,
    159     STATUS_LINE = 1<<2,
    160     RESPONSE_HEADERS = 1<<3,
    161     REDIRECT_REQUEST_LINE = 1<<4,
    162     REDIRECT_REQUEST_HEADERS = 1<<5,
    163     BLOCKING = 1<<6,
    164   };
    165 
    166   static bool InitFromValue(const ListValue& value, int* extra_info_spec);
    167 };
    168 
    169 // Represents a single unique listener to an event, along with whatever filter
    170 // parameters and extra_info_spec were specified at the time the listener was
    171 // added.
    172 struct ExtensionWebRequestEventRouter::EventListener {
    173   std::string extension_id;
    174   std::string sub_event_name;
    175   RequestFilter filter;
    176   int extra_info_spec;
    177   mutable std::set<uint64> blocked_requests;
    178 
    179   // Comparator to work with std::set.
    180   bool operator<(const EventListener& that) const {
    181     if (extension_id < that.extension_id)
    182       return true;
    183     if (extension_id == that.extension_id &&
    184         sub_event_name < that.sub_event_name)
    185       return true;
    186     return false;
    187   }
    188 };
    189 
    190 // Contains info about requests that are blocked waiting for a response from
    191 // an extension.
    192 struct ExtensionWebRequestEventRouter::BlockedRequest {
    193   // The number of event handlers that we are awaiting a response from.
    194   int num_handlers_blocking;
    195 
    196   // The callback to call when we get a response from all event handlers.
    197   net::CompletionCallback* callback;
    198 
    199   // If non-empty, this contains the new URL that the request will redirect to.
    200   GURL* new_url;
    201 
    202   // Time the request was issued. Used for logging purposes.
    203   base::Time request_time;
    204 
    205   BlockedRequest() : num_handlers_blocking(0), callback(NULL), new_url(NULL) {}
    206 };
    207 
    208 bool ExtensionWebRequestEventRouter::RequestFilter::InitFromValue(
    209     const DictionaryValue& value) {
    210   for (DictionaryValue::key_iterator key = value.begin_keys();
    211        key != value.end_keys(); ++key) {
    212     if (*key == "urls") {
    213       ListValue* urls_value = NULL;
    214       if (!value.GetList("urls", &urls_value))
    215         return false;
    216       for (size_t i = 0; i < urls_value->GetSize(); ++i) {
    217         std::string url;
    218         URLPattern pattern(URLPattern::SCHEME_ALL);
    219         if (!urls_value->GetString(i, &url) ||
    220             pattern.Parse(url, URLPattern::PARSE_STRICT) !=
    221                 URLPattern::PARSE_SUCCESS)
    222           return false;
    223         urls.AddPattern(pattern);
    224       }
    225     } else if (*key == "types") {
    226       ListValue* types_value = NULL;
    227       if (!value.GetList("types", &types_value))
    228         return false;
    229       for (size_t i = 0; i < types_value->GetSize(); ++i) {
    230         std::string type_str;
    231         ResourceType::Type type;
    232         if (!types_value->GetString(i, &type_str) ||
    233             !ParseResourceType(type_str, &type))
    234           return false;
    235         types.push_back(type);
    236       }
    237     } else if (*key == "tabId") {
    238       if (!value.GetInteger("tabId", &tab_id))
    239         return false;
    240     } else if (*key == "windowId") {
    241       if (!value.GetInteger("windowId", &window_id))
    242         return false;
    243     } else {
    244       return false;
    245     }
    246   }
    247   return true;
    248 }
    249 
    250 // static
    251 bool ExtensionWebRequestEventRouter::ExtraInfoSpec::InitFromValue(
    252     const ListValue& value, int* extra_info_spec) {
    253   *extra_info_spec = 0;
    254   for (size_t i = 0; i < value.GetSize(); ++i) {
    255     std::string str;
    256     if (!value.GetString(i, &str))
    257       return false;
    258 
    259     // TODO(mpcomplete): not all of these are valid for every event.
    260     if (str == "requestLine")
    261       *extra_info_spec |= REQUEST_LINE;
    262     else if (str == "requestHeaders")
    263       *extra_info_spec |= REQUEST_HEADERS;
    264     else if (str == "statusLine")
    265       *extra_info_spec |= STATUS_LINE;
    266     else if (str == "responseHeaders")
    267       *extra_info_spec |= RESPONSE_HEADERS;
    268     else if (str == "redirectRequestLine")
    269       *extra_info_spec |= REDIRECT_REQUEST_LINE;
    270     else if (str == "redirectRequestHeaders")
    271       *extra_info_spec |= REDIRECT_REQUEST_HEADERS;
    272     else if (str == "blocking")
    273       *extra_info_spec |= BLOCKING;
    274     else
    275       return false;
    276   }
    277   return true;
    278 }
    279 
    280 // static
    281 ExtensionWebRequestEventRouter* ExtensionWebRequestEventRouter::GetInstance() {
    282   return Singleton<ExtensionWebRequestEventRouter>::get();
    283 }
    284 
    285 ExtensionWebRequestEventRouter::ExtensionWebRequestEventRouter() {
    286 }
    287 
    288 ExtensionWebRequestEventRouter::~ExtensionWebRequestEventRouter() {
    289 }
    290 
    291 int ExtensionWebRequestEventRouter::OnBeforeRequest(
    292     ProfileId profile_id,
    293     ExtensionEventRouterForwarder* event_router,
    294     net::URLRequest* request,
    295     net::CompletionCallback* callback,
    296     GURL* new_url) {
    297   // TODO(jochen): Figure out what to do with events from the system context.
    298   if (profile_id == Profile::kInvalidProfileId)
    299     return net::OK;
    300 
    301   int tab_id = -1;
    302   int window_id = -1;
    303   ResourceType::Type resource_type = ResourceType::LAST_TYPE;
    304   ExtractRequestInfo(request, &tab_id, &window_id, &resource_type);
    305 
    306   std::vector<const EventListener*> listeners =
    307       GetMatchingListeners(profile_id, keys::kOnBeforeRequest, request->url(),
    308                            tab_id, window_id, resource_type);
    309   if (listeners.empty())
    310     return net::OK;
    311 
    312   // If this is an HTTP request, keep track of it. HTTP-specific events only
    313   // have the request ID, so we'll need to look up the URLRequest from that.
    314   if (request->url().SchemeIs(chrome::kHttpScheme) ||
    315       request->url().SchemeIs(chrome::kHttpsScheme)) {
    316     http_requests_[request->identifier()] = request;
    317   }
    318 
    319   ListValue args;
    320   DictionaryValue* dict = new DictionaryValue();
    321   dict->SetString(keys::kRequestIdKey,
    322                   base::Uint64ToString(request->identifier()));
    323   dict->SetString(keys::kUrlKey, request->url().spec());
    324   dict->SetString(keys::kMethodKey, request->method());
    325   dict->SetInteger(keys::kTabIdKey, tab_id);
    326   dict->SetString(keys::kTypeKey, ResourceTypeToString(resource_type));
    327   dict->SetDouble(keys::kTimeStampKey,
    328                   request->request_time().ToDoubleT() * 1000);
    329   args.Append(dict);
    330 
    331   if (DispatchEvent(profile_id, event_router, request, callback, listeners,
    332                     args)) {
    333     blocked_requests_[request->identifier()].new_url = new_url;
    334     return net::ERR_IO_PENDING;
    335   }
    336   return net::OK;
    337 }
    338 
    339 int ExtensionWebRequestEventRouter::OnBeforeSendHeaders(
    340     ProfileId profile_id,
    341     ExtensionEventRouterForwarder* event_router,
    342     uint64 request_id,
    343     net::CompletionCallback* callback,
    344     net::HttpRequestHeaders* headers) {
    345   // TODO(jochen): Figure out what to do with events from the system context.
    346   if (profile_id == Profile::kInvalidProfileId)
    347     return net::OK;
    348 
    349   HttpRequestMap::iterator iter = http_requests_.find(request_id);
    350   if (iter == http_requests_.end())
    351     return net::OK;
    352 
    353   net::URLRequest* request = iter->second;
    354   http_requests_.erase(iter);
    355 
    356   std::vector<const EventListener*> listeners =
    357       GetMatchingListeners(profile_id, keys::kOnBeforeSendHeaders, request);
    358   if (listeners.empty())
    359     return net::OK;
    360 
    361   ListValue args;
    362   DictionaryValue* dict = new DictionaryValue();
    363   dict->SetString(keys::kRequestIdKey,
    364                   base::Uint64ToString(request->identifier()));
    365   dict->SetString(keys::kUrlKey, request->url().spec());
    366   dict->SetDouble(keys::kTimeStampKey,
    367                   request->request_time().ToDoubleT() * 1000);
    368   // TODO(mpcomplete): request headers.
    369   args.Append(dict);
    370 
    371   if (DispatchEvent(profile_id, event_router, request, callback, listeners,
    372                     args))
    373     return net::ERR_IO_PENDING;
    374   return net::OK;
    375 }
    376 
    377 void ExtensionWebRequestEventRouter::OnURLRequestDestroyed(
    378     ProfileId profile_id, net::URLRequest* request) {
    379   http_requests_.erase(request->identifier());
    380   blocked_requests_.erase(request->identifier());
    381 }
    382 
    383 bool ExtensionWebRequestEventRouter::DispatchEvent(
    384     ProfileId profile_id,
    385     ExtensionEventRouterForwarder* event_router,
    386     net::URLRequest* request,
    387     net::CompletionCallback* callback,
    388     const std::vector<const EventListener*>& listeners,
    389     const ListValue& args) {
    390   std::string json_args;
    391   base::JSONWriter::Write(&args, false, &json_args);
    392 
    393   // TODO(mpcomplete): Consider consolidating common (extension_id,json_args)
    394   // pairs into a single message sent to a list of sub_event_names.
    395   int num_handlers_blocking = 0;
    396   for (std::vector<const EventListener*>::const_iterator it = listeners.begin();
    397        it != listeners.end(); ++it) {
    398     event_router->DispatchEventToExtension(
    399         (*it)->extension_id, (*it)->sub_event_name, json_args,
    400         profile_id, true, GURL());
    401     if (callback && (*it)->extra_info_spec & ExtraInfoSpec::BLOCKING) {
    402       (*it)->blocked_requests.insert(request->identifier());
    403       ++num_handlers_blocking;
    404     }
    405   }
    406 
    407   if (num_handlers_blocking > 0) {
    408     CHECK(blocked_requests_.find(request->identifier()) ==
    409           blocked_requests_.end());
    410     blocked_requests_[request->identifier()].num_handlers_blocking =
    411         num_handlers_blocking;
    412     blocked_requests_[request->identifier()].callback = callback;
    413     blocked_requests_[request->identifier()].request_time =
    414         request->request_time();
    415 
    416     return true;
    417   }
    418 
    419   return false;
    420 }
    421 
    422 void ExtensionWebRequestEventRouter::OnEventHandled(
    423     ProfileId profile_id,
    424     const std::string& extension_id,
    425     const std::string& event_name,
    426     const std::string& sub_event_name,
    427     uint64 request_id,
    428     bool cancel,
    429     const GURL& new_url) {
    430   EventListener listener;
    431   listener.extension_id = extension_id;
    432   listener.sub_event_name = sub_event_name;
    433 
    434   // The listener may have been removed (e.g. due to the process going away)
    435   // before we got here.
    436   std::set<EventListener>::iterator found =
    437       listeners_[profile_id][event_name].find(listener);
    438   if (found != listeners_[profile_id][event_name].end())
    439     found->blocked_requests.erase(request_id);
    440 
    441   DecrementBlockCount(request_id, cancel, new_url);
    442 }
    443 
    444 void ExtensionWebRequestEventRouter::AddEventListener(
    445     ProfileId profile_id,
    446     const std::string& extension_id,
    447     const std::string& event_name,
    448     const std::string& sub_event_name,
    449     const RequestFilter& filter,
    450     int extra_info_spec) {
    451   if (!IsWebRequestEvent(event_name))
    452     return;
    453 
    454   EventListener listener;
    455   listener.extension_id = extension_id;
    456   listener.sub_event_name = sub_event_name;
    457   listener.filter = filter;
    458   listener.extra_info_spec = extra_info_spec;
    459 
    460   CHECK_EQ(listeners_[profile_id][event_name].count(listener), 0u) <<
    461       "extension=" << extension_id << " event=" << event_name;
    462   listeners_[profile_id][event_name].insert(listener);
    463 }
    464 
    465 void ExtensionWebRequestEventRouter::RemoveEventListener(
    466     ProfileId profile_id,
    467     const std::string& extension_id,
    468     const std::string& sub_event_name) {
    469   size_t slash_sep = sub_event_name.find('/');
    470   std::string event_name = sub_event_name.substr(0, slash_sep);
    471 
    472   if (!IsWebRequestEvent(event_name))
    473     return;
    474 
    475   EventListener listener;
    476   listener.extension_id = extension_id;
    477   listener.sub_event_name = sub_event_name;
    478 
    479   CHECK_EQ(listeners_[profile_id][event_name].count(listener), 1u) <<
    480       "extension=" << extension_id << " event=" << event_name;
    481 
    482   // Unblock any request that this event listener may have been blocking.
    483   std::set<EventListener>::iterator found =
    484       listeners_[profile_id][event_name].find(listener);
    485   for (std::set<uint64>::iterator it = found->blocked_requests.begin();
    486        it != found->blocked_requests.end(); ++it) {
    487     DecrementBlockCount(*it, false, GURL());
    488   }
    489 
    490   listeners_[profile_id][event_name].erase(listener);
    491 }
    492 
    493 std::vector<const ExtensionWebRequestEventRouter::EventListener*>
    494 ExtensionWebRequestEventRouter::GetMatchingListeners(
    495     ProfileId profile_id,
    496     const std::string& event_name,
    497     const GURL& url,
    498     int tab_id,
    499     int window_id,
    500     ResourceType::Type resource_type) {
    501   // TODO(mpcomplete): handle profile_id == invalid (should collect all
    502   // listeners).
    503   std::vector<const EventListener*> matching_listeners;
    504   std::set<EventListener>& listeners = listeners_[profile_id][event_name];
    505   for (std::set<EventListener>::iterator it = listeners.begin();
    506        it != listeners.end(); ++it) {
    507     if (!it->filter.urls.is_empty() && !it->filter.urls.ContainsURL(url))
    508       continue;
    509     if (it->filter.tab_id != -1 && tab_id != it->filter.tab_id)
    510       continue;
    511     if (it->filter.window_id != -1 && window_id != it->filter.window_id)
    512       continue;
    513     if (!it->filter.types.empty() &&
    514         std::find(it->filter.types.begin(), it->filter.types.end(),
    515                   resource_type) == it->filter.types.end())
    516       continue;
    517 
    518     matching_listeners.push_back(&(*it));
    519   }
    520   return matching_listeners;
    521 }
    522 
    523 std::vector<const ExtensionWebRequestEventRouter::EventListener*>
    524 ExtensionWebRequestEventRouter::GetMatchingListeners(
    525     ProfileId profile_id,
    526     const std::string& event_name,
    527     net::URLRequest* request) {
    528   int tab_id = -1;
    529   int window_id = -1;
    530   ResourceType::Type resource_type = ResourceType::LAST_TYPE;
    531   ExtractRequestInfo(request, &tab_id, &window_id, &resource_type);
    532 
    533   return GetMatchingListeners(
    534       profile_id, event_name, request->url(), tab_id, window_id, resource_type);
    535 }
    536 
    537 void ExtensionWebRequestEventRouter::DecrementBlockCount(uint64 request_id,
    538                                                          bool cancel,
    539                                                          const GURL& new_url) {
    540   // It's possible that this request was deleted, or cancelled by a previous
    541   // event handler. If so, ignore this response.
    542   if (blocked_requests_.find(request_id) == blocked_requests_.end())
    543     return;
    544 
    545   BlockedRequest& blocked_request = blocked_requests_[request_id];
    546   int num_handlers_blocking = --blocked_request.num_handlers_blocking;
    547   CHECK_GE(num_handlers_blocking, 0);
    548 
    549   if (num_handlers_blocking == 0 || cancel || !new_url.is_empty()) {
    550     HISTOGRAM_TIMES("Extensions.NetworkDelay",
    551                      base::Time::Now() - blocked_request.request_time);
    552 
    553     CHECK(blocked_request.callback);
    554     if (!new_url.is_empty()) {
    555       CHECK(new_url.is_valid());
    556       *blocked_request.new_url = new_url;
    557     }
    558     blocked_request.callback->Run(cancel ? net::ERR_EMPTY_RESPONSE : net::OK);
    559     blocked_requests_.erase(request_id);
    560   }
    561 }
    562 
    563 bool WebRequestAddEventListener::RunImpl() {
    564   // Argument 0 is the callback, which we don't use here.
    565 
    566   ExtensionWebRequestEventRouter::RequestFilter filter;
    567   if (HasOptionalArgument(1)) {
    568     DictionaryValue* value = NULL;
    569     EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &value));
    570     EXTENSION_FUNCTION_VALIDATE(filter.InitFromValue(*value));
    571   }
    572 
    573   int extra_info_spec = 0;
    574   if (HasOptionalArgument(2)) {
    575     ListValue* value = NULL;
    576     EXTENSION_FUNCTION_VALIDATE(args_->GetList(2, &value));
    577     EXTENSION_FUNCTION_VALIDATE(
    578         ExtensionWebRequestEventRouter::ExtraInfoSpec::InitFromValue(
    579             *value, &extra_info_spec));
    580   }
    581 
    582   std::string event_name;
    583   EXTENSION_FUNCTION_VALIDATE(args_->GetString(3, &event_name));
    584 
    585   std::string sub_event_name;
    586   EXTENSION_FUNCTION_VALIDATE(args_->GetString(4, &sub_event_name));
    587 
    588   BrowserThread::PostTask(
    589       BrowserThread::IO, FROM_HERE,
    590       NewRunnableFunction(
    591           &AddEventListenerOnIOThread,
    592           profile()->GetRuntimeId(), extension_id(),
    593           event_name, sub_event_name, filter, extra_info_spec));
    594 
    595   return true;
    596 }
    597 
    598 bool WebRequestEventHandled::RunImpl() {
    599   std::string event_name;
    600   EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &event_name));
    601 
    602   std::string sub_event_name;
    603   EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &sub_event_name));
    604 
    605   std::string request_id_str;
    606   EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &request_id_str));
    607   // TODO(mpcomplete): string-to-uint64?
    608   int64 request_id;
    609   EXTENSION_FUNCTION_VALIDATE(base::StringToInt64(request_id_str, &request_id));
    610 
    611   bool cancel = false;
    612   GURL new_url;
    613   if (HasOptionalArgument(3)) {
    614     DictionaryValue* value = NULL;
    615     EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(3, &value));
    616 
    617     if (value->HasKey("cancel"))
    618       EXTENSION_FUNCTION_VALIDATE(value->GetBoolean("cancel", &cancel));
    619 
    620     std::string new_url_str;
    621     if (value->HasKey("redirectUrl")) {
    622       EXTENSION_FUNCTION_VALIDATE(value->GetString("redirectUrl",
    623                                                    &new_url_str));
    624       new_url = GURL(new_url_str);
    625       if (!new_url.is_valid()) {
    626         error_ = ExtensionErrorUtils::FormatErrorMessage(
    627             keys::kInvalidRedirectUrl, new_url_str);
    628         return false;
    629       }
    630     }
    631   }
    632 
    633   BrowserThread::PostTask(
    634       BrowserThread::IO, FROM_HERE,
    635       NewRunnableFunction(
    636           &EventHandledOnIOThread,
    637           profile()->GetRuntimeId(), extension_id(),
    638           event_name, sub_event_name, request_id, cancel, new_url));
    639 
    640   return true;
    641 }
    642