extension_tab_util.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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/extensions/extension_tab_util.h"
6
7#include "base/strings/string_number_conversions.h"
8#include "base/strings/stringprintf.h"
9#include "chrome/browser/extensions/api/tabs/tabs_constants.h"
10#include "chrome/browser/extensions/chrome_extension_function.h"
11#include "chrome/browser/extensions/chrome_extension_function_details.h"
12#include "chrome/browser/extensions/tab_helper.h"
13#include "chrome/browser/extensions/window_controller.h"
14#include "chrome/browser/extensions/window_controller_list.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/sessions/session_tab_helper.h"
17#include "chrome/browser/ui/browser.h"
18#include "chrome/browser/ui/browser_finder.h"
19#include "chrome/browser/ui/browser_iterator.h"
20#include "chrome/browser/ui/browser_window.h"
21#include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
22#include "chrome/browser/ui/singleton_tabs.h"
23#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
24#include "chrome/browser/ui/tabs/tab_strip_model.h"
25#include "chrome/common/extensions/api/tabs.h"
26#include "chrome/common/url_constants.h"
27#include "components/url_fixer/url_fixer.h"
28#include "content/public/browser/favicon_status.h"
29#include "content/public/browser/navigation_entry.h"
30#include "content/public/browser/web_contents.h"
31#include "extensions/browser/app_window/app_window.h"
32#include "extensions/browser/app_window/app_window_registry.h"
33#include "extensions/common/constants.h"
34#include "extensions/common/error_utils.h"
35#include "extensions/common/extension.h"
36#include "extensions/common/feature_switch.h"
37#include "extensions/common/manifest_constants.h"
38#include "extensions/common/manifest_handlers/incognito_info.h"
39#include "extensions/common/manifest_handlers/options_page_info.h"
40#include "extensions/common/permissions/api_permission.h"
41#include "extensions/common/permissions/permissions_data.h"
42#include "url/gurl.h"
43
44using content::NavigationEntry;
45using content::WebContents;
46
47namespace extensions {
48
49namespace {
50
51namespace keys = tabs_constants;
52
53WindowController* GetAppWindowController(const WebContents* contents) {
54  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
55  AppWindowRegistry* registry = AppWindowRegistry::Get(profile);
56  if (!registry)
57    return NULL;
58  AppWindow* app_window =
59      registry->GetAppWindowForRenderViewHost(contents->GetRenderViewHost());
60  if (!app_window)
61    return NULL;
62  return WindowControllerList::GetInstance()->FindWindowById(
63      app_window->session_id().id());
64}
65
66// |error_message| can optionally be passed in and will be set with an
67// appropriate message if the window cannot be found by id.
68Browser* GetBrowserInProfileWithId(Profile* profile,
69                                   const int window_id,
70                                   bool include_incognito,
71                                   std::string* error_message) {
72  Profile* incognito_profile =
73      include_incognito && profile->HasOffTheRecordProfile()
74          ? profile->GetOffTheRecordProfile()
75          : NULL;
76  for (chrome::BrowserIterator it; !it.done(); it.Next()) {
77    Browser* browser = *it;
78    if ((browser->profile() == profile ||
79         browser->profile() == incognito_profile) &&
80        ExtensionTabUtil::GetWindowId(browser) == window_id &&
81        browser->window()) {
82      return browser;
83    }
84  }
85
86  if (error_message)
87    *error_message = ErrorUtils::FormatErrorMessage(
88        keys::kWindowNotFoundError, base::IntToString(window_id));
89
90  return NULL;
91}
92
93Browser* CreateBrowser(ChromeUIThreadExtensionFunction* function,
94                       int window_id,
95                       std::string* error) {
96  content::WebContents* web_contents = function->GetAssociatedWebContents();
97  DCHECK(web_contents);
98  DCHECK(web_contents->GetNativeView());
99  DCHECK(!chrome::FindBrowserWithWebContents(web_contents));
100
101  chrome::HostDesktopType desktop_type =
102      chrome::GetHostDesktopTypeForNativeView(web_contents->GetNativeView());
103  Browser::CreateParams params(
104      Browser::TYPE_TABBED, function->GetProfile(), desktop_type);
105  Browser* browser = new Browser(params);
106  browser->window()->Show();
107  return browser;
108}
109
110}  // namespace
111
112ExtensionTabUtil::OpenTabParams::OpenTabParams()
113    : create_browser_if_needed(false) {
114}
115
116ExtensionTabUtil::OpenTabParams::~OpenTabParams() {
117}
118
119// Opens a new tab for a given extension. Returns NULL and sets |error| if an
120// error occurs.
121base::DictionaryValue* ExtensionTabUtil::OpenTab(
122    ChromeUIThreadExtensionFunction* function,
123    const OpenTabParams& params,
124    std::string* error) {
125  // windowId defaults to "current" window.
126  int window_id = extension_misc::kCurrentWindowId;
127  if (params.window_id.get())
128    window_id = *params.window_id;
129
130  Browser* browser = GetBrowserFromWindowID(function, window_id, error);
131  if (!browser) {
132    if (!params.create_browser_if_needed) {
133      return NULL;
134    }
135    browser = CreateBrowser(function, window_id, error);
136    if (!browser)
137      return NULL;
138  }
139
140  // Ensure the selected browser is tabbed.
141  if (!browser->is_type_tabbed() && browser->IsAttemptingToCloseBrowser())
142    browser = chrome::FindTabbedBrowser(function->GetProfile(),
143                                        function->include_incognito(),
144                                        browser->host_desktop_type());
145
146  if (!browser || !browser->window()) {
147    // TODO(rpaquay): Error message?
148    return NULL;
149  }
150
151  // TODO(jstritar): Add a constant, chrome.tabs.TAB_ID_ACTIVE, that
152  // represents the active tab.
153  WebContents* opener = NULL;
154  if (params.opener_tab_id.get()) {
155    int opener_id = *params.opener_tab_id;
156
157    if (!ExtensionTabUtil::GetTabById(opener_id,
158                                      function->GetProfile(),
159                                      function->include_incognito(),
160                                      NULL,
161                                      NULL,
162                                      &opener,
163                                      NULL)) {
164      // TODO(rpaquay): Error message?
165      return NULL;
166    }
167  }
168
169  // TODO(rafaelw): handle setting remaining tab properties:
170  // -title
171  // -favIconUrl
172
173  GURL url;
174  if (params.url.get()) {
175    std::string url_string= *params.url;
176    url = ExtensionTabUtil::ResolvePossiblyRelativeURL(url_string,
177                                                       function->extension());
178    if (!url.is_valid()) {
179      *error =
180          ErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, url_string);
181      return NULL;
182    }
183  } else {
184    url = GURL(chrome::kChromeUINewTabURL);
185  }
186
187  // Don't let extensions crash the browser or renderers.
188  if (ExtensionTabUtil::IsCrashURL(url)) {
189    *error = keys::kNoCrashBrowserError;
190    return NULL;
191  }
192
193  // Default to foreground for the new tab. The presence of 'active' property
194  // will override this default.
195  bool active = true;
196  if (params.active.get())
197    active = *params.active;
198
199  // Default to not pinning the tab. Setting the 'pinned' property to true
200  // will override this default.
201  bool pinned = false;
202  if (params.pinned.get())
203    pinned = *params.pinned;
204
205  // We can't load extension URLs into incognito windows unless the extension
206  // uses split mode. Special case to fall back to a tabbed window.
207  if (url.SchemeIs(kExtensionScheme) &&
208      !IncognitoInfo::IsSplitMode(function->extension()) &&
209      browser->profile()->IsOffTheRecord()) {
210    Profile* profile = browser->profile()->GetOriginalProfile();
211    chrome::HostDesktopType desktop_type = browser->host_desktop_type();
212
213    browser = chrome::FindTabbedBrowser(profile, false, desktop_type);
214    if (!browser) {
215      browser = new Browser(
216          Browser::CreateParams(Browser::TYPE_TABBED, profile, desktop_type));
217      browser->window()->Show();
218    }
219  }
220
221  // If index is specified, honor the value, but keep it bound to
222  // -1 <= index <= tab_strip->count() where -1 invokes the default behavior.
223  int index = -1;
224  if (params.index.get())
225    index = *params.index;
226
227  TabStripModel* tab_strip = browser->tab_strip_model();
228
229  index = std::min(std::max(index, -1), tab_strip->count());
230
231  int add_types = active ? TabStripModel::ADD_ACTIVE : TabStripModel::ADD_NONE;
232  add_types |= TabStripModel::ADD_FORCE_INDEX;
233  if (pinned)
234    add_types |= TabStripModel::ADD_PINNED;
235  chrome::NavigateParams navigate_params(
236      browser, url, ui::PAGE_TRANSITION_LINK);
237  navigate_params.disposition =
238      active ? NEW_FOREGROUND_TAB : NEW_BACKGROUND_TAB;
239  navigate_params.tabstrip_index = index;
240  navigate_params.tabstrip_add_types = add_types;
241  chrome::Navigate(&navigate_params);
242
243  // The tab may have been created in a different window, so make sure we look
244  // at the right tab strip.
245  tab_strip = navigate_params.browser->tab_strip_model();
246  int new_index =
247      tab_strip->GetIndexOfWebContents(navigate_params.target_contents);
248  if (opener)
249    tab_strip->SetOpenerOfWebContentsAt(new_index, opener);
250
251  if (active)
252    navigate_params.target_contents->SetInitialFocus();
253
254  // Return data about the newly created tab.
255  return ExtensionTabUtil::CreateTabValue(navigate_params.target_contents,
256                                          tab_strip,
257                                          new_index,
258                                          function->extension());
259}
260
261Browser* ExtensionTabUtil::GetBrowserFromWindowID(
262    ChromeUIThreadExtensionFunction* function,
263    int window_id,
264    std::string* error) {
265  if (window_id == extension_misc::kCurrentWindowId) {
266    Browser* result = function->GetCurrentBrowser();
267    if (!result || !result->window()) {
268      if (error)
269        *error = keys::kNoCurrentWindowError;
270      return NULL;
271    }
272    return result;
273  } else {
274    return GetBrowserInProfileWithId(function->GetProfile(),
275                                     window_id,
276                                     function->include_incognito(),
277                                     error);
278  }
279}
280
281Browser* ExtensionTabUtil::GetBrowserFromWindowID(
282    const ChromeExtensionFunctionDetails& details,
283    int window_id,
284    std::string* error) {
285  if (window_id == extension_misc::kCurrentWindowId) {
286    Browser* result = details.GetCurrentBrowser();
287    if (!result || !result->window()) {
288      if (error)
289        *error = keys::kNoCurrentWindowError;
290      return NULL;
291    }
292    return result;
293  } else {
294    return GetBrowserInProfileWithId(details.GetProfile(),
295                                     window_id,
296                                     details.function()->include_incognito(),
297                                     error);
298  }
299}
300
301int ExtensionTabUtil::GetWindowId(const Browser* browser) {
302  return browser->session_id().id();
303}
304
305int ExtensionTabUtil::GetWindowIdOfTabStripModel(
306    const TabStripModel* tab_strip_model) {
307  for (chrome::BrowserIterator it; !it.done(); it.Next()) {
308    if (it->tab_strip_model() == tab_strip_model)
309      return GetWindowId(*it);
310  }
311  return -1;
312}
313
314int ExtensionTabUtil::GetTabId(const WebContents* web_contents) {
315  return SessionTabHelper::IdForTab(web_contents);
316}
317
318std::string ExtensionTabUtil::GetTabStatusText(bool is_loading) {
319  return is_loading ? keys::kStatusValueLoading : keys::kStatusValueComplete;
320}
321
322int ExtensionTabUtil::GetWindowIdOfTab(const WebContents* web_contents) {
323  return SessionTabHelper::IdForWindowContainingTab(web_contents);
324}
325
326base::DictionaryValue* ExtensionTabUtil::CreateTabValue(
327    WebContents* contents,
328    TabStripModel* tab_strip,
329    int tab_index,
330    const Extension* extension) {
331  // If we have a matching AppWindow with a controller, get the tab value
332  // from its controller instead.
333  WindowController* controller = GetAppWindowController(contents);
334  if (controller &&
335      (!extension || controller->IsVisibleToExtension(extension))) {
336    return controller->CreateTabValue(extension, tab_index);
337  }
338  base::DictionaryValue* result =
339      CreateTabValue(contents, tab_strip, tab_index);
340  ScrubTabValueForExtension(contents, extension, result);
341  return result;
342}
343
344base::ListValue* ExtensionTabUtil::CreateTabList(
345    const Browser* browser,
346    const Extension* extension) {
347  base::ListValue* tab_list = new base::ListValue();
348  TabStripModel* tab_strip = browser->tab_strip_model();
349  for (int i = 0; i < tab_strip->count(); ++i) {
350    tab_list->Append(CreateTabValue(tab_strip->GetWebContentsAt(i),
351                                    tab_strip,
352                                    i,
353                                    extension));
354  }
355
356  return tab_list;
357}
358
359base::DictionaryValue* ExtensionTabUtil::CreateTabValue(
360    WebContents* contents,
361    TabStripModel* tab_strip,
362    int tab_index) {
363  // If we have a matching AppWindow with a controller, get the tab value
364  // from its controller instead.
365  WindowController* controller = GetAppWindowController(contents);
366  if (controller)
367    return controller->CreateTabValue(NULL, tab_index);
368
369  if (!tab_strip)
370    ExtensionTabUtil::GetTabStripModel(contents, &tab_strip, &tab_index);
371
372  base::DictionaryValue* result = new base::DictionaryValue();
373  bool is_loading = contents->IsLoading();
374  result->SetInteger(keys::kIdKey, GetTabId(contents));
375  result->SetInteger(keys::kIndexKey, tab_index);
376  result->SetInteger(keys::kWindowIdKey, GetWindowIdOfTab(contents));
377  result->SetString(keys::kStatusKey, GetTabStatusText(is_loading));
378  result->SetBoolean(keys::kActiveKey,
379                     tab_strip && tab_index == tab_strip->active_index());
380  result->SetBoolean(keys::kSelectedKey,
381                     tab_strip && tab_index == tab_strip->active_index());
382  result->SetBoolean(keys::kHighlightedKey,
383                   tab_strip && tab_strip->IsTabSelected(tab_index));
384  result->SetBoolean(keys::kPinnedKey,
385                     tab_strip && tab_strip->IsTabPinned(tab_index));
386  result->SetBoolean(keys::kIncognitoKey,
387                     contents->GetBrowserContext()->IsOffTheRecord());
388  result->SetInteger(keys::kWidthKey,
389                     contents->GetContainerBounds().size().width());
390  result->SetInteger(keys::kHeightKey,
391                     contents->GetContainerBounds().size().height());
392
393  // Privacy-sensitive fields: these should be stripped off by
394  // ScrubTabValueForExtension if the extension should not see them.
395  result->SetString(keys::kUrlKey, contents->GetURL().spec());
396  result->SetString(keys::kTitleKey, contents->GetTitle());
397  if (!is_loading) {
398    NavigationEntry* entry = contents->GetController().GetVisibleEntry();
399    if (entry && entry->GetFavicon().valid)
400      result->SetString(keys::kFaviconUrlKey, entry->GetFavicon().url.spec());
401  }
402
403  if (tab_strip) {
404    WebContents* opener = tab_strip->GetOpenerOfWebContentsAt(tab_index);
405    if (opener)
406      result->SetInteger(keys::kOpenerTabIdKey, GetTabId(opener));
407  }
408
409  return result;
410}
411
412void ExtensionTabUtil::ScrubTabValueForExtension(
413    WebContents* contents,
414    const Extension* extension,
415    base::DictionaryValue* tab_info) {
416  bool has_permission = extension &&
417                        extension->permissions_data()->HasAPIPermissionForTab(
418                            GetTabId(contents), APIPermission::kTab);
419
420  if (!has_permission) {
421    tab_info->Remove(keys::kUrlKey, NULL);
422    tab_info->Remove(keys::kTitleKey, NULL);
423    tab_info->Remove(keys::kFaviconUrlKey, NULL);
424  }
425}
426
427void ExtensionTabUtil::ScrubTabForExtension(const Extension* extension,
428                                            api::tabs::Tab* tab) {
429  bool has_permission =
430      extension &&
431      extension->permissions_data()->HasAPIPermission(APIPermission::kTab);
432
433  if (!has_permission) {
434    tab->url.reset();
435    tab->title.reset();
436    tab->fav_icon_url.reset();
437  }
438}
439
440bool ExtensionTabUtil::GetTabStripModel(const WebContents* web_contents,
441                                        TabStripModel** tab_strip_model,
442                                        int* tab_index) {
443  DCHECK(web_contents);
444  DCHECK(tab_strip_model);
445  DCHECK(tab_index);
446
447  for (chrome::BrowserIterator it; !it.done(); it.Next()) {
448    TabStripModel* tab_strip = it->tab_strip_model();
449    int index = tab_strip->GetIndexOfWebContents(web_contents);
450    if (index != -1) {
451      *tab_strip_model = tab_strip;
452      *tab_index = index;
453      return true;
454    }
455  }
456
457  return false;
458}
459
460bool ExtensionTabUtil::GetDefaultTab(Browser* browser,
461                                     WebContents** contents,
462                                     int* tab_id) {
463  DCHECK(browser);
464  DCHECK(contents);
465
466  *contents = browser->tab_strip_model()->GetActiveWebContents();
467  if (*contents) {
468    if (tab_id)
469      *tab_id = GetTabId(*contents);
470    return true;
471  }
472
473  return false;
474}
475
476bool ExtensionTabUtil::GetTabById(int tab_id,
477                                  content::BrowserContext* browser_context,
478                                  bool include_incognito,
479                                  Browser** browser,
480                                  TabStripModel** tab_strip,
481                                  WebContents** contents,
482                                  int* tab_index) {
483  Profile* profile = Profile::FromBrowserContext(browser_context);
484  Profile* incognito_profile =
485      include_incognito && profile->HasOffTheRecordProfile() ?
486          profile->GetOffTheRecordProfile() : NULL;
487  for (chrome::BrowserIterator it; !it.done(); it.Next()) {
488    Browser* target_browser = *it;
489    if (target_browser->profile() == profile ||
490        target_browser->profile() == incognito_profile) {
491      TabStripModel* target_tab_strip = target_browser->tab_strip_model();
492      for (int i = 0; i < target_tab_strip->count(); ++i) {
493        WebContents* target_contents = target_tab_strip->GetWebContentsAt(i);
494        if (SessionTabHelper::IdForTab(target_contents) == tab_id) {
495          if (browser)
496            *browser = target_browser;
497          if (tab_strip)
498            *tab_strip = target_tab_strip;
499          if (contents)
500            *contents = target_contents;
501          if (tab_index)
502            *tab_index = i;
503          return true;
504        }
505      }
506    }
507  }
508  return false;
509}
510
511GURL ExtensionTabUtil::ResolvePossiblyRelativeURL(const std::string& url_string,
512                                                  const Extension* extension) {
513  GURL url = GURL(url_string);
514  if (!url.is_valid())
515    url = extension->GetResourceURL(url_string);
516
517  return url;
518}
519
520bool ExtensionTabUtil::IsCrashURL(const GURL& url) {
521  // Check a fixed-up URL, to normalize the scheme and parse hosts correctly.
522  GURL fixed_url =
523      url_fixer::FixupURL(url.possibly_invalid_spec(), std::string());
524  return (fixed_url.SchemeIs(content::kChromeUIScheme) &&
525          (fixed_url.host() == content::kChromeUIBrowserCrashHost ||
526           fixed_url.host() == chrome::kChromeUICrashHost));
527}
528
529void ExtensionTabUtil::CreateTab(WebContents* web_contents,
530                                 const std::string& extension_id,
531                                 WindowOpenDisposition disposition,
532                                 const gfx::Rect& initial_pos,
533                                 bool user_gesture) {
534  Profile* profile =
535      Profile::FromBrowserContext(web_contents->GetBrowserContext());
536  chrome::HostDesktopType active_desktop = chrome::GetActiveDesktop();
537  Browser* browser = chrome::FindTabbedBrowser(profile, false, active_desktop);
538  const bool browser_created = !browser;
539  if (!browser)
540    browser = new Browser(Browser::CreateParams(profile, active_desktop));
541  chrome::NavigateParams params(browser, web_contents);
542
543  // The extension_app_id parameter ends up as app_name in the Browser
544  // which causes the Browser to return true for is_app().  This affects
545  // among other things, whether the location bar gets displayed.
546  // TODO(mpcomplete): This seems wrong. What if the extension content is hosted
547  // in a tab?
548  if (disposition == NEW_POPUP)
549    params.extension_app_id = extension_id;
550
551  params.disposition = disposition;
552  params.window_bounds = initial_pos;
553  params.window_action = chrome::NavigateParams::SHOW_WINDOW;
554  params.user_gesture = user_gesture;
555  chrome::Navigate(&params);
556
557  // Close the browser if chrome::Navigate created a new one.
558  if (browser_created && (browser != params.browser))
559    browser->window()->Close();
560}
561
562// static
563void ExtensionTabUtil::ForEachTab(
564    const base::Callback<void(WebContents*)>& callback) {
565  for (TabContentsIterator iterator; !iterator.done(); iterator.Next())
566    callback.Run(*iterator);
567}
568
569// static
570WindowController* ExtensionTabUtil::GetWindowControllerOfTab(
571    const WebContents* web_contents) {
572  Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
573  if (browser != NULL)
574    return browser->extension_window_controller();
575
576  return NULL;
577}
578
579void ExtensionTabUtil::OpenOptionsPage(const Extension* extension,
580                                       Browser* browser) {
581  DCHECK(OptionsPageInfo::HasOptionsPage(extension));
582
583  // Force the options page to open in non-OTR window, because it won't be
584  // able to save settings from OTR.
585  scoped_ptr<chrome::ScopedTabbedBrowserDisplayer> displayer;
586  if (browser->profile()->IsOffTheRecord()) {
587    displayer.reset(new chrome::ScopedTabbedBrowserDisplayer(
588        browser->profile()->GetOriginalProfile(),
589        browser->host_desktop_type()));
590    browser = displayer->browser();
591  }
592
593  if (!OptionsPageInfo::ShouldOpenInTab(extension)) {
594    // If we should embed the options page for this extension, open
595    // chrome://extensions in a new tab and show the extension options in an
596    // embedded popup.
597    chrome::NavigateParams params(chrome::GetSingletonTabNavigateParams(
598        browser, GURL(chrome::kChromeUIExtensionsURL)));
599    params.path_behavior = chrome::NavigateParams::IGNORE_AND_NAVIGATE;
600
601    GURL::Replacements replacements;
602    std::string query =
603        base::StringPrintf("options=%s", extension->id().c_str());
604    replacements.SetQueryStr(query);
605    params.url = params.url.ReplaceComponents(replacements);
606
607    chrome::ShowSingletonTabOverwritingNTP(browser, params);
608  } else {
609    // Otherwise open a new tab with the extension's options page
610    content::OpenURLParams params(OptionsPageInfo::GetOptionsPage(extension),
611                                  content::Referrer(),
612                                  SINGLETON_TAB,
613                                  ui::PAGE_TRANSITION_LINK,
614                                  false);
615    browser->OpenURL(params);
616    browser->window()->Show();
617    WebContents* web_contents =
618        browser->tab_strip_model()->GetActiveWebContents();
619    web_contents->GetDelegate()->ActivateContents(web_contents);
620  }
621}
622
623}  // namespace extensions
624