普通文本  |  489行  |  17.08 KB

// 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(&timestamp_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