blob: aa6fbc70ff6194d8e2570cf0a97fdf3bc68d72ec [file] [log] [blame]
// 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/lazy_instance.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/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/common/url_constants.h"
#include "content/browser/tab_contents/navigation_details.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/common/notification_service.h"
#include "net/base/net_errors.h"
namespace keys = extension_webnavigation_api_constants;
namespace {
typedef std::map<TabContents*, ExtensionWebNavigationTabObserver*>
TabObserverMap;
static base::LazyInstance<TabObserverMap> g_tab_observer(
base::LINKER_INITIALIZED);
// URL schemes for which we'll send events.
const char* kValidSchemes[] = {
chrome::kHttpScheme,
chrome::kHttpsScheme,
chrome::kFileScheme,
chrome::kFtpScheme,
chrome::kJavaScriptScheme,
chrome::kDataScheme,
};
// Returns the frame ID as it will be passed to the extension:
// 0 if the navigation happens in the main frame, or the frame ID
// modulo 32 bits otherwise.
// Keep this in sync with the GetFrameId() function in
// extension_webrequest_api.cc.
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(content::BrowserContext* browser_context,
const char* event_name,
const std::string& json_args) {
Profile* profile = Profile::FromBrowserContext(browser_context);
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) {
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->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
args.Append(dict);
std::string json_args;
base::JSONWriter::Write(&args, false, &json_args);
DispatchEvent(tab_contents->browser_context(),
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"));
if (transition_type & PageTransition::FROM_ADDRESS_BAR)
qualifiers->Append(Value::CreateStringValue("from_address_bar"));
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->browser_context(), 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, GetFrameId(is_main_frame, 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->browser_context(),
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, GetFrameId(is_main_frame, 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->browser_context(), keys::kOnCompleted, json_args);
}
// Constructs and dispatches an onBeforeRetarget event.
void DispatchOnBeforeRetarget(TabContents* tab_contents,
content::BrowserContext* browser_context,
int64 source_frame_id,
bool source_frame_is_main_frame,
TabContents* target_tab_contents,
const GURL& target_url) {
ListValue args;
DictionaryValue* dict = new DictionaryValue();
dict->SetInteger(keys::kSourceTabIdKey,
ExtensionTabUtil::GetTabId(tab_contents));
dict->SetInteger(keys::kSourceFrameIdKey,
GetFrameId(source_frame_is_main_frame, source_frame_id));
dict->SetString(keys::kUrlKey, target_url.possibly_invalid_spec());
dict->SetInteger(keys::kTabIdKey,
ExtensionTabUtil::GetTabId(target_tab_contents));
dict->SetDouble(keys::kTimeStampKey, MilliSecondsFromTime(base::Time::Now()));
args.Append(dict);
std::string json_args;
base::JSONWriter::Write(&args, false, &json_args);
DispatchEvent(browser_context, keys::kOnBeforeRetarget, json_args);
}
// Constructs and dispatches an onErrorOccurred event.
void DispatchOnErrorOccurred(TabContents* tab_contents,
const GURL& url,
int64 frame_id,
bool is_main_frame,
int error_code) {
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::kErrorKey, 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);
DispatchEvent(tab_contents->browser_context(),
keys::kOnErrorOccurred,
json_args);
}
} // namespace
// FrameNavigationState -------------------------------------------------------
// static
bool FrameNavigationState::allow_extension_scheme_ = false;
FrameNavigationState::FrameNavigationState()
: main_frame_id_(-1) {
}
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;
}
// Allow about:blank.
if (frame_state->second.url.spec() == chrome::kAboutBlankURL)
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) {
if (is_main_frame) {
frame_state_map_.clear();
frame_ids_.clear();
}
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;
frame_state.is_navigating = true;
if (is_main_frame) {
main_frame_id_ = frame_id;
}
frame_ids_.insert(frame_id);
}
bool FrameNavigationState::IsValidFrame(int64 frame_id) const {
FrameIdToStateMap::const_iterator frame_state =
frame_state_map_.find(frame_id);
return (frame_state != frame_state_map_.end());
}
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 {
return main_frame_id_ != -1 && main_frame_id_ == frame_id;
}
int64 FrameNavigationState::GetMainFrameID() const {
return main_frame_id_;
}
void FrameNavigationState::SetErrorOccurredInFrame(int64 frame_id) {
DCHECK(frame_state_map_.find(frame_id) != frame_state_map_.end());
frame_state_map_[frame_id].error_occurred = true;
}
bool FrameNavigationState::GetErrorOccurredInFrame(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::SetNavigationCompleted(int64 frame_id) {
DCHECK(frame_state_map_.find(frame_id) != frame_state_map_.end());
frame_state_map_[frame_id].is_navigating = false;
}
bool FrameNavigationState::GetNavigationCompleted(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.is_navigating);
}
// ExtensionWebNavigtionEventRouter -------------------------------------------
ExtensionWebNavigationEventRouter::PendingTabContents::PendingTabContents()
: source_tab_contents(NULL),
source_frame_id(0),
source_frame_is_main_frame(false),
target_tab_contents(NULL),
target_url() {
}
ExtensionWebNavigationEventRouter::PendingTabContents::PendingTabContents(
TabContents* source_tab_contents,
int64 source_frame_id,
bool source_frame_is_main_frame,
TabContents* target_tab_contents,
const GURL& target_url)
: source_tab_contents(source_tab_contents),
source_frame_id(source_frame_id),
source_frame_is_main_frame(source_frame_is_main_frame),
target_tab_contents(target_tab_contents),
target_url(target_url) {
}
ExtensionWebNavigationEventRouter::PendingTabContents::~PendingTabContents() {}
ExtensionWebNavigationEventRouter::ExtensionWebNavigationEventRouter(
Profile* profile) : profile_(profile) {}
ExtensionWebNavigationEventRouter::~ExtensionWebNavigationEventRouter() {}
void ExtensionWebNavigationEventRouter::Init() {
if (registrar_.IsEmpty()) {
registrar_.Add(this,
content::NOTIFICATION_RETARGETING,
Source<content::BrowserContext>(profile_));
registrar_.Add(this,
content::NOTIFICATION_TAB_ADDED,
NotificationService::AllSources());
registrar_.Add(this,
content::NOTIFICATION_TAB_CONTENTS_DESTROYED,
NotificationService::AllSources());
}
}
void ExtensionWebNavigationEventRouter::Observe(
int type,
const NotificationSource& source,
const NotificationDetails& details) {
switch (type) {
case content::NOTIFICATION_RETARGETING:
Retargeting(Details<const content::RetargetingDetails>(details).ptr());
break;
case content::NOTIFICATION_TAB_ADDED:
TabAdded(Details<TabContents>(details).ptr());
break;
case content::NOTIFICATION_TAB_CONTENTS_DESTROYED:
TabDestroyed(Source<TabContents>(source).ptr());
break;
default:
NOTREACHED();
}
}
void ExtensionWebNavigationEventRouter::Retargeting(
const content::RetargetingDetails* details) {
if (details->source_frame_id == 0)
return;
ExtensionWebNavigationTabObserver* tab_observer =
ExtensionWebNavigationTabObserver::Get(details->source_tab_contents);
if (!tab_observer) {
NOTREACHED();
return;
}
const FrameNavigationState& frame_navigation_state =
tab_observer->frame_navigation_state();
if (!frame_navigation_state.CanSendEvents(details->source_frame_id))
return;
// If the TabContents was created as a response to an IPC from a renderer, it
// doesn't yet have a wrapper, and we need to delay the extension event until
// the TabContents is fully initialized.
if (TabContentsWrapper::GetCurrentWrapperForContents(
details->target_tab_contents) == NULL) {
pending_tab_contents_[details->target_tab_contents] =
PendingTabContents(
details->source_tab_contents,
details->source_frame_id,
frame_navigation_state.IsMainFrame(details->source_frame_id),
details->target_tab_contents,
details->target_url);
} else {
DispatchOnBeforeRetarget(
details->source_tab_contents,
details->target_tab_contents->browser_context(),
details->source_frame_id,
frame_navigation_state.IsMainFrame(details->source_frame_id),
details->target_tab_contents,
details->target_url);
}
}
void ExtensionWebNavigationEventRouter::TabAdded(TabContents* tab_contents) {
std::map<TabContents*, PendingTabContents>::iterator iter =
pending_tab_contents_.find(tab_contents);
if (iter == pending_tab_contents_.end())
return;
DispatchOnBeforeRetarget(iter->second.source_tab_contents,
iter->second.target_tab_contents->browser_context(),
iter->second.source_frame_id,
iter->second.source_frame_is_main_frame,
iter->second.target_tab_contents,
iter->second.target_url);
pending_tab_contents_.erase(iter);
}
void ExtensionWebNavigationEventRouter::TabDestroyed(
TabContents* tab_contents) {
pending_tab_contents_.erase(tab_contents);
for (std::map<TabContents*, PendingTabContents>::iterator i =
pending_tab_contents_.begin(); i != pending_tab_contents_.end(); ) {
if (i->second.source_tab_contents == tab_contents)
pending_tab_contents_.erase(i++);
else
++i;
}
}
// ExtensionWebNavigationTabObserver ------------------------------------------
ExtensionWebNavigationTabObserver::ExtensionWebNavigationTabObserver(
TabContents* tab_contents)
: TabContentsObserver(tab_contents) {
g_tab_observer.Get().insert(TabObserverMap::value_type(tab_contents, this));
}
ExtensionWebNavigationTabObserver::~ExtensionWebNavigationTabObserver() {}
// static
ExtensionWebNavigationTabObserver* ExtensionWebNavigationTabObserver::Get(
TabContents* tab_contents) {
TabObserverMap::iterator i = g_tab_observer.Get().find(tab_contents);
return i == g_tab_observer.Get().end() ? NULL : i->second;
}
void ExtensionWebNavigationTabObserver::DidStartProvisionalLoadForFrame(
int64 frame_id,
bool is_main_frame,
const GURL& validated_url,
bool is_error_page,
RenderViewHost* render_view_host) {
navigation_state_.TrackFrame(frame_id,
validated_url,
is_main_frame,
is_error_page);
if (!navigation_state_.CanSendEvents(frame_id))
return;
DispatchOnBeforeNavigate(
tab_contents(), frame_id, is_main_frame, validated_url);
}
void ExtensionWebNavigationTabObserver::DidCommitProvisionalLoadForFrame(
int64 frame_id,
bool is_main_frame,
const GURL& url,
PageTransition::Type transition_type) {
if (!navigation_state_.CanSendEvents(frame_id))
return;
bool is_reference_fragment_navigation =
IsReferenceFragmentNavigation(frame_id, url);
// Update the URL as it might have changed.
navigation_state_.TrackFrame(frame_id,
url,
is_main_frame,
false);
// 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 (is_reference_fragment_navigation) {
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;
navigation_state_.SetErrorOccurredInFrame(frame_id);
DispatchOnErrorOccurred(
tab_contents(), validated_url, frame_id, is_main_frame, error_code);
}
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;
navigation_state_.SetNavigationCompleted(frame_id);
DispatchOnCompleted(tab_contents(),
navigation_state_.GetUrl(frame_id),
navigation_state_.IsMainFrame(frame_id),
frame_id);
}
void ExtensionWebNavigationTabObserver::TabContentsDestroyed(
TabContents* tab) {
g_tab_observer.Get().erase(tab);
for (FrameNavigationState::const_iterator frame = navigation_state_.begin();
frame != navigation_state_.end(); ++frame) {
if (!navigation_state_.GetNavigationCompleted(*frame) &&
navigation_state_.CanSendEvents(*frame)) {
DispatchOnErrorOccurred(
tab,
navigation_state_.GetUrl(*frame),
*frame,
navigation_state_.IsMainFrame(*frame),
net::ERR_ABORTED);
}
}
}
// 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) {
DispatchOnBeforeNavigate(tab_contents(),
frame_id,
is_main_frame,
url);
DispatchOnCommitted(tab_contents(),
frame_id,
is_main_frame,
url,
transition_type);
DispatchOnDOMContentLoaded(tab_contents(),
url,
is_main_frame,
frame_id);
navigation_state_.SetNavigationCompleted(frame_id);
DispatchOnCompleted(tab_contents(),
url,
is_main_frame,
frame_id);
}
bool GetFrameFunction::RunImpl() {
DictionaryValue* details;
EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &details));
DCHECK(details);
int tab_id;
int frame_id;
EXTENSION_FUNCTION_VALIDATE(details->GetInteger(keys::kTabIdKey, &tab_id));
EXTENSION_FUNCTION_VALIDATE(
details->GetInteger(keys::kFrameIdKey, &frame_id));
result_.reset(Value::CreateNullValue());
TabContentsWrapper* wrapper;
if (!ExtensionTabUtil::GetTabById(
tab_id, profile(), include_incognito(), NULL, NULL, &wrapper, NULL) ||
!wrapper) {
return true;
}
TabContents* tab_contents = wrapper->tab_contents();
ExtensionWebNavigationTabObserver* observer =
ExtensionWebNavigationTabObserver::Get(tab_contents);
DCHECK(observer);
const FrameNavigationState& frame_navigation_state =
observer->frame_navigation_state();
if (frame_id == 0)
frame_id = frame_navigation_state.GetMainFrameID();
if (!frame_navigation_state.IsValidFrame(frame_id))
return true;
DictionaryValue* resultDict = new DictionaryValue();
resultDict->SetString(
keys::kUrlKey,
frame_navigation_state.GetUrl(frame_id).spec());
resultDict->SetBoolean(
keys::kErrorOccurredKey,
frame_navigation_state.GetErrorOccurredInFrame(frame_id));
result_.reset(resultDict);
return true;
}