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