extension_tabs_module.cc revision 3345a6884c488ff3a535c2c9acdd33d74b37e311
1// Copyright (c) 2010 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_tabs_module.h" 6 7#include "base/base64.h" 8#include "base/string_number_conversions.h" 9#include "base/string_util.h" 10#include "base/stringprintf.h" 11#include "base/utf_string_conversions.h" 12#include "chrome/browser/browser.h" 13#include "chrome/browser/browser_list.h" 14#include "chrome/browser/browser_window.h" 15#include "chrome/browser/extensions/extension_function_dispatcher.h" 16#include "chrome/browser/extensions/extension_host.h" 17#include "chrome/browser/extensions/extension_infobar_delegate.h" 18#include "chrome/browser/extensions/extension_tabs_module_constants.h" 19#include "chrome/browser/extensions/extensions_service.h" 20#include "chrome/browser/profile.h" 21#include "chrome/browser/renderer_host/backing_store.h" 22#include "chrome/browser/renderer_host/render_view_host.h" 23#include "chrome/browser/renderer_host/render_view_host_delegate.h" 24#include "chrome/browser/tab_contents/navigation_entry.h" 25#include "chrome/browser/tab_contents/tab_contents.h" 26#include "chrome/browser/tabs/tab_strip_model.h" 27#include "chrome/browser/window_sizer.h" 28#include "chrome/common/extensions/extension.h" 29#include "chrome/common/extensions/extension_error_utils.h" 30#include "chrome/common/url_constants.h" 31#include "gfx/codec/jpeg_codec.h" 32#include "gfx/codec/png_codec.h" 33#include "skia/ext/image_operations.h" 34#include "skia/ext/platform_canvas.h" 35#include "third_party/skia/include/core/SkBitmap.h" 36 37namespace keys = extension_tabs_module_constants; 38 39const int CaptureVisibleTabFunction::kDefaultQuality = 90; 40 41// Forward declare static helper functions defined below. 42 43// |error_message| can optionally be passed in a will be set with an appropriate 44// message if the window cannot be found by id. 45static Browser* GetBrowserInProfileWithId(Profile* profile, 46 const int window_id, 47 bool include_incognito, 48 std::string* error_message); 49 50// |error_message| can optionally be passed in and will be set with an 51// appropriate message if the tab cannot be found by id. 52static bool GetTabById(int tab_id, Profile* profile, 53 bool include_incognito, 54 Browser** browser, 55 TabStripModel** tab_strip, 56 TabContents** contents, 57 int* tab_index, std::string* error_message); 58 59// Takes |url_string| and returns a GURL which is either valid and absolute 60// or invalid. If |url_string| is not directly interpretable as a valid (it is 61// likely a relative URL) an attempt is made to resolve it. |extension| is 62// provided so it can be resolved relative to its extension base 63// (chrome-extension://<id>/). Using the source frame url would be more correct, 64// but because the api shipped with urls resolved relative to their extension 65// base, we decided it wasn't worth breaking existing extensions to fix. 66static GURL ResolvePossiblyRelativeURL(std::string url_string, 67 Extension* extension); 68 69// Return the type name for a browser window type. 70static std::string GetWindowTypeText(Browser::Type type); 71 72int ExtensionTabUtil::GetWindowId(const Browser* browser) { 73 return browser->session_id().id(); 74} 75 76int ExtensionTabUtil::GetTabId(const TabContents* tab_contents) { 77 return tab_contents->controller().session_id().id(); 78} 79 80std::string ExtensionTabUtil::GetTabStatusText(bool is_loading) { 81 return is_loading ? keys::kStatusValueLoading : keys::kStatusValueComplete; 82} 83 84int ExtensionTabUtil::GetWindowIdOfTab(const TabContents* tab_contents) { 85 return tab_contents->controller().window_id().id(); 86} 87 88DictionaryValue* ExtensionTabUtil::CreateTabValue( 89 const TabContents* contents) { 90 // Find the tab strip and index of this guy. 91 for (BrowserList::const_iterator it = BrowserList::begin(); 92 it != BrowserList::end(); ++it) { 93 TabStripModel* tab_strip = (*it)->tabstrip_model(); 94 int tab_index = tab_strip->GetIndexOfTabContents(contents); 95 if (tab_index != -1) { 96 return ExtensionTabUtil::CreateTabValue(contents, tab_strip, tab_index); 97 } 98 } 99 100 // Couldn't find it. This can happen if the tab is being dragged. 101 return ExtensionTabUtil::CreateTabValue(contents, NULL, -1); 102} 103 104ListValue* ExtensionTabUtil::CreateTabList(const Browser* browser) { 105 ListValue* tab_list = new ListValue(); 106 TabStripModel* tab_strip = browser->tabstrip_model(); 107 for (int i = 0; i < tab_strip->count(); ++i) { 108 tab_list->Append(ExtensionTabUtil::CreateTabValue( 109 tab_strip->GetTabContentsAt(i), tab_strip, i)); 110 } 111 112 return tab_list; 113} 114 115DictionaryValue* ExtensionTabUtil::CreateTabValue( 116 const TabContents* contents, TabStripModel* tab_strip, int tab_index) { 117 DictionaryValue* result = new DictionaryValue(); 118 result->SetInteger(keys::kIdKey, ExtensionTabUtil::GetTabId(contents)); 119 result->SetInteger(keys::kIndexKey, tab_index); 120 result->SetInteger(keys::kWindowIdKey, 121 ExtensionTabUtil::GetWindowIdOfTab(contents)); 122 result->SetString(keys::kUrlKey, contents->GetURL().spec()); 123 result->SetString(keys::kStatusKey, GetTabStatusText(contents->is_loading())); 124 result->SetBoolean(keys::kSelectedKey, 125 tab_strip && tab_index == tab_strip->selected_index()); 126 result->SetString(keys::kTitleKey, contents->GetTitle()); 127 result->SetBoolean(keys::kIncognitoKey, 128 contents->profile()->IsOffTheRecord()); 129 130 if (!contents->is_loading()) { 131 NavigationEntry* entry = contents->controller().GetActiveEntry(); 132 if (entry) { 133 if (entry->favicon().is_valid()) 134 result->SetString(keys::kFavIconUrlKey, entry->favicon().url().spec()); 135 } 136 } 137 138 return result; 139} 140 141// if |populate| is true, each window gets a list property |tabs| which contains 142// fully populated tab objects. 143DictionaryValue* ExtensionTabUtil::CreateWindowValue(const Browser* browser, 144 bool populate_tabs) { 145 DCHECK(browser); 146 DCHECK(browser->window()); 147 DictionaryValue* result = new DictionaryValue(); 148 result->SetInteger(keys::kIdKey, ExtensionTabUtil::GetWindowId(browser)); 149 result->SetBoolean(keys::kIncognitoKey, 150 browser->profile()->IsOffTheRecord()); 151 result->SetBoolean(keys::kFocusedKey, browser->window()->IsActive()); 152 gfx::Rect bounds = browser->window()->GetRestoredBounds(); 153 154 result->SetInteger(keys::kLeftKey, bounds.x()); 155 result->SetInteger(keys::kTopKey, bounds.y()); 156 result->SetInteger(keys::kWidthKey, bounds.width()); 157 result->SetInteger(keys::kHeightKey, bounds.height()); 158 result->SetString(keys::kWindowTypeKey, GetWindowTypeText(browser->type())); 159 160 if (populate_tabs) { 161 result->Set(keys::kTabsKey, ExtensionTabUtil::CreateTabList(browser)); 162 } 163 164 return result; 165} 166 167bool ExtensionTabUtil::GetDefaultTab(Browser* browser, TabContents** contents, 168 int* tab_id) { 169 DCHECK(browser); 170 DCHECK(contents); 171 DCHECK(tab_id); 172 173 *contents = browser->tabstrip_model()->GetSelectedTabContents(); 174 if (*contents) { 175 if (tab_id) 176 *tab_id = ExtensionTabUtil::GetTabId(*contents); 177 return true; 178 } 179 180 return false; 181} 182 183bool ExtensionTabUtil::GetTabById(int tab_id, Profile* profile, 184 bool include_incognito, 185 Browser** browser, 186 TabStripModel** tab_strip, 187 TabContents** contents, 188 int* tab_index) { 189 Browser* target_browser; 190 TabStripModel* target_tab_strip; 191 TabContents* target_contents; 192 Profile* incognito_profile = 193 include_incognito && profile->HasOffTheRecordProfile() ? 194 profile->GetOffTheRecordProfile() : NULL; 195 for (BrowserList::const_iterator iter = BrowserList::begin(); 196 iter != BrowserList::end(); ++iter) { 197 target_browser = *iter; 198 if (target_browser->profile() == profile || 199 target_browser->profile() == incognito_profile) { 200 target_tab_strip = target_browser->tabstrip_model(); 201 for (int i = 0; i < target_tab_strip->count(); ++i) { 202 target_contents = target_tab_strip->GetTabContentsAt(i); 203 if (target_contents->controller().session_id().id() == tab_id) { 204 if (browser) 205 *browser = target_browser; 206 if (tab_strip) 207 *tab_strip = target_tab_strip; 208 if (contents) 209 *contents = target_contents; 210 if (tab_index) 211 *tab_index = i; 212 return true; 213 } 214 } 215 } 216 } 217 return false; 218} 219 220// Windows --------------------------------------------------------------------- 221 222bool GetWindowFunction::RunImpl() { 223 int window_id; 224 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); 225 226 Browser* browser = GetBrowserInProfileWithId(profile(), window_id, 227 include_incognito(), &error_); 228 if (!browser || !browser->window()) { 229 error_ = ExtensionErrorUtils::FormatErrorMessage( 230 keys::kWindowNotFoundError, base::IntToString(window_id)); 231 return false; 232 } 233 234 result_.reset(ExtensionTabUtil::CreateWindowValue(browser, false)); 235 return true; 236} 237 238bool GetCurrentWindowFunction::RunImpl() { 239 Browser* browser = GetCurrentBrowser(); 240 if (!browser || !browser->window()) { 241 error_ = keys::kNoCurrentWindowError; 242 return false; 243 } 244 result_.reset(ExtensionTabUtil::CreateWindowValue(browser, false)); 245 return true; 246} 247 248bool GetLastFocusedWindowFunction::RunImpl() { 249 Browser* browser = BrowserList::FindBrowserWithType( 250 profile(), Browser::TYPE_ANY, include_incognito()); 251 if (!browser || !browser->window()) { 252 error_ = keys::kNoLastFocusedWindowError; 253 return false; 254 } 255 result_.reset(ExtensionTabUtil::CreateWindowValue(browser, false)); 256 return true; 257} 258 259bool GetAllWindowsFunction::RunImpl() { 260 bool populate_tabs = false; 261 if (HasOptionalArgument(0)) { 262 DictionaryValue* args; 263 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args)); 264 265 if (args->HasKey(keys::kPopulateKey)) { 266 EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kPopulateKey, 267 &populate_tabs)); 268 } 269 } 270 271 result_.reset(new ListValue()); 272 Profile* incognito_profile = 273 include_incognito() && profile()->HasOffTheRecordProfile() ? 274 profile()->GetOffTheRecordProfile() : NULL; 275 for (BrowserList::const_iterator browser = BrowserList::begin(); 276 browser != BrowserList::end(); ++browser) { 277 // Only examine browsers in the current profile that have windows. 278 if (((*browser)->profile() == profile() || 279 (*browser)->profile() == incognito_profile) && 280 (*browser)->window()) { 281 static_cast<ListValue*>(result_.get())-> 282 Append(ExtensionTabUtil::CreateWindowValue(*browser, populate_tabs)); 283 } 284 } 285 286 return true; 287} 288 289bool CreateWindowFunction::RunImpl() { 290 GURL url; 291 DictionaryValue* args = NULL; 292 293 if (HasOptionalArgument(0)) 294 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args)); 295 296 // Look for optional url. 297 if (args) { 298 std::string url_string; 299 if (args->HasKey(keys::kUrlKey)) { 300 EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kUrlKey, 301 &url_string)); 302 url = ResolvePossiblyRelativeURL(url_string, GetExtension()); 303 if (!url.is_valid()) { 304 error_ = ExtensionErrorUtils::FormatErrorMessage( 305 keys::kInvalidUrlError, url_string); 306 return false; 307 } 308 } 309 } 310 311 // Try to position the new browser relative its originating browser window. 312 gfx::Rect empty_bounds; 313 gfx::Rect bounds; 314 bool maximized; 315 // The call offsets the bounds by kWindowTilePixels (defined in WindowSizer to 316 // be 10) 317 // 318 // NOTE(rafaelw): It's ok if GetCurrentBrowser() returns NULL here. 319 // GetBrowserWindowBounds will default to saved "default" values for the app. 320 WindowSizer::GetBrowserWindowBounds(std::string(), empty_bounds, 321 GetCurrentBrowser(), &bounds, 322 &maximized); 323 324 Profile* window_profile = profile(); 325 Browser::Type window_type = Browser::TYPE_NORMAL; 326 327 if (args) { 328 // Any part of the bounds can optionally be set by the caller. 329 int bounds_val; 330 if (args->HasKey(keys::kLeftKey)) { 331 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kLeftKey, 332 &bounds_val)); 333 bounds.set_x(bounds_val); 334 } 335 336 if (args->HasKey(keys::kTopKey)) { 337 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kTopKey, 338 &bounds_val)); 339 bounds.set_y(bounds_val); 340 } 341 342 if (args->HasKey(keys::kWidthKey)) { 343 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kWidthKey, 344 &bounds_val)); 345 bounds.set_width(bounds_val); 346 } 347 348 if (args->HasKey(keys::kHeightKey)) { 349 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kHeightKey, 350 &bounds_val)); 351 bounds.set_height(bounds_val); 352 } 353 354 bool incognito = false; 355 if (args->HasKey(keys::kIncognitoKey)) { 356 EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kIncognitoKey, 357 &incognito)); 358 if (incognito) 359 window_profile = window_profile->GetOffTheRecordProfile(); 360 } 361 362 std::string type_str; 363 if (args->HasKey(keys::kWindowTypeKey)) { 364 EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kWindowTypeKey, 365 &type_str)); 366 if (type_str == keys::kWindowTypeValueNormal) { 367 window_type = Browser::TYPE_NORMAL; 368 } else if (type_str == keys::kWindowTypeValuePopup) { 369 window_type = Browser::TYPE_POPUP; 370 } else { 371 EXTENSION_FUNCTION_VALIDATE(false); 372 } 373 } 374 } 375 376 Browser* new_window = new Browser(window_type, window_profile); 377 new_window->CreateBrowserWindow(); 378 new_window->AddTabWithURL(url, GURL(), PageTransition::LINK, -1, 379 TabStripModel::ADD_SELECTED, NULL, std::string(), 380 &new_window); 381 382 new_window->window()->SetBounds(bounds); 383 new_window->window()->Show(); 384 385 if (new_window->profile()->IsOffTheRecord() && !include_incognito()) { 386 // Don't expose incognito windows if the extension isn't allowed. 387 result_.reset(Value::CreateNullValue()); 388 } else { 389 result_.reset(ExtensionTabUtil::CreateWindowValue(new_window, false)); 390 } 391 392 return true; 393} 394 395bool UpdateWindowFunction::RunImpl() { 396 int window_id; 397 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); 398 DictionaryValue* update_props; 399 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props)); 400 401 Browser* browser = GetBrowserInProfileWithId(profile(), window_id, 402 include_incognito(), &error_); 403 if (!browser || !browser->window()) { 404 error_ = ExtensionErrorUtils::FormatErrorMessage( 405 keys::kWindowNotFoundError, base::IntToString(window_id)); 406 return false; 407 } 408 409 gfx::Rect bounds = browser->window()->GetRestoredBounds(); 410 // Any part of the bounds can optionally be set by the caller. 411 int bounds_val; 412 if (update_props->HasKey(keys::kLeftKey)) { 413 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( 414 keys::kLeftKey, 415 &bounds_val)); 416 bounds.set_x(bounds_val); 417 } 418 419 if (update_props->HasKey(keys::kTopKey)) { 420 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( 421 keys::kTopKey, 422 &bounds_val)); 423 bounds.set_y(bounds_val); 424 } 425 426 if (update_props->HasKey(keys::kWidthKey)) { 427 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( 428 keys::kWidthKey, 429 &bounds_val)); 430 bounds.set_width(bounds_val); 431 } 432 433 if (update_props->HasKey(keys::kHeightKey)) { 434 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( 435 keys::kHeightKey, 436 &bounds_val)); 437 bounds.set_height(bounds_val); 438 } 439 440 browser->window()->SetBounds(bounds); 441 result_.reset(ExtensionTabUtil::CreateWindowValue(browser, false)); 442 443 return true; 444} 445 446bool RemoveWindowFunction::RunImpl() { 447 int window_id; 448 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); 449 450 Browser* browser = GetBrowserInProfileWithId(profile(), window_id, 451 include_incognito(), &error_); 452 if (!browser) 453 return false; 454 455 browser->CloseWindow(); 456 457 return true; 458} 459 460// Tabs ------------------------------------------------------------------------ 461 462bool GetSelectedTabFunction::RunImpl() { 463 Browser* browser; 464 // windowId defaults to "current" window. 465 int window_id = -1; 466 467 if (HasOptionalArgument(0)) { 468 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); 469 browser = GetBrowserInProfileWithId(profile(), window_id, 470 include_incognito(), &error_); 471 } else { 472 browser = GetCurrentBrowser(); 473 if (!browser) 474 error_ = keys::kNoCurrentWindowError; 475 } 476 if (!browser) 477 return false; 478 479 TabStripModel* tab_strip = browser->tabstrip_model(); 480 TabContents* contents = tab_strip->GetSelectedTabContents(); 481 if (!contents) { 482 error_ = keys::kNoSelectedTabError; 483 return false; 484 } 485 result_.reset(ExtensionTabUtil::CreateTabValue(contents, tab_strip, 486 tab_strip->selected_index())); 487 return true; 488} 489 490bool GetAllTabsInWindowFunction::RunImpl() { 491 Browser* browser; 492 // windowId defaults to "current" window. 493 int window_id = -1; 494 if (HasOptionalArgument(0)) { 495 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); 496 browser = GetBrowserInProfileWithId(profile(), window_id, 497 include_incognito(), &error_); 498 } else { 499 browser = GetCurrentBrowser(); 500 if (!browser) 501 error_ = keys::kNoCurrentWindowError; 502 } 503 if (!browser) 504 return false; 505 506 result_.reset(ExtensionTabUtil::CreateTabList(browser)); 507 508 return true; 509} 510 511bool CreateTabFunction::RunImpl() { 512 DictionaryValue* args; 513 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args)); 514 515 Browser *browser; 516 // windowId defaults to "current" window. 517 int window_id = -1; 518 if (args->HasKey(keys::kWindowIdKey)) { 519 EXTENSION_FUNCTION_VALIDATE(args->GetInteger( 520 keys::kWindowIdKey, &window_id)); 521 browser = GetBrowserInProfileWithId(profile(), window_id, 522 include_incognito(), &error_); 523 } else { 524 browser = GetCurrentBrowser(); 525 if (!browser) 526 error_ = keys::kNoCurrentWindowError; 527 } 528 if (!browser) 529 return false; 530 531 // TODO(rafaelw): handle setting remaining tab properties: 532 // -title 533 // -favIconUrl 534 535 std::string url_string; 536 GURL url; 537 if (args->HasKey(keys::kUrlKey)) { 538 EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kUrlKey, 539 &url_string)); 540 url = ResolvePossiblyRelativeURL(url_string, GetExtension()); 541 if (!url.is_valid()) { 542 error_ = ExtensionErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, 543 url_string); 544 return false; 545 } 546 } 547 548 // Default to foreground for the new tab. The presence of 'selected' property 549 // will override this default. 550 bool selected = true; 551 if (args->HasKey(keys::kSelectedKey)) 552 EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kSelectedKey, 553 &selected)); 554 // If index is specified, honor the value, but keep it bound to 555 // 0 <= index <= tab_strip->count() 556 int index = -1; 557 if (args->HasKey(keys::kIndexKey)) 558 EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kIndexKey, 559 &index)); 560 561 // We can't load extension URLs into incognito windows. Special case to 562 // fall back to a normal window. 563 if (url.SchemeIs(chrome::kExtensionScheme) && 564 browser->profile()->IsOffTheRecord()) { 565 Profile* profile = browser->profile()->GetOriginalProfile(); 566 browser = BrowserList::FindBrowserWithType(profile, 567 Browser::TYPE_NORMAL, false); 568 if (!browser) { 569 browser = Browser::Create(profile); 570 browser->window()->Show(); 571 } 572 } 573 574 TabStripModel* tab_strip = browser->tabstrip_model(); 575 576 if (index < 0) { 577 // Default insert behavior. 578 index = -1; 579 } 580 if (index > tab_strip->count()) { 581 index = tab_strip->count(); 582 } 583 584 int add_types = selected ? TabStripModel::ADD_SELECTED : 585 TabStripModel::ADD_NONE; 586 add_types |= TabStripModel::ADD_FORCE_INDEX; 587 TabContents* contents = browser->AddTabWithURL(url, GURL(), 588 PageTransition::LINK, index, add_types, NULL, std::string(), &browser); 589 index = browser->tabstrip_model()->GetIndexOfTabContents(contents); 590 591 if (selected) 592 contents->Focus(); 593 594 // Return data about the newly created tab. 595 if (has_callback()) 596 result_.reset(ExtensionTabUtil::CreateTabValue(contents, 597 browser->tabstrip_model(), 598 index)); 599 600 return true; 601} 602 603bool GetTabFunction::RunImpl() { 604 int tab_id; 605 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); 606 607 TabStripModel* tab_strip = NULL; 608 TabContents* contents = NULL; 609 int tab_index = -1; 610 if (!GetTabById(tab_id, profile(), include_incognito(), 611 NULL, &tab_strip, &contents, &tab_index, &error_)) 612 return false; 613 614 result_.reset(ExtensionTabUtil::CreateTabValue(contents, tab_strip, 615 tab_index)); 616 return true; 617} 618 619bool GetCurrentTabFunction::RunImpl() { 620 DCHECK(dispatcher()); 621 622 TabContents* contents = dispatcher()->delegate()->associated_tab_contents(); 623 if (contents) 624 result_.reset(ExtensionTabUtil::CreateTabValue(contents)); 625 626 return true; 627} 628 629bool UpdateTabFunction::RunImpl() { 630 int tab_id; 631 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); 632 DictionaryValue* update_props; 633 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props)); 634 635 TabStripModel* tab_strip = NULL; 636 TabContents* contents = NULL; 637 int tab_index = -1; 638 if (!GetTabById(tab_id, profile(), include_incognito(), 639 NULL, &tab_strip, &contents, &tab_index, &error_)) 640 return false; 641 642 NavigationController& controller = contents->controller(); 643 644 // TODO(rafaelw): handle setting remaining tab properties: 645 // -title 646 // -favIconUrl 647 648 // Navigate the tab to a new location if the url different. 649 std::string url_string; 650 if (update_props->HasKey(keys::kUrlKey)) { 651 EXTENSION_FUNCTION_VALIDATE(update_props->GetString( 652 keys::kUrlKey, &url_string)); 653 GURL url = ResolvePossiblyRelativeURL(url_string, GetExtension()); 654 655 if (!url.is_valid()) { 656 error_ = ExtensionErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, 657 url_string); 658 return false; 659 } 660 661 // JavaScript URLs can do the same kinds of things as cross-origin XHR, so 662 // we need to check host permissions before allowing them. 663 if (url.SchemeIs(chrome::kJavaScriptScheme)) { 664 if (!profile()->GetExtensionsService()->CanExecuteScriptOnHost( 665 GetExtension(), contents->GetURL(), &error_)) 666 return false; 667 668 // TODO(aa): How does controller queue URLs? Is there any chance that this 669 // JavaScript URL will end up applying to something other than 670 // controller->GetURL()? 671 } 672 673 if (tab_strip->IsTabPinned(tab_index)) { 674 // Don't allow changing the url of pinned tabs. 675 error_ = keys::kCannotUpdatePinnedTab; 676 return false; 677 } 678 679 controller.LoadURL(url, GURL(), PageTransition::LINK); 680 681 // The URL of a tab contents never actually changes to a JavaScript URL, so 682 // this check only makes sense in other cases. 683 if (!url.SchemeIs(chrome::kJavaScriptScheme)) 684 DCHECK_EQ(url.spec(), contents->GetURL().spec()); 685 } 686 687 bool selected = false; 688 // TODO(rafaelw): Setting |selected| from js doesn't make much sense. 689 // Move tab selection management up to window. 690 if (update_props->HasKey(keys::kSelectedKey)) { 691 EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean( 692 keys::kSelectedKey, 693 &selected)); 694 if (selected) { 695 if (tab_strip->selected_index() != tab_index) { 696 tab_strip->SelectTabContentsAt(tab_index, false); 697 DCHECK_EQ(contents, tab_strip->GetSelectedTabContents()); 698 } 699 contents->Focus(); 700 } 701 } 702 703 if (has_callback()) 704 result_.reset(ExtensionTabUtil::CreateTabValue(contents, tab_strip, 705 tab_index)); 706 707 return true; 708} 709 710bool MoveTabFunction::RunImpl() { 711 int tab_id; 712 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); 713 DictionaryValue* update_props; 714 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props)); 715 716 int new_index; 717 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( 718 keys::kIndexKey, &new_index)); 719 EXTENSION_FUNCTION_VALIDATE(new_index >= 0); 720 721 Browser* source_browser = NULL; 722 TabStripModel* source_tab_strip = NULL; 723 TabContents* contents = NULL; 724 int tab_index = -1; 725 if (!GetTabById(tab_id, profile(), include_incognito(), 726 &source_browser, &source_tab_strip, &contents, 727 &tab_index, &error_)) 728 return false; 729 730 if (source_browser->type() != Browser::TYPE_NORMAL) { 731 error_ = keys::kCanOnlyMoveTabsWithinNormalWindowsError; 732 return false; 733 } 734 735 if (update_props->HasKey(keys::kWindowIdKey)) { 736 Browser* target_browser; 737 int window_id; 738 EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( 739 keys::kWindowIdKey, &window_id)); 740 target_browser = GetBrowserInProfileWithId(profile(), window_id, 741 include_incognito(), &error_); 742 if (!target_browser) 743 return false; 744 745 if (target_browser->type() != Browser::TYPE_NORMAL) { 746 error_ = keys::kCanOnlyMoveTabsWithinNormalWindowsError; 747 return false; 748 } 749 750 // If windowId is different from the current window, move between windows. 751 if (ExtensionTabUtil::GetWindowId(target_browser) != 752 ExtensionTabUtil::GetWindowId(source_browser)) { 753 TabStripModel* target_tab_strip = target_browser->tabstrip_model(); 754 contents = source_tab_strip->DetachTabContentsAt(tab_index); 755 if (!contents) { 756 error_ = ExtensionErrorUtils::FormatErrorMessage( 757 keys::kTabNotFoundError, base::IntToString(tab_id)); 758 return false; 759 } 760 761 // Clamp move location to the last position. 762 // This is ">" because it can append to a new index position. 763 if (new_index > target_tab_strip->count()) 764 new_index = target_tab_strip->count(); 765 766 target_tab_strip->InsertTabContentsAt(new_index, contents, 767 TabStripModel::ADD_NONE); 768 769 if (has_callback()) 770 result_.reset(ExtensionTabUtil::CreateTabValue(contents, 771 target_tab_strip, new_index)); 772 773 return true; 774 } 775 } 776 777 // Perform a simple within-window move. 778 // Clamp move location to the last position. 779 // This is ">=" because the move must be to an existing location. 780 if (new_index >= source_tab_strip->count()) 781 new_index = source_tab_strip->count() - 1; 782 783 if (new_index != tab_index) 784 source_tab_strip->MoveTabContentsAt(tab_index, new_index, false); 785 786 if (has_callback()) 787 result_.reset(ExtensionTabUtil::CreateTabValue(contents, source_tab_strip, 788 new_index)); 789 return true; 790} 791 792 793bool RemoveTabFunction::RunImpl() { 794 int tab_id; 795 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); 796 797 Browser* browser = NULL; 798 TabContents* contents = NULL; 799 if (!GetTabById(tab_id, profile(), include_incognito(), 800 &browser, NULL, &contents, NULL, &error_)) 801 return false; 802 803 int tab_index = browser->GetIndexOfController(&contents->controller()); 804 if (browser->tabstrip_model()->IsPhantomTab(tab_index)) { 805 // Don't allow closing phantom tabs. 806 error_ = keys::kCannotRemovePhantomTab; 807 return false; 808 } 809 810 // Close the tab in this convoluted way, since there's a chance that the tab 811 // is being dragged, or we're in some other nested event loop. This code path 812 // should ensure that the tab is safely closed under such circumstances, 813 // whereas |Browser::CloseTabContents()| does not. 814 RenderViewHost* render_view_host = contents->render_view_host(); 815 render_view_host->delegate()->Close(render_view_host); 816 return true; 817} 818 819bool CaptureVisibleTabFunction::RunImpl() { 820 Browser* browser; 821 // windowId defaults to "current" window. 822 int window_id = -1; 823 824 if (HasOptionalArgument(0)) { 825 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); 826 browser = GetBrowserInProfileWithId(profile(), window_id, 827 include_incognito(), &error_); 828 } else { 829 browser = GetCurrentBrowser(); 830 } 831 832 if (!browser) { 833 error_ = keys::kNoCurrentWindowError; 834 return false; 835 } 836 837 image_format_ = FORMAT_JPEG; // Default format is JPEG. 838 image_quality_ = kDefaultQuality; // Default quality setting. 839 840 if (HasOptionalArgument(1)) { 841 DictionaryValue* options; 842 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &options)); 843 844 if (options->HasKey(keys::kFormatKey)) { 845 std::string format; 846 EXTENSION_FUNCTION_VALIDATE( 847 options->GetString(keys::kFormatKey, &format)); 848 849 if (format == keys::kFormatValueJpeg) { 850 image_format_ = FORMAT_JPEG; 851 } else if (format == keys::kFormatValuePng) { 852 image_format_ = FORMAT_PNG; 853 } else { 854 // Schema validation should make this unreachable. 855 EXTENSION_FUNCTION_VALIDATE(0); 856 } 857 } 858 859 if (options->HasKey(keys::kQualityKey)) { 860 EXTENSION_FUNCTION_VALIDATE( 861 options->GetInteger(keys::kQualityKey, &image_quality_)); 862 } 863 } 864 865 TabContents* tab_contents = browser->GetSelectedTabContents(); 866 if (!tab_contents) { 867 error_ = keys::kInternalVisibleTabCaptureError; 868 return false; 869 } 870 RenderViewHost* render_view_host = tab_contents->render_view_host(); 871 872 // If a backing store is cached for the tab we want to capture, 873 // and it can be copied into a bitmap, then use it to generate the image. 874 BackingStore* backing_store = render_view_host->GetBackingStore(false); 875 if (backing_store && CaptureSnapshotFromBackingStore(backing_store)) 876 return true; 877 878 // Ask the renderer for a snapshot of the tab. 879 render_view_host->CaptureSnapshot(); 880 registrar_.Add(this, 881 NotificationType::TAB_SNAPSHOT_TAKEN, 882 NotificationService::AllSources()); 883 AddRef(); // Balanced in CaptureVisibleTabFunction::Observe(). 884 885 return true; 886} 887 888// Build the image of a tab's contents out of a backing store. 889// This may fail if we can not copy a backing store into a bitmap. 890// For example, some uncommon X11 visual modes are not supported by 891// CopyFromBackingStore(). 892bool CaptureVisibleTabFunction::CaptureSnapshotFromBackingStore( 893 BackingStore* backing_store) { 894 895 skia::PlatformCanvas temp_canvas; 896 if (!backing_store->CopyFromBackingStore(gfx::Rect(backing_store->size()), 897 &temp_canvas)) { 898 return false; 899 } 900 LOG(INFO) << "captureVisibleTab() Got image from backing store."; 901 902 SendResultFromBitmap( 903 temp_canvas.getTopPlatformDevice().accessBitmap(false)); 904 return true; 905} 906 907// If a backing store was not available in CaptureVisibleTabFunction::RunImpl, 908// than the renderer was asked for a snapshot. Listen for a notification 909// that the snapshot is available. 910void CaptureVisibleTabFunction::Observe(NotificationType type, 911 const NotificationSource& source, 912 const NotificationDetails& details) { 913 DCHECK(type == NotificationType::TAB_SNAPSHOT_TAKEN); 914 915 const SkBitmap *screen_capture = Details<const SkBitmap>(details).ptr(); 916 const bool error = screen_capture->empty(); 917 918 if (error) { 919 error_ = keys::kInternalVisibleTabCaptureError; 920 SendResponse(false); 921 } else { 922 LOG(INFO) << "captureVisibleTab() Got image from renderer."; 923 SendResultFromBitmap(*screen_capture); 924 } 925 926 Release(); // Balanced in CaptureVisibleTabFunction::RunImpl(). 927} 928 929// Turn a bitmap of the screen into an image, set that image as the result, 930// and call SendResponse(). 931void CaptureVisibleTabFunction::SendResultFromBitmap( 932 const SkBitmap& screen_capture) { 933 scoped_refptr<RefCountedBytes> image_data(new RefCountedBytes); 934 SkAutoLockPixels screen_capture_lock(screen_capture); 935 bool encoded = false; 936 std::string mime_type; 937 switch (image_format_) { 938 case FORMAT_JPEG: 939 encoded = gfx::JPEGCodec::Encode( 940 reinterpret_cast<unsigned char*>(screen_capture.getAddr32(0, 0)), 941 gfx::JPEGCodec::FORMAT_BGRA, 942 screen_capture.width(), 943 screen_capture.height(), 944 static_cast<int>(screen_capture.rowBytes()), 945 image_quality_, 946 &image_data->data); 947 mime_type = keys::kMimeTypeJpeg; 948 break; 949 case FORMAT_PNG: 950 encoded = gfx::PNGCodec::EncodeBGRASkBitmap( 951 screen_capture, 952 true, // Discard transparency. 953 &image_data->data); 954 mime_type = keys::kMimeTypePng; 955 break; 956 default: 957 NOTREACHED() << "Invalid image format."; 958 } 959 960 if (!encoded) { 961 error_ = ExtensionErrorUtils::FormatErrorMessage( 962 keys::kInternalVisibleTabCaptureError, ""); 963 SendResponse(false); 964 return; 965 } 966 967 std::string base64_result; 968 std::string stream_as_string; 969 stream_as_string.resize(image_data->data.size()); 970 memcpy(&stream_as_string[0], 971 reinterpret_cast<const char*>(&image_data->data[0]), 972 image_data->data.size()); 973 974 base::Base64Encode(stream_as_string, &base64_result); 975 base64_result.insert(0, base::StringPrintf("data:%s;base64,", 976 mime_type.c_str())); 977 result_.reset(new StringValue(base64_result)); 978 SendResponse(true); 979} 980 981bool DetectTabLanguageFunction::RunImpl() { 982 int tab_id = 0; 983 Browser* browser = NULL; 984 TabContents* contents = NULL; 985 986 // If |tab_id| is specified, look for it. Otherwise default to selected tab 987 // in the current window. 988 if (HasOptionalArgument(0)) { 989 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); 990 if (!GetTabById(tab_id, profile(), include_incognito(), 991 &browser, NULL, &contents, NULL, &error_)) { 992 return false; 993 } 994 if (!browser || !contents) 995 return false; 996 } else { 997 browser = GetCurrentBrowser(); 998 if (!browser) 999 return false; 1000 contents = browser->tabstrip_model()->GetSelectedTabContents(); 1001 if (!contents) 1002 return false; 1003 } 1004 1005 if (contents->controller().needs_reload()) { 1006 // If the tab hasn't been loaded, such as happens with phantom tabs, don't 1007 // wait for the tab to load, instead return. 1008 error_ = keys::kCannotDetermineLanguageOfUnloadedTab; 1009 return false; 1010 } 1011 1012 AddRef(); // Balanced in GotLanguage() 1013 1014 if (!contents->language_state().original_language().empty()) { 1015 // Delay the callback invocation until after the current JS call has 1016 // returned. 1017 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( 1018 this, &DetectTabLanguageFunction::GotLanguage, 1019 contents->language_state().original_language())); 1020 return true; 1021 } 1022 // The tab contents does not know its language yet. Let's wait until it 1023 // receives it, or until the tab is closed/navigates to some other page. 1024 registrar_.Add(this, NotificationType::TAB_LANGUAGE_DETERMINED, 1025 Source<TabContents>(contents)); 1026 registrar_.Add(this, NotificationType::TAB_CLOSING, 1027 Source<NavigationController>(&(contents->controller()))); 1028 registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, 1029 Source<NavigationController>(&(contents->controller()))); 1030 return true; 1031} 1032 1033void DetectTabLanguageFunction::Observe(NotificationType type, 1034 const NotificationSource& source, 1035 const NotificationDetails& details) { 1036 std::string language; 1037 if (type == NotificationType::TAB_LANGUAGE_DETERMINED) 1038 language = *Details<std::string>(details).ptr(); 1039 1040 registrar_.RemoveAll(); 1041 1042 // Call GotLanguage in all cases as we want to guarantee the callback is 1043 // called for every API call the extension made. 1044 GotLanguage(language); 1045} 1046 1047void DetectTabLanguageFunction::GotLanguage(const std::string& language) { 1048 result_.reset(Value::CreateStringValue(language.c_str())); 1049 SendResponse(true); 1050 1051 Release(); // Balanced in Run() 1052} 1053 1054// static helpers 1055 1056static Browser* GetBrowserInProfileWithId(Profile* profile, 1057 const int window_id, 1058 bool include_incognito, 1059 std::string* error_message) { 1060 Profile* incognito_profile = 1061 include_incognito && profile->HasOffTheRecordProfile() ? 1062 profile->GetOffTheRecordProfile() : NULL; 1063 for (BrowserList::const_iterator browser = BrowserList::begin(); 1064 browser != BrowserList::end(); ++browser) { 1065 if (((*browser)->profile() == profile || 1066 (*browser)->profile() == incognito_profile) && 1067 ExtensionTabUtil::GetWindowId(*browser) == window_id) 1068 return *browser; 1069 } 1070 1071 if (error_message) 1072 *error_message = ExtensionErrorUtils::FormatErrorMessage( 1073 keys::kWindowNotFoundError, base::IntToString(window_id)); 1074 1075 return NULL; 1076} 1077 1078static bool GetTabById(int tab_id, Profile* profile, 1079 bool include_incognito, 1080 Browser** browser, 1081 TabStripModel** tab_strip, 1082 TabContents** contents, 1083 int* tab_index, 1084 std::string* error_message) { 1085 if (ExtensionTabUtil::GetTabById(tab_id, profile, include_incognito, 1086 browser, tab_strip, contents, tab_index)) 1087 return true; 1088 1089 if (error_message) 1090 *error_message = ExtensionErrorUtils::FormatErrorMessage( 1091 keys::kTabNotFoundError, base::IntToString(tab_id)); 1092 1093 return false; 1094} 1095 1096static std::string GetWindowTypeText(Browser::Type type) { 1097 // Note: for app popups, we report "app". 1098 if ((type & Browser::TYPE_APP) != 0 || type == Browser::TYPE_EXTENSION_APP) 1099 return keys::kWindowTypeValueApp; 1100 if ((type & Browser::TYPE_POPUP) != 0) 1101 return keys::kWindowTypeValuePopup; 1102 1103 DCHECK(type == Browser::TYPE_NORMAL); 1104 return keys::kWindowTypeValueNormal; 1105} 1106 1107static GURL ResolvePossiblyRelativeURL(std::string url_string, 1108 Extension* extension) { 1109 GURL url = GURL(url_string); 1110 if (!url.is_valid()) 1111 url = extension->GetResourceURL(url_string); 1112 1113 return url; 1114} 1115