// Copyright (c) 2010 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/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/browser/tab_contents/navigation_controller.h" #include "chrome/browser/tab_contents/provisional_load_details.h" #include "chrome/common/notification_type.h" #include "chrome/common/notification_service.h" #include "net/base/net_errors.h" namespace keys = extension_webnavigation_api_constants; namespace { // Returns 0 if the navigation happens in the main frame, or the frame ID // modulo 32 bits otherwise. int GetFrameId(ProvisionalLoadDetails* details) { return details->main_frame() ? 0 : static_cast(details->frame_id()); } // Returns |time| as milliseconds since the epoch. double MilliSecondsFromTime(const base::Time& time) { return 1000 * time.ToDoubleT(); } } // namespace FrameNavigationState::FrameNavigationState() { } FrameNavigationState::~FrameNavigationState() { } bool FrameNavigationState::CanSendEvents(int64 frame_id) const { FrameIdToStateMap::const_iterator frame_state = frame_state_map_.find(frame_id); return frame_state != frame_state_map_.end() && !frame_state->second.error_occurred; } 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); } // static ExtensionWebNavigationEventRouter* ExtensionWebNavigationEventRouter::GetInstance() { return Singleton::get(); } void ExtensionWebNavigationEventRouter::Init() { if (registrar_.IsEmpty()) { registrar_.Add(this, NotificationType::FRAME_PROVISIONAL_LOAD_START, NotificationService::AllSources()); registrar_.Add(this, NotificationType::FRAME_PROVISIONAL_LOAD_COMMITTED, NotificationService::AllSources()); registrar_.Add(this, NotificationType::FRAME_DOM_CONTENT_LOADED, NotificationService::AllSources()); registrar_.Add(this, NotificationType::FRAME_DID_FINISH_LOAD, NotificationService::AllSources()); registrar_.Add(this, NotificationType::FAIL_PROVISIONAL_LOAD_WITH_ERROR, NotificationService::AllSources()); registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED, NotificationService::AllSources()); } } void ExtensionWebNavigationEventRouter::Observe( NotificationType type, const NotificationSource& source, const NotificationDetails& details) { switch (type.value) { case NotificationType::FRAME_PROVISIONAL_LOAD_START: FrameProvisionalLoadStart( Source(source).ptr(), Details(details).ptr()); break; case NotificationType::FRAME_PROVISIONAL_LOAD_COMMITTED: FrameProvisionalLoadCommitted( Source(source).ptr(), Details(details).ptr()); break; case NotificationType::FRAME_DOM_CONTENT_LOADED: FrameDomContentLoaded( Source(source).ptr(), *Details(details).ptr()); break; case NotificationType::FRAME_DID_FINISH_LOAD: FrameDidFinishLoad( Source(source).ptr(), *Details(details).ptr()); break; case NotificationType::FAIL_PROVISIONAL_LOAD_WITH_ERROR: FailProvisionalLoadWithError( Source(source).ptr(), Details(details).ptr()); break; case NotificationType::TAB_CONTENTS_DESTROYED: navigation_state_.RemoveTabContentsState( Source(source).ptr()); break; default: NOTREACHED(); } } void ExtensionWebNavigationEventRouter::FrameProvisionalLoadStart( NavigationController* controller, ProvisionalLoadDetails* details) { navigation_state_.TrackFrame(details->frame_id(), details->url(), details->main_frame(), details->is_error_page(), controller->tab_contents()); if (!navigation_state_.CanSendEvents(details->frame_id())) return; ListValue args; DictionaryValue* dict = new DictionaryValue(); dict->SetInteger(keys::kTabIdKey, ExtensionTabUtil::GetTabId(controller->tab_contents())); dict->SetString(keys::kUrlKey, details->url().spec()); dict->SetInteger(keys::kFrameIdKey, GetFrameId(details)); dict->SetInteger(keys::kRequestIdKey, 0); dict->SetReal(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now())); args.Append(dict); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(controller->profile(), keys::kOnBeforeNavigate, json_args); } void ExtensionWebNavigationEventRouter::FrameProvisionalLoadCommitted( NavigationController* controller, ProvisionalLoadDetails* details) { if (!navigation_state_.CanSendEvents(details->frame_id())) return; ListValue args; DictionaryValue* dict = new DictionaryValue(); dict->SetInteger(keys::kTabIdKey, ExtensionTabUtil::GetTabId(controller->tab_contents())); dict->SetString(keys::kUrlKey, details->url().spec()); dict->SetInteger(keys::kFrameIdKey, GetFrameId(details)); dict->SetString(keys::kTransitionTypeKey, PageTransition::CoreTransitionString( details->transition_type())); dict->SetString(keys::kTransitionQualifiersKey, PageTransition::QualifierString( details->transition_type())); dict->SetReal(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now())); args.Append(dict); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(controller->profile(), keys::kOnCommitted, json_args); } void ExtensionWebNavigationEventRouter::FrameDomContentLoaded( NavigationController* controller, int64 frame_id) { if (!navigation_state_.CanSendEvents(frame_id)) return; ListValue args; DictionaryValue* dict = new DictionaryValue(); dict->SetInteger(keys::kTabIdKey, ExtensionTabUtil::GetTabId(controller->tab_contents())); dict->SetString(keys::kUrlKey, navigation_state_.GetUrl(frame_id).spec()); dict->SetInteger(keys::kFrameIdKey, navigation_state_.IsMainFrame(frame_id) ? 0 : static_cast(frame_id)); dict->SetReal(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now())); args.Append(dict); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(controller->profile(), keys::kOnDOMContentLoaded, json_args); } void ExtensionWebNavigationEventRouter::FrameDidFinishLoad( NavigationController* controller, int64 frame_id) { if (!navigation_state_.CanSendEvents(frame_id)) return; ListValue args; DictionaryValue* dict = new DictionaryValue(); dict->SetInteger(keys::kTabIdKey, ExtensionTabUtil::GetTabId(controller->tab_contents())); dict->SetString(keys::kUrlKey, navigation_state_.GetUrl(frame_id).spec()); dict->SetInteger(keys::kFrameIdKey, navigation_state_.IsMainFrame(frame_id) ? 0 : static_cast(frame_id)); dict->SetReal(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now())); args.Append(dict); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(controller->profile(), keys::kOnCompleted, json_args); } void ExtensionWebNavigationEventRouter::FailProvisionalLoadWithError( NavigationController* controller, ProvisionalLoadDetails* details) { if (!navigation_state_.CanSendEvents(details->frame_id())) return; ListValue args; DictionaryValue* dict = new DictionaryValue(); dict->SetInteger(keys::kTabIdKey, ExtensionTabUtil::GetTabId(controller->tab_contents())); dict->SetString(keys::kUrlKey, details->url().spec()); dict->SetInteger(keys::kFrameIdKey, GetFrameId(details)); dict->SetString(keys::kErrorKey, std::string(net::ErrorToString(details->error_code()))); dict->SetReal(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now())); args.Append(dict); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); navigation_state_.ErrorOccurredInFrame(details->frame_id()); DispatchEvent(controller->profile(), keys::kOnErrorOccurred, json_args); } void ExtensionWebNavigationEventRouter::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()); } }