Home | History | Annotate | Download | only in extensions
      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/url_constants.h"
     26 #include "components/url_fixer/url_fixer.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 
     39 using apps::AppWindow;
     40 using content::NavigationEntry;
     41 using content::WebContents;
     42 
     43 namespace extensions {
     44 
     45 namespace {
     46 
     47 namespace keys = tabs_constants;
     48 
     49 WindowController* 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.
     64 Browser* 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 
     89 Browser* 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 
    108 ExtensionTabUtil::OpenTabParams::OpenTabParams()
    109     : create_browser_if_needed(false) {
    110 }
    111 
    112 ExtensionTabUtil::OpenTabParams::~OpenTabParams() {
    113 }
    114 
    115 // Opens a new tab for a given extension. Returns NULL and sets |error| if an
    116 // error occurs.
    117 base::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 
    257 Browser* 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 
    277 int ExtensionTabUtil::GetWindowId(const Browser* browser) {
    278   return browser->session_id().id();
    279 }
    280 
    281 int 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 
    290 int ExtensionTabUtil::GetTabId(const WebContents* web_contents) {
    291   return SessionID::IdForTab(web_contents);
    292 }
    293 
    294 std::string ExtensionTabUtil::GetTabStatusText(bool is_loading) {
    295   return is_loading ? keys::kStatusValueLoading : keys::kStatusValueComplete;
    296 }
    297 
    298 int ExtensionTabUtil::GetWindowIdOfTab(const WebContents* web_contents) {
    299   return SessionID::IdForWindowContainingTab(web_contents);
    300 }
    301 
    302 base::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 
    320 base::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 
    335 base::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 
    388 void ExtensionTabUtil::ScrubTabValueForExtension(
    389     WebContents* contents,
    390     const Extension* extension,
    391     base::DictionaryValue* tab_info) {
    392   bool has_permission = extension &&
    393                         extension->permissions_data()->HasAPIPermissionForTab(
    394                             GetTabId(contents), APIPermission::kTab);
    395 
    396   if (!has_permission) {
    397     tab_info->Remove(keys::kUrlKey, NULL);
    398     tab_info->Remove(keys::kTitleKey, NULL);
    399     tab_info->Remove(keys::kFaviconUrlKey, NULL);
    400   }
    401 }
    402 
    403 void ExtensionTabUtil::ScrubTabForExtension(const Extension* extension,
    404                                             api::tabs::Tab* tab) {
    405   bool has_permission =
    406       extension &&
    407       extension->permissions_data()->HasAPIPermission(APIPermission::kTab);
    408 
    409   if (!has_permission) {
    410     tab->url.reset();
    411     tab->title.reset();
    412     tab->fav_icon_url.reset();
    413   }
    414 }
    415 
    416 bool 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 
    436 bool 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 
    452 bool 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 
    486 GURL 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 
    495 bool ExtensionTabUtil::IsCrashURL(const GURL& url) {
    496   // Check a fixed-up URL, to normalize the scheme and parse hosts correctly.
    497   GURL fixed_url =
    498       url_fixer::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 
    504 void 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
    538 void 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
    545 WindowController* 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 
    554 void 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