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