// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Implements the Chrome Extensions WebNavigation API. #include "chrome/browser/extensions/extension_webnavigation_api.h" #include "base/json/json_writer.h" #include "base/string_number_conversions.h" #include "base/time.h" #include "base/values.h" #include "chrome/browser/extensions/extension_event_router.h" #include "chrome/browser/extensions/extension_tabs_module.h" #include "chrome/browser/extensions/extension_webnavigation_api_constants.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/url_constants.h" #include "content/browser/tab_contents/tab_contents.h" #include "content/common/notification_service.h" #include "content/common/view_messages.h" #include "net/base/net_errors.h" namespace keys = extension_webnavigation_api_constants; namespace { // URL schemes for which we'll send events. const char* kValidSchemes[] = { chrome::kHttpScheme, chrome::kHttpsScheme, chrome::kFileScheme, chrome::kFtpScheme, }; // Returns 0 if the navigation happens in the main frame, or the frame ID // modulo 32 bits otherwise. int GetFrameId(bool is_main_frame, int64 frame_id) { return is_main_frame ? 0 : static_cast(frame_id); } // Returns |time| as milliseconds since the epoch. double MilliSecondsFromTime(const base::Time& time) { return 1000 * time.ToDoubleT(); } // Dispatches events to the extension message service. void DispatchEvent(Profile* profile, const char* event_name, const std::string& json_args) { if (profile && profile->GetExtensionEventRouter()) { profile->GetExtensionEventRouter()->DispatchEventToRenderers( event_name, json_args, profile, GURL()); } } // Constructs and dispatches an onBeforeNavigate event. void DispatchOnBeforeNavigate(TabContents* tab_contents, int64 frame_id, bool is_main_frame, const GURL& validated_url, uint64 request_id) { ListValue args; DictionaryValue* dict = new DictionaryValue(); dict->SetInteger(keys::kTabIdKey, ExtensionTabUtil::GetTabId(tab_contents)); dict->SetString(keys::kUrlKey, validated_url.spec()); dict->SetInteger(keys::kFrameIdKey, GetFrameId(is_main_frame, frame_id)); dict->SetString(keys::kRequestIdKey, base::Uint64ToString(request_id)); dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now())); args.Append(dict); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(tab_contents->profile(), keys::kOnBeforeNavigate, json_args); } // Constructs and dispatches an onCommitted event. void DispatchOnCommitted(TabContents* tab_contents, int64 frame_id, bool is_main_frame, const GURL& url, PageTransition::Type transition_type) { ListValue args; DictionaryValue* dict = new DictionaryValue(); dict->SetInteger(keys::kTabIdKey, ExtensionTabUtil::GetTabId(tab_contents)); dict->SetString(keys::kUrlKey, url.spec()); dict->SetInteger(keys::kFrameIdKey, GetFrameId(is_main_frame, frame_id)); dict->SetString(keys::kTransitionTypeKey, PageTransition::CoreTransitionString(transition_type)); ListValue* qualifiers = new ListValue(); if (transition_type & PageTransition::CLIENT_REDIRECT) qualifiers->Append(Value::CreateStringValue("client_redirect")); if (transition_type & PageTransition::SERVER_REDIRECT) qualifiers->Append(Value::CreateStringValue("server_redirect")); if (transition_type & PageTransition::FORWARD_BACK) qualifiers->Append(Value::CreateStringValue("forward_back")); dict->Set(keys::kTransitionQualifiersKey, qualifiers); dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now())); args.Append(dict); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(tab_contents->profile(), keys::kOnCommitted, json_args); } // Constructs and dispatches an onDOMContentLoaded event. void DispatchOnDOMContentLoaded(TabContents* tab_contents, const GURL& url, bool is_main_frame, int64 frame_id) { ListValue args; DictionaryValue* dict = new DictionaryValue(); dict->SetInteger(keys::kTabIdKey, ExtensionTabUtil::GetTabId(tab_contents)); dict->SetString(keys::kUrlKey, url.spec()); dict->SetInteger(keys::kFrameIdKey, is_main_frame ? 0 : static_cast(frame_id)); dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now())); args.Append(dict); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(tab_contents->profile(), keys::kOnDOMContentLoaded, json_args); } // Constructs and dispatches an onCompleted event. void DispatchOnCompleted(TabContents* tab_contents, const GURL& url, bool is_main_frame, int64 frame_id) { ListValue args; DictionaryValue* dict = new DictionaryValue(); dict->SetInteger(keys::kTabIdKey, ExtensionTabUtil::GetTabId(tab_contents)); dict->SetString(keys::kUrlKey, url.spec()); dict->SetInteger(keys::kFrameIdKey, is_main_frame ? 0 : static_cast(frame_id)); dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now())); args.Append(dict); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(tab_contents->profile(), keys::kOnCompleted, json_args); } // Constructs and dispatches an onBeforeRetarget event. void DispatchOnBeforeRetarget(TabContents* tab_contents, Profile* profile, const GURL& opener_url, const GURL& target_url) { ListValue args; DictionaryValue* dict = new DictionaryValue(); dict->SetInteger(keys::kSourceTabIdKey, ExtensionTabUtil::GetTabId(tab_contents)); dict->SetString(keys::kSourceUrlKey, opener_url.spec()); dict->SetString(keys::kUrlKey, target_url.possibly_invalid_spec()); dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now())); args.Append(dict); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(profile, keys::kOnBeforeRetarget, json_args); } } // namespace // FrameNavigationState ------------------------------------------------------- // static bool FrameNavigationState::allow_extension_scheme_ = false; FrameNavigationState::FrameNavigationState() {} FrameNavigationState::~FrameNavigationState() {} bool FrameNavigationState::CanSendEvents(int64 frame_id) const { FrameIdToStateMap::const_iterator frame_state = frame_state_map_.find(frame_id); if (frame_state == frame_state_map_.end() || frame_state->second.error_occurred) { return false; } const std::string& scheme = frame_state->second.url.scheme(); for (unsigned i = 0; i < arraysize(kValidSchemes); ++i) { if (scheme == kValidSchemes[i]) return true; } if (allow_extension_scheme_ && scheme == chrome::kExtensionScheme) return true; return false; } void FrameNavigationState::TrackFrame(int64 frame_id, const GURL& url, bool is_main_frame, bool is_error_page, const TabContents* tab_contents) { if (is_main_frame) RemoveTabContentsState(tab_contents); tab_contents_map_.insert(std::make_pair(tab_contents, frame_id)); FrameState& frame_state = frame_state_map_[frame_id]; frame_state.error_occurred = is_error_page; frame_state.url = url; frame_state.is_main_frame = is_main_frame; } GURL FrameNavigationState::GetUrl(int64 frame_id) const { FrameIdToStateMap::const_iterator frame_state = frame_state_map_.find(frame_id); if (frame_state == frame_state_map_.end()) { NOTREACHED(); return GURL(); } return frame_state->second.url; } bool FrameNavigationState::IsMainFrame(int64 frame_id) const { FrameIdToStateMap::const_iterator frame_state = frame_state_map_.find(frame_id); if (frame_state == frame_state_map_.end()) { NOTREACHED(); return false; } return frame_state->second.is_main_frame; } void FrameNavigationState::ErrorOccurredInFrame(int64 frame_id) { DCHECK(frame_state_map_.find(frame_id) != frame_state_map_.end()); frame_state_map_[frame_id].error_occurred = true; } void FrameNavigationState::RemoveTabContentsState( const TabContents* tab_contents) { typedef TabContentsToFrameIdMap::iterator FrameIdIterator; std::pair frame_ids = tab_contents_map_.equal_range(tab_contents); for (FrameIdIterator frame_id = frame_ids.first; frame_id != frame_ids.second; ++frame_id) { frame_state_map_.erase(frame_id->second); } tab_contents_map_.erase(tab_contents); } // ExtensionWebNavigtionEventRouter ------------------------------------------- ExtensionWebNavigationEventRouter::ExtensionWebNavigationEventRouter() {} ExtensionWebNavigationEventRouter::~ExtensionWebNavigationEventRouter() {} // static ExtensionWebNavigationEventRouter* ExtensionWebNavigationEventRouter::GetInstance() { return Singleton::get(); } void ExtensionWebNavigationEventRouter::Init() { if (registrar_.IsEmpty()) { registrar_.Add(this, NotificationType::CREATING_NEW_WINDOW, NotificationService::AllSources()); } } void ExtensionWebNavigationEventRouter::Observe( NotificationType type, const NotificationSource& source, const NotificationDetails& details) { switch (type.value) { case NotificationType::CREATING_NEW_WINDOW: CreatingNewWindow( Source(source).ptr(), Details(details).ptr()); break; default: NOTREACHED(); } } void ExtensionWebNavigationEventRouter::CreatingNewWindow( TabContents* tab_contents, const ViewHostMsg_CreateWindow_Params* details) { DispatchOnBeforeRetarget(tab_contents, tab_contents->profile(), details->opener_url, details->target_url); } // ExtensionWebNavigationTabObserver ------------------------------------------ ExtensionWebNavigationTabObserver::ExtensionWebNavigationTabObserver( TabContents* tab_contents) : TabContentsObserver(tab_contents) {} ExtensionWebNavigationTabObserver::~ExtensionWebNavigationTabObserver() {} void ExtensionWebNavigationTabObserver::DidStartProvisionalLoadForFrame( int64 frame_id, bool is_main_frame, const GURL& validated_url, bool is_error_page) { navigation_state_.TrackFrame(frame_id, validated_url, is_main_frame, is_error_page, tab_contents()); if (!navigation_state_.CanSendEvents(frame_id)) return; DispatchOnBeforeNavigate( tab_contents(), frame_id, is_main_frame, validated_url, 0); } void ExtensionWebNavigationTabObserver::DidCommitProvisionalLoadForFrame( int64 frame_id, bool is_main_frame, const GURL& url, PageTransition::Type transition_type) { if (!navigation_state_.CanSendEvents(frame_id)) return; // On reference fragment navigations, only a new navigation state is // committed. We need to catch this case and generate a full sequence // of events. if (IsReferenceFragmentNavigation(frame_id, url)) { NavigatedReferenceFragment(frame_id, is_main_frame, url, transition_type); return; } DispatchOnCommitted( tab_contents(), frame_id, is_main_frame, url, transition_type); } void ExtensionWebNavigationTabObserver::DidFailProvisionalLoad( int64 frame_id, bool is_main_frame, const GURL& validated_url, int error_code) { if (!navigation_state_.CanSendEvents(frame_id)) return; ListValue args; DictionaryValue* dict = new DictionaryValue(); dict->SetInteger(keys::kTabIdKey, ExtensionTabUtil::GetTabId(tab_contents())); dict->SetString(keys::kUrlKey, validated_url.spec()); dict->SetInteger(keys::kFrameIdKey, GetFrameId(is_main_frame, frame_id)); dict->SetString(keys::kErrorKey, std::string(net::ErrorToString(error_code))); dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now())); args.Append(dict); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); navigation_state_.ErrorOccurredInFrame(frame_id); DispatchEvent(tab_contents()->profile(), keys::kOnErrorOccurred, json_args); } void ExtensionWebNavigationTabObserver::DocumentLoadedInFrame( int64 frame_id) { if (!navigation_state_.CanSendEvents(frame_id)) return; DispatchOnDOMContentLoaded(tab_contents(), navigation_state_.GetUrl(frame_id), navigation_state_.IsMainFrame(frame_id), frame_id); } void ExtensionWebNavigationTabObserver::DidFinishLoad( int64 frame_id) { if (!navigation_state_.CanSendEvents(frame_id)) return; DispatchOnCompleted(tab_contents(), navigation_state_.GetUrl(frame_id), navigation_state_.IsMainFrame(frame_id), frame_id); } void ExtensionWebNavigationTabObserver::TabContentsDestroyed( TabContents* tab) { navigation_state_.RemoveTabContentsState(tab); } void ExtensionWebNavigationTabObserver::DidOpenURL( const GURL& url, const GURL& referrer, WindowOpenDisposition disposition, PageTransition::Type transition) { if (disposition != NEW_FOREGROUND_TAB && disposition != NEW_BACKGROUND_TAB && disposition != NEW_WINDOW && disposition != OFF_THE_RECORD) { return; } Profile* profile = tab_contents()->profile(); if (disposition == OFF_THE_RECORD) { if (!profile->HasOffTheRecordProfile()) { NOTREACHED(); return; } profile = profile->GetOffTheRecordProfile(); } DispatchOnBeforeRetarget(tab_contents(), profile, tab_contents()->GetURL(), url); } // See also NavigationController::IsURLInPageNavigation. bool ExtensionWebNavigationTabObserver::IsReferenceFragmentNavigation( int64 frame_id, const GURL& url) { GURL existing_url = navigation_state_.GetUrl(frame_id); if (existing_url == url) return false; url_canon::Replacements replacements; replacements.ClearRef(); return existing_url.ReplaceComponents(replacements) == url.ReplaceComponents(replacements); } void ExtensionWebNavigationTabObserver::NavigatedReferenceFragment( int64 frame_id, bool is_main_frame, const GURL& url, PageTransition::Type transition_type) { navigation_state_.TrackFrame(frame_id, url, is_main_frame, false, tab_contents()); DispatchOnBeforeNavigate(tab_contents(), frame_id, is_main_frame, url, 0); DispatchOnCommitted(tab_contents(), frame_id, is_main_frame, url, transition_type); DispatchOnDOMContentLoaded(tab_contents(), url, is_main_frame, frame_id); DispatchOnCompleted(tab_contents(), url, is_main_frame, frame_id); }