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(¶ms); 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