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 // Implements the Chrome Extensions WebNavigation API.
      6 
      7 #include "chrome/browser/extensions/extension_webnavigation_api.h"
      8 
      9 #include "base/json/json_writer.h"
     10 #include "base/string_number_conversions.h"
     11 #include "base/time.h"
     12 #include "base/values.h"
     13 #include "chrome/browser/extensions/extension_event_router.h"
     14 #include "chrome/browser/extensions/extension_tabs_module.h"
     15 #include "chrome/browser/extensions/extension_webnavigation_api_constants.h"
     16 #include "chrome/browser/profiles/profile.h"
     17 #include "chrome/common/url_constants.h"
     18 #include "content/browser/tab_contents/tab_contents.h"
     19 #include "content/common/notification_service.h"
     20 #include "content/common/view_messages.h"
     21 #include "net/base/net_errors.h"
     22 
     23 namespace keys = extension_webnavigation_api_constants;
     24 
     25 namespace {
     26 
     27 // URL schemes for which we'll send events.
     28 const char* kValidSchemes[] = {
     29   chrome::kHttpScheme,
     30   chrome::kHttpsScheme,
     31   chrome::kFileScheme,
     32   chrome::kFtpScheme,
     33 };
     34 
     35 // Returns 0 if the navigation happens in the main frame, or the frame ID
     36 // modulo 32 bits otherwise.
     37 int GetFrameId(bool is_main_frame, int64 frame_id) {
     38   return is_main_frame ? 0 : static_cast<int>(frame_id);
     39 }
     40 
     41 // Returns |time| as milliseconds since the epoch.
     42 double MilliSecondsFromTime(const base::Time& time) {
     43   return 1000 * time.ToDoubleT();
     44 }
     45 
     46 // Dispatches events to the extension message service.
     47 void DispatchEvent(Profile* profile,
     48                    const char* event_name,
     49                    const std::string& json_args) {
     50   if (profile && profile->GetExtensionEventRouter()) {
     51     profile->GetExtensionEventRouter()->DispatchEventToRenderers(
     52         event_name, json_args, profile, GURL());
     53   }
     54 }
     55 
     56 // Constructs and dispatches an onBeforeNavigate event.
     57 void DispatchOnBeforeNavigate(TabContents* tab_contents,
     58                               int64 frame_id,
     59                               bool is_main_frame,
     60                               const GURL& validated_url,
     61                               uint64 request_id) {
     62   ListValue args;
     63   DictionaryValue* dict = new DictionaryValue();
     64   dict->SetInteger(keys::kTabIdKey,
     65                    ExtensionTabUtil::GetTabId(tab_contents));
     66   dict->SetString(keys::kUrlKey, validated_url.spec());
     67   dict->SetInteger(keys::kFrameIdKey, GetFrameId(is_main_frame, frame_id));
     68   dict->SetString(keys::kRequestIdKey,
     69                   base::Uint64ToString(request_id));
     70   dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
     71   args.Append(dict);
     72 
     73   std::string json_args;
     74   base::JSONWriter::Write(&args, false, &json_args);
     75   DispatchEvent(tab_contents->profile(), keys::kOnBeforeNavigate, json_args);
     76 }
     77 
     78 // Constructs and dispatches an onCommitted event.
     79 void DispatchOnCommitted(TabContents* tab_contents,
     80                          int64 frame_id,
     81                          bool is_main_frame,
     82                          const GURL& url,
     83                          PageTransition::Type transition_type) {
     84   ListValue args;
     85   DictionaryValue* dict = new DictionaryValue();
     86   dict->SetInteger(keys::kTabIdKey,
     87                    ExtensionTabUtil::GetTabId(tab_contents));
     88   dict->SetString(keys::kUrlKey, url.spec());
     89   dict->SetInteger(keys::kFrameIdKey, GetFrameId(is_main_frame, frame_id));
     90   dict->SetString(keys::kTransitionTypeKey,
     91                   PageTransition::CoreTransitionString(transition_type));
     92   ListValue* qualifiers = new ListValue();
     93   if (transition_type & PageTransition::CLIENT_REDIRECT)
     94     qualifiers->Append(Value::CreateStringValue("client_redirect"));
     95   if (transition_type & PageTransition::SERVER_REDIRECT)
     96     qualifiers->Append(Value::CreateStringValue("server_redirect"));
     97   if (transition_type & PageTransition::FORWARD_BACK)
     98     qualifiers->Append(Value::CreateStringValue("forward_back"));
     99   dict->Set(keys::kTransitionQualifiersKey, qualifiers);
    100   dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
    101   args.Append(dict);
    102 
    103   std::string json_args;
    104   base::JSONWriter::Write(&args, false, &json_args);
    105   DispatchEvent(tab_contents->profile(), keys::kOnCommitted, json_args);
    106 }
    107 
    108 // Constructs and dispatches an onDOMContentLoaded event.
    109 void DispatchOnDOMContentLoaded(TabContents* tab_contents,
    110                                 const GURL& url,
    111                                 bool is_main_frame,
    112                                 int64 frame_id) {
    113   ListValue args;
    114   DictionaryValue* dict = new DictionaryValue();
    115   dict->SetInteger(keys::kTabIdKey,
    116                    ExtensionTabUtil::GetTabId(tab_contents));
    117   dict->SetString(keys::kUrlKey, url.spec());
    118   dict->SetInteger(keys::kFrameIdKey,
    119       is_main_frame ? 0 : static_cast<int>(frame_id));
    120   dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
    121   args.Append(dict);
    122 
    123   std::string json_args;
    124   base::JSONWriter::Write(&args, false, &json_args);
    125   DispatchEvent(tab_contents->profile(), keys::kOnDOMContentLoaded, json_args);
    126 }
    127 
    128 // Constructs and dispatches an onCompleted event.
    129 void DispatchOnCompleted(TabContents* tab_contents,
    130                          const GURL& url,
    131                          bool is_main_frame,
    132                          int64 frame_id) {
    133   ListValue args;
    134   DictionaryValue* dict = new DictionaryValue();
    135   dict->SetInteger(keys::kTabIdKey,
    136                    ExtensionTabUtil::GetTabId(tab_contents));
    137   dict->SetString(keys::kUrlKey, url.spec());
    138   dict->SetInteger(keys::kFrameIdKey,
    139       is_main_frame ? 0 : static_cast<int>(frame_id));
    140   dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
    141   args.Append(dict);
    142 
    143   std::string json_args;
    144   base::JSONWriter::Write(&args, false, &json_args);
    145   DispatchEvent(tab_contents->profile(), keys::kOnCompleted, json_args);
    146 }
    147 
    148 // Constructs and dispatches an onBeforeRetarget event.
    149 void DispatchOnBeforeRetarget(TabContents* tab_contents,
    150                               Profile* profile,
    151                               const GURL& opener_url,
    152                               const GURL& target_url) {
    153   ListValue args;
    154   DictionaryValue* dict = new DictionaryValue();
    155   dict->SetInteger(keys::kSourceTabIdKey,
    156                    ExtensionTabUtil::GetTabId(tab_contents));
    157   dict->SetString(keys::kSourceUrlKey, opener_url.spec());
    158   dict->SetString(keys::kUrlKey, target_url.possibly_invalid_spec());
    159   dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
    160   args.Append(dict);
    161 
    162   std::string json_args;
    163   base::JSONWriter::Write(&args, false, &json_args);
    164   DispatchEvent(profile, keys::kOnBeforeRetarget, json_args);
    165 }
    166 
    167 }  // namespace
    168 
    169 
    170 // FrameNavigationState -------------------------------------------------------
    171 
    172 // static
    173 bool FrameNavigationState::allow_extension_scheme_ = false;
    174 
    175 FrameNavigationState::FrameNavigationState() {}
    176 
    177 FrameNavigationState::~FrameNavigationState() {}
    178 
    179 bool FrameNavigationState::CanSendEvents(int64 frame_id) const {
    180   FrameIdToStateMap::const_iterator frame_state =
    181       frame_state_map_.find(frame_id);
    182   if (frame_state == frame_state_map_.end() ||
    183       frame_state->second.error_occurred) {
    184     return false;
    185   }
    186   const std::string& scheme = frame_state->second.url.scheme();
    187   for (unsigned i = 0; i < arraysize(kValidSchemes); ++i) {
    188     if (scheme == kValidSchemes[i])
    189       return true;
    190   }
    191   if (allow_extension_scheme_ && scheme == chrome::kExtensionScheme)
    192     return true;
    193   return false;
    194 }
    195 
    196 void FrameNavigationState::TrackFrame(int64 frame_id,
    197                                       const GURL& url,
    198                                       bool is_main_frame,
    199                                       bool is_error_page,
    200                                       const TabContents* tab_contents) {
    201   if (is_main_frame)
    202     RemoveTabContentsState(tab_contents);
    203   tab_contents_map_.insert(std::make_pair(tab_contents, frame_id));
    204   FrameState& frame_state = frame_state_map_[frame_id];
    205   frame_state.error_occurred = is_error_page;
    206   frame_state.url = url;
    207   frame_state.is_main_frame = is_main_frame;
    208 }
    209 
    210 GURL FrameNavigationState::GetUrl(int64 frame_id) const {
    211   FrameIdToStateMap::const_iterator frame_state =
    212       frame_state_map_.find(frame_id);
    213   if (frame_state == frame_state_map_.end()) {
    214     NOTREACHED();
    215     return GURL();
    216   }
    217   return frame_state->second.url;
    218 }
    219 
    220 bool FrameNavigationState::IsMainFrame(int64 frame_id) const {
    221   FrameIdToStateMap::const_iterator frame_state =
    222       frame_state_map_.find(frame_id);
    223   if (frame_state == frame_state_map_.end()) {
    224     NOTREACHED();
    225     return false;
    226   }
    227   return frame_state->second.is_main_frame;
    228 }
    229 
    230 void FrameNavigationState::ErrorOccurredInFrame(int64 frame_id) {
    231   DCHECK(frame_state_map_.find(frame_id) != frame_state_map_.end());
    232   frame_state_map_[frame_id].error_occurred = true;
    233 }
    234 
    235 void FrameNavigationState::RemoveTabContentsState(
    236     const TabContents* tab_contents) {
    237   typedef TabContentsToFrameIdMap::iterator FrameIdIterator;
    238   std::pair<FrameIdIterator, FrameIdIterator> frame_ids =
    239       tab_contents_map_.equal_range(tab_contents);
    240   for (FrameIdIterator frame_id = frame_ids.first; frame_id != frame_ids.second;
    241        ++frame_id) {
    242     frame_state_map_.erase(frame_id->second);
    243   }
    244   tab_contents_map_.erase(tab_contents);
    245 }
    246 
    247 
    248 // ExtensionWebNavigtionEventRouter -------------------------------------------
    249 
    250 ExtensionWebNavigationEventRouter::ExtensionWebNavigationEventRouter() {}
    251 
    252 ExtensionWebNavigationEventRouter::~ExtensionWebNavigationEventRouter() {}
    253 
    254 // static
    255 ExtensionWebNavigationEventRouter*
    256 ExtensionWebNavigationEventRouter::GetInstance() {
    257   return Singleton<ExtensionWebNavigationEventRouter>::get();
    258 }
    259 
    260 void ExtensionWebNavigationEventRouter::Init() {
    261   if (registrar_.IsEmpty()) {
    262     registrar_.Add(this,
    263                    NotificationType::CREATING_NEW_WINDOW,
    264                    NotificationService::AllSources());
    265   }
    266 }
    267 
    268 void ExtensionWebNavigationEventRouter::Observe(
    269     NotificationType type,
    270     const NotificationSource& source,
    271     const NotificationDetails& details) {
    272   switch (type.value) {
    273     case NotificationType::CREATING_NEW_WINDOW:
    274       CreatingNewWindow(
    275           Source<TabContents>(source).ptr(),
    276           Details<const ViewHostMsg_CreateWindow_Params>(details).ptr());
    277       break;
    278 
    279     default:
    280       NOTREACHED();
    281   }
    282 }
    283 
    284 void ExtensionWebNavigationEventRouter::CreatingNewWindow(
    285     TabContents* tab_contents,
    286     const ViewHostMsg_CreateWindow_Params* details) {
    287   DispatchOnBeforeRetarget(tab_contents,
    288                            tab_contents->profile(),
    289                            details->opener_url,
    290                            details->target_url);
    291 }
    292 
    293 
    294 // ExtensionWebNavigationTabObserver ------------------------------------------
    295 
    296 ExtensionWebNavigationTabObserver::ExtensionWebNavigationTabObserver(
    297     TabContents* tab_contents)
    298     : TabContentsObserver(tab_contents) {}
    299 
    300 ExtensionWebNavigationTabObserver::~ExtensionWebNavigationTabObserver() {}
    301 
    302 void ExtensionWebNavigationTabObserver::DidStartProvisionalLoadForFrame(
    303     int64 frame_id,
    304     bool is_main_frame,
    305     const GURL& validated_url,
    306     bool is_error_page) {
    307   navigation_state_.TrackFrame(frame_id,
    308                                validated_url,
    309                                is_main_frame,
    310                                is_error_page,
    311                                tab_contents());
    312   if (!navigation_state_.CanSendEvents(frame_id))
    313     return;
    314   DispatchOnBeforeNavigate(
    315       tab_contents(), frame_id, is_main_frame, validated_url, 0);
    316 }
    317 
    318 void ExtensionWebNavigationTabObserver::DidCommitProvisionalLoadForFrame(
    319     int64 frame_id,
    320     bool is_main_frame,
    321     const GURL& url,
    322     PageTransition::Type transition_type) {
    323   if (!navigation_state_.CanSendEvents(frame_id))
    324     return;
    325   // On reference fragment navigations, only a new navigation state is
    326   // committed. We need to catch this case and generate a full sequence
    327   // of events.
    328   if (IsReferenceFragmentNavigation(frame_id, url)) {
    329     NavigatedReferenceFragment(frame_id, is_main_frame, url, transition_type);
    330     return;
    331   }
    332   DispatchOnCommitted(
    333       tab_contents(), frame_id, is_main_frame, url, transition_type);
    334 }
    335 
    336 void ExtensionWebNavigationTabObserver::DidFailProvisionalLoad(
    337     int64 frame_id,
    338     bool is_main_frame,
    339     const GURL& validated_url,
    340     int error_code) {
    341   if (!navigation_state_.CanSendEvents(frame_id))
    342     return;
    343   ListValue args;
    344   DictionaryValue* dict = new DictionaryValue();
    345   dict->SetInteger(keys::kTabIdKey,
    346                    ExtensionTabUtil::GetTabId(tab_contents()));
    347   dict->SetString(keys::kUrlKey, validated_url.spec());
    348   dict->SetInteger(keys::kFrameIdKey, GetFrameId(is_main_frame, frame_id));
    349   dict->SetString(keys::kErrorKey,
    350                   std::string(net::ErrorToString(error_code)));
    351   dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
    352   args.Append(dict);
    353 
    354   std::string json_args;
    355   base::JSONWriter::Write(&args, false, &json_args);
    356   navigation_state_.ErrorOccurredInFrame(frame_id);
    357   DispatchEvent(tab_contents()->profile(), keys::kOnErrorOccurred, json_args);
    358 }
    359 
    360 void ExtensionWebNavigationTabObserver::DocumentLoadedInFrame(
    361     int64 frame_id) {
    362   if (!navigation_state_.CanSendEvents(frame_id))
    363     return;
    364   DispatchOnDOMContentLoaded(tab_contents(),
    365                              navigation_state_.GetUrl(frame_id),
    366                              navigation_state_.IsMainFrame(frame_id),
    367                              frame_id);
    368 }
    369 
    370 void ExtensionWebNavigationTabObserver::DidFinishLoad(
    371     int64 frame_id) {
    372   if (!navigation_state_.CanSendEvents(frame_id))
    373     return;
    374   DispatchOnCompleted(tab_contents(),
    375                       navigation_state_.GetUrl(frame_id),
    376                       navigation_state_.IsMainFrame(frame_id),
    377                       frame_id);
    378 }
    379 
    380 void ExtensionWebNavigationTabObserver::TabContentsDestroyed(
    381     TabContents* tab) {
    382   navigation_state_.RemoveTabContentsState(tab);
    383 }
    384 
    385 void ExtensionWebNavigationTabObserver::DidOpenURL(
    386     const GURL& url,
    387     const GURL& referrer,
    388     WindowOpenDisposition disposition,
    389     PageTransition::Type transition) {
    390   if (disposition != NEW_FOREGROUND_TAB &&
    391       disposition != NEW_BACKGROUND_TAB &&
    392       disposition != NEW_WINDOW &&
    393       disposition != OFF_THE_RECORD) {
    394     return;
    395   }
    396   Profile* profile = tab_contents()->profile();
    397   if (disposition == OFF_THE_RECORD) {
    398     if (!profile->HasOffTheRecordProfile()) {
    399       NOTREACHED();
    400       return;
    401     }
    402     profile = profile->GetOffTheRecordProfile();
    403   }
    404   DispatchOnBeforeRetarget(tab_contents(),
    405                            profile,
    406                            tab_contents()->GetURL(),
    407                            url);
    408 }
    409 
    410 // See also NavigationController::IsURLInPageNavigation.
    411 bool ExtensionWebNavigationTabObserver::IsReferenceFragmentNavigation(
    412     int64 frame_id,
    413     const GURL& url) {
    414   GURL existing_url = navigation_state_.GetUrl(frame_id);
    415   if (existing_url == url)
    416     return false;
    417 
    418   url_canon::Replacements<char> replacements;
    419   replacements.ClearRef();
    420   return existing_url.ReplaceComponents(replacements) ==
    421       url.ReplaceComponents(replacements);
    422 }
    423 
    424 void ExtensionWebNavigationTabObserver::NavigatedReferenceFragment(
    425     int64 frame_id,
    426     bool is_main_frame,
    427     const GURL& url,
    428     PageTransition::Type transition_type) {
    429   navigation_state_.TrackFrame(frame_id,
    430                                url,
    431                                is_main_frame,
    432                                false,
    433                                tab_contents());
    434 
    435   DispatchOnBeforeNavigate(tab_contents(),
    436                            frame_id,
    437                            is_main_frame,
    438                            url,
    439                            0);
    440   DispatchOnCommitted(tab_contents(),
    441                       frame_id,
    442                       is_main_frame,
    443                       url,
    444                       transition_type);
    445   DispatchOnDOMContentLoaded(tab_contents(),
    446                              url,
    447                              is_main_frame,
    448                              frame_id);
    449   DispatchOnCompleted(tab_contents(),
    450                       url,
    451                       is_main_frame,
    452                       frame_id);
    453 }
    454