Home | History | Annotate | Download | only in frame_host
      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 "content/browser/frame_host/navigation_controller_impl.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/command_line.h"
      9 #include "base/debug/trace_event.h"
     10 #include "base/logging.h"
     11 #include "base/metrics/histogram.h"
     12 #include "base/strings/string_number_conversions.h"  // Temporary
     13 #include "base/strings/string_util.h"
     14 #include "base/strings/utf_string_conversions.h"
     15 #include "base/time/time.h"
     16 #include "cc/base/switches.h"
     17 #include "content/browser/browser_url_handler_impl.h"
     18 #include "content/browser/dom_storage/dom_storage_context_wrapper.h"
     19 #include "content/browser/dom_storage/session_storage_namespace_impl.h"
     20 #include "content/browser/frame_host/debug_urls.h"
     21 #include "content/browser/frame_host/interstitial_page_impl.h"
     22 #include "content/browser/frame_host/navigation_entry_impl.h"
     23 #include "content/browser/frame_host/navigation_entry_screenshot_manager.h"
     24 #include "content/browser/renderer_host/render_view_host_impl.h"  // Temporary
     25 #include "content/browser/site_instance_impl.h"
     26 #include "content/common/frame_messages.h"
     27 #include "content/common/view_messages.h"
     28 #include "content/public/browser/browser_context.h"
     29 #include "content/public/browser/content_browser_client.h"
     30 #include "content/public/browser/invalidate_type.h"
     31 #include "content/public/browser/navigation_details.h"
     32 #include "content/public/browser/notification_service.h"
     33 #include "content/public/browser/notification_types.h"
     34 #include "content/public/browser/render_widget_host.h"
     35 #include "content/public/browser/render_widget_host_view.h"
     36 #include "content/public/browser/storage_partition.h"
     37 #include "content/public/browser/user_metrics.h"
     38 #include "content/public/common/content_client.h"
     39 #include "content/public/common/content_constants.h"
     40 #include "net/base/escape.h"
     41 #include "net/base/mime_util.h"
     42 #include "net/base/net_util.h"
     43 #include "skia/ext/platform_canvas.h"
     44 #include "url/url_constants.h"
     45 
     46 namespace content {
     47 namespace {
     48 
     49 const int kInvalidateAll = 0xFFFFFFFF;
     50 
     51 // Invoked when entries have been pruned, or removed. For example, if the
     52 // current entries are [google, digg, yahoo], with the current entry google,
     53 // and the user types in cnet, then digg and yahoo are pruned.
     54 void NotifyPrunedEntries(NavigationControllerImpl* nav_controller,
     55                          bool from_front,
     56                          int count) {
     57   PrunedDetails details;
     58   details.from_front = from_front;
     59   details.count = count;
     60   NotificationService::current()->Notify(
     61       NOTIFICATION_NAV_LIST_PRUNED,
     62       Source<NavigationController>(nav_controller),
     63       Details<PrunedDetails>(&details));
     64 }
     65 
     66 // Ensure the given NavigationEntry has a valid state, so that WebKit does not
     67 // get confused if we navigate back to it.
     68 //
     69 // An empty state is treated as a new navigation by WebKit, which would mean
     70 // losing the navigation entries and generating a new navigation entry after
     71 // this one. We don't want that. To avoid this we create a valid state which
     72 // WebKit will not treat as a new navigation.
     73 void SetPageStateIfEmpty(NavigationEntryImpl* entry) {
     74   if (!entry->GetPageState().IsValid())
     75     entry->SetPageState(PageState::CreateFromURL(entry->GetURL()));
     76 }
     77 
     78 NavigationEntryImpl::RestoreType ControllerRestoreTypeToEntryType(
     79     NavigationController::RestoreType type) {
     80   switch (type) {
     81     case NavigationController::RESTORE_CURRENT_SESSION:
     82       return NavigationEntryImpl::RESTORE_CURRENT_SESSION;
     83     case NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY:
     84       return NavigationEntryImpl::RESTORE_LAST_SESSION_EXITED_CLEANLY;
     85     case NavigationController::RESTORE_LAST_SESSION_CRASHED:
     86       return NavigationEntryImpl::RESTORE_LAST_SESSION_CRASHED;
     87   }
     88   NOTREACHED();
     89   return NavigationEntryImpl::RESTORE_CURRENT_SESSION;
     90 }
     91 
     92 // Configure all the NavigationEntries in entries for restore. This resets
     93 // the transition type to reload and makes sure the content state isn't empty.
     94 void ConfigureEntriesForRestore(
     95     std::vector<linked_ptr<NavigationEntryImpl> >* entries,
     96     NavigationController::RestoreType type) {
     97   for (size_t i = 0; i < entries->size(); ++i) {
     98     // Use a transition type of reload so that we don't incorrectly increase
     99     // the typed count.
    100     (*entries)[i]->SetTransitionType(PAGE_TRANSITION_RELOAD);
    101     (*entries)[i]->set_restore_type(ControllerRestoreTypeToEntryType(type));
    102     // NOTE(darin): This code is only needed for backwards compat.
    103     SetPageStateIfEmpty((*entries)[i].get());
    104   }
    105 }
    106 
    107 // See NavigationController::IsURLInPageNavigation for how this works and why.
    108 bool AreURLsInPageNavigation(const GURL& existing_url,
    109                              const GURL& new_url,
    110                              bool renderer_says_in_page,
    111                              NavigationType navigation_type) {
    112   if (existing_url.GetOrigin() == new_url.GetOrigin())
    113     return renderer_says_in_page;
    114 
    115   if (!new_url.has_ref()) {
    116     // When going back from the ref URL to the non ref one the navigation type
    117     // is IN_PAGE.
    118     return navigation_type == NAVIGATION_TYPE_IN_PAGE;
    119   }
    120 
    121   url::Replacements<char> replacements;
    122   replacements.ClearRef();
    123   return existing_url.ReplaceComponents(replacements) ==
    124       new_url.ReplaceComponents(replacements);
    125 }
    126 
    127 // Determines whether or not we should be carrying over a user agent override
    128 // between two NavigationEntries.
    129 bool ShouldKeepOverride(const NavigationEntry* last_entry) {
    130   return last_entry && last_entry->GetIsOverridingUserAgent();
    131 }
    132 
    133 }  // namespace
    134 
    135 // NavigationControllerImpl ----------------------------------------------------
    136 
    137 const size_t kMaxEntryCountForTestingNotSet = -1;
    138 
    139 // static
    140 size_t NavigationControllerImpl::max_entry_count_for_testing_ =
    141     kMaxEntryCountForTestingNotSet;
    142 
    143 // Should Reload check for post data? The default is true, but is set to false
    144 // when testing.
    145 static bool g_check_for_repost = true;
    146 
    147 // static
    148 NavigationEntry* NavigationController::CreateNavigationEntry(
    149       const GURL& url,
    150       const Referrer& referrer,
    151       PageTransition transition,
    152       bool is_renderer_initiated,
    153       const std::string& extra_headers,
    154       BrowserContext* browser_context) {
    155   // Allow the browser URL handler to rewrite the URL. This will, for example,
    156   // remove "view-source:" from the beginning of the URL to get the URL that
    157   // will actually be loaded. This real URL won't be shown to the user, just
    158   // used internally.
    159   GURL loaded_url(url);
    160   bool reverse_on_redirect = false;
    161   BrowserURLHandlerImpl::GetInstance()->RewriteURLIfNecessary(
    162       &loaded_url, browser_context, &reverse_on_redirect);
    163 
    164   NavigationEntryImpl* entry = new NavigationEntryImpl(
    165       NULL,  // The site instance for tabs is sent on navigation
    166              // (WebContents::GetSiteInstance).
    167       -1,
    168       loaded_url,
    169       referrer,
    170       base::string16(),
    171       transition,
    172       is_renderer_initiated);
    173   entry->SetVirtualURL(url);
    174   entry->set_user_typed_url(url);
    175   entry->set_update_virtual_url_with_url(reverse_on_redirect);
    176   entry->set_extra_headers(extra_headers);
    177   return entry;
    178 }
    179 
    180 // static
    181 void NavigationController::DisablePromptOnRepost() {
    182   g_check_for_repost = false;
    183 }
    184 
    185 base::Time NavigationControllerImpl::TimeSmoother::GetSmoothedTime(
    186     base::Time t) {
    187   // If |t| is between the water marks, we're in a run of duplicates
    188   // or just getting out of it, so increase the high-water mark to get
    189   // a time that probably hasn't been used before and return it.
    190   if (low_water_mark_ <= t && t <= high_water_mark_) {
    191     high_water_mark_ += base::TimeDelta::FromMicroseconds(1);
    192     return high_water_mark_;
    193   }
    194 
    195   // Otherwise, we're clear of the last duplicate run, so reset the
    196   // water marks.
    197   low_water_mark_ = high_water_mark_ = t;
    198   return t;
    199 }
    200 
    201 NavigationControllerImpl::NavigationControllerImpl(
    202     NavigationControllerDelegate* delegate,
    203     BrowserContext* browser_context)
    204     : browser_context_(browser_context),
    205       pending_entry_(NULL),
    206       last_committed_entry_index_(-1),
    207       pending_entry_index_(-1),
    208       transient_entry_index_(-1),
    209       delegate_(delegate),
    210       max_restored_page_id_(-1),
    211       ssl_manager_(this),
    212       needs_reload_(false),
    213       is_initial_navigation_(true),
    214       in_navigate_to_pending_entry_(false),
    215       pending_reload_(NO_RELOAD),
    216       get_timestamp_callback_(base::Bind(&base::Time::Now)),
    217       screenshot_manager_(new NavigationEntryScreenshotManager(this)) {
    218   DCHECK(browser_context_);
    219 }
    220 
    221 NavigationControllerImpl::~NavigationControllerImpl() {
    222   DiscardNonCommittedEntriesInternal();
    223 }
    224 
    225 WebContents* NavigationControllerImpl::GetWebContents() const {
    226   return delegate_->GetWebContents();
    227 }
    228 
    229 BrowserContext* NavigationControllerImpl::GetBrowserContext() const {
    230   return browser_context_;
    231 }
    232 
    233 void NavigationControllerImpl::SetBrowserContext(
    234     BrowserContext* browser_context) {
    235   browser_context_ = browser_context;
    236 }
    237 
    238 void NavigationControllerImpl::Restore(
    239     int selected_navigation,
    240     RestoreType type,
    241     std::vector<NavigationEntry*>* entries) {
    242   // Verify that this controller is unused and that the input is valid.
    243   DCHECK(GetEntryCount() == 0 && !GetPendingEntry());
    244   DCHECK(selected_navigation >= 0 &&
    245          selected_navigation < static_cast<int>(entries->size()));
    246 
    247   needs_reload_ = true;
    248   for (size_t i = 0; i < entries->size(); ++i) {
    249     NavigationEntryImpl* entry =
    250         NavigationEntryImpl::FromNavigationEntry((*entries)[i]);
    251     entries_.push_back(linked_ptr<NavigationEntryImpl>(entry));
    252   }
    253   entries->clear();
    254 
    255   // And finish the restore.
    256   FinishRestore(selected_navigation, type);
    257 }
    258 
    259 void NavigationControllerImpl::Reload(bool check_for_repost) {
    260   ReloadInternal(check_for_repost, RELOAD);
    261 }
    262 void NavigationControllerImpl::ReloadIgnoringCache(bool check_for_repost) {
    263   ReloadInternal(check_for_repost, RELOAD_IGNORING_CACHE);
    264 }
    265 void NavigationControllerImpl::ReloadOriginalRequestURL(bool check_for_repost) {
    266   ReloadInternal(check_for_repost, RELOAD_ORIGINAL_REQUEST_URL);
    267 }
    268 
    269 void NavigationControllerImpl::ReloadInternal(bool check_for_repost,
    270                                               ReloadType reload_type) {
    271   if (transient_entry_index_ != -1) {
    272     // If an interstitial is showing, treat a reload as a navigation to the
    273     // transient entry's URL.
    274     NavigationEntryImpl* transient_entry =
    275         NavigationEntryImpl::FromNavigationEntry(GetTransientEntry());
    276     if (!transient_entry)
    277       return;
    278     LoadURL(transient_entry->GetURL(),
    279             Referrer(),
    280             PAGE_TRANSITION_RELOAD,
    281             transient_entry->extra_headers());
    282     return;
    283   }
    284 
    285   NavigationEntryImpl* entry = NULL;
    286   int current_index = -1;
    287 
    288   // If we are reloading the initial navigation, just use the current
    289   // pending entry.  Otherwise look up the current entry.
    290   if (IsInitialNavigation() && pending_entry_) {
    291     entry = pending_entry_;
    292     // The pending entry might be in entries_ (e.g., after a Clone), so we
    293     // should also update the current_index.
    294     current_index = pending_entry_index_;
    295   } else {
    296     DiscardNonCommittedEntriesInternal();
    297     current_index = GetCurrentEntryIndex();
    298     if (current_index != -1) {
    299       entry = NavigationEntryImpl::FromNavigationEntry(
    300           GetEntryAtIndex(current_index));
    301     }
    302   }
    303 
    304   // If we are no where, then we can't reload.  TODO(darin): We should add a
    305   // CanReload method.
    306   if (!entry)
    307     return;
    308 
    309   if (reload_type == NavigationControllerImpl::RELOAD_ORIGINAL_REQUEST_URL &&
    310       entry->GetOriginalRequestURL().is_valid() && !entry->GetHasPostData()) {
    311     // We may have been redirected when navigating to the current URL.
    312     // Use the URL the user originally intended to visit, if it's valid and if a
    313     // POST wasn't involved; the latter case avoids issues with sending data to
    314     // the wrong page.
    315     entry->SetURL(entry->GetOriginalRequestURL());
    316     entry->SetReferrer(Referrer());
    317   }
    318 
    319   if (g_check_for_repost && check_for_repost &&
    320       entry->GetHasPostData()) {
    321     // The user is asking to reload a page with POST data. Prompt to make sure
    322     // they really want to do this. If they do, the dialog will call us back
    323     // with check_for_repost = false.
    324     delegate_->NotifyBeforeFormRepostWarningShow();
    325 
    326     pending_reload_ = reload_type;
    327     delegate_->ActivateAndShowRepostFormWarningDialog();
    328   } else {
    329     if (!IsInitialNavigation())
    330       DiscardNonCommittedEntriesInternal();
    331 
    332     // If we are reloading an entry that no longer belongs to the current
    333     // site instance (for example, refreshing a page for just installed app),
    334     // the reload must happen in a new process.
    335     // The new entry must have a new page_id and site instance, so it behaves
    336     // as new navigation (which happens to clear forward history).
    337     // Tabs that are discarded due to low memory conditions may not have a site
    338     // instance, and should not be treated as a cross-site reload.
    339     SiteInstanceImpl* site_instance = entry->site_instance();
    340     // Permit reloading guests without further checks.
    341     bool is_isolated_guest = site_instance && site_instance->HasProcess() &&
    342         site_instance->GetProcess()->IsIsolatedGuest();
    343     if (!is_isolated_guest && site_instance &&
    344         site_instance->HasWrongProcessForURL(entry->GetURL())) {
    345       // Create a navigation entry that resembles the current one, but do not
    346       // copy page id, site instance, content state, or timestamp.
    347       NavigationEntryImpl* nav_entry = NavigationEntryImpl::FromNavigationEntry(
    348           CreateNavigationEntry(
    349               entry->GetURL(), entry->GetReferrer(), entry->GetTransitionType(),
    350               false, entry->extra_headers(), browser_context_));
    351 
    352       // Mark the reload type as NO_RELOAD, so navigation will not be considered
    353       // a reload in the renderer.
    354       reload_type = NavigationController::NO_RELOAD;
    355 
    356       nav_entry->set_should_replace_entry(true);
    357       pending_entry_ = nav_entry;
    358     } else {
    359       pending_entry_ = entry;
    360       pending_entry_index_ = current_index;
    361 
    362       // The title of the page being reloaded might have been removed in the
    363       // meanwhile, so we need to revert to the default title upon reload and
    364       // invalidate the previously cached title (SetTitle will do both).
    365       // See Chromium issue 96041.
    366       pending_entry_->SetTitle(base::string16());
    367 
    368       pending_entry_->SetTransitionType(PAGE_TRANSITION_RELOAD);
    369     }
    370 
    371     NavigateToPendingEntry(reload_type);
    372   }
    373 }
    374 
    375 void NavigationControllerImpl::CancelPendingReload() {
    376   DCHECK(pending_reload_ != NO_RELOAD);
    377   pending_reload_ = NO_RELOAD;
    378 }
    379 
    380 void NavigationControllerImpl::ContinuePendingReload() {
    381   if (pending_reload_ == NO_RELOAD) {
    382     NOTREACHED();
    383   } else {
    384     ReloadInternal(false, pending_reload_);
    385     pending_reload_ = NO_RELOAD;
    386   }
    387 }
    388 
    389 bool NavigationControllerImpl::IsInitialNavigation() const {
    390   return is_initial_navigation_;
    391 }
    392 
    393 NavigationEntryImpl* NavigationControllerImpl::GetEntryWithPageID(
    394   SiteInstance* instance, int32 page_id) const {
    395   int index = GetEntryIndexWithPageID(instance, page_id);
    396   return (index != -1) ? entries_[index].get() : NULL;
    397 }
    398 
    399 void NavigationControllerImpl::LoadEntry(NavigationEntryImpl* entry) {
    400   // When navigating to a new page, we don't know for sure if we will actually
    401   // end up leaving the current page.  The new page load could for example
    402   // result in a download or a 'no content' response (e.g., a mailto: URL).
    403   SetPendingEntry(entry);
    404   NavigateToPendingEntry(NO_RELOAD);
    405 }
    406 
    407 void NavigationControllerImpl::SetPendingEntry(NavigationEntryImpl* entry) {
    408   DiscardNonCommittedEntriesInternal();
    409   pending_entry_ = entry;
    410   NotificationService::current()->Notify(
    411       NOTIFICATION_NAV_ENTRY_PENDING,
    412       Source<NavigationController>(this),
    413       Details<NavigationEntry>(entry));
    414 }
    415 
    416 NavigationEntry* NavigationControllerImpl::GetActiveEntry() const {
    417   if (transient_entry_index_ != -1)
    418     return entries_[transient_entry_index_].get();
    419   if (pending_entry_)
    420     return pending_entry_;
    421   return GetLastCommittedEntry();
    422 }
    423 
    424 NavigationEntry* NavigationControllerImpl::GetVisibleEntry() const {
    425   if (transient_entry_index_ != -1)
    426     return entries_[transient_entry_index_].get();
    427   // The pending entry is safe to return for new (non-history), browser-
    428   // initiated navigations.  Most renderer-initiated navigations should not
    429   // show the pending entry, to prevent URL spoof attacks.
    430   //
    431   // We make an exception for renderer-initiated navigations in new tabs, as
    432   // long as no other page has tried to access the initial empty document in
    433   // the new tab.  If another page modifies this blank page, a URL spoof is
    434   // possible, so we must stop showing the pending entry.
    435   bool safe_to_show_pending =
    436       pending_entry_ &&
    437       // Require a new navigation.
    438       pending_entry_->GetPageID() == -1 &&
    439       // Require either browser-initiated or an unmodified new tab.
    440       (!pending_entry_->is_renderer_initiated() || IsUnmodifiedBlankTab());
    441 
    442   // Also allow showing the pending entry for history navigations in a new tab,
    443   // such as Ctrl+Back.  In this case, no existing page is visible and no one
    444   // can script the new tab before it commits.
    445   if (!safe_to_show_pending &&
    446       pending_entry_ &&
    447       pending_entry_->GetPageID() != -1 &&
    448       IsInitialNavigation() &&
    449       !pending_entry_->is_renderer_initiated())
    450     safe_to_show_pending = true;
    451 
    452   if (safe_to_show_pending)
    453     return pending_entry_;
    454   return GetLastCommittedEntry();
    455 }
    456 
    457 int NavigationControllerImpl::GetCurrentEntryIndex() const {
    458   if (transient_entry_index_ != -1)
    459     return transient_entry_index_;
    460   if (pending_entry_index_ != -1)
    461     return pending_entry_index_;
    462   return last_committed_entry_index_;
    463 }
    464 
    465 NavigationEntry* NavigationControllerImpl::GetLastCommittedEntry() const {
    466   if (last_committed_entry_index_ == -1)
    467     return NULL;
    468   return entries_[last_committed_entry_index_].get();
    469 }
    470 
    471 bool NavigationControllerImpl::CanViewSource() const {
    472   const std::string& mime_type = delegate_->GetContentsMimeType();
    473   bool is_viewable_mime_type = net::IsSupportedNonImageMimeType(mime_type) &&
    474       !net::IsSupportedMediaMimeType(mime_type);
    475   NavigationEntry* visible_entry = GetVisibleEntry();
    476   return visible_entry && !visible_entry->IsViewSourceMode() &&
    477       is_viewable_mime_type && !delegate_->GetInterstitialPage();
    478 }
    479 
    480 int NavigationControllerImpl::GetLastCommittedEntryIndex() const {
    481   return last_committed_entry_index_;
    482 }
    483 
    484 int NavigationControllerImpl::GetEntryCount() const {
    485   DCHECK(entries_.size() <= max_entry_count());
    486   return static_cast<int>(entries_.size());
    487 }
    488 
    489 NavigationEntry* NavigationControllerImpl::GetEntryAtIndex(
    490     int index) const {
    491   return entries_.at(index).get();
    492 }
    493 
    494 NavigationEntry* NavigationControllerImpl::GetEntryAtOffset(
    495     int offset) const {
    496   int index = GetIndexForOffset(offset);
    497   if (index < 0 || index >= GetEntryCount())
    498     return NULL;
    499 
    500   return entries_[index].get();
    501 }
    502 
    503 int NavigationControllerImpl::GetIndexForOffset(int offset) const {
    504   return GetCurrentEntryIndex() + offset;
    505 }
    506 
    507 void NavigationControllerImpl::TakeScreenshot() {
    508   screenshot_manager_->TakeScreenshot();
    509 }
    510 
    511 void NavigationControllerImpl::SetScreenshotManager(
    512     NavigationEntryScreenshotManager* manager) {
    513   screenshot_manager_.reset(manager ? manager :
    514                             new NavigationEntryScreenshotManager(this));
    515 }
    516 
    517 bool NavigationControllerImpl::CanGoBack() const {
    518   return entries_.size() > 1 && GetCurrentEntryIndex() > 0;
    519 }
    520 
    521 bool NavigationControllerImpl::CanGoForward() const {
    522   int index = GetCurrentEntryIndex();
    523   return index >= 0 && index < (static_cast<int>(entries_.size()) - 1);
    524 }
    525 
    526 bool NavigationControllerImpl::CanGoToOffset(int offset) const {
    527   int index = GetIndexForOffset(offset);
    528   return index >= 0 && index < GetEntryCount();
    529 }
    530 
    531 void NavigationControllerImpl::GoBack() {
    532   if (!CanGoBack()) {
    533     NOTREACHED();
    534     return;
    535   }
    536 
    537   // Base the navigation on where we are now...
    538   int current_index = GetCurrentEntryIndex();
    539 
    540   DiscardNonCommittedEntries();
    541 
    542   pending_entry_index_ = current_index - 1;
    543   entries_[pending_entry_index_]->SetTransitionType(
    544       PageTransitionFromInt(
    545           entries_[pending_entry_index_]->GetTransitionType() |
    546           PAGE_TRANSITION_FORWARD_BACK));
    547   NavigateToPendingEntry(NO_RELOAD);
    548 }
    549 
    550 void NavigationControllerImpl::GoForward() {
    551   if (!CanGoForward()) {
    552     NOTREACHED();
    553     return;
    554   }
    555 
    556   bool transient = (transient_entry_index_ != -1);
    557 
    558   // Base the navigation on where we are now...
    559   int current_index = GetCurrentEntryIndex();
    560 
    561   DiscardNonCommittedEntries();
    562 
    563   pending_entry_index_ = current_index;
    564   // If there was a transient entry, we removed it making the current index
    565   // the next page.
    566   if (!transient)
    567     pending_entry_index_++;
    568 
    569   entries_[pending_entry_index_]->SetTransitionType(
    570       PageTransitionFromInt(
    571           entries_[pending_entry_index_]->GetTransitionType() |
    572           PAGE_TRANSITION_FORWARD_BACK));
    573   NavigateToPendingEntry(NO_RELOAD);
    574 }
    575 
    576 void NavigationControllerImpl::GoToIndex(int index) {
    577   if (index < 0 || index >= static_cast<int>(entries_.size())) {
    578     NOTREACHED();
    579     return;
    580   }
    581 
    582   if (transient_entry_index_ != -1) {
    583     if (index == transient_entry_index_) {
    584       // Nothing to do when navigating to the transient.
    585       return;
    586     }
    587     if (index > transient_entry_index_) {
    588       // Removing the transient is goint to shift all entries by 1.
    589       index--;
    590     }
    591   }
    592 
    593   DiscardNonCommittedEntries();
    594 
    595   pending_entry_index_ = index;
    596   entries_[pending_entry_index_]->SetTransitionType(
    597       PageTransitionFromInt(
    598           entries_[pending_entry_index_]->GetTransitionType() |
    599           PAGE_TRANSITION_FORWARD_BACK));
    600   NavigateToPendingEntry(NO_RELOAD);
    601 }
    602 
    603 void NavigationControllerImpl::GoToOffset(int offset) {
    604   if (!CanGoToOffset(offset))
    605     return;
    606 
    607   GoToIndex(GetIndexForOffset(offset));
    608 }
    609 
    610 bool NavigationControllerImpl::RemoveEntryAtIndex(int index) {
    611   if (index == last_committed_entry_index_ ||
    612       index == pending_entry_index_)
    613     return false;
    614 
    615   RemoveEntryAtIndexInternal(index);
    616   return true;
    617 }
    618 
    619 void NavigationControllerImpl::UpdateVirtualURLToURL(
    620     NavigationEntryImpl* entry, const GURL& new_url) {
    621   GURL new_virtual_url(new_url);
    622   if (BrowserURLHandlerImpl::GetInstance()->ReverseURLRewrite(
    623           &new_virtual_url, entry->GetVirtualURL(), browser_context_)) {
    624     entry->SetVirtualURL(new_virtual_url);
    625   }
    626 }
    627 
    628 void NavigationControllerImpl::LoadURL(
    629     const GURL& url,
    630     const Referrer& referrer,
    631     PageTransition transition,
    632     const std::string& extra_headers) {
    633   LoadURLParams params(url);
    634   params.referrer = referrer;
    635   params.transition_type = transition;
    636   params.extra_headers = extra_headers;
    637   LoadURLWithParams(params);
    638 }
    639 
    640 void NavigationControllerImpl::LoadURLWithParams(const LoadURLParams& params) {
    641   TRACE_EVENT0("browser", "NavigationControllerImpl::LoadURLWithParams");
    642   if (HandleDebugURL(params.url, params.transition_type))
    643     return;
    644 
    645   // Any renderer-side debug URLs or javascript: URLs should be ignored if the
    646   // renderer process is not live, unless it is the initial navigation of the
    647   // tab.
    648   if (IsRendererDebugURL(params.url)) {
    649     // TODO(creis): Find the RVH for the correct frame.
    650     if (!delegate_->GetRenderViewHost()->IsRenderViewLive() &&
    651         !IsInitialNavigation())
    652       return;
    653   }
    654 
    655   // Checks based on params.load_type.
    656   switch (params.load_type) {
    657     case LOAD_TYPE_DEFAULT:
    658       break;
    659     case LOAD_TYPE_BROWSER_INITIATED_HTTP_POST:
    660       if (!params.url.SchemeIs(url::kHttpScheme) &&
    661           !params.url.SchemeIs(url::kHttpsScheme)) {
    662         NOTREACHED() << "Http post load must use http(s) scheme.";
    663         return;
    664       }
    665       break;
    666     case LOAD_TYPE_DATA:
    667       if (!params.url.SchemeIs(url::kDataScheme)) {
    668         NOTREACHED() << "Data load must use data scheme.";
    669         return;
    670       }
    671       break;
    672     default:
    673       NOTREACHED();
    674       break;
    675   };
    676 
    677   // The user initiated a load, we don't need to reload anymore.
    678   needs_reload_ = false;
    679 
    680   bool override = false;
    681   switch (params.override_user_agent) {
    682     case UA_OVERRIDE_INHERIT:
    683       override = ShouldKeepOverride(GetLastCommittedEntry());
    684       break;
    685     case UA_OVERRIDE_TRUE:
    686       override = true;
    687       break;
    688     case UA_OVERRIDE_FALSE:
    689       override = false;
    690       break;
    691     default:
    692       NOTREACHED();
    693       break;
    694   }
    695 
    696   NavigationEntryImpl* entry = NavigationEntryImpl::FromNavigationEntry(
    697       CreateNavigationEntry(
    698           params.url,
    699           params.referrer,
    700           params.transition_type,
    701           params.is_renderer_initiated,
    702           params.extra_headers,
    703           browser_context_));
    704   if (params.frame_tree_node_id != -1)
    705     entry->set_frame_tree_node_id(params.frame_tree_node_id);
    706   if (params.redirect_chain.size() > 0)
    707     entry->SetRedirectChain(params.redirect_chain);
    708   if (params.should_replace_current_entry)
    709     entry->set_should_replace_entry(true);
    710   entry->set_should_clear_history_list(params.should_clear_history_list);
    711   entry->SetIsOverridingUserAgent(override);
    712   entry->set_transferred_global_request_id(
    713       params.transferred_global_request_id);
    714   entry->SetFrameToNavigate(params.frame_name);
    715 
    716   switch (params.load_type) {
    717     case LOAD_TYPE_DEFAULT:
    718       break;
    719     case LOAD_TYPE_BROWSER_INITIATED_HTTP_POST:
    720       entry->SetHasPostData(true);
    721       entry->SetBrowserInitiatedPostData(
    722           params.browser_initiated_post_data.get());
    723       break;
    724     case LOAD_TYPE_DATA:
    725       entry->SetBaseURLForDataURL(params.base_url_for_data_url);
    726       entry->SetVirtualURL(params.virtual_url_for_data_url);
    727       entry->SetCanLoadLocalResources(params.can_load_local_resources);
    728       break;
    729     default:
    730       NOTREACHED();
    731       break;
    732   };
    733 
    734   LoadEntry(entry);
    735 }
    736 
    737 bool NavigationControllerImpl::RendererDidNavigate(
    738     RenderFrameHost* rfh,
    739     const FrameHostMsg_DidCommitProvisionalLoad_Params& params,
    740     LoadCommittedDetails* details) {
    741   is_initial_navigation_ = false;
    742 
    743   // Save the previous state before we clobber it.
    744   if (GetLastCommittedEntry()) {
    745     details->previous_url = GetLastCommittedEntry()->GetURL();
    746     details->previous_entry_index = GetLastCommittedEntryIndex();
    747   } else {
    748     details->previous_url = GURL();
    749     details->previous_entry_index = -1;
    750   }
    751 
    752   // If we have a pending entry at this point, it should have a SiteInstance.
    753   // Restored entries start out with a null SiteInstance, but we should have
    754   // assigned one in NavigateToPendingEntry.
    755   DCHECK(pending_entry_index_ == -1 || pending_entry_->site_instance());
    756 
    757   // If we are doing a cross-site reload, we need to replace the existing
    758   // navigation entry, not add another entry to the history. This has the side
    759   // effect of removing forward browsing history, if such existed.
    760   // Or if we are doing a cross-site redirect navigation,
    761   // we will do a similar thing.
    762   details->did_replace_entry =
    763       pending_entry_ && pending_entry_->should_replace_entry();
    764 
    765   // Do navigation-type specific actions. These will make and commit an entry.
    766   details->type = ClassifyNavigation(rfh, params);
    767 
    768   // is_in_page must be computed before the entry gets committed.
    769   details->is_in_page = IsURLInPageNavigation(
    770       params.url, params.was_within_same_page, details->type);
    771 
    772   switch (details->type) {
    773     case NAVIGATION_TYPE_NEW_PAGE:
    774       RendererDidNavigateToNewPage(rfh, params, details->did_replace_entry);
    775       break;
    776     case NAVIGATION_TYPE_EXISTING_PAGE:
    777       RendererDidNavigateToExistingPage(rfh, params);
    778       break;
    779     case NAVIGATION_TYPE_SAME_PAGE:
    780       RendererDidNavigateToSamePage(rfh, params);
    781       break;
    782     case NAVIGATION_TYPE_IN_PAGE:
    783       RendererDidNavigateInPage(rfh, params, &details->did_replace_entry);
    784       break;
    785     case NAVIGATION_TYPE_NEW_SUBFRAME:
    786       RendererDidNavigateNewSubframe(rfh, params);
    787       break;
    788     case NAVIGATION_TYPE_AUTO_SUBFRAME:
    789       if (!RendererDidNavigateAutoSubframe(rfh, params))
    790         return false;
    791       break;
    792     case NAVIGATION_TYPE_NAV_IGNORE:
    793       // If a pending navigation was in progress, this canceled it.  We should
    794       // discard it and make sure it is removed from the URL bar.  After that,
    795       // there is nothing we can do with this navigation, so we just return to
    796       // the caller that nothing has happened.
    797       if (pending_entry_) {
    798         DiscardNonCommittedEntries();
    799         delegate_->NotifyNavigationStateChanged(INVALIDATE_TYPE_URL);
    800       }
    801       return false;
    802     default:
    803       NOTREACHED();
    804   }
    805 
    806   // At this point, we know that the navigation has just completed, so
    807   // record the time.
    808   //
    809   // TODO(akalin): Use "sane time" as described in
    810   // http://www.chromium.org/developers/design-documents/sane-time .
    811   base::Time timestamp =
    812       time_smoother_.GetSmoothedTime(get_timestamp_callback_.Run());
    813   DVLOG(1) << "Navigation finished at (smoothed) timestamp "
    814            << timestamp.ToInternalValue();
    815 
    816   // We should not have a pending entry anymore.  Clear it again in case any
    817   // error cases above forgot to do so.
    818   DiscardNonCommittedEntriesInternal();
    819 
    820   // All committed entries should have nonempty content state so WebKit doesn't
    821   // get confused when we go back to them (see the function for details).
    822   DCHECK(params.page_state.IsValid());
    823   NavigationEntryImpl* active_entry =
    824       NavigationEntryImpl::FromNavigationEntry(GetLastCommittedEntry());
    825   active_entry->SetTimestamp(timestamp);
    826   active_entry->SetHttpStatusCode(params.http_status_code);
    827   active_entry->SetPageState(params.page_state);
    828   active_entry->SetRedirectChain(params.redirects);
    829 
    830   // Use histogram to track memory impact of redirect chain because it's now
    831   // not cleared for committed entries.
    832   size_t redirect_chain_size = 0;
    833   for (size_t i = 0; i < params.redirects.size(); ++i) {
    834     redirect_chain_size += params.redirects[i].spec().length();
    835   }
    836   UMA_HISTOGRAM_COUNTS("Navigation.RedirectChainSize", redirect_chain_size);
    837 
    838   // Once it is committed, we no longer need to track several pieces of state on
    839   // the entry.
    840   active_entry->ResetForCommit();
    841 
    842   // The active entry's SiteInstance should match our SiteInstance.
    843   // TODO(creis): This check won't pass for subframes until we create entries
    844   // for subframe navigations.
    845   if (PageTransitionIsMainFrame(params.transition))
    846     CHECK(active_entry->site_instance() == rfh->GetSiteInstance());
    847 
    848   // Remember the bindings the renderer process has at this point, so that
    849   // we do not grant this entry additional bindings if we come back to it.
    850   active_entry->SetBindings(
    851       static_cast<RenderFrameHostImpl*>(rfh)->GetEnabledBindings());
    852 
    853   // Now prep the rest of the details for the notification and broadcast.
    854   details->entry = active_entry;
    855   details->is_main_frame =
    856       PageTransitionIsMainFrame(params.transition);
    857   details->serialized_security_info = params.security_info;
    858   details->http_status_code = params.http_status_code;
    859   NotifyNavigationEntryCommitted(details);
    860 
    861   return true;
    862 }
    863 
    864 NavigationType NavigationControllerImpl::ClassifyNavigation(
    865     RenderFrameHost* rfh,
    866     const FrameHostMsg_DidCommitProvisionalLoad_Params& params) const {
    867   if (params.page_id == -1) {
    868     // The renderer generates the page IDs, and so if it gives us the invalid
    869     // page ID (-1) we know it didn't actually navigate. This happens in a few
    870     // cases:
    871     //
    872     // - If a page makes a popup navigated to about blank, and then writes
    873     //   stuff like a subframe navigated to a real page. We'll get the commit
    874     //   for the subframe, but there won't be any commit for the outer page.
    875     //
    876     // - We were also getting these for failed loads (for example, bug 21849).
    877     //   The guess is that we get a "load commit" for the alternate error page,
    878     //   but that doesn't affect the page ID, so we get the "old" one, which
    879     //   could be invalid. This can also happen for a cross-site transition
    880     //   that causes us to swap processes. Then the error page load will be in
    881     //   a new process with no page IDs ever assigned (and hence a -1 value),
    882     //   yet the navigation controller still might have previous pages in its
    883     //   list.
    884     //
    885     // In these cases, there's nothing we can do with them, so ignore.
    886     return NAVIGATION_TYPE_NAV_IGNORE;
    887   }
    888 
    889   if (params.page_id > delegate_->GetMaxPageIDForSiteInstance(
    890           rfh->GetSiteInstance())) {
    891     // Greater page IDs than we've ever seen before are new pages. We may or may
    892     // not have a pending entry for the page, and this may or may not be the
    893     // main frame.
    894     if (PageTransitionIsMainFrame(params.transition))
    895       return NAVIGATION_TYPE_NEW_PAGE;
    896 
    897     // When this is a new subframe navigation, we should have a committed page
    898     // for which it's a suframe in. This may not be the case when an iframe is
    899     // navigated on a popup navigated to about:blank (the iframe would be
    900     // written into the popup by script on the main page). For these cases,
    901     // there isn't any navigation stuff we can do, so just ignore it.
    902     if (!GetLastCommittedEntry())
    903       return NAVIGATION_TYPE_NAV_IGNORE;
    904 
    905     // Valid subframe navigation.
    906     return NAVIGATION_TYPE_NEW_SUBFRAME;
    907   }
    908 
    909   // We only clear the session history when navigating to a new page.
    910   DCHECK(!params.history_list_was_cleared);
    911 
    912   // Now we know that the notification is for an existing page. Find that entry.
    913   int existing_entry_index = GetEntryIndexWithPageID(
    914       rfh->GetSiteInstance(),
    915       params.page_id);
    916   if (existing_entry_index == -1) {
    917     // The page was not found. It could have been pruned because of the limit on
    918     // back/forward entries (not likely since we'll usually tell it to navigate
    919     // to such entries). It could also mean that the renderer is smoking crack.
    920     NOTREACHED();
    921 
    922     // Because the unknown entry has committed, we risk showing the wrong URL in
    923     // release builds. Instead, we'll kill the renderer process to be safe.
    924     LOG(ERROR) << "terminating renderer for bad navigation: " << params.url;
    925     RecordAction(base::UserMetricsAction("BadMessageTerminate_NC"));
    926 
    927     // Temporary code so we can get more information.  Format:
    928     //  http://url/foo.html#page1#max3#frame1#ids:2_Nx,1_1x,3_2
    929     std::string temp = params.url.spec();
    930     temp.append("#page");
    931     temp.append(base::IntToString(params.page_id));
    932     temp.append("#max");
    933     temp.append(base::IntToString(delegate_->GetMaxPageID()));
    934     temp.append("#frame");
    935     temp.append(base::IntToString(rfh->GetRoutingID()));
    936     temp.append("#ids");
    937     for (int i = 0; i < static_cast<int>(entries_.size()); ++i) {
    938       // Append entry metadata (e.g., 3_7x):
    939       //  3: page_id
    940       //  7: SiteInstance ID, or N for null
    941       //  x: appended if not from the current SiteInstance
    942       temp.append(base::IntToString(entries_[i]->GetPageID()));
    943       temp.append("_");
    944       if (entries_[i]->site_instance())
    945         temp.append(base::IntToString(entries_[i]->site_instance()->GetId()));
    946       else
    947         temp.append("N");
    948       if (entries_[i]->site_instance() != rfh->GetSiteInstance())
    949         temp.append("x");
    950       temp.append(",");
    951     }
    952     GURL url(temp);
    953     static_cast<RenderFrameHostImpl*>(rfh)->render_view_host()->Send(
    954         new ViewMsg_TempCrashWithData(url));
    955     return NAVIGATION_TYPE_NAV_IGNORE;
    956   }
    957   NavigationEntryImpl* existing_entry = entries_[existing_entry_index].get();
    958 
    959   if (!PageTransitionIsMainFrame(params.transition)) {
    960     // All manual subframes would get new IDs and were handled above, so we
    961     // know this is auto. Since the current page was found in the navigation
    962     // entry list, we're guaranteed to have a last committed entry.
    963     DCHECK(GetLastCommittedEntry());
    964     return NAVIGATION_TYPE_AUTO_SUBFRAME;
    965   }
    966 
    967   // Anything below here we know is a main frame navigation.
    968   if (pending_entry_ &&
    969       !pending_entry_->is_renderer_initiated() &&
    970       existing_entry != pending_entry_ &&
    971       pending_entry_->GetPageID() == -1 &&
    972       existing_entry == GetLastCommittedEntry()) {
    973     // In this case, we have a pending entry for a URL but WebCore didn't do a
    974     // new navigation. This happens when you press enter in the URL bar to
    975     // reload. We will create a pending entry, but WebKit will convert it to
    976     // a reload since it's the same page and not create a new entry for it
    977     // (the user doesn't want to have a new back/forward entry when they do
    978     // this). If this matches the last committed entry, we want to just ignore
    979     // the pending entry and go back to where we were (the "existing entry").
    980     return NAVIGATION_TYPE_SAME_PAGE;
    981   }
    982 
    983   // Any toplevel navigations with the same base (minus the reference fragment)
    984   // are in-page navigations. We weeded out subframe navigations above. Most of
    985   // the time this doesn't matter since WebKit doesn't tell us about subframe
    986   // navigations that don't actually navigate, but it can happen when there is
    987   // an encoding override (it always sends a navigation request).
    988   if (AreURLsInPageNavigation(existing_entry->GetURL(), params.url,
    989                               params.was_within_same_page,
    990                               NAVIGATION_TYPE_UNKNOWN)) {
    991     return NAVIGATION_TYPE_IN_PAGE;
    992   }
    993 
    994   // Since we weeded out "new" navigations above, we know this is an existing
    995   // (back/forward) navigation.
    996   return NAVIGATION_TYPE_EXISTING_PAGE;
    997 }
    998 
    999 void NavigationControllerImpl::RendererDidNavigateToNewPage(
   1000     RenderFrameHost* rfh,
   1001     const FrameHostMsg_DidCommitProvisionalLoad_Params& params,
   1002     bool replace_entry) {
   1003   NavigationEntryImpl* new_entry;
   1004   bool update_virtual_url;
   1005   // Only make a copy of the pending entry if it is appropriate for the new page
   1006   // that was just loaded.  We verify this at a coarse grain by checking that
   1007   // the SiteInstance hasn't been assigned to something else.
   1008   if (pending_entry_ &&
   1009       (!pending_entry_->site_instance() ||
   1010        pending_entry_->site_instance() == rfh->GetSiteInstance())) {
   1011     new_entry = new NavigationEntryImpl(*pending_entry_);
   1012 
   1013     // Don't use the page type from the pending entry. Some interstitial page
   1014     // may have set the type to interstitial. Once we commit, however, the page
   1015     // type must always be normal.
   1016     new_entry->set_page_type(PAGE_TYPE_NORMAL);
   1017     update_virtual_url = new_entry->update_virtual_url_with_url();
   1018   } else {
   1019     new_entry = new NavigationEntryImpl;
   1020 
   1021     // Find out whether the new entry needs to update its virtual URL on URL
   1022     // change and set up the entry accordingly. This is needed to correctly
   1023     // update the virtual URL when replaceState is called after a pushState.
   1024     GURL url = params.url;
   1025     bool needs_update = false;
   1026     // We call RewriteURLIfNecessary twice: once when page navigation
   1027     // begins in CreateNavigationEntry, and once here when it commits.
   1028     // With the kEnableGpuBenchmarking flag, the rewriting includes
   1029     // handling debug URLs which cause an action to occur, and thus we
   1030     // should not rewrite them a second time.
   1031     bool skip_rewrite =
   1032         IsDebugURL(url) && base::CommandLine::ForCurrentProcess()->HasSwitch(
   1033             cc::switches::kEnableGpuBenchmarking);
   1034     if (!skip_rewrite) {
   1035       BrowserURLHandlerImpl::GetInstance()->RewriteURLIfNecessary(
   1036           &url, browser_context_, &needs_update);
   1037     }
   1038     new_entry->set_update_virtual_url_with_url(needs_update);
   1039 
   1040     // When navigating to a new page, give the browser URL handler a chance to
   1041     // update the virtual URL based on the new URL. For example, this is needed
   1042     // to show chrome://bookmarks/#1 when the bookmarks webui extension changes
   1043     // the URL.
   1044     update_virtual_url = needs_update;
   1045   }
   1046 
   1047   new_entry->SetURL(params.url);
   1048   if (update_virtual_url)
   1049     UpdateVirtualURLToURL(new_entry, params.url);
   1050   new_entry->SetReferrer(params.referrer);
   1051   new_entry->SetPageID(params.page_id);
   1052   new_entry->SetTransitionType(params.transition);
   1053   new_entry->set_site_instance(
   1054       static_cast<SiteInstanceImpl*>(rfh->GetSiteInstance()));
   1055   new_entry->SetHasPostData(params.is_post);
   1056   new_entry->SetPostID(params.post_id);
   1057   new_entry->SetOriginalRequestURL(params.original_request_url);
   1058   new_entry->SetIsOverridingUserAgent(params.is_overriding_user_agent);
   1059 
   1060   // history.pushState() is classified as a navigation to a new page, but
   1061   // sets was_within_same_page to true. In this case, we already have the
   1062   // title available, so set it immediately.
   1063   if (params.was_within_same_page && GetLastCommittedEntry())
   1064     new_entry->SetTitle(GetLastCommittedEntry()->GetTitle());
   1065 
   1066   DCHECK(!params.history_list_was_cleared || !replace_entry);
   1067   // The browser requested to clear the session history when it initiated the
   1068   // navigation. Now we know that the renderer has updated its state accordingly
   1069   // and it is safe to also clear the browser side history.
   1070   if (params.history_list_was_cleared) {
   1071     DiscardNonCommittedEntriesInternal();
   1072     entries_.clear();
   1073     last_committed_entry_index_ = -1;
   1074   }
   1075 
   1076   InsertOrReplaceEntry(new_entry, replace_entry);
   1077 }
   1078 
   1079 void NavigationControllerImpl::RendererDidNavigateToExistingPage(
   1080     RenderFrameHost* rfh,
   1081     const FrameHostMsg_DidCommitProvisionalLoad_Params& params) {
   1082   // We should only get here for main frame navigations.
   1083   DCHECK(PageTransitionIsMainFrame(params.transition));
   1084 
   1085   // This is a back/forward navigation. The existing page for the ID is
   1086   // guaranteed to exist by ClassifyNavigation, and we just need to update it
   1087   // with new information from the renderer.
   1088   int entry_index = GetEntryIndexWithPageID(rfh->GetSiteInstance(),
   1089                                             params.page_id);
   1090   DCHECK(entry_index >= 0 &&
   1091          entry_index < static_cast<int>(entries_.size()));
   1092   NavigationEntryImpl* entry = entries_[entry_index].get();
   1093 
   1094   // The URL may have changed due to redirects.
   1095   entry->SetURL(params.url);
   1096   entry->SetReferrer(params.referrer);
   1097   if (entry->update_virtual_url_with_url())
   1098     UpdateVirtualURLToURL(entry, params.url);
   1099 
   1100   // The redirected to page should not inherit the favicon from the previous
   1101   // page.
   1102   if (PageTransitionIsRedirect(params.transition))
   1103     entry->GetFavicon() = FaviconStatus();
   1104 
   1105   // The site instance will normally be the same except during session restore,
   1106   // when no site instance will be assigned.
   1107   DCHECK(entry->site_instance() == NULL ||
   1108          entry->site_instance() == rfh->GetSiteInstance());
   1109   entry->set_site_instance(
   1110       static_cast<SiteInstanceImpl*>(rfh->GetSiteInstance()));
   1111 
   1112   entry->SetHasPostData(params.is_post);
   1113   entry->SetPostID(params.post_id);
   1114 
   1115   // The entry we found in the list might be pending if the user hit
   1116   // back/forward/reload. This load should commit it (since it's already in the
   1117   // list, we can just discard the pending pointer).  We should also discard the
   1118   // pending entry if it corresponds to a different navigation, since that one
   1119   // is now likely canceled.  If it is not canceled, we will treat it as a new
   1120   // navigation when it arrives, which is also ok.
   1121   //
   1122   // Note that we need to use the "internal" version since we don't want to
   1123   // actually change any other state, just kill the pointer.
   1124   DiscardNonCommittedEntriesInternal();
   1125 
   1126   // If a transient entry was removed, the indices might have changed, so we
   1127   // have to query the entry index again.
   1128   last_committed_entry_index_ =
   1129       GetEntryIndexWithPageID(rfh->GetSiteInstance(), params.page_id);
   1130 }
   1131 
   1132 void NavigationControllerImpl::RendererDidNavigateToSamePage(
   1133     RenderFrameHost* rfh,
   1134     const FrameHostMsg_DidCommitProvisionalLoad_Params& params) {
   1135   // This mode implies we have a pending entry that's the same as an existing
   1136   // entry for this page ID. This entry is guaranteed to exist by
   1137   // ClassifyNavigation. All we need to do is update the existing entry.
   1138   NavigationEntryImpl* existing_entry = GetEntryWithPageID(
   1139       rfh->GetSiteInstance(), params.page_id);
   1140 
   1141   // We assign the entry's unique ID to be that of the new one. Since this is
   1142   // always the result of a user action, we want to dismiss infobars, etc. like
   1143   // a regular user-initiated navigation.
   1144   existing_entry->set_unique_id(pending_entry_->GetUniqueID());
   1145 
   1146   // The URL may have changed due to redirects.
   1147   if (existing_entry->update_virtual_url_with_url())
   1148     UpdateVirtualURLToURL(existing_entry, params.url);
   1149   existing_entry->SetURL(params.url);
   1150   existing_entry->SetReferrer(params.referrer);
   1151 
   1152   // The page may have been requested with a different HTTP method.
   1153   existing_entry->SetHasPostData(params.is_post);
   1154   existing_entry->SetPostID(params.post_id);
   1155 
   1156   DiscardNonCommittedEntries();
   1157 }
   1158 
   1159 void NavigationControllerImpl::RendererDidNavigateInPage(
   1160     RenderFrameHost* rfh,
   1161     const FrameHostMsg_DidCommitProvisionalLoad_Params& params,
   1162     bool* did_replace_entry) {
   1163   DCHECK(PageTransitionIsMainFrame(params.transition)) <<
   1164       "WebKit should only tell us about in-page navs for the main frame.";
   1165   // We're guaranteed to have an entry for this one.
   1166   NavigationEntryImpl* existing_entry = GetEntryWithPageID(
   1167       rfh->GetSiteInstance(), params.page_id);
   1168 
   1169   // Reference fragment navigation. We're guaranteed to have the last_committed
   1170   // entry and it will be the same page as the new navigation (minus the
   1171   // reference fragments, of course).  We'll update the URL of the existing
   1172   // entry without pruning the forward history.
   1173   existing_entry->SetURL(params.url);
   1174   if (existing_entry->update_virtual_url_with_url())
   1175     UpdateVirtualURLToURL(existing_entry, params.url);
   1176 
   1177   existing_entry->SetHasPostData(params.is_post);
   1178   existing_entry->SetPostID(params.post_id);
   1179 
   1180   // This replaces the existing entry since the page ID didn't change.
   1181   *did_replace_entry = true;
   1182 
   1183   DiscardNonCommittedEntriesInternal();
   1184 
   1185   // If a transient entry was removed, the indices might have changed, so we
   1186   // have to query the entry index again.
   1187   last_committed_entry_index_ =
   1188       GetEntryIndexWithPageID(rfh->GetSiteInstance(), params.page_id);
   1189 }
   1190 
   1191 void NavigationControllerImpl::RendererDidNavigateNewSubframe(
   1192     RenderFrameHost* rfh,
   1193     const FrameHostMsg_DidCommitProvisionalLoad_Params& params) {
   1194   if (PageTransitionCoreTypeIs(params.transition,
   1195                                PAGE_TRANSITION_AUTO_SUBFRAME)) {
   1196     // This is not user-initiated. Ignore.
   1197     DiscardNonCommittedEntriesInternal();
   1198     return;
   1199   }
   1200 
   1201   // Manual subframe navigations just get the current entry cloned so the user
   1202   // can go back or forward to it. The actual subframe information will be
   1203   // stored in the page state for each of those entries. This happens out of
   1204   // band with the actual navigations.
   1205   DCHECK(GetLastCommittedEntry()) << "ClassifyNavigation should guarantee "
   1206                                   << "that a last committed entry exists.";
   1207   NavigationEntryImpl* new_entry = new NavigationEntryImpl(
   1208       *NavigationEntryImpl::FromNavigationEntry(GetLastCommittedEntry()));
   1209   new_entry->SetPageID(params.page_id);
   1210   InsertOrReplaceEntry(new_entry, false);
   1211 }
   1212 
   1213 bool NavigationControllerImpl::RendererDidNavigateAutoSubframe(
   1214     RenderFrameHost* rfh,
   1215     const FrameHostMsg_DidCommitProvisionalLoad_Params& params) {
   1216   // We're guaranteed to have a previously committed entry, and we now need to
   1217   // handle navigation inside of a subframe in it without creating a new entry.
   1218   DCHECK(GetLastCommittedEntry());
   1219 
   1220   // Handle the case where we're navigating back/forward to a previous subframe
   1221   // navigation entry. This is case "2." in NAV_AUTO_SUBFRAME comment in the
   1222   // header file. In case "1." this will be a NOP.
   1223   int entry_index = GetEntryIndexWithPageID(
   1224       rfh->GetSiteInstance(),
   1225       params.page_id);
   1226   if (entry_index < 0 ||
   1227       entry_index >= static_cast<int>(entries_.size())) {
   1228     NOTREACHED();
   1229     return false;
   1230   }
   1231 
   1232   // Update the current navigation entry in case we're going back/forward.
   1233   if (entry_index != last_committed_entry_index_) {
   1234     last_committed_entry_index_ = entry_index;
   1235     DiscardNonCommittedEntriesInternal();
   1236     return true;
   1237   }
   1238 
   1239   // We do not need to discard the pending entry in this case, since we will
   1240   // not generate commit notifications for this auto-subframe navigation.
   1241   return false;
   1242 }
   1243 
   1244 int NavigationControllerImpl::GetIndexOfEntry(
   1245     const NavigationEntryImpl* entry) const {
   1246   const NavigationEntries::const_iterator i(std::find(
   1247       entries_.begin(),
   1248       entries_.end(),
   1249       entry));
   1250   return (i == entries_.end()) ? -1 : static_cast<int>(i - entries_.begin());
   1251 }
   1252 
   1253 bool NavigationControllerImpl::IsURLInPageNavigation(
   1254     const GURL& url,
   1255     bool renderer_says_in_page,
   1256     NavigationType navigation_type) const {
   1257   NavigationEntry* last_committed = GetLastCommittedEntry();
   1258   return last_committed && AreURLsInPageNavigation(
   1259       last_committed->GetURL(), url, renderer_says_in_page, navigation_type);
   1260 }
   1261 
   1262 void NavigationControllerImpl::CopyStateFrom(
   1263     const NavigationController& temp) {
   1264   const NavigationControllerImpl& source =
   1265       static_cast<const NavigationControllerImpl&>(temp);
   1266   // Verify that we look new.
   1267   DCHECK(GetEntryCount() == 0 && !GetPendingEntry());
   1268 
   1269   if (source.GetEntryCount() == 0)
   1270     return;  // Nothing new to do.
   1271 
   1272   needs_reload_ = true;
   1273   InsertEntriesFrom(source, source.GetEntryCount());
   1274 
   1275   for (SessionStorageNamespaceMap::const_iterator it =
   1276            source.session_storage_namespace_map_.begin();
   1277        it != source.session_storage_namespace_map_.end();
   1278        ++it) {
   1279     SessionStorageNamespaceImpl* source_namespace =
   1280         static_cast<SessionStorageNamespaceImpl*>(it->second.get());
   1281     session_storage_namespace_map_[it->first] = source_namespace->Clone();
   1282   }
   1283 
   1284   FinishRestore(source.last_committed_entry_index_, RESTORE_CURRENT_SESSION);
   1285 
   1286   // Copy the max page id map from the old tab to the new tab.  This ensures
   1287   // that new and existing navigations in the tab's current SiteInstances
   1288   // are identified properly.
   1289   delegate_->CopyMaxPageIDsFrom(source.delegate()->GetWebContents());
   1290 }
   1291 
   1292 void NavigationControllerImpl::CopyStateFromAndPrune(
   1293     NavigationController* temp,
   1294     bool replace_entry) {
   1295   // It is up to callers to check the invariants before calling this.
   1296   CHECK(CanPruneAllButLastCommitted());
   1297 
   1298   NavigationControllerImpl* source =
   1299       static_cast<NavigationControllerImpl*>(temp);
   1300   // The SiteInstance and page_id of the last committed entry needs to be
   1301   // remembered at this point, in case there is only one committed entry
   1302   // and it is pruned.  We use a scoped_refptr to ensure the SiteInstance
   1303   // can't be freed during this time period.
   1304   NavigationEntryImpl* last_committed =
   1305       NavigationEntryImpl::FromNavigationEntry(GetLastCommittedEntry());
   1306   scoped_refptr<SiteInstance> site_instance(
   1307       last_committed->site_instance());
   1308   int32 minimum_page_id = last_committed->GetPageID();
   1309   int32 max_page_id =
   1310       delegate_->GetMaxPageIDForSiteInstance(site_instance.get());
   1311 
   1312   // Remove all the entries leaving the active entry.
   1313   PruneAllButLastCommittedInternal();
   1314 
   1315   // We now have one entry, possibly with a new pending entry.  Ensure that
   1316   // adding the entries from source won't put us over the limit.
   1317   DCHECK_EQ(1, GetEntryCount());
   1318   if (!replace_entry)
   1319     source->PruneOldestEntryIfFull();
   1320 
   1321   // Insert the entries from source. Don't use source->GetCurrentEntryIndex as
   1322   // we don't want to copy over the transient entry.  Ignore any pending entry,
   1323   // since it has not committed in source.
   1324   int max_source_index = source->last_committed_entry_index_;
   1325   if (max_source_index == -1)
   1326     max_source_index = source->GetEntryCount();
   1327   else
   1328     max_source_index++;
   1329 
   1330   // Ignore the source's current entry if merging with replacement.
   1331   // TODO(davidben): This should preserve entries forward of the current
   1332   // too. http://crbug.com/317872
   1333   if (replace_entry && max_source_index > 0)
   1334     max_source_index--;
   1335 
   1336   InsertEntriesFrom(*source, max_source_index);
   1337 
   1338   // Adjust indices such that the last entry and pending are at the end now.
   1339   last_committed_entry_index_ = GetEntryCount() - 1;
   1340 
   1341   delegate_->SetHistoryLengthAndPrune(site_instance.get(),
   1342                                       max_source_index,
   1343                                       minimum_page_id);
   1344 
   1345   // Copy the max page id map from the old tab to the new tab.  This ensures
   1346   // that new and existing navigations in the tab's current SiteInstances
   1347   // are identified properly.
   1348   delegate_->CopyMaxPageIDsFrom(source->delegate()->GetWebContents());
   1349   max_restored_page_id_ = source->max_restored_page_id_;
   1350 
   1351   // If there is a last committed entry, be sure to include it in the new
   1352   // max page ID map.
   1353   if (max_page_id > -1) {
   1354     delegate_->UpdateMaxPageIDForSiteInstance(site_instance.get(),
   1355                                               max_page_id);
   1356   }
   1357 }
   1358 
   1359 bool NavigationControllerImpl::CanPruneAllButLastCommitted() {
   1360   // If there is no last committed entry, we cannot prune.  Even if there is a
   1361   // pending entry, it may not commit, leaving this WebContents blank, despite
   1362   // possibly giving it new entries via CopyStateFromAndPrune.
   1363   if (last_committed_entry_index_ == -1)
   1364     return false;
   1365 
   1366   // We cannot prune if there is a pending entry at an existing entry index.
   1367   // It may not commit, so we have to keep the last committed entry, and thus
   1368   // there is no sensible place to keep the pending entry.  It is ok to have
   1369   // a new pending entry, which can optionally commit as a new navigation.
   1370   if (pending_entry_index_ != -1)
   1371     return false;
   1372 
   1373   // We should not prune if we are currently showing a transient entry.
   1374   if (transient_entry_index_ != -1)
   1375     return false;
   1376 
   1377   return true;
   1378 }
   1379 
   1380 void NavigationControllerImpl::PruneAllButLastCommitted() {
   1381   PruneAllButLastCommittedInternal();
   1382 
   1383   // We should still have a last committed entry.
   1384   DCHECK_NE(-1, last_committed_entry_index_);
   1385 
   1386   // We pass 0 instead of GetEntryCount() for the history_length parameter of
   1387   // SetHistoryLengthAndPrune, because it will create history_length additional
   1388   // history entries.
   1389   // TODO(jochen): This API is confusing and we should clean it up.
   1390   // http://crbug.com/178491
   1391   NavigationEntryImpl* entry =
   1392       NavigationEntryImpl::FromNavigationEntry(GetVisibleEntry());
   1393   delegate_->SetHistoryLengthAndPrune(
   1394       entry->site_instance(), 0, entry->GetPageID());
   1395 }
   1396 
   1397 void NavigationControllerImpl::PruneAllButLastCommittedInternal() {
   1398   // It is up to callers to check the invariants before calling this.
   1399   CHECK(CanPruneAllButLastCommitted());
   1400 
   1401   // Erase all entries but the last committed entry.  There may still be a
   1402   // new pending entry after this.
   1403   entries_.erase(entries_.begin(),
   1404                  entries_.begin() + last_committed_entry_index_);
   1405   entries_.erase(entries_.begin() + 1, entries_.end());
   1406   last_committed_entry_index_ = 0;
   1407 }
   1408 
   1409 void NavigationControllerImpl::ClearAllScreenshots() {
   1410   screenshot_manager_->ClearAllScreenshots();
   1411 }
   1412 
   1413 void NavigationControllerImpl::SetSessionStorageNamespace(
   1414     const std::string& partition_id,
   1415     SessionStorageNamespace* session_storage_namespace) {
   1416   if (!session_storage_namespace)
   1417     return;
   1418 
   1419   // We can't overwrite an existing SessionStorage without violating spec.
   1420   // Attempts to do so may give a tab access to another tab's session storage
   1421   // so die hard on an error.
   1422   bool successful_insert = session_storage_namespace_map_.insert(
   1423       make_pair(partition_id,
   1424                 static_cast<SessionStorageNamespaceImpl*>(
   1425                     session_storage_namespace)))
   1426           .second;
   1427   CHECK(successful_insert) << "Cannot replace existing SessionStorageNamespace";
   1428 }
   1429 
   1430 void NavigationControllerImpl::SetMaxRestoredPageID(int32 max_id) {
   1431   max_restored_page_id_ = max_id;
   1432 }
   1433 
   1434 int32 NavigationControllerImpl::GetMaxRestoredPageID() const {
   1435   return max_restored_page_id_;
   1436 }
   1437 
   1438 bool NavigationControllerImpl::IsUnmodifiedBlankTab() const {
   1439   return IsInitialNavigation() &&
   1440          !GetLastCommittedEntry() &&
   1441          !delegate_->HasAccessedInitialDocument();
   1442 }
   1443 
   1444 SessionStorageNamespace*
   1445 NavigationControllerImpl::GetSessionStorageNamespace(SiteInstance* instance) {
   1446   std::string partition_id;
   1447   if (instance) {
   1448     // TODO(ajwong): When GetDefaultSessionStorageNamespace() goes away, remove
   1449     // this if statement so |instance| must not be NULL.
   1450     partition_id =
   1451         GetContentClient()->browser()->GetStoragePartitionIdForSite(
   1452             browser_context_, instance->GetSiteURL());
   1453   }
   1454 
   1455   SessionStorageNamespaceMap::const_iterator it =
   1456       session_storage_namespace_map_.find(partition_id);
   1457   if (it != session_storage_namespace_map_.end())
   1458     return it->second.get();
   1459 
   1460   // Create one if no one has accessed session storage for this partition yet.
   1461   //
   1462   // TODO(ajwong): Should this use the |partition_id| directly rather than
   1463   // re-lookup via |instance|?  http://crbug.com/142685
   1464   StoragePartition* partition =
   1465               BrowserContext::GetStoragePartition(browser_context_, instance);
   1466   SessionStorageNamespaceImpl* session_storage_namespace =
   1467       new SessionStorageNamespaceImpl(
   1468           static_cast<DOMStorageContextWrapper*>(
   1469               partition->GetDOMStorageContext()));
   1470   session_storage_namespace_map_[partition_id] = session_storage_namespace;
   1471 
   1472   return session_storage_namespace;
   1473 }
   1474 
   1475 SessionStorageNamespace*
   1476 NavigationControllerImpl::GetDefaultSessionStorageNamespace() {
   1477   // TODO(ajwong): Remove if statement in GetSessionStorageNamespace().
   1478   return GetSessionStorageNamespace(NULL);
   1479 }
   1480 
   1481 const SessionStorageNamespaceMap&
   1482 NavigationControllerImpl::GetSessionStorageNamespaceMap() const {
   1483   return session_storage_namespace_map_;
   1484 }
   1485 
   1486 bool NavigationControllerImpl::NeedsReload() const {
   1487   return needs_reload_;
   1488 }
   1489 
   1490 void NavigationControllerImpl::SetNeedsReload() {
   1491   needs_reload_ = true;
   1492 }
   1493 
   1494 void NavigationControllerImpl::RemoveEntryAtIndexInternal(int index) {
   1495   DCHECK(index < GetEntryCount());
   1496   DCHECK(index != last_committed_entry_index_);
   1497 
   1498   DiscardNonCommittedEntries();
   1499 
   1500   entries_.erase(entries_.begin() + index);
   1501   if (last_committed_entry_index_ > index)
   1502     last_committed_entry_index_--;
   1503 }
   1504 
   1505 void NavigationControllerImpl::DiscardNonCommittedEntries() {
   1506   bool transient = transient_entry_index_ != -1;
   1507   DiscardNonCommittedEntriesInternal();
   1508 
   1509   // If there was a transient entry, invalidate everything so the new active
   1510   // entry state is shown.
   1511   if (transient) {
   1512     delegate_->NotifyNavigationStateChanged(kInvalidateAll);
   1513   }
   1514 }
   1515 
   1516 NavigationEntry* NavigationControllerImpl::GetPendingEntry() const {
   1517   return pending_entry_;
   1518 }
   1519 
   1520 int NavigationControllerImpl::GetPendingEntryIndex() const {
   1521   return pending_entry_index_;
   1522 }
   1523 
   1524 void NavigationControllerImpl::InsertOrReplaceEntry(NavigationEntryImpl* entry,
   1525                                                     bool replace) {
   1526   DCHECK(entry->GetTransitionType() != PAGE_TRANSITION_AUTO_SUBFRAME);
   1527 
   1528   // Copy the pending entry's unique ID to the committed entry.
   1529   // I don't know if pending_entry_index_ can be other than -1 here.
   1530   const NavigationEntryImpl* const pending_entry =
   1531       (pending_entry_index_ == -1) ?
   1532           pending_entry_ : entries_[pending_entry_index_].get();
   1533   if (pending_entry)
   1534     entry->set_unique_id(pending_entry->GetUniqueID());
   1535 
   1536   DiscardNonCommittedEntriesInternal();
   1537 
   1538   int current_size = static_cast<int>(entries_.size());
   1539 
   1540   if (current_size > 0) {
   1541     // Prune any entries which are in front of the current entry.
   1542     // Also prune the current entry if we are to replace the current entry.
   1543     // last_committed_entry_index_ must be updated here since calls to
   1544     // NotifyPrunedEntries() below may re-enter and we must make sure
   1545     // last_committed_entry_index_ is not left in an invalid state.
   1546     if (replace)
   1547       --last_committed_entry_index_;
   1548 
   1549     int num_pruned = 0;
   1550     while (last_committed_entry_index_ < (current_size - 1)) {
   1551       num_pruned++;
   1552       entries_.pop_back();
   1553       current_size--;
   1554     }
   1555     if (num_pruned > 0)  // Only notify if we did prune something.
   1556       NotifyPrunedEntries(this, false, num_pruned);
   1557   }
   1558 
   1559   PruneOldestEntryIfFull();
   1560 
   1561   entries_.push_back(linked_ptr<NavigationEntryImpl>(entry));
   1562   last_committed_entry_index_ = static_cast<int>(entries_.size()) - 1;
   1563 
   1564   // This is a new page ID, so we need everybody to know about it.
   1565   delegate_->UpdateMaxPageID(entry->GetPageID());
   1566 }
   1567 
   1568 void NavigationControllerImpl::PruneOldestEntryIfFull() {
   1569   if (entries_.size() >= max_entry_count()) {
   1570     DCHECK_EQ(max_entry_count(), entries_.size());
   1571     DCHECK_GT(last_committed_entry_index_, 0);
   1572     RemoveEntryAtIndex(0);
   1573     NotifyPrunedEntries(this, true, 1);
   1574   }
   1575 }
   1576 
   1577 void NavigationControllerImpl::NavigateToPendingEntry(ReloadType reload_type) {
   1578   needs_reload_ = false;
   1579 
   1580   // If we were navigating to a slow-to-commit page, and the user performs
   1581   // a session history navigation to the last committed page, RenderViewHost
   1582   // will force the throbber to start, but WebKit will essentially ignore the
   1583   // navigation, and won't send a message to stop the throbber. To prevent this
   1584   // from happening, we drop the navigation here and stop the slow-to-commit
   1585   // page from loading (which would normally happen during the navigation).
   1586   if (pending_entry_index_ != -1 &&
   1587       pending_entry_index_ == last_committed_entry_index_ &&
   1588       (entries_[pending_entry_index_]->restore_type() ==
   1589           NavigationEntryImpl::RESTORE_NONE) &&
   1590       (entries_[pending_entry_index_]->GetTransitionType() &
   1591           PAGE_TRANSITION_FORWARD_BACK)) {
   1592     delegate_->Stop();
   1593 
   1594     // If an interstitial page is showing, we want to close it to get back
   1595     // to what was showing before.
   1596     if (delegate_->GetInterstitialPage())
   1597       delegate_->GetInterstitialPage()->DontProceed();
   1598 
   1599     DiscardNonCommittedEntries();
   1600     return;
   1601   }
   1602 
   1603   // If an interstitial page is showing, the previous renderer is blocked and
   1604   // cannot make new requests.  Unblock (and disable) it to allow this
   1605   // navigation to succeed.  The interstitial will stay visible until the
   1606   // resulting DidNavigate.
   1607   if (delegate_->GetInterstitialPage()) {
   1608     static_cast<InterstitialPageImpl*>(delegate_->GetInterstitialPage())->
   1609         CancelForNavigation();
   1610   }
   1611 
   1612   // For session history navigations only the pending_entry_index_ is set.
   1613   if (!pending_entry_) {
   1614     DCHECK_NE(pending_entry_index_, -1);
   1615     pending_entry_ = entries_[pending_entry_index_].get();
   1616   }
   1617 
   1618   // This call does not support re-entrancy.  See http://crbug.com/347742.
   1619   CHECK(!in_navigate_to_pending_entry_);
   1620   in_navigate_to_pending_entry_ = true;
   1621   bool success = delegate_->NavigateToPendingEntry(reload_type);
   1622   in_navigate_to_pending_entry_ = false;
   1623 
   1624   if (!success)
   1625     DiscardNonCommittedEntries();
   1626 
   1627   // If the entry is being restored and doesn't have a SiteInstance yet, fill
   1628   // it in now that we know. This allows us to find the entry when it commits.
   1629   if (pending_entry_ && !pending_entry_->site_instance() &&
   1630       pending_entry_->restore_type() != NavigationEntryImpl::RESTORE_NONE) {
   1631     pending_entry_->set_site_instance(static_cast<SiteInstanceImpl*>(
   1632         delegate_->GetPendingSiteInstance()));
   1633     pending_entry_->set_restore_type(NavigationEntryImpl::RESTORE_NONE);
   1634   }
   1635 }
   1636 
   1637 void NavigationControllerImpl::NotifyNavigationEntryCommitted(
   1638     LoadCommittedDetails* details) {
   1639   details->entry = GetLastCommittedEntry();
   1640 
   1641   // We need to notify the ssl_manager_ before the web_contents_ so the
   1642   // location bar will have up-to-date information about the security style
   1643   // when it wants to draw.  See http://crbug.com/11157
   1644   ssl_manager_.DidCommitProvisionalLoad(*details);
   1645 
   1646   delegate_->NotifyNavigationStateChanged(kInvalidateAll);
   1647   delegate_->NotifyNavigationEntryCommitted(*details);
   1648 
   1649   // TODO(avi): Remove. http://crbug.com/170921
   1650   NotificationDetails notification_details =
   1651       Details<LoadCommittedDetails>(details);
   1652   NotificationService::current()->Notify(
   1653       NOTIFICATION_NAV_ENTRY_COMMITTED,
   1654       Source<NavigationController>(this),
   1655       notification_details);
   1656 }
   1657 
   1658 // static
   1659 size_t NavigationControllerImpl::max_entry_count() {
   1660   if (max_entry_count_for_testing_ != kMaxEntryCountForTestingNotSet)
   1661      return max_entry_count_for_testing_;
   1662   return kMaxSessionHistoryEntries;
   1663 }
   1664 
   1665 void NavigationControllerImpl::SetActive(bool is_active) {
   1666   if (is_active && needs_reload_)
   1667     LoadIfNecessary();
   1668 }
   1669 
   1670 void NavigationControllerImpl::LoadIfNecessary() {
   1671   if (!needs_reload_)
   1672     return;
   1673 
   1674   // Calling Reload() results in ignoring state, and not loading.
   1675   // Explicitly use NavigateToPendingEntry so that the renderer uses the
   1676   // cached state.
   1677   pending_entry_index_ = last_committed_entry_index_;
   1678   NavigateToPendingEntry(NO_RELOAD);
   1679 }
   1680 
   1681 void NavigationControllerImpl::NotifyEntryChanged(const NavigationEntry* entry,
   1682                                                   int index) {
   1683   EntryChangedDetails det;
   1684   det.changed_entry = entry;
   1685   det.index = index;
   1686   NotificationService::current()->Notify(
   1687       NOTIFICATION_NAV_ENTRY_CHANGED,
   1688       Source<NavigationController>(this),
   1689       Details<EntryChangedDetails>(&det));
   1690 }
   1691 
   1692 void NavigationControllerImpl::FinishRestore(int selected_index,
   1693                                              RestoreType type) {
   1694   DCHECK(selected_index >= 0 && selected_index < GetEntryCount());
   1695   ConfigureEntriesForRestore(&entries_, type);
   1696 
   1697   SetMaxRestoredPageID(static_cast<int32>(GetEntryCount()));
   1698 
   1699   last_committed_entry_index_ = selected_index;
   1700 }
   1701 
   1702 void NavigationControllerImpl::DiscardNonCommittedEntriesInternal() {
   1703   DiscardPendingEntry();
   1704   DiscardTransientEntry();
   1705 }
   1706 
   1707 void NavigationControllerImpl::DiscardPendingEntry() {
   1708   // It is not safe to call DiscardPendingEntry while NavigateToEntry is in
   1709   // progress, since this will cause a use-after-free.  (We only allow this
   1710   // when the tab is being destroyed for shutdown, since it won't return to
   1711   // NavigateToEntry in that case.)  http://crbug.com/347742.
   1712   CHECK(!in_navigate_to_pending_entry_ || delegate_->IsBeingDestroyed());
   1713 
   1714   if (pending_entry_index_ == -1)
   1715     delete pending_entry_;
   1716   pending_entry_ = NULL;
   1717   pending_entry_index_ = -1;
   1718 }
   1719 
   1720 void NavigationControllerImpl::DiscardTransientEntry() {
   1721   if (transient_entry_index_ == -1)
   1722     return;
   1723   entries_.erase(entries_.begin() + transient_entry_index_);
   1724   if (last_committed_entry_index_ > transient_entry_index_)
   1725     last_committed_entry_index_--;
   1726   transient_entry_index_ = -1;
   1727 }
   1728 
   1729 int NavigationControllerImpl::GetEntryIndexWithPageID(
   1730     SiteInstance* instance, int32 page_id) const {
   1731   for (int i = static_cast<int>(entries_.size()) - 1; i >= 0; --i) {
   1732     if ((entries_[i]->site_instance() == instance) &&
   1733         (entries_[i]->GetPageID() == page_id))
   1734       return i;
   1735   }
   1736   return -1;
   1737 }
   1738 
   1739 NavigationEntry* NavigationControllerImpl::GetTransientEntry() const {
   1740   if (transient_entry_index_ == -1)
   1741     return NULL;
   1742   return entries_[transient_entry_index_].get();
   1743 }
   1744 
   1745 void NavigationControllerImpl::SetTransientEntry(NavigationEntry* entry) {
   1746   // Discard any current transient entry, we can only have one at a time.
   1747   int index = 0;
   1748   if (last_committed_entry_index_ != -1)
   1749     index = last_committed_entry_index_ + 1;
   1750   DiscardTransientEntry();
   1751   entries_.insert(
   1752       entries_.begin() + index, linked_ptr<NavigationEntryImpl>(
   1753           NavigationEntryImpl::FromNavigationEntry(entry)));
   1754   transient_entry_index_ = index;
   1755   delegate_->NotifyNavigationStateChanged(kInvalidateAll);
   1756 }
   1757 
   1758 void NavigationControllerImpl::InsertEntriesFrom(
   1759     const NavigationControllerImpl& source,
   1760     int max_index) {
   1761   DCHECK_LE(max_index, source.GetEntryCount());
   1762   size_t insert_index = 0;
   1763   for (int i = 0; i < max_index; i++) {
   1764     // When cloning a tab, copy all entries except interstitial pages
   1765     if (source.entries_[i].get()->GetPageType() !=
   1766         PAGE_TYPE_INTERSTITIAL) {
   1767       entries_.insert(entries_.begin() + insert_index++,
   1768                       linked_ptr<NavigationEntryImpl>(
   1769                           new NavigationEntryImpl(*source.entries_[i])));
   1770     }
   1771   }
   1772 }
   1773 
   1774 void NavigationControllerImpl::SetGetTimestampCallbackForTest(
   1775     const base::Callback<base::Time()>& get_timestamp_callback) {
   1776   get_timestamp_callback_ = get_timestamp_callback;
   1777 }
   1778 
   1779 }  // namespace content
   1780