blob: 715e33f77bb046f261f50a8420b2b99f2d85225e [file] [log] [blame]
license.botbf09a502008-08-24 00:55:551// Copyright (c) 2006-2008 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.
initial.commit09911bf2008-07-26 23:55:294
5#include "chrome/browser/navigation_controller.h"
6
7#include "base/command_line.h"
8#include "base/file_util.h"
9#include "base/logging.h"
10#include "base/string_util.h"
11#include "chrome/common/navigation_types.h"
12#include "chrome/common/resource_bundle.h"
13#include "chrome/common/scoped_vector.h"
14#include "chrome/browser/browser_process.h"
15#include "chrome/browser/dom_ui/dom_ui_host.h"
16#include "chrome/browser/navigation_entry.h"
17#include "chrome/browser/profile.h"
18#include "chrome/browser/repost_form_warning_dialog.h"
19#include "chrome/browser/site_instance.h"
20#include "chrome/browser/tab_contents.h"
21#include "chrome/browser/tab_contents_delegate.h"
22#include "chrome/common/chrome_switches.h"
23#include "net/base/net_util.h"
[email protected]765b35502008-08-21 00:51:2024#include "webkit/glue/webkit_glue.h"
initial.commit09911bf2008-07-26 23:55:2925
26// TabContentsCollector ---------------------------------------------------
27
28// We never destroy a TabContents synchronously because there are some
29// complex code path that cause the current TabContents to be in the call
30// stack. So instead, we use a TabContentsCollector which either destroys
31// the TabContents or does nothing if it has been cancelled.
32class TabContentsCollector : public Task {
33 public:
34 TabContentsCollector(NavigationController* target,
35 TabContentsType target_type)
36 : target_(target),
37 target_type_(target_type) {
38 }
39
40 void Cancel() {
41 target_ = NULL;
42 }
43
44 virtual void Run() {
45 if (target_) {
46 // Note: this will cancel this task as a side effect so target_ is
47 // now null.
48 TabContents* tc = target_->GetTabContents(target_type_);
49 tc->Destroy();
50 }
51 }
52
53 private:
54 // The NavigationController we are acting on.
55 NavigationController* target_;
56
57 // The TabContentsType that needs to be collected.
58 TabContentsType target_type_;
59
60 DISALLOW_EVIL_CONSTRUCTORS(TabContentsCollector);
61};
62
63// NavigationController ---------------------------------------------------
64
[email protected]765b35502008-08-21 00:51:2065// The maximum number of entries that a navigation controller can store.
66// static
67const static size_t kMaxEntryCount = 50;
68
initial.commit09911bf2008-07-26 23:55:2969// static
70bool NavigationController::check_for_repost_ = true;
71
72// Creates a new NavigationEntry for each TabNavigation in navigations, adding
73// the NavigationEntry to entries. This is used during session restore.
74static void CreateNavigationEntriesFromTabNavigations(
75 const std::vector<TabNavigation>& navigations,
[email protected]765b35502008-08-21 00:51:2076 std::vector<linked_ptr<NavigationEntry> >* entries) {
initial.commit09911bf2008-07-26 23:55:2977 // Create a NavigationEntry for each of the navigations.
78 for (std::vector<TabNavigation>::const_iterator i =
79 navigations.begin(); i != navigations.end(); ++i) {
80 const TabNavigation& navigation = *i;
81
82 GURL real_url = navigation.url;
83 TabContentsType type = TabContents::TypeForURL(&real_url);
84 DCHECK(type != TAB_CONTENTS_UNKNOWN_TYPE);
85
86 NavigationEntry* entry = new NavigationEntry(
87 type,
88 NULL, // The site instance for restored tabs is sent on naviagion
89 // (WebContents::GetSiteInstanceForEntry).
90 static_cast<int>(i - navigations.begin()),
91 real_url,
92 navigation.title,
93 // Use a transition type of reload so that we don't incorrectly
94 // increase the typed count.
95 PageTransition::RELOAD);
[email protected]1e5645ff2008-08-27 18:09:0796 entry->set_display_url(navigation.url);
97 entry->set_content_state(navigation.state);
98 entry->set_has_post_data(
99 navigation.type_mask & TabNavigation::HAS_POST_DATA);
[email protected]765b35502008-08-21 00:51:20100 entries->push_back(linked_ptr<NavigationEntry>(entry));
initial.commit09911bf2008-07-26 23:55:29101 }
102}
103
104// Configure all the NavigationEntries in entries for restore. This resets
105// the transition type to reload and makes sure the content state isn't empty.
106static void ConfigureEntriesForRestore(
[email protected]765b35502008-08-21 00:51:20107 std::vector<linked_ptr<NavigationEntry> >* entries) {
initial.commit09911bf2008-07-26 23:55:29108 for (size_t i = 0, count = entries->size(); i < count; ++i) {
109 // Use a transition type of reload so that we don't incorrectly increase
110 // the typed count.
[email protected]1e5645ff2008-08-27 18:09:07111 (*entries)[i]->set_transition_type(PageTransition::RELOAD);
initial.commit09911bf2008-07-26 23:55:29112 (*entries)[i]->set_restored(true);
113 // NOTE(darin): This code is only needed for backwards compat.
[email protected]765b35502008-08-21 00:51:20114 NavigationController::SetContentStateIfEmpty((*entries)[i].get());
initial.commit09911bf2008-07-26 23:55:29115 }
116}
117
118NavigationController::NavigationController(TabContents* contents,
119 Profile* profile)
120 : profile_(profile),
[email protected]765b35502008-08-21 00:51:20121 pending_entry_(NULL),
122 last_committed_entry_index_(-1),
123 pending_entry_index_(-1),
124 max_entry_count_(kMaxEntryCount),
initial.commit09911bf2008-07-26 23:55:29125 active_contents_(contents),
126 alternate_nav_url_fetcher_entry_unique_id_(0),
127 max_restored_page_id_(-1),
128 ssl_manager_(this, NULL),
129 needs_reload_(false),
130 load_pending_entry_when_active_(false) {
131 if (contents)
132 RegisterTabContents(contents);
133 DCHECK(profile_);
134 profile_->RegisterNavigationController(this);
135}
136
137NavigationController::NavigationController(
138 Profile* profile,
139 const std::vector<TabNavigation>& navigations,
140 int selected_navigation,
141 HWND parent)
142 : profile_(profile),
[email protected]765b35502008-08-21 00:51:20143 pending_entry_(NULL),
144 last_committed_entry_index_(-1),
145 pending_entry_index_(-1),
146 max_entry_count_(kMaxEntryCount),
initial.commit09911bf2008-07-26 23:55:29147 active_contents_(NULL),
148 alternate_nav_url_fetcher_entry_unique_id_(0),
149 max_restored_page_id_(-1),
150 ssl_manager_(this, NULL),
151 needs_reload_(true),
152 load_pending_entry_when_active_(false) {
153 DCHECK(profile_);
154 DCHECK(selected_navigation >= 0 &&
155 selected_navigation < static_cast<int>(navigations.size()));
156
157 profile_->RegisterNavigationController(this);
158
159 // Populate entries_ from the supplied TabNavigations.
160 CreateNavigationEntriesFromTabNavigations(navigations, &entries_);
161
162 // And finish the restore.
163 FinishRestore(parent, selected_navigation);
164}
165
166NavigationController::~NavigationController() {
167 DCHECK(tab_contents_map_.empty());
168 DCHECK(tab_contents_collector_map_.empty());
169
[email protected]c0993872008-08-21 19:59:44170 DiscardPendingEntryInternal();
171
initial.commit09911bf2008-07-26 23:55:29172 profile_->UnregisterNavigationController(this);
[email protected]534e54b2008-08-13 15:40:09173 NotificationService::current()->Notify(NOTIFY_TAB_CLOSED,
174 Source<NavigationController>(this),
175 NotificationService::NoDetails());
initial.commit09911bf2008-07-26 23:55:29176}
177
178TabContents* NavigationController::GetTabContents(TabContentsType t) {
179 // Make sure the TabContents is no longer scheduled for collection.
180 CancelTabContentsCollection(t);
181 return tab_contents_map_[t];
182}
183
initial.commit09911bf2008-07-26 23:55:29184void NavigationController::Reload() {
185 // TODO(pkasting): http://b/1113085 Should this use DiscardPendingEntry()?
186 DiscardPendingEntryInternal();
187 int current_index = GetCurrentEntryIndex();
188 if (check_for_repost_ && current_index != -1 &&
[email protected]1e5645ff2008-08-27 18:09:07189 GetEntryAtIndex(current_index)->has_post_data() &&
initial.commit09911bf2008-07-26 23:55:29190 active_contents_->AsWebContents() &&
191 !active_contents_->AsWebContents()->showing_repost_interstitial()) {
192 // The user is asking to reload a page with POST data and we're not showing
193 // the POST interstitial. Prompt to make sure they really want to do this.
194 // If they do, RepostFormWarningDialog calls us back with
195 // ReloadDontCheckForRepost.
196 active_contents_->Activate();
197 RepostFormWarningDialog::RunRepostFormWarningDialog(this);
198 } else {
[email protected]765b35502008-08-21 00:51:20199 // Base the navigation on where we are now...
200 int current_index = GetCurrentEntryIndex();
201
202 // If we are no where, then we can't reload. TODO(darin): We should add a
203 // CanReload method.
204 if (current_index == -1)
205 return;
206
207 // TODO(pkasting): http://b/1113085 Should this use DiscardPendingEntry()?
208 DiscardPendingEntryInternal();
209
210 pending_entry_index_ = current_index;
[email protected]1e5645ff2008-08-27 18:09:07211 entries_[pending_entry_index_]->set_transition_type(PageTransition::RELOAD);
[email protected]765b35502008-08-21 00:51:20212 NavigateToPendingEntry(true);
initial.commit09911bf2008-07-26 23:55:29213 }
214}
215
[email protected]765b35502008-08-21 00:51:20216NavigationEntry* NavigationController::GetEntryWithPageID(
217 TabContentsType type, SiteInstance* instance, int32 page_id) const {
218 int index = GetEntryIndexWithPageID(type, instance, page_id);
219 return (index != -1) ? entries_[index].get() : NULL;
220}
221
222void NavigationController::LoadEntry(NavigationEntry* entry) {
223 // When navigating to a new page, we don't know for sure if we will actually
224 // end up leaving the current page. The new page load could for example
225 // result in a download or a 'no content' response (e.g., a mailto: URL).
226
227 // TODO(pkasting): http://b/1113085 Should this use DiscardPendingEntry()?
228 DiscardPendingEntryInternal();
229 pending_entry_ = entry;
230 NotificationService::current()->Notify(
231 NOTIFY_NAV_ENTRY_PENDING,
232 Source<NavigationController>(this),
233 NotificationService::NoDetails());
234 NavigateToPendingEntry(false);
235}
236
237/* static */
238void NavigationController::SetContentStateIfEmpty(
239 NavigationEntry* entry) {
[email protected]1e5645ff2008-08-27 18:09:07240 if (entry->content_state().empty() &&
241 (entry->tab_type() == TAB_CONTENTS_WEB ||
242 entry->tab_type() == TAB_CONTENTS_NEW_TAB_UI ||
243 entry->tab_type() == TAB_CONTENTS_ABOUT_UI ||
244 entry->tab_type() == TAB_CONTENTS_HTML_DIALOG)) {
[email protected]765b35502008-08-21 00:51:20245 // The state is empty and the url will be rendered by WebKit. An empty
246 // state is treated as a new navigation by WebKit, which would mean
247 // losing the navigation entries and generating a new navigation
248 // entry after this one. We don't want that. To avoid this we create
249 // a valid state which WebKit will not treat as a new navigation.
[email protected]1e5645ff2008-08-27 18:09:07250 entry->set_content_state(
251 webkit_glue::CreateHistoryStateForURL(entry->url()));
[email protected]765b35502008-08-21 00:51:20252 }
253}
254
255NavigationEntry* NavigationController::GetActiveEntry() const {
256 NavigationEntry* entry = pending_entry_;
257 if (!entry)
258 entry = GetLastCommittedEntry();
259 return entry;
260}
261
262int NavigationController::GetCurrentEntryIndex() const {
263 if (pending_entry_index_ != -1)
264 return pending_entry_index_;
265 return last_committed_entry_index_;
266}
267
268NavigationEntry* NavigationController::GetLastCommittedEntry() const {
269 if (last_committed_entry_index_ == -1)
270 return NULL;
271 return entries_[last_committed_entry_index_].get();
272}
273
274NavigationEntry* NavigationController::GetEntryAtOffset(int offset) const {
275 int index = last_committed_entry_index_ + offset;
276 if (index < 0 || index >= GetEntryCount())
277 return NULL;
278
279 return entries_[index].get();
280}
281
282bool NavigationController::CanStop() const {
283 // TODO(darin): do we have something pending that we can stop?
284 return false;
285}
286
287bool NavigationController::CanGoBack() const {
288 return entries_.size() > 1 && GetCurrentEntryIndex() > 0;
289}
290
291bool NavigationController::CanGoForward() const {
292 int index = GetCurrentEntryIndex();
293 return index >= 0 && index < (static_cast<int>(entries_.size()) - 1);
294}
295
296void NavigationController::GoBack() {
297 if (!CanGoBack()) {
298 NOTREACHED();
299 return;
300 }
301
302 // Base the navigation on where we are now...
303 int current_index = GetCurrentEntryIndex();
304
305 DiscardPendingEntry();
306
307 pending_entry_index_ = current_index - 1;
308 NavigateToPendingEntry(false);
309}
310
311void NavigationController::GoForward() {
312 if (!CanGoForward()) {
313 NOTREACHED();
314 return;
315 }
316
317 // Base the navigation on where we are now...
318 int current_index = GetCurrentEntryIndex();
319
320 DiscardPendingEntry();
321
322 pending_entry_index_ = current_index + 1;
323 NavigateToPendingEntry(false);
324}
325
326void NavigationController::GoToIndex(int index) {
327 if (index < 0 || index >= static_cast<int>(entries_.size())) {
328 NOTREACHED();
329 return;
330 }
331
332 DiscardPendingEntry();
333
334 pending_entry_index_ = index;
335 NavigateToPendingEntry(false);
336}
337
338void NavigationController::GoToOffset(int offset) {
339 int index = last_committed_entry_index_ + offset;
340 if (index < 0 || index >= GetEntryCount())
341 return;
342
343 GoToIndex(index);
344}
345
346void NavigationController::Stop() {
347 DCHECK(CanStop());
348
349 // TODO(darin): we probably want to just call Stop on the active tab
350 // contents, but should we also call DiscardPendingEntry?
351 NOTREACHED() << "implement me";
352}
353
initial.commit09911bf2008-07-26 23:55:29354void NavigationController::ReloadDontCheckForRepost() {
[email protected]765b35502008-08-21 00:51:20355 Reload();
initial.commit09911bf2008-07-26 23:55:29356}
357
358void NavigationController::Destroy() {
[email protected]b33452302008-08-04 19:36:36359 // Close all tab contents owned by this controller. We make a list on the
360 // stack because they are removed from the map as they are Destroyed
initial.commit09911bf2008-07-26 23:55:29361 // (invalidating the iterators), which may or may not occur synchronously.
[email protected]b33452302008-08-04 19:36:36362 // We also keep track of any NULL entries in the map so that we can clean
363 // them out.
initial.commit09911bf2008-07-26 23:55:29364 std::list<TabContents*> tabs_to_destroy;
[email protected]b33452302008-08-04 19:36:36365 std::list<TabContentsType> tab_types_to_erase;
initial.commit09911bf2008-07-26 23:55:29366 for (TabContentsMap::iterator i = tab_contents_map_.begin();
367 i != tab_contents_map_.end(); ++i) {
[email protected]b33452302008-08-04 19:36:36368 if (i->second)
369 tabs_to_destroy.push_back(i->second);
370 else
371 tab_types_to_erase.push_back(i->first);
372 }
373
374 // Clean out all NULL entries in the map so that we know empty map means all
375 // tabs destroyed. This is needed since TabContentsWasDestroyed() won't get
376 // called for types that are in our map with a NULL contents. (We don't do
377 // this by iterating over TAB_CONTENTS_NUM_TYPES because some tests create
378 // additional types.)
379 for (std::list<TabContentsType>::iterator i = tab_types_to_erase.begin();
380 i != tab_types_to_erase.end(); ++i) {
381 TabContentsMap::iterator map_iterator = tab_contents_map_.find(*i);
382 if (map_iterator != tab_contents_map_.end()) {
383 DCHECK(!map_iterator->second);
384 tab_contents_map_.erase(map_iterator);
385 }
initial.commit09911bf2008-07-26 23:55:29386 }
387
388 // Cancel all the TabContentsCollectors.
389 for (TabContentsCollectorMap::iterator i =
390 tab_contents_collector_map_.begin();
391 i != tab_contents_collector_map_.end(); ++i) {
392 DCHECK(i->second);
393 i->second->Cancel();
394 }
[email protected]ccfc1a7b2008-08-14 16:26:20395 tab_contents_collector_map_.clear();
396
initial.commit09911bf2008-07-26 23:55:29397
398 // Finally destroy all the tab contents.
399 for (std::list<TabContents*>::iterator i = tabs_to_destroy.begin();
400 i != tabs_to_destroy.end(); ++i) {
401 (*i)->Destroy();
402 }
403 // We are deleted at this point.
404}
405
406void NavigationController::TabContentsWasDestroyed(TabContentsType type) {
407 TabContentsMap::iterator i = tab_contents_map_.find(type);
408 DCHECK(i != tab_contents_map_.end());
409 tab_contents_map_.erase(i);
410
411 // Make sure we cancel any collector for that TabContents.
[email protected]ccfc1a7b2008-08-14 16:26:20412 CancelTabContentsCollection(type);
initial.commit09911bf2008-07-26 23:55:29413
414 // If that was the last tab to be destroyed, delete ourselves.
415 if (tab_contents_map_.empty())
416 delete this;
417}
418
419NavigationEntry* NavigationController::CreateNavigationEntry(
420 const GURL& url, PageTransition::Type transition) {
421 GURL real_url = url;
422 TabContentsType type;
423
424 // If the active contents supports |url|, use it.
425 // Note: in both cases, we give TabContents a chance to rewrite the URL.
426 TabContents* active = active_contents();
427 if (active && active->SupportsURL(&real_url))
428 type = active->type();
429 else
430 type = TabContents::TypeForURL(&real_url);
431
432 NavigationEntry* entry = new NavigationEntry(type, NULL, -1, real_url,
433 std::wstring(), transition);
[email protected]1e5645ff2008-08-27 18:09:07434 entry->set_display_url(url);
435 entry->set_user_typed_url(url);
initial.commit09911bf2008-07-26 23:55:29436 if (url.SchemeIsFile()) {
[email protected]1e5645ff2008-08-27 18:09:07437 entry->set_title(file_util::GetFilenameFromPath(UTF8ToWide(url.host() +
438 url.path())));
initial.commit09911bf2008-07-26 23:55:29439 }
440 return entry;
441}
442
443void NavigationController::LoadURL(const GURL& url,
444 PageTransition::Type transition) {
445 // The user initiated a load, we don't need to reload anymore.
446 needs_reload_ = false;
447
448 NavigationEntry* entry = CreateNavigationEntry(url, transition);
449
450 LoadEntry(entry);
451}
452
453void NavigationController::LoadURLLazily(const GURL& url,
454 PageTransition::Type type,
455 const std::wstring& title,
456 SkBitmap* icon) {
457 NavigationEntry* entry = CreateNavigationEntry(url, type);
[email protected]1e5645ff2008-08-27 18:09:07458 entry->set_title(title);
initial.commit09911bf2008-07-26 23:55:29459 if (icon)
[email protected]1e5645ff2008-08-27 18:09:07460 entry->favicon().set_bitmap(*icon);
initial.commit09911bf2008-07-26 23:55:29461
462 // TODO(pkasting): http://b/1113085 Should this use DiscardPendingEntry()?
463 DiscardPendingEntryInternal();
464 pending_entry_ = entry;
465 load_pending_entry_when_active_ = true;
466}
467
468bool NavigationController::LoadingURLLazily() {
469 return load_pending_entry_when_active_;
470}
471
472const std::wstring& NavigationController::GetLazyTitle() const {
473 if (pending_entry_)
[email protected]1e5645ff2008-08-27 18:09:07474 return pending_entry_->title();
initial.commit09911bf2008-07-26 23:55:29475 else
476 return EmptyWString();
477}
478
479const SkBitmap& NavigationController::GetLazyFavIcon() const {
480 if (pending_entry_) {
[email protected]1e5645ff2008-08-27 18:09:07481 return pending_entry_->favicon().bitmap();
initial.commit09911bf2008-07-26 23:55:29482 } else {
483 ResourceBundle &rb = ResourceBundle::GetSharedInstance();
484 return *rb.GetBitmapNamed(IDR_DEFAULT_FAVICON);
485 }
486}
487
initial.commit09911bf2008-07-26 23:55:29488void NavigationController::SetAlternateNavURLFetcher(
489 AlternateNavURLFetcher* alternate_nav_url_fetcher) {
490 DCHECK(!alternate_nav_url_fetcher_.get());
491 DCHECK(pending_entry_);
492 alternate_nav_url_fetcher_.reset(alternate_nav_url_fetcher);
493 alternate_nav_url_fetcher_entry_unique_id_ = pending_entry_->unique_id();
494}
495
496void NavigationController::DidNavigateToEntry(NavigationEntry* entry) {
497 DCHECK(active_contents_);
[email protected]1e5645ff2008-08-27 18:09:07498 DCHECK(entry->tab_type() == active_contents_->type());
initial.commit09911bf2008-07-26 23:55:29499
[email protected]765b35502008-08-21 00:51:20500 SetContentStateIfEmpty(entry);
501
502 entry->set_restored(false);
503
504 // If the entry is that of a page with PageID larger than any this Tab has
505 // seen before, then consider it a new navigation. Note that if the entry
506 // has a SiteInstance, it should be the same as the SiteInstance of the
507 // active WebContents, because we have just navigated to it.
[email protected]1e5645ff2008-08-27 18:09:07508 DCHECK(entry->page_id() >= 0) << "Page ID must be set before calling us.";
509 if (entry->page_id() > GetMaxPageID()) {
[email protected]765b35502008-08-21 00:51:20510 InsertEntry(entry);
511 NotifyNavigationEntryCommitted();
[email protected]50664fd2008-08-28 16:10:30512 // It is now a safe time to schedule collection for any tab contents of a
513 // different type, because a navigation is necessary to get back to them.
514 ScheduleTabContentsCollectionForInactiveTabs();
[email protected]765b35502008-08-21 00:51:20515 return;
516 }
517
518 // Otherwise, we just need to update an existing entry with matching PageID.
519 // If the existing entry corresponds to the entry which is pending, then we
520 // must update the current entry index accordingly. When navigating to the
521 // same URL, a new PageID is not created.
522
[email protected]1e5645ff2008-08-27 18:09:07523 int existing_entry_index = GetEntryIndexWithPageID(entry->tab_type(),
[email protected]765b35502008-08-21 00:51:20524 entry->site_instance(),
[email protected]1e5645ff2008-08-27 18:09:07525 entry->page_id());
[email protected]765b35502008-08-21 00:51:20526 NavigationEntry* existing_entry = (existing_entry_index != -1) ?
527 entries_[existing_entry_index].get() : NULL;
528 if (!existing_entry) {
529 // No existing entry, then simply ignore this navigation!
[email protected]1e5645ff2008-08-27 18:09:07530 DLOG(WARNING) << "ignoring navigation for page: " << entry->page_id();
[email protected]765b35502008-08-21 00:51:20531 } else if ((existing_entry != pending_entry_) && pending_entry_ &&
[email protected]1e5645ff2008-08-27 18:09:07532 (pending_entry_->page_id() == -1) &&
533 (pending_entry_->url() == existing_entry->url())) {
[email protected]765b35502008-08-21 00:51:20534 // Not a new navigation.
535 existing_entry->set_unique_id(pending_entry_->unique_id());
536 DiscardPendingEntry();
537 } else {
538 DCHECK(existing_entry != entry);
539 // The given entry might provide a new URL... e.g., navigating back to a
540 // page in session history could have resulted in a new client redirect.
541 // The given entry might also provide a new title (typically an empty title
542 // to overwrite the existing title).
[email protected]1e5645ff2008-08-27 18:09:07543 existing_entry->set_url(entry->url());
544 existing_entry->set_title(entry->title());
545 existing_entry->favicon() = entry->favicon();
546 existing_entry->set_content_state(entry->content_state());
[email protected]765b35502008-08-21 00:51:20547
548 // TODO(brettw) why only copy the security style and no other SSL stuff?
549 existing_entry->ssl().set_security_style(entry->ssl().security_style());
550
551 const int prev_entry_index = last_committed_entry_index_;
552 if (existing_entry == pending_entry_) {
553 DCHECK(pending_entry_index_ != -1);
554 last_committed_entry_index_ = pending_entry_index_;
555 // TODO(pkasting): http://b/1113085 Should this use DiscardPendingEntry()?
556 DiscardPendingEntryInternal();
557 } else {
558 // NOTE: Do not update the unique ID here, as we don't want infobars etc.
559 // to dismiss.
560
561 // The navigation could have been issued by the renderer, so be sure that
562 // we update our current index.
563 last_committed_entry_index_ = existing_entry_index;
564 }
565 IndexOfActiveEntryChanged(prev_entry_index);
566 }
567
568 delete entry;
569 NotifyNavigationEntryCommitted();
initial.commit09911bf2008-07-26 23:55:29570
571 if (alternate_nav_url_fetcher_.get()) {
572 // Because this call may synchronously show an infobar, we do it last, to
573 // make sure all other state is stable and the infobar won't get blown away
574 // by some transition.
575 alternate_nav_url_fetcher_->OnNavigatedToEntry();
576 }
577
[email protected]b33452302008-08-04 19:36:36578 // It is now a safe time to schedule collection for any tab contents of a
579 // different type, because a navigation is necessary to get back to them.
[email protected]50664fd2008-08-28 16:10:30580 ScheduleTabContentsCollectionForInactiveTabs();
initial.commit09911bf2008-07-26 23:55:29581}
582
[email protected]765b35502008-08-21 00:51:20583
584int NavigationController::GetIndexOfEntry(
585 const NavigationEntry* entry) const {
586 const NavigationEntries::const_iterator i(std::find(
587 entries_.begin(),
588 entries_.end(),
589 entry));
590 return (i == entries_.end()) ? -1 : static_cast<int>(i - entries_.begin());
591}
592
593void NavigationController::RemoveLastEntry() {
594 int current_size = static_cast<int>(entries_.size());
595
596 if (current_size > 0) {
597 if (pending_entry_ == entries_[current_size - 1] ||
598 pending_entry_index_ == current_size - 1)
599 DiscardPendingEntryInternal();
600
601 entries_.pop_back();
602
603 if (last_committed_entry_index_ >= current_size - 1)
604 last_committed_entry_index_ = current_size - 2;
605
606 NotifyPrunedEntries();
607 }
608}
609
initial.commit09911bf2008-07-26 23:55:29610void NavigationController::DiscardPendingEntry() {
[email protected]765b35502008-08-21 00:51:20611 DiscardPendingEntryInternal();
initial.commit09911bf2008-07-26 23:55:29612
613 // Synchronize the active_contents_ to the last committed entry.
614 NavigationEntry* last_entry = GetLastCommittedEntry();
[email protected]1e5645ff2008-08-27 18:09:07615 if (last_entry && last_entry->tab_type() != active_contents_->type()) {
initial.commit09911bf2008-07-26 23:55:29616 TabContents* from_contents = active_contents_;
617 from_contents->SetActive(false);
618
619 // Switch back to the previous tab contents.
[email protected]1e5645ff2008-08-27 18:09:07620 active_contents_ = GetTabContents(last_entry->tab_type());
initial.commit09911bf2008-07-26 23:55:29621 DCHECK(active_contents_);
622
623 active_contents_->SetActive(true);
624
625 // If we are transitioning from two types of WebContents, we need to migrate
626 // the download shelf if it is visible. The download shelf may have been
627 // created before the error that caused us to discard the entry.
628 WebContents::MigrateShelfView(from_contents, active_contents_);
629
630 if (from_contents->delegate()) {
631 from_contents->delegate()->ReplaceContents(from_contents,
632 active_contents_);
633 }
634
635 // The entry we just discarded needed a different TabContents type. We no
636 // longer need it but we can't destroy it just yet because the TabContents
637 // is very likely involved in the current stack.
638 DCHECK(from_contents != active_contents_);
639 ScheduleTabContentsCollection(from_contents->type());
640 }
initial.commit09911bf2008-07-26 23:55:29641}
642
643void NavigationController::InsertEntry(NavigationEntry* entry) {
[email protected]1e5645ff2008-08-27 18:09:07644 DCHECK(entry->transition_type() != PageTransition::AUTO_SUBFRAME);
[email protected]765b35502008-08-21 00:51:20645
646 // Copy the pending entry's unique ID to the committed entry.
647 // I don't know if pending_entry_index_ can be other than -1 here.
648 const NavigationEntry* const pending_entry = (pending_entry_index_ == -1) ?
649 pending_entry_ : entries_[pending_entry_index_].get();
650 if (pending_entry)
651 entry->set_unique_id(pending_entry->unique_id());
652
653 DiscardPendingEntryInternal();
654
655 int current_size = static_cast<int>(entries_.size());
656
657 // Prune any entries which are in front of the current entry.
658 if (current_size > 0) {
659 bool pruned = false;
660 while (last_committed_entry_index_ < (current_size - 1)) {
661 pruned = true;
662 entries_.pop_back();
663 current_size--;
664 }
665 if (pruned) // Only notify if we did prune something.
666 NotifyPrunedEntries();
667 }
668
669 if (entries_.size() >= max_entry_count_)
670 RemoveEntryAtIndex(0);
671
672 entries_.push_back(linked_ptr<NavigationEntry>(entry));
673 last_committed_entry_index_ = static_cast<int>(entries_.size()) - 1;
674
initial.commit09911bf2008-07-26 23:55:29675 active_contents_->NotifyDidNavigate(NAVIGATION_NEW, 0);
676}
677
678void NavigationController::SetWindowID(const SessionID& id) {
679 window_id_ = id;
[email protected]534e54b2008-08-13 15:40:09680 NotificationService::current()->Notify(NOTIFY_TAB_PARENTED,
681 Source<NavigationController>(this),
682 NotificationService::NoDetails());
initial.commit09911bf2008-07-26 23:55:29683}
684
685void NavigationController::NavigateToPendingEntry(bool reload) {
686 TabContents* from_contents = active_contents_;
687
688 // For session history navigations only the pending_entry_index_ is set.
689 if (!pending_entry_) {
690 DCHECK(pending_entry_index_ != -1);
[email protected]765b35502008-08-21 00:51:20691 pending_entry_ = entries_[pending_entry_index_].get();
initial.commit09911bf2008-07-26 23:55:29692 }
693
694 // Reset the security states as any SSL error may have been resolved since we
695 // last visited that page.
[email protected]eb34392b2008-08-19 15:42:20696 pending_entry_->ssl() = NavigationEntry::SSLStatus();
initial.commit09911bf2008-07-26 23:55:29697
[email protected]1e5645ff2008-08-27 18:09:07698 if (from_contents && from_contents->type() != pending_entry_->tab_type())
initial.commit09911bf2008-07-26 23:55:29699 from_contents->SetActive(false);
700
701 HWND parent =
702 from_contents ? GetParent(from_contents->GetContainerHWND()) : 0;
703 TabContents* contents =
704 GetTabContentsCreateIfNecessary(parent, *pending_entry_);
705
706 contents->SetActive(true);
707 active_contents_ = contents;
708
709 if (from_contents && from_contents != contents) {
710 if (from_contents->delegate())
711 from_contents->delegate()->ReplaceContents(from_contents, contents);
712 }
713
[email protected]6cf85902008-08-19 17:38:12714 if (!contents->Navigate(*pending_entry_, reload))
initial.commit09911bf2008-07-26 23:55:29715 DiscardPendingEntry();
initial.commit09911bf2008-07-26 23:55:29716}
717
[email protected]6cf85902008-08-19 17:38:12718void NavigationController::NotifyNavigationEntryCommitted() {
initial.commit09911bf2008-07-26 23:55:29719 // Reset the Alternate Nav URL Fetcher if we're loading some page it doesn't
720 // care about. We must do this before calling Notify() below as that may
721 // result in the creation of a new fetcher.
[email protected]6cf85902008-08-19 17:38:12722 //
723 // TODO(brettw) bug 1324500: this logic should be moved out of the controller!
initial.commit09911bf2008-07-26 23:55:29724 const NavigationEntry* const entry = GetActiveEntry();
725 if (!entry ||
726 (entry->unique_id() != alternate_nav_url_fetcher_entry_unique_id_)) {
727 alternate_nav_url_fetcher_.reset();
728 alternate_nav_url_fetcher_entry_unique_id_ = 0;
729 }
730
731 // TODO(pkasting): http://b/1113079 Probably these explicit notification paths
732 // should be removed, and interested parties should just listen for the
733 // notification below instead.
734 ssl_manager_.NavigationStateChanged();
735 active_contents_->NotifyNavigationStateChanged(
736 TabContents::INVALIDATE_EVERYTHING);
737
[email protected]6cf85902008-08-19 17:38:12738 NotificationService::current()->Notify(NOTIFY_NAV_ENTRY_COMMITTED,
[email protected]534e54b2008-08-13 15:40:09739 Source<NavigationController>(this),
740 NotificationService::NoDetails());
initial.commit09911bf2008-07-26 23:55:29741}
742
743void NavigationController::NotifyPrunedEntries() {
[email protected]534e54b2008-08-13 15:40:09744 NotificationService::current()->Notify(NOTIFY_NAV_LIST_PRUNED,
745 Source<NavigationController>(this),
746 NotificationService::NoDetails());
initial.commit09911bf2008-07-26 23:55:29747}
748
749void NavigationController::IndexOfActiveEntryChanged(
750 int prev_committed_index) {
751 NavigationType nav_type = NAVIGATION_BACK_FORWARD;
752 int relative_navigation_offset =
753 GetLastCommittedEntryIndex() - prev_committed_index;
754 if (relative_navigation_offset == 0) {
755 nav_type = NAVIGATION_REPLACE;
756 }
757 active_contents_->NotifyDidNavigate(nav_type, relative_navigation_offset);
initial.commit09911bf2008-07-26 23:55:29758}
759
760TabContents* NavigationController::GetTabContentsCreateIfNecessary(
761 HWND parent,
762 const NavigationEntry& entry) {
[email protected]1e5645ff2008-08-27 18:09:07763 TabContents* contents = GetTabContents(entry.tab_type());
initial.commit09911bf2008-07-26 23:55:29764 if (!contents) {
[email protected]1e5645ff2008-08-27 18:09:07765 contents = TabContents::CreateWithType(entry.tab_type(), parent, profile_,
initial.commit09911bf2008-07-26 23:55:29766 entry.site_instance());
767 if (!contents->AsWebContents()) {
768 // Update the max page id, otherwise the newly created TabContents may
769 // have reset its max page id resulting in all new navigations. We only
770 // do this for non-WebContents as WebContents takes care of this via its
771 // SiteInstance. If this creation is the result of a restore, WebContents
772 // handles invoking ReservePageIDRange to make sure the renderer's
773 // max_page_id is updated to reflect the restored range of page ids.
774 int32 max_page_id = contents->GetMaxPageID();
775 for (size_t i = 0; i < entries_.size(); ++i) {
[email protected]1e5645ff2008-08-27 18:09:07776 if (entries_[i]->tab_type() == entry.tab_type())
777 max_page_id = std::max(max_page_id, entries_[i]->page_id());
initial.commit09911bf2008-07-26 23:55:29778 }
779 contents->UpdateMaxPageID(max_page_id);
780 }
781 RegisterTabContents(contents);
782 }
783
784 // We should not be trying to collect this tab contents.
785 DCHECK(tab_contents_collector_map_.find(contents->type()) ==
786 tab_contents_collector_map_.end());
787
788 return contents;
789}
790
791void NavigationController::RegisterTabContents(TabContents* some_contents) {
792 DCHECK(some_contents);
793 TabContentsType t = some_contents->type();
794 TabContents* tc;
795 if ((tc = tab_contents_map_[t]) != some_contents) {
796 if (tc) {
797 NOTREACHED() << "Should not happen. Multiple contents for one type";
798 } else {
799 tab_contents_map_[t] = some_contents;
800 some_contents->set_controller(this);
801 }
802 }
803 if (some_contents->AsDOMUIHost())
804 some_contents->AsDOMUIHost()->AttachMessageHandlers();
805}
806
[email protected]534e54b2008-08-13 15:40:09807void NavigationController::NotifyEntryChangedByPageID(
initial.commit09911bf2008-07-26 23:55:29808 TabContentsType type,
809 SiteInstance *instance,
[email protected]534e54b2008-08-13 15:40:09810 int32 page_id) {
initial.commit09911bf2008-07-26 23:55:29811 int index = GetEntryIndexWithPageID(type, instance, page_id);
812 if (index != -1)
[email protected]765b35502008-08-21 00:51:20813 NotifyEntryChanged(entries_[index].get(), index);
initial.commit09911bf2008-07-26 23:55:29814}
815
816// static
817void NavigationController::DisablePromptOnRepost() {
818 check_for_repost_ = false;
819}
820
821void NavigationController::SetActive(bool is_active) {
822 if (is_active) {
823 if (needs_reload_) {
824 LoadIfNecessary();
825 } else if (load_pending_entry_when_active_) {
826 NavigateToPendingEntry(false);
827 load_pending_entry_when_active_ = false;
828 }
829 }
830}
831
832void NavigationController::LoadIfNecessary() {
833 if (!needs_reload_)
834 return;
835
836 needs_reload_ = false;
837 // Calling Reload() results in ignoring state, and not loading.
838 // Explicitly use NavigateToPendingEntry so that the renderer uses the
839 // cached state.
840 pending_entry_index_ = last_committed_entry_index_;
841 NavigateToPendingEntry(false);
842}
843
[email protected]765b35502008-08-21 00:51:20844void NavigationController::ResetInternal() {
845 // WARNING: this is invoked from the destructor, be sure not to invoke any
846 // virtual methods from this.
847 entries_.clear();
848 DiscardPendingEntryInternal();
849}
850
[email protected]534e54b2008-08-13 15:40:09851void NavigationController::NotifyEntryChanged(const NavigationEntry* entry,
852 int index) {
853 EntryChangedDetails det;
854 det.changed_entry = entry;
855 det.index = index;
856 NotificationService::current()->Notify(NOTIFY_NAV_ENTRY_CHANGED,
857 Source<NavigationController>(this),
858 Details<EntryChangedDetails>(&det));
initial.commit09911bf2008-07-26 23:55:29859}
860
[email protected]765b35502008-08-21 00:51:20861void NavigationController::RemoveEntryAtIndex(int index) {
862 // TODO(brettw) this is only called to remove the first one when we've got
863 // too many entries. It should probably be more specific for this case.
864 if (index >= static_cast<int>(entries_.size()) ||
865 index == pending_entry_index_ || index == last_committed_entry_index_) {
866 NOTREACHED();
867 return;
868 }
869
870 entries_.erase(entries_.begin() + index);
871
872 if (last_committed_entry_index_ >= index) {
873 if (!entries_.empty())
874 last_committed_entry_index_--;
875 else
876 last_committed_entry_index_ = -1;
877 }
878
879 // TODO(brettw) bug 1324021: we probably need some notification here so the
880 // session service can stay in sync.
881}
882
initial.commit09911bf2008-07-26 23:55:29883int NavigationController::GetMaxPageID() const {
884 return active_contents_->GetMaxPageID();
885}
886
887NavigationController* NavigationController::Clone(HWND parent_hwnd) {
888 NavigationController* nc = new NavigationController(NULL, profile_);
889
890 if (GetEntryCount() == 0)
891 return nc;
892
893 nc->needs_reload_ = true;
894
895 nc->entries_.reserve(entries_.size());
[email protected]765b35502008-08-21 00:51:20896 for (int i = 0, c = GetEntryCount(); i < c; ++i) {
897 nc->entries_.push_back(linked_ptr<NavigationEntry>(
898 new NavigationEntry(*GetEntryAtIndex(i))));
899 }
initial.commit09911bf2008-07-26 23:55:29900
901 nc->FinishRestore(parent_hwnd, last_committed_entry_index_);
902
903 return nc;
904}
905
[email protected]50664fd2008-08-28 16:10:30906void NavigationController::ScheduleTabContentsCollectionForInactiveTabs() {
907 int index = GetCurrentEntryIndex();
908 if (index < 0 || GetPendingEntryIndex() != -1)
909 return;
910
911 TabContentsType active_type = GetEntryAtIndex(index)->tab_type();
912 for (TabContentsMap::iterator i = tab_contents_map_.begin();
913 i != tab_contents_map_.end(); ++i) {
914 if (i->first != active_type)
915 ScheduleTabContentsCollection(i->first);
916 }
917}
918
initial.commit09911bf2008-07-26 23:55:29919void NavigationController::ScheduleTabContentsCollection(TabContentsType t) {
920 TabContentsCollectorMap::const_iterator i =
921 tab_contents_collector_map_.find(t);
922
923 // The tab contents is already scheduled for collection.
924 if (i != tab_contents_collector_map_.end())
925 return;
926
927 // If we currently don't have a TabContents for t, skip.
928 if (tab_contents_map_.find(t) == tab_contents_map_.end())
929 return;
930
931 // Create a collector and schedule it.
932 TabContentsCollector* tcc = new TabContentsCollector(this, t);
933 tab_contents_collector_map_[t] = tcc;
934 MessageLoop::current()->PostTask(FROM_HERE, tcc);
935}
936
937void NavigationController::CancelTabContentsCollection(TabContentsType t) {
938 TabContentsCollectorMap::iterator i = tab_contents_collector_map_.find(t);
939
940 if (i != tab_contents_collector_map_.end()) {
941 DCHECK(i->second);
942 i->second->Cancel();
943 tab_contents_collector_map_.erase(i);
944 }
945}
946
947void NavigationController::FinishRestore(HWND parent_hwnd, int selected_index) {
948 DCHECK(selected_index >= 0 && selected_index < GetEntryCount());
949 ConfigureEntriesForRestore(&entries_);
950
951 set_max_restored_page_id(GetEntryCount());
952
953 last_committed_entry_index_ = selected_index;
954
955 // Callers assume we have an active_contents after restoring, so set it now.
956 active_contents_ =
957 GetTabContentsCreateIfNecessary(parent_hwnd, *entries_[selected_index]);
958}
[email protected]765b35502008-08-21 00:51:20959
960void NavigationController::DiscardPendingEntryInternal() {
961 if (pending_entry_index_ == -1)
962 delete pending_entry_;
963 pending_entry_ = NULL;
964 pending_entry_index_ = -1;
965}
966
967int NavigationController::GetEntryIndexWithPageID(
968 TabContentsType type, SiteInstance* instance, int32 page_id) const {
969 // The instance should only be specified for contents displaying web pages.
970 // TODO(evanm): checking against NEW_TAB_UI and HTML_DLG here is lame.
971 // It'd be nice for DomUIHost to just use SiteInstances for keeping content
972 // separated properly.
973 if (type != TAB_CONTENTS_WEB &&
974 type != TAB_CONTENTS_NEW_TAB_UI &&
975 type != TAB_CONTENTS_ABOUT_UI &&
976 type != TAB_CONTENTS_HTML_DIALOG &&
977 type != TAB_CONTENTS_VIEW_SOURCE &&
978 type != TAB_CONTENTS_DEBUGGER)
979 DCHECK(instance == NULL);
980
981 for (int i = static_cast<int>(entries_.size()) - 1; i >= 0; --i) {
[email protected]1e5645ff2008-08-27 18:09:07982 if ((entries_[i]->tab_type() == type) &&
[email protected]765b35502008-08-21 00:51:20983 (entries_[i]->site_instance() == instance) &&
[email protected]1e5645ff2008-08-27 18:09:07984 (entries_[i]->page_id() == page_id))
[email protected]765b35502008-08-21 00:51:20985 return i;
986 }
987 return -1;
988}