1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/browser_navigator.h"
6
7#include <algorithm>
8
9#include "base/command_line.h"
10#include "chrome/browser/browser_url_handler.h"
11#include "chrome/browser/extensions/extension_tab_helper.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/tabs/tab_strip_model.h"
14#include "chrome/browser/ui/browser.h"
15#include "chrome/browser/ui/browser_list.h"
16#include "chrome/browser/ui/browser_window.h"
17#include "chrome/browser/ui/omnibox/location_bar.h"
18#include "chrome/browser/ui/status_bubble.h"
19#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
20#include "chrome/browser/ui/webui/chrome_web_ui_factory.h"
21#include "chrome/common/chrome_switches.h"
22#include "chrome/common/url_constants.h"
23#include "content/browser/site_instance.h"
24#include "content/browser/tab_contents/tab_contents.h"
25
26namespace {
27
28// Returns an appropriate SiteInstance for WebUI URLs, or the SiteInstance for
29// |source_contents| if it represents the same website as |url|.  Returns NULL
30// otherwise.
31SiteInstance* GetSiteInstance(TabContents* source_contents, Profile* profile,
32                              const GURL& url) {
33  // If url is a WebUI or extension, we need to be sure to use the right type
34  // of renderer process up front.  Otherwise, we create a normal SiteInstance
35  // as part of creating the tab.
36  if (ChromeWebUIFactory::GetInstance()->UseWebUIForURL(profile, url))
37    return SiteInstance::CreateSiteInstanceForURL(profile, url);
38
39  if (!source_contents)
40    return NULL;
41
42  // Don't use this logic when "--process-per-tab" is specified.
43  if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kProcessPerTab) &&
44      SiteInstance::IsSameWebSite(source_contents->profile(),
45                                  source_contents->GetURL(),
46                                  url)) {
47    return source_contents->GetSiteInstance();
48  }
49  return NULL;
50}
51
52// Returns true if the specified Browser can open tabs. Not all Browsers support
53// multiple tabs, such as app frames and popups. This function returns false for
54// those types of Browser.
55bool WindowCanOpenTabs(Browser* browser) {
56  return browser->CanSupportWindowFeature(Browser::FEATURE_TABSTRIP) ||
57      browser->tabstrip_model()->empty();
58}
59
60// Finds an existing Browser compatible with |profile|, making a new one if no
61// such Browser is located.
62Browser* GetOrCreateBrowser(Profile* profile) {
63  Browser* browser = BrowserList::FindBrowserWithType(profile,
64                                                      Browser::TYPE_NORMAL,
65                                                      false);
66  return browser ? browser : Browser::Create(profile);
67}
68
69// Returns true if two URLs are equal after taking |replacements| into account.
70bool CompareURLsWithReplacements(
71    const GURL& url,
72    const GURL& other,
73    const url_canon::Replacements<char>& replacements) {
74  if (url == other)
75    return true;
76
77  GURL url_replaced = url.ReplaceComponents(replacements);
78  GURL other_replaced = other.ReplaceComponents(replacements);
79  return url_replaced == other_replaced;
80}
81
82// Returns the index of an existing singleton tab in |params->browser| matching
83// the URL specified in |params|.
84int GetIndexOfSingletonTab(browser::NavigateParams* params) {
85  if (params->disposition != SINGLETON_TAB)
86    return -1;
87
88  // In case the URL was rewritten by the BrowserURLHandler we need to ensure
89  // that we do not open another URL that will get redirected to the rewritten
90  // URL.
91  GURL rewritten_url(params->url);
92  bool reverse_on_redirect = false;
93  BrowserURLHandler::RewriteURLIfNecessary(&rewritten_url,
94                                           params->browser->profile(),
95                                           &reverse_on_redirect);
96
97  // If there are several matches: prefer the active tab by starting there.
98  int start_index = std::max(0, params->browser->active_index());
99  int tab_count = params->browser->tab_count();
100  for (int i = 0; i < tab_count; ++i) {
101    int tab_index = (start_index + i) % tab_count;
102    TabContentsWrapper* tab =
103        params->browser->GetTabContentsWrapperAt(tab_index);
104
105    url_canon::Replacements<char> replacements;
106    replacements.ClearRef();
107    if (params->path_behavior == browser::NavigateParams::IGNORE_AND_NAVIGATE ||
108        params->path_behavior == browser::NavigateParams::IGNORE_AND_STAY_PUT) {
109      replacements.ClearPath();
110      replacements.ClearQuery();
111    }
112
113    if (CompareURLsWithReplacements(tab->tab_contents()->GetURL(),
114                                    params->url, replacements) ||
115        CompareURLsWithReplacements(tab->tab_contents()->GetURL(),
116                                    rewritten_url, replacements)) {
117      params->target_contents = tab;
118      return tab_index;
119    }
120  }
121
122  return -1;
123}
124
125// Change some of the navigation parameters based on the particular URL.
126// Currently this applies to chrome://settings and the bookmark manager,
127// which we always want to open in a normal (not incognito) window. Guest
128// session is an exception.
129void AdjustNavigateParamsForURL(browser::NavigateParams* params) {
130  if (!params->target_contents &&
131      params->url.scheme() == chrome::kChromeUIScheme &&
132      (params->url.host() == chrome::kChromeUISettingsHost ||
133       params->url.host() == chrome::kChromeUIBookmarksHost)) {
134    Profile* profile =
135        params->browser ? params->browser->profile() : params->profile;
136
137    if (profile->IsOffTheRecord() && !Profile::IsGuestSession()) {
138      profile = profile->GetOriginalProfile();
139
140      params->disposition = SINGLETON_TAB;
141      params->profile = profile;
142      params->browser = Browser::GetOrCreateTabbedBrowser(profile);
143      params->window_action = browser::NavigateParams::SHOW_WINDOW;
144    }
145  }
146}
147
148// Returns a Browser that can host the navigation or tab addition specified in
149// |params|. This might just return the same Browser specified in |params|, or
150// some other if that Browser is deemed incompatible.
151Browser* GetBrowserForDisposition(browser::NavigateParams* params) {
152  // If no source TabContents was specified, we use the selected one from the
153  // target browser. This must happen first, before GetBrowserForDisposition()
154  // has a chance to replace |params->browser| with another one.
155  if (!params->source_contents && params->browser)
156    params->source_contents =
157        params->browser->GetSelectedTabContentsWrapper();
158
159  Profile* profile =
160      params->browser ? params->browser->profile() : params->profile;
161
162  switch (params->disposition) {
163    case CURRENT_TAB:
164      if (!params->browser && profile) {
165        // We specified a profile instead of a browser; find or create one.
166        params->browser = Browser::GetOrCreateTabbedBrowser(profile);
167      }
168      return params->browser;
169    case SINGLETON_TAB:
170    case NEW_FOREGROUND_TAB:
171    case NEW_BACKGROUND_TAB:
172      // See if we can open the tab in the window this navigator is bound to.
173      if (params->browser && WindowCanOpenTabs(params->browser))
174        return params->browser;
175      // Find a compatible window and re-execute this command in it. Otherwise
176      // re-run with NEW_WINDOW.
177      if (profile)
178        return GetOrCreateBrowser(profile);
179      return NULL;
180    case NEW_POPUP: {
181      // Make a new popup window. Coerce app-style if |params->browser| or the
182      // |source| represents an app.
183      Browser::Type type = Browser::TYPE_POPUP;
184      if ((params->browser && (params->browser->type() & Browser::TYPE_APP)) ||
185          (params->source_contents &&
186           params->source_contents->extension_tab_helper()->is_app())) {
187        type = Browser::TYPE_APP_POPUP;
188      }
189      if (profile) {
190        Browser* browser = new Browser(type, profile);
191        browser->set_override_bounds(params->window_bounds);
192        browser->InitBrowserWindow();
193        return browser;
194      }
195      return NULL;
196    }
197    case NEW_WINDOW:
198      // Make a new normal browser window.
199      if (profile) {
200        Browser* browser = new Browser(Browser::TYPE_NORMAL, profile);
201        browser->InitBrowserWindow();
202        return browser;
203      }
204      return NULL;
205    case OFF_THE_RECORD:
206      // Make or find an incognito window.
207      if (profile)
208        return GetOrCreateBrowser(profile->GetOffTheRecordProfile());
209      return NULL;
210    // The following types all result in no navigation.
211    case SUPPRESS_OPEN:
212    case SAVE_TO_DISK:
213    case IGNORE_ACTION:
214      return NULL;
215    default:
216      NOTREACHED();
217  }
218  return NULL;
219}
220
221// Fix disposition and other parameter values depending on prevailing
222// conditions.
223void NormalizeDisposition(browser::NavigateParams* params) {
224  // Calculate the WindowOpenDisposition if necessary.
225  if (params->browser->tabstrip_model()->empty() &&
226      (params->disposition == NEW_BACKGROUND_TAB ||
227       params->disposition == CURRENT_TAB ||
228       params->disposition == SINGLETON_TAB)) {
229    params->disposition = NEW_FOREGROUND_TAB;
230  }
231  if (params->browser->profile()->IsOffTheRecord() &&
232      params->disposition == OFF_THE_RECORD) {
233    params->disposition = NEW_FOREGROUND_TAB;
234  }
235
236  switch (params->disposition) {
237    case NEW_BACKGROUND_TAB:
238      // Disposition trumps add types. ADD_ACTIVE is a default, so we need to
239      // remove it if disposition implies the tab is going to open in the
240      // background.
241      params->tabstrip_add_types &= ~TabStripModel::ADD_ACTIVE;
242      break;
243
244    case NEW_WINDOW:
245    case NEW_POPUP:
246      // Code that wants to open a new window typically expects it to be shown
247      // automatically.
248      if (params->window_action == browser::NavigateParams::NO_ACTION)
249        params->window_action = browser::NavigateParams::SHOW_WINDOW;
250      // Fall-through.
251    case NEW_FOREGROUND_TAB:
252    case SINGLETON_TAB:
253      params->tabstrip_add_types |= TabStripModel::ADD_ACTIVE;
254      break;
255
256    default:
257      break;
258  }
259}
260
261// Obtain the profile used by the code that originated the Navigate() request.
262// |source_browser| represents the Browser that was supplied in |params| before
263// it was modified.
264Profile* GetSourceProfile(browser::NavigateParams* params,
265    Browser* source_browser) {
266  if (params->source_contents)
267    return params->source_contents->profile();
268
269  if (source_browser)
270    return source_browser->profile();
271
272  if (params->profile)
273    return params->profile;
274
275  // We couldn't find one in any of the source metadata, so we'll fall back to
276  // the profile associated with the target browser.
277  return params->browser->profile();
278}
279
280
281// This class makes sure the Browser object held in |params| is made visible
282// by the time it goes out of scope, provided |params| wants it to be shown.
283class ScopedBrowserDisplayer {
284 public:
285  explicit ScopedBrowserDisplayer(browser::NavigateParams* params)
286      : params_(params) {
287  }
288  ~ScopedBrowserDisplayer() {
289    if (params_->window_action == browser::NavigateParams::SHOW_WINDOW_INACTIVE)
290      params_->browser->window()->ShowInactive();
291    else if (params_->window_action == browser::NavigateParams::SHOW_WINDOW)
292      params_->browser->window()->Show();
293  }
294 private:
295  browser::NavigateParams* params_;
296  DISALLOW_COPY_AND_ASSIGN(ScopedBrowserDisplayer);
297};
298
299// This class manages the lifetime of a TabContents created by the Navigate()
300// function. When Navigate() creates a TabContents for a URL, an instance of
301// this class takes ownership of it via TakeOwnership() until the TabContents
302// is added to a tab strip at which time ownership is relinquished via
303// ReleaseOwnership(). If this object goes out of scope without being added
304// to a tab strip, the created TabContents is deleted to avoid a leak and the
305// params->target_contents field is set to NULL.
306class ScopedTargetContentsOwner {
307 public:
308  explicit ScopedTargetContentsOwner(browser::NavigateParams* params)
309      : params_(params) {
310  }
311  ~ScopedTargetContentsOwner() {
312    if (target_contents_owner_.get())
313      params_->target_contents = NULL;
314  }
315
316  // Assumes ownership of |params_|' target_contents until ReleaseOwnership
317  // is called.
318  void TakeOwnership() {
319    target_contents_owner_.reset(params_->target_contents);
320  }
321
322  // Relinquishes ownership of |params_|' target_contents.
323  TabContentsWrapper* ReleaseOwnership() {
324    return target_contents_owner_.release();
325  }
326
327 private:
328  browser::NavigateParams* params_;
329  scoped_ptr<TabContentsWrapper> target_contents_owner_;
330  DISALLOW_COPY_AND_ASSIGN(ScopedTargetContentsOwner);
331};
332
333}  // namespace
334
335namespace browser {
336
337NavigateParams::NavigateParams(
338    Browser* a_browser,
339    const GURL& a_url,
340    PageTransition::Type a_transition)
341    : url(a_url),
342      target_contents(NULL),
343      source_contents(NULL),
344      disposition(CURRENT_TAB),
345      transition(a_transition),
346      tabstrip_index(-1),
347      tabstrip_add_types(TabStripModel::ADD_ACTIVE),
348      window_action(NO_ACTION),
349      path_behavior(RESPECT),
350      browser(a_browser),
351      profile(NULL) {
352}
353
354NavigateParams::NavigateParams(Browser* a_browser,
355                               TabContentsWrapper* a_target_contents)
356    : target_contents(a_target_contents),
357      source_contents(NULL),
358      disposition(CURRENT_TAB),
359      transition(PageTransition::LINK),
360      tabstrip_index(-1),
361      tabstrip_add_types(TabStripModel::ADD_ACTIVE),
362      window_action(NO_ACTION),
363      path_behavior(RESPECT),
364      browser(a_browser),
365      profile(NULL) {
366}
367
368NavigateParams::~NavigateParams() {
369}
370
371void Navigate(NavigateParams* params) {
372  Browser* source_browser = params->browser;
373  AdjustNavigateParamsForURL(params);
374
375  params->browser = GetBrowserForDisposition(params);
376  if (!params->browser)
377    return;
378  // Navigate() must not return early after this point.
379
380  if (GetSourceProfile(params, source_browser) != params->browser->profile()) {
381    // A tab is being opened from a link from a different profile, we must reset
382    // source information that may cause state to be shared.
383    params->source_contents = NULL;
384    params->referrer = GURL();
385  }
386
387  if (params->window_action == browser::NavigateParams::NO_ACTION &&
388      source_browser != params->browser &&
389      params->browser->tabstrip_model()->empty()) {
390    // A new window has been created. So it needs to be displayed.
391    params->window_action = browser::NavigateParams::SHOW_WINDOW;
392  }
393
394  // Make sure the Browser is shown if params call for it.
395  ScopedBrowserDisplayer displayer(params);
396
397  // Makes sure any TabContents created by this function is destroyed if
398  // not properly added to a tab strip.
399  ScopedTargetContentsOwner target_contents_owner(params);
400
401  // Some dispositions need coercion to base types.
402  NormalizeDisposition(params);
403
404  // Determine if the navigation was user initiated. If it was, we need to
405  // inform the target TabContents, and we may need to update the UI.
406  PageTransition::Type base_transition =
407      PageTransition::StripQualifier(params->transition);
408  bool user_initiated = base_transition == PageTransition::TYPED ||
409                        base_transition == PageTransition::AUTO_BOOKMARK;
410
411  // Check if this is a singleton tab that already exists
412  int singleton_index = GetIndexOfSingletonTab(params);
413
414  // If no target TabContents was specified, we need to construct one if we are
415  // supposed to target a new tab; unless it's a singleton that already exists.
416  if (!params->target_contents && singleton_index < 0) {
417    GURL url = params->url.is_empty() ? params->browser->GetHomePage()
418                                      : params->url;
419    if (params->disposition != CURRENT_TAB) {
420      TabContents* source_contents = params->source_contents ?
421          params->source_contents->tab_contents() : NULL;
422      params->target_contents =
423          Browser::TabContentsFactory(
424              params->browser->profile(),
425              GetSiteInstance(source_contents, params->browser->profile(), url),
426              MSG_ROUTING_NONE,
427              source_contents,
428              NULL);
429      // This function takes ownership of |params->target_contents| until it
430      // is added to a TabStripModel.
431      target_contents_owner.TakeOwnership();
432      params->target_contents->extension_tab_helper()->
433          SetExtensionAppById(params->extension_app_id);
434      // TODO(sky): figure out why this is needed. Without it we seem to get
435      // failures in startup tests.
436      // By default, content believes it is not hidden.  When adding contents
437      // in the background, tell it that it's hidden.
438      if ((params->tabstrip_add_types & TabStripModel::ADD_ACTIVE) == 0) {
439        // TabStripModel::AddTabContents invokes HideContents if not foreground.
440        params->target_contents->tab_contents()->WasHidden();
441      }
442    } else {
443      // ... otherwise if we're loading in the current tab, the target is the
444      // same as the source.
445      params->target_contents = params->source_contents;
446      DCHECK(params->target_contents);
447    }
448
449    if (user_initiated) {
450      static_cast<RenderViewHostDelegate*>(params->target_contents->
451          tab_contents())->OnUserGesture();
452    }
453
454    // Perform the actual navigation.
455    params->target_contents->controller().LoadURL(url, params->referrer,
456                                                  params->transition);
457  } else {
458    // |target_contents| was specified non-NULL, and so we assume it has already
459    // been navigated appropriately. We need to do nothing more other than
460    // add it to the appropriate tabstrip.
461  }
462
463  if (params->source_contents == params->target_contents) {
464    // The navigation occurred in the source tab.
465    params->browser->UpdateUIForNavigationInTab(
466        params->target_contents,
467        params->transition,
468        user_initiated);
469  } else if (singleton_index == -1) {
470    // If some non-default value is set for the index, we should tell the
471    // TabStripModel to respect it.
472    if (params->tabstrip_index != -1)
473      params->tabstrip_add_types |= TabStripModel::ADD_FORCE_INDEX;
474
475    // The navigation should insert a new tab into the target Browser.
476    params->browser->tabstrip_model()->AddTabContents(
477        params->target_contents,
478        params->tabstrip_index,
479        params->transition,
480        params->tabstrip_add_types);
481    // Now that the |params->target_contents| is safely owned by the target
482    // Browser's TabStripModel, we can release ownership.
483    target_contents_owner.ReleaseOwnership();
484  }
485
486  if (singleton_index >= 0) {
487    TabContents* target = params->browser->GetTabContentsAt(singleton_index);
488
489    if (params->path_behavior == NavigateParams::IGNORE_AND_NAVIGATE &&
490        target->GetURL() != params->url) {
491      target->controller().LoadURL(
492          params->url, params->referrer, params->transition);
493    }
494
495    // If the singleton tab isn't already selected, select it.
496    if (params->source_contents != params->target_contents)
497      params->browser->ActivateTabAt(singleton_index, user_initiated);
498  }
499}
500
501}  // namespace browser
502