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