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
26 namespace {
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.
GetSiteInstance(TabContents * source_contents,Profile * profile,const GURL & url)31 SiteInstance* 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.
WindowCanOpenTabs(Browser * browser)55 bool 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.
GetOrCreateBrowser(Profile * profile)62 Browser* 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.
CompareURLsWithReplacements(const GURL & url,const GURL & other,const url_canon::Replacements<char> & replacements)70 bool 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|.
GetIndexOfSingletonTab(browser::NavigateParams * params)84 int 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.
AdjustNavigateParamsForURL(browser::NavigateParams * params)129 void 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.
GetBrowserForDisposition(browser::NavigateParams * params)151 Browser* 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.
NormalizeDisposition(browser::NavigateParams * params)223 void 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.
GetSourceProfile(browser::NavigateParams * params,Browser * source_browser)264 Profile* 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.
283 class ScopedBrowserDisplayer {
284 public:
ScopedBrowserDisplayer(browser::NavigateParams * params)285 explicit ScopedBrowserDisplayer(browser::NavigateParams* params)
286 : params_(params) {
287 }
~ScopedBrowserDisplayer()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.
306 class ScopedTargetContentsOwner {
307 public:
ScopedTargetContentsOwner(browser::NavigateParams * params)308 explicit ScopedTargetContentsOwner(browser::NavigateParams* params)
309 : params_(params) {
310 }
~ScopedTargetContentsOwner()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.
TakeOwnership()318 void TakeOwnership() {
319 target_contents_owner_.reset(params_->target_contents);
320 }
321
322 // Relinquishes ownership of |params_|' target_contents.
ReleaseOwnership()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
335 namespace browser {
336
NavigateParams(Browser * a_browser,const GURL & a_url,PageTransition::Type a_transition)337 NavigateParams::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
NavigateParams(Browser * a_browser,TabContentsWrapper * a_target_contents)354 NavigateParams::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
~NavigateParams()368 NavigateParams::~NavigateParams() {
369 }
370
Navigate(NavigateParams * params)371 void 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