blob: e35ee59ec6f8479bccc06503faf3b6796e195ae8 [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
[email protected]c0993872008-08-21 19:59:44194 DiscardPendingEntryInternal();
195
initial.commit09911bf2008-07-26 23:55:29196 profile_->UnregisterNavigationController(this);
[email protected]534e54b2008-08-13 15:40:09197 NotificationService::current()->Notify(NOTIFY_TAB_CLOSED,
198 Source<NavigationController>(this),
199 NotificationService::NoDetails());
initial.commit09911bf2008-07-26 23:55:29200}
201
202TabContents* NavigationController::GetTabContents(TabContentsType t) {
203 // Make sure the TabContents is no longer scheduled for collection.
204 CancelTabContentsCollection(t);
205 return tab_contents_map_[t];
206}
207
initial.commit09911bf2008-07-26 23:55:29208void NavigationController::Reload() {
209 // TODO(pkasting): http://b/1113085 Should this use DiscardPendingEntry()?
210 DiscardPendingEntryInternal();
211 int current_index = GetCurrentEntryIndex();
212 if (check_for_repost_ && current_index != -1 &&
213 GetEntryAtIndex(current_index)->HasPostData() &&
214 active_contents_->AsWebContents() &&
215 !active_contents_->AsWebContents()->showing_repost_interstitial()) {
216 // The user is asking to reload a page with POST data and we're not showing
217 // the POST interstitial. Prompt to make sure they really want to do this.
218 // If they do, RepostFormWarningDialog calls us back with
219 // ReloadDontCheckForRepost.
220 active_contents_->Activate();
221 RepostFormWarningDialog::RunRepostFormWarningDialog(this);
222 } else {
[email protected]765b35502008-08-21 00:51:20223 // Base the navigation on where we are now...
224 int current_index = GetCurrentEntryIndex();
225
226 // If we are no where, then we can't reload. TODO(darin): We should add a
227 // CanReload method.
228 if (current_index == -1)
229 return;
230
231 // TODO(pkasting): http://b/1113085 Should this use DiscardPendingEntry()?
232 DiscardPendingEntryInternal();
233
234 pending_entry_index_ = current_index;
235 entries_[pending_entry_index_]->SetTransitionType(PageTransition::RELOAD);
236 NavigateToPendingEntry(true);
initial.commit09911bf2008-07-26 23:55:29237 }
238}
239
[email protected]765b35502008-08-21 00:51:20240NavigationEntry* NavigationController::GetEntryWithPageID(
241 TabContentsType type, SiteInstance* instance, int32 page_id) const {
242 int index = GetEntryIndexWithPageID(type, instance, page_id);
243 return (index != -1) ? entries_[index].get() : NULL;
244}
245
246void NavigationController::LoadEntry(NavigationEntry* entry) {
247 // When navigating to a new page, we don't know for sure if we will actually
248 // end up leaving the current page. The new page load could for example
249 // result in a download or a 'no content' response (e.g., a mailto: URL).
250
251 // TODO(pkasting): http://b/1113085 Should this use DiscardPendingEntry()?
252 DiscardPendingEntryInternal();
253 pending_entry_ = entry;
254 NotificationService::current()->Notify(
255 NOTIFY_NAV_ENTRY_PENDING,
256 Source<NavigationController>(this),
257 NotificationService::NoDetails());
258 NavigateToPendingEntry(false);
259}
260
261/* static */
262void NavigationController::SetContentStateIfEmpty(
263 NavigationEntry* entry) {
264 if (entry->GetContentState().empty() &&
265 (entry->GetType() == TAB_CONTENTS_WEB ||
266 entry->GetType() == TAB_CONTENTS_NEW_TAB_UI ||
267 entry->GetType() == TAB_CONTENTS_ABOUT_UI ||
268 entry->GetType() == TAB_CONTENTS_HTML_DIALOG)) {
269 // The state is empty and the url will be rendered by WebKit. An empty
270 // state is treated as a new navigation by WebKit, which would mean
271 // losing the navigation entries and generating a new navigation
272 // entry after this one. We don't want that. To avoid this we create
273 // a valid state which WebKit will not treat as a new navigation.
274 entry->SetContentState(
275 webkit_glue::CreateHistoryStateForURL(entry->GetURL()));
276 }
277}
278
279NavigationEntry* NavigationController::GetActiveEntry() const {
280 NavigationEntry* entry = pending_entry_;
281 if (!entry)
282 entry = GetLastCommittedEntry();
283 return entry;
284}
285
286int NavigationController::GetCurrentEntryIndex() const {
287 if (pending_entry_index_ != -1)
288 return pending_entry_index_;
289 return last_committed_entry_index_;
290}
291
292NavigationEntry* NavigationController::GetLastCommittedEntry() const {
293 if (last_committed_entry_index_ == -1)
294 return NULL;
295 return entries_[last_committed_entry_index_].get();
296}
297
298NavigationEntry* NavigationController::GetEntryAtOffset(int offset) const {
299 int index = last_committed_entry_index_ + offset;
300 if (index < 0 || index >= GetEntryCount())
301 return NULL;
302
303 return entries_[index].get();
304}
305
306bool NavigationController::CanStop() const {
307 // TODO(darin): do we have something pending that we can stop?
308 return false;
309}
310
311bool NavigationController::CanGoBack() const {
312 return entries_.size() > 1 && GetCurrentEntryIndex() > 0;
313}
314
315bool NavigationController::CanGoForward() const {
316 int index = GetCurrentEntryIndex();
317 return index >= 0 && index < (static_cast<int>(entries_.size()) - 1);
318}
319
320void NavigationController::GoBack() {
321 if (!CanGoBack()) {
322 NOTREACHED();
323 return;
324 }
325
326 // Base the navigation on where we are now...
327 int current_index = GetCurrentEntryIndex();
328
329 DiscardPendingEntry();
330
331 pending_entry_index_ = current_index - 1;
332 NavigateToPendingEntry(false);
333}
334
335void NavigationController::GoForward() {
336 if (!CanGoForward()) {
337 NOTREACHED();
338 return;
339 }
340
341 // Base the navigation on where we are now...
342 int current_index = GetCurrentEntryIndex();
343
344 DiscardPendingEntry();
345
346 pending_entry_index_ = current_index + 1;
347 NavigateToPendingEntry(false);
348}
349
350void NavigationController::GoToIndex(int index) {
351 if (index < 0 || index >= static_cast<int>(entries_.size())) {
352 NOTREACHED();
353 return;
354 }
355
356 DiscardPendingEntry();
357
358 pending_entry_index_ = index;
359 NavigateToPendingEntry(false);
360}
361
362void NavigationController::GoToOffset(int offset) {
363 int index = last_committed_entry_index_ + offset;
364 if (index < 0 || index >= GetEntryCount())
365 return;
366
367 GoToIndex(index);
368}
369
370void NavigationController::Stop() {
371 DCHECK(CanStop());
372
373 // TODO(darin): we probably want to just call Stop on the active tab
374 // contents, but should we also call DiscardPendingEntry?
375 NOTREACHED() << "implement me";
376}
377
initial.commit09911bf2008-07-26 23:55:29378void NavigationController::ReloadDontCheckForRepost() {
[email protected]765b35502008-08-21 00:51:20379 Reload();
initial.commit09911bf2008-07-26 23:55:29380}
381
382void NavigationController::Destroy() {
[email protected]b33452302008-08-04 19:36:36383 // Close all tab contents owned by this controller. We make a list on the
384 // stack because they are removed from the map as they are Destroyed
initial.commit09911bf2008-07-26 23:55:29385 // (invalidating the iterators), which may or may not occur synchronously.
[email protected]b33452302008-08-04 19:36:36386 // We also keep track of any NULL entries in the map so that we can clean
387 // them out.
initial.commit09911bf2008-07-26 23:55:29388 std::list<TabContents*> tabs_to_destroy;
[email protected]b33452302008-08-04 19:36:36389 std::list<TabContentsType> tab_types_to_erase;
initial.commit09911bf2008-07-26 23:55:29390 for (TabContentsMap::iterator i = tab_contents_map_.begin();
391 i != tab_contents_map_.end(); ++i) {
[email protected]b33452302008-08-04 19:36:36392 if (i->second)
393 tabs_to_destroy.push_back(i->second);
394 else
395 tab_types_to_erase.push_back(i->first);
396 }
397
398 // Clean out all NULL entries in the map so that we know empty map means all
399 // tabs destroyed. This is needed since TabContentsWasDestroyed() won't get
400 // called for types that are in our map with a NULL contents. (We don't do
401 // this by iterating over TAB_CONTENTS_NUM_TYPES because some tests create
402 // additional types.)
403 for (std::list<TabContentsType>::iterator i = tab_types_to_erase.begin();
404 i != tab_types_to_erase.end(); ++i) {
405 TabContentsMap::iterator map_iterator = tab_contents_map_.find(*i);
406 if (map_iterator != tab_contents_map_.end()) {
407 DCHECK(!map_iterator->second);
408 tab_contents_map_.erase(map_iterator);
409 }
initial.commit09911bf2008-07-26 23:55:29410 }
411
412 // Cancel all the TabContentsCollectors.
413 for (TabContentsCollectorMap::iterator i =
414 tab_contents_collector_map_.begin();
415 i != tab_contents_collector_map_.end(); ++i) {
416 DCHECK(i->second);
417 i->second->Cancel();
418 }
[email protected]ccfc1a7b2008-08-14 16:26:20419 tab_contents_collector_map_.clear();
420
initial.commit09911bf2008-07-26 23:55:29421
422 // Finally destroy all the tab contents.
423 for (std::list<TabContents*>::iterator i = tabs_to_destroy.begin();
424 i != tabs_to_destroy.end(); ++i) {
425 (*i)->Destroy();
426 }
427 // We are deleted at this point.
428}
429
430void NavigationController::TabContentsWasDestroyed(TabContentsType type) {
431 TabContentsMap::iterator i = tab_contents_map_.find(type);
432 DCHECK(i != tab_contents_map_.end());
433 tab_contents_map_.erase(i);
434
435 // Make sure we cancel any collector for that TabContents.
[email protected]ccfc1a7b2008-08-14 16:26:20436 CancelTabContentsCollection(type);
initial.commit09911bf2008-07-26 23:55:29437
438 // If that was the last tab to be destroyed, delete ourselves.
439 if (tab_contents_map_.empty())
440 delete this;
441}
442
443NavigationEntry* NavigationController::CreateNavigationEntry(
444 const GURL& url, PageTransition::Type transition) {
445 GURL real_url = url;
446 TabContentsType type;
447
448 // If the active contents supports |url|, use it.
449 // Note: in both cases, we give TabContents a chance to rewrite the URL.
450 TabContents* active = active_contents();
451 if (active && active->SupportsURL(&real_url))
452 type = active->type();
453 else
454 type = TabContents::TypeForURL(&real_url);
455
456 NavigationEntry* entry = new NavigationEntry(type, NULL, -1, real_url,
457 std::wstring(), transition);
458 entry->SetDisplayURL(url);
459 entry->SetUserTypedURL(url);
460 if (url.SchemeIsFile()) {
461 entry->SetTitle(file_util::GetFilenameFromPath(UTF8ToWide(url.host() +
462 url.path())));
463 }
464 return entry;
465}
466
467void NavigationController::LoadURL(const GURL& url,
468 PageTransition::Type transition) {
469 // The user initiated a load, we don't need to reload anymore.
470 needs_reload_ = false;
471
472 NavigationEntry* entry = CreateNavigationEntry(url, transition);
473
474 LoadEntry(entry);
475}
476
477void NavigationController::LoadURLLazily(const GURL& url,
478 PageTransition::Type type,
479 const std::wstring& title,
480 SkBitmap* icon) {
481 NavigationEntry* entry = CreateNavigationEntry(url, type);
482 entry->SetTitle(title);
483 if (icon)
484 entry->SetFavIcon(*icon);
485
486 // TODO(pkasting): http://b/1113085 Should this use DiscardPendingEntry()?
487 DiscardPendingEntryInternal();
488 pending_entry_ = entry;
489 load_pending_entry_when_active_ = true;
490}
491
492bool NavigationController::LoadingURLLazily() {
493 return load_pending_entry_when_active_;
494}
495
496const std::wstring& NavigationController::GetLazyTitle() const {
497 if (pending_entry_)
498 return pending_entry_->GetTitle();
499 else
500 return EmptyWString();
501}
502
503const SkBitmap& NavigationController::GetLazyFavIcon() const {
504 if (pending_entry_) {
505 return pending_entry_->GetFavIcon();
506 } else {
507 ResourceBundle &rb = ResourceBundle::GetSharedInstance();
508 return *rb.GetBitmapNamed(IDR_DEFAULT_FAVICON);
509 }
510}
511
initial.commit09911bf2008-07-26 23:55:29512void NavigationController::SetAlternateNavURLFetcher(
513 AlternateNavURLFetcher* alternate_nav_url_fetcher) {
514 DCHECK(!alternate_nav_url_fetcher_.get());
515 DCHECK(pending_entry_);
516 alternate_nav_url_fetcher_.reset(alternate_nav_url_fetcher);
517 alternate_nav_url_fetcher_entry_unique_id_ = pending_entry_->unique_id();
518}
519
520void NavigationController::DidNavigateToEntry(NavigationEntry* entry) {
521 DCHECK(active_contents_);
522 DCHECK(entry->GetType() == active_contents_->type());
523
[email protected]765b35502008-08-21 00:51:20524 SetContentStateIfEmpty(entry);
525
526 entry->set_restored(false);
527
528 // If the entry is that of a page with PageID larger than any this Tab has
529 // seen before, then consider it a new navigation. Note that if the entry
530 // has a SiteInstance, it should be the same as the SiteInstance of the
531 // active WebContents, because we have just navigated to it.
532 if (entry->GetPageID() > GetMaxPageID()) {
533 InsertEntry(entry);
534 NotifyNavigationEntryCommitted();
535 return;
536 }
537
538 // Otherwise, we just need to update an existing entry with matching PageID.
539 // If the existing entry corresponds to the entry which is pending, then we
540 // must update the current entry index accordingly. When navigating to the
541 // same URL, a new PageID is not created.
542
543 int existing_entry_index = GetEntryIndexWithPageID(entry->GetType(),
544 entry->site_instance(),
545 entry->GetPageID());
546 NavigationEntry* existing_entry = (existing_entry_index != -1) ?
547 entries_[existing_entry_index].get() : NULL;
548 if (!existing_entry) {
549 // No existing entry, then simply ignore this navigation!
550 DLOG(WARNING) << "ignoring navigation for page: " << entry->GetPageID();
551 } else if ((existing_entry != pending_entry_) && pending_entry_ &&
552 (pending_entry_->GetPageID() == -1) &&
553 (pending_entry_->GetURL() == existing_entry->GetURL())) {
554 // Not a new navigation.
555 existing_entry->set_unique_id(pending_entry_->unique_id());
556 DiscardPendingEntry();
557 } else {
558 DCHECK(existing_entry != entry);
559 // The given entry might provide a new URL... e.g., navigating back to a
560 // page in session history could have resulted in a new client redirect.
561 // The given entry might also provide a new title (typically an empty title
562 // to overwrite the existing title).
563 existing_entry->SetURL(entry->GetURL());
564 existing_entry->SetTitle(entry->GetTitle());
565 existing_entry->SetFavIconURL(entry->GetFavIconURL());
566 existing_entry->SetFavIcon(entry->GetFavIcon());
567 existing_entry->SetValidFavIcon(entry->IsValidFavIcon());
568 existing_entry->SetContentState(entry->GetContentState());
569
570 // TODO(brettw) why only copy the security style and no other SSL stuff?
571 existing_entry->ssl().set_security_style(entry->ssl().security_style());
572
573 const int prev_entry_index = last_committed_entry_index_;
574 if (existing_entry == pending_entry_) {
575 DCHECK(pending_entry_index_ != -1);
576 last_committed_entry_index_ = pending_entry_index_;
577 // TODO(pkasting): http://b/1113085 Should this use DiscardPendingEntry()?
578 DiscardPendingEntryInternal();
579 } else {
580 // NOTE: Do not update the unique ID here, as we don't want infobars etc.
581 // to dismiss.
582
583 // The navigation could have been issued by the renderer, so be sure that
584 // we update our current index.
585 last_committed_entry_index_ = existing_entry_index;
586 }
587 IndexOfActiveEntryChanged(prev_entry_index);
588 }
589
590 delete entry;
591 NotifyNavigationEntryCommitted();
initial.commit09911bf2008-07-26 23:55:29592
593 if (alternate_nav_url_fetcher_.get()) {
594 // Because this call may synchronously show an infobar, we do it last, to
595 // make sure all other state is stable and the infobar won't get blown away
596 // by some transition.
597 alternate_nav_url_fetcher_->OnNavigatedToEntry();
598 }
599
[email protected]b33452302008-08-04 19:36:36600 // It is now a safe time to schedule collection for any tab contents of a
601 // different type, because a navigation is necessary to get back to them.
initial.commit09911bf2008-07-26 23:55:29602 int index = GetCurrentEntryIndex();
603 if (index < 0 || GetPendingEntryIndex() != -1)
604 return;
605
606 TabContentsType active_type = GetEntryAtIndex(index)->GetType();
[email protected]b33452302008-08-04 19:36:36607 for (TabContentsMap::iterator i = tab_contents_map_.begin();
608 i != tab_contents_map_.end(); ++i) {
609 if (i->first != active_type)
610 ScheduleTabContentsCollection(i->first);
initial.commit09911bf2008-07-26 23:55:29611 }
612}
613
[email protected]765b35502008-08-21 00:51:20614
615int NavigationController::GetIndexOfEntry(
616 const NavigationEntry* entry) const {
617 const NavigationEntries::const_iterator i(std::find(
618 entries_.begin(),
619 entries_.end(),
620 entry));
621 return (i == entries_.end()) ? -1 : static_cast<int>(i - entries_.begin());
622}
623
624void NavigationController::RemoveLastEntry() {
625 int current_size = static_cast<int>(entries_.size());
626
627 if (current_size > 0) {
628 if (pending_entry_ == entries_[current_size - 1] ||
629 pending_entry_index_ == current_size - 1)
630 DiscardPendingEntryInternal();
631
632 entries_.pop_back();
633
634 if (last_committed_entry_index_ >= current_size - 1)
635 last_committed_entry_index_ = current_size - 2;
636
637 NotifyPrunedEntries();
638 }
639}
640
initial.commit09911bf2008-07-26 23:55:29641void NavigationController::DiscardPendingEntry() {
[email protected]765b35502008-08-21 00:51:20642 DiscardPendingEntryInternal();
initial.commit09911bf2008-07-26 23:55:29643
644 // Synchronize the active_contents_ to the last committed entry.
645 NavigationEntry* last_entry = GetLastCommittedEntry();
646 if (last_entry && last_entry->GetType() != active_contents_->type()) {
647 TabContents* from_contents = active_contents_;
648 from_contents->SetActive(false);
649
650 // Switch back to the previous tab contents.
651 active_contents_ = GetTabContents(last_entry->GetType());
652 DCHECK(active_contents_);
653
654 active_contents_->SetActive(true);
655
656 // If we are transitioning from two types of WebContents, we need to migrate
657 // the download shelf if it is visible. The download shelf may have been
658 // created before the error that caused us to discard the entry.
659 WebContents::MigrateShelfView(from_contents, active_contents_);
660
661 if (from_contents->delegate()) {
662 from_contents->delegate()->ReplaceContents(from_contents,
663 active_contents_);
664 }
665
666 // The entry we just discarded needed a different TabContents type. We no
667 // longer need it but we can't destroy it just yet because the TabContents
668 // is very likely involved in the current stack.
669 DCHECK(from_contents != active_contents_);
670 ScheduleTabContentsCollection(from_contents->type());
671 }
initial.commit09911bf2008-07-26 23:55:29672}
673
674void NavigationController::InsertEntry(NavigationEntry* entry) {
[email protected]765b35502008-08-21 00:51:20675 DCHECK(entry->GetTransitionType() != PageTransition::AUTO_SUBFRAME);
676
677 // Copy the pending entry's unique ID to the committed entry.
678 // I don't know if pending_entry_index_ can be other than -1 here.
679 const NavigationEntry* const pending_entry = (pending_entry_index_ == -1) ?
680 pending_entry_ : entries_[pending_entry_index_].get();
681 if (pending_entry)
682 entry->set_unique_id(pending_entry->unique_id());
683
684 DiscardPendingEntryInternal();
685
686 int current_size = static_cast<int>(entries_.size());
687
688 // Prune any entries which are in front of the current entry.
689 if (current_size > 0) {
690 bool pruned = false;
691 while (last_committed_entry_index_ < (current_size - 1)) {
692 pruned = true;
693 entries_.pop_back();
694 current_size--;
695 }
696 if (pruned) // Only notify if we did prune something.
697 NotifyPrunedEntries();
698 }
699
700 if (entries_.size() >= max_entry_count_)
701 RemoveEntryAtIndex(0);
702
703 entries_.push_back(linked_ptr<NavigationEntry>(entry));
704 last_committed_entry_index_ = static_cast<int>(entries_.size()) - 1;
705
initial.commit09911bf2008-07-26 23:55:29706 active_contents_->NotifyDidNavigate(NAVIGATION_NEW, 0);
707}
708
709void NavigationController::SetWindowID(const SessionID& id) {
710 window_id_ = id;
[email protected]534e54b2008-08-13 15:40:09711 NotificationService::current()->Notify(NOTIFY_TAB_PARENTED,
712 Source<NavigationController>(this),
713 NotificationService::NoDetails());
initial.commit09911bf2008-07-26 23:55:29714}
715
716void NavigationController::NavigateToPendingEntry(bool reload) {
717 TabContents* from_contents = active_contents_;
718
719 // For session history navigations only the pending_entry_index_ is set.
720 if (!pending_entry_) {
721 DCHECK(pending_entry_index_ != -1);
[email protected]765b35502008-08-21 00:51:20722 pending_entry_ = entries_[pending_entry_index_].get();
initial.commit09911bf2008-07-26 23:55:29723 }
724
725 // Reset the security states as any SSL error may have been resolved since we
726 // last visited that page.
[email protected]eb34392b2008-08-19 15:42:20727 pending_entry_->ssl() = NavigationEntry::SSLStatus();
initial.commit09911bf2008-07-26 23:55:29728
729 if (from_contents && from_contents->type() != pending_entry_->GetType())
730 from_contents->SetActive(false);
731
732 HWND parent =
733 from_contents ? GetParent(from_contents->GetContainerHWND()) : 0;
734 TabContents* contents =
735 GetTabContentsCreateIfNecessary(parent, *pending_entry_);
736
737 contents->SetActive(true);
738 active_contents_ = contents;
739
740 if (from_contents && from_contents != contents) {
741 if (from_contents->delegate())
742 from_contents->delegate()->ReplaceContents(from_contents, contents);
743 }
744
[email protected]6cf85902008-08-19 17:38:12745 if (!contents->Navigate(*pending_entry_, reload))
initial.commit09911bf2008-07-26 23:55:29746 DiscardPendingEntry();
initial.commit09911bf2008-07-26 23:55:29747}
748
[email protected]6cf85902008-08-19 17:38:12749void NavigationController::NotifyNavigationEntryCommitted() {
initial.commit09911bf2008-07-26 23:55:29750 // Reset the Alternate Nav URL Fetcher if we're loading some page it doesn't
751 // care about. We must do this before calling Notify() below as that may
752 // result in the creation of a new fetcher.
[email protected]6cf85902008-08-19 17:38:12753 //
754 // TODO(brettw) bug 1324500: this logic should be moved out of the controller!
initial.commit09911bf2008-07-26 23:55:29755 const NavigationEntry* const entry = GetActiveEntry();
756 if (!entry ||
757 (entry->unique_id() != alternate_nav_url_fetcher_entry_unique_id_)) {
758 alternate_nav_url_fetcher_.reset();
759 alternate_nav_url_fetcher_entry_unique_id_ = 0;
760 }
761
762 // TODO(pkasting): http://b/1113079 Probably these explicit notification paths
763 // should be removed, and interested parties should just listen for the
764 // notification below instead.
765 ssl_manager_.NavigationStateChanged();
766 active_contents_->NotifyNavigationStateChanged(
767 TabContents::INVALIDATE_EVERYTHING);
768
[email protected]6cf85902008-08-19 17:38:12769 NotificationService::current()->Notify(NOTIFY_NAV_ENTRY_COMMITTED,
[email protected]534e54b2008-08-13 15:40:09770 Source<NavigationController>(this),
771 NotificationService::NoDetails());
initial.commit09911bf2008-07-26 23:55:29772}
773
774void NavigationController::NotifyPrunedEntries() {
[email protected]534e54b2008-08-13 15:40:09775 NotificationService::current()->Notify(NOTIFY_NAV_LIST_PRUNED,
776 Source<NavigationController>(this),
777 NotificationService::NoDetails());
initial.commit09911bf2008-07-26 23:55:29778}
779
780void NavigationController::IndexOfActiveEntryChanged(
781 int prev_committed_index) {
782 NavigationType nav_type = NAVIGATION_BACK_FORWARD;
783 int relative_navigation_offset =
784 GetLastCommittedEntryIndex() - prev_committed_index;
785 if (relative_navigation_offset == 0) {
786 nav_type = NAVIGATION_REPLACE;
787 }
788 active_contents_->NotifyDidNavigate(nav_type, relative_navigation_offset);
initial.commit09911bf2008-07-26 23:55:29789}
790
791TabContents* NavigationController::GetTabContentsCreateIfNecessary(
792 HWND parent,
793 const NavigationEntry& entry) {
794 TabContents* contents = GetTabContents(entry.GetType());
795 if (!contents) {
796 contents = TabContents::CreateWithType(entry.GetType(), parent, profile_,
797 entry.site_instance());
798 if (!contents->AsWebContents()) {
799 // Update the max page id, otherwise the newly created TabContents may
800 // have reset its max page id resulting in all new navigations. We only
801 // do this for non-WebContents as WebContents takes care of this via its
802 // SiteInstance. If this creation is the result of a restore, WebContents
803 // handles invoking ReservePageIDRange to make sure the renderer's
804 // max_page_id is updated to reflect the restored range of page ids.
805 int32 max_page_id = contents->GetMaxPageID();
806 for (size_t i = 0; i < entries_.size(); ++i) {
807 if (entries_[i]->GetType() == entry.GetType())
808 max_page_id = std::max(max_page_id, entries_[i]->GetPageID());
809 }
810 contents->UpdateMaxPageID(max_page_id);
811 }
812 RegisterTabContents(contents);
813 }
814
815 // We should not be trying to collect this tab contents.
816 DCHECK(tab_contents_collector_map_.find(contents->type()) ==
817 tab_contents_collector_map_.end());
818
819 return contents;
820}
821
822void NavigationController::RegisterTabContents(TabContents* some_contents) {
823 DCHECK(some_contents);
824 TabContentsType t = some_contents->type();
825 TabContents* tc;
826 if ((tc = tab_contents_map_[t]) != some_contents) {
827 if (tc) {
828 NOTREACHED() << "Should not happen. Multiple contents for one type";
829 } else {
830 tab_contents_map_[t] = some_contents;
831 some_contents->set_controller(this);
832 }
833 }
834 if (some_contents->AsDOMUIHost())
835 some_contents->AsDOMUIHost()->AttachMessageHandlers();
836}
837
[email protected]534e54b2008-08-13 15:40:09838void NavigationController::NotifyEntryChangedByPageID(
initial.commit09911bf2008-07-26 23:55:29839 TabContentsType type,
840 SiteInstance *instance,
[email protected]534e54b2008-08-13 15:40:09841 int32 page_id) {
initial.commit09911bf2008-07-26 23:55:29842 int index = GetEntryIndexWithPageID(type, instance, page_id);
843 if (index != -1)
[email protected]765b35502008-08-21 00:51:20844 NotifyEntryChanged(entries_[index].get(), index);
initial.commit09911bf2008-07-26 23:55:29845}
846
847// static
848void NavigationController::DisablePromptOnRepost() {
849 check_for_repost_ = false;
850}
851
852void NavigationController::SetActive(bool is_active) {
853 if (is_active) {
854 if (needs_reload_) {
855 LoadIfNecessary();
856 } else if (load_pending_entry_when_active_) {
857 NavigateToPendingEntry(false);
858 load_pending_entry_when_active_ = false;
859 }
860 }
861}
862
863void NavigationController::LoadIfNecessary() {
864 if (!needs_reload_)
865 return;
866
867 needs_reload_ = false;
868 // Calling Reload() results in ignoring state, and not loading.
869 // Explicitly use NavigateToPendingEntry so that the renderer uses the
870 // cached state.
871 pending_entry_index_ = last_committed_entry_index_;
872 NavigateToPendingEntry(false);
873}
874
[email protected]765b35502008-08-21 00:51:20875void NavigationController::ResetInternal() {
876 // WARNING: this is invoked from the destructor, be sure not to invoke any
877 // virtual methods from this.
878 entries_.clear();
879 DiscardPendingEntryInternal();
880}
881
[email protected]534e54b2008-08-13 15:40:09882void NavigationController::NotifyEntryChanged(const NavigationEntry* entry,
883 int index) {
884 EntryChangedDetails det;
885 det.changed_entry = entry;
886 det.index = index;
887 NotificationService::current()->Notify(NOTIFY_NAV_ENTRY_CHANGED,
888 Source<NavigationController>(this),
889 Details<EntryChangedDetails>(&det));
initial.commit09911bf2008-07-26 23:55:29890}
891
[email protected]765b35502008-08-21 00:51:20892void NavigationController::RemoveEntryAtIndex(int index) {
893 // TODO(brettw) this is only called to remove the first one when we've got
894 // too many entries. It should probably be more specific for this case.
895 if (index >= static_cast<int>(entries_.size()) ||
896 index == pending_entry_index_ || index == last_committed_entry_index_) {
897 NOTREACHED();
898 return;
899 }
900
901 entries_.erase(entries_.begin() + index);
902
903 if (last_committed_entry_index_ >= index) {
904 if (!entries_.empty())
905 last_committed_entry_index_--;
906 else
907 last_committed_entry_index_ = -1;
908 }
909
910 // TODO(brettw) bug 1324021: we probably need some notification here so the
911 // session service can stay in sync.
912}
913
initial.commit09911bf2008-07-26 23:55:29914int NavigationController::GetMaxPageID() const {
915 return active_contents_->GetMaxPageID();
916}
917
918NavigationController* NavigationController::Clone(HWND parent_hwnd) {
919 NavigationController* nc = new NavigationController(NULL, profile_);
920
921 if (GetEntryCount() == 0)
922 return nc;
923
924 nc->needs_reload_ = true;
925
926 nc->entries_.reserve(entries_.size());
[email protected]765b35502008-08-21 00:51:20927 for (int i = 0, c = GetEntryCount(); i < c; ++i) {
928 nc->entries_.push_back(linked_ptr<NavigationEntry>(
929 new NavigationEntry(*GetEntryAtIndex(i))));
930 }
initial.commit09911bf2008-07-26 23:55:29931
932 nc->FinishRestore(parent_hwnd, last_committed_entry_index_);
933
934 return nc;
935}
936
937void NavigationController::ScheduleTabContentsCollection(TabContentsType t) {
938 TabContentsCollectorMap::const_iterator i =
939 tab_contents_collector_map_.find(t);
940
941 // The tab contents is already scheduled for collection.
942 if (i != tab_contents_collector_map_.end())
943 return;
944
945 // If we currently don't have a TabContents for t, skip.
946 if (tab_contents_map_.find(t) == tab_contents_map_.end())
947 return;
948
949 // Create a collector and schedule it.
950 TabContentsCollector* tcc = new TabContentsCollector(this, t);
951 tab_contents_collector_map_[t] = tcc;
952 MessageLoop::current()->PostTask(FROM_HERE, tcc);
953}
954
955void NavigationController::CancelTabContentsCollection(TabContentsType t) {
956 TabContentsCollectorMap::iterator i = tab_contents_collector_map_.find(t);
957
958 if (i != tab_contents_collector_map_.end()) {
959 DCHECK(i->second);
960 i->second->Cancel();
961 tab_contents_collector_map_.erase(i);
962 }
963}
964
965void NavigationController::FinishRestore(HWND parent_hwnd, int selected_index) {
966 DCHECK(selected_index >= 0 && selected_index < GetEntryCount());
967 ConfigureEntriesForRestore(&entries_);
968
969 set_max_restored_page_id(GetEntryCount());
970
971 last_committed_entry_index_ = selected_index;
972
973 // Callers assume we have an active_contents after restoring, so set it now.
974 active_contents_ =
975 GetTabContentsCreateIfNecessary(parent_hwnd, *entries_[selected_index]);
976}
[email protected]765b35502008-08-21 00:51:20977
978void NavigationController::DiscardPendingEntryInternal() {
979 if (pending_entry_index_ == -1)
980 delete pending_entry_;
981 pending_entry_ = NULL;
982 pending_entry_index_ = -1;
983}
984
985int NavigationController::GetEntryIndexWithPageID(
986 TabContentsType type, SiteInstance* instance, int32 page_id) const {
987 // The instance should only be specified for contents displaying web pages.
988 // TODO(evanm): checking against NEW_TAB_UI and HTML_DLG here is lame.
989 // It'd be nice for DomUIHost to just use SiteInstances for keeping content
990 // separated properly.
991 if (type != TAB_CONTENTS_WEB &&
992 type != TAB_CONTENTS_NEW_TAB_UI &&
993 type != TAB_CONTENTS_ABOUT_UI &&
994 type != TAB_CONTENTS_HTML_DIALOG &&
995 type != TAB_CONTENTS_VIEW_SOURCE &&
996 type != TAB_CONTENTS_DEBUGGER)
997 DCHECK(instance == NULL);
998
999 for (int i = static_cast<int>(entries_.size()) - 1; i >= 0; --i) {
1000 if ((entries_[i]->GetType() == type) &&
1001 (entries_[i]->site_instance() == instance) &&
1002 (entries_[i]->GetPageID() == page_id))
1003 return i;
1004 }
1005 return -1;
1006}