blob: 2e3a1ca0c852453e8ca911295a0dd4281775f1a3 [file] [log] [blame]
// 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<int>(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<FrameIdIterator, FrameIdIterator> 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<ExtensionWebNavigationEventRouter>::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<NavigationController>(source).ptr(),
Details<ProvisionalLoadDetails>(details).ptr());
break;
case NotificationType::FRAME_PROVISIONAL_LOAD_COMMITTED:
FrameProvisionalLoadCommitted(
Source<NavigationController>(source).ptr(),
Details<ProvisionalLoadDetails>(details).ptr());
break;
case NotificationType::FRAME_DOM_CONTENT_LOADED:
FrameDomContentLoaded(
Source<NavigationController>(source).ptr(),
*Details<int64>(details).ptr());
break;
case NotificationType::FRAME_DID_FINISH_LOAD:
FrameDidFinishLoad(
Source<NavigationController>(source).ptr(),
*Details<int64>(details).ptr());
break;
case NotificationType::FAIL_PROVISIONAL_LOAD_WITH_ERROR:
FailProvisionalLoadWithError(
Source<NavigationController>(source).ptr(),
Details<ProvisionalLoadDetails>(details).ptr());
break;
case NotificationType::TAB_CONTENTS_DESTROYED:
navigation_state_.RemoveTabContentsState(
Source<TabContents>(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<int>(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<int>(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());
}
}