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