1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "components/sessions/serialized_navigation_entry.h" 6 7 #include "base/pickle.h" 8 #include "base/strings/utf_string_conversions.h" 9 #include "content/public/browser/favicon_status.h" 10 #include "content/public/browser/navigation_controller.h" 11 #include "content/public/browser/navigation_entry.h" 12 #include "sync/protocol/session_specifics.pb.h" 13 #include "sync/util/time.h" 14 #include "third_party/WebKit/public/platform/WebReferrerPolicy.h" 15 16 using content::NavigationEntry; 17 18 namespace sessions { 19 20 const char kSearchTermsKey[] = "search_terms"; 21 22 SerializedNavigationEntry::SerializedNavigationEntry() 23 : index_(-1), 24 unique_id_(0), 25 transition_type_(content::PAGE_TRANSITION_TYPED), 26 has_post_data_(false), 27 post_id_(-1), 28 is_overriding_user_agent_(false), 29 blocked_state_(STATE_INVALID) {} 30 31 SerializedNavigationEntry::~SerializedNavigationEntry() {} 32 33 // static 34 SerializedNavigationEntry SerializedNavigationEntry::FromNavigationEntry( 35 int index, 36 const NavigationEntry& entry) { 37 SerializedNavigationEntry navigation; 38 navigation.index_ = index; 39 navigation.unique_id_ = entry.GetUniqueID(); 40 navigation.referrer_ = entry.GetReferrer(); 41 navigation.virtual_url_ = entry.GetVirtualURL(); 42 navigation.title_ = entry.GetTitle(); 43 navigation.page_state_ = entry.GetPageState(); 44 navigation.transition_type_ = entry.GetTransitionType(); 45 navigation.has_post_data_ = entry.GetHasPostData(); 46 navigation.post_id_ = entry.GetPostID(); 47 navigation.original_request_url_ = entry.GetOriginalRequestURL(); 48 navigation.is_overriding_user_agent_ = entry.GetIsOverridingUserAgent(); 49 navigation.timestamp_ = entry.GetTimestamp(); 50 // If you want to navigate a named frame in Chrome, you will first need to 51 // add support for persisting it. It is currently only used for layout tests. 52 CHECK(entry.GetFrameToNavigate().empty()); 53 entry.GetExtraData(kSearchTermsKey, &navigation.search_terms_); 54 if (entry.GetFavicon().valid) 55 navigation.favicon_url_ = entry.GetFavicon().url; 56 57 return navigation; 58 } 59 60 SerializedNavigationEntry SerializedNavigationEntry::FromSyncData( 61 int index, 62 const sync_pb::TabNavigation& sync_data) { 63 SerializedNavigationEntry navigation; 64 navigation.index_ = index; 65 navigation.unique_id_ = sync_data.unique_id(); 66 navigation.referrer_ = 67 content::Referrer(GURL(sync_data.referrer()), 68 WebKit::WebReferrerPolicyDefault); 69 navigation.virtual_url_ = GURL(sync_data.virtual_url()); 70 navigation.title_ = UTF8ToUTF16(sync_data.title()); 71 navigation.page_state_ = 72 content::PageState::CreateFromEncodedData(sync_data.state()); 73 74 uint32 transition = 0; 75 if (sync_data.has_page_transition()) { 76 switch (sync_data.page_transition()) { 77 case sync_pb::SyncEnums_PageTransition_LINK: 78 transition = content::PAGE_TRANSITION_LINK; 79 break; 80 case sync_pb::SyncEnums_PageTransition_TYPED: 81 transition = content::PAGE_TRANSITION_TYPED; 82 break; 83 case sync_pb::SyncEnums_PageTransition_AUTO_BOOKMARK: 84 transition = content::PAGE_TRANSITION_AUTO_BOOKMARK; 85 break; 86 case sync_pb::SyncEnums_PageTransition_AUTO_SUBFRAME: 87 transition = content::PAGE_TRANSITION_AUTO_SUBFRAME; 88 break; 89 case sync_pb::SyncEnums_PageTransition_MANUAL_SUBFRAME: 90 transition = content::PAGE_TRANSITION_MANUAL_SUBFRAME; 91 break; 92 case sync_pb::SyncEnums_PageTransition_GENERATED: 93 transition = content::PAGE_TRANSITION_GENERATED; 94 break; 95 case sync_pb::SyncEnums_PageTransition_AUTO_TOPLEVEL: 96 transition = content::PAGE_TRANSITION_AUTO_TOPLEVEL; 97 break; 98 case sync_pb::SyncEnums_PageTransition_FORM_SUBMIT: 99 transition = content::PAGE_TRANSITION_FORM_SUBMIT; 100 break; 101 case sync_pb::SyncEnums_PageTransition_RELOAD: 102 transition = content::PAGE_TRANSITION_RELOAD; 103 break; 104 case sync_pb::SyncEnums_PageTransition_KEYWORD: 105 transition = content::PAGE_TRANSITION_KEYWORD; 106 break; 107 case sync_pb::SyncEnums_PageTransition_KEYWORD_GENERATED: 108 transition = 109 content::PAGE_TRANSITION_KEYWORD_GENERATED; 110 break; 111 default: 112 transition = content::PAGE_TRANSITION_LINK; 113 break; 114 } 115 } 116 117 if (sync_data.has_redirect_type()) { 118 switch (sync_data.redirect_type()) { 119 case sync_pb::SyncEnums_PageTransitionRedirectType_CLIENT_REDIRECT: 120 transition |= content::PAGE_TRANSITION_CLIENT_REDIRECT; 121 break; 122 case sync_pb::SyncEnums_PageTransitionRedirectType_SERVER_REDIRECT: 123 transition |= content::PAGE_TRANSITION_SERVER_REDIRECT; 124 break; 125 } 126 } 127 if (sync_data.navigation_forward_back()) 128 transition |= content::PAGE_TRANSITION_FORWARD_BACK; 129 if (sync_data.navigation_from_address_bar()) 130 transition |= content::PAGE_TRANSITION_FROM_ADDRESS_BAR; 131 if (sync_data.navigation_home_page()) 132 transition |= content::PAGE_TRANSITION_HOME_PAGE; 133 if (sync_data.navigation_chain_start()) 134 transition |= content::PAGE_TRANSITION_CHAIN_START; 135 if (sync_data.navigation_chain_end()) 136 transition |= content::PAGE_TRANSITION_CHAIN_END; 137 138 navigation.transition_type_ = 139 static_cast<content::PageTransition>(transition); 140 141 navigation.timestamp_ = base::Time(); 142 navigation.search_terms_ = UTF8ToUTF16(sync_data.search_terms()); 143 if (sync_data.has_favicon_url()) 144 navigation.favicon_url_ = GURL(sync_data.favicon_url()); 145 146 // We shouldn't sync session data for managed users down at the moment. 147 DCHECK(!sync_data.has_blocked_state()); 148 DCHECK_EQ(0, sync_data.content_pack_categories_size()); 149 150 return navigation; 151 } 152 153 namespace { 154 155 // Helper used by SerializedNavigationEntry::WriteToPickle(). It writes |str| to 156 // |pickle|, if and only if |str| fits within (|max_bytes| - 157 // |*bytes_written|). |bytes_written| is incremented to reflect the 158 // data written. 159 // 160 // TODO(akalin): Unify this with the same function in 161 // base_session_service.cc. 162 void WriteStringToPickle(Pickle* pickle, 163 int* bytes_written, 164 int max_bytes, 165 const std::string& str) { 166 int num_bytes = str.size() * sizeof(char); 167 if (*bytes_written + num_bytes < max_bytes) { 168 *bytes_written += num_bytes; 169 pickle->WriteString(str); 170 } else { 171 pickle->WriteString(std::string()); 172 } 173 } 174 175 // string16 version of WriteStringToPickle. 176 // 177 // TODO(akalin): Unify this, too. 178 void WriteString16ToPickle(Pickle* pickle, 179 int* bytes_written, 180 int max_bytes, 181 const string16& str) { 182 int num_bytes = str.size() * sizeof(char16); 183 if (*bytes_written + num_bytes < max_bytes) { 184 *bytes_written += num_bytes; 185 pickle->WriteString16(str); 186 } else { 187 pickle->WriteString16(string16()); 188 } 189 } 190 191 // A mask used for arbitrary boolean values needed to represent a 192 // NavigationEntry. Currently only contains HAS_POST_DATA. 193 // 194 // NOTE(akalin): We may want to just serialize |has_post_data_| 195 // directly. Other bools (|is_overriding_user_agent_|) haven't been 196 // added to this mask. 197 enum TypeMask { 198 HAS_POST_DATA = 1 199 }; 200 201 } // namespace 202 203 // Pickle order: 204 // 205 // index_ 206 // virtual_url_ 207 // title_ 208 // page_state_ 209 // transition_type_ 210 // 211 // Added on later: 212 // 213 // type_mask (has_post_data_) 214 // referrer_ 215 // original_request_url_ 216 // is_overriding_user_agent_ 217 // timestamp_ 218 // search_terms_ 219 220 void SerializedNavigationEntry::WriteToPickle(int max_size, 221 Pickle* pickle) const { 222 pickle->WriteInt(index_); 223 224 int bytes_written = 0; 225 226 WriteStringToPickle(pickle, &bytes_written, max_size, 227 virtual_url_.spec()); 228 229 WriteString16ToPickle(pickle, &bytes_written, max_size, title_); 230 231 content::PageState page_state = page_state_; 232 if (has_post_data_) 233 page_state = page_state.RemovePasswordData(); 234 235 WriteStringToPickle(pickle, &bytes_written, max_size, 236 page_state.ToEncodedData()); 237 238 pickle->WriteInt(transition_type_); 239 240 const int type_mask = has_post_data_ ? HAS_POST_DATA : 0; 241 pickle->WriteInt(type_mask); 242 243 WriteStringToPickle( 244 pickle, &bytes_written, max_size, 245 referrer_.url.is_valid() ? referrer_.url.spec() : std::string()); 246 247 pickle->WriteInt(referrer_.policy); 248 249 // Save info required to override the user agent. 250 WriteStringToPickle( 251 pickle, &bytes_written, max_size, 252 original_request_url_.is_valid() ? 253 original_request_url_.spec() : std::string()); 254 pickle->WriteBool(is_overriding_user_agent_); 255 pickle->WriteInt64(timestamp_.ToInternalValue()); 256 257 WriteString16ToPickle(pickle, &bytes_written, max_size, search_terms_); 258 } 259 260 bool SerializedNavigationEntry::ReadFromPickle(PickleIterator* iterator) { 261 *this = SerializedNavigationEntry(); 262 std::string virtual_url_spec, page_state_data; 263 int transition_type_int = 0; 264 if (!iterator->ReadInt(&index_) || 265 !iterator->ReadString(&virtual_url_spec) || 266 !iterator->ReadString16(&title_) || 267 !iterator->ReadString(&page_state_data) || 268 !iterator->ReadInt(&transition_type_int)) 269 return false; 270 virtual_url_ = GURL(virtual_url_spec); 271 page_state_ = content::PageState::CreateFromEncodedData(page_state_data); 272 transition_type_ = static_cast<content::PageTransition>(transition_type_int); 273 274 // type_mask did not always exist in the written stream. As such, we 275 // don't fail if it can't be read. 276 int type_mask = 0; 277 bool has_type_mask = iterator->ReadInt(&type_mask); 278 279 if (has_type_mask) { 280 has_post_data_ = type_mask & HAS_POST_DATA; 281 // the "referrer" property was added after type_mask to the written 282 // stream. As such, we don't fail if it can't be read. 283 std::string referrer_spec; 284 if (!iterator->ReadString(&referrer_spec)) 285 referrer_spec = std::string(); 286 // The "referrer policy" property was added even later, so we fall back to 287 // the default policy if the property is not present. 288 int policy_int; 289 WebKit::WebReferrerPolicy policy; 290 if (iterator->ReadInt(&policy_int)) 291 policy = static_cast<WebKit::WebReferrerPolicy>(policy_int); 292 else 293 policy = WebKit::WebReferrerPolicyDefault; 294 referrer_ = content::Referrer(GURL(referrer_spec), policy); 295 296 // If the original URL can't be found, leave it empty. 297 std::string original_request_url_spec; 298 if (!iterator->ReadString(&original_request_url_spec)) 299 original_request_url_spec = std::string(); 300 original_request_url_ = GURL(original_request_url_spec); 301 302 // Default to not overriding the user agent if we don't have info. 303 if (!iterator->ReadBool(&is_overriding_user_agent_)) 304 is_overriding_user_agent_ = false; 305 306 int64 timestamp_internal_value = 0; 307 if (iterator->ReadInt64(×tamp_internal_value)) { 308 timestamp_ = base::Time::FromInternalValue(timestamp_internal_value); 309 } else { 310 timestamp_ = base::Time(); 311 } 312 313 // If the search terms field can't be found, leave it empty. 314 if (!iterator->ReadString16(&search_terms_)) 315 search_terms_.clear(); 316 } 317 318 return true; 319 } 320 321 scoped_ptr<NavigationEntry> SerializedNavigationEntry::ToNavigationEntry( 322 int page_id, 323 content::BrowserContext* browser_context) const { 324 scoped_ptr<NavigationEntry> entry( 325 content::NavigationController::CreateNavigationEntry( 326 virtual_url_, 327 referrer_, 328 // Use a transition type of reload so that we don't incorrectly 329 // increase the typed count. 330 content::PAGE_TRANSITION_RELOAD, 331 false, 332 // The extra headers are not sync'ed across sessions. 333 std::string(), 334 browser_context)); 335 336 entry->SetTitle(title_); 337 entry->SetPageState(page_state_); 338 entry->SetPageID(page_id); 339 entry->SetHasPostData(has_post_data_); 340 entry->SetPostID(post_id_); 341 entry->SetOriginalRequestURL(original_request_url_); 342 entry->SetIsOverridingUserAgent(is_overriding_user_agent_); 343 entry->SetTimestamp(timestamp_); 344 entry->SetExtraData(kSearchTermsKey, search_terms_); 345 346 // These fields should have default values. 347 DCHECK_EQ(STATE_INVALID, blocked_state_); 348 DCHECK_EQ(0u, content_pack_categories_.size()); 349 350 return entry.Pass(); 351 } 352 353 // TODO(zea): perhaps sync state (scroll position, form entries, etc.) as well? 354 // See http://crbug.com/67068. 355 sync_pb::TabNavigation SerializedNavigationEntry::ToSyncData() const { 356 sync_pb::TabNavigation sync_data; 357 sync_data.set_virtual_url(virtual_url_.spec()); 358 // FIXME(zea): Support referrer policy? 359 sync_data.set_referrer(referrer_.url.spec()); 360 sync_data.set_title(UTF16ToUTF8(title_)); 361 362 // Page transition core. 363 COMPILE_ASSERT(content::PAGE_TRANSITION_LAST_CORE == 364 content::PAGE_TRANSITION_KEYWORD_GENERATED, 365 PageTransitionCoreBounds); 366 switch (PageTransitionStripQualifier(transition_type_)) { 367 case content::PAGE_TRANSITION_LINK: 368 sync_data.set_page_transition( 369 sync_pb::SyncEnums_PageTransition_LINK); 370 break; 371 case content::PAGE_TRANSITION_TYPED: 372 sync_data.set_page_transition( 373 sync_pb::SyncEnums_PageTransition_TYPED); 374 break; 375 case content::PAGE_TRANSITION_AUTO_BOOKMARK: 376 sync_data.set_page_transition( 377 sync_pb::SyncEnums_PageTransition_AUTO_BOOKMARK); 378 break; 379 case content::PAGE_TRANSITION_AUTO_SUBFRAME: 380 sync_data.set_page_transition( 381 sync_pb::SyncEnums_PageTransition_AUTO_SUBFRAME); 382 break; 383 case content::PAGE_TRANSITION_MANUAL_SUBFRAME: 384 sync_data.set_page_transition( 385 sync_pb::SyncEnums_PageTransition_MANUAL_SUBFRAME); 386 break; 387 case content::PAGE_TRANSITION_GENERATED: 388 sync_data.set_page_transition( 389 sync_pb::SyncEnums_PageTransition_GENERATED); 390 break; 391 case content::PAGE_TRANSITION_AUTO_TOPLEVEL: 392 sync_data.set_page_transition( 393 sync_pb::SyncEnums_PageTransition_AUTO_TOPLEVEL); 394 break; 395 case content::PAGE_TRANSITION_FORM_SUBMIT: 396 sync_data.set_page_transition( 397 sync_pb::SyncEnums_PageTransition_FORM_SUBMIT); 398 break; 399 case content::PAGE_TRANSITION_RELOAD: 400 sync_data.set_page_transition( 401 sync_pb::SyncEnums_PageTransition_RELOAD); 402 break; 403 case content::PAGE_TRANSITION_KEYWORD: 404 sync_data.set_page_transition( 405 sync_pb::SyncEnums_PageTransition_KEYWORD); 406 break; 407 case content::PAGE_TRANSITION_KEYWORD_GENERATED: 408 sync_data.set_page_transition( 409 sync_pb::SyncEnums_PageTransition_KEYWORD_GENERATED); 410 break; 411 default: 412 NOTREACHED(); 413 } 414 415 // Page transition qualifiers. 416 if (PageTransitionIsRedirect(transition_type_)) { 417 if (transition_type_ & content::PAGE_TRANSITION_CLIENT_REDIRECT) { 418 sync_data.set_redirect_type( 419 sync_pb::SyncEnums_PageTransitionRedirectType_CLIENT_REDIRECT); 420 } else if (transition_type_ & content::PAGE_TRANSITION_SERVER_REDIRECT) { 421 sync_data.set_redirect_type( 422 sync_pb::SyncEnums_PageTransitionRedirectType_SERVER_REDIRECT); 423 } 424 } 425 sync_data.set_navigation_forward_back( 426 (transition_type_ & content::PAGE_TRANSITION_FORWARD_BACK) != 0); 427 sync_data.set_navigation_from_address_bar( 428 (transition_type_ & content::PAGE_TRANSITION_FROM_ADDRESS_BAR) != 0); 429 sync_data.set_navigation_home_page( 430 (transition_type_ & content::PAGE_TRANSITION_HOME_PAGE) != 0); 431 sync_data.set_navigation_chain_start( 432 (transition_type_ & content::PAGE_TRANSITION_CHAIN_START) != 0); 433 sync_data.set_navigation_chain_end( 434 (transition_type_ & content::PAGE_TRANSITION_CHAIN_END) != 0); 435 436 sync_data.set_unique_id(unique_id_); 437 sync_data.set_timestamp_msec(syncer::TimeToProtoTime(timestamp_)); 438 // The full-resolution timestamp works as a global ID. 439 sync_data.set_global_id(timestamp_.ToInternalValue()); 440 441 sync_data.set_search_terms(UTF16ToUTF8(search_terms_)); 442 443 if (favicon_url_.is_valid()) 444 sync_data.set_favicon_url(favicon_url_.spec()); 445 446 if (blocked_state_ != STATE_INVALID) { 447 sync_data.set_blocked_state( 448 static_cast<sync_pb::TabNavigation_BlockedState>(blocked_state_)); 449 } 450 451 for (std::set<std::string>::const_iterator it = 452 content_pack_categories_.begin(); 453 it != content_pack_categories_.end(); ++it) { 454 sync_data.add_content_pack_categories(*it); 455 } 456 457 return sync_data; 458 } 459 460 // static 461 std::vector<NavigationEntry*> SerializedNavigationEntry::ToNavigationEntries( 462 const std::vector<SerializedNavigationEntry>& navigations, 463 content::BrowserContext* browser_context) { 464 int page_id = 0; 465 std::vector<NavigationEntry*> entries; 466 for (std::vector<SerializedNavigationEntry>::const_iterator 467 it = navigations.begin(); it != navigations.end(); ++it) { 468 entries.push_back( 469 it->ToNavigationEntry(page_id, browser_context).release()); 470 ++page_id; 471 } 472 return entries; 473 } 474 475 } // namespace sessions 476