// Copyright 2013 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.
#include "components/sessions/serialized_navigation_entry.h"
#include "base/pickle.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/favicon_status.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "sync/protocol/session_specifics.pb.h"
#include "sync/util/time.h"
#include "third_party/WebKit/public/platform/WebReferrerPolicy.h"
using content::NavigationEntry;
namespace sessions {
const char kSearchTermsKey[] = "search_terms";
SerializedNavigationEntry::SerializedNavigationEntry()
: index_(-1),
unique_id_(0),
transition_type_(content::PAGE_TRANSITION_TYPED),
has_post_data_(false),
post_id_(-1),
is_overriding_user_agent_(false),
http_status_code_(0),
blocked_state_(STATE_INVALID) {}
SerializedNavigationEntry::~SerializedNavigationEntry() {}
// static
SerializedNavigationEntry SerializedNavigationEntry::FromNavigationEntry(
int index,
const NavigationEntry& entry) {
SerializedNavigationEntry navigation;
navigation.index_ = index;
navigation.unique_id_ = entry.GetUniqueID();
navigation.referrer_ = entry.GetReferrer();
navigation.virtual_url_ = entry.GetVirtualURL();
navigation.title_ = entry.GetTitle();
navigation.page_state_ = entry.GetPageState();
navigation.transition_type_ = entry.GetTransitionType();
navigation.has_post_data_ = entry.GetHasPostData();
navigation.post_id_ = entry.GetPostID();
navigation.original_request_url_ = entry.GetOriginalRequestURL();
navigation.is_overriding_user_agent_ = entry.GetIsOverridingUserAgent();
navigation.timestamp_ = entry.GetTimestamp();
// If you want to navigate a named frame in Chrome, you will first need to
// add support for persisting it. It is currently only used for layout tests.
CHECK(entry.GetFrameToNavigate().empty());
entry.GetExtraData(kSearchTermsKey, &navigation.search_terms_);
if (entry.GetFavicon().valid)
navigation.favicon_url_ = entry.GetFavicon().url;
navigation.http_status_code_ = entry.GetHttpStatusCode();
return navigation;
}
SerializedNavigationEntry SerializedNavigationEntry::FromSyncData(
int index,
const sync_pb::TabNavigation& sync_data) {
SerializedNavigationEntry navigation;
navigation.index_ = index;
navigation.unique_id_ = sync_data.unique_id();
navigation.referrer_ =
content::Referrer(GURL(sync_data.referrer()),
blink::WebReferrerPolicyDefault);
navigation.virtual_url_ = GURL(sync_data.virtual_url());
navigation.title_ = UTF8ToUTF16(sync_data.title());
navigation.page_state_ =
content::PageState::CreateFromEncodedData(sync_data.state());
uint32 transition = 0;
if (sync_data.has_page_transition()) {
switch (sync_data.page_transition()) {
case sync_pb::SyncEnums_PageTransition_LINK:
transition = content::PAGE_TRANSITION_LINK;
break;
case sync_pb::SyncEnums_PageTransition_TYPED:
transition = content::PAGE_TRANSITION_TYPED;
break;
case sync_pb::SyncEnums_PageTransition_AUTO_BOOKMARK:
transition = content::PAGE_TRANSITION_AUTO_BOOKMARK;
break;
case sync_pb::SyncEnums_PageTransition_AUTO_SUBFRAME:
transition = content::PAGE_TRANSITION_AUTO_SUBFRAME;
break;
case sync_pb::SyncEnums_PageTransition_MANUAL_SUBFRAME:
transition = content::PAGE_TRANSITION_MANUAL_SUBFRAME;
break;
case sync_pb::SyncEnums_PageTransition_GENERATED:
transition = content::PAGE_TRANSITION_GENERATED;
break;
case sync_pb::SyncEnums_PageTransition_AUTO_TOPLEVEL:
transition = content::PAGE_TRANSITION_AUTO_TOPLEVEL;
break;
case sync_pb::SyncEnums_PageTransition_FORM_SUBMIT:
transition = content::PAGE_TRANSITION_FORM_SUBMIT;
break;
case sync_pb::SyncEnums_PageTransition_RELOAD:
transition = content::PAGE_TRANSITION_RELOAD;
break;
case sync_pb::SyncEnums_PageTransition_KEYWORD:
transition = content::PAGE_TRANSITION_KEYWORD;
break;
case sync_pb::SyncEnums_PageTransition_KEYWORD_GENERATED:
transition =
content::PAGE_TRANSITION_KEYWORD_GENERATED;
break;
default:
transition = content::PAGE_TRANSITION_LINK;
break;
}
}
if (sync_data.has_redirect_type()) {
switch (sync_data.redirect_type()) {
case sync_pb::SyncEnums_PageTransitionRedirectType_CLIENT_REDIRECT:
transition |= content::PAGE_TRANSITION_CLIENT_REDIRECT;
break;
case sync_pb::SyncEnums_PageTransitionRedirectType_SERVER_REDIRECT:
transition |= content::PAGE_TRANSITION_SERVER_REDIRECT;
break;
}
}
if (sync_data.navigation_forward_back())
transition |= content::PAGE_TRANSITION_FORWARD_BACK;
if (sync_data.navigation_from_address_bar())
transition |= content::PAGE_TRANSITION_FROM_ADDRESS_BAR;
if (sync_data.navigation_home_page())
transition |= content::PAGE_TRANSITION_HOME_PAGE;
if (sync_data.navigation_chain_start())
transition |= content::PAGE_TRANSITION_CHAIN_START;
if (sync_data.navigation_chain_end())
transition |= content::PAGE_TRANSITION_CHAIN_END;
navigation.transition_type_ =
static_cast<content::PageTransition>(transition);
navigation.timestamp_ = base::Time();
navigation.search_terms_ = UTF8ToUTF16(sync_data.search_terms());
if (sync_data.has_favicon_url())
navigation.favicon_url_ = GURL(sync_data.favicon_url());
navigation.http_status_code_ = sync_data.http_status_code();
// We shouldn't sync session data for managed users down at the moment.
DCHECK(!sync_data.has_blocked_state());
DCHECK_EQ(0, sync_data.content_pack_categories_size());
return navigation;
}
namespace {
// Helper used by SerializedNavigationEntry::WriteToPickle(). It writes |str| to
// |pickle|, if and only if |str| fits within (|max_bytes| -
// |*bytes_written|). |bytes_written| is incremented to reflect the
// data written.
//
// TODO(akalin): Unify this with the same function in
// base_session_service.cc.
void WriteStringToPickle(Pickle* pickle,
int* bytes_written,
int max_bytes,
const std::string& str) {
int num_bytes = str.size() * sizeof(char);
if (*bytes_written + num_bytes < max_bytes) {
*bytes_written += num_bytes;
pickle->WriteString(str);
} else {
pickle->WriteString(std::string());
}
}
// base::string16 version of WriteStringToPickle.
//
// TODO(akalin): Unify this, too.
void WriteString16ToPickle(Pickle* pickle,
int* bytes_written,
int max_bytes,
const base::string16& str) {
int num_bytes = str.size() * sizeof(char16);
if (*bytes_written + num_bytes < max_bytes) {
*bytes_written += num_bytes;
pickle->WriteString16(str);
} else {
pickle->WriteString16(base::string16());
}
}
// A mask used for arbitrary boolean values needed to represent a
// NavigationEntry. Currently only contains HAS_POST_DATA.
//
// NOTE(akalin): We may want to just serialize |has_post_data_|
// directly. Other bools (|is_overriding_user_agent_|) haven't been
// added to this mask.
enum TypeMask {
HAS_POST_DATA = 1
};
} // namespace
// Pickle order:
//
// index_
// virtual_url_
// title_
// page_state_
// transition_type_
//
// Added on later:
//
// type_mask (has_post_data_)
// referrer_
// original_request_url_
// is_overriding_user_agent_
// timestamp_
// search_terms_
// http_status_code_
void SerializedNavigationEntry::WriteToPickle(int max_size,
Pickle* pickle) const {
pickle->WriteInt(index_);
int bytes_written = 0;
WriteStringToPickle(pickle, &bytes_written, max_size,
virtual_url_.spec());
WriteString16ToPickle(pickle, &bytes_written, max_size, title_);
content::PageState page_state = page_state_;
if (has_post_data_)
page_state = page_state.RemovePasswordData();
WriteStringToPickle(pickle, &bytes_written, max_size,
page_state.ToEncodedData());
pickle->WriteInt(transition_type_);
const int type_mask = has_post_data_ ? HAS_POST_DATA : 0;
pickle->WriteInt(type_mask);
WriteStringToPickle(
pickle, &bytes_written, max_size,
referrer_.url.is_valid() ? referrer_.url.spec() : std::string());
pickle->WriteInt(referrer_.policy);
// Save info required to override the user agent.
WriteStringToPickle(
pickle, &bytes_written, max_size,
original_request_url_.is_valid() ?
original_request_url_.spec() : std::string());
pickle->WriteBool(is_overriding_user_agent_);
pickle->WriteInt64(timestamp_.ToInternalValue());
WriteString16ToPickle(pickle, &bytes_written, max_size, search_terms_);
pickle->WriteInt(http_status_code_);
}
bool SerializedNavigationEntry::ReadFromPickle(PickleIterator* iterator) {
*this = SerializedNavigationEntry();
std::string virtual_url_spec, page_state_data;
int transition_type_int = 0;
if (!iterator->ReadInt(&index_) ||
!iterator->ReadString(&virtual_url_spec) ||
!iterator->ReadString16(&title_) ||
!iterator->ReadString(&page_state_data) ||
!iterator->ReadInt(&transition_type_int))
return false;
virtual_url_ = GURL(virtual_url_spec);
page_state_ = content::PageState::CreateFromEncodedData(page_state_data);
transition_type_ = static_cast<content::PageTransition>(transition_type_int);
// type_mask did not always exist in the written stream. As such, we
// don't fail if it can't be read.
int type_mask = 0;
bool has_type_mask = iterator->ReadInt(&type_mask);
if (has_type_mask) {
has_post_data_ = type_mask & HAS_POST_DATA;
// the "referrer" property was added after type_mask to the written
// stream. As such, we don't fail if it can't be read.
std::string referrer_spec;
if (!iterator->ReadString(&referrer_spec))
referrer_spec = std::string();
// The "referrer policy" property was added even later, so we fall back to
// the default policy if the property is not present.
int policy_int;
blink::WebReferrerPolicy policy;
if (iterator->ReadInt(&policy_int))
policy = static_cast<blink::WebReferrerPolicy>(policy_int);
else
policy = blink::WebReferrerPolicyDefault;
referrer_ = content::Referrer(GURL(referrer_spec), policy);
// If the original URL can't be found, leave it empty.
std::string original_request_url_spec;
if (!iterator->ReadString(&original_request_url_spec))
original_request_url_spec = std::string();
original_request_url_ = GURL(original_request_url_spec);
// Default to not overriding the user agent if we don't have info.
if (!iterator->ReadBool(&is_overriding_user_agent_))
is_overriding_user_agent_ = false;
int64 timestamp_internal_value = 0;
if (iterator->ReadInt64(×tamp_internal_value)) {
timestamp_ = base::Time::FromInternalValue(timestamp_internal_value);
} else {
timestamp_ = base::Time();
}
// If the search terms field can't be found, leave it empty.
if (!iterator->ReadString16(&search_terms_))
search_terms_.clear();
if (!iterator->ReadInt(&http_status_code_))
http_status_code_ = 0;
}
return true;
}
scoped_ptr<NavigationEntry> SerializedNavigationEntry::ToNavigationEntry(
int page_id,
content::BrowserContext* browser_context) const {
scoped_ptr<NavigationEntry> entry(
content::NavigationController::CreateNavigationEntry(
virtual_url_,
referrer_,
// Use a transition type of reload so that we don't incorrectly
// increase the typed count.
content::PAGE_TRANSITION_RELOAD,
false,
// The extra headers are not sync'ed across sessions.
std::string(),
browser_context));
entry->SetTitle(title_);
entry->SetPageState(page_state_);
entry->SetPageID(page_id);
entry->SetHasPostData(has_post_data_);
entry->SetPostID(post_id_);
entry->SetOriginalRequestURL(original_request_url_);
entry->SetIsOverridingUserAgent(is_overriding_user_agent_);
entry->SetTimestamp(timestamp_);
entry->SetExtraData(kSearchTermsKey, search_terms_);
entry->SetHttpStatusCode(http_status_code_);
// These fields should have default values.
DCHECK_EQ(STATE_INVALID, blocked_state_);
DCHECK_EQ(0u, content_pack_categories_.size());
return entry.Pass();
}
// TODO(zea): perhaps sync state (scroll position, form entries, etc.) as well?
// See http://crbug.com/67068.
sync_pb::TabNavigation SerializedNavigationEntry::ToSyncData() const {
sync_pb::TabNavigation sync_data;
sync_data.set_virtual_url(virtual_url_.spec());
// FIXME(zea): Support referrer policy?
sync_data.set_referrer(referrer_.url.spec());
sync_data.set_title(UTF16ToUTF8(title_));
// Page transition core.
COMPILE_ASSERT(content::PAGE_TRANSITION_LAST_CORE ==
content::PAGE_TRANSITION_KEYWORD_GENERATED,
PageTransitionCoreBounds);
switch (PageTransitionStripQualifier(transition_type_)) {
case content::PAGE_TRANSITION_LINK:
sync_data.set_page_transition(
sync_pb::SyncEnums_PageTransition_LINK);
break;
case content::PAGE_TRANSITION_TYPED:
sync_data.set_page_transition(
sync_pb::SyncEnums_PageTransition_TYPED);
break;
case content::PAGE_TRANSITION_AUTO_BOOKMARK:
sync_data.set_page_transition(
sync_pb::SyncEnums_PageTransition_AUTO_BOOKMARK);
break;
case content::PAGE_TRANSITION_AUTO_SUBFRAME:
sync_data.set_page_transition(
sync_pb::SyncEnums_PageTransition_AUTO_SUBFRAME);
break;
case content::PAGE_TRANSITION_MANUAL_SUBFRAME:
sync_data.set_page_transition(
sync_pb::SyncEnums_PageTransition_MANUAL_SUBFRAME);
break;
case content::PAGE_TRANSITION_GENERATED:
sync_data.set_page_transition(
sync_pb::SyncEnums_PageTransition_GENERATED);
break;
case content::PAGE_TRANSITION_AUTO_TOPLEVEL:
sync_data.set_page_transition(
sync_pb::SyncEnums_PageTransition_AUTO_TOPLEVEL);
break;
case content::PAGE_TRANSITION_FORM_SUBMIT:
sync_data.set_page_transition(
sync_pb::SyncEnums_PageTransition_FORM_SUBMIT);
break;
case content::PAGE_TRANSITION_RELOAD:
sync_data.set_page_transition(
sync_pb::SyncEnums_PageTransition_RELOAD);
break;
case content::PAGE_TRANSITION_KEYWORD:
sync_data.set_page_transition(
sync_pb::SyncEnums_PageTransition_KEYWORD);
break;
case content::PAGE_TRANSITION_KEYWORD_GENERATED:
sync_data.set_page_transition(
sync_pb::SyncEnums_PageTransition_KEYWORD_GENERATED);
break;
default:
NOTREACHED();
}
// Page transition qualifiers.
if (PageTransitionIsRedirect(transition_type_)) {
if (transition_type_ & content::PAGE_TRANSITION_CLIENT_REDIRECT) {
sync_data.set_redirect_type(
sync_pb::SyncEnums_PageTransitionRedirectType_CLIENT_REDIRECT);
} else if (transition_type_ & content::PAGE_TRANSITION_SERVER_REDIRECT) {
sync_data.set_redirect_type(
sync_pb::SyncEnums_PageTransitionRedirectType_SERVER_REDIRECT);
}
}
sync_data.set_navigation_forward_back(
(transition_type_ & content::PAGE_TRANSITION_FORWARD_BACK) != 0);
sync_data.set_navigation_from_address_bar(
(transition_type_ & content::PAGE_TRANSITION_FROM_ADDRESS_BAR) != 0);
sync_data.set_navigation_home_page(
(transition_type_ & content::PAGE_TRANSITION_HOME_PAGE) != 0);
sync_data.set_navigation_chain_start(
(transition_type_ & content::PAGE_TRANSITION_CHAIN_START) != 0);
sync_data.set_navigation_chain_end(
(transition_type_ & content::PAGE_TRANSITION_CHAIN_END) != 0);
sync_data.set_unique_id(unique_id_);
sync_data.set_timestamp_msec(syncer::TimeToProtoTime(timestamp_));
// The full-resolution timestamp works as a global ID.
sync_data.set_global_id(timestamp_.ToInternalValue());
sync_data.set_search_terms(UTF16ToUTF8(search_terms_));
sync_data.set_http_status_code(http_status_code_);
if (favicon_url_.is_valid())
sync_data.set_favicon_url(favicon_url_.spec());
if (blocked_state_ != STATE_INVALID) {
sync_data.set_blocked_state(
static_cast<sync_pb::TabNavigation_BlockedState>(blocked_state_));
}
for (std::set<std::string>::const_iterator it =
content_pack_categories_.begin();
it != content_pack_categories_.end(); ++it) {
sync_data.add_content_pack_categories(*it);
}
return sync_data;
}
// static
std::vector<NavigationEntry*> SerializedNavigationEntry::ToNavigationEntries(
const std::vector<SerializedNavigationEntry>& navigations,
content::BrowserContext* browser_context) {
int page_id = 0;
std::vector<NavigationEntry*> entries;
for (std::vector<SerializedNavigationEntry>::const_iterator
it = navigations.begin(); it != navigations.end(); ++it) {
entries.push_back(
it->ToNavigationEntry(page_id, browser_context).release());
++page_id;
}
return entries;
}
} // namespace sessions