browser_navigator.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
1// Copyright (c) 2012 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 "base/stringprintf.h"
11#include "base/utf_string_conversions.h"
12#include "chrome/browser/browser_about_handler.h"
13#include "chrome/browser/extensions/extension_service.h"
14#include "chrome/browser/extensions/tab_helper.h"
15#include "chrome/browser/google/google_url_tracker.h"
16#include "chrome/browser/prefs/incognito_mode_prefs.h"
17#include "chrome/browser/prefs/pref_service.h"
18#include "chrome/browser/prerender/prerender_manager.h"
19#include "chrome/browser/prerender/prerender_manager_factory.h"
20#include "chrome/browser/profiles/profile.h"
21#include "chrome/browser/tab_contents/tab_util.h"
22#include "chrome/browser/ui/browser.h"
23#include "chrome/browser/ui/browser_finder.h"
24#include "chrome/browser/ui/browser_tabstrip.h"
25#include "chrome/browser/ui/browser_window.h"
26#include "chrome/browser/ui/omnibox/location_bar.h"
27#include "chrome/browser/ui/singleton_tabs.h"
28#include "chrome/browser/ui/status_bubble.h"
29#include "chrome/browser/ui/tab_contents/tab_contents.h"
30#include "chrome/browser/ui/tabs/tab_strip_model.h"
31#include "chrome/browser/web_applications/web_app.h"
32#include "chrome/common/chrome_notification_types.h"
33#include "chrome/common/extensions/extension.h"
34#include "chrome/common/pref_names.h"
35#include "chrome/common/url_constants.h"
36#include "content/public/browser/browser_url_handler.h"
37#include "content/public/browser/notification_service.h"
38#include "content/public/browser/render_view_host.h"
39#include "content/public/browser/web_contents.h"
40
41using content::GlobalRequestID;
42using content::WebContents;
43
44namespace {
45
46// Returns true if the specified Browser can open tabs. Not all Browsers support
47// multiple tabs, such as app frames and popups. This function returns false for
48// those types of Browser.
49bool WindowCanOpenTabs(Browser* browser) {
50  return browser->CanSupportWindowFeature(Browser::FEATURE_TABSTRIP) ||
51      browser->tab_strip_model()->empty();
52}
53
54// Finds an existing Browser compatible with |profile|, making a new one if no
55// such Browser is located.
56Browser* GetOrCreateBrowser(Profile* profile,
57                            chrome::HostDesktopType host_desktop_type) {
58  Browser* browser =
59      browser::FindTabbedBrowser(profile, false, host_desktop_type);
60  return browser ? browser : new Browser(Browser::CreateParams(profile));
61}
62
63// Change some of the navigation parameters based on the particular URL.
64// Currently this applies to some chrome:// pages which we always want to open
65// in a non-incognito window. Note that even though a ChromeOS guest session is
66// technically an incognito window, these URLs are allowed.
67// Returns true on success. Otherwise, if changing params leads the browser into
68// an erroneous state, returns false.
69bool AdjustNavigateParamsForURL(chrome::NavigateParams* params) {
70  if (params->target_contents != NULL ||
71      chrome::IsURLAllowedInIncognito(params->url,
72          params->initiating_profile) ||
73      Profile::IsGuestSession()) {
74    return true;
75  }
76
77  Profile* profile = params->initiating_profile;
78
79  if (profile->IsOffTheRecord() || params->disposition == OFF_THE_RECORD) {
80    profile = profile->GetOriginalProfile();
81
82    // If incognito is forced, we punt.
83    PrefService* prefs = profile->GetPrefs();
84    if (prefs && IncognitoModePrefs::GetAvailability(prefs) ==
85            IncognitoModePrefs::FORCED) {
86      return false;
87    }
88
89    params->disposition = SINGLETON_TAB;
90    params->browser =
91        browser::FindOrCreateTabbedBrowser(profile, params->host_desktop_type);
92    params->window_action = chrome::NavigateParams::SHOW_WINDOW;
93  }
94
95  return true;
96}
97
98// Returns a Browser that can host the navigation or tab addition specified in
99// |params|. This might just return the same Browser specified in |params|, or
100// some other if that Browser is deemed incompatible.
101Browser* GetBrowserForDisposition(chrome::NavigateParams* params) {
102  // If no source TabContents was specified, we use the selected one from
103  // the target browser. This must happen first, before
104  // GetBrowserForDisposition() has a chance to replace |params->browser| with
105  // another one.
106  if (!params->source_contents && params->browser)
107    params->source_contents = chrome::GetActiveTabContents(params->browser);
108
109  Profile* profile = params->initiating_profile;
110
111  switch (params->disposition) {
112    case CURRENT_TAB:
113      if (params->browser)
114        return params->browser;
115      // Find a compatible window and re-execute this command in it. Otherwise
116      // re-run with NEW_WINDOW.
117      return GetOrCreateBrowser(profile, params->host_desktop_type);
118    case SINGLETON_TAB:
119    case NEW_FOREGROUND_TAB:
120    case NEW_BACKGROUND_TAB:
121      // See if we can open the tab in the window this navigator is bound to.
122      if (params->browser && WindowCanOpenTabs(params->browser))
123        return params->browser;
124      // Find a compatible window and re-execute this command in it. Otherwise
125      // re-run with NEW_WINDOW.
126      return GetOrCreateBrowser(profile, params->host_desktop_type);
127    case NEW_POPUP: {
128      // Make a new popup window.
129      // Coerce app-style if |source| represents an app.
130      std::string app_name;
131      if (!params->extension_app_id.empty()) {
132        app_name = web_app::GenerateApplicationNameFromExtensionId(
133            params->extension_app_id);
134      } else if (!params->browser->app_name().empty()) {
135        app_name = params->browser->app_name();
136      } else if (params->source_contents) {
137        extensions::TabHelper* extensions_tab_helper =
138            extensions::TabHelper::FromWebContents(
139                params->source_contents->web_contents());
140        if (extensions_tab_helper->is_app()) {
141          app_name = web_app::GenerateApplicationNameFromExtensionId(
142              extensions_tab_helper->extension_app()->id());
143        }
144      }
145      if (app_name.empty()) {
146        Browser::CreateParams browser_params(Browser::TYPE_POPUP, profile);
147        browser_params.initial_bounds = params->window_bounds;
148        return new Browser(browser_params);
149      }
150
151      return new Browser(Browser::CreateParams::CreateForApp(
152          Browser::TYPE_POPUP, app_name, params->window_bounds, profile));
153    }
154    case NEW_WINDOW: {
155      // Make a new normal browser window.
156      return new Browser(Browser::CreateParams(profile));
157    }
158    case OFF_THE_RECORD:
159      // Make or find an incognito window.
160      return GetOrCreateBrowser(profile->GetOffTheRecordProfile(),
161                                params->host_desktop_type);
162    // The following types all result in no navigation.
163    case SUPPRESS_OPEN:
164    case SAVE_TO_DISK:
165    case IGNORE_ACTION:
166      return NULL;
167    default:
168      NOTREACHED();
169  }
170  return NULL;
171}
172
173// Fix disposition and other parameter values depending on prevailing
174// conditions.
175void NormalizeDisposition(chrome::NavigateParams* params) {
176  // Calculate the WindowOpenDisposition if necessary.
177  if (params->browser->tab_strip_model()->empty() &&
178      (params->disposition == NEW_BACKGROUND_TAB ||
179       params->disposition == CURRENT_TAB ||
180       params->disposition == SINGLETON_TAB)) {
181    params->disposition = NEW_FOREGROUND_TAB;
182  }
183  if (params->browser->profile()->IsOffTheRecord() &&
184      params->disposition == OFF_THE_RECORD) {
185    params->disposition = NEW_FOREGROUND_TAB;
186  }
187  if (!params->source_contents && params->disposition == CURRENT_TAB)
188    params->disposition = NEW_FOREGROUND_TAB;
189
190  switch (params->disposition) {
191    case NEW_BACKGROUND_TAB:
192      // Disposition trumps add types. ADD_ACTIVE is a default, so we need to
193      // remove it if disposition implies the tab is going to open in the
194      // background.
195      params->tabstrip_add_types &= ~TabStripModel::ADD_ACTIVE;
196      break;
197
198    case NEW_WINDOW:
199    case NEW_POPUP:
200      // Code that wants to open a new window typically expects it to be shown
201      // automatically.
202      if (params->window_action == chrome::NavigateParams::NO_ACTION)
203        params->window_action = chrome::NavigateParams::SHOW_WINDOW;
204      // Fall-through.
205    case NEW_FOREGROUND_TAB:
206    case SINGLETON_TAB:
207      params->tabstrip_add_types |= TabStripModel::ADD_ACTIVE;
208      break;
209
210    default:
211      break;
212  }
213}
214
215// Obtain the profile used by the code that originated the Navigate() request.
216Profile* GetSourceProfile(chrome::NavigateParams* params) {
217  if (params->source_contents)
218    return params->source_contents->profile();
219
220  return params->initiating_profile;
221}
222
223void LoadURLInContents(WebContents* target_contents,
224                       const GURL& url,
225                       chrome::NavigateParams* params) {
226  content::NavigationController::LoadURLParams load_url_params(url);
227  load_url_params.referrer = params->referrer;
228  load_url_params.transition_type = params->transition;
229  load_url_params.extra_headers = params->extra_headers;
230
231  if (params->transferred_global_request_id != GlobalRequestID()) {
232    load_url_params.is_renderer_initiated = params->is_renderer_initiated;
233    load_url_params.transferred_global_request_id =
234        params->transferred_global_request_id;
235  } else if (params->is_renderer_initiated) {
236    load_url_params.is_renderer_initiated = true;
237  }
238  target_contents->GetController().LoadURLWithParams(load_url_params);
239}
240
241// This class makes sure the Browser object held in |params| is made visible
242// by the time it goes out of scope, provided |params| wants it to be shown.
243class ScopedBrowserDisplayer {
244 public:
245  explicit ScopedBrowserDisplayer(chrome::NavigateParams* params)
246      : params_(params) {
247  }
248  ~ScopedBrowserDisplayer() {
249    if (params_->window_action == chrome::NavigateParams::SHOW_WINDOW_INACTIVE)
250      params_->browser->window()->ShowInactive();
251    else if (params_->window_action == chrome::NavigateParams::SHOW_WINDOW)
252      params_->browser->window()->Show();
253  }
254 private:
255  chrome::NavigateParams* params_;
256  DISALLOW_COPY_AND_ASSIGN(ScopedBrowserDisplayer);
257};
258
259// This class manages the lifetime of a TabContents created by the
260// Navigate() function. When Navigate() creates a TabContents for a URL,
261// an instance of this class takes ownership of it via TakeOwnership() until the
262// TabContents is added to a tab strip at which time ownership is
263// relinquished via ReleaseOwnership(). If this object goes out of scope without
264// being added to a tab strip, the created TabContents is deleted to
265// avoid a leak and the params->target_contents field is set to NULL.
266class ScopedTargetContentsOwner {
267 public:
268  explicit ScopedTargetContentsOwner(chrome::NavigateParams* params)
269      : params_(params) {
270  }
271  ~ScopedTargetContentsOwner() {
272    if (target_contents_owner_.get())
273      params_->target_contents = NULL;
274  }
275
276  // Assumes ownership of |params_|' target_contents until ReleaseOwnership
277  // is called.
278  void TakeOwnership() {
279    target_contents_owner_.reset(params_->target_contents);
280  }
281
282  // Relinquishes ownership of |params_|' target_contents.
283  TabContents* ReleaseOwnership() {
284    return target_contents_owner_.release();
285  }
286
287 private:
288  chrome::NavigateParams* params_;
289  scoped_ptr<TabContents> target_contents_owner_;
290  DISALLOW_COPY_AND_ASSIGN(ScopedTargetContentsOwner);
291};
292
293// If a prerendered page exists for |url|, replace the page at |target_contents|
294// with it.
295bool SwapInPrerender(TabContents* target_contents, const GURL& url) {
296  prerender::PrerenderManager* prerender_manager =
297      prerender::PrerenderManagerFactory::GetForProfile(
298          target_contents->profile());
299  WebContents* web_contents = target_contents->web_contents();
300  return prerender_manager &&
301      prerender_manager->MaybeUsePrerenderedPage(web_contents, url);
302}
303
304}  // namespace
305
306namespace chrome {
307
308NavigateParams::NavigateParams(Browser* a_browser,
309                               const GURL& a_url,
310                               content::PageTransition a_transition)
311    : url(a_url),
312      target_contents(NULL),
313      source_contents(NULL),
314      disposition(CURRENT_TAB),
315      transition(a_transition),
316      is_renderer_initiated(false),
317      tabstrip_index(-1),
318      tabstrip_add_types(TabStripModel::ADD_ACTIVE),
319      window_action(NO_ACTION),
320      user_gesture(true),
321      path_behavior(RESPECT),
322      ref_behavior(IGNORE_REF),
323      browser(a_browser),
324      initiating_profile(NULL) {
325        if (a_browser)
326          host_desktop_type = a_browser->host_desktop_type();
327        else
328          host_desktop_type = chrome::HOST_DESKTOP_TYPE_NATIVE;
329      }
330
331NavigateParams::NavigateParams(Browser* a_browser,
332                               TabContents* a_target_contents)
333    : target_contents(a_target_contents),
334      source_contents(NULL),
335      disposition(CURRENT_TAB),
336      transition(content::PAGE_TRANSITION_LINK),
337      is_renderer_initiated(false),
338      tabstrip_index(-1),
339      tabstrip_add_types(TabStripModel::ADD_ACTIVE),
340      window_action(NO_ACTION),
341      user_gesture(true),
342      path_behavior(RESPECT),
343      ref_behavior(IGNORE_REF),
344      browser(a_browser),
345      initiating_profile(NULL) {
346        if (a_browser)
347          host_desktop_type = a_browser->host_desktop_type();
348        else
349          host_desktop_type = chrome::HOST_DESKTOP_TYPE_NATIVE;
350      }
351
352NavigateParams::NavigateParams(Profile* a_profile,
353                               const GURL& a_url,
354                               content::PageTransition a_transition)
355    : url(a_url),
356      target_contents(NULL),
357      source_contents(NULL),
358      disposition(NEW_FOREGROUND_TAB),
359      transition(a_transition),
360      is_renderer_initiated(false),
361      tabstrip_index(-1),
362      tabstrip_add_types(TabStripModel::ADD_ACTIVE),
363      window_action(SHOW_WINDOW),
364      user_gesture(true),
365      path_behavior(RESPECT),
366      ref_behavior(IGNORE_REF),
367      browser(NULL),
368      initiating_profile(a_profile),
369      host_desktop_type(chrome::HOST_DESKTOP_TYPE_NATIVE) {}
370
371NavigateParams::~NavigateParams() {}
372
373void Navigate(NavigateParams* params) {
374  Browser* source_browser = params->browser;
375  if (source_browser)
376    params->initiating_profile = source_browser->profile();
377  DCHECK(params->initiating_profile);
378
379  if (!AdjustNavigateParamsForURL(params))
380    return;
381
382  ExtensionService* service = params->initiating_profile->GetExtensionService();
383  if (service)
384    service->ShouldBlockUrlInBrowserTab(&params->url);
385
386  // The browser window may want to adjust the disposition.
387  if (params->disposition == NEW_POPUP &&
388      source_browser &&
389      source_browser->window()) {
390    params->disposition =
391        source_browser->window()->GetDispositionForPopupBounds(
392            params->window_bounds);
393  }
394
395  params->browser = GetBrowserForDisposition(params);
396
397  if (!params->browser)
398    return;
399
400  // Navigate() must not return early after this point.
401
402  if (GetSourceProfile(params) != params->browser->profile()) {
403    // A tab is being opened from a link from a different profile, we must reset
404    // source information that may cause state to be shared.
405    params->source_contents = NULL;
406    params->referrer = content::Referrer();
407  }
408
409  // Make sure the Browser is shown if params call for it.
410  ScopedBrowserDisplayer displayer(params);
411
412  // Makes sure any TabContents created by this function is destroyed if
413  // not properly added to a tab strip.
414  ScopedTargetContentsOwner target_contents_owner(params);
415
416  // Some dispositions need coercion to base types.
417  NormalizeDisposition(params);
418
419  // If a new window has been created, it needs to be displayed.
420  if (params->window_action == NavigateParams::NO_ACTION &&
421      source_browser != params->browser &&
422      params->browser->tab_strip_model()->empty()) {
423    params->window_action = NavigateParams::SHOW_WINDOW;
424  }
425
426  // If we create a popup window from a non user-gesture, don't activate it.
427  if (params->window_action == NavigateParams::SHOW_WINDOW &&
428      params->disposition == NEW_POPUP &&
429      params->user_gesture == false) {
430    params->window_action = NavigateParams::SHOW_WINDOW_INACTIVE;
431  }
432
433  // Determine if the navigation was user initiated. If it was, we need to
434  // inform the target TabContents, and we may need to update the UI.
435  content::PageTransition base_transition =
436      content::PageTransitionStripQualifier(params->transition);
437  bool user_initiated =
438      params->transition & content::PAGE_TRANSITION_FROM_ADDRESS_BAR ||
439      base_transition == content::PAGE_TRANSITION_TYPED ||
440      base_transition == content::PAGE_TRANSITION_AUTO_BOOKMARK ||
441      base_transition == content::PAGE_TRANSITION_GENERATED ||
442      base_transition == content::PAGE_TRANSITION_AUTO_TOPLEVEL ||
443      base_transition == content::PAGE_TRANSITION_RELOAD ||
444      base_transition == content::PAGE_TRANSITION_KEYWORD;
445
446  // Check if this is a singleton tab that already exists
447  int singleton_index = chrome::GetIndexOfSingletonTab(params);
448
449  // If no target TabContents was specified, we need to construct one if
450  // we are supposed to target a new tab; unless it's a singleton that already
451  // exists.
452  if (!params->target_contents && singleton_index < 0) {
453    GURL url;
454    if (params->url.is_empty()) {
455      url = params->browser->profile()->GetHomePage();
456      params->transition = content::PageTransitionFromInt(
457          params->transition | content::PAGE_TRANSITION_HOME_PAGE);
458    } else {
459      url = params->url;
460    }
461
462    if (params->disposition != CURRENT_TAB) {
463      WebContents* source_contents = params->source_contents ?
464          params->source_contents->web_contents() : NULL;
465      params->target_contents =
466          chrome::TabContentsFactory(
467              params->browser->profile(),
468              tab_util::GetSiteInstanceForNewTab(
469                  params->browser->profile(), url),
470              MSG_ROUTING_NONE,
471              source_contents);
472      // This function takes ownership of |params->target_contents| until it
473      // is added to a TabStripModel.
474      target_contents_owner.TakeOwnership();
475      extensions::TabHelper::FromWebContents(
476          params->target_contents->web_contents())->
477              SetExtensionAppById(params->extension_app_id);
478      // TODO(sky): figure out why this is needed. Without it we seem to get
479      // failures in startup tests.
480      // By default, content believes it is not hidden.  When adding contents
481      // in the background, tell it that it's hidden.
482      if ((params->tabstrip_add_types & TabStripModel::ADD_ACTIVE) == 0) {
483        // TabStripModel::AddTabContents invokes WasHidden if not foreground.
484        params->target_contents->web_contents()->WasHidden();
485      }
486    } else {
487      // ... otherwise if we're loading in the current tab, the target is the
488      // same as the source.
489      params->target_contents = params->source_contents;
490      DCHECK(params->target_contents);
491    }
492
493    if (user_initiated)
494      params->target_contents->web_contents()->UserGestureDone();
495
496    if (SwapInPrerender(params->target_contents, url))
497      return;
498
499    // Try to handle non-navigational URLs that popup dialogs and such, these
500    // should not actually navigate.
501    if (!HandleNonNavigationAboutURL(url)) {
502      // Perform the actual navigation, tracking whether it came from the
503      // renderer.
504
505      LoadURLInContents(params->target_contents->web_contents(), url, params);
506    }
507  } else {
508    // |target_contents| was specified non-NULL, and so we assume it has already
509    // been navigated appropriately. We need to do nothing more other than
510    // add it to the appropriate tabstrip.
511  }
512
513  // If the user navigated from the omnibox, and the selected tab is going to
514  // lose focus, then make sure the focus for the source tab goes away from the
515  // omnibox.
516  if (params->source_contents &&
517      (params->disposition == NEW_FOREGROUND_TAB ||
518       params->disposition == NEW_WINDOW) &&
519      (params->tabstrip_add_types & TabStripModel::ADD_INHERIT_OPENER))
520    params->source_contents->web_contents()->Focus();
521
522  if (params->source_contents == params->target_contents) {
523    // The navigation occurred in the source tab.
524    params->browser->UpdateUIForNavigationInTab(
525        params->target_contents,
526        params->transition,
527        user_initiated);
528  } else if (singleton_index == -1) {
529    // If some non-default value is set for the index, we should tell the
530    // TabStripModel to respect it.
531    if (params->tabstrip_index != -1)
532      params->tabstrip_add_types |= TabStripModel::ADD_FORCE_INDEX;
533
534    // The navigation should insert a new tab into the target Browser.
535    params->browser->tab_strip_model()->AddTabContents(
536        params->target_contents,
537        params->tabstrip_index,
538        params->transition,
539        params->tabstrip_add_types);
540    // Now that the |params->target_contents| is safely owned by the target
541    // Browser's TabStripModel, we can release ownership.
542    target_contents_owner.ReleaseOwnership();
543  }
544
545  if (singleton_index >= 0) {
546    WebContents* target =
547        chrome::GetWebContentsAt(params->browser, singleton_index);
548
549    if (target->IsCrashed()) {
550      target->GetController().Reload(true);
551    } else if (params->path_behavior == NavigateParams::IGNORE_AND_NAVIGATE &&
552        target->GetURL() != params->url) {
553      LoadURLInContents(target, params->url, params);
554    }
555
556    // If the singleton tab isn't already selected, select it.
557    if (params->source_contents != params->target_contents)
558      chrome::ActivateTabAt(params->browser, singleton_index, user_initiated);
559  }
560
561  if (params->disposition != CURRENT_TAB) {
562    content::NotificationService::current()->Notify(
563        chrome::NOTIFICATION_TAB_ADDED,
564        content::Source<content::WebContentsDelegate>(params->browser),
565        content::Details<WebContents>(params->target_contents->web_contents()));
566  }
567}
568
569bool IsURLAllowedInIncognito(const GURL& url,
570                             content::BrowserContext* browser_context) {
571  // Most URLs are allowed in incognito; the following are exceptions.
572  // chrome://extensions is on the list because it redirects to
573  // chrome://settings.
574  if (url.scheme() == chrome::kChromeUIScheme &&
575      (url.host() == chrome::kChromeUISettingsHost ||
576       url.host() == chrome::kChromeUISettingsFrameHost ||
577       url.host() == chrome::kChromeUIExtensionsHost ||
578       url.host() == chrome::kChromeUIBookmarksHost ||
579       url.host() == chrome::kChromeUISyncPromoHost ||
580       url.host() == chrome::kChromeUIUberHost)) {
581    return false;
582  }
583
584  GURL rewritten_url = url;
585  bool reverse_on_redirect = false;
586  content::BrowserURLHandler::GetInstance()->RewriteURLIfNecessary(
587      &rewritten_url, browser_context, &reverse_on_redirect);
588
589  // Some URLs are mapped to uber subpages. Do not allow them in incognito.
590  return !(rewritten_url.scheme() == chrome::kChromeUIScheme &&
591           rewritten_url.host() == chrome::kChromeUIUberHost);
592}
593
594}  // namespace chrome
595