| // Copyright (c) 2011 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/ui/browser_navigator.h" |
| |
| #include <algorithm> |
| |
| #include "base/command_line.h" |
| #include "chrome/browser/browser_url_handler.h" |
| #include "chrome/browser/extensions/extension_tab_helper.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/omnibox/location_bar.h" |
| #include "chrome/browser/ui/status_bubble.h" |
| #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" |
| #include "chrome/browser/ui/webui/chrome_web_ui_factory.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/url_constants.h" |
| #include "content/browser/site_instance.h" |
| #include "content/browser/tab_contents/tab_contents.h" |
| |
| namespace { |
| |
| // Returns an appropriate SiteInstance for WebUI URLs, or the SiteInstance for |
| // |source_contents| if it represents the same website as |url|. Returns NULL |
| // otherwise. |
| SiteInstance* GetSiteInstance(TabContents* source_contents, Profile* profile, |
| const GURL& url) { |
| // If url is a WebUI or extension, we need to be sure to use the right type |
| // of renderer process up front. Otherwise, we create a normal SiteInstance |
| // as part of creating the tab. |
| if (ChromeWebUIFactory::GetInstance()->UseWebUIForURL(profile, url)) |
| return SiteInstance::CreateSiteInstanceForURL(profile, url); |
| |
| if (!source_contents) |
| return NULL; |
| |
| // Don't use this logic when "--process-per-tab" is specified. |
| if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kProcessPerTab) && |
| SiteInstance::IsSameWebSite(source_contents->profile(), |
| source_contents->GetURL(), |
| url)) { |
| return source_contents->GetSiteInstance(); |
| } |
| return NULL; |
| } |
| |
| // Returns true if the specified Browser can open tabs. Not all Browsers support |
| // multiple tabs, such as app frames and popups. This function returns false for |
| // those types of Browser. |
| bool WindowCanOpenTabs(Browser* browser) { |
| return browser->CanSupportWindowFeature(Browser::FEATURE_TABSTRIP) || |
| browser->tabstrip_model()->empty(); |
| } |
| |
| // Finds an existing Browser compatible with |profile|, making a new one if no |
| // such Browser is located. |
| Browser* GetOrCreateBrowser(Profile* profile) { |
| Browser* browser = BrowserList::FindBrowserWithType(profile, |
| Browser::TYPE_NORMAL, |
| false); |
| return browser ? browser : Browser::Create(profile); |
| } |
| |
| // Returns true if two URLs are equal after taking |replacements| into account. |
| bool CompareURLsWithReplacements( |
| const GURL& url, |
| const GURL& other, |
| const url_canon::Replacements<char>& replacements) { |
| if (url == other) |
| return true; |
| |
| GURL url_replaced = url.ReplaceComponents(replacements); |
| GURL other_replaced = other.ReplaceComponents(replacements); |
| return url_replaced == other_replaced; |
| } |
| |
| // Returns the index of an existing singleton tab in |params->browser| matching |
| // the URL specified in |params|. |
| int GetIndexOfSingletonTab(browser::NavigateParams* params) { |
| if (params->disposition != SINGLETON_TAB) |
| return -1; |
| |
| // In case the URL was rewritten by the BrowserURLHandler we need to ensure |
| // that we do not open another URL that will get redirected to the rewritten |
| // URL. |
| GURL rewritten_url(params->url); |
| bool reverse_on_redirect = false; |
| BrowserURLHandler::RewriteURLIfNecessary(&rewritten_url, |
| params->browser->profile(), |
| &reverse_on_redirect); |
| |
| // If there are several matches: prefer the active tab by starting there. |
| int start_index = std::max(0, params->browser->active_index()); |
| int tab_count = params->browser->tab_count(); |
| for (int i = 0; i < tab_count; ++i) { |
| int tab_index = (start_index + i) % tab_count; |
| TabContentsWrapper* tab = |
| params->browser->GetTabContentsWrapperAt(tab_index); |
| |
| url_canon::Replacements<char> replacements; |
| replacements.ClearRef(); |
| if (params->path_behavior == browser::NavigateParams::IGNORE_AND_NAVIGATE || |
| params->path_behavior == browser::NavigateParams::IGNORE_AND_STAY_PUT) { |
| replacements.ClearPath(); |
| replacements.ClearQuery(); |
| } |
| |
| if (CompareURLsWithReplacements(tab->tab_contents()->GetURL(), |
| params->url, replacements) || |
| CompareURLsWithReplacements(tab->tab_contents()->GetURL(), |
| rewritten_url, replacements)) { |
| params->target_contents = tab; |
| return tab_index; |
| } |
| } |
| |
| return -1; |
| } |
| |
| // Change some of the navigation parameters based on the particular URL. |
| // Currently this applies to chrome://settings and the bookmark manager, |
| // which we always want to open in a normal (not incognito) window. Guest |
| // session is an exception. |
| void AdjustNavigateParamsForURL(browser::NavigateParams* params) { |
| if (!params->target_contents && |
| params->url.scheme() == chrome::kChromeUIScheme && |
| (params->url.host() == chrome::kChromeUISettingsHost || |
| params->url.host() == chrome::kChromeUIBookmarksHost)) { |
| Profile* profile = |
| params->browser ? params->browser->profile() : params->profile; |
| |
| if (profile->IsOffTheRecord() && !Profile::IsGuestSession()) { |
| profile = profile->GetOriginalProfile(); |
| |
| params->disposition = SINGLETON_TAB; |
| params->profile = profile; |
| params->browser = Browser::GetOrCreateTabbedBrowser(profile); |
| params->window_action = browser::NavigateParams::SHOW_WINDOW; |
| } |
| } |
| } |
| |
| // Returns a Browser that can host the navigation or tab addition specified in |
| // |params|. This might just return the same Browser specified in |params|, or |
| // some other if that Browser is deemed incompatible. |
| Browser* GetBrowserForDisposition(browser::NavigateParams* params) { |
| // If no source TabContents was specified, we use the selected one from the |
| // target browser. This must happen first, before GetBrowserForDisposition() |
| // has a chance to replace |params->browser| with another one. |
| if (!params->source_contents && params->browser) |
| params->source_contents = |
| params->browser->GetSelectedTabContentsWrapper(); |
| |
| Profile* profile = |
| params->browser ? params->browser->profile() : params->profile; |
| |
| switch (params->disposition) { |
| case CURRENT_TAB: |
| if (!params->browser && profile) { |
| // We specified a profile instead of a browser; find or create one. |
| params->browser = Browser::GetOrCreateTabbedBrowser(profile); |
| } |
| return params->browser; |
| case SINGLETON_TAB: |
| case NEW_FOREGROUND_TAB: |
| case NEW_BACKGROUND_TAB: |
| // See if we can open the tab in the window this navigator is bound to. |
| if (params->browser && WindowCanOpenTabs(params->browser)) |
| return params->browser; |
| // Find a compatible window and re-execute this command in it. Otherwise |
| // re-run with NEW_WINDOW. |
| if (profile) |
| return GetOrCreateBrowser(profile); |
| return NULL; |
| case NEW_POPUP: { |
| // Make a new popup window. Coerce app-style if |params->browser| or the |
| // |source| represents an app. |
| Browser::Type type = Browser::TYPE_POPUP; |
| if ((params->browser && (params->browser->type() & Browser::TYPE_APP)) || |
| (params->source_contents && |
| params->source_contents->extension_tab_helper()->is_app())) { |
| type = Browser::TYPE_APP_POPUP; |
| } |
| if (profile) { |
| Browser* browser = new Browser(type, profile); |
| browser->set_override_bounds(params->window_bounds); |
| browser->InitBrowserWindow(); |
| return browser; |
| } |
| return NULL; |
| } |
| case NEW_WINDOW: |
| // Make a new normal browser window. |
| if (profile) { |
| Browser* browser = new Browser(Browser::TYPE_NORMAL, profile); |
| browser->InitBrowserWindow(); |
| return browser; |
| } |
| return NULL; |
| case OFF_THE_RECORD: |
| // Make or find an incognito window. |
| if (profile) |
| return GetOrCreateBrowser(profile->GetOffTheRecordProfile()); |
| return NULL; |
| // The following types all result in no navigation. |
| case SUPPRESS_OPEN: |
| case SAVE_TO_DISK: |
| case IGNORE_ACTION: |
| return NULL; |
| default: |
| NOTREACHED(); |
| } |
| return NULL; |
| } |
| |
| // Fix disposition and other parameter values depending on prevailing |
| // conditions. |
| void NormalizeDisposition(browser::NavigateParams* params) { |
| // Calculate the WindowOpenDisposition if necessary. |
| if (params->browser->tabstrip_model()->empty() && |
| (params->disposition == NEW_BACKGROUND_TAB || |
| params->disposition == CURRENT_TAB || |
| params->disposition == SINGLETON_TAB)) { |
| params->disposition = NEW_FOREGROUND_TAB; |
| } |
| if (params->browser->profile()->IsOffTheRecord() && |
| params->disposition == OFF_THE_RECORD) { |
| params->disposition = NEW_FOREGROUND_TAB; |
| } |
| |
| switch (params->disposition) { |
| case NEW_BACKGROUND_TAB: |
| // Disposition trumps add types. ADD_ACTIVE is a default, so we need to |
| // remove it if disposition implies the tab is going to open in the |
| // background. |
| params->tabstrip_add_types &= ~TabStripModel::ADD_ACTIVE; |
| break; |
| |
| case NEW_WINDOW: |
| case NEW_POPUP: |
| // Code that wants to open a new window typically expects it to be shown |
| // automatically. |
| if (params->window_action == browser::NavigateParams::NO_ACTION) |
| params->window_action = browser::NavigateParams::SHOW_WINDOW; |
| // Fall-through. |
| case NEW_FOREGROUND_TAB: |
| case SINGLETON_TAB: |
| params->tabstrip_add_types |= TabStripModel::ADD_ACTIVE; |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| // Obtain the profile used by the code that originated the Navigate() request. |
| // |source_browser| represents the Browser that was supplied in |params| before |
| // it was modified. |
| Profile* GetSourceProfile(browser::NavigateParams* params, |
| Browser* source_browser) { |
| if (params->source_contents) |
| return params->source_contents->profile(); |
| |
| if (source_browser) |
| return source_browser->profile(); |
| |
| if (params->profile) |
| return params->profile; |
| |
| // We couldn't find one in any of the source metadata, so we'll fall back to |
| // the profile associated with the target browser. |
| return params->browser->profile(); |
| } |
| |
| |
| // This class makes sure the Browser object held in |params| is made visible |
| // by the time it goes out of scope, provided |params| wants it to be shown. |
| class ScopedBrowserDisplayer { |
| public: |
| explicit ScopedBrowserDisplayer(browser::NavigateParams* params) |
| : params_(params) { |
| } |
| ~ScopedBrowserDisplayer() { |
| if (params_->window_action == browser::NavigateParams::SHOW_WINDOW_INACTIVE) |
| params_->browser->window()->ShowInactive(); |
| else if (params_->window_action == browser::NavigateParams::SHOW_WINDOW) |
| params_->browser->window()->Show(); |
| } |
| private: |
| browser::NavigateParams* params_; |
| DISALLOW_COPY_AND_ASSIGN(ScopedBrowserDisplayer); |
| }; |
| |
| // This class manages the lifetime of a TabContents created by the Navigate() |
| // function. When Navigate() creates a TabContents for a URL, an instance of |
| // this class takes ownership of it via TakeOwnership() until the TabContents |
| // is added to a tab strip at which time ownership is relinquished via |
| // ReleaseOwnership(). If this object goes out of scope without being added |
| // to a tab strip, the created TabContents is deleted to avoid a leak and the |
| // params->target_contents field is set to NULL. |
| class ScopedTargetContentsOwner { |
| public: |
| explicit ScopedTargetContentsOwner(browser::NavigateParams* params) |
| : params_(params) { |
| } |
| ~ScopedTargetContentsOwner() { |
| if (target_contents_owner_.get()) |
| params_->target_contents = NULL; |
| } |
| |
| // Assumes ownership of |params_|' target_contents until ReleaseOwnership |
| // is called. |
| void TakeOwnership() { |
| target_contents_owner_.reset(params_->target_contents); |
| } |
| |
| // Relinquishes ownership of |params_|' target_contents. |
| TabContentsWrapper* ReleaseOwnership() { |
| return target_contents_owner_.release(); |
| } |
| |
| private: |
| browser::NavigateParams* params_; |
| scoped_ptr<TabContentsWrapper> target_contents_owner_; |
| DISALLOW_COPY_AND_ASSIGN(ScopedTargetContentsOwner); |
| }; |
| |
| } // namespace |
| |
| namespace browser { |
| |
| NavigateParams::NavigateParams( |
| Browser* a_browser, |
| const GURL& a_url, |
| PageTransition::Type a_transition) |
| : url(a_url), |
| target_contents(NULL), |
| source_contents(NULL), |
| disposition(CURRENT_TAB), |
| transition(a_transition), |
| tabstrip_index(-1), |
| tabstrip_add_types(TabStripModel::ADD_ACTIVE), |
| window_action(NO_ACTION), |
| path_behavior(RESPECT), |
| browser(a_browser), |
| profile(NULL) { |
| } |
| |
| NavigateParams::NavigateParams(Browser* a_browser, |
| TabContentsWrapper* a_target_contents) |
| : target_contents(a_target_contents), |
| source_contents(NULL), |
| disposition(CURRENT_TAB), |
| transition(PageTransition::LINK), |
| tabstrip_index(-1), |
| tabstrip_add_types(TabStripModel::ADD_ACTIVE), |
| window_action(NO_ACTION), |
| path_behavior(RESPECT), |
| browser(a_browser), |
| profile(NULL) { |
| } |
| |
| NavigateParams::~NavigateParams() { |
| } |
| |
| void Navigate(NavigateParams* params) { |
| Browser* source_browser = params->browser; |
| AdjustNavigateParamsForURL(params); |
| |
| params->browser = GetBrowserForDisposition(params); |
| if (!params->browser) |
| return; |
| // Navigate() must not return early after this point. |
| |
| if (GetSourceProfile(params, source_browser) != params->browser->profile()) { |
| // A tab is being opened from a link from a different profile, we must reset |
| // source information that may cause state to be shared. |
| params->source_contents = NULL; |
| params->referrer = GURL(); |
| } |
| |
| if (params->window_action == browser::NavigateParams::NO_ACTION && |
| source_browser != params->browser && |
| params->browser->tabstrip_model()->empty()) { |
| // A new window has been created. So it needs to be displayed. |
| params->window_action = browser::NavigateParams::SHOW_WINDOW; |
| } |
| |
| // Make sure the Browser is shown if params call for it. |
| ScopedBrowserDisplayer displayer(params); |
| |
| // Makes sure any TabContents created by this function is destroyed if |
| // not properly added to a tab strip. |
| ScopedTargetContentsOwner target_contents_owner(params); |
| |
| // Some dispositions need coercion to base types. |
| NormalizeDisposition(params); |
| |
| // Determine if the navigation was user initiated. If it was, we need to |
| // inform the target TabContents, and we may need to update the UI. |
| PageTransition::Type base_transition = |
| PageTransition::StripQualifier(params->transition); |
| bool user_initiated = base_transition == PageTransition::TYPED || |
| base_transition == PageTransition::AUTO_BOOKMARK; |
| |
| // Check if this is a singleton tab that already exists |
| int singleton_index = GetIndexOfSingletonTab(params); |
| |
| // If no target TabContents was specified, we need to construct one if we are |
| // supposed to target a new tab; unless it's a singleton that already exists. |
| if (!params->target_contents && singleton_index < 0) { |
| GURL url = params->url.is_empty() ? params->browser->GetHomePage() |
| : params->url; |
| if (params->disposition != CURRENT_TAB) { |
| TabContents* source_contents = params->source_contents ? |
| params->source_contents->tab_contents() : NULL; |
| params->target_contents = |
| Browser::TabContentsFactory( |
| params->browser->profile(), |
| GetSiteInstance(source_contents, params->browser->profile(), url), |
| MSG_ROUTING_NONE, |
| source_contents, |
| NULL); |
| // This function takes ownership of |params->target_contents| until it |
| // is added to a TabStripModel. |
| target_contents_owner.TakeOwnership(); |
| params->target_contents->extension_tab_helper()-> |
| SetExtensionAppById(params->extension_app_id); |
| // TODO(sky): figure out why this is needed. Without it we seem to get |
| // failures in startup tests. |
| // By default, content believes it is not hidden. When adding contents |
| // in the background, tell it that it's hidden. |
| if ((params->tabstrip_add_types & TabStripModel::ADD_ACTIVE) == 0) { |
| // TabStripModel::AddTabContents invokes HideContents if not foreground. |
| params->target_contents->tab_contents()->WasHidden(); |
| } |
| } else { |
| // ... otherwise if we're loading in the current tab, the target is the |
| // same as the source. |
| params->target_contents = params->source_contents; |
| DCHECK(params->target_contents); |
| } |
| |
| if (user_initiated) { |
| static_cast<RenderViewHostDelegate*>(params->target_contents-> |
| tab_contents())->OnUserGesture(); |
| } |
| |
| // Perform the actual navigation. |
| params->target_contents->controller().LoadURL(url, params->referrer, |
| params->transition); |
| } else { |
| // |target_contents| was specified non-NULL, and so we assume it has already |
| // been navigated appropriately. We need to do nothing more other than |
| // add it to the appropriate tabstrip. |
| } |
| |
| if (params->source_contents == params->target_contents) { |
| // The navigation occurred in the source tab. |
| params->browser->UpdateUIForNavigationInTab( |
| params->target_contents, |
| params->transition, |
| user_initiated); |
| } else if (singleton_index == -1) { |
| // If some non-default value is set for the index, we should tell the |
| // TabStripModel to respect it. |
| if (params->tabstrip_index != -1) |
| params->tabstrip_add_types |= TabStripModel::ADD_FORCE_INDEX; |
| |
| // The navigation should insert a new tab into the target Browser. |
| params->browser->tabstrip_model()->AddTabContents( |
| params->target_contents, |
| params->tabstrip_index, |
| params->transition, |
| params->tabstrip_add_types); |
| // Now that the |params->target_contents| is safely owned by the target |
| // Browser's TabStripModel, we can release ownership. |
| target_contents_owner.ReleaseOwnership(); |
| } |
| |
| if (singleton_index >= 0) { |
| TabContents* target = params->browser->GetTabContentsAt(singleton_index); |
| |
| if (params->path_behavior == NavigateParams::IGNORE_AND_NAVIGATE && |
| target->GetURL() != params->url) { |
| target->controller().LoadURL( |
| params->url, params->referrer, params->transition); |
| } |
| |
| // If the singleton tab isn't already selected, select it. |
| if (params->source_contents != params->target_contents) |
| params->browser->ActivateTabAt(singleton_index, user_initiated); |
| } |
| } |
| |
| } // namespace browser |