summaryrefslogtreecommitdiffstats
path: root/chrome/browser/browser.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/browser.cc')
-rw-r--r--chrome/browser/browser.cc1665
1 files changed, 1665 insertions, 0 deletions
diff --git a/chrome/browser/browser.cc b/chrome/browser/browser.cc
new file mode 100644
index 0000000..75c6bb4
--- /dev/null
+++ b/chrome/browser/browser.cc
@@ -0,0 +1,1665 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "chrome/browser/browser.h"
+
+#include <windows.h>
+#include <shellapi.h>
+
+#include "base/command_line.h"
+#include "base/idle_timer.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "chrome/browser/browser_list.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_shutdown.h"
+#include "chrome/browser/browser_url_handler.h"
+#include "chrome/browser/cert_store.h"
+#include "chrome/browser/navigation_controller.h"
+#include "chrome/browser/navigation_entry.h"
+#include "chrome/browser/plugin_process_host.h"
+#include "chrome/browser/plugin_service.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/render_view_host.h"
+#include "chrome/browser/save_package.h"
+#include "chrome/browser/ssl_error_info.h"
+#include "chrome/browser/site_instance.h"
+#include "chrome/browser/tabs/tab_strip.h"
+#include "chrome/browser/url_fixer_upper.h"
+#include "chrome/browser/user_metrics.h"
+#include "chrome/browser/view_ids.h"
+#include "chrome/browser/views/download_shelf_view.h"
+#include "chrome/browser/views/go_button.h"
+#include "chrome/browser/views/bookmark_bar_view.h"
+#include "chrome/browser/views/location_bar_view.h"
+#include "chrome/browser/views/toolbar_star_toggle.h"
+#include "chrome/browser/vista_frame.h"
+#include "chrome/browser/window_sizer.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/pref_service.h"
+#include "net/base/cookie_monster.h"
+#include "net/base/cookie_policy.h"
+#include "net/base/net_util.h"
+#include "net/base/registry_controlled_domain.h"
+
+#include "generated_resources.h"
+
+static BrowserList g_browserlist;
+
+// How long we wait before updating the browser chrome while loading a page.
+static const int kUIUpdateCoalescingTimeMS = 200;
+
+// Idle time before helping prune memory consumption.
+static const int kBrowserReleaseMemoryInterval = 30; // In seconds.
+
+// How much horizontal and vertical offset there is between newly opened
+// windows.
+static const int kWindowTilePixels = 10;
+
+// How frequently we check for hung plugin windows.
+static const int kDefaultHungPluginDetectFrequency = 2000;
+
+// How long do we wait before we consider a window hung (in ms).
+static const int kDefaultPluginMessageResponseTimeout = 5000;
+
+////////////////////////////////////////////////////////////////////////////////
+
+// A task to reduce the working set of the plugins.
+class ReducePluginsWorkingSetTask : public Task {
+ public:
+ virtual void Run() {
+ for (PluginProcessHostIterator iter; !iter.Done(); ++iter) {
+ PluginProcessHost* plugin = const_cast<PluginProcessHost*>(*iter);
+ DCHECK(plugin->process());
+ Process process(plugin->process());
+ process.ReduceWorkingSet();
+ }
+ }
+};
+
+// A browser task to run when the user is not using the browser.
+// In our case, we're trying to be nice to the operating system and release
+// memory not in use.
+class BrowserIdleTask : public IdleTimerTask {
+ public:
+ BrowserIdleTask()
+ : IdleTimerTask(
+ TimeDelta::FromSeconds(kBrowserReleaseMemoryInterval), false) {
+ }
+
+ virtual void OnIdle() {
+ // We're idle. Release browser and renderer unused pages.
+
+ // Handle the Browser.
+ Process process(GetCurrentProcess());
+ process.ReduceWorkingSet();
+
+ // Handle the Renderer(s).
+ RenderProcessHost::iterator renderer_iter;
+ for (renderer_iter = RenderProcessHost::begin(); renderer_iter !=
+ RenderProcessHost::end(); renderer_iter++) {
+ Process process(renderer_iter->second->process());
+ process.ReduceWorkingSet();
+ }
+
+ // Handle the Plugin(s). We need to iterate through the plugin processes on
+ // the IO thread because that thread manages the plugin process collection.
+ g_browser_process->io_thread()->message_loop()->PostTask(FROM_HERE,
+ new ReducePluginsWorkingSetTask());
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct Browser::UIUpdate {
+ UIUpdate(const TabContents* src, unsigned flags)
+ : source(src),
+ changed_flags(flags) {
+ }
+
+ // The source of the update.
+ const TabContents* source;
+
+ // What changed in the UI.
+ unsigned changed_flags;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// static
+Browser* Browser::GetBrowserForController(
+ const NavigationController* controller, int* index_result) {
+ BrowserList::const_iterator it;
+ for (it = BrowserList::begin(); it != BrowserList::end(); ++it) {
+ int index = (*it)->tabstrip_model_.GetIndexOfController(controller);
+ if (index != TabStripModel::kNoTab) {
+ if (index_result)
+ *index_result = index;
+ return *it;
+ }
+ }
+
+ return NULL;
+}
+
+// static
+void Browser::OpenNewBrowserWindow(Profile* profile, int show_command) {
+ Browser* browser = new Browser(gfx::Rect(), show_command, profile,
+ BrowserType::TABBED_BROWSER, L"");
+ browser->AddBlankTab(true);
+ browser->Show();
+}
+
+// static
+void Browser::RegisterPrefs(PrefService* prefs) {
+ prefs->RegisterIntegerPref(prefs::kPluginMessageResponseTimeout,
+ kDefaultPluginMessageResponseTimeout);
+ prefs->RegisterIntegerPref(prefs::kHungPluginDetectFrequency,
+ kDefaultHungPluginDetectFrequency);
+ prefs->RegisterDictionaryPref(prefs::kBrowserWindowPlacement);
+ prefs->RegisterBooleanPref(prefs::kMetricsReportingEnabled, true);
+ prefs->RegisterIntegerPref(prefs::kOptionsWindowLastTabIndex, 0);
+}
+
+// static
+void Browser::RegisterUserPrefs(PrefService* prefs) {
+ prefs->RegisterStringPref(prefs::kHomePage, L"chrome-internal:");
+ prefs->RegisterIntegerPref(prefs::kCookieBehavior,
+ CookiePolicy::ALLOW_ALL_COOKIES);
+ prefs->RegisterBooleanPref(prefs::kShowHomeButton, false);
+ prefs->RegisterStringPref(prefs::kRecentlySelectedEncoding, L"");
+}
+
+Browser::Browser(const gfx::Rect& initial_bounds,
+ int show_command,
+ Profile* profile,
+ BrowserType::Type type,
+ const std::wstring& app_name)
+ : profile_(profile),
+ frame_(NULL),
+ initial_show_command_(show_command),
+ is_processing_tab_unload_events_(false),
+ controller_(this),
+ toolbar_(&controller_, this),
+ chrome_updater_factory_(this),
+ frame_method_factory_(this),
+ hung_window_detector_(&hung_plugin_action_),
+ ticker_(0),
+ tabstrip_model_(this, profile),
+ toolbar_model_(this),
+ type_(type),
+ app_name_(app_name),
+ idle_task_(new BrowserIdleTask()) {
+ tabstrip_model_.AddObserver(this);
+
+ CommandLine parsed_command_line;
+
+ gfx::Rect create_bounds;
+ bool maximized = false;
+ WindowSizer::GetBrowserWindowBounds(app_name_, initial_bounds,
+ &create_bounds, &maximized);
+ if (parsed_command_line.HasSwitch(switches::kStartMaximized))
+ maximized = true;
+ if (maximized)
+ initial_show_command_ = SW_SHOWMAXIMIZED;
+ frame_ = ChromeFrame::CreateChromeFrame(create_bounds, this);
+
+ toolbar_.SetID(VIEW_ID_TOOLBAR);
+ toolbar_.Init(profile_);
+
+ // See note where SIZE_TO_CONTENTS is defined in browser.h for an explanation
+ // of this hack.
+ if (show_command == SIZE_TO_CONTENTS) {
+ // SizeToContents causes a Layout so make sure the tab strip and toolbar
+ // are already initialized.
+ frame_->SizeToContents(initial_bounds);
+ initial_show_command_ = SW_SHOWNORMAL;
+ }
+
+ // Start a hung plugin window detector for this browser object (as long as
+ // hang detection is not disabled).
+ if (!parsed_command_line.HasSwitch(switches::kDisableHangMonitor))
+ InitHangMonitor();
+
+ NotificationService::current()->
+ AddObserver(this, NOTIFY_BOOKMARK_BAR_VISIBILITY_PREF_CHANGED,
+ NotificationService::AllSources());
+
+ if (profile->HasSessionService()) {
+ SessionService* session_service = profile->GetSessionService();
+ if (session_service)
+ session_service->SetWindowType(session_id_, type_);
+ }
+
+ InitCommandState();
+ BrowserList::AddBrowser(this);
+
+ encoding_auto_detect_.Init(prefs::kWebKitUsesUniversalDetector,
+ profile_->GetPrefs(), NULL);
+
+ // Trim browser memory on idle for low & medium memory models.
+ if (g_browser_process->memory_model() < BrowserProcess::HIGH_MEMORY_MODEL)
+ idle_task_->Start();
+
+ // Show the First Run information bubble if we've been told to.
+ PrefService* local_state = g_browser_process->local_state();
+ if (local_state->IsPrefRegistered(prefs::kShouldShowFirstRunBubble) &&
+ local_state->GetBoolean(prefs::kShouldShowFirstRunBubble)) {
+ // Reset the preference so we don't show the bubble for subsequent windows.
+ local_state->ClearPref(prefs::kShouldShowFirstRunBubble);
+ GetLocationBarView()->ShowFirstRunBubble();
+ }
+}
+
+Browser::~Browser() {
+ // The tab strip should be empty at this point.
+ DCHECK(tabstrip_model_.empty());
+ tabstrip_model_.RemoveObserver(this);
+
+ BrowserList::RemoveBrowser(this);
+
+ if (!BrowserList::HasBrowserWithProfile(profile_)) {
+ // We're the last browser window with this profile. We need to nuke the
+ // TabRestoreService, which will start the shutdown of the
+ // NavigationControllers and allow for proper shutdown. If we don't do this
+ // chrome won't shutdown cleanly, and may end up crashing when some
+ // thread tries to use the IO thread (or another thread) that is no longer
+ // valid.
+ profile_->ResetTabRestoreService();
+ }
+
+ SessionService* session_service = profile_->GetSessionService();
+ if (session_service)
+ session_service->WindowClosed(session_id_);
+
+ NotificationService::current()->
+ RemoveObserver(this, NOTIFY_BOOKMARK_BAR_VISIBILITY_PREF_CHANGED,
+ NotificationService::AllSources());
+
+ ChromeViews::View* p;
+ // Remove our main views from the view hierarchy to prevent
+ // a double delete (the Browser is deleted before the RootView that contains
+ // the toolbar and tab_strip).
+ if ((p = toolbar_.GetParent()))
+ p->RemoveChildView(&toolbar_);
+ if (bookmark_bar_view_.get() && (p = bookmark_bar_view_->GetParent()))
+ p->RemoveChildView(bookmark_bar_view_.get());
+
+ // Stop hung plugin monitoring.
+ ticker_.Stop();
+ ticker_.UnregisterTickHandler(&hung_window_detector_);
+
+ if (profile_->IsOffTheRecord() &&
+ !BrowserList::IsOffTheRecordSessionActive()) {
+ // We reuse the OTR cookie store across OTR windows. If the last OTR
+ // window is closed, then we want to wipe the cookie store clean, so when
+ // an OTR window is open again, it starts with an empty cookie store. This
+ // also frees up the memory that the OTR cookies were using. OTR never
+ // loads or writes persistent cookies (there is no backing store), so we
+ // can just delete all of the cookies in the store.
+ profile_->GetRequestContext()->cookie_store()->DeleteAll(false);
+ }
+
+ // There may be pending file dialogs, we need to tell them that we've gone
+ // away so they don't try and call back to us.
+ if (select_file_dialog_.get())
+ select_file_dialog_->ListenerDestroyed();
+}
+
+void Browser::ShowAndFit(bool resize_to_fit) {
+ // Only allow one call after the browser is created.
+ if (initial_show_command_ < 0) {
+ // The frame is already visible, we're being invoked again either by the
+ // user clicking a link in another app or from a desktop shortcut.
+ frame_->Activate();
+ return;
+ }
+ frame_->Show(initial_show_command_, resize_to_fit);
+ if ((initial_show_command_ == SW_SHOWNORMAL) ||
+ (initial_show_command_ == SW_SHOWMAXIMIZED))
+ frame_->Activate();
+ initial_show_command_ = -1;
+
+ // Setting the focus doesn't work when the window is invisible, so any focus
+ // initialization that happened before this will be lost.
+ //
+ // We really "should" restore the focus whenever the window becomes unhidden,
+ // but I think initializing is the only time where this can happen where there
+ // is some focus change we need to pick up, and this is easier than plumbing
+ // through an unhide message all the way from the frame.
+ //
+ // If we do find there are cases where we need to restore the focus on show,
+ // that should be added and this should be removed.
+ TabContents* selected_tab_contents = GetSelectedTabContents();
+ if (selected_tab_contents)
+ selected_tab_contents->RestoreFocus();
+}
+
+void Browser::CloseFrame() {
+ frame_->Close();
+}
+
+ChromeViews::View* Browser::GetToolbar() {
+ return &toolbar_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Browser::SyncWindowTitle() {
+ TabContents* current_tab = GetSelectedTabContents();
+ if (!current_tab || current_tab->GetTitle().empty()) {
+ frame_->SetWindowTitle(l10n_util::GetString(IDS_PRODUCT_NAME));
+ return;
+ }
+
+ frame_->SetWindowTitle(
+ l10n_util::GetStringF(IDS_BROWSER_WINDOW_TITLE_FORMAT,
+ current_tab->GetTitle()));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Event Handlers
+
+void Browser::WindowActivationChanged(bool is_active) {
+ if (is_active)
+ BrowserList::SetLastActive(this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Toolbar creation, management
+
+LocationBarView* Browser::GetLocationBarView() const {
+ return toolbar_.GetLocationBarView();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Chrome update coalescing
+
+void Browser::UpdateToolBar(bool should_restore_state) {
+ toolbar_.Update(GetSelectedTabContents(), should_restore_state);
+}
+
+void Browser::ScheduleUIUpdate(const TabContents* source,
+ unsigned changed_flags) {
+ // Synchronously update the URL.
+ if (changed_flags & TabContents::INVALIDATE_URL &&
+ source == GetSelectedTabContents()) {
+ // Only update the URL for the current tab. Note that we do not update
+ // the navigation commands since those would have already been updated
+ // synchronously by NavigationStateChanged.
+ UpdateToolBar(false);
+
+ if (changed_flags == TabContents::INVALIDATE_URL)
+ return; // Just had an update URL and nothing else.
+ }
+
+ // Save the dirty bits.
+ scheduled_updates_.push_back(UIUpdate(source, changed_flags));
+
+ if (chrome_updater_factory_.empty()) {
+ // No task currently scheduled, start another.
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ chrome_updater_factory_.NewRunnableMethod(
+ &Browser::ProcessPendingUIUpdates),
+ kUIUpdateCoalescingTimeMS);
+ }
+}
+
+void Browser::ProcessPendingUIUpdates() {
+#ifndef NDEBUG
+ // Validate that all tabs we have pending updates for exist. This is scary
+ // because the pending list must be kept in sync with any detached or
+ // deleted tabs. This code does not dereference any TabContents pointers.
+ for (size_t i = 0; i < scheduled_updates_.size(); i++) {
+ bool found = false;
+ for (int tab = 0; tab < tab_count(); tab++) {
+ if (GetTabContentsAt(tab)->controller() ==
+ scheduled_updates_[i].source->controller()) {
+ found = true;
+ break;
+ }
+ }
+ DCHECK(found);
+ }
+#endif
+
+ chrome_updater_factory_.RevokeAll();
+
+ // We could have many updates for the same thing in the queue. This map tracks
+ // the bits of the stuff we've already updated for each TabContents so we
+ // don't update again.
+ typedef std::map<const TabContents*, unsigned> UpdateTracker;
+ UpdateTracker updated_stuff;
+
+ for (size_t i = 0; i < scheduled_updates_.size(); i++) {
+ // Do not dereference |contents|, it may be out-of-date!
+ const TabContents* contents = scheduled_updates_[i].source;
+ unsigned flags = scheduled_updates_[i].changed_flags;
+
+ // Remove any bits we have already updated, and save the new bits.
+ UpdateTracker::iterator updated = updated_stuff.find(contents);
+ if (updated != updated_stuff.end()) {
+ // Turn off bits already set.
+ flags &= ~updated->second;
+ if (!flags)
+ continue;
+
+ updated->second |= flags;
+ } else {
+ updated_stuff[contents] = flags;
+ }
+
+ // Updates to the title or favicon require a tab repaint. However, the
+ // inverse is not true since updates to the title also update the window
+ // title.
+ bool invalidate_tab = false;
+ if (flags & TabContents::INVALIDATE_TITLE ||
+ flags & TabContents::INVALIDATE_FAVICON) {
+ invalidate_tab = true;
+
+ // Anything that repaints the tab means the favicon is updated.
+ updated_stuff[contents] |= TabContents::INVALIDATE_FAVICON;
+ }
+
+ // Updating the URL happens synchronously in ScheduleUIUpdate.
+
+ if (flags & TabContents::INVALIDATE_TITLE)
+ SyncWindowTitle(); // We'll update the tab due to invalide_tab below.
+
+ if (flags & TabContents::INVALIDATE_LOAD)
+ GetStatusBubble()->SetStatus(GetSelectedTabContents()->GetStatusText());
+
+ if (invalidate_tab) { // INVALIDATE_TITLE or INVALIDATE_FAVICON.
+ tabstrip_model_.UpdateTabContentsStateAt(
+ tabstrip_model_.GetIndexOfController(contents->controller()));
+ frame_->UpdateTitleBar();
+
+ if (contents == GetSelectedTabContents()) {
+ TabContents* current_tab = GetSelectedTabContents();
+ controller_.UpdateCommandEnabled(IDC_CREATE_SHORTCUT,
+ current_tab->type() == TAB_CONTENTS_WEB &&
+ !current_tab->GetFavIcon().isNull());
+ }
+ }
+
+ // We don't need to process INVALIDATE_STATE, since that's not visible.
+ }
+
+ scheduled_updates_.clear();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TabContentsDelegate
+
+void Browser::OpenURLFromTab(TabContents* source,
+ const GURL& url,
+ WindowOpenDisposition disposition,
+ PageTransition::Type transition) {
+ // No code for these yet
+ DCHECK((disposition != NEW_POPUP) && (disposition != SAVE_TO_DISK));
+
+ TabContents* current_tab = source ? source : GetSelectedTabContents();
+ bool source_tab_was_frontmost = (current_tab == GetSelectedTabContents());
+ TabContents* new_contents = NULL;
+
+ // If the URL is part of the same web site, then load it in the same
+ // SiteInstance (and thus the same process). This is an optimization to
+ // reduce process overhead; it is not necessary for compatibility. (That is,
+ // the new tab will not have script connections to the previous tab, so it
+ // does not need to be part of the same SiteInstance or BrowsingInstance.)
+ // Default to loading in a new SiteInstance and BrowsingInstance.
+ // TODO(creis): should this apply to applications?
+ SiteInstance* instance = NULL;
+ // Don't use this logic when "--process-per-tab" is specified.
+ if (!CommandLine().HasSwitch(switches::kProcessPerTab)) {
+ if (current_tab) {
+ const WebContents* const web_contents = current_tab->AsWebContents();
+ if (web_contents) {
+ const GURL& current_url = web_contents->GetURL();
+ if (SiteInstance::IsSameWebSite(current_url, url))
+ instance = web_contents->site_instance();
+ }
+ }
+ }
+
+ // If this is an application we can only have one tab so a new tab always
+ // goes into a tabbed browser window.
+ if (disposition != NEW_WINDOW && type_ == BrowserType::APPLICATION) {
+ // If the disposition is OFF_THE_RECORD we don't want to create a new
+ // browser that will itself create another OTR browser. This will result in
+ // a browser leak (and crash below because no tab is created or selected).
+ if (disposition == OFF_THE_RECORD) {
+ OpenURLOffTheRecord(profile_, url);
+ return;
+ }
+
+ Browser* b = GetOrCreateTabbedBrowser();
+ DCHECK(b);
+
+ // If we have just created a new browser window, make sure we select the
+ // tab.
+ if (b->tab_count() == 0 && disposition == NEW_BACKGROUND_TAB)
+ disposition = NEW_FOREGROUND_TAB;
+
+ b->OpenURL(url, disposition, transition);
+ b->Show();
+ b->MoveToFront(true);
+ return;
+ }
+
+ if (profile_->IsOffTheRecord() && disposition == OFF_THE_RECORD)
+ disposition = NEW_FOREGROUND_TAB;
+
+ if (disposition == NEW_WINDOW) {
+ Browser* new_browser = new Browser(gfx::Rect(), SW_SHOWNORMAL, profile_,
+ BrowserType::TABBED_BROWSER, L"");
+ new_contents = new_browser->AddTabWithURL(url, transition, true, instance);
+ new_browser->Show();
+ } else if ((disposition == CURRENT_TAB) && current_tab) {
+ if (transition == PageTransition::TYPED ||
+ transition == PageTransition::AUTO_BOOKMARK ||
+ transition == PageTransition::GENERATED ||
+ transition == PageTransition::START_PAGE) {
+ // If the user navigates the current tab to another page in any way other
+ // than by clicking a link, we want to pro-actively forget all TabStrip
+ // opener relationships since we assume they're beginning a different
+ // task by reusing the current tab.
+ tabstrip_model_.ForgetAllOpeners();
+ // In this specific case we also want to reset the group relationship,
+ // since it is now technically invalid.
+ tabstrip_model_.ForgetGroup(current_tab);
+ }
+ current_tab->controller()->LoadURL(url, transition);
+ // The TabContents might have changed as part of the navigation (ex: new tab
+ // page can become WebContents).
+ new_contents = current_tab->controller()->active_contents();
+ GetStatusBubble()->Hide();
+
+ // Synchronously update the location bar. This allows us to immediately
+ // have the URL bar update when the user types something, rather than
+ // going through the normal system of ScheduleUIUpdate which has a delay.
+ UpdateToolBar(false);
+ } else if (disposition == OFF_THE_RECORD) {
+ OpenURLOffTheRecord(profile_, url);
+ return;
+ } else if (disposition != SUPPRESS_OPEN) {
+ new_contents =
+ AddTabWithURL(url, transition, disposition != NEW_BACKGROUND_TAB,
+ instance);
+ }
+
+ if (disposition != NEW_BACKGROUND_TAB && source_tab_was_frontmost) {
+ // Give the focus to the newly navigated tab, if the source tab was front-most
+ new_contents->Focus();
+ }
+}
+
+void Browser::NavigationStateChanged(const TabContents* source,
+ unsigned changed_flags) {
+ if (!GetSelectedTabContents()) {
+ // Nothing is selected. This can happen when being restored from history,
+ // bail.
+ return;
+ }
+
+ // Only update the UI when something visible has changed.
+ if (changed_flags && changed_flags != TabContents::INVALIDATE_STATE)
+ ScheduleUIUpdate(source, changed_flags);
+
+ // We don't schedule updates to the navigation commands since they will only
+ // change once per navigation, so we don't have to worry about flickering.
+ if (changed_flags & TabContents::INVALIDATE_URL) {
+ UpdateNavigationCommands();
+ }
+}
+
+void Browser::ReplaceContents(TabContents* source, TabContents* new_contents) {
+ source->set_delegate(NULL);
+ new_contents->set_delegate(this);
+
+ RemoveScheduledUpdatesFor(source);
+
+ int index = tabstrip_model_.GetIndexOfTabContents(source);
+ tabstrip_model_.ReplaceTabContentsAt(index, new_contents);
+}
+
+void Browser::AddNewContents(TabContents* source,
+ TabContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {
+ DCHECK(disposition != SAVE_TO_DISK); // No code for this yet
+
+ // If this is an application we can only have one tab so we need to process
+ // this in tabbed browser window.
+ if (tabstrip_model_.count() > 0 &&
+ disposition != NEW_WINDOW && disposition != NEW_POPUP &&
+ type_ != BrowserType::TABBED_BROWSER) {
+ Browser* b = GetOrCreateTabbedBrowser();
+ DCHECK(b);
+ PageTransition::Type transition = PageTransition::LINK;
+ // If we were called from an "installed webapp" we want to emulate the code
+ // that is run from browser_init.cc for links from external applications.
+ // This means we need to open the tab with the START PAGE transition.
+ // AddNewContents doesn't support this but the TabStripModel's
+ // AddTabContents method does.
+ if (type_ == BrowserType::APPLICATION)
+ transition = PageTransition::START_PAGE;
+ b->tabstrip_model()->AddTabContents(new_contents, -1, transition, true);
+ b->Show();
+ b->MoveToFront(true);
+ return;
+ }
+
+ if (disposition == NEW_POPUP) {
+ BuildPopupWindow(source, new_contents, initial_pos);
+ } else if (disposition == NEW_WINDOW) {
+ Browser* new_browser = new Browser(gfx::Rect(), SW_SHOWNORMAL, profile_,
+ BrowserType::TABBED_BROWSER, L"");
+ new_browser->AddNewContents(source, new_contents, NEW_FOREGROUND_TAB,
+ initial_pos, user_gesture);
+ new_browser->Show();
+ } else if (disposition == CURRENT_TAB) {
+ ReplaceContents(source, new_contents);
+ } else if (disposition != SUPPRESS_OPEN) {
+ tabstrip_model_.AddTabContents(new_contents, -1, PageTransition::LINK,
+ disposition == NEW_FOREGROUND_TAB);
+ }
+}
+
+void Browser::StartDraggingDetachedContents(TabContents* source,
+ TabContents* new_contents,
+ const gfx::Rect& contents_bounds,
+ const gfx::Point& mouse_pt,
+ int frame_component) {
+ BrowserType::Type new_type = BrowserType::BROWSER;
+
+ // If this is a minimal chrome browser, propagate to detached contents to
+ // avoid having URL fields in popups.
+ if (type_ == BrowserType::APPLICATION)
+ new_type = type_;
+
+ Browser* browser = new Browser(contents_bounds, SIZE_TO_CONTENTS, profile_,
+ new_type, L"");
+ browser->AddNewContents(
+ source, new_contents, NEW_FOREGROUND_TAB, contents_bounds, true);
+ browser->Show();
+ browser->frame_->ContinueDetachConstrainedWindowDrag(
+ mouse_pt, frame_component);
+}
+
+void Browser::ActivateContents(TabContents* contents) {
+ tabstrip_model_.SelectTabContentsAt(
+ tabstrip_model_.GetIndexOfTabContents(contents), false);
+ frame_->Activate();
+}
+
+HWND Browser::GetTopLevelHWND() const {
+ return frame_ ? reinterpret_cast<HWND>(frame_->GetPlatformID()) : NULL;
+}
+
+void Browser::LoadingStateChanged(TabContents* source) {
+ tabstrip_model_.UpdateTabContentsLoadingAnimations();
+
+ frame_->UpdateTitleBar();
+
+ // Let the go button know that it should change appearance if possible.
+ if (source == GetSelectedTabContents()) {
+ GetGoButton()->ScheduleChangeMode(
+ source->is_loading() ? GoButton::MODE_STOP : GoButton::MODE_GO);
+
+ GetStatusBubble()->SetStatus(GetSelectedTabContents()->GetStatusText());
+ }
+}
+
+void Browser::CloseContents(TabContents* source) {
+ int index = tabstrip_model_.GetIndexOfTabContents(source);
+ if (index == TabStripModel::kNoTab) {
+ NOTREACHED() << "CloseContents called for tab not in our strip";
+ return;
+ }
+ tabstrip_model_.CloseTabContentsAt(index);
+}
+
+void Browser::MoveContents(TabContents* source, const gfx::Rect& pos) {
+ if (GetType() != BrowserType::BROWSER) {
+ NOTREACHED() << "moving invalid browser type";
+ return;
+ }
+
+ ::SetWindowPos(GetTopLevelHWND(), NULL, pos.x(), pos.y(), pos.width(),
+ pos.height(), 0);
+ win_util::AdjustWindowToFit(GetTopLevelHWND());
+}
+
+bool Browser::IsPopup(TabContents* source) {
+ // A non-tabbed BROWSER is an unconstrained popup.
+ return (GetType() == BrowserType::BROWSER);
+}
+
+void Browser::ShowHtmlDialog(HtmlDialogContentsDelegate* delegate,
+ HWND parent_hwnd) {
+ parent_hwnd = parent_hwnd ? parent_hwnd : GetTopLevelHWND();
+ HtmlDialogView* html_view = new HtmlDialogView(this, profile_, delegate);
+ ChromeViews::Window* html_dialog = ChromeViews::Window::CreateChromeWindow(
+ parent_hwnd, gfx::Rect(), html_view, html_view);
+ html_view->InitDialog(html_dialog);
+ html_dialog->Show();
+}
+
+void Browser::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ if (type == NOTIFY_BOOKMARK_BAR_VISIBILITY_PREF_CHANGED) {
+ TabContents* current_tab = GetSelectedTabContents();
+ if (current_tab) {
+ Profile* event_profile = Source<Profile>(source).ptr();
+ if (event_profile->IsSameProfile(current_tab->profile())) {
+ // This forces the browser to query for the BookmarkBar again.
+ frame_->ShelfVisibilityChanged();
+ }
+ }
+ } else {
+ NOTREACHED() << "Got a notification we didn't register for.";
+ }
+}
+
+void Browser::UpdateNavigationCommands() {
+ const TabContents* const current_tab = GetSelectedTabContents();
+ NavigationController* nc = current_tab->controller();
+ controller_.UpdateCommandEnabled(IDC_BACK, nc->CanGoBack());
+ controller_.UpdateCommandEnabled(IDC_FORWARD, nc->CanGoForward());
+
+ const WebContents* const web_contents = current_tab->AsWebContents();
+
+ if (web_contents) {
+ controller_.UpdateCommandEnabled(IDC_STAR, true);
+ SetStarredButtonToggled(web_contents->is_starred());
+
+ // View-source should not be enabled if already in view-source mode.
+ controller_.UpdateCommandEnabled(IDC_VIEWSOURCE,
+ current_tab->type() != TAB_CONTENTS_VIEW_SOURCE &&
+ current_tab->controller()->GetActiveEntry());
+
+ controller_.UpdateCommandEnabled(IDC_ZOOM, true);
+ bool enable_encoding =
+ SavePackage::IsSavableContents(web_contents->contents_mime_type()) &&
+ SavePackage::IsSavableURL(current_tab->GetURL());
+ controller_.UpdateCommandEnabled(IDC_ENCODING, enable_encoding);
+
+ controller_.UpdateCommandEnabled(IDC_SAVEPAGE,
+ SavePackage::IsSavableURL(current_tab->GetURL()));
+ controller_.UpdateCommandEnabled(IDC_SHOW_JS_CONSOLE, true);
+ } else {
+ controller_.UpdateCommandEnabled(IDC_VIEWSOURCE, false);
+ controller_.UpdateCommandEnabled(IDC_SHOW_JS_CONSOLE, false);
+
+ // Both disable the starring button and ensure it doesn't show a star.
+ controller_.UpdateCommandEnabled(IDC_STAR, false);
+ SetStarredButtonToggled(false);
+ controller_.UpdateCommandEnabled(IDC_ZOOM, false);
+ controller_.UpdateCommandEnabled(IDC_ENCODING, false);
+
+ controller_.UpdateCommandEnabled(IDC_SAVEPAGE, false);
+ }
+
+ controller_.UpdateCommandEnabled(IDC_CREATE_SHORTCUT,
+ current_tab->type() == TAB_CONTENTS_WEB &&
+ !current_tab->GetFavIcon().isNull());
+ controller_.UpdateCommandEnabled(IDC_FIND, web_contents != NULL);
+ controller_.UpdateCommandEnabled(IDC_PRINT, web_contents != NULL);
+ controller_.UpdateCommandEnabled(IDC_DUPLICATE,
+ CanDuplicateContentsAt(selected_index()));
+
+ controller_.UpdateCommandEnabled(IDC_GOOFFTHERECORD,
+ profile_->GetOffTheRecordProfile() != profile_);
+}
+
+// Notification that the starredness of a tab changed.
+void Browser::URLStarredChanged(TabContents* source, bool starred) {
+ if (source == GetSelectedTabContents())
+ SetStarredButtonToggled(starred);
+}
+
+StatusBubble* Browser::GetStatusBubble() {
+ return frame_->GetStatusBubble();
+}
+
+// Called whenever the window is moved so that we can update the position
+// of any WS_POPUP HWNDs.
+void Browser::WindowMoved() {
+ GetStatusBubble()->Reposition();
+
+ // Close the omnibox popup, if any.
+ LocationBarView* location_bar = GetLocationBarView();
+ if (location_bar)
+ location_bar->location_entry()->ClosePopup();
+}
+
+void Browser::ContentsMouseEvent(TabContents* source, UINT message) {
+ if (source == GetSelectedTabContents()) {
+ if (message == WM_MOUSEMOVE) {
+ GetStatusBubble()->MouseMoved();
+ } else if (message == WM_MOUSELEAVE) {
+ GetStatusBubble()->SetURL(GURL(), std::wstring());
+ }
+ }
+}
+
+void Browser::UpdateTargetURL(TabContents* source, const GURL& url) {
+ if (source == GetSelectedTabContents()) {
+ PrefService* prefs = profile_->GetPrefs();
+ GetStatusBubble()->SetURL(url, prefs->GetString(prefs::kAcceptLanguages));
+ }
+}
+
+void Browser::SetStarredButtonToggled(bool starred) {
+ toolbar_.star_button()->SetToggled(starred);
+}
+
+GoButton* Browser::GetGoButton() {
+ return toolbar_.GetGoButton();
+}
+
+void Browser::ContentsZoomChange(bool zoom_in) {
+ controller_.ExecuteCommand(zoom_in ? IDC_ZOOM_PLUS : IDC_ZOOM_MINUS);
+}
+
+bool Browser::IsApplication() const {
+ return type_ == BrowserType::APPLICATION;
+}
+
+void Browser::CrashedStateChanged(TabContents* source) {
+ int index = tabstrip_model_.GetIndexOfTabContents(source);
+ if (index != TabStripModel::kNoTab)
+ tabstrip_model_.UpdateTabContentsStateAt(index);
+}
+
+bool Browser::ShouldDisplayURLField() {
+ return !IsApplication();
+}
+
+BookmarkBarView* Browser::GetBookmarkBarView() {
+ TabContents* current_tab = GetSelectedTabContents();
+ if (!current_tab || !current_tab->profile())
+ return NULL;
+
+ if (!bookmark_bar_view_.get())
+ bookmark_bar_view_.reset(new BookmarkBarView(current_tab->profile(), this));
+ else
+ bookmark_bar_view_->SetProfile(current_tab->profile());
+ bookmark_bar_view_->SetPageNavigator(current_tab);
+ return bookmark_bar_view_.get();
+}
+
+void Browser::SaveWindowPlacementToDatabase() {
+ // We don't want to be the ones who cause lazy initialization of the session
+ // service. This function gets called during initial window showing, and we
+ // don't want to bring in the session service this early.
+ if (!profile()->HasSessionService())
+ return;
+ SessionService* session_service = profile()->GetSessionService();
+ if (!session_service)
+ return;
+
+ WINDOWPLACEMENT wp;
+ wp.length = sizeof(wp);
+
+ HWND hwnd = reinterpret_cast<HWND>(frame_->GetPlatformID());
+ if (!::GetWindowPlacement(hwnd, &wp))
+ return;
+
+ session_service->SetWindowBounds(session_id_,
+ gfx::Rect(wp.rcNormalPosition),
+ (wp.showCmd & SW_MAXIMIZE) == SW_MAXIMIZE);
+}
+
+void Browser::SaveWindowPlacement() {
+ WINDOWPLACEMENT wp;
+ wp.length = sizeof(wp);
+
+ HWND hwnd = reinterpret_cast<HWND>(frame_->GetPlatformID());
+ if (!::GetWindowPlacement(hwnd, &wp))
+ return;
+
+ PrefService* prefs = g_browser_process->local_state();
+ DCHECK(prefs);
+ std::wstring name(prefs::kBrowserWindowPlacement);
+ if (!app_name_.empty()) {
+ name.append(L"_");
+ name.append(app_name_);
+ }
+
+ DictionaryValue* win_pref = prefs->GetMutableDictionary(name.c_str());
+ DCHECK(win_pref);
+ win_pref->SetInteger(L"top", wp.rcNormalPosition.top);
+ win_pref->SetInteger(L"left", wp.rcNormalPosition.left);
+ win_pref->SetInteger(L"bottom", wp.rcNormalPosition.bottom);
+ win_pref->SetInteger(L"right", wp.rcNormalPosition.right);
+ win_pref->SetBoolean(L"maximized", wp.showCmd == SW_SHOWMAXIMIZED);
+}
+
+void Browser::FocusLocationBar() {
+ LocationBarView* location_bar = GetLocationBarView();
+ if (location_bar)
+ location_bar->location_entry()->SetFocus();
+}
+
+void Browser::SyncHistoryWithTabs(int index) {
+ if (!profile()->HasSessionService())
+ return;
+ SessionService* session_service = profile()->GetSessionService();
+ if (session_service) {
+ for (int i = index; i < tab_count(); ++i) {
+ TabContents* contents = GetTabContentsAt(i);
+ if (contents) {
+ session_service->SetTabIndexInWindow(
+ session_id(), contents->controller()->session_id(), i);
+ }
+ }
+ }
+}
+
+void Browser::ToolbarSizeChanged(TabContents* source, bool is_animating) {
+ if (source == GetSelectedTabContents() || source == NULL) {
+ // This will refresh the shelf if needed.
+ frame_->SelectedTabToolbarSizeChanged(is_animating);
+ }
+}
+
+void Browser::MoveToFront(bool should_activate) {
+ frame_->Activate();
+}
+
+bool Browser::ShouldCloseWindow() {
+ if (is_processing_tab_unload_events_) {
+ return false;
+ }
+ is_processing_tab_unload_events_ = true;
+
+ for (int i = 0; i < tab_count(); ++i) {
+ if (tabstrip_model_.TabHasUnloadListener(i))
+ tabs_needing_before_unload_fired_.push_back(GetTabContentsAt(i));
+ }
+
+ if (tabs_needing_before_unload_fired_.empty()) {
+ is_processing_tab_unload_events_ = false;
+ return true;
+ }
+
+ ProcessPendingBeforeUnloadTabs();
+ return false;
+}
+
+void Browser::ProcessPendingBeforeUnloadTabs() {
+ DCHECK(is_processing_tab_unload_events_);
+
+ TabContents* tab = tabs_needing_before_unload_fired_.back();
+ tab->AsWebContents()->render_view_host()->AttemptToClosePage(true);
+}
+
+void Browser::ProcessPendingUnloadTabs() {
+ DCHECK(is_processing_tab_unload_events_);
+
+ TabContents* tab = tabs_needing_unload_fired_.back();
+ tab->AsWebContents()->render_view_host()->OnProceedWithClosePage(true);
+}
+
+void Browser::BeforeUnloadFired(TabContents* tab, bool proceed) {
+ DCHECK(is_processing_tab_unload_events_);
+
+ if (!proceed) {
+ tabs_needing_before_unload_fired_.clear();
+ tabs_needing_unload_fired_.clear();
+ is_processing_tab_unload_events_ = false;
+ return;
+ }
+
+ tabs_needing_unload_fired_.push_back(tab);
+
+ for (UnloadListenerVector::iterator it =
+ tabs_needing_before_unload_fired_.begin();
+ it != tabs_needing_before_unload_fired_.end();
+ ++it) {
+ if (*it == tab) {
+ tabs_needing_before_unload_fired_.erase(it);
+ break;
+ }
+ }
+
+ if (tabs_needing_before_unload_fired_.empty()) {
+ // We've finished firing all beforeunload events and can proceed with unload
+ // events.
+ // TODO(ojan): We should add a call to browser_shutdown::OnShutdownStarting
+ // somewhere around here so that we have accurate measurements of shutdown
+ // time.
+ ProcessPendingUnloadTabs();
+ } else {
+ ProcessPendingBeforeUnloadTabs();
+ }
+}
+
+void Browser::UnloadFired(TabContents* tab) {
+ DCHECK(is_processing_tab_unload_events_);
+
+ for (UnloadListenerVector::iterator it = tabs_needing_unload_fired_.begin();
+ it != tabs_needing_unload_fired_.end();
+ ++it) {
+ if (*it == tab) {
+ tabs_needing_unload_fired_.erase(it);
+ break;
+ }
+ }
+
+ if (tabs_needing_unload_fired_.empty()) {
+ // We've finished all the unload events and can proceed to close the
+ // browser.
+ is_processing_tab_unload_events_ = false;
+ OnWindowClosing();
+ } else {
+ ProcessPendingUnloadTabs();
+ }
+}
+
+
+void Browser::OnWindowClosing() {
+ if (!ShouldCloseWindow())
+ return;
+
+ if (BrowserList::size() == 1)
+ browser_shutdown::OnShutdownStarting(browser_shutdown::WINDOW_CLOSE);
+
+ // Don't use HasSessionService here, we want to force creation of the
+ // session service so that user can restore what was open.
+ SessionService* session_service = profile()->GetSessionService();
+ if (session_service)
+ session_service->WindowClosing(session_id());
+
+ CloseAllTabs();
+}
+
+// Tab Creation Functions
+
+TabContents* Browser::AddTabWithURL(
+ const GURL& url, PageTransition::Type transition, bool foreground,
+ SiteInstance* instance) {
+ if (type_ == BrowserType::APPLICATION && tabstrip_model_.count() == 1) {
+ NOTREACHED() << "Cannot add a tab in a mono tab application.";
+ return NULL;
+ }
+
+ GURL url_to_load = url;
+ if (url_to_load.is_empty())
+ url_to_load = GURL(profile_->GetPrefs()->GetString(prefs::kHomePage));
+ TabContents* contents =
+ CreateTabContentsForURL(url_to_load, profile_, transition, false,
+ instance);
+ tabstrip_model_.AddTabContents(contents, -1, transition, foreground);
+ // By default, content believes it is not hidden. When adding contents
+ // in the background, tell it that it's hidden.
+ if (!foreground)
+ contents->WasHidden();
+ return contents;
+}
+
+TabContents* Browser::AddWebApplicationTab(Profile* profile,
+ WebApp* web_app,
+ bool lazy) {
+ DCHECK(web_app);
+
+ // TODO(acw): Do we need an "application launched" transition type?
+ // TODO(creis): Should we reuse the current instance (ie. process) here?
+ TabContents* contents =
+ CreateTabContentsForURL(web_app->url(), profile, PageTransition::LINK,
+ lazy, NULL);
+ if (contents->AsWebContents())
+ contents->AsWebContents()->SetWebApp(web_app);
+
+ if (lazy) {
+ contents->controller()->LoadURLLazily(
+ web_app->url(), PageTransition::LINK, web_app->name(), NULL);
+ }
+ tabstrip_model_.AddTabContents(contents, -1, PageTransition::LINK, !lazy);
+ return contents;
+}
+
+TabContents* Browser::AddTabWithNavigationController(
+ NavigationController* ctrl, PageTransition::Type type) {
+ TabContents* tc = ctrl->active_contents();
+ tabstrip_model_.AddTabContents(tc, -1, type, true);
+ return tc;
+}
+
+NavigationController* Browser::AddRestoredTab(
+ const std::vector<TabNavigation>& navigations,
+ int selected_navigation,
+ bool select) {
+ NavigationController* restored_controller =
+ BuildRestoredNavigationController(navigations, selected_navigation);
+
+ tabstrip_model_.AppendTabContents(restored_controller->active_contents(),
+ select);
+ if (profile_->HasSessionService()) {
+ SessionService* session_service = profile_->GetSessionService();
+ if (session_service)
+ session_service->TabRestored(restored_controller);
+ }
+ return restored_controller;
+}
+
+void Browser::ReplaceRestoredTab(
+ const std::vector<TabNavigation>& navigations,
+ int selected_navigation) {
+ NavigationController* restored_controller =
+ BuildRestoredNavigationController(navigations, selected_navigation);
+
+ tabstrip_model_.ReplaceNavigationControllerAt(
+ tabstrip_model_.selected_index(),
+ restored_controller);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Browser, TabStripModelDelegate implementation:
+
+void Browser::CreateNewStripWithContents(TabContents* detached_contents,
+ const gfx::Point& drop_point) {
+ DCHECK(type_ == BrowserType::TABBED_BROWSER);
+
+ // Create an empty new browser window the same size as the old one.
+ CRect browser_rect;
+ GetWindowRect(reinterpret_cast<HWND>(frame_->GetPlatformID()), &browser_rect);
+ gfx::Rect rect(0, 0);
+ if (drop_point.x() != 0 || drop_point.y() != 0) {
+ rect.SetRect(drop_point.x(), drop_point.y(), browser_rect.Width(),
+ browser_rect.Height());
+ }
+ Browser* new_window =
+ new Browser(rect, SW_SHOWNORMAL, profile_, BrowserType::TABBED_BROWSER,
+ std::wstring());
+ // Need to do this _before_ appending the TabContents so that the window is
+ // appropriately sized.
+ new_window->Show();
+ new_window->tabstrip_model()->AppendTabContents(detached_contents, true);
+
+ // When we detach a tab we need to make sure any associated Find window moves
+ // along with it to its new home (basically we just make new_window the parent
+ // of the Find window).
+ new_window->AdoptFindWindow(detached_contents);
+}
+
+int Browser::GetDragActions() const {
+ int result = 0;
+ if (BrowserList::GetBrowserCountForType(profile_,
+ BrowserType::TABBED_BROWSER) > 1 ||
+ tab_count() > 1)
+ result |= TAB_TEAROFF_ACTION;
+ if (tab_count() > 1)
+ result |= TAB_MOVE_ACTION;
+ return result;
+}
+
+TabContents* Browser::CreateTabContentsForURL(
+ const GURL& url, Profile* profile, PageTransition::Type transition,
+ bool defer_load, SiteInstance* instance) const {
+ // Create an appropriate tab contents.
+ GURL real_url = url;
+ TabContentsType type = TabContents::TypeForURL(&real_url);
+ DCHECK(type != TAB_CONTENTS_UNKNOWN_TYPE);
+
+ TabContents* contents =
+ TabContents::CreateWithType(type, GetTopLevelHWND(), profile, instance);
+ contents->SetupController(profile);
+
+ if (!defer_load) {
+ // Load the initial URL before adding the new tab contents to the tab strip
+ // so that the tab contents has navigation state.
+ contents->controller()->LoadURL(url, transition);
+ }
+
+ return contents;
+}
+
+void Browser::ShowApplicationMenu(const gfx::Point p) {
+ if (!frame_)
+ return;
+
+ HWND hwnd = reinterpret_cast<HWND>(frame_->GetPlatformID());
+ CPoint t;
+ t.x = p.x();
+ t.y = p.y();
+ RunSimpleFrameMenu(t, hwnd);
+}
+
+void Browser::ValidateLoadingAnimations() {
+ if (frame_)
+ frame_->ValidateThrobber();
+}
+
+void Browser::CloseFrameAfterDragSession() {
+ // This is scheduled to run after we return to the message loop because
+ // otherwise the frame will think the drag session is still active and ignore
+ // the request.
+ MessageLoop::current()->PostTask(FROM_HERE,
+ frame_method_factory_.NewRunnableMethod(&Browser::CloseFrame));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Browser, TabStripModelObserver implementation:
+
+void Browser::TabInsertedAt(TabContents* contents,
+ int index,
+ bool foreground) {
+ contents->set_delegate(this);
+
+ NavigationController* controller = contents->controller();
+ DCHECK(controller);
+ NotificationService::current()->
+ Notify(NOTIFY_TAB_APPENDED,
+ Source<NavigationController>(controller),
+ Details<Browser>(this));
+
+ contents->controller()->SetWindowID(session_id());
+
+ SyncHistoryWithTabs(tabstrip_model_.GetIndexOfTabContents(contents));
+
+ // When a tab is dropped into a tab strip we need to make sure that the
+ // associated Find window is moved along with it. We therefore change the
+ // parent of the Find window (if the parent is already correctly set this
+ // does nothing).
+ AdoptFindWindow(contents);
+}
+
+void Browser::TabClosingAt(TabContents* contents, int index) {
+ NavigationController* controller = contents->controller();
+ DCHECK(controller);
+ NotificationService::current()->
+ Notify(NOTIFY_TAB_CLOSING,
+ Source<NavigationController>(controller),
+ NotificationService::NoDetails());
+
+ // Sever the TabContents' connection back to us.
+ contents->set_delegate(NULL);
+
+ if (contents == GetSelectedTabContents()) {
+ // We need to reset the current tab contents to NULL before it gets
+ // freed. This is because the focus manager performs some operation
+ // on the selected tab contents when it is removed.
+ frame_->ShowTabContents(NULL);
+ }
+}
+
+void Browser::TabDetachedAt(TabContents* contents, int index) {
+ // TODO(beng): (http://b/1085418) figure out if we really need to do this
+ // here - surely the subsequent selection of another tab would
+ // result in this action taking place?
+ if (contents == GetSelectedTabContents())
+ RemoveShelvesForTabContents(contents);
+
+ contents->set_delegate(NULL);
+ if (!tabstrip_model_.closing_all())
+ SyncHistoryWithTabs(0);
+
+ RemoveScheduledUpdatesFor(contents);
+}
+
+void Browser::TabSelectedAt(TabContents* old_contents,
+ TabContents* new_contents,
+ int index,
+ bool user_gesture) {
+ if (new_contents == old_contents)
+ return;
+
+ // If we have any update pending, do it now.
+ if (!chrome_updater_factory_.empty() && old_contents)
+ ProcessPendingUIUpdates();
+
+ LocationBarView* location_bar = GetLocationBarView();
+ if (old_contents) {
+ // Have the contents remember where focus was.
+ old_contents->StoreFocus();
+
+ // Save what the user's currently typing, so it can be restored when we
+ // switch back to this tab.
+ if (location_bar)
+ location_bar->location_entry()->SaveStateToTab(old_contents);
+ }
+
+ // Tell the frame what happened so that the TabContents gets resized, etc.
+ frame_->ShowTabContents(new_contents);
+
+ // Inform the tab that it is now selected.
+ new_contents->DidBecomeSelected();
+ if (BrowserList::GetLastActive() == this)
+ new_contents->RestoreFocus();
+
+ // Propagate the profile to the location bar.
+ toolbar_.SetProfile(new_contents->profile());
+ UpdateToolBar(true);
+
+ // Force the go/stop button to change.
+ if (new_contents->AsWebContents()) {
+ GetGoButton()->ChangeMode(
+ new_contents->is_loading() ? GoButton::MODE_STOP : GoButton::MODE_GO);
+ } else {
+ GetGoButton()->ChangeMode(GoButton::MODE_GO);
+ }
+
+ // Update other parts of the toolbar.
+ UpdateNavigationCommands();
+
+ // Reset the status bubble.
+ GetStatusBubble()->Hide();
+
+ // Show the loading state (if any).
+ GetStatusBubble()->SetStatus(GetSelectedTabContents()->GetStatusText());
+
+ SyncWindowTitle();
+
+ // Update sessions. Don't force creation of sessions. If sessions doesn't
+ // exist, the change will be picked up by sessions when created.
+ if (profile_->HasSessionService()) {
+ SessionService* session_service = profile_->GetSessionService();
+ if (session_service && !tabstrip_model_.closing_all()) {
+ session_service->SetSelectedTabInWindow(session_id(),
+ tabstrip_model_.selected_index());
+ }
+ }
+}
+
+void Browser::TabMoved(TabContents* contents,
+ int from_index,
+ int to_index) {
+ DCHECK(from_index >= 0 && to_index >= 0);
+ // Notify the history service.
+ SyncHistoryWithTabs(std::min(from_index, to_index));
+}
+
+void Browser::TabStripEmpty() {
+ // We need to reset the frame contents just in case this wasn't done while
+ // detaching the tab. This happens when dragging out the last tab.
+ frame_->ShowTabContents(NULL);
+
+ // Close the frame after we return to the message loop (not immediately,
+ // otherwise it will destroy this object before the stack has a chance to
+ // cleanly unwind.)
+ // Note: This will be called several times if TabStripEmpty is called several
+ // times. This is because it does not close the window if tabs are
+ // still present.
+ // NOTE: If you change to be immediate (no invokeLater) then you'll need to
+ // update BrowserList::CloseAllBrowsers.
+ MessageLoop::current()->PostTask(FROM_HERE,
+ frame_method_factory_.NewRunnableMethod(&Browser::CloseFrame));
+}
+
+void Browser::RemoveShelvesForTabContents(TabContents* contents) {
+ ChromeViews::View* shelf = contents->GetDownloadShelfView();
+ if (shelf && shelf->GetParent() != NULL)
+ shelf->GetParent()->RemoveChildView(shelf);
+
+ ChromeViews::View* info_bar = contents->GetInfoBarView();
+ if (info_bar && info_bar->GetParent() != NULL)
+ info_bar->GetParent()->RemoveChildView(info_bar);
+}
+
+BrowserType::Type Browser::GetType() const {
+ return type_;
+}
+
+void Browser::InitHangMonitor() {
+ PrefService* pref_service = g_browser_process->local_state();
+ DCHECK(pref_service != NULL);
+ int plugin_message_response_timeout =
+ pref_service->GetInteger(prefs::kPluginMessageResponseTimeout);
+ int hung_plugin_detect_freq =
+ pref_service->GetInteger(prefs::kHungPluginDetectFrequency);
+ if ((hung_plugin_detect_freq > 0) &&
+ hung_window_detector_.Initialize(GetTopLevelHWND(),
+ plugin_message_response_timeout)) {
+ ticker_.set_tick_interval(hung_plugin_detect_freq);
+ ticker_.RegisterTickHandler(&hung_window_detector_);
+ ticker_.Start();
+
+ pref_service->SetInteger(prefs::kPluginMessageResponseTimeout,
+ plugin_message_response_timeout);
+ pref_service->SetInteger(prefs::kHungPluginDetectFrequency,
+ hung_plugin_detect_freq);
+ }
+}
+
+
+Browser* Browser::GetOrCreateTabbedBrowser() {
+ Browser* browser = BrowserList::FindBrowserWithType(
+ profile_, BrowserType::TABBED_BROWSER);
+ if (!browser) {
+ browser = new Browser(gfx::Rect(), SW_SHOWNORMAL, profile_,
+ BrowserType::TABBED_BROWSER, std::wstring());
+ }
+ return browser;
+}
+
+void Browser::RemoveScheduledUpdatesFor(TabContents* contents) {
+ if (!contents)
+ return;
+
+ // Remove any pending UI updates for the detached tab.
+ UpdateVector::iterator cur_update = scheduled_updates_.begin();
+ while (cur_update != scheduled_updates_.end()) {
+ if (cur_update->source == contents) {
+ cur_update = scheduled_updates_.erase(cur_update);
+ } else {
+ ++cur_update;
+ }
+ }
+}
+
+void Browser::ShowNativeUI(const GURL& url) {
+ int i, c;
+ TabContents* tc;
+ for (i = 0, c = tabstrip_model_.count(); i < c; ++i) {
+ tc = tabstrip_model_.GetTabContentsAt(i);
+ if (tc->type() == TAB_CONTENTS_NATIVE_UI &&
+ tc->GetURL() == url) {
+ tabstrip_model_.SelectTabContentsAt(i, false);
+ return;
+ }
+ }
+
+ TabContents* contents = CreateTabContentsForURL(url, profile_,
+ PageTransition::LINK, false,
+ NULL);
+ AddNewContents(NULL, contents, NEW_FOREGROUND_TAB, gfx::Rect(), true);
+}
+
+NavigationController* Browser::BuildRestoredNavigationController(
+ const std::vector<TabNavigation>& navigations,
+ int selected_navigation) {
+ if (!navigations.empty()) {
+ DCHECK(selected_navigation >= 0 &&
+ selected_navigation < static_cast<int>(navigations.size()));
+ // We should have a valid URL, if we don't fall back to the default.
+ GURL url = navigations[selected_navigation].url;
+ if (url.is_empty())
+ url = GURL(profile_->GetPrefs()->GetString(prefs::kHomePage));
+
+ // Create a NavigationController. This constructor creates the appropriate
+ // set of TabContents.
+ return new NavigationController(
+ profile_, navigations, selected_navigation, GetTopLevelHWND());
+ } else {
+ // No navigations. Create a tab with about:blank.
+ TabContents* contents =
+ CreateTabContentsForURL(GURL("about:blank"), profile_,
+ PageTransition::START_PAGE, false, NULL);
+ return new NavigationController(contents, profile_);
+ }
+}
+
+// static
+void Browser::OpenURLOffTheRecord(Profile* profile, const GURL& url) {
+ Profile* off_the_record_profile = profile->GetOffTheRecordProfile();
+ Browser* browser = BrowserList::FindBrowserWithType(
+ off_the_record_profile, BrowserType::TABBED_BROWSER);
+ if (browser == NULL) {
+ browser = new Browser(gfx::Rect(), SW_SHOWNORMAL, off_the_record_profile,
+ BrowserType::TABBED_BROWSER, L"");
+ }
+ browser->AddTabWithURL(url, PageTransition::LINK, true, NULL);
+ browser->Show();
+ browser->MoveToFront(true);
+}
+
+// static
+std::wstring Browser::ComputePopupTitle(const GURL& url,
+ const std::wstring& title) {
+ std::wstring result(title);
+ Tab::FormatTitleForDisplay(&result);
+ return result;
+}
+
+void Browser::ConvertToTabbedBrowser() {
+ if (GetType() != BrowserType::BROWSER) {
+ NOTREACHED();
+ return;
+ }
+
+ int tab_strip_index = tabstrip_model_.selected_index();
+ TabContents* contents = tabstrip_model_.DetachTabContentsAt(tab_strip_index);
+ Browser* browser = new Browser(gfx::Rect(), SW_SHOWNORMAL, profile_,
+ BrowserType::TABBED_BROWSER, L"");
+ browser->AddNewContents(
+ NULL, contents, NEW_FOREGROUND_TAB, gfx::Rect(), true);
+ browser->Show();
+}
+
+void Browser::BuildPopupWindow(TabContents* source,
+ TabContents* new_contents,
+ const gfx::Rect& initial_pos) {
+ Browser* browser = new Browser(gfx::Rect(), SW_SHOWNORMAL, profile_,
+ BrowserType::BROWSER, std::wstring());
+ browser->AddNewContents(source, new_contents,
+ NEW_FOREGROUND_TAB, gfx::Rect(), true);
+
+ // TODO(erg): Need to move all popup sizing logic here, instead of
+ // having it spread across three files.
+
+ // For newly opened popup windows, the incoming width/height
+ // numbers are for the content area, but x/y are for the actual
+ // window position. Thus we can't just call MoveContents().
+ gfx::Rect window_rect =
+ browser->frame()->GetBoundsForContentBounds(initial_pos);
+ window_rect.set_origin(initial_pos.origin());
+ ::SetWindowPos(browser->GetTopLevelHWND(), NULL,
+ window_rect.x(), window_rect.y(),
+ window_rect.width(), window_rect.height(), 0);
+ win_util::AdjustWindowToFit(browser->GetTopLevelHWND());
+
+ browser->Show();
+}
+
+void Browser::ConvertContentsToApplication(TabContents* contents) {
+ if (!contents->AsWebContents() || !contents->AsWebContents()->web_app()) {
+ NOTREACHED();
+ return;
+ }
+
+ int index = tabstrip_model_.GetIndexOfTabContents(contents);
+ if (index < 0)
+ return;
+
+ WebApp* app = contents->AsWebContents()->web_app();
+ const std::wstring& app_name =
+ app->name().empty() ? ComputeApplicationNameFromURL(app->url()) :
+ app->name();
+ RegisterAppPrefs(app_name);
+
+ tabstrip_model_.DetachTabContentsAt(index);
+ Browser* browser = new Browser(gfx::Rect(), SW_SHOWNORMAL, profile_,
+ BrowserType::APPLICATION, app_name);
+ browser->AddNewContents(
+ NULL, contents, NEW_FOREGROUND_TAB, gfx::Rect(), true);
+ browser->Show();
+}
+
+// static
+std::wstring Browser::ComputeApplicationNameFromURL(const GURL& url) {
+ std::string t;
+ t.append(url.host());
+ t.append("_");
+ t.append(url.path());
+ return UTF8ToWide(t);
+}
+
+// static
+void Browser::OpenWebApplication(Profile* profile,
+ WebApp* app,
+ int show_command) {
+ const std::wstring& app_name =
+ app->name().empty() ? ComputeApplicationNameFromURL(app->url()) :
+ app->name();
+
+ RegisterAppPrefs(app_name);
+ Browser* browser = new Browser(gfx::Rect(), show_command, profile,
+ BrowserType::APPLICATION, app_name);
+ browser->AddWebApplicationTab(profile, app, false);
+ browser->Show();
+}
+
+// static
+void Browser::RegisterAppPrefs(const std::wstring& app_name) {
+ // A set of apps that we've already started.
+ static std::set<std::wstring>* g_app_names = NULL;
+
+ if (!g_app_names)
+ g_app_names = new std::set<std::wstring>;
+
+ // Only register once for each app name.
+ if (g_app_names->find(app_name) != g_app_names->end())
+ return;
+ g_app_names->insert(app_name);
+
+ // We need to register the window position pref.
+ std::wstring window_pref(prefs::kBrowserWindowPlacement);
+ window_pref.append(L"_");
+ window_pref.append(app_name);
+ PrefService* prefs = g_browser_process->local_state();
+ DCHECK(prefs);
+
+ prefs->RegisterDictionaryPref(window_pref.c_str());
+}
+
+NavigationController* Browser::GetSelectedNavigationController() const {
+ TabContents* tc = GetSelectedTabContents();
+ if (tc)
+ return tc->controller();
+ else
+ return NULL;
+}