| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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 "content/browser/tab_contents/navigation_controller_impl.h" | |
| 6 | |
| 7 #include "base/file_util.h" | |
| 8 #include "base/logging.h" | |
| 9 #include "base/string_number_conversions.h" // Temporary | |
| 10 #include "base/string_util.h" | |
| 11 #include "base/time.h" | |
| 12 #include "base/utf_string_conversions.h" | |
| 13 #include "content/browser/browser_url_handler_impl.h" | |
| 14 #include "content/browser/child_process_security_policy_impl.h" | |
| 15 #include "content/browser/in_process_webkit/dom_storage_context_impl.h" | |
| 16 #include "content/browser/in_process_webkit/session_storage_namespace_impl.h" | |
| 17 #include "content/browser/renderer_host/render_view_host_impl.h" // Temporary | |
| 18 #include "content/browser/site_instance_impl.h" | |
| 19 #include "content/browser/tab_contents/debug_urls.h" | |
| 20 #include "content/browser/tab_contents/interstitial_page_impl.h" | |
| 21 #include "content/browser/tab_contents/navigation_entry_impl.h" | |
| 22 #include "content/browser/tab_contents/tab_contents.h" | |
| 23 #include "content/common/view_messages.h" | |
| 24 #include "content/public/browser/browser_context.h" | |
| 25 #include "content/public/browser/invalidate_type.h" | |
| 26 #include "content/public/browser/navigation_details.h" | |
| 27 #include "content/public/browser/notification_service.h" | |
| 28 #include "content/public/browser/notification_types.h" | |
| 29 #include "content/public/browser/user_metrics.h" | |
| 30 #include "content/public/browser/web_contents_delegate.h" | |
| 31 #include "content/public/common/content_constants.h" | |
| 32 #include "net/base/escape.h" | |
| 33 #include "net/base/mime_util.h" | |
| 34 #include "net/base/net_util.h" | |
| 35 #include "webkit/glue/webkit_glue.h" | |
| 36 | |
| 37 using content::BrowserContext; | |
| 38 using content::DOMStorageContext; | |
| 39 using content::GlobalRequestID; | |
| 40 using content::NavigationController; | |
| 41 using content::NavigationEntry; | |
| 42 using content::NavigationEntryImpl; | |
| 43 using content::RenderViewHostImpl; | |
| 44 using content::SessionStorageNamespace; | |
| 45 using content::SiteInstance; | |
| 46 using content::UserMetricsAction; | |
| 47 using content::WebContents; | |
| 48 | |
| 49 namespace { | |
| 50 | |
| 51 const int kInvalidateAll = 0xFFFFFFFF; | |
| 52 | |
| 53 // Invoked when entries have been pruned, or removed. For example, if the | |
| 54 // current entries are [google, digg, yahoo], with the current entry google, | |
| 55 // and the user types in cnet, then digg and yahoo are pruned. | |
| 56 void NotifyPrunedEntries(NavigationControllerImpl* nav_controller, | |
| 57 bool from_front, | |
| 58 int count) { | |
| 59 content::PrunedDetails details; | |
| 60 details.from_front = from_front; | |
| 61 details.count = count; | |
| 62 content::NotificationService::current()->Notify( | |
| 63 content::NOTIFICATION_NAV_LIST_PRUNED, | |
| 64 content::Source<NavigationController>(nav_controller), | |
| 65 content::Details<content::PrunedDetails>(&details)); | |
| 66 } | |
| 67 | |
| 68 // Ensure the given NavigationEntry has a valid state, so that WebKit does not | |
| 69 // get confused if we navigate back to it. | |
| 70 // | |
| 71 // An empty state is treated as a new navigation by WebKit, which would mean | |
| 72 // losing the navigation entries and generating a new navigation entry after | |
| 73 // this one. We don't want that. To avoid this we create a valid state which | |
| 74 // WebKit will not treat as a new navigation. | |
| 75 void SetContentStateIfEmpty(NavigationEntryImpl* entry) { | |
| 76 if (entry->GetContentState().empty()) { | |
| 77 entry->SetContentState( | |
| 78 webkit_glue::CreateHistoryStateForURL(entry->GetURL())); | |
| 79 } | |
| 80 } | |
| 81 | |
| 82 // Configure all the NavigationEntries in entries for restore. This resets | |
| 83 // the transition type to reload and makes sure the content state isn't empty. | |
| 84 void ConfigureEntriesForRestore( | |
| 85 std::vector<linked_ptr<NavigationEntryImpl> >* entries, | |
| 86 bool from_last_session) { | |
| 87 for (size_t i = 0; i < entries->size(); ++i) { | |
| 88 // Use a transition type of reload so that we don't incorrectly increase | |
| 89 // the typed count. | |
| 90 (*entries)[i]->SetTransitionType(content::PAGE_TRANSITION_RELOAD); | |
| 91 (*entries)[i]->set_restore_type(from_last_session ? | |
| 92 NavigationEntryImpl::RESTORE_LAST_SESSION : | |
| 93 NavigationEntryImpl::RESTORE_CURRENT_SESSION); | |
| 94 // NOTE(darin): This code is only needed for backwards compat. | |
| 95 SetContentStateIfEmpty((*entries)[i].get()); | |
| 96 } | |
| 97 } | |
| 98 | |
| 99 // See NavigationController::IsURLInPageNavigation for how this works and why. | |
| 100 bool AreURLsInPageNavigation(const GURL& existing_url, const GURL& new_url) { | |
| 101 if (existing_url == new_url || !new_url.has_ref()) { | |
| 102 // TODO(jcampan): what about when navigating back from a ref URL to the top | |
| 103 // non ref URL? Nothing is loaded in that case but we return false here. | |
| 104 // The user could also navigate from the ref URL to the non ref URL by | |
| 105 // entering the non ref URL in the location bar or through a bookmark, in | |
| 106 // which case there would be a load. I am not sure if the non-load/load | |
| 107 // scenarios can be differentiated with the TransitionType. | |
| 108 return false; | |
| 109 } | |
| 110 | |
| 111 url_canon::Replacements<char> replacements; | |
| 112 replacements.ClearRef(); | |
| 113 return existing_url.ReplaceComponents(replacements) == | |
| 114 new_url.ReplaceComponents(replacements); | |
| 115 } | |
| 116 | |
| 117 } // namespace | |
| 118 | |
| 119 // NavigationControllerImpl ---------------------------------------------------- | |
| 120 | |
| 121 const size_t kMaxEntryCountForTestingNotSet = -1; | |
| 122 | |
| 123 // static | |
| 124 size_t NavigationControllerImpl::max_entry_count_for_testing_ = | |
| 125 kMaxEntryCountForTestingNotSet; | |
| 126 | |
| 127 // Should Reload check for post data? The default is true, but is set to false | |
| 128 // when testing. | |
| 129 static bool g_check_for_repost = true; | |
| 130 | |
| 131 namespace content { | |
| 132 // static | |
| 133 NavigationEntry* NavigationController::CreateNavigationEntry( | |
| 134 const GURL& url, | |
| 135 const Referrer& referrer, | |
| 136 PageTransition transition, | |
| 137 bool is_renderer_initiated, | |
| 138 const std::string& extra_headers, | |
| 139 BrowserContext* browser_context) { | |
| 140 // Allow the browser URL handler to rewrite the URL. This will, for example, | |
| 141 // remove "view-source:" from the beginning of the URL to get the URL that | |
| 142 // will actually be loaded. This real URL won't be shown to the user, just | |
| 143 // used internally. | |
| 144 GURL loaded_url(url); | |
| 145 bool reverse_on_redirect = false; | |
| 146 BrowserURLHandlerImpl::GetInstance()->RewriteURLIfNecessary( | |
| 147 &loaded_url, browser_context, &reverse_on_redirect); | |
| 148 | |
| 149 NavigationEntryImpl* entry = new NavigationEntryImpl( | |
| 150 NULL, // The site instance for tabs is sent on navigation | |
| 151 // (TabContents::GetSiteInstance). | |
| 152 -1, | |
| 153 loaded_url, | |
| 154 referrer, | |
| 155 string16(), | |
| 156 transition, | |
| 157 is_renderer_initiated); | |
| 158 entry->SetVirtualURL(url); | |
| 159 entry->set_user_typed_url(url); | |
| 160 entry->set_update_virtual_url_with_url(reverse_on_redirect); | |
| 161 entry->set_extra_headers(extra_headers); | |
| 162 return entry; | |
| 163 } | |
| 164 | |
| 165 // static | |
| 166 void NavigationController::DisablePromptOnRepost() { | |
| 167 g_check_for_repost = false; | |
| 168 } | |
| 169 | |
| 170 } // namespace content | |
| 171 | |
| 172 NavigationControllerImpl::NavigationControllerImpl( | |
| 173 TabContents* contents, | |
| 174 BrowserContext* browser_context, | |
| 175 SessionStorageNamespaceImpl* session_storage_namespace) | |
| 176 : browser_context_(browser_context), | |
| 177 pending_entry_(NULL), | |
| 178 last_committed_entry_index_(-1), | |
| 179 pending_entry_index_(-1), | |
| 180 transient_entry_index_(-1), | |
| 181 tab_contents_(contents), | |
| 182 max_restored_page_id_(-1), | |
| 183 ALLOW_THIS_IN_INITIALIZER_LIST(ssl_manager_(this)), | |
| 184 needs_reload_(false), | |
| 185 session_storage_namespace_(session_storage_namespace), | |
| 186 pending_reload_(NO_RELOAD) { | |
| 187 DCHECK(browser_context_); | |
| 188 if (!session_storage_namespace_) { | |
| 189 session_storage_namespace_ = new SessionStorageNamespaceImpl( | |
| 190 static_cast<DOMStorageContextImpl*>( | |
| 191 BrowserContext::GetDOMStorageContext(browser_context_))); | |
| 192 } | |
| 193 } | |
| 194 | |
| 195 NavigationControllerImpl::~NavigationControllerImpl() { | |
| 196 DiscardNonCommittedEntriesInternal(); | |
| 197 | |
| 198 content::NotificationService::current()->Notify( | |
| 199 content::NOTIFICATION_TAB_CLOSED, | |
| 200 content::Source<NavigationController>(this), | |
| 201 content::NotificationService::NoDetails()); | |
| 202 } | |
| 203 | |
| 204 WebContents* NavigationControllerImpl::GetWebContents() const { | |
| 205 return tab_contents_; | |
| 206 } | |
| 207 | |
| 208 BrowserContext* NavigationControllerImpl::GetBrowserContext() const { | |
| 209 return browser_context_; | |
| 210 } | |
| 211 | |
| 212 void NavigationControllerImpl::SetBrowserContext( | |
| 213 BrowserContext* browser_context) { | |
| 214 browser_context_ = browser_context; | |
| 215 } | |
| 216 | |
| 217 void NavigationControllerImpl::Restore( | |
| 218 int selected_navigation, | |
| 219 bool from_last_session, | |
| 220 std::vector<NavigationEntry*>* entries) { | |
| 221 // Verify that this controller is unused and that the input is valid. | |
| 222 DCHECK(GetEntryCount() == 0 && !GetPendingEntry()); | |
| 223 DCHECK(selected_navigation >= 0 && | |
| 224 selected_navigation < static_cast<int>(entries->size())); | |
| 225 | |
| 226 needs_reload_ = true; | |
| 227 for (size_t i = 0; i < entries->size(); ++i) { | |
| 228 NavigationEntryImpl* entry = | |
| 229 NavigationEntryImpl::FromNavigationEntry((*entries)[i]); | |
| 230 entries_.push_back(linked_ptr<NavigationEntryImpl>(entry)); | |
| 231 } | |
| 232 entries->clear(); | |
| 233 | |
| 234 // And finish the restore. | |
| 235 FinishRestore(selected_navigation, from_last_session); | |
| 236 } | |
| 237 | |
| 238 void NavigationControllerImpl::Reload(bool check_for_repost) { | |
| 239 ReloadInternal(check_for_repost, RELOAD); | |
| 240 } | |
| 241 void NavigationControllerImpl::ReloadIgnoringCache(bool check_for_repost) { | |
| 242 ReloadInternal(check_for_repost, RELOAD_IGNORING_CACHE); | |
| 243 } | |
| 244 | |
| 245 void NavigationControllerImpl::ReloadInternal(bool check_for_repost, | |
| 246 ReloadType reload_type) { | |
| 247 // Reloading a transient entry does nothing. | |
| 248 if (transient_entry_index_ != -1) | |
| 249 return; | |
| 250 | |
| 251 DiscardNonCommittedEntriesInternal(); | |
| 252 int current_index = GetCurrentEntryIndex(); | |
| 253 // If we are no where, then we can't reload. TODO(darin): We should add a | |
| 254 // CanReload method. | |
| 255 if (current_index == -1) { | |
| 256 return; | |
| 257 } | |
| 258 | |
| 259 if (g_check_for_repost && check_for_repost && | |
| 260 GetEntryAtIndex(current_index)->GetHasPostData()) { | |
| 261 // The user is asking to reload a page with POST data. Prompt to make sure | |
| 262 // they really want to do this. If they do, the dialog will call us back | |
| 263 // with check_for_repost = false. | |
| 264 content::NotificationService::current()->Notify( | |
| 265 content::NOTIFICATION_REPOST_WARNING_SHOWN, | |
| 266 content::Source<NavigationController>(this), | |
| 267 content::NotificationService::NoDetails()); | |
| 268 | |
| 269 pending_reload_ = reload_type; | |
| 270 tab_contents_->Activate(); | |
| 271 tab_contents_->GetDelegate()->ShowRepostFormWarningDialog(tab_contents_); | |
| 272 } else { | |
| 273 DiscardNonCommittedEntriesInternal(); | |
| 274 | |
| 275 NavigationEntryImpl* entry = entries_[current_index].get(); | |
| 276 SiteInstanceImpl* site_instance = entry->site_instance(); | |
| 277 DCHECK(site_instance); | |
| 278 | |
| 279 // If we are reloading an entry that no longer belongs to the current | |
| 280 // site instance (for example, refreshing a page for just installed app), | |
| 281 // the reload must happen in a new process. | |
| 282 // The new entry must have a new page_id and site instance, so it behaves | |
| 283 // as new navigation (which happens to clear forward history). | |
| 284 // Tabs that are discarded due to low memory conditions may not have a site | |
| 285 // instance, and should not be treated as a cross-site reload. | |
| 286 if (site_instance && | |
| 287 site_instance->HasWrongProcessForURL(entry->GetURL())) { | |
| 288 // Create a navigation entry that resembles the current one, but do not | |
| 289 // copy page id, site instance, and content state. | |
| 290 NavigationEntryImpl* nav_entry = NavigationEntryImpl::FromNavigationEntry( | |
| 291 CreateNavigationEntry( | |
| 292 entry->GetURL(), entry->GetReferrer(), entry->GetTransitionType(), | |
| 293 false, entry->extra_headers(), browser_context_)); | |
| 294 | |
| 295 // Mark the reload type as NO_RELOAD, so navigation will not be considered | |
| 296 // a reload in the renderer. | |
| 297 reload_type = NavigationController::NO_RELOAD; | |
| 298 | |
| 299 nav_entry->set_is_cross_site_reload(true); | |
| 300 pending_entry_ = nav_entry; | |
| 301 } else { | |
| 302 pending_entry_index_ = current_index; | |
| 303 | |
| 304 // The title of the page being reloaded might have been removed in the | |
| 305 // meanwhile, so we need to revert to the default title upon reload and | |
| 306 // invalidate the previously cached title (SetTitle will do both). | |
| 307 // See Chromium issue 96041. | |
| 308 entries_[pending_entry_index_]->SetTitle(string16()); | |
| 309 | |
| 310 entries_[pending_entry_index_]->SetTransitionType( | |
| 311 content::PAGE_TRANSITION_RELOAD); | |
| 312 } | |
| 313 | |
| 314 NavigateToPendingEntry(reload_type); | |
| 315 } | |
| 316 } | |
| 317 | |
| 318 void NavigationControllerImpl::CancelPendingReload() { | |
| 319 DCHECK(pending_reload_ != NO_RELOAD); | |
| 320 pending_reload_ = NO_RELOAD; | |
| 321 } | |
| 322 | |
| 323 void NavigationControllerImpl::ContinuePendingReload() { | |
| 324 if (pending_reload_ == NO_RELOAD) { | |
| 325 NOTREACHED(); | |
| 326 } else { | |
| 327 ReloadInternal(false, pending_reload_); | |
| 328 pending_reload_ = NO_RELOAD; | |
| 329 } | |
| 330 } | |
| 331 | |
| 332 bool NavigationControllerImpl::IsInitialNavigation() { | |
| 333 return last_document_loaded_.is_null(); | |
| 334 } | |
| 335 | |
| 336 NavigationEntryImpl* NavigationControllerImpl::GetEntryWithPageID( | |
| 337 SiteInstance* instance, int32 page_id) const { | |
| 338 int index = GetEntryIndexWithPageID(instance, page_id); | |
| 339 return (index != -1) ? entries_[index].get() : NULL; | |
| 340 } | |
| 341 | |
| 342 void NavigationControllerImpl::LoadEntry(NavigationEntryImpl* entry) { | |
| 343 // Don't navigate to URLs disabled by policy. This prevents showing the URL | |
| 344 // on the Omnibar when it is also going to be blocked by | |
| 345 // ChildProcessSecurityPolicy::CanRequestURL. | |
| 346 ChildProcessSecurityPolicyImpl* policy = | |
| 347 ChildProcessSecurityPolicyImpl::GetInstance(); | |
| 348 if (policy->IsDisabledScheme(entry->GetURL().scheme()) || | |
| 349 policy->IsDisabledScheme(entry->GetVirtualURL().scheme())) { | |
| 350 VLOG(1) << "URL not loaded because the scheme is blocked by policy: " | |
| 351 << entry->GetURL(); | |
| 352 delete entry; | |
| 353 return; | |
| 354 } | |
| 355 | |
| 356 // When navigating to a new page, we don't know for sure if we will actually | |
| 357 // end up leaving the current page. The new page load could for example | |
| 358 // result in a download or a 'no content' response (e.g., a mailto: URL). | |
| 359 DiscardNonCommittedEntriesInternal(); | |
| 360 pending_entry_ = entry; | |
| 361 content::NotificationService::current()->Notify( | |
| 362 content::NOTIFICATION_NAV_ENTRY_PENDING, | |
| 363 content::Source<NavigationController>(this), | |
| 364 content::Details<NavigationEntry>(entry)); | |
| 365 NavigateToPendingEntry(NO_RELOAD); | |
| 366 } | |
| 367 | |
| 368 NavigationEntry* NavigationControllerImpl::GetActiveEntry() const { | |
| 369 if (transient_entry_index_ != -1) | |
| 370 return entries_[transient_entry_index_].get(); | |
| 371 if (pending_entry_) | |
| 372 return pending_entry_; | |
| 373 return GetLastCommittedEntry(); | |
| 374 } | |
| 375 | |
| 376 NavigationEntry* NavigationControllerImpl::GetVisibleEntry() const { | |
| 377 if (transient_entry_index_ != -1) | |
| 378 return entries_[transient_entry_index_].get(); | |
| 379 // Only return the pending_entry for new (non-history), browser-initiated | |
| 380 // navigations, in order to prevent URL spoof attacks. | |
| 381 // Ideally we would also show the pending entry's URL for new renderer- | |
| 382 // initiated navigations with no last committed entry (e.g., a link opening | |
| 383 // in a new tab), but an attacker can insert content into the about:blank | |
| 384 // page while the pending URL loads in that case. | |
| 385 if (pending_entry_ && | |
| 386 pending_entry_->GetPageID() == -1 && | |
| 387 !pending_entry_->is_renderer_initiated()) | |
| 388 return pending_entry_; | |
| 389 return GetLastCommittedEntry(); | |
| 390 } | |
| 391 | |
| 392 int NavigationControllerImpl::GetCurrentEntryIndex() const { | |
| 393 if (transient_entry_index_ != -1) | |
| 394 return transient_entry_index_; | |
| 395 if (pending_entry_index_ != -1) | |
| 396 return pending_entry_index_; | |
| 397 return last_committed_entry_index_; | |
| 398 } | |
| 399 | |
| 400 NavigationEntry* NavigationControllerImpl::GetLastCommittedEntry() const { | |
| 401 if (last_committed_entry_index_ == -1) | |
| 402 return NULL; | |
| 403 return entries_[last_committed_entry_index_].get(); | |
| 404 } | |
| 405 | |
| 406 bool NavigationControllerImpl::CanViewSource() const { | |
| 407 bool is_supported_mime_type = net::IsSupportedNonImageMimeType( | |
| 408 tab_contents_->GetContentsMimeType().c_str()); | |
| 409 NavigationEntry* active_entry = GetActiveEntry(); | |
| 410 return active_entry && !active_entry->IsViewSourceMode() && | |
| 411 is_supported_mime_type && !tab_contents_->GetInterstitialPage(); | |
| 412 } | |
| 413 | |
| 414 int NavigationControllerImpl::GetLastCommittedEntryIndex() const { | |
| 415 return last_committed_entry_index_; | |
| 416 } | |
| 417 | |
| 418 int NavigationControllerImpl::GetEntryCount() const { | |
| 419 DCHECK(entries_.size() <= max_entry_count()); | |
| 420 return static_cast<int>(entries_.size()); | |
| 421 } | |
| 422 | |
| 423 NavigationEntry* NavigationControllerImpl::GetEntryAtIndex( | |
| 424 int index) const { | |
| 425 return entries_.at(index).get(); | |
| 426 } | |
| 427 | |
| 428 NavigationEntry* NavigationControllerImpl::GetEntryAtOffset( | |
| 429 int offset) const { | |
| 430 int index = (transient_entry_index_ != -1) ? | |
| 431 transient_entry_index_ + offset : | |
| 432 last_committed_entry_index_ + offset; | |
| 433 if (index < 0 || index >= GetEntryCount()) | |
| 434 return NULL; | |
| 435 | |
| 436 return entries_[index].get(); | |
| 437 } | |
| 438 | |
| 439 bool NavigationControllerImpl::CanGoBack() const { | |
| 440 return entries_.size() > 1 && GetCurrentEntryIndex() > 0; | |
| 441 } | |
| 442 | |
| 443 bool NavigationControllerImpl::CanGoForward() const { | |
| 444 int index = GetCurrentEntryIndex(); | |
| 445 return index >= 0 && index < (static_cast<int>(entries_.size()) - 1); | |
| 446 } | |
| 447 | |
| 448 void NavigationControllerImpl::GoBack() { | |
| 449 if (!CanGoBack()) { | |
| 450 NOTREACHED(); | |
| 451 return; | |
| 452 } | |
| 453 | |
| 454 // Base the navigation on where we are now... | |
| 455 int current_index = GetCurrentEntryIndex(); | |
| 456 | |
| 457 DiscardNonCommittedEntries(); | |
| 458 | |
| 459 pending_entry_index_ = current_index - 1; | |
| 460 entries_[pending_entry_index_]->SetTransitionType( | |
| 461 content::PageTransitionFromInt( | |
| 462 entries_[pending_entry_index_]->GetTransitionType() | | |
| 463 content::PAGE_TRANSITION_FORWARD_BACK)); | |
| 464 NavigateToPendingEntry(NO_RELOAD); | |
| 465 } | |
| 466 | |
| 467 void NavigationControllerImpl::GoForward() { | |
| 468 if (!CanGoForward()) { | |
| 469 NOTREACHED(); | |
| 470 return; | |
| 471 } | |
| 472 | |
| 473 bool transient = (transient_entry_index_ != -1); | |
| 474 | |
| 475 // Base the navigation on where we are now... | |
| 476 int current_index = GetCurrentEntryIndex(); | |
| 477 | |
| 478 DiscardNonCommittedEntries(); | |
| 479 | |
| 480 pending_entry_index_ = current_index; | |
| 481 // If there was a transient entry, we removed it making the current index | |
| 482 // the next page. | |
| 483 if (!transient) | |
| 484 pending_entry_index_++; | |
| 485 | |
| 486 entries_[pending_entry_index_]->SetTransitionType( | |
| 487 content::PageTransitionFromInt( | |
| 488 entries_[pending_entry_index_]->GetTransitionType() | | |
| 489 content::PAGE_TRANSITION_FORWARD_BACK)); | |
| 490 NavigateToPendingEntry(NO_RELOAD); | |
| 491 } | |
| 492 | |
| 493 void NavigationControllerImpl::GoToIndex(int index) { | |
| 494 if (index < 0 || index >= static_cast<int>(entries_.size())) { | |
| 495 NOTREACHED(); | |
| 496 return; | |
| 497 } | |
| 498 | |
| 499 if (transient_entry_index_ != -1) { | |
| 500 if (index == transient_entry_index_) { | |
| 501 // Nothing to do when navigating to the transient. | |
| 502 return; | |
| 503 } | |
| 504 if (index > transient_entry_index_) { | |
| 505 // Removing the transient is goint to shift all entries by 1. | |
| 506 index--; | |
| 507 } | |
| 508 } | |
| 509 | |
| 510 DiscardNonCommittedEntries(); | |
| 511 | |
| 512 pending_entry_index_ = index; | |
| 513 entries_[pending_entry_index_]->SetTransitionType( | |
| 514 content::PageTransitionFromInt( | |
| 515 entries_[pending_entry_index_]->GetTransitionType() | | |
| 516 content::PAGE_TRANSITION_FORWARD_BACK)); | |
| 517 NavigateToPendingEntry(NO_RELOAD); | |
| 518 } | |
| 519 | |
| 520 void NavigationControllerImpl::GoToOffset(int offset) { | |
| 521 int index = (transient_entry_index_ != -1) ? | |
| 522 transient_entry_index_ + offset : | |
| 523 last_committed_entry_index_ + offset; | |
| 524 if (index < 0 || index >= GetEntryCount()) | |
| 525 return; | |
| 526 | |
| 527 GoToIndex(index); | |
| 528 } | |
| 529 | |
| 530 void NavigationControllerImpl::RemoveEntryAtIndex(int index) { | |
| 531 if (index == last_committed_entry_index_) | |
| 532 return; | |
| 533 | |
| 534 RemoveEntryAtIndexInternal(index); | |
| 535 } | |
| 536 | |
| 537 void NavigationControllerImpl::UpdateVirtualURLToURL( | |
| 538 NavigationEntryImpl* entry, const GURL& new_url) { | |
| 539 GURL new_virtual_url(new_url); | |
| 540 if (BrowserURLHandlerImpl::GetInstance()->ReverseURLRewrite( | |
| 541 &new_virtual_url, entry->GetVirtualURL(), browser_context_)) { | |
| 542 entry->SetVirtualURL(new_virtual_url); | |
| 543 } | |
| 544 } | |
| 545 | |
| 546 void NavigationControllerImpl::AddTransientEntry(NavigationEntryImpl* entry) { | |
| 547 // Discard any current transient entry, we can only have one at a time. | |
| 548 int index = 0; | |
| 549 if (last_committed_entry_index_ != -1) | |
| 550 index = last_committed_entry_index_ + 1; | |
| 551 DiscardTransientEntry(); | |
| 552 entries_.insert( | |
| 553 entries_.begin() + index, linked_ptr<NavigationEntryImpl>(entry)); | |
| 554 transient_entry_index_ = index; | |
| 555 tab_contents_->NotifyNavigationStateChanged(kInvalidateAll); | |
| 556 } | |
| 557 | |
| 558 void NavigationControllerImpl::TransferURL( | |
| 559 const GURL& url, | |
| 560 const content::Referrer& referrer, | |
| 561 content::PageTransition transition, | |
| 562 const std::string& extra_headers, | |
| 563 const GlobalRequestID& transferred_global_request_id, | |
| 564 bool is_renderer_initiated) { | |
| 565 // The user initiated a load, we don't need to reload anymore. | |
| 566 needs_reload_ = false; | |
| 567 | |
| 568 NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry( | |
| 569 CreateNavigationEntry( | |
| 570 url, referrer, transition, is_renderer_initiated, extra_headers, | |
| 571 browser_context_)); | |
| 572 entry->set_transferred_global_request_id(transferred_global_request_id); | |
| 573 | |
| 574 LoadEntry(entry); | |
| 575 } | |
| 576 | |
| 577 void NavigationControllerImpl::LoadURL( | |
| 578 const GURL& url, | |
| 579 const content::Referrer& referrer, | |
| 580 content::PageTransition transition, | |
| 581 const std::string& extra_headers) { | |
| 582 if (content::HandleDebugURL(url, transition)) | |
| 583 return; | |
| 584 | |
| 585 // The user initiated a load, we don't need to reload anymore. | |
| 586 needs_reload_ = false; | |
| 587 | |
| 588 NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry( | |
| 589 CreateNavigationEntry( | |
| 590 url, referrer, transition, false, extra_headers, browser_context_)); | |
| 591 | |
| 592 LoadEntry(entry); | |
| 593 } | |
| 594 | |
| 595 void NavigationControllerImpl::LoadURLFromRenderer( | |
| 596 const GURL& url, | |
| 597 const content::Referrer& referrer, | |
| 598 content::PageTransition transition, | |
| 599 const std::string& extra_headers) { | |
| 600 // The user initiated a load, we don't need to reload anymore. | |
| 601 needs_reload_ = false; | |
| 602 | |
| 603 NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry( | |
| 604 CreateNavigationEntry( | |
| 605 url, referrer, transition, true, extra_headers, browser_context_)); | |
| 606 | |
| 607 LoadEntry(entry); | |
| 608 } | |
| 609 | |
| 610 void NavigationControllerImpl::DocumentLoadedInFrame() { | |
| 611 last_document_loaded_ = base::TimeTicks::Now(); | |
| 612 } | |
| 613 | |
| 614 bool NavigationControllerImpl::RendererDidNavigate( | |
| 615 const ViewHostMsg_FrameNavigate_Params& params, | |
| 616 content::LoadCommittedDetails* details) { | |
| 617 | |
| 618 // Save the previous state before we clobber it. | |
| 619 if (GetLastCommittedEntry()) { | |
| 620 details->previous_url = GetLastCommittedEntry()->GetURL(); | |
| 621 details->previous_entry_index = GetLastCommittedEntryIndex(); | |
| 622 } else { | |
| 623 details->previous_url = GURL(); | |
| 624 details->previous_entry_index = -1; | |
| 625 } | |
| 626 | |
| 627 // If we have a pending entry at this point, it should have a SiteInstance. | |
| 628 // Restored entries start out with a null SiteInstance, but we should have | |
| 629 // assigned one in NavigateToPendingEntry. | |
| 630 DCHECK(pending_entry_index_ == -1 || pending_entry_->site_instance()); | |
| 631 | |
| 632 // If we are doing a cross-site reload, we need to replace the existing | |
| 633 // navigation entry, not add another entry to the history. This has the side | |
| 634 // effect of removing forward browsing history, if such existed. | |
| 635 if (pending_entry_ != NULL) { | |
| 636 details->did_replace_entry = pending_entry_->is_cross_site_reload(); | |
| 637 } | |
| 638 | |
| 639 // is_in_page must be computed before the entry gets committed. | |
| 640 details->is_in_page = IsURLInPageNavigation(params.url); | |
| 641 | |
| 642 // Do navigation-type specific actions. These will make and commit an entry. | |
| 643 details->type = ClassifyNavigation(params); | |
| 644 | |
| 645 switch (details->type) { | |
| 646 case content::NAVIGATION_TYPE_NEW_PAGE: | |
| 647 RendererDidNavigateToNewPage(params, &(details->did_replace_entry)); | |
| 648 break; | |
| 649 case content::NAVIGATION_TYPE_EXISTING_PAGE: | |
| 650 RendererDidNavigateToExistingPage(params); | |
| 651 break; | |
| 652 case content::NAVIGATION_TYPE_SAME_PAGE: | |
| 653 RendererDidNavigateToSamePage(params); | |
| 654 break; | |
| 655 case content::NAVIGATION_TYPE_IN_PAGE: | |
| 656 RendererDidNavigateInPage(params, &(details->did_replace_entry)); | |
| 657 break; | |
| 658 case content::NAVIGATION_TYPE_NEW_SUBFRAME: | |
| 659 RendererDidNavigateNewSubframe(params); | |
| 660 break; | |
| 661 case content::NAVIGATION_TYPE_AUTO_SUBFRAME: | |
| 662 if (!RendererDidNavigateAutoSubframe(params)) | |
| 663 return false; | |
| 664 break; | |
| 665 case content::NAVIGATION_TYPE_NAV_IGNORE: | |
| 666 // If a pending navigation was in progress, this canceled it. We should | |
| 667 // discard it and make sure it is removed from the URL bar. After that, | |
| 668 // there is nothing we can do with this navigation, so we just return to | |
| 669 // the caller that nothing has happened. | |
| 670 if (pending_entry_) { | |
| 671 DiscardNonCommittedEntries(); | |
| 672 tab_contents_->NotifyNavigationStateChanged( | |
| 673 content::INVALIDATE_TYPE_URL); | |
| 674 } | |
| 675 return false; | |
| 676 default: | |
| 677 NOTREACHED(); | |
| 678 } | |
| 679 | |
| 680 // All committed entries should have nonempty content state so WebKit doesn't | |
| 681 // get confused when we go back to them (see the function for details). | |
| 682 DCHECK(!params.content_state.empty()); | |
| 683 NavigationEntryImpl* active_entry = | |
| 684 NavigationEntryImpl::FromNavigationEntry(GetActiveEntry()); | |
| 685 active_entry->SetContentState(params.content_state); | |
| 686 | |
| 687 // Once committed, we do not need to track if the entry was initiated by | |
| 688 // the renderer. | |
| 689 active_entry->set_is_renderer_initiated(false); | |
| 690 | |
| 691 // The active entry's SiteInstance should match our SiteInstance. | |
| 692 DCHECK(active_entry->site_instance() == tab_contents_->GetSiteInstance()); | |
| 693 | |
| 694 // Now prep the rest of the details for the notification and broadcast. | |
| 695 details->entry = active_entry; | |
| 696 details->is_main_frame = | |
| 697 content::PageTransitionIsMainFrame(params.transition); | |
| 698 details->serialized_security_info = params.security_info; | |
| 699 details->http_status_code = params.http_status_code; | |
| 700 NotifyNavigationEntryCommitted(details); | |
| 701 | |
| 702 return true; | |
| 703 } | |
| 704 | |
| 705 content::NavigationType NavigationControllerImpl::ClassifyNavigation( | |
| 706 const ViewHostMsg_FrameNavigate_Params& params) const { | |
| 707 if (params.page_id == -1) { | |
| 708 // The renderer generates the page IDs, and so if it gives us the invalid | |
| 709 // page ID (-1) we know it didn't actually navigate. This happens in a few | |
| 710 // cases: | |
| 711 // | |
| 712 // - If a page makes a popup navigated to about blank, and then writes | |
| 713 // stuff like a subframe navigated to a real page. We'll get the commit | |
| 714 // for the subframe, but there won't be any commit for the outer page. | |
| 715 // | |
| 716 // - We were also getting these for failed loads (for example, bug 21849). | |
| 717 // The guess is that we get a "load commit" for the alternate error page, | |
| 718 // but that doesn't affect the page ID, so we get the "old" one, which | |
| 719 // could be invalid. This can also happen for a cross-site transition | |
| 720 // that causes us to swap processes. Then the error page load will be in | |
| 721 // a new process with no page IDs ever assigned (and hence a -1 value), | |
| 722 // yet the navigation controller still might have previous pages in its | |
| 723 // list. | |
| 724 // | |
| 725 // In these cases, there's nothing we can do with them, so ignore. | |
| 726 return content::NAVIGATION_TYPE_NAV_IGNORE; | |
| 727 } | |
| 728 | |
| 729 if (params.page_id > tab_contents_->GetMaxPageID()) { | |
| 730 // Greater page IDs than we've ever seen before are new pages. We may or may | |
| 731 // not have a pending entry for the page, and this may or may not be the | |
| 732 // main frame. | |
| 733 if (content::PageTransitionIsMainFrame(params.transition)) | |
| 734 return content::NAVIGATION_TYPE_NEW_PAGE; | |
| 735 | |
| 736 // When this is a new subframe navigation, we should have a committed page | |
| 737 // for which it's a suframe in. This may not be the case when an iframe is | |
| 738 // navigated on a popup navigated to about:blank (the iframe would be | |
| 739 // written into the popup by script on the main page). For these cases, | |
| 740 // there isn't any navigation stuff we can do, so just ignore it. | |
| 741 if (!GetLastCommittedEntry()) | |
| 742 return content::NAVIGATION_TYPE_NAV_IGNORE; | |
| 743 | |
| 744 // Valid subframe navigation. | |
| 745 return content::NAVIGATION_TYPE_NEW_SUBFRAME; | |
| 746 } | |
| 747 | |
| 748 // Now we know that the notification is for an existing page. Find that entry. | |
| 749 int existing_entry_index = GetEntryIndexWithPageID( | |
| 750 tab_contents_->GetSiteInstance(), | |
| 751 params.page_id); | |
| 752 if (existing_entry_index == -1) { | |
| 753 // The page was not found. It could have been pruned because of the limit on | |
| 754 // back/forward entries (not likely since we'll usually tell it to navigate | |
| 755 // to such entries). It could also mean that the renderer is smoking crack. | |
| 756 NOTREACHED(); | |
| 757 | |
| 758 // Because the unknown entry has committed, we risk showing the wrong URL in | |
| 759 // release builds. Instead, we'll kill the renderer process to be safe. | |
| 760 LOG(ERROR) << "terminating renderer for bad navigation: " << params.url; | |
| 761 content::RecordAction(UserMetricsAction("BadMessageTerminate_NC")); | |
| 762 | |
| 763 // Temporary code so we can get more information. Format: | |
| 764 // http://url/foo.html#page1#max3#frame1#ids:2_Nx,1_1x,3_2 | |
| 765 std::string temp = params.url.spec(); | |
| 766 temp.append("#page"); | |
| 767 temp.append(base::IntToString(params.page_id)); | |
| 768 temp.append("#max"); | |
| 769 temp.append(base::IntToString(tab_contents_->GetMaxPageID())); | |
| 770 temp.append("#frame"); | |
| 771 temp.append(base::IntToString(params.frame_id)); | |
| 772 temp.append("#ids"); | |
| 773 for (int i = 0; i < static_cast<int>(entries_.size()); ++i) { | |
| 774 // Append entry metadata (e.g., 3_7x): | |
| 775 // 3: page_id | |
| 776 // 7: SiteInstance ID, or N for null | |
| 777 // x: appended if not from the current SiteInstance | |
| 778 temp.append(base::IntToString(entries_[i]->GetPageID())); | |
| 779 temp.append("_"); | |
| 780 if (entries_[i]->site_instance()) | |
| 781 temp.append(base::IntToString(entries_[i]->site_instance()->GetId())); | |
| 782 else | |
| 783 temp.append("N"); | |
| 784 if (entries_[i]->site_instance() != tab_contents_->GetSiteInstance()) | |
| 785 temp.append("x"); | |
| 786 temp.append(","); | |
| 787 } | |
| 788 GURL url(temp); | |
| 789 static_cast<RenderViewHostImpl*>( | |
| 790 tab_contents_->GetRenderViewHost())->Send( | |
| 791 new ViewMsg_TempCrashWithData(url)); | |
| 792 return content::NAVIGATION_TYPE_NAV_IGNORE; | |
| 793 } | |
| 794 NavigationEntryImpl* existing_entry = entries_[existing_entry_index].get(); | |
| 795 | |
| 796 if (!content::PageTransitionIsMainFrame(params.transition)) { | |
| 797 // All manual subframes would get new IDs and were handled above, so we | |
| 798 // know this is auto. Since the current page was found in the navigation | |
| 799 // entry list, we're guaranteed to have a last committed entry. | |
| 800 DCHECK(GetLastCommittedEntry()); | |
| 801 return content::NAVIGATION_TYPE_AUTO_SUBFRAME; | |
| 802 } | |
| 803 | |
| 804 // Anything below here we know is a main frame navigation. | |
| 805 if (pending_entry_ && | |
| 806 existing_entry != pending_entry_ && | |
| 807 pending_entry_->GetPageID() == -1 && | |
| 808 existing_entry == GetLastCommittedEntry()) { | |
| 809 // In this case, we have a pending entry for a URL but WebCore didn't do a | |
| 810 // new navigation. This happens when you press enter in the URL bar to | |
| 811 // reload. We will create a pending entry, but WebKit will convert it to | |
| 812 // a reload since it's the same page and not create a new entry for it | |
| 813 // (the user doesn't want to have a new back/forward entry when they do | |
| 814 // this). If this matches the last committed entry, we want to just ignore | |
| 815 // the pending entry and go back to where we were (the "existing entry"). | |
| 816 return content::NAVIGATION_TYPE_SAME_PAGE; | |
| 817 } | |
| 818 | |
| 819 // Any toplevel navigations with the same base (minus the reference fragment) | |
| 820 // are in-page navigations. We weeded out subframe navigations above. Most of | |
| 821 // the time this doesn't matter since WebKit doesn't tell us about subframe | |
| 822 // navigations that don't actually navigate, but it can happen when there is | |
| 823 // an encoding override (it always sends a navigation request). | |
| 824 if (AreURLsInPageNavigation(existing_entry->GetURL(), params.url)) | |
| 825 return content::NAVIGATION_TYPE_IN_PAGE; | |
| 826 | |
| 827 // Since we weeded out "new" navigations above, we know this is an existing | |
| 828 // (back/forward) navigation. | |
| 829 return content::NAVIGATION_TYPE_EXISTING_PAGE; | |
| 830 } | |
| 831 | |
| 832 bool NavigationControllerImpl::IsRedirect( | |
| 833 const ViewHostMsg_FrameNavigate_Params& params) { | |
| 834 // For main frame transition, we judge by params.transition. | |
| 835 // Otherwise, by params.redirects. | |
| 836 if (content::PageTransitionIsMainFrame(params.transition)) { | |
| 837 return content::PageTransitionIsRedirect(params.transition); | |
| 838 } | |
| 839 return params.redirects.size() > 1; | |
| 840 } | |
| 841 | |
| 842 void NavigationControllerImpl::RendererDidNavigateToNewPage( | |
| 843 const ViewHostMsg_FrameNavigate_Params& params, bool* did_replace_entry) { | |
| 844 NavigationEntryImpl* new_entry; | |
| 845 bool update_virtual_url; | |
| 846 if (pending_entry_) { | |
| 847 // TODO(brettw) this assumes that the pending entry is appropriate for the | |
| 848 // new page that was just loaded. I don't think this is necessarily the | |
| 849 // case! We should have some more tracking to know for sure. | |
| 850 new_entry = new NavigationEntryImpl(*pending_entry_); | |
| 851 | |
| 852 // Don't use the page type from the pending entry. Some interstitial page | |
| 853 // may have set the type to interstitial. Once we commit, however, the page | |
| 854 // type must always be normal. | |
| 855 new_entry->set_page_type(content::PAGE_TYPE_NORMAL); | |
| 856 update_virtual_url = new_entry->update_virtual_url_with_url(); | |
| 857 } else { | |
| 858 new_entry = new NavigationEntryImpl; | |
| 859 // When navigating to a new page, give the browser URL handler a chance to | |
| 860 // update the virtual URL based on the new URL. For example, this is needed | |
| 861 // to show chrome://bookmarks/#1 when the bookmarks webui extension changes | |
| 862 // the URL. | |
| 863 update_virtual_url = true; | |
| 864 } | |
| 865 | |
| 866 new_entry->SetURL(params.url); | |
| 867 if (update_virtual_url) | |
| 868 UpdateVirtualURLToURL(new_entry, params.url); | |
| 869 new_entry->SetReferrer(params.referrer); | |
| 870 new_entry->SetPageID(params.page_id); | |
| 871 new_entry->SetTransitionType(params.transition); | |
| 872 new_entry->set_site_instance( | |
| 873 static_cast<SiteInstanceImpl*>(tab_contents_->GetSiteInstance())); | |
| 874 new_entry->SetHasPostData(params.is_post); | |
| 875 new_entry->SetPostID(params.post_id); | |
| 876 | |
| 877 InsertOrReplaceEntry(new_entry, *did_replace_entry); | |
| 878 } | |
| 879 | |
| 880 void NavigationControllerImpl::RendererDidNavigateToExistingPage( | |
| 881 const ViewHostMsg_FrameNavigate_Params& params) { | |
| 882 // We should only get here for main frame navigations. | |
| 883 DCHECK(content::PageTransitionIsMainFrame(params.transition)); | |
| 884 | |
| 885 // This is a back/forward navigation. The existing page for the ID is | |
| 886 // guaranteed to exist by ClassifyNavigation, and we just need to update it | |
| 887 // with new information from the renderer. | |
| 888 int entry_index = GetEntryIndexWithPageID(tab_contents_->GetSiteInstance(), | |
| 889 params.page_id); | |
| 890 DCHECK(entry_index >= 0 && | |
| 891 entry_index < static_cast<int>(entries_.size())); | |
| 892 NavigationEntryImpl* entry = entries_[entry_index].get(); | |
| 893 | |
| 894 // The URL may have changed due to redirects. The site instance will normally | |
| 895 // be the same except during session restore, when no site instance will be | |
| 896 // assigned. | |
| 897 entry->SetURL(params.url); | |
| 898 if (entry->update_virtual_url_with_url()) | |
| 899 UpdateVirtualURLToURL(entry, params.url); | |
| 900 DCHECK(entry->site_instance() == NULL || | |
| 901 entry->site_instance() == tab_contents_->GetSiteInstance()); | |
| 902 entry->set_site_instance( | |
| 903 static_cast<SiteInstanceImpl*>(tab_contents_->GetSiteInstance())); | |
| 904 | |
| 905 entry->SetHasPostData(params.is_post); | |
| 906 entry->SetPostID(params.post_id); | |
| 907 | |
| 908 // The entry we found in the list might be pending if the user hit | |
| 909 // back/forward/reload. This load should commit it (since it's already in the | |
| 910 // list, we can just discard the pending pointer). We should also discard the | |
| 911 // pending entry if it corresponds to a different navigation, since that one | |
| 912 // is now likely canceled. If it is not canceled, we will treat it as a new | |
| 913 // navigation when it arrives, which is also ok. | |
| 914 // | |
| 915 // Note that we need to use the "internal" version since we don't want to | |
| 916 // actually change any other state, just kill the pointer. | |
| 917 if (pending_entry_) | |
| 918 DiscardNonCommittedEntriesInternal(); | |
| 919 | |
| 920 // If a transient entry was removed, the indices might have changed, so we | |
| 921 // have to query the entry index again. | |
| 922 last_committed_entry_index_ = | |
| 923 GetEntryIndexWithPageID(tab_contents_->GetSiteInstance(), params.page_id); | |
| 924 } | |
| 925 | |
| 926 void NavigationControllerImpl::RendererDidNavigateToSamePage( | |
| 927 const ViewHostMsg_FrameNavigate_Params& params) { | |
| 928 // This mode implies we have a pending entry that's the same as an existing | |
| 929 // entry for this page ID. This entry is guaranteed to exist by | |
| 930 // ClassifyNavigation. All we need to do is update the existing entry. | |
| 931 NavigationEntryImpl* existing_entry = GetEntryWithPageID( | |
| 932 tab_contents_->GetSiteInstance(), params.page_id); | |
| 933 | |
| 934 // We assign the entry's unique ID to be that of the new one. Since this is | |
| 935 // always the result of a user action, we want to dismiss infobars, etc. like | |
| 936 // a regular user-initiated navigation. | |
| 937 existing_entry->set_unique_id(pending_entry_->GetUniqueID()); | |
| 938 | |
| 939 // The URL may have changed due to redirects. | |
| 940 if (existing_entry->update_virtual_url_with_url()) | |
| 941 UpdateVirtualURLToURL(existing_entry, params.url); | |
| 942 existing_entry->SetURL(params.url); | |
| 943 | |
| 944 DiscardNonCommittedEntries(); | |
| 945 } | |
| 946 | |
| 947 void NavigationControllerImpl::RendererDidNavigateInPage( | |
| 948 const ViewHostMsg_FrameNavigate_Params& params, bool* did_replace_entry) { | |
| 949 DCHECK(content::PageTransitionIsMainFrame(params.transition)) << | |
| 950 "WebKit should only tell us about in-page navs for the main frame."; | |
| 951 // We're guaranteed to have an entry for this one. | |
| 952 NavigationEntryImpl* existing_entry = GetEntryWithPageID( | |
| 953 tab_contents_->GetSiteInstance(), params.page_id); | |
| 954 | |
| 955 // Reference fragment navigation. We're guaranteed to have the last_committed | |
| 956 // entry and it will be the same page as the new navigation (minus the | |
| 957 // reference fragments, of course). We'll update the URL of the existing | |
| 958 // entry without pruning the forward history. | |
| 959 existing_entry->SetURL(params.url); | |
| 960 if (existing_entry->update_virtual_url_with_url()) | |
| 961 UpdateVirtualURLToURL(existing_entry, params.url); | |
| 962 | |
| 963 // This replaces the existing entry since the page ID didn't change. | |
| 964 *did_replace_entry = true; | |
| 965 | |
| 966 if (pending_entry_) | |
| 967 DiscardNonCommittedEntriesInternal(); | |
| 968 | |
| 969 // If a transient entry was removed, the indices might have changed, so we | |
| 970 // have to query the entry index again. | |
| 971 last_committed_entry_index_ = | |
| 972 GetEntryIndexWithPageID(tab_contents_->GetSiteInstance(), params.page_id); | |
| 973 } | |
| 974 | |
| 975 void NavigationControllerImpl::RendererDidNavigateNewSubframe( | |
| 976 const ViewHostMsg_FrameNavigate_Params& params) { | |
| 977 if (content::PageTransitionStripQualifier(params.transition) == | |
| 978 content::PAGE_TRANSITION_AUTO_SUBFRAME) { | |
| 979 // This is not user-initiated. Ignore. | |
| 980 return; | |
| 981 } | |
| 982 | |
| 983 // Manual subframe navigations just get the current entry cloned so the user | |
| 984 // can go back or forward to it. The actual subframe information will be | |
| 985 // stored in the page state for each of those entries. This happens out of | |
| 986 // band with the actual navigations. | |
| 987 DCHECK(GetLastCommittedEntry()) << "ClassifyNavigation should guarantee " | |
| 988 << "that a last committed entry exists."; | |
| 989 NavigationEntryImpl* new_entry = new NavigationEntryImpl( | |
| 990 *NavigationEntryImpl::FromNavigationEntry(GetLastCommittedEntry())); | |
| 991 new_entry->SetPageID(params.page_id); | |
| 992 InsertOrReplaceEntry(new_entry, false); | |
| 993 } | |
| 994 | |
| 995 bool NavigationControllerImpl::RendererDidNavigateAutoSubframe( | |
| 996 const ViewHostMsg_FrameNavigate_Params& params) { | |
| 997 // We're guaranteed to have a previously committed entry, and we now need to | |
| 998 // handle navigation inside of a subframe in it without creating a new entry. | |
| 999 DCHECK(GetLastCommittedEntry()); | |
| 1000 | |
| 1001 // Handle the case where we're navigating back/forward to a previous subframe | |
| 1002 // navigation entry. This is case "2." in NAV_AUTO_SUBFRAME comment in the | |
| 1003 // header file. In case "1." this will be a NOP. | |
| 1004 int entry_index = GetEntryIndexWithPageID( | |
| 1005 tab_contents_->GetSiteInstance(), | |
| 1006 params.page_id); | |
| 1007 if (entry_index < 0 || | |
| 1008 entry_index >= static_cast<int>(entries_.size())) { | |
| 1009 NOTREACHED(); | |
| 1010 return false; | |
| 1011 } | |
| 1012 | |
| 1013 // Update the current navigation entry in case we're going back/forward. | |
| 1014 if (entry_index != last_committed_entry_index_) { | |
| 1015 last_committed_entry_index_ = entry_index; | |
| 1016 return true; | |
| 1017 } | |
| 1018 return false; | |
| 1019 } | |
| 1020 | |
| 1021 int NavigationControllerImpl::GetIndexOfEntry( | |
| 1022 const NavigationEntryImpl* entry) const { | |
| 1023 const NavigationEntries::const_iterator i(std::find( | |
| 1024 entries_.begin(), | |
| 1025 entries_.end(), | |
| 1026 entry)); | |
| 1027 return (i == entries_.end()) ? -1 : static_cast<int>(i - entries_.begin()); | |
| 1028 } | |
| 1029 | |
| 1030 bool NavigationControllerImpl::IsURLInPageNavigation(const GURL& url) const { | |
| 1031 NavigationEntry* last_committed = GetLastCommittedEntry(); | |
| 1032 if (!last_committed) | |
| 1033 return false; | |
| 1034 return AreURLsInPageNavigation(last_committed->GetURL(), url); | |
| 1035 } | |
| 1036 | |
| 1037 void NavigationControllerImpl::CopyStateFrom( | |
| 1038 const NavigationController& temp) { | |
| 1039 const NavigationControllerImpl& source = | |
| 1040 static_cast<const NavigationControllerImpl&>(temp); | |
| 1041 // Verify that we look new. | |
| 1042 DCHECK(GetEntryCount() == 0 && !GetPendingEntry()); | |
| 1043 | |
| 1044 if (source.GetEntryCount() == 0) | |
| 1045 return; // Nothing new to do. | |
| 1046 | |
| 1047 needs_reload_ = true; | |
| 1048 InsertEntriesFrom(source, source.GetEntryCount()); | |
| 1049 | |
| 1050 session_storage_namespace_ = source.session_storage_namespace_->Clone(); | |
| 1051 | |
| 1052 FinishRestore(source.last_committed_entry_index_, false); | |
| 1053 | |
| 1054 // Copy the max page id map from the old tab to the new tab. This ensures | |
| 1055 // that new and existing navigations in the tab's current SiteInstances | |
| 1056 // are identified properly. | |
| 1057 tab_contents_->CopyMaxPageIDsFrom(source.tab_contents()); | |
| 1058 } | |
| 1059 | |
| 1060 void NavigationControllerImpl::CopyStateFromAndPrune( | |
| 1061 NavigationController* temp) { | |
| 1062 NavigationControllerImpl* source = | |
| 1063 static_cast<NavigationControllerImpl*>(temp); | |
| 1064 // The SiteInstance and page_id of the last committed entry needs to be | |
| 1065 // remembered at this point, in case there is only one committed entry | |
| 1066 // and it is pruned. We use a scoped_refptr to ensure the SiteInstance | |
| 1067 // can't be freed during this time period. | |
| 1068 NavigationEntryImpl* last_committed = | |
| 1069 NavigationEntryImpl::FromNavigationEntry(GetLastCommittedEntry()); | |
| 1070 scoped_refptr<SiteInstance> site_instance( | |
| 1071 last_committed ? last_committed->site_instance() : NULL); | |
| 1072 int32 minimum_page_id = last_committed ? last_committed->GetPageID() : -1; | |
| 1073 int32 max_page_id = last_committed ? | |
| 1074 tab_contents_->GetMaxPageIDForSiteInstance(site_instance.get()) : -1; | |
| 1075 | |
| 1076 // This code is intended for use when the last entry is the active entry. | |
| 1077 DCHECK( | |
| 1078 (transient_entry_index_ != -1 && | |
| 1079 transient_entry_index_ == GetEntryCount() - 1) || | |
| 1080 (pending_entry_ && (pending_entry_index_ == -1 || | |
| 1081 pending_entry_index_ == GetEntryCount() - 1)) || | |
| 1082 (!pending_entry_ && last_committed_entry_index_ == GetEntryCount() - 1)); | |
| 1083 | |
| 1084 // Remove all the entries leaving the active entry. | |
| 1085 PruneAllButActive(); | |
| 1086 | |
| 1087 // We now have zero or one entries. Ensure that adding the entries from | |
| 1088 // source won't put us over the limit. | |
| 1089 DCHECK(GetEntryCount() == 0 || GetEntryCount() == 1); | |
| 1090 if (GetEntryCount() > 0) | |
| 1091 source->PruneOldestEntryIfFull(); | |
| 1092 | |
| 1093 // Insert the entries from source. Don't use source->GetCurrentEntryIndex as | |
| 1094 // we don't want to copy over the transient entry. | |
| 1095 int max_source_index = source->pending_entry_index_ != -1 ? | |
| 1096 source->pending_entry_index_ : source->last_committed_entry_index_; | |
| 1097 if (max_source_index == -1) | |
| 1098 max_source_index = source->GetEntryCount(); | |
| 1099 else | |
| 1100 max_source_index++; | |
| 1101 InsertEntriesFrom(*source, max_source_index); | |
| 1102 | |
| 1103 // Adjust indices such that the last entry and pending are at the end now. | |
| 1104 last_committed_entry_index_ = GetEntryCount() - 1; | |
| 1105 if (pending_entry_index_ != -1) | |
| 1106 pending_entry_index_ = GetEntryCount() - 1; | |
| 1107 if (transient_entry_index_ != -1) { | |
| 1108 // There's a transient entry. In this case we want the last committed to | |
| 1109 // point to the previous entry. | |
| 1110 transient_entry_index_ = GetEntryCount() - 1; | |
| 1111 if (last_committed_entry_index_ != -1) | |
| 1112 last_committed_entry_index_--; | |
| 1113 } | |
| 1114 | |
| 1115 tab_contents_->SetHistoryLengthAndPrune(site_instance.get(), | |
| 1116 max_source_index, | |
| 1117 minimum_page_id); | |
| 1118 | |
| 1119 // Copy the max page id map from the old tab to the new tab. This ensures | |
| 1120 // that new and existing navigations in the tab's current SiteInstances | |
| 1121 // are identified properly. | |
| 1122 tab_contents_->CopyMaxPageIDsFrom(source->tab_contents()); | |
| 1123 | |
| 1124 // If there is a last committed entry, be sure to include it in the new | |
| 1125 // max page ID map. | |
| 1126 if (max_page_id > -1) { | |
| 1127 tab_contents_->UpdateMaxPageIDForSiteInstance(site_instance.get(), | |
| 1128 max_page_id); | |
| 1129 } | |
| 1130 } | |
| 1131 | |
| 1132 void NavigationControllerImpl::PruneAllButActive() { | |
| 1133 if (transient_entry_index_ != -1) { | |
| 1134 // There is a transient entry. Prune up to it. | |
| 1135 DCHECK_EQ(GetEntryCount() - 1, transient_entry_index_); | |
| 1136 entries_.erase(entries_.begin(), entries_.begin() + transient_entry_index_); | |
| 1137 transient_entry_index_ = 0; | |
| 1138 last_committed_entry_index_ = -1; | |
| 1139 pending_entry_index_ = -1; | |
| 1140 } else if (!pending_entry_) { | |
| 1141 // There's no pending entry. Leave the last entry (if there is one). | |
| 1142 if (!GetEntryCount()) | |
| 1143 return; | |
| 1144 | |
| 1145 DCHECK(last_committed_entry_index_ >= 0); | |
| 1146 entries_.erase(entries_.begin(), | |
| 1147 entries_.begin() + last_committed_entry_index_); | |
| 1148 entries_.erase(entries_.begin() + 1, entries_.end()); | |
| 1149 last_committed_entry_index_ = 0; | |
| 1150 } else if (pending_entry_index_ != -1) { | |
| 1151 entries_.erase(entries_.begin(), entries_.begin() + pending_entry_index_); | |
| 1152 entries_.erase(entries_.begin() + 1, entries_.end()); | |
| 1153 pending_entry_index_ = 0; | |
| 1154 last_committed_entry_index_ = 0; | |
| 1155 } else { | |
| 1156 // There is a pending_entry, but it's not in entries_. | |
| 1157 pending_entry_index_ = -1; | |
| 1158 last_committed_entry_index_ = -1; | |
| 1159 entries_.clear(); | |
| 1160 } | |
| 1161 | |
| 1162 if (tab_contents_->GetInterstitialPage()) { | |
| 1163 // Normally the interstitial page hides itself if the user doesn't proceeed. | |
| 1164 // This would result in showing a NavigationEntry we just removed. Set this | |
| 1165 // so the interstitial triggers a reload if the user doesn't proceed. | |
| 1166 static_cast<InterstitialPageImpl*>(tab_contents_->GetInterstitialPage())-> | |
| 1167 set_reload_on_dont_proceed(true); | |
| 1168 } | |
| 1169 } | |
| 1170 | |
| 1171 void NavigationControllerImpl::SetMaxRestoredPageID(int32 max_id) { | |
| 1172 max_restored_page_id_ = max_id; | |
| 1173 } | |
| 1174 | |
| 1175 int32 NavigationControllerImpl::GetMaxRestoredPageID() const { | |
| 1176 return max_restored_page_id_; | |
| 1177 } | |
| 1178 | |
| 1179 SessionStorageNamespace* | |
| 1180 NavigationControllerImpl::GetSessionStorageNamespace() const { | |
| 1181 return session_storage_namespace_; | |
| 1182 } | |
| 1183 | |
| 1184 bool NavigationControllerImpl::NeedsReload() const { | |
| 1185 return needs_reload_; | |
| 1186 } | |
| 1187 | |
| 1188 void NavigationControllerImpl::RemoveEntryAtIndexInternal(int index) { | |
| 1189 DCHECK(index < GetEntryCount()); | |
| 1190 DCHECK(index != last_committed_entry_index_); | |
| 1191 | |
| 1192 DiscardNonCommittedEntries(); | |
| 1193 | |
| 1194 entries_.erase(entries_.begin() + index); | |
| 1195 if (last_committed_entry_index_ > index) | |
| 1196 last_committed_entry_index_--; | |
| 1197 } | |
| 1198 | |
| 1199 void NavigationControllerImpl::DiscardNonCommittedEntries() { | |
| 1200 bool transient = transient_entry_index_ != -1; | |
| 1201 DiscardNonCommittedEntriesInternal(); | |
| 1202 | |
| 1203 // If there was a transient entry, invalidate everything so the new active | |
| 1204 // entry state is shown. | |
| 1205 if (transient) { | |
| 1206 tab_contents_->NotifyNavigationStateChanged(kInvalidateAll); | |
| 1207 } | |
| 1208 } | |
| 1209 | |
| 1210 NavigationEntry* NavigationControllerImpl::GetPendingEntry() const { | |
| 1211 return pending_entry_; | |
| 1212 } | |
| 1213 | |
| 1214 int NavigationControllerImpl::GetPendingEntryIndex() const { | |
| 1215 return pending_entry_index_; | |
| 1216 } | |
| 1217 | |
| 1218 void NavigationControllerImpl::InsertOrReplaceEntry(NavigationEntryImpl* entry, | |
| 1219 bool replace) { | |
| 1220 DCHECK(entry->GetTransitionType() != content::PAGE_TRANSITION_AUTO_SUBFRAME); | |
| 1221 | |
| 1222 // Copy the pending entry's unique ID to the committed entry. | |
| 1223 // I don't know if pending_entry_index_ can be other than -1 here. | |
| 1224 const NavigationEntryImpl* const pending_entry = | |
| 1225 (pending_entry_index_ == -1) ? | |
| 1226 pending_entry_ : entries_[pending_entry_index_].get(); | |
| 1227 if (pending_entry) | |
| 1228 entry->set_unique_id(pending_entry->GetUniqueID()); | |
| 1229 | |
| 1230 DiscardNonCommittedEntriesInternal(); | |
| 1231 | |
| 1232 int current_size = static_cast<int>(entries_.size()); | |
| 1233 | |
| 1234 if (current_size > 0) { | |
| 1235 // Prune any entries which are in front of the current entry. | |
| 1236 // Also prune the current entry if we are to replace the current entry. | |
| 1237 // last_committed_entry_index_ must be updated here since calls to | |
| 1238 // NotifyPrunedEntries() below may re-enter and we must make sure | |
| 1239 // last_committed_entry_index_ is not left in an invalid state. | |
| 1240 if (replace) | |
| 1241 --last_committed_entry_index_; | |
| 1242 | |
| 1243 int num_pruned = 0; | |
| 1244 while (last_committed_entry_index_ < (current_size - 1)) { | |
| 1245 num_pruned++; | |
| 1246 entries_.pop_back(); | |
| 1247 current_size--; | |
| 1248 } | |
| 1249 if (num_pruned > 0) // Only notify if we did prune something. | |
| 1250 NotifyPrunedEntries(this, false, num_pruned); | |
| 1251 } | |
| 1252 | |
| 1253 PruneOldestEntryIfFull(); | |
| 1254 | |
| 1255 entries_.push_back(linked_ptr<NavigationEntryImpl>(entry)); | |
| 1256 last_committed_entry_index_ = static_cast<int>(entries_.size()) - 1; | |
| 1257 | |
| 1258 // This is a new page ID, so we need everybody to know about it. | |
| 1259 tab_contents_->UpdateMaxPageID(entry->GetPageID()); | |
| 1260 } | |
| 1261 | |
| 1262 void NavigationControllerImpl::PruneOldestEntryIfFull() { | |
| 1263 if (entries_.size() >= max_entry_count()) { | |
| 1264 DCHECK_EQ(max_entry_count(), entries_.size()); | |
| 1265 DCHECK(last_committed_entry_index_ > 0); | |
| 1266 RemoveEntryAtIndex(0); | |
| 1267 NotifyPrunedEntries(this, true, 1); | |
| 1268 } | |
| 1269 } | |
| 1270 | |
| 1271 void NavigationControllerImpl::NavigateToPendingEntry(ReloadType reload_type) { | |
| 1272 needs_reload_ = false; | |
| 1273 | |
| 1274 // If we were navigating to a slow-to-commit page, and the user performs | |
| 1275 // a session history navigation to the last committed page, RenderViewHost | |
| 1276 // will force the throbber to start, but WebKit will essentially ignore the | |
| 1277 // navigation, and won't send a message to stop the throbber. To prevent this | |
| 1278 // from happening, we drop the navigation here and stop the slow-to-commit | |
| 1279 // page from loading (which would normally happen during the navigation). | |
| 1280 if (pending_entry_index_ != -1 && | |
| 1281 pending_entry_index_ == last_committed_entry_index_ && | |
| 1282 (entries_[pending_entry_index_]->restore_type() == | |
| 1283 NavigationEntryImpl::RESTORE_NONE) && | |
| 1284 (entries_[pending_entry_index_]->GetTransitionType() & | |
| 1285 content::PAGE_TRANSITION_FORWARD_BACK)) { | |
| 1286 tab_contents_->Stop(); | |
| 1287 | |
| 1288 // If an interstitial page is showing, we want to close it to get back | |
| 1289 // to what was showing before. | |
| 1290 if (tab_contents_->GetInterstitialPage()) | |
| 1291 tab_contents_->GetInterstitialPage()->DontProceed(); | |
| 1292 | |
| 1293 DiscardNonCommittedEntries(); | |
| 1294 return; | |
| 1295 } | |
| 1296 | |
| 1297 // If an interstitial page is showing, the previous renderer is blocked and | |
| 1298 // cannot make new requests. Unblock (and disable) it to allow this | |
| 1299 // navigation to succeed. The interstitial will stay visible until the | |
| 1300 // resulting DidNavigate. | |
| 1301 if (tab_contents_->GetInterstitialPage()) { | |
| 1302 static_cast<InterstitialPageImpl*>(tab_contents_->GetInterstitialPage())-> | |
| 1303 CancelForNavigation(); | |
| 1304 } | |
| 1305 | |
| 1306 // For session history navigations only the pending_entry_index_ is set. | |
| 1307 if (!pending_entry_) { | |
| 1308 DCHECK_NE(pending_entry_index_, -1); | |
| 1309 pending_entry_ = entries_[pending_entry_index_].get(); | |
| 1310 } | |
| 1311 | |
| 1312 if (!tab_contents_->NavigateToPendingEntry(reload_type)) | |
| 1313 DiscardNonCommittedEntries(); | |
| 1314 | |
| 1315 // If the entry is being restored and doesn't have a SiteInstance yet, fill | |
| 1316 // it in now that we know. This allows us to find the entry when it commits. | |
| 1317 // This works for browser-initiated navigations. We handle renderer-initiated | |
| 1318 // navigations to restored entries in TabContents::OnGoToEntryAtOffset. | |
| 1319 if (pending_entry_ && !pending_entry_->site_instance() && | |
| 1320 pending_entry_->restore_type() != NavigationEntryImpl::RESTORE_NONE) { | |
| 1321 pending_entry_->set_site_instance(static_cast<SiteInstanceImpl*>( | |
| 1322 tab_contents_->GetPendingSiteInstance())); | |
| 1323 pending_entry_->set_restore_type(NavigationEntryImpl::RESTORE_NONE); | |
| 1324 } | |
| 1325 } | |
| 1326 | |
| 1327 void NavigationControllerImpl::NotifyNavigationEntryCommitted( | |
| 1328 content::LoadCommittedDetails* details) { | |
| 1329 details->entry = GetActiveEntry(); | |
| 1330 content::NotificationDetails notification_details = | |
| 1331 content::Details<content::LoadCommittedDetails>(details); | |
| 1332 | |
| 1333 // We need to notify the ssl_manager_ before the tab_contents_ so the | |
| 1334 // location bar will have up-to-date information about the security style | |
| 1335 // when it wants to draw. See http://crbug.com/11157 | |
| 1336 ssl_manager_.DidCommitProvisionalLoad(notification_details); | |
| 1337 | |
| 1338 // TODO(pkasting): http://b/1113079 Probably these explicit notification paths | |
| 1339 // should be removed, and interested parties should just listen for the | |
| 1340 // notification below instead. | |
| 1341 tab_contents_->NotifyNavigationStateChanged(kInvalidateAll); | |
| 1342 | |
| 1343 content::NotificationService::current()->Notify( | |
| 1344 content::NOTIFICATION_NAV_ENTRY_COMMITTED, | |
| 1345 content::Source<NavigationController>(this), | |
| 1346 notification_details); | |
| 1347 } | |
| 1348 | |
| 1349 // static | |
| 1350 size_t NavigationControllerImpl::max_entry_count() { | |
| 1351 if (max_entry_count_for_testing_ != kMaxEntryCountForTestingNotSet) | |
| 1352 return max_entry_count_for_testing_; | |
| 1353 return content::kMaxSessionHistoryEntries; | |
| 1354 } | |
| 1355 | |
| 1356 void NavigationControllerImpl::SetActive(bool is_active) { | |
| 1357 if (is_active && needs_reload_) | |
| 1358 LoadIfNecessary(); | |
| 1359 } | |
| 1360 | |
| 1361 void NavigationControllerImpl::LoadIfNecessary() { | |
| 1362 if (!needs_reload_) | |
| 1363 return; | |
| 1364 | |
| 1365 // Calling Reload() results in ignoring state, and not loading. | |
| 1366 // Explicitly use NavigateToPendingEntry so that the renderer uses the | |
| 1367 // cached state. | |
| 1368 pending_entry_index_ = last_committed_entry_index_; | |
| 1369 NavigateToPendingEntry(NO_RELOAD); | |
| 1370 } | |
| 1371 | |
| 1372 void NavigationControllerImpl::NotifyEntryChanged(const NavigationEntry* entry, | |
| 1373 int index) { | |
| 1374 content::EntryChangedDetails det; | |
| 1375 det.changed_entry = entry; | |
| 1376 det.index = index; | |
| 1377 content::NotificationService::current()->Notify( | |
| 1378 content::NOTIFICATION_NAV_ENTRY_CHANGED, | |
| 1379 content::Source<NavigationController>(this), | |
| 1380 content::Details<content::EntryChangedDetails>(&det)); | |
| 1381 } | |
| 1382 | |
| 1383 void NavigationControllerImpl::FinishRestore(int selected_index, | |
| 1384 bool from_last_session) { | |
| 1385 DCHECK(selected_index >= 0 && selected_index < GetEntryCount()); | |
| 1386 ConfigureEntriesForRestore(&entries_, from_last_session); | |
| 1387 | |
| 1388 SetMaxRestoredPageID(static_cast<int32>(GetEntryCount())); | |
| 1389 | |
| 1390 last_committed_entry_index_ = selected_index; | |
| 1391 } | |
| 1392 | |
| 1393 void NavigationControllerImpl::DiscardNonCommittedEntriesInternal() { | |
| 1394 if (pending_entry_index_ == -1) | |
| 1395 delete pending_entry_; | |
| 1396 pending_entry_ = NULL; | |
| 1397 pending_entry_index_ = -1; | |
| 1398 | |
| 1399 DiscardTransientEntry(); | |
| 1400 } | |
| 1401 | |
| 1402 void NavigationControllerImpl::DiscardTransientEntry() { | |
| 1403 if (transient_entry_index_ == -1) | |
| 1404 return; | |
| 1405 entries_.erase(entries_.begin() + transient_entry_index_); | |
| 1406 if (last_committed_entry_index_ > transient_entry_index_) | |
| 1407 last_committed_entry_index_--; | |
| 1408 transient_entry_index_ = -1; | |
| 1409 } | |
| 1410 | |
| 1411 int NavigationControllerImpl::GetEntryIndexWithPageID( | |
| 1412 SiteInstance* instance, int32 page_id) const { | |
| 1413 for (int i = static_cast<int>(entries_.size()) - 1; i >= 0; --i) { | |
| 1414 if ((entries_[i]->site_instance() == instance) && | |
| 1415 (entries_[i]->GetPageID() == page_id)) | |
| 1416 return i; | |
| 1417 } | |
| 1418 return -1; | |
| 1419 } | |
| 1420 | |
| 1421 NavigationEntry* NavigationControllerImpl::GetTransientEntry() const { | |
| 1422 if (transient_entry_index_ == -1) | |
| 1423 return NULL; | |
| 1424 return entries_[transient_entry_index_].get(); | |
| 1425 } | |
| 1426 | |
| 1427 void NavigationControllerImpl::InsertEntriesFrom( | |
| 1428 const NavigationControllerImpl& source, | |
| 1429 int max_index) { | |
| 1430 DCHECK_LE(max_index, source.GetEntryCount()); | |
| 1431 size_t insert_index = 0; | |
| 1432 for (int i = 0; i < max_index; i++) { | |
| 1433 // When cloning a tab, copy all entries except interstitial pages | |
| 1434 if (source.entries_[i].get()->GetPageType() != | |
| 1435 content::PAGE_TYPE_INTERSTITIAL) { | |
| 1436 entries_.insert(entries_.begin() + insert_index++, | |
| 1437 linked_ptr<NavigationEntryImpl>( | |
| 1438 new NavigationEntryImpl(*source.entries_[i]))); | |
| 1439 } | |
| 1440 } | |
| 1441 } | |
| OLD | NEW |