// 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<int>(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<int>(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<int>(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<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);
}
// ExtensionWebNavigtionEventRouter -------------------------------------------
ExtensionWebNavigationEventRouter::ExtensionWebNavigationEventRouter() {}
ExtensionWebNavigationEventRouter::~ExtensionWebNavigationEventRouter() {}
// static
ExtensionWebNavigationEventRouter*
ExtensionWebNavigationEventRouter::GetInstance() {
return Singleton<ExtensionWebNavigationEventRouter>::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<TabContents>(source).ptr(),
Details<const ViewHostMsg_CreateWindow_Params>(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<char> 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);
}