普通文本  |  526行  |  18.2 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_(ui::PAGE_TRANSITION_TYPED),
      has_post_data_(false),
      post_id_(-1),
      is_overriding_user_agent_(false),
      http_status_code_(0),
      is_restored_(false),
      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();
  navigation.is_restored_ = entry.IsRestored();
  // 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();
  navigation.redirect_chain_ = entry.GetRedirectChain();

  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()),
      static_cast<blink::WebReferrerPolicy>(sync_data.referrer_policy()));
  navigation.virtual_url_ = GURL(sync_data.virtual_url());
  navigation.title_ = base::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 = ui::PAGE_TRANSITION_LINK;
        break;
      case sync_pb::SyncEnums_PageTransition_TYPED:
        transition = ui::PAGE_TRANSITION_TYPED;
        break;
      case sync_pb::SyncEnums_PageTransition_AUTO_BOOKMARK:
        transition = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
        break;
      case sync_pb::SyncEnums_PageTransition_AUTO_SUBFRAME:
        transition = ui::PAGE_TRANSITION_AUTO_SUBFRAME;
        break;
      case sync_pb::SyncEnums_PageTransition_MANUAL_SUBFRAME:
        transition = ui::PAGE_TRANSITION_MANUAL_SUBFRAME;
        break;
      case sync_pb::SyncEnums_PageTransition_GENERATED:
        transition = ui::PAGE_TRANSITION_GENERATED;
        break;
      case sync_pb::SyncEnums_PageTransition_AUTO_TOPLEVEL:
        transition = ui::PAGE_TRANSITION_AUTO_TOPLEVEL;
        break;
      case sync_pb::SyncEnums_PageTransition_FORM_SUBMIT:
        transition = ui::PAGE_TRANSITION_FORM_SUBMIT;
        break;
      case sync_pb::SyncEnums_PageTransition_RELOAD:
        transition = ui::PAGE_TRANSITION_RELOAD;
        break;
      case sync_pb::SyncEnums_PageTransition_KEYWORD:
        transition = ui::PAGE_TRANSITION_KEYWORD;
        break;
      case sync_pb::SyncEnums_PageTransition_KEYWORD_GENERATED:
        transition = ui::PAGE_TRANSITION_KEYWORD_GENERATED;
        break;
      default:
        transition = ui::PAGE_TRANSITION_LINK;
        break;
    }
  }

  if  (sync_data.has_redirect_type()) {
    switch (sync_data.redirect_type()) {
      case sync_pb::SyncEnums_PageTransitionRedirectType_CLIENT_REDIRECT:
        transition |= ui::PAGE_TRANSITION_CLIENT_REDIRECT;
        break;
      case sync_pb::SyncEnums_PageTransitionRedirectType_SERVER_REDIRECT:
        transition |= ui::PAGE_TRANSITION_SERVER_REDIRECT;
        break;
    }
  }
  if (sync_data.navigation_forward_back())
      transition |= ui::PAGE_TRANSITION_FORWARD_BACK;
  if (sync_data.navigation_from_address_bar())
      transition |= ui::PAGE_TRANSITION_FROM_ADDRESS_BAR;
  if (sync_data.navigation_home_page())
      transition |= ui::PAGE_TRANSITION_HOME_PAGE;
  if (sync_data.navigation_chain_start())
      transition |= ui::PAGE_TRANSITION_CHAIN_START;
  if (sync_data.navigation_chain_end())
      transition |= ui::PAGE_TRANSITION_CHAIN_END;

  navigation.transition_type_ = static_cast<ui::PageTransition>(transition);

  navigation.timestamp_ = base::Time();
  navigation.search_terms_ = base::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();

  navigation.Sanitize();

  navigation.is_restored_ = true;

  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(base::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_ = ui::PageTransitionFromInt(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;
  }

  Sanitize();

  is_restored_ = true;

  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.
          ui::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_);
  entry->SetRedirectChain(redirect_chain_);

  // 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());
  sync_data.set_referrer(referrer_.url.spec());
  sync_data.set_referrer_policy(referrer_.policy);
  sync_data.set_title(base::UTF16ToUTF8(title_));

  // Page transition core.
  COMPILE_ASSERT(ui::PAGE_TRANSITION_LAST_CORE ==
                 ui::PAGE_TRANSITION_KEYWORD_GENERATED,
                 PageTransitionCoreBounds);
  switch (ui::PageTransitionStripQualifier(transition_type_)) {
    case ui::PAGE_TRANSITION_LINK:
      sync_data.set_page_transition(
          sync_pb::SyncEnums_PageTransition_LINK);
      break;
    case ui::PAGE_TRANSITION_TYPED:
      sync_data.set_page_transition(
          sync_pb::SyncEnums_PageTransition_TYPED);
      break;
    case ui::PAGE_TRANSITION_AUTO_BOOKMARK:
      sync_data.set_page_transition(
          sync_pb::SyncEnums_PageTransition_AUTO_BOOKMARK);
      break;
    case ui::PAGE_TRANSITION_AUTO_SUBFRAME:
      sync_data.set_page_transition(
        sync_pb::SyncEnums_PageTransition_AUTO_SUBFRAME);
      break;
    case ui::PAGE_TRANSITION_MANUAL_SUBFRAME:
      sync_data.set_page_transition(
        sync_pb::SyncEnums_PageTransition_MANUAL_SUBFRAME);
      break;
    case ui::PAGE_TRANSITION_GENERATED:
      sync_data.set_page_transition(
        sync_pb::SyncEnums_PageTransition_GENERATED);
      break;
    case ui::PAGE_TRANSITION_AUTO_TOPLEVEL:
      sync_data.set_page_transition(
        sync_pb::SyncEnums_PageTransition_AUTO_TOPLEVEL);
      break;
    case ui::PAGE_TRANSITION_FORM_SUBMIT:
      sync_data.set_page_transition(
        sync_pb::SyncEnums_PageTransition_FORM_SUBMIT);
      break;
    case ui::PAGE_TRANSITION_RELOAD:
      sync_data.set_page_transition(
        sync_pb::SyncEnums_PageTransition_RELOAD);
      break;
    case ui::PAGE_TRANSITION_KEYWORD:
      sync_data.set_page_transition(
        sync_pb::SyncEnums_PageTransition_KEYWORD);
      break;
    case ui::PAGE_TRANSITION_KEYWORD_GENERATED:
      sync_data.set_page_transition(
        sync_pb::SyncEnums_PageTransition_KEYWORD_GENERATED);
      break;
    default:
      NOTREACHED();
  }

  // Page transition qualifiers.
  if (ui::PageTransitionIsRedirect(transition_type_)) {
    if (transition_type_ & ui::PAGE_TRANSITION_CLIENT_REDIRECT) {
      sync_data.set_redirect_type(
        sync_pb::SyncEnums_PageTransitionRedirectType_CLIENT_REDIRECT);
    } else if (transition_type_ & ui::PAGE_TRANSITION_SERVER_REDIRECT) {
      sync_data.set_redirect_type(
        sync_pb::SyncEnums_PageTransitionRedirectType_SERVER_REDIRECT);
    }
  }
  sync_data.set_navigation_forward_back(
      (transition_type_ & ui::PAGE_TRANSITION_FORWARD_BACK) != 0);
  sync_data.set_navigation_from_address_bar(
      (transition_type_ & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) != 0);
  sync_data.set_navigation_home_page(
      (transition_type_ & ui::PAGE_TRANSITION_HOME_PAGE) != 0);
  sync_data.set_navigation_chain_start(
      (transition_type_ & ui::PAGE_TRANSITION_CHAIN_START) != 0);
  sync_data.set_navigation_chain_end(
      (transition_type_ & ui::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(base::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);
  }

  // Copy all redirect chain entries except the last URL (which should match
  // the virtual_url).
  if (redirect_chain_.size() > 1) {  // Single entry chains have no redirection.
    size_t last_entry = redirect_chain_.size() - 1;
    for (size_t i = 0; i < last_entry; i++) {
      sync_pb::NavigationRedirect* navigation_redirect =
          sync_data.add_navigation_redirect();
      navigation_redirect->set_url(redirect_chain_[i].spec());
    }
    // If the last URL didn't match the virtual_url, record it separately.
    if (sync_data.virtual_url() != redirect_chain_[last_entry].spec()) {
      sync_data.set_last_navigation_redirect_url(
          redirect_chain_[last_entry].spec());
    }
  }

  sync_data.set_is_restored(is_restored_);

  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;
}

void SerializedNavigationEntry::Sanitize() {
  content::Referrer new_referrer =
      content::Referrer::SanitizeForRequest(virtual_url_, referrer_);

  // No need to compare the policy, as it doesn't change during
  // sanitization. If there has been a change, the referrer needs to be
  // stripped from the page state as well.
  if (referrer_.url != new_referrer.url) {
    referrer_ = content::Referrer();
    page_state_ = page_state_.RemoveReferrer();
  }
}

}  // namespace sessions