blob: 1b86157e09c412799189acd7716ce32bb9437682 [file] [log] [blame]
initial.commit09911bf2008-07-26 23:55:291// Copyright 2008, Google Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14// * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30#include "chrome/browser/navigation_controller.h"
31
32#include "base/command_line.h"
33#include "base/file_util.h"
34#include "base/logging.h"
35#include "base/string_util.h"
36#include "chrome/common/navigation_types.h"
37#include "chrome/common/resource_bundle.h"
38#include "chrome/common/scoped_vector.h"
39#include "chrome/browser/browser_process.h"
40#include "chrome/browser/dom_ui/dom_ui_host.h"
41#include "chrome/browser/navigation_entry.h"
42#include "chrome/browser/profile.h"
43#include "chrome/browser/repost_form_warning_dialog.h"
44#include "chrome/browser/site_instance.h"
45#include "chrome/browser/tab_contents.h"
46#include "chrome/browser/tab_contents_delegate.h"
47#include "chrome/common/chrome_switches.h"
48#include "net/base/net_util.h"
[email protected]765b35502008-08-21 00:51:2049#include "webkit/glue/webkit_glue.h"
initial.commit09911bf2008-07-26 23:55:2950
51// TabContentsCollector ---------------------------------------------------
52
53// We never destroy a TabContents synchronously because there are some
54// complex code path that cause the current TabContents to be in the call
55// stack. So instead, we use a TabContentsCollector which either destroys
56// the TabContents or does nothing if it has been cancelled.
57class TabContentsCollector : public Task {
58 public:
59 TabContentsCollector(NavigationController* target,
60 TabContentsType target_type)
61 : target_(target),
62 target_type_(target_type) {
63 }
64
65 void Cancel() {
66 target_ = NULL;
67 }
68
69 virtual void Run() {
70 if (target_) {
71 // Note: this will cancel this task as a side effect so target_ is
72 // now null.
73 TabContents* tc = target_->GetTabContents(target_type_);
74 tc->Destroy();
75 }
76 }
77
78 private:
79 // The NavigationController we are acting on.
80 NavigationController* target_;
81
82 // The TabContentsType that needs to be collected.
83 TabContentsType target_type_;
84
85 DISALLOW_EVIL_CONSTRUCTORS(TabContentsCollector);
86};
87
88// NavigationController ---------------------------------------------------
89
[email protected]765b35502008-08-21 00:51:2090// The maximum number of entries that a navigation controller can store.
91// static
92const static size_t kMaxEntryCount = 50;
93
initial.commit09911bf2008-07-26 23:55:2994// static
95bool NavigationController::check_for_repost_ = true;
96
97// Creates a new NavigationEntry for each TabNavigation in navigations, adding
98// the NavigationEntry to entries. This is used during session restore.
99static void CreateNavigationEntriesFromTabNavigations(
100 const std::vector<TabNavigation>& navigations,
[email protected]765b35502008-08-21 00:51:20101 std::vector<linked_ptr<NavigationEntry> >* entries) {
initial.commit09911bf2008-07-26 23:55:29102 // Create a NavigationEntry for each of the navigations.
103 for (std::vector<TabNavigation>::const_iterator i =
104 navigations.begin(); i != navigations.end(); ++i) {
105 const TabNavigation& navigation = *i;
106
107 GURL real_url = navigation.url;
108 TabContentsType type = TabContents::TypeForURL(&real_url);
109 DCHECK(type != TAB_CONTENTS_UNKNOWN_TYPE);
110
111 NavigationEntry* entry = new NavigationEntry(
112 type,
113 NULL, // The site instance for restored tabs is sent on naviagion
114 // (WebContents::GetSiteInstanceForEntry).
115 static_cast<int>(i - navigations.begin()),
116 real_url,
117 navigation.title,
118 // Use a transition type of reload so that we don't incorrectly
119 // increase the typed count.
120 PageTransition::RELOAD);
121 entry->SetDisplayURL(navigation.url);
122 entry->SetContentState(navigation.state);
123 entry->SetHasPostData(navigation.type_mask & TabNavigation::HAS_POST_DATA);
[email protected]765b35502008-08-21 00:51:20124 entries->push_back(linked_ptr<NavigationEntry>(entry));
initial.commit09911bf2008-07-26 23:55:29125 }
126}
127
128// Configure all the NavigationEntries in entries for restore. This resets
129// the transition type to reload and makes sure the content state isn't empty.
130static void ConfigureEntriesForRestore(
[email protected]765b35502008-08-21 00:51:20131 std::vector<linked_ptr<NavigationEntry> >* entries) {
initial.commit09911bf2008-07-26 23:55:29132 for (size_t i = 0, count = entries->size(); i < count; ++i) {
133 // Use a transition type of reload so that we don't incorrectly increase
134 // the typed count.
135 (*entries)[i]->SetTransitionType(PageTransition::RELOAD);
136 (*entries)[i]->set_restored(true);
137 // NOTE(darin): This code is only needed for backwards compat.
[email protected]765b35502008-08-21 00:51:20138 NavigationController::SetContentStateIfEmpty((*entries)[i].get());
initial.commit09911bf2008-07-26 23:55:29139 }
140}
141
142NavigationController::NavigationController(TabContents* contents,
143 Profile* profile)
144 : profile_(profile),
[email protected]765b35502008-08-21 00:51:20145 pending_entry_(NULL),
146 last_committed_entry_index_(-1),
147 pending_entry_index_(-1),
148 max_entry_count_(kMaxEntryCount),
initial.commit09911bf2008-07-26 23:55:29149 active_contents_(contents),
150 alternate_nav_url_fetcher_entry_unique_id_(0),
151 max_restored_page_id_(-1),
152 ssl_manager_(this, NULL),
153 needs_reload_(false),
154 load_pending_entry_when_active_(false) {
155 if (contents)
156 RegisterTabContents(contents);
157 DCHECK(profile_);
158 profile_->RegisterNavigationController(this);
159}
160
161NavigationController::NavigationController(
162 Profile* profile,
163 const std::vector<TabNavigation>& navigations,
164 int selected_navigation,
165 HWND parent)
166 : profile_(profile),
[email protected]765b35502008-08-21 00:51:20167 pending_entry_(NULL),
168 last_committed_entry_index_(-1),
169 pending_entry_index_(-1),
170 max_entry_count_(kMaxEntryCount),
initial.commit09911bf2008-07-26 23:55:29171 active_contents_(NULL),
172 alternate_nav_url_fetcher_entry_unique_id_(0),
173 max_restored_page_id_(-1),
174 ssl_manager_(this, NULL),
175 needs_reload_(true),
176 load_pending_entry_when_active_(false) {
177 DCHECK(profile_);
178 DCHECK(selected_navigation >= 0 &&
179 selected_navigation < static_cast<int>(navigations.size()));
180
181 profile_->RegisterNavigationController(this);
182
183 // Populate entries_ from the supplied TabNavigations.
184 CreateNavigationEntriesFromTabNavigations(navigations, &entries_);
185
186 // And finish the restore.
187 FinishRestore(parent, selected_navigation);
188}
189
190NavigationController::~NavigationController() {
191 DCHECK(tab_contents_map_.empty());
192 DCHECK(tab_contents_collector_map_.empty());
193
initial.commit09911bf2008-07-26 23:55:29194 profile_->UnregisterNavigationController(this);
[email protected]534e54b2008-08-13 15:40:09195 NotificationService::current()->Notify(NOTIFY_TAB_CLOSED,
196 Source<NavigationController>(this),
197 NotificationService::NoDetails());
initial.commit09911bf2008-07-26 23:55:29198}
199
200TabContents* NavigationController::GetTabContents(TabContentsType t) {
201 // Make sure the TabContents is no longer scheduled for collection.
202 CancelTabContentsCollection(t);
203 return tab_contents_map_[t];
204}
205
initial.commit09911bf2008-07-26 23:55:29206void NavigationController::Reload() {
207 // TODO(pkasting): http://b/1113085 Should this use DiscardPendingEntry()?
208 DiscardPendingEntryInternal();
209 int current_index = GetCurrentEntryIndex();
210 if (check_for_repost_ && current_index != -1 &&
211 GetEntryAtIndex(current_index)->HasPostData() &&
212 active_contents_->AsWebContents() &&
213 !active_contents_->AsWebContents()->showing_repost_interstitial()) {
214 // The user is asking to reload a page with POST data and we're not showing
215 // the POST interstitial. Prompt to make sure they really want to do this.
216 // If they do, RepostFormWarningDialog calls us back with
217 // ReloadDontCheckForRepost.
218 active_contents_->Activate();
219 RepostFormWarningDialog::RunRepostFormWarningDialog(this);
220 } else {
[email protected]765b35502008-08-21 00:51:20221 // Base the navigation on where we are now...
222 int current_index = GetCurrentEntryIndex();
223
224 // If we are no where, then we can't reload. TODO(darin): We should add a
225 // CanReload method.
226 if (current_index == -1)
227 return;
228
229 // TODO(pkasting): http://b/1113085 Should this use DiscardPendingEntry()?
230 DiscardPendingEntryInternal();
231
232 pending_entry_index_ = current_index;
233 entries_[pending_entry_index_]->SetTransitionType(PageTransition::RELOAD);
234 NavigateToPendingEntry(true);
initial.commit09911bf2008-07-26 23:55:29235 }
236}
237
[email protected]765b35502008-08-21 00:51:20238NavigationEntry* NavigationController::GetEntryWithPageID(
239 TabContentsType type, SiteInstance* instance, int32 page_id) const {
240 int index = GetEntryIndexWithPageID(type, instance, page_id);
241 return (index != -1) ? entries_[index].get() : NULL;
242}
243
244void NavigationController::LoadEntry(NavigationEntry* entry) {
245 // When navigating to a new page, we don't know for sure if we will actually
246 // end up leaving the current page. The new page load could for example
247 // result in a download or a 'no content' response (e.g., a mailto: URL).
248
249 // TODO(pkasting): http://b/1113085 Should this use DiscardPendingEntry()?
250 DiscardPendingEntryInternal();
251 pending_entry_ = entry;
252 NotificationService::current()->Notify(
253 NOTIFY_NAV_ENTRY_PENDING,
254 Source<NavigationController>(this),
255 NotificationService::NoDetails());
256 NavigateToPendingEntry(false);
257}
258
259/* static */
260void NavigationController::SetContentStateIfEmpty(
261 NavigationEntry* entry) {
262 if (entry->GetContentState().empty() &&
263 (entry->GetType() == TAB_CONTENTS_WEB ||
264 entry->GetType() == TAB_CONTENTS_NEW_TAB_UI ||
265 entry->GetType() == TAB_CONTENTS_ABOUT_UI ||
266 entry->GetType() == TAB_CONTENTS_HTML_DIALOG)) {
267 // The state is empty and the url will be rendered by WebKit. An empty
268 // state is treated as a new navigation by WebKit, which would mean
269 // losing the navigation entries and generating a new navigation
270 // entry after this one. We don't want that. To avoid this we create
271 // a valid state which WebKit will not treat as a new navigation.
272 entry->SetContentState(
273 webkit_glue::CreateHistoryStateForURL(entry->GetURL()));
274 }
275}
276
277NavigationEntry* NavigationController::GetActiveEntry() const {
278 NavigationEntry* entry = pending_entry_;
279 if (!entry)
280 entry = GetLastCommittedEntry();
281 return entry;
282}
283
284int NavigationController::GetCurrentEntryIndex() const {
285 if (pending_entry_index_ != -1)
286 return pending_entry_index_;
287 return last_committed_entry_index_;
288}
289
290NavigationEntry* NavigationController::GetLastCommittedEntry() const {
291 if (last_committed_entry_index_ == -1)
292 return NULL;
293 return entries_[last_committed_entry_index_].get();
294}
295
296NavigationEntry* NavigationController::GetEntryAtOffset(int offset) const {
297 int index = last_committed_entry_index_ + offset;
298 if (index < 0 || index >= GetEntryCount())
299 return NULL;
300
301 return entries_[index].get();
302}
303
304bool NavigationController::CanStop() const {
305 // TODO(darin): do we have something pending that we can stop?
306 return false;
307}
308
309bool NavigationController::CanGoBack() const {
310 return entries_.size() > 1 && GetCurrentEntryIndex() > 0;
311}
312
313bool NavigationController::CanGoForward() const {
314 int index = GetCurrentEntryIndex();
315 return index >= 0 && index < (static_cast<int>(entries_.size()) - 1);
316}
317
318void NavigationController::GoBack() {
319 if (!CanGoBack()) {
320 NOTREACHED();
321 return;
322 }
323
324 // Base the navigation on where we are now...
325 int current_index = GetCurrentEntryIndex();
326
327 DiscardPendingEntry();
328
329 pending_entry_index_ = current_index - 1;
330 NavigateToPendingEntry(false);
331}
332
333void NavigationController::GoForward() {
334 if (!CanGoForward()) {
335 NOTREACHED();
336 return;
337 }
338
339 // Base the navigation on where we are now...
340 int current_index = GetCurrentEntryIndex();
341
342 DiscardPendingEntry();
343
344 pending_entry_index_ = current_index + 1;
345 NavigateToPendingEntry(false);
346}
347
348void NavigationController::GoToIndex(int index) {
349 if (index < 0 || index >= static_cast<int>(entries_.size())) {
350 NOTREACHED();
351 return;
352 }
353
354 DiscardPendingEntry();
355
356 pending_entry_index_ = index;
357 NavigateToPendingEntry(false);
358}
359
360void NavigationController::GoToOffset(int offset) {
361 int index = last_committed_entry_index_ + offset;
362 if (index < 0 || index >= GetEntryCount())
363 return;
364
365 GoToIndex(index);
366}
367
368void NavigationController::Stop() {
369 DCHECK(CanStop());
370
371 // TODO(darin): we probably want to just call Stop on the active tab
372 // contents, but should we also call DiscardPendingEntry?
373 NOTREACHED() << "implement me";
374}
375
initial.commit09911bf2008-07-26 23:55:29376void NavigationController::ReloadDontCheckForRepost() {
[email protected]765b35502008-08-21 00:51:20377 Reload();
initial.commit09911bf2008-07-26 23:55:29378}
379
380void NavigationController::Destroy() {
[email protected]b33452302008-08-04 19:36:36381 // Close all tab contents owned by this controller. We make a list on the
382 // stack because they are removed from the map as they are Destroyed
initial.commit09911bf2008-07-26 23:55:29383 // (invalidating the iterators), which may or may not occur synchronously.
[email protected]b33452302008-08-04 19:36:36384 // We also keep track of any NULL entries in the map so that we can clean
385 // them out.
initial.commit09911bf2008-07-26 23:55:29386 std::list<TabContents*> tabs_to_destroy;
[email protected]b33452302008-08-04 19:36:36387 std::list<TabContentsType> tab_types_to_erase;
initial.commit09911bf2008-07-26 23:55:29388 for (TabContentsMap::iterator i = tab_contents_map_.begin();
389 i != tab_contents_map_.end(); ++i) {
[email protected]b33452302008-08-04 19:36:36390 if (i->second)
391 tabs_to_destroy.push_back(i->second);
392 else
393 tab_types_to_erase.push_back(i->first);
394 }
395
396 // Clean out all NULL entries in the map so that we know empty map means all
397 // tabs destroyed. This is needed since TabContentsWasDestroyed() won't get
398 // called for types that are in our map with a NULL contents. (We don't do
399 // this by iterating over TAB_CONTENTS_NUM_TYPES because some tests create
400 // additional types.)
401 for (std::list<TabContentsType>::iterator i = tab_types_to_erase.begin();
402 i != tab_types_to_erase.end(); ++i) {
403 TabContentsMap::iterator map_iterator = tab_contents_map_.find(*i);
404 if (map_iterator != tab_contents_map_.end()) {
405 DCHECK(!map_iterator->second);
406 tab_contents_map_.erase(map_iterator);
407 }
initial.commit09911bf2008-07-26 23:55:29408 }
409
410 // Cancel all the TabContentsCollectors.
411 for (TabContentsCollectorMap::iterator i =
412 tab_contents_collector_map_.begin();
413 i != tab_contents_collector_map_.end(); ++i) {
414 DCHECK(i->second);
415 i->second->Cancel();
416 }
[email protected]ccfc1a7b2008-08-14 16:26:20417 tab_contents_collector_map_.clear();
418
initial.commit09911bf2008-07-26 23:55:29419
420 // Finally destroy all the tab contents.
421 for (std::list<TabContents*>::iterator i = tabs_to_destroy.begin();
422 i != tabs_to_destroy.end(); ++i) {
423 (*i)->Destroy();
424 }
425 // We are deleted at this point.
426}
427
428void NavigationController::TabContentsWasDestroyed(TabContentsType type) {
429 TabContentsMap::iterator i = tab_contents_map_.find(type);
430 DCHECK(i != tab_contents_map_.end());
431 tab_contents_map_.erase(i);
432
433 // Make sure we cancel any collector for that TabContents.
[email protected]ccfc1a7b2008-08-14 16:26:20434 CancelTabContentsCollection(type);
initial.commit09911bf2008-07-26 23:55:29435
436 // If that was the last tab to be destroyed, delete ourselves.
437 if (tab_contents_map_.empty())
438 delete this;
439}
440
441NavigationEntry* NavigationController::CreateNavigationEntry(
442 const GURL& url, PageTransition::Type transition) {
443 GURL real_url = url;
444 TabContentsType type;
445
446 // If the active contents supports |url|, use it.
447 // Note: in both cases, we give TabContents a chance to rewrite the URL.
448 TabContents* active = active_contents();
449 if (active && active->SupportsURL(&real_url))
450 type = active->type();
451 else
452 type = TabContents::TypeForURL(&real_url);
453
454 NavigationEntry* entry = new NavigationEntry(type, NULL, -1, real_url,
455 std::wstring(), transition);
456 entry->SetDisplayURL(url);
457 entry->SetUserTypedURL(url);
458 if (url.SchemeIsFile()) {
459 entry->SetTitle(file_util::GetFilenameFromPath(UTF8ToWide(url.host() +
460 url.path())));
461 }
462 return entry;
463}
464
465void NavigationController::LoadURL(const GURL& url,
466 PageTransition::Type transition) {
467 // The user initiated a load, we don't need to reload anymore.
468 needs_reload_ = false;
469
470 NavigationEntry* entry = CreateNavigationEntry(url, transition);
471
472 LoadEntry(entry);
473}
474
475void NavigationController::LoadURLLazily(const GURL& url,
476 PageTransition::Type type,
477 const std::wstring& title,
478 SkBitmap* icon) {
479 NavigationEntry* entry = CreateNavigationEntry(url, type);
480 entry->SetTitle(title);
481 if (icon)
482 entry->SetFavIcon(*icon);
483
484 // TODO(pkasting): http://b/1113085 Should this use DiscardPendingEntry()?
485 DiscardPendingEntryInternal();
486 pending_entry_ = entry;
487 load_pending_entry_when_active_ = true;
488}
489
490bool NavigationController::LoadingURLLazily() {
491 return load_pending_entry_when_active_;
492}
493
494const std::wstring& NavigationController::GetLazyTitle() const {
495 if (pending_entry_)
496 return pending_entry_->GetTitle();
497 else
498 return EmptyWString();
499}
500
501const SkBitmap& NavigationController::GetLazyFavIcon() const {
502 if (pending_entry_) {
503 return pending_entry_->GetFavIcon();
504 } else {
505 ResourceBundle &rb = ResourceBundle::GetSharedInstance();
506 return *rb.GetBitmapNamed(IDR_DEFAULT_FAVICON);
507 }
508}
509
initial.commit09911bf2008-07-26 23:55:29510void NavigationController::SetAlternateNavURLFetcher(
511 AlternateNavURLFetcher* alternate_nav_url_fetcher) {
512 DCHECK(!alternate_nav_url_fetcher_.get());
513 DCHECK(pending_entry_);
514 alternate_nav_url_fetcher_.reset(alternate_nav_url_fetcher);
515 alternate_nav_url_fetcher_entry_unique_id_ = pending_entry_->unique_id();
516}
517
518void NavigationController::DidNavigateToEntry(NavigationEntry* entry) {
519 DCHECK(active_contents_);
520 DCHECK(entry->GetType() == active_contents_->type());
521
[email protected]765b35502008-08-21 00:51:20522 SetContentStateIfEmpty(entry);
523
524 entry->set_restored(false);
525
526 // If the entry is that of a page with PageID larger than any this Tab has
527 // seen before, then consider it a new navigation. Note that if the entry
528 // has a SiteInstance, it should be the same as the SiteInstance of the
529 // active WebContents, because we have just navigated to it.
530 if (entry->GetPageID() > GetMaxPageID()) {
531 InsertEntry(entry);
532 NotifyNavigationEntryCommitted();
533 return;
534 }
535
536 // Otherwise, we just need to update an existing entry with matching PageID.
537 // If the existing entry corresponds to the entry which is pending, then we
538 // must update the current entry index accordingly. When navigating to the
539 // same URL, a new PageID is not created.
540
541 int existing_entry_index = GetEntryIndexWithPageID(entry->GetType(),
542 entry->site_instance(),
543 entry->GetPageID());
544 NavigationEntry* existing_entry = (existing_entry_index != -1) ?
545 entries_[existing_entry_index].get() : NULL;
546 if (!existing_entry) {
547 // No existing entry, then simply ignore this navigation!
548 DLOG(WARNING) << "ignoring navigation for page: " << entry->GetPageID();
549 } else if ((existing_entry != pending_entry_) && pending_entry_ &&
550 (pending_entry_->GetPageID() == -1) &&
551 (pending_entry_->GetURL() == existing_entry->GetURL())) {
552 // Not a new navigation.
553 existing_entry->set_unique_id(pending_entry_->unique_id());
554 DiscardPendingEntry();
555 } else {
556 DCHECK(existing_entry != entry);
557 // The given entry might provide a new URL... e.g., navigating back to a
558 // page in session history could have resulted in a new client redirect.
559 // The given entry might also provide a new title (typically an empty title
560 // to overwrite the existing title).
561 existing_entry->SetURL(entry->GetURL());
562 existing_entry->SetTitle(entry->GetTitle());
563 existing_entry->SetFavIconURL(entry->GetFavIconURL());
564 existing_entry->SetFavIcon(entry->GetFavIcon());
565 existing_entry->SetValidFavIcon(entry->IsValidFavIcon());
566 existing_entry->SetContentState(entry->GetContentState());
567
568 // TODO(brettw) why only copy the security style and no other SSL stuff?
569 existing_entry->ssl().set_security_style(entry->ssl().security_style());
570
571 const int prev_entry_index = last_committed_entry_index_;
572 if (existing_entry == pending_entry_) {
573 DCHECK(pending_entry_index_ != -1);
574 last_committed_entry_index_ = pending_entry_index_;
575 // TODO(pkasting): http://b/1113085 Should this use DiscardPendingEntry()?
576 DiscardPendingEntryInternal();
577 } else {
578 // NOTE: Do not update the unique ID here, as we don't want infobars etc.
579 // to dismiss.
580
581 // The navigation could have been issued by the renderer, so be sure that
582 // we update our current index.
583 last_committed_entry_index_ = existing_entry_index;
584 }
585 IndexOfActiveEntryChanged(prev_entry_index);
586 }
587
588 delete entry;
589 NotifyNavigationEntryCommitted();
initial.commit09911bf2008-07-26 23:55:29590
591 if (alternate_nav_url_fetcher_.get()) {
592 // Because this call may synchronously show an infobar, we do it last, to
593 // make sure all other state is stable and the infobar won't get blown away
594 // by some transition.
595 alternate_nav_url_fetcher_->OnNavigatedToEntry();
596 }
597
[email protected]b33452302008-08-04 19:36:36598 // It is now a safe time to schedule collection for any tab contents of a
599 // different type, because a navigation is necessary to get back to them.
initial.commit09911bf2008-07-26 23:55:29600 int index = GetCurrentEntryIndex();
601 if (index < 0 || GetPendingEntryIndex() != -1)
602 return;
603
604 TabContentsType active_type = GetEntryAtIndex(index)->GetType();
[email protected]b33452302008-08-04 19:36:36605 for (TabContentsMap::iterator i = tab_contents_map_.begin();
606 i != tab_contents_map_.end(); ++i) {
607 if (i->first != active_type)
608 ScheduleTabContentsCollection(i->first);
initial.commit09911bf2008-07-26 23:55:29609 }
610}
611
[email protected]765b35502008-08-21 00:51:20612
613int NavigationController::GetIndexOfEntry(
614 const NavigationEntry* entry) const {
615 const NavigationEntries::const_iterator i(std::find(
616 entries_.begin(),
617 entries_.end(),
618 entry));
619 return (i == entries_.end()) ? -1 : static_cast<int>(i - entries_.begin());
620}
621
622void NavigationController::RemoveLastEntry() {
623 int current_size = static_cast<int>(entries_.size());
624
625 if (current_size > 0) {
626 if (pending_entry_ == entries_[current_size - 1] ||
627 pending_entry_index_ == current_size - 1)
628 DiscardPendingEntryInternal();
629
630 entries_.pop_back();
631
632 if (last_committed_entry_index_ >= current_size - 1)
633 last_committed_entry_index_ = current_size - 2;
634
635 NotifyPrunedEntries();
636 }
637}
638
initial.commit09911bf2008-07-26 23:55:29639void NavigationController::DiscardPendingEntry() {
[email protected]765b35502008-08-21 00:51:20640 DiscardPendingEntryInternal();
initial.commit09911bf2008-07-26 23:55:29641
642 // Synchronize the active_contents_ to the last committed entry.
643 NavigationEntry* last_entry = GetLastCommittedEntry();
644 if (last_entry && last_entry->GetType() != active_contents_->type()) {
645 TabContents* from_contents = active_contents_;
646 from_contents->SetActive(false);
647
648 // Switch back to the previous tab contents.
649 active_contents_ = GetTabContents(last_entry->GetType());
650 DCHECK(active_contents_);
651
652 active_contents_->SetActive(true);
653
654 // If we are transitioning from two types of WebContents, we need to migrate
655 // the download shelf if it is visible. The download shelf may have been
656 // created before the error that caused us to discard the entry.
657 WebContents::MigrateShelfView(from_contents, active_contents_);
658
659 if (from_contents->delegate()) {
660 from_contents->delegate()->ReplaceContents(from_contents,
661 active_contents_);
662 }
663
664 // The entry we just discarded needed a different TabContents type. We no
665 // longer need it but we can't destroy it just yet because the TabContents
666 // is very likely involved in the current stack.
667 DCHECK(from_contents != active_contents_);
668 ScheduleTabContentsCollection(from_contents->type());
669 }
initial.commit09911bf2008-07-26 23:55:29670}
671
672void NavigationController::InsertEntry(NavigationEntry* entry) {
[email protected]765b35502008-08-21 00:51:20673 DCHECK(entry->GetTransitionType() != PageTransition::AUTO_SUBFRAME);
674
675 // Copy the pending entry's unique ID to the committed entry.
676 // I don't know if pending_entry_index_ can be other than -1 here.
677 const NavigationEntry* const pending_entry = (pending_entry_index_ == -1) ?
678 pending_entry_ : entries_[pending_entry_index_].get();
679 if (pending_entry)
680 entry->set_unique_id(pending_entry->unique_id());
681
682 DiscardPendingEntryInternal();
683
684 int current_size = static_cast<int>(entries_.size());
685
686 // Prune any entries which are in front of the current entry.
687 if (current_size > 0) {
688 bool pruned = false;
689 while (last_committed_entry_index_ < (current_size - 1)) {
690 pruned = true;
691 entries_.pop_back();
692 current_size--;
693 }
694 if (pruned) // Only notify if we did prune something.
695 NotifyPrunedEntries();
696 }
697
698 if (entries_.size() >= max_entry_count_)
699 RemoveEntryAtIndex(0);
700
701 entries_.push_back(linked_ptr<NavigationEntry>(entry));
702 last_committed_entry_index_ = static_cast<int>(entries_.size()) - 1;
703
initial.commit09911bf2008-07-26 23:55:29704 active_contents_->NotifyDidNavigate(NAVIGATION_NEW, 0);
705}
706
707void NavigationController::SetWindowID(const SessionID& id) {
708 window_id_ = id;
[email protected]534e54b2008-08-13 15:40:09709 NotificationService::current()->Notify(NOTIFY_TAB_PARENTED,
710 Source<NavigationController>(this),
711 NotificationService::NoDetails());
initial.commit09911bf2008-07-26 23:55:29712}
713
714void NavigationController::NavigateToPendingEntry(bool reload) {
715 TabContents* from_contents = active_contents_;
716
717 // For session history navigations only the pending_entry_index_ is set.
718 if (!pending_entry_) {
719 DCHECK(pending_entry_index_ != -1);
[email protected]765b35502008-08-21 00:51:20720 pending_entry_ = entries_[pending_entry_index_].get();
initial.commit09911bf2008-07-26 23:55:29721 }
722
723 // Reset the security states as any SSL error may have been resolved since we
724 // last visited that page.
[email protected]eb34392b2008-08-19 15:42:20725 pending_entry_->ssl() = NavigationEntry::SSLStatus();
initial.commit09911bf2008-07-26 23:55:29726
727 if (from_contents && from_contents->type() != pending_entry_->GetType())
728 from_contents->SetActive(false);
729
730 HWND parent =
731 from_contents ? GetParent(from_contents->GetContainerHWND()) : 0;
732 TabContents* contents =
733 GetTabContentsCreateIfNecessary(parent, *pending_entry_);
734
735 contents->SetActive(true);
736 active_contents_ = contents;
737
738 if (from_contents && from_contents != contents) {
739 if (from_contents->delegate())
740 from_contents->delegate()->ReplaceContents(from_contents, contents);
741 }
742
[email protected]6cf85902008-08-19 17:38:12743 if (!contents->Navigate(*pending_entry_, reload))
initial.commit09911bf2008-07-26 23:55:29744 DiscardPendingEntry();
initial.commit09911bf2008-07-26 23:55:29745}
746
[email protected]6cf85902008-08-19 17:38:12747void NavigationController::NotifyNavigationEntryCommitted() {
initial.commit09911bf2008-07-26 23:55:29748 // Reset the Alternate Nav URL Fetcher if we're loading some page it doesn't
749 // care about. We must do this before calling Notify() below as that may
750 // result in the creation of a new fetcher.
[email protected]6cf85902008-08-19 17:38:12751 //
752 // TODO(brettw) bug 1324500: this logic should be moved out of the controller!
initial.commit09911bf2008-07-26 23:55:29753 const NavigationEntry* const entry = GetActiveEntry();
754 if (!entry ||
755 (entry->unique_id() != alternate_nav_url_fetcher_entry_unique_id_)) {
756 alternate_nav_url_fetcher_.reset();
757 alternate_nav_url_fetcher_entry_unique_id_ = 0;
758 }
759
760 // TODO(pkasting): http://b/1113079 Probably these explicit notification paths
761 // should be removed, and interested parties should just listen for the
762 // notification below instead.
763 ssl_manager_.NavigationStateChanged();
764 active_contents_->NotifyNavigationStateChanged(
765 TabContents::INVALIDATE_EVERYTHING);
766
[email protected]6cf85902008-08-19 17:38:12767 NotificationService::current()->Notify(NOTIFY_NAV_ENTRY_COMMITTED,
[email protected]534e54b2008-08-13 15:40:09768 Source<NavigationController>(this),
769 NotificationService::NoDetails());
initial.commit09911bf2008-07-26 23:55:29770}
771
772void NavigationController::NotifyPrunedEntries() {
[email protected]534e54b2008-08-13 15:40:09773 NotificationService::current()->Notify(NOTIFY_NAV_LIST_PRUNED,
774 Source<NavigationController>(this),
775 NotificationService::NoDetails());
initial.commit09911bf2008-07-26 23:55:29776}
777
778void NavigationController::IndexOfActiveEntryChanged(
779 int prev_committed_index) {
780 NavigationType nav_type = NAVIGATION_BACK_FORWARD;
781 int relative_navigation_offset =
782 GetLastCommittedEntryIndex() - prev_committed_index;
783 if (relative_navigation_offset == 0) {
784 nav_type = NAVIGATION_REPLACE;
785 }
786 active_contents_->NotifyDidNavigate(nav_type, relative_navigation_offset);
initial.commit09911bf2008-07-26 23:55:29787}
788
789TabContents* NavigationController::GetTabContentsCreateIfNecessary(
790 HWND parent,
791 const NavigationEntry& entry) {
792 TabContents* contents = GetTabContents(entry.GetType());
793 if (!contents) {
794 contents = TabContents::CreateWithType(entry.GetType(), parent, profile_,
795 entry.site_instance());
796 if (!contents->AsWebContents()) {
797 // Update the max page id, otherwise the newly created TabContents may
798 // have reset its max page id resulting in all new navigations. We only
799 // do this for non-WebContents as WebContents takes care of this via its
800 // SiteInstance. If this creation is the result of a restore, WebContents
801 // handles invoking ReservePageIDRange to make sure the renderer's
802 // max_page_id is updated to reflect the restored range of page ids.
803 int32 max_page_id = contents->GetMaxPageID();
804 for (size_t i = 0; i < entries_.size(); ++i) {
805 if (entries_[i]->GetType() == entry.GetType())
806 max_page_id = std::max(max_page_id, entries_[i]->GetPageID());
807 }
808 contents->UpdateMaxPageID(max_page_id);
809 }
810 RegisterTabContents(contents);
811 }
812
813 // We should not be trying to collect this tab contents.
814 DCHECK(tab_contents_collector_map_.find(contents->type()) ==
815 tab_contents_collector_map_.end());
816
817 return contents;
818}
819
820void NavigationController::RegisterTabContents(TabContents* some_contents) {
821 DCHECK(some_contents);
822 TabContentsType t = some_contents->type();
823 TabContents* tc;
824 if ((tc = tab_contents_map_[t]) != some_contents) {
825 if (tc) {
826 NOTREACHED() << "Should not happen. Multiple contents for one type";
827 } else {
828 tab_contents_map_[t] = some_contents;
829 some_contents->set_controller(this);
830 }
831 }
832 if (some_contents->AsDOMUIHost())
833 some_contents->AsDOMUIHost()->AttachMessageHandlers();
834}
835
[email protected]534e54b2008-08-13 15:40:09836void NavigationController::NotifyEntryChangedByPageID(
initial.commit09911bf2008-07-26 23:55:29837 TabContentsType type,
838 SiteInstance *instance,
[email protected]534e54b2008-08-13 15:40:09839 int32 page_id) {
initial.commit09911bf2008-07-26 23:55:29840 int index = GetEntryIndexWithPageID(type, instance, page_id);
841 if (index != -1)
[email protected]765b35502008-08-21 00:51:20842 NotifyEntryChanged(entries_[index].get(), index);
initial.commit09911bf2008-07-26 23:55:29843}
844
845// static
846void NavigationController::DisablePromptOnRepost() {
847 check_for_repost_ = false;
848}
849
850void NavigationController::SetActive(bool is_active) {
851 if (is_active) {
852 if (needs_reload_) {
853 LoadIfNecessary();
854 } else if (load_pending_entry_when_active_) {
855 NavigateToPendingEntry(false);
856 load_pending_entry_when_active_ = false;
857 }
858 }
859}
860
861void NavigationController::LoadIfNecessary() {
862 if (!needs_reload_)
863 return;
864
865 needs_reload_ = false;
866 // Calling Reload() results in ignoring state, and not loading.
867 // Explicitly use NavigateToPendingEntry so that the renderer uses the
868 // cached state.
869 pending_entry_index_ = last_committed_entry_index_;
870 NavigateToPendingEntry(false);
871}
872
[email protected]765b35502008-08-21 00:51:20873void NavigationController::ResetInternal() {
874 // WARNING: this is invoked from the destructor, be sure not to invoke any
875 // virtual methods from this.
876 entries_.clear();
877 DiscardPendingEntryInternal();
878}
879
[email protected]534e54b2008-08-13 15:40:09880void NavigationController::NotifyEntryChanged(const NavigationEntry* entry,
881 int index) {
882 EntryChangedDetails det;
883 det.changed_entry = entry;
884 det.index = index;
885 NotificationService::current()->Notify(NOTIFY_NAV_ENTRY_CHANGED,
886 Source<NavigationController>(this),
887 Details<EntryChangedDetails>(&det));
initial.commit09911bf2008-07-26 23:55:29888}
889
[email protected]765b35502008-08-21 00:51:20890void NavigationController::RemoveEntryAtIndex(int index) {
891 // TODO(brettw) this is only called to remove the first one when we've got
892 // too many entries. It should probably be more specific for this case.
893 if (index >= static_cast<int>(entries_.size()) ||
894 index == pending_entry_index_ || index == last_committed_entry_index_) {
895 NOTREACHED();
896 return;
897 }
898
899 entries_.erase(entries_.begin() + index);
900
901 if (last_committed_entry_index_ >= index) {
902 if (!entries_.empty())
903 last_committed_entry_index_--;
904 else
905 last_committed_entry_index_ = -1;
906 }
907
908 // TODO(brettw) bug 1324021: we probably need some notification here so the
909 // session service can stay in sync.
910}
911
initial.commit09911bf2008-07-26 23:55:29912int NavigationController::GetMaxPageID() const {
913 return active_contents_->GetMaxPageID();
914}
915
916NavigationController* NavigationController::Clone(HWND parent_hwnd) {
917 NavigationController* nc = new NavigationController(NULL, profile_);
918
919 if (GetEntryCount() == 0)
920 return nc;
921
922 nc->needs_reload_ = true;
923
924 nc->entries_.reserve(entries_.size());
[email protected]765b35502008-08-21 00:51:20925 for (int i = 0, c = GetEntryCount(); i < c; ++i) {
926 nc->entries_.push_back(linked_ptr<NavigationEntry>(
927 new NavigationEntry(*GetEntryAtIndex(i))));
928 }
initial.commit09911bf2008-07-26 23:55:29929
930 nc->FinishRestore(parent_hwnd, last_committed_entry_index_);
931
932 return nc;
933}
934
935void NavigationController::ScheduleTabContentsCollection(TabContentsType t) {
936 TabContentsCollectorMap::const_iterator i =
937 tab_contents_collector_map_.find(t);
938
939 // The tab contents is already scheduled for collection.
940 if (i != tab_contents_collector_map_.end())
941 return;
942
943 // If we currently don't have a TabContents for t, skip.
944 if (tab_contents_map_.find(t) == tab_contents_map_.end())
945 return;
946
947 // Create a collector and schedule it.
948 TabContentsCollector* tcc = new TabContentsCollector(this, t);
949 tab_contents_collector_map_[t] = tcc;
950 MessageLoop::current()->PostTask(FROM_HERE, tcc);
951}
952
953void NavigationController::CancelTabContentsCollection(TabContentsType t) {
954 TabContentsCollectorMap::iterator i = tab_contents_collector_map_.find(t);
955
956 if (i != tab_contents_collector_map_.end()) {
957 DCHECK(i->second);
958 i->second->Cancel();
959 tab_contents_collector_map_.erase(i);
960 }
961}
962
963void NavigationController::FinishRestore(HWND parent_hwnd, int selected_index) {
964 DCHECK(selected_index >= 0 && selected_index < GetEntryCount());
965 ConfigureEntriesForRestore(&entries_);
966
967 set_max_restored_page_id(GetEntryCount());
968
969 last_committed_entry_index_ = selected_index;
970
971 // Callers assume we have an active_contents after restoring, so set it now.
972 active_contents_ =
973 GetTabContentsCreateIfNecessary(parent_hwnd, *entries_[selected_index]);
974}
[email protected]765b35502008-08-21 00:51:20975
976void NavigationController::DiscardPendingEntryInternal() {
977 if (pending_entry_index_ == -1)
978 delete pending_entry_;
979 pending_entry_ = NULL;
980 pending_entry_index_ = -1;
981}
982
983int NavigationController::GetEntryIndexWithPageID(
984 TabContentsType type, SiteInstance* instance, int32 page_id) const {
985 // The instance should only be specified for contents displaying web pages.
986 // TODO(evanm): checking against NEW_TAB_UI and HTML_DLG here is lame.
987 // It'd be nice for DomUIHost to just use SiteInstances for keeping content
988 // separated properly.
989 if (type != TAB_CONTENTS_WEB &&
990 type != TAB_CONTENTS_NEW_TAB_UI &&
991 type != TAB_CONTENTS_ABOUT_UI &&
992 type != TAB_CONTENTS_HTML_DIALOG &&
993 type != TAB_CONTENTS_VIEW_SOURCE &&
994 type != TAB_CONTENTS_DEBUGGER)
995 DCHECK(instance == NULL);
996
997 for (int i = static_cast<int>(entries_.size()) - 1; i >= 0; --i) {
998 if ((entries_[i]->GetType() == type) &&
999 (entries_[i]->site_instance() == instance) &&
1000 (entries_[i]->GetPageID() == page_id))
1001 return i;
1002 }
1003 return -1;
1004}