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