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