diff options
-rw-r--r-- | chrome/app/generated_resources.grd | 6 | ||||
-rw-r--r-- | chrome/browser/about_flags.cc | 7 | ||||
-rw-r--r-- | chrome/browser/tabs/tab_finder.cc | 242 | ||||
-rw-r--r-- | chrome/browser/tabs/tab_finder.h | 111 | ||||
-rw-r--r-- | chrome/browser/ui/browser.cc | 16 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 | ||||
-rw-r--r-- | chrome/common/chrome_switches.cc | 3 | ||||
-rw-r--r-- | chrome/common/chrome_switches.h | 1 | ||||
-rw-r--r-- | content/browser/tab_contents/tab_contents.cc | 2 | ||||
-rw-r--r-- | content/browser/tab_contents/tab_contents_observer.cc | 31 | ||||
-rw-r--r-- | content/browser/tab_contents/tab_contents_observer.h | 20 |
11 files changed, 434 insertions, 7 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 144042c..ff8a6ff 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -4034,6 +4034,12 @@ Keep your key file in a safe place. You will need it to create new versions of y <message name="IDS_FLAGS_REMOTING_DESCRIPTION" desc="Description of the 'Remoting' client lab."> Enable the UI plus backing code for the Remoting serivce process, and client plugin. Warning: This is currently for developer testing only. Unless you are on the dev team and whitelisted, nothing in the enabled UI will work. </message> + <message name="IDS_FLAGS_FOCUS_EXISTING_TAB_ON_OPEN_NAME" desc="Name for 'Focus existing tab on open' lab"> + Focus existing tab on open + </message> + <message name="IDS_FLAGS_FOCUS_EXISTING_TAB_ON_OPEN_DESCRIPTION" desc="Description of the 'Focus existing tab on open' lab."> + Typing a URL of an existing tab into the omnibox results in refocusing the tab instead of loading in the current tab. + </message> <message name="IDS_FLAGS_ENABLE_NACL_NAME" desc="Description of the 'Enable Native Client' lab."> Native Client </message> diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index 3fe6e719..4d59c635 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc @@ -262,6 +262,13 @@ const Experiment kExperiments[] = { kOsAll, SINGLE_VALUE_TYPE(switches::kEnableP2PApi) }, + { + "focus-existing-tab-on-open", // FLAGS:RECORD_UMA + IDS_FLAGS_FOCUS_EXISTING_TAB_ON_OPEN_NAME, + IDS_FLAGS_FOCUS_EXISTING_TAB_ON_OPEN_DESCRIPTION, + kOsAll, + SINGLE_VALUE_TYPE(switches::kFocusExistingTabOnOpen) + }, }; const Experiment* experiments = kExperiments; diff --git a/chrome/browser/tabs/tab_finder.cc b/chrome/browser/tabs/tab_finder.cc new file mode 100644 index 0000000..6758714 --- /dev/null +++ b/chrome/browser/tabs/tab_finder.cc @@ -0,0 +1,242 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/tabs/tab_finder.h" + +#include "base/command_line.h" +#include "base/stl_util-inl.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/notification_type.h" +#include "chrome/common/page_transition_types.h" +#include "chrome/common/render_messages_params.h" +#include "content/browser/tab_contents/navigation_entry.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/browser/tab_contents/tab_contents_observer.h" + +class TabFinder::TabContentsObserverImpl : public TabContentsObserver { + public: + TabContentsObserverImpl(TabContents* tab, TabFinder* finder); + virtual ~TabContentsObserverImpl(); + + TabContents* tab_contents() { return TabContentsObserver::tab_contents(); } + + // TabContentsObserver overrides: + virtual void DidNavigateAnyFramePostCommit( + const NavigationController::LoadCommittedDetails& details, + const ViewHostMsg_FrameNavigate_Params& params) OVERRIDE; + virtual void OnTabContentsDestroyed(TabContents* tab) OVERRIDE; + + private: + TabFinder* finder_; + + DISALLOW_COPY_AND_ASSIGN(TabContentsObserverImpl); +}; + +TabFinder::TabContentsObserverImpl::TabContentsObserverImpl( + TabContents* tab, + TabFinder* finder) + : TabContentsObserver(tab), + finder_(finder) { +} + +TabFinder::TabContentsObserverImpl::~TabContentsObserverImpl() { +} + +void TabFinder::TabContentsObserverImpl::DidNavigateAnyFramePostCommit( + const NavigationController::LoadCommittedDetails& details, + const ViewHostMsg_FrameNavigate_Params& params) { + finder_->DidNavigateAnyFramePostCommit(tab_contents(), details, params); +} + +void TabFinder::TabContentsObserverImpl::OnTabContentsDestroyed( + TabContents* tab) { + finder_->TabDestroyed(this); + delete this; +} + +// static +TabFinder* TabFinder::GetInstance() { + return IsEnabled() ? Singleton<TabFinder>::get() : NULL; +} + +// static +bool TabFinder::IsEnabled() { + return CommandLine::ForCurrentProcess()->HasSwitch( + switches::kFocusExistingTabOnOpen); +} + +TabContents* TabFinder::FindTab(Browser* browser, + const GURL& url, + Browser** existing_browser) { + if (browser->profile()->IsOffTheRecord()) + return NULL; + + // If the current tab matches the url, ignore it and let the user reload the + // existing tab. + TabContents* selected_tab = browser->GetSelectedTabContents(); + if (TabMatchesURL(selected_tab, url)) + return NULL; + + // See if the current browser has a tab matching the specified url. + TabContents* tab_in_browser = FindTabInBrowser(browser, url); + if (tab_in_browser) { + *existing_browser = browser; + return tab_in_browser; + } + + // Then check other browsers. + for (BrowserList::const_iterator i = BrowserList::begin(); + i != BrowserList::end(); ++i) { + if (!(*i)->profile()->IsOffTheRecord()) { + tab_in_browser = FindTabInBrowser(*i, url); + if (tab_in_browser) { + *existing_browser = *i; + return tab_in_browser; + } + } + } + + return NULL; +} + +void TabFinder::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK_EQ(type.value, NotificationType::TAB_PARENTED); + + // The tab was added to a browser. Query for its state now. + NavigationController* controller = + Source<NavigationController>(source).ptr(); + TrackTab(controller->tab_contents()); +} + +TabFinder::TabFinder() { + registrar_.Add(this, NotificationType::TAB_PARENTED, + NotificationService::AllSources()); +} + +TabFinder::~TabFinder() { + STLDeleteElements(&tab_contents_observers_); +} + +void TabFinder::Init() { + for (BrowserList::const_iterator i = BrowserList::begin(); + i != BrowserList::end(); ++i) { + if (!(*i)->profile()->IsOffTheRecord()) + TrackBrowser(*i); + } +} + +void TabFinder::DidNavigateAnyFramePostCommit( + TabContents* source, + const NavigationController::LoadCommittedDetails& details, + const ViewHostMsg_FrameNavigate_Params& params) { + CancelRequestsFor(source); + + if (PageTransition::IsRedirect(params.transition)) { + // If this is a redirect, we need to go to the db to get the start. + FetchRedirectStart(source); + } else if (params.redirects.size() > 1 || + params.redirects[0] != details.entry->url()) { + tab_contents_to_url_[source] = params.redirects[0]; + } +} + +bool TabFinder::TabMatchesURL(TabContents* tab_contents, const GURL& url) { + if (tab_contents->GetURL() == url) + return true; + + TabContentsToURLMap::const_iterator i = + tab_contents_to_url_.find(tab_contents); + return i != tab_contents_to_url_.end() && i->second == url; +} + +TabContents* TabFinder::FindTabInBrowser(Browser* browser, const GURL& url) { + if (browser->type() != Browser::TYPE_NORMAL) + return NULL; + + for (int i = 0; i < browser->tab_count(); ++i) { + if (TabMatchesURL(browser->GetTabContentsAt(i), url)) + return browser->GetTabContentsAt(i); + } + return NULL; +} + +void TabFinder::TrackTab(TabContents* tab) { + for (TabContentsObservers::const_iterator i = tab_contents_observers_.begin(); + i != tab_contents_observers_.end(); ++i) { + if ((*i)->tab_contents() == tab) { + // Already tracking the tab. + return; + } + } + TabContentsObserverImpl* observer = new TabContentsObserverImpl(tab, this); + tab_contents_observers_.insert(observer); + FetchRedirectStart(tab); +} + +void TabFinder::TrackBrowser(Browser* browser) { + for (int i = 0; i < browser->tab_count(); ++i) + FetchRedirectStart(browser->GetTabContentsAt(i)); +} + +void TabFinder::TabDestroyed(TabContentsObserverImpl* observer) { + DCHECK_GT(tab_contents_observers_.count(observer), 0u); + tab_contents_observers_.erase(observer); +} + +void TabFinder::CancelRequestsFor(TabContents* tab_contents) { + if (tab_contents->profile()->IsOffTheRecord()) + return; + + tab_contents_to_url_.erase(tab_contents); + + HistoryService* history = tab_contents->profile()->GetHistoryService( + Profile::EXPLICIT_ACCESS); + if (history) { + CancelableRequestProvider::Handle request_handle; + if (callback_consumer_.GetFirstHandleForClientData(tab_contents, + &request_handle)) { + history->CancelRequest(request_handle); + } + } +} + +void TabFinder::FetchRedirectStart(TabContents* tab) { + if (tab->profile()->IsOffTheRecord()) + return; + + NavigationEntry* committed_entry = tab->controller().GetLastCommittedEntry(); + if (!committed_entry || committed_entry->url().is_empty()) + return; + + HistoryService* history =tab->profile()->GetHistoryService( + Profile::EXPLICIT_ACCESS); + if (history) { + CancelableRequestProvider::Handle request_handle = + history->QueryRedirectsTo( + committed_entry->url(), + &callback_consumer_, + NewCallback(this, &TabFinder::QueryRedirectsToComplete)); + callback_consumer_.SetClientData(history, request_handle, tab); + } +} + +void TabFinder::QueryRedirectsToComplete(HistoryService::Handle handle, + GURL url, + bool success, + history::RedirectList* redirects) { + if (success && !redirects->empty()) { + TabContents* tab_contents = + callback_consumer_.GetClientDataForCurrentRequest(); + DCHECK(tab_contents); + tab_contents_to_url_[tab_contents] = redirects->back(); + } +} diff --git a/chrome/browser/tabs/tab_finder.h b/chrome/browser/tabs/tab_finder.h new file mode 100644 index 0000000..908b238 --- /dev/null +++ b/chrome/browser/tabs/tab_finder.h @@ -0,0 +1,111 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_TABS_TAB_FINDER_H_ +#define CHROME_BROWSER_TABS_TAB_FINDER_H_ +#pragma once + +#include <map> +#include <set> + +#include "base/basictypes.h" +#include "base/singleton.h" +#include "chrome/browser/history/history_types.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "content/browser/cancelable_request.h" +#include "content/browser/tab_contents/navigation_controller.h" + +class Browser; +class GURL; +class TabContents; + +// TabFinder is used to locate a tab by URL. TabFinder matches tabs based +// on the tabs current url, or the start of the redirect chain. +// +// TODO: if we end up keeping this (moving it out of about:flags) then we +// should persist the start of the redirect chain in the navigation entry. +class TabFinder : public NotificationObserver { + public: + // Returns the TabFinder, or NULL if TabFinder is not enabled. + static TabFinder* GetInstance(); + + // Returns true if TabFinder is enabled. + static bool IsEnabled(); + + // Returns the tab that matches the specified url. If a tab is found the + // browser containing the tab is set in |existing_browser|. This searches + // in |browser| first before checking any other browsers. + TabContents* FindTab(Browser* browser, + const GURL& url, + Browser** existing_browser); + + // NotificationObserver overrides: + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) OVERRIDE; + + private: + friend struct DefaultSingletonTraits<TabFinder>; + + class TabContentsObserverImpl; + + typedef std::map<TabContents*, GURL> TabContentsToURLMap; + typedef std::set<TabContentsObserverImpl*> TabContentsObservers; + + TabFinder(); + ~TabFinder(); + + void Init(); + + // Forwarded from TabContentsObserverImpl. + void DidNavigateAnyFramePostCommit( + TabContents* source, + const NavigationController::LoadCommittedDetails& details, + const ViewHostMsg_FrameNavigate_Params& params); + + // Returns true if the tab's current url is |url|, or the start of the + // redirect chain for the tab is |url|. + bool TabMatchesURL(TabContents* tab_contents, const GURL& url); + + // Returns the first tab in the specified browser that matches the specified + // url. Returns NULL if there are no tabs matching the specified url. + TabContents* FindTabInBrowser(Browser* browser, const GURL& url); + + // If we're not currently tracking |tab| this creates a + // TabContentsObserverImpl to listen for navigations. + void TrackTab(TabContents* tab); + + // Queries all the tabs in |browser| for the start of the redirect chain. + void TrackBrowser(Browser* browser); + + // Invoked when a TabContents is being destroyed. + void TabDestroyed(TabContentsObserverImpl* observer); + + // Cancels any pending requests for the specified tabs redirect chain. + void CancelRequestsFor(TabContents* tab_contents); + + // Starts the fetch for the redirect chain of the specified TabContents. + // QueryRedirectsToComplete is invoked when the redirect chain is retrieved. + void FetchRedirectStart(TabContents* tab); + + // Callback when we get the redirect list for a tab. + void QueryRedirectsToComplete(CancelableRequestProvider::Handle handle, + GURL url, + bool success, + history::RedirectList* redirects); + + // Maps from TabContents to the start of the redirect chain. + TabContentsToURLMap tab_contents_to_url_; + + CancelableRequestConsumerTSimple<TabContents*> callback_consumer_; + + NotificationRegistrar registrar_; + + TabContentsObservers tab_contents_observers_; + + DISALLOW_COPY_AND_ASSIGN(TabFinder); +}; + +#endif // CHROME_BROWSER_TABS_TAB_FINDER_H_ diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc index ae9eba0..eadf787 100644 --- a/chrome/browser/ui/browser.cc +++ b/chrome/browser/ui/browser.cc @@ -66,6 +66,7 @@ #include "chrome/browser/sync/sync_ui_util.h" #include "chrome/browser/tab_closeable_state_watcher.h" #include "chrome/browser/tab_contents/simple_alert_infobar_delegate.h" +#include "chrome/browser/tabs/tab_finder.h" #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/browser/ui/find_bar/find_bar.h" #include "chrome/browser/ui/find_bar/find_bar_controller.h" @@ -260,6 +261,10 @@ Browser::Browser(Type type, Profile* profile) profile_->GetProfileSyncService()->AddObserver(this); CreateInstantIfNecessary(); + + // Make sure TabFinder has been created. This does nothing if TabFinder is + // not enabled. + TabFinder::GetInstance(); } Browser::~Browser() { @@ -1302,6 +1307,17 @@ void Browser::OpenCurrentURL() { return; GURL url(WideToUTF8(location_bar->GetInputString())); + + if (open_disposition == CURRENT_TAB && TabFinder::IsEnabled()) { + Browser* existing_browser = NULL; + TabContents* existing_tab = TabFinder::GetInstance()->FindTab( + this, url, &existing_browser); + if (existing_tab) { + existing_browser->ActivateContents(existing_tab); + return; + } + } + browser::NavigateParams params(this, url, location_bar->GetPageTransition()); params.disposition = open_disposition; // Use ADD_INHERIT_OPENER so that all pages opened by the omnibox at least diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index e535fd3..293fb27 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -2012,6 +2012,8 @@ 'browser/tabs/pinned_tab_service.cc', 'browser/tabs/pinned_tab_service.h', 'browser/tabs/tab_handler.h', + 'browser/tabs/tab_finder.cc', + 'browser/tabs/tab_finder.h', 'browser/tabs/tab_strip_model.cc', 'browser/tabs/tab_strip_model.h', 'browser/tabs/tab_strip_model_delegate.h', diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index 2c427e0..cd560e3 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -617,6 +617,9 @@ const char kFeedbackServer[] = "feedback-server"; // gracefully. const char kFileDescriptorLimit[] = "file-descriptor-limit"; +// If true opening a url from the omnibox attepts to focus an existing tab. +const char kFocusExistingTabOnOpen[] = "focus-existing-tab-on-open"; + // Display the First Run experience when the browser is started, regardless of // whether or not it's actually the first run. const char kFirstRun[] = "first-run"; diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index 1cf6dda..9880f7f 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -182,6 +182,7 @@ extern const char kFlagSwitchesBegin[]; extern const char kFlagSwitchesEnd[]; extern const char kFeedbackServer[]; extern const char kFileDescriptorLimit[]; +extern const char kFocusExistingTabOnOpen[]; extern const char kFirstRun[]; extern const char kForceAppsPromoVisible[]; extern const char kForceFieldTestNameAndValue[]; diff --git a/content/browser/tab_contents/tab_contents.cc b/content/browser/tab_contents/tab_contents.cc index f837d98..6e7e91b 100644 --- a/content/browser/tab_contents/tab_contents.cc +++ b/content/browser/tab_contents/tab_contents.cc @@ -391,7 +391,7 @@ TabContents::~TabContents() { base::TimeTicks::Now() - tab_close_start_time_); } - FOR_EACH_OBSERVER(TabContentsObserver, observers_, set_tab_contents(NULL)); + FOR_EACH_OBSERVER(TabContentsObserver, observers_, TabContentsDestroyed()); net::NetworkChangeNotifier::RemoveOnlineStateObserver(this); } diff --git a/content/browser/tab_contents/tab_contents_observer.cc b/content/browser/tab_contents/tab_contents_observer.cc index a94acbd..c3d9947 100644 --- a/content/browser/tab_contents/tab_contents_observer.cc +++ b/content/browser/tab_contents/tab_contents_observer.cc @@ -7,6 +7,25 @@ #include "content/browser/renderer_host/render_view_host.h" #include "content/browser/tab_contents/tab_contents.h" +void TabContentsObserver::NavigateToPendingEntry() { +} + +void TabContentsObserver::DidNavigateMainFramePostCommit( + const NavigationController::LoadCommittedDetails& details, + const ViewHostMsg_FrameNavigate_Params& params) { +} + +void TabContentsObserver::DidNavigateAnyFramePostCommit( + const NavigationController::LoadCommittedDetails& details, + const ViewHostMsg_FrameNavigate_Params& params) { +} + +void TabContentsObserver::DidStartLoading() { +} + +void TabContentsObserver::DidStopLoading() { +} + TabContentsObserver::TabContentsObserver(TabContents* tab_contents) : tab_contents_(tab_contents), routing_id_(tab_contents->render_view_host()->routing_id()) { @@ -18,6 +37,9 @@ TabContentsObserver::~TabContentsObserver() { tab_contents_->RemoveObserver(this); } +void TabContentsObserver::OnTabContentsDestroyed(TabContents* tab) { +} + bool TabContentsObserver::OnMessageReceived(const IPC::Message& message) { return false; } @@ -30,3 +52,12 @@ bool TabContentsObserver::Send(IPC::Message* message) { return tab_contents_->render_view_host()->Send(message); } + +void TabContentsObserver::TabContentsDestroyed() { + // Do cleanup so that 'this' can safely be deleted from + // OnTabContentsDestroyed. + tab_contents_->RemoveObserver(this); + TabContents* tab = tab_contents_; + tab_contents_ = NULL; + OnTabContentsDestroyed(tab); +} diff --git a/content/browser/tab_contents/tab_contents_observer.h b/content/browser/tab_contents/tab_contents_observer.h index b8bb343..82fc220 100644 --- a/content/browser/tab_contents/tab_contents_observer.h +++ b/content/browser/tab_contents/tab_contents_observer.h @@ -14,17 +14,17 @@ struct ViewHostMsg_FrameNavigate_Params; // load events from TabContents. They also get a chance to filter IPC messages. class TabContentsObserver : public IPC::Channel::Listener { public: - virtual void NavigateToPendingEntry() { } + virtual void NavigateToPendingEntry(); virtual void DidNavigateMainFramePostCommit( const NavigationController::LoadCommittedDetails& details, - const ViewHostMsg_FrameNavigate_Params& params) { } + const ViewHostMsg_FrameNavigate_Params& params); virtual void DidNavigateAnyFramePostCommit( const NavigationController::LoadCommittedDetails& details, - const ViewHostMsg_FrameNavigate_Params& params) { } + const ViewHostMsg_FrameNavigate_Params& params); - virtual void DidStartLoading() { } - virtual void DidStopLoading() { } + virtual void DidStartLoading(); + virtual void DidStopLoading(); #if 0 // For unifying with delegate... @@ -45,6 +45,11 @@ class TabContentsObserver : public IPC::Channel::Listener { TabContentsObserver(TabContents* tab_contents); virtual ~TabContentsObserver(); + // Invoked when the TabContents is being destroyed. Gives subclasses a chance + // to cleanup. At the time this is invoked |tab_contents()| returns NULL. + // It is safe to delete 'this' from here. + virtual void OnTabContentsDestroyed(TabContents* tab); + // IPC::Channel::Listener implementation. virtual bool OnMessageReceived(const IPC::Message& message); @@ -57,9 +62,12 @@ class TabContentsObserver : public IPC::Channel::Listener { private: friend class TabContents; - void set_tab_contents(TabContents* tc) { tab_contents_ = tc; } + // Invoked from TabContents. Invokes OnTabContentsDestroyed and NULL out + // |tab_contents_|. + void TabContentsDestroyed(); TabContents* tab_contents_; + // The routing ID of the associated TabContents. int routing_id_; |