| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/extension_tabs_module.h" |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/bind.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/stl_util.h" |
| #include "base/string16.h" |
| #include "base/string_number_conversions.h" |
| #include "base/string_util.h" |
| #include "base/stringprintf.h" |
| #include "base/utf_string_conversions.h" |
| #include "chrome/browser/extensions/extension_function_dispatcher.h" |
| #include "chrome/browser/extensions/extension_host.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/extension_tab_util.h" |
| #include "chrome/browser/extensions/extension_tabs_module_constants.h" |
| #include "chrome/browser/net/url_fixer_upper.h" |
| #include "chrome/browser/prefs/incognito_mode_prefs.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/sessions/restore_tab_helper.h" |
| #include "chrome/browser/tabs/tab_strip_model.h" |
| #include "chrome/browser/translate/translate_tab_helper.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/browser_navigator.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" |
| #include "chrome/browser/ui/window_sizer.h" |
| #include "chrome/browser/web_applications/web_app.h" |
| #include "chrome/common/chrome_notification_types.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/extensions/extension.h" |
| #include "chrome/common/extensions/extension_error_utils.h" |
| #include "chrome/common/extensions/extension_messages.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/url_constants.h" |
| #include "content/browser/renderer_host/backing_store.h" |
| #include "content/browser/renderer_host/render_view_host.h" |
| #include "content/browser/renderer_host/render_view_host_delegate.h" |
| #include "content/browser/tab_contents/navigation_entry.h" |
| #include "content/browser/tab_contents/tab_contents.h" |
| #include "content/browser/tab_contents/tab_contents_view.h" |
| #include "content/public/browser/notification_details.h" |
| #include "content/public/browser/notification_source.h" |
| #include "skia/ext/image_operations.h" |
| #include "skia/ext/platform_canvas.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/gfx/codec/jpeg_codec.h" |
| #include "ui/gfx/codec/png_codec.h" |
| |
| namespace keys = extension_tabs_module_constants; |
| namespace errors = extension_manifest_errors; |
| |
| const int CaptureVisibleTabFunction::kDefaultQuality = 90; |
| |
| namespace { |
| |
| // |error_message| can optionally be passed in a will be set with an appropriate |
| // message if the window cannot be found by id. |
| Browser* GetBrowserInProfileWithId(Profile* profile, |
| const int window_id, |
| bool include_incognito, |
| std::string* error_message) { |
| Profile* incognito_profile = |
| include_incognito && profile->HasOffTheRecordProfile() ? |
| profile->GetOffTheRecordProfile() : NULL; |
| for (BrowserList::const_iterator browser = BrowserList::begin(); |
| browser != BrowserList::end(); ++browser) { |
| if (((*browser)->profile() == profile || |
| (*browser)->profile() == incognito_profile) && |
| ExtensionTabUtil::GetWindowId(*browser) == window_id) |
| return *browser; |
| } |
| |
| if (error_message) |
| *error_message = ExtensionErrorUtils::FormatErrorMessage( |
| keys::kWindowNotFoundError, base::IntToString(window_id)); |
| |
| return NULL; |
| } |
| |
| // |error_message| can optionally be passed in and will be set with an |
| // appropriate message if the tab cannot be found by id. |
| bool GetTabById(int tab_id, |
| Profile* profile, |
| bool include_incognito, |
| Browser** browser, |
| TabStripModel** tab_strip, |
| TabContentsWrapper** contents, |
| int* tab_index, |
| std::string* error_message) { |
| if (ExtensionTabUtil::GetTabById(tab_id, profile, include_incognito, |
| browser, tab_strip, contents, tab_index)) |
| return true; |
| |
| if (error_message) |
| *error_message = ExtensionErrorUtils::FormatErrorMessage( |
| keys::kTabNotFoundError, base::IntToString(tab_id)); |
| |
| return false; |
| } |
| |
| // Takes |url_string| and returns a GURL which is either valid and absolute |
| // or invalid. If |url_string| is not directly interpretable as a valid (it is |
| // likely a relative URL) an attempt is made to resolve it. |extension| is |
| // provided so it can be resolved relative to its extension base |
| // (chrome-extension://<id>/). Using the source frame url would be more correct, |
| // but because the api shipped with urls resolved relative to their extension |
| // base, we decided it wasn't worth breaking existing extensions to fix. |
| GURL ResolvePossiblyRelativeURL(const std::string& url_string, |
| const Extension* extension) { |
| GURL url = GURL(url_string); |
| if (!url.is_valid()) |
| url = extension->GetResourceURL(url_string); |
| |
| return url; |
| } |
| |
| bool IsCrashURL(const GURL& url) { |
| // Check a fixed-up URL, to normalize the scheme and parse hosts correctly. |
| GURL fixed_url = |
| URLFixerUpper::FixupURL(url.possibly_invalid_spec(), std::string()); |
| return (fixed_url.SchemeIs(chrome::kChromeUIScheme) && |
| (fixed_url.host() == chrome::kChromeUIBrowserCrashHost || |
| fixed_url.host() == chrome::kChromeUICrashHost)); |
| } |
| |
| // Reads the |value| as either a single integer value or a list of integers. |
| bool ReadOneOrMoreIntegers( |
| Value* value, std::vector<int>* result) { |
| if (value->IsType(Value::TYPE_INTEGER)) { |
| int tab_id; |
| if (!value->GetAsInteger(&tab_id)) |
| return false; |
| result->push_back(tab_id); |
| return true; |
| |
| } else if (value->IsType(Value::TYPE_LIST)) { |
| ListValue* tabs = static_cast<ListValue*>(value); |
| for (size_t i = 0; i < tabs->GetSize(); ++i) { |
| int tab_id; |
| if (!tabs->GetInteger(i, &tab_id)) |
| return false; |
| result->push_back(tab_id); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| // A three state enum to distinguish between when a boolean query argument is |
| // set or not. |
| enum QueryArg { |
| NOT_SET = -1, |
| MATCH_FALSE, |
| MATCH_TRUE |
| }; |
| |
| bool MatchesQueryArg(QueryArg arg, bool value) { |
| if (arg == NOT_SET) |
| return true; |
| |
| return (arg == MATCH_TRUE && value) || (arg == MATCH_FALSE && !value); |
| } |
| |
| QueryArg ParseBoolQueryArg(base::DictionaryValue* query, const char* key) { |
| if (query->HasKey(key)) { |
| bool value = false; |
| CHECK(query->GetBoolean(key, &value)); |
| return value ? MATCH_TRUE : MATCH_FALSE; |
| } |
| return NOT_SET; |
| } |
| |
| } // namespace |
| |
| // Windows --------------------------------------------------------------------- |
| |
| bool GetWindowFunction::RunImpl() { |
| int window_id; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); |
| |
| Browser* browser = GetBrowserInProfileWithId(profile(), window_id, |
| include_incognito(), &error_); |
| if (!browser || !browser->window()) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage( |
| keys::kWindowNotFoundError, base::IntToString(window_id)); |
| return false; |
| } |
| |
| result_.reset(ExtensionTabUtil::CreateWindowValue(browser, false)); |
| return true; |
| } |
| |
| bool GetCurrentWindowFunction::RunImpl() { |
| Browser* browser = GetCurrentBrowser(); |
| if (!browser || !browser->window()) { |
| error_ = keys::kNoCurrentWindowError; |
| return false; |
| } |
| result_.reset(ExtensionTabUtil::CreateWindowValue(browser, false)); |
| return true; |
| } |
| |
| bool GetLastFocusedWindowFunction::RunImpl() { |
| Browser* browser = BrowserList::FindAnyBrowser( |
| profile(), include_incognito()); |
| if (!browser || !browser->window()) { |
| error_ = keys::kNoLastFocusedWindowError; |
| return false; |
| } |
| result_.reset(ExtensionTabUtil::CreateWindowValue(browser, false)); |
| return true; |
| } |
| |
| bool GetAllWindowsFunction::RunImpl() { |
| bool populate_tabs = false; |
| if (HasOptionalArgument(0)) { |
| DictionaryValue* args; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args)); |
| |
| if (args->HasKey(keys::kPopulateKey)) { |
| EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kPopulateKey, |
| &populate_tabs)); |
| } |
| } |
| |
| result_.reset(new ListValue()); |
| Profile* incognito_profile = |
| include_incognito() && profile()->HasOffTheRecordProfile() ? |
| profile()->GetOffTheRecordProfile() : NULL; |
| for (BrowserList::const_iterator browser = BrowserList::begin(); |
| browser != BrowserList::end(); ++browser) { |
| // Only examine browsers in the current profile that have windows. |
| if (((*browser)->profile() == profile() || |
| (*browser)->profile() == incognito_profile) && |
| (*browser)->window()) { |
| static_cast<ListValue*>(result_.get())-> |
| Append(ExtensionTabUtil::CreateWindowValue(*browser, populate_tabs)); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool CreateWindowFunction::RunImpl() { |
| DictionaryValue* args = NULL; |
| std::vector<GURL> urls; |
| TabContentsWrapper* contents = NULL; |
| |
| if (HasOptionalArgument(0)) |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args)); |
| |
| // Look for optional url. |
| if (args) { |
| if (args->HasKey(keys::kUrlKey)) { |
| Value* url_value; |
| std::vector<std::string> url_strings; |
| args->Get(keys::kUrlKey, &url_value); |
| |
| // First, get all the URLs the client wants to open. |
| if (url_value->IsType(Value::TYPE_STRING)) { |
| std::string url_string; |
| url_value->GetAsString(&url_string); |
| url_strings.push_back(url_string); |
| } else if (url_value->IsType(Value::TYPE_LIST)) { |
| const ListValue* url_list = static_cast<const ListValue*>(url_value); |
| for (size_t i = 0; i < url_list->GetSize(); ++i) { |
| std::string url_string; |
| EXTENSION_FUNCTION_VALIDATE(url_list->GetString(i, &url_string)); |
| url_strings.push_back(url_string); |
| } |
| } |
| |
| // Second, resolve, validate and convert them to GURLs. |
| for (std::vector<std::string>::iterator i = url_strings.begin(); |
| i != url_strings.end(); ++i) { |
| GURL url = ResolvePossiblyRelativeURL(*i, GetExtension()); |
| if (!url.is_valid()) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage( |
| keys::kInvalidUrlError, *i); |
| return false; |
| } |
| // Don't let the extension crash the browser or renderers. |
| if (IsCrashURL(url)) { |
| error_ = keys::kNoCrashBrowserError; |
| return false; |
| } |
| urls.push_back(url); |
| } |
| } |
| } |
| |
| // Look for optional tab id. |
| if (args) { |
| int tab_id; |
| if (args->HasKey(keys::kTabIdKey)) { |
| EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kTabIdKey, &tab_id)); |
| |
| // Find the tab and detach it from the original window. |
| Browser* source_browser = NULL; |
| TabStripModel* source_tab_strip = NULL; |
| int tab_index = -1; |
| if (!GetTabById(tab_id, profile(), include_incognito(), |
| &source_browser, &source_tab_strip, &contents, |
| &tab_index, &error_)) |
| return false; |
| contents = source_tab_strip->DetachTabContentsAt(tab_index); |
| if (!contents) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage( |
| keys::kTabNotFoundError, base::IntToString(tab_id)); |
| return false; |
| } |
| } |
| } |
| |
| // Try to position the new browser relative its originating browser window. |
| gfx::Rect window_bounds; |
| bool maximized; |
| // The call offsets the bounds by kWindowTilePixels (defined in WindowSizer to |
| // be 10) |
| // |
| // NOTE(rafaelw): It's ok if GetCurrentBrowser() returns NULL here. |
| // GetBrowserWindowBounds will default to saved "default" values for the app. |
| WindowSizer::GetBrowserWindowBounds(std::string(), gfx::Rect(), |
| GetCurrentBrowser(), &window_bounds, |
| &maximized); |
| |
| // Calculate popup and panels bounds separately. |
| gfx::Rect popup_bounds; |
| gfx::Rect panel_bounds; // Use 0x0 for panels. Panel manager sizes them. |
| |
| // In ChromiumOS the default popup bounds is 0x0 which indicates default |
| // window sizes in PanelBrowserView. In other OSs use the same default |
| // bounds as windows. |
| #if defined(OS_CHROMEOS) |
| popup_bounds = panel_bounds; |
| #else |
| popup_bounds = window_bounds; // Use window size as default for popups |
| #endif |
| |
| Profile* window_profile = profile(); |
| Browser::Type window_type = Browser::TYPE_TABBED; |
| bool focused = true; |
| bool saw_focus_key = false; |
| std::string extension_id; |
| |
| if (args) { |
| // Any part of the bounds can optionally be set by the caller. |
| int bounds_val; |
| if (args->HasKey(keys::kLeftKey)) { |
| EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kLeftKey, |
| &bounds_val)); |
| window_bounds.set_x(bounds_val); |
| popup_bounds.set_x(bounds_val); |
| panel_bounds.set_x(bounds_val); |
| } |
| |
| if (args->HasKey(keys::kTopKey)) { |
| EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kTopKey, |
| &bounds_val)); |
| window_bounds.set_y(bounds_val); |
| popup_bounds.set_y(bounds_val); |
| panel_bounds.set_y(bounds_val); |
| } |
| |
| if (args->HasKey(keys::kWidthKey)) { |
| EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kWidthKey, |
| &bounds_val)); |
| window_bounds.set_width(bounds_val); |
| popup_bounds.set_width(bounds_val); |
| panel_bounds.set_width(bounds_val); |
| } |
| |
| if (args->HasKey(keys::kHeightKey)) { |
| EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kHeightKey, |
| &bounds_val)); |
| window_bounds.set_height(bounds_val); |
| popup_bounds.set_height(bounds_val); |
| panel_bounds.set_height(bounds_val); |
| } |
| |
| bool incognito = false; |
| if (args->HasKey(keys::kIncognitoKey)) { |
| EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kIncognitoKey, |
| &incognito)); |
| if (IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) == |
| IncognitoModePrefs::DISABLED) { |
| error_ = keys::kIncognitoModeIsDisabled; |
| return false; |
| } |
| |
| if (incognito) { |
| std::string first_url_erased; |
| // Guest session is an exception as it always opens in incognito mode. |
| for (size_t i = 0; i < urls.size();) { |
| if (browser::IsURLAllowedInIncognito(urls[i]) && |
| !Profile::IsGuestSession()) { |
| if (first_url_erased.empty()) |
| first_url_erased = urls[i].spec(); |
| urls.erase(urls.begin() + i); |
| } else { |
| i++; |
| } |
| } |
| if (urls.empty() && !first_url_erased.empty()) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage( |
| keys::kURLsNotAllowedInIncognitoError, first_url_erased); |
| return false; |
| } |
| window_profile = window_profile->GetOffTheRecordProfile(); |
| } |
| } |
| |
| if (args->HasKey(keys::kFocusedKey)) { |
| EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kFocusedKey, |
| &focused)); |
| saw_focus_key = true; |
| } |
| |
| std::string type_str; |
| if (args->HasKey(keys::kWindowTypeKey)) { |
| EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kWindowTypeKey, |
| &type_str)); |
| if (type_str == keys::kWindowTypeValuePopup) { |
| window_type = Browser::TYPE_POPUP; |
| extension_id = GetExtension()->id(); |
| } else if (type_str == keys::kWindowTypeValuePanel) { |
| if (CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnablePanels)) { |
| window_type = Browser::TYPE_PANEL; |
| } else { |
| window_type = Browser::TYPE_POPUP; |
| } |
| extension_id = GetExtension()->id(); |
| } else if (type_str != keys::kWindowTypeValueNormal) { |
| EXTENSION_FUNCTION_VALIDATE(false); |
| } |
| } |
| } |
| |
| // Unlike other window types, Panels do not take focus by default. |
| if (!saw_focus_key && window_type == Browser::TYPE_PANEL) |
| focused = false; |
| |
| Browser* new_window; |
| if (extension_id.empty()) { |
| new_window = Browser::CreateForType(window_type, window_profile); |
| new_window->window()->SetBounds(window_bounds); |
| } else { |
| new_window = Browser::CreateForApp( |
| window_type, |
| web_app::GenerateApplicationNameFromExtensionId(extension_id), |
| (window_type == Browser::TYPE_PANEL ? panel_bounds : popup_bounds), |
| window_profile); |
| } |
| for (std::vector<GURL>::iterator i = urls.begin(); i != urls.end(); ++i) |
| new_window->AddSelectedTabWithURL(*i, content::PAGE_TRANSITION_LINK); |
| if (contents) { |
| TabStripModel* target_tab_strip = new_window->tabstrip_model(); |
| target_tab_strip->InsertTabContentsAt(urls.size(), contents, |
| TabStripModel::ADD_NONE); |
| } else if (urls.empty()) { |
| new_window->NewTab(); |
| } |
| new_window->SelectNumberedTab(0); |
| |
| if (focused) |
| new_window->window()->Show(); |
| else |
| new_window->window()->ShowInactive(); |
| |
| if (new_window->profile()->IsOffTheRecord() && !include_incognito()) { |
| // Don't expose incognito windows if the extension isn't allowed. |
| result_.reset(Value::CreateNullValue()); |
| } else { |
| result_.reset(ExtensionTabUtil::CreateWindowValue(new_window, true)); |
| } |
| |
| return true; |
| } |
| |
| bool UpdateWindowFunction::RunImpl() { |
| int window_id; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); |
| DictionaryValue* update_props; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props)); |
| |
| Browser* browser = GetBrowserInProfileWithId(profile(), window_id, |
| include_incognito(), &error_); |
| if (!browser || !browser->window()) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage( |
| keys::kWindowNotFoundError, base::IntToString(window_id)); |
| return false; |
| } |
| |
| gfx::Rect bounds = browser->window()->GetRestoredBounds(); |
| bool set_bounds = false; |
| // Any part of the bounds can optionally be set by the caller. |
| int bounds_val; |
| if (update_props->HasKey(keys::kLeftKey)) { |
| EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( |
| keys::kLeftKey, |
| &bounds_val)); |
| bounds.set_x(bounds_val); |
| set_bounds = true; |
| } |
| |
| if (update_props->HasKey(keys::kTopKey)) { |
| EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( |
| keys::kTopKey, |
| &bounds_val)); |
| bounds.set_y(bounds_val); |
| set_bounds = true; |
| } |
| |
| if (update_props->HasKey(keys::kWidthKey)) { |
| EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( |
| keys::kWidthKey, |
| &bounds_val)); |
| bounds.set_width(bounds_val); |
| set_bounds = true; |
| } |
| |
| if (update_props->HasKey(keys::kHeightKey)) { |
| EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( |
| keys::kHeightKey, |
| &bounds_val)); |
| bounds.set_height(bounds_val); |
| set_bounds = true; |
| } |
| if (set_bounds) |
| browser->window()->SetBounds(bounds); |
| |
| bool active_val = false; |
| if (update_props->HasKey(keys::kFocusedKey)) { |
| EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean( |
| keys::kFocusedKey, &active_val)); |
| if (active_val) |
| browser->window()->Activate(); |
| else |
| browser->window()->Deactivate(); |
| } |
| |
| bool draw_attention = false; |
| if (update_props->HasKey(keys::kDrawAttentionKey)) { |
| EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean( |
| keys::kDrawAttentionKey, &draw_attention)); |
| if (draw_attention) |
| browser->window()->FlashFrame(); |
| } |
| |
| result_.reset(ExtensionTabUtil::CreateWindowValue(browser, false)); |
| |
| return true; |
| } |
| |
| bool RemoveWindowFunction::RunImpl() { |
| int window_id; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); |
| |
| Browser* browser = GetBrowserInProfileWithId(profile(), window_id, |
| include_incognito(), &error_); |
| if (!browser) |
| return false; |
| |
| // Don't let the extension remove the window if the user is dragging tabs |
| // in that window. |
| if (!browser->IsTabStripEditable()) { |
| error_ = keys::kTabStripNotEditableError; |
| return false; |
| } |
| |
| browser->CloseWindow(); |
| |
| return true; |
| } |
| |
| // Tabs ------------------------------------------------------------------------ |
| |
| bool GetSelectedTabFunction::RunImpl() { |
| Browser* browser; |
| // windowId defaults to "current" window. |
| int window_id = -1; |
| |
| if (HasOptionalArgument(0)) { |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); |
| browser = GetBrowserInProfileWithId(profile(), window_id, |
| include_incognito(), &error_); |
| } else { |
| browser = GetCurrentBrowser(); |
| if (!browser) |
| error_ = keys::kNoCurrentWindowError; |
| } |
| if (!browser) |
| return false; |
| |
| TabStripModel* tab_strip = browser->tabstrip_model(); |
| TabContentsWrapper* contents = tab_strip->GetActiveTabContents(); |
| if (!contents) { |
| error_ = keys::kNoSelectedTabError; |
| return false; |
| } |
| result_.reset(ExtensionTabUtil::CreateTabValue(contents->tab_contents(), |
| tab_strip, |
| tab_strip->active_index())); |
| return true; |
| } |
| |
| bool GetAllTabsInWindowFunction::RunImpl() { |
| Browser* browser; |
| // windowId defaults to "current" window. |
| int window_id = -1; |
| if (HasOptionalArgument(0)) { |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); |
| browser = GetBrowserInProfileWithId(profile(), window_id, |
| include_incognito(), &error_); |
| } else { |
| browser = GetCurrentBrowser(); |
| if (!browser) |
| error_ = keys::kNoCurrentWindowError; |
| } |
| if (!browser) |
| return false; |
| |
| result_.reset(ExtensionTabUtil::CreateTabList(browser)); |
| |
| return true; |
| } |
| |
| bool QueryTabsFunction::RunImpl() { |
| DictionaryValue* query = NULL; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &query)); |
| |
| QueryArg active = ParseBoolQueryArg(query, keys::kActiveKey); |
| QueryArg pinned = ParseBoolQueryArg(query, keys::kPinnedKey); |
| QueryArg selected = ParseBoolQueryArg(query, keys::kHighlightedKey); |
| |
| QueryArg loading = NOT_SET; |
| if (query->HasKey(keys::kStatusKey)) { |
| std::string status; |
| EXTENSION_FUNCTION_VALIDATE(query->GetString(keys::kStatusKey, &status)); |
| loading = (status == keys::kStatusValueLoading) ? MATCH_TRUE : MATCH_FALSE; |
| } |
| |
| URLPattern url_pattern(URLPattern::SCHEME_ALL, "<all_urls>"); |
| if (query->HasKey(keys::kUrlKey)) { |
| std::string value; |
| EXTENSION_FUNCTION_VALIDATE(query->GetString(keys::kUrlKey, &value)); |
| url_pattern = URLPattern(URLPattern::SCHEME_ALL, value); |
| } |
| |
| std::string title; |
| if (query->HasKey(keys::kTitleKey)) |
| EXTENSION_FUNCTION_VALIDATE( |
| query->GetString(keys::kTitleKey, &title)); |
| |
| int window_id = -1; |
| if (query->HasKey(keys::kWindowIdKey)) |
| EXTENSION_FUNCTION_VALIDATE( |
| query->GetInteger(keys::kWindowIdKey, &window_id)); |
| |
| std::string window_type; |
| if (query->HasKey(keys::kWindowTypeLongKey)) |
| EXTENSION_FUNCTION_VALIDATE( |
| query->GetString(keys::kWindowTypeLongKey, &window_type)); |
| |
| ListValue* result = new ListValue(); |
| for (BrowserList::const_iterator browser = BrowserList::begin(); |
| browser != BrowserList::end(); ++browser) { |
| if (!profile()->IsSameProfile((*browser)->profile())) |
| continue; |
| |
| if (!(*browser)->window()) |
| continue; |
| |
| if (window_id >= 0 && window_id != ExtensionTabUtil::GetWindowId(*browser)) |
| continue; |
| |
| if (!window_type.empty() && |
| window_type != ExtensionTabUtil::GetWindowTypeText(*browser)) |
| continue; |
| |
| TabStripModel* tab_strip = (*browser)->tabstrip_model(); |
| for (int i = 0; i < tab_strip->count(); ++i) { |
| const TabContents* tab_contents = |
| tab_strip->GetTabContentsAt(i)->tab_contents(); |
| |
| if (!MatchesQueryArg(selected, tab_strip->IsTabSelected(i))) |
| continue; |
| |
| if (!MatchesQueryArg(active, i == tab_strip->active_index())) |
| continue; |
| |
| if (!MatchesQueryArg(pinned, tab_strip->IsTabPinned(i))) |
| continue; |
| |
| if (!title.empty() && !MatchPattern(tab_contents->GetTitle(), |
| UTF8ToUTF16(title))) |
| continue; |
| |
| if (!url_pattern.MatchesURL(tab_contents->GetURL())) |
| continue; |
| |
| if (!MatchesQueryArg(loading, tab_contents->IsLoading())) |
| continue; |
| |
| result->Append(ExtensionTabUtil::CreateTabValue( |
| tab_contents, tab_strip, i)); |
| } |
| } |
| |
| result_.reset(result); |
| return true; |
| } |
| |
| bool CreateTabFunction::RunImpl() { |
| DictionaryValue* args; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args)); |
| |
| Browser *browser; |
| // windowId defaults to "current" window. |
| int window_id = -1; |
| if (args->HasKey(keys::kWindowIdKey)) { |
| EXTENSION_FUNCTION_VALIDATE(args->GetInteger( |
| keys::kWindowIdKey, &window_id)); |
| browser = GetBrowserInProfileWithId(profile(), window_id, |
| include_incognito(), &error_); |
| } else { |
| browser = GetCurrentBrowser(); |
| if (!browser) |
| error_ = keys::kNoCurrentWindowError; |
| } |
| if (!browser) |
| return false; |
| |
| // TODO(rafaelw): handle setting remaining tab properties: |
| // -title |
| // -favIconUrl |
| |
| std::string url_string; |
| GURL url; |
| if (args->HasKey(keys::kUrlKey)) { |
| EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kUrlKey, |
| &url_string)); |
| url = ResolvePossiblyRelativeURL(url_string, GetExtension()); |
| if (!url.is_valid()) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, |
| url_string); |
| return false; |
| } |
| } |
| |
| // Don't let extensions crash the browser or renderers. |
| if (IsCrashURL(url)) { |
| error_ = keys::kNoCrashBrowserError; |
| return false; |
| } |
| |
| // Default to foreground for the new tab. The presence of 'selected' property |
| // will override this default. This property is deprecated ('active' should |
| // be used instead). |
| bool active = true; |
| if (args->HasKey(keys::kSelectedKey)) |
| EXTENSION_FUNCTION_VALIDATE( |
| args->GetBoolean(keys::kSelectedKey, &active)); |
| |
| // The 'active' property has replaced the 'selected' property. |
| if (args->HasKey(keys::kActiveKey)) |
| EXTENSION_FUNCTION_VALIDATE( |
| args->GetBoolean(keys::kActiveKey, &active)); |
| |
| // Default to not pinning the tab. Setting the 'pinned' property to true |
| // will override this default. |
| bool pinned = false; |
| if (args->HasKey(keys::kPinnedKey)) |
| EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kPinnedKey, &pinned)); |
| |
| // We can't load extension URLs into incognito windows unless the extension |
| // uses split mode. Special case to fall back to a tabbed window. |
| if (url.SchemeIs(chrome::kExtensionScheme) && |
| !GetExtension()->incognito_split_mode() && |
| browser->profile()->IsOffTheRecord()) { |
| Profile* profile = browser->profile()->GetOriginalProfile(); |
| browser = BrowserList::FindTabbedBrowser(profile, false); |
| if (!browser) { |
| browser = Browser::Create(profile); |
| browser->window()->Show(); |
| } |
| } |
| |
| // If index is specified, honor the value, but keep it bound to |
| // -1 <= index <= tab_strip->count() where -1 invokes the default behavior. |
| int index = -1; |
| if (args->HasKey(keys::kIndexKey)) |
| EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kIndexKey, &index)); |
| |
| TabStripModel* tab_strip = browser->tabstrip_model(); |
| |
| index = std::min(std::max(index, -1), tab_strip->count()); |
| |
| int add_types = active ? TabStripModel::ADD_ACTIVE : |
| TabStripModel::ADD_NONE; |
| add_types |= TabStripModel::ADD_FORCE_INDEX; |
| if (pinned) |
| add_types |= TabStripModel::ADD_PINNED; |
| browser::NavigateParams params(browser, url, content::PAGE_TRANSITION_LINK); |
| params.disposition = active ? NEW_FOREGROUND_TAB : NEW_BACKGROUND_TAB; |
| params.tabstrip_index = index; |
| params.tabstrip_add_types = add_types; |
| browser::Navigate(¶ms); |
| |
| if (active) |
| params.target_contents->view()->SetInitialFocus(); |
| |
| // Return data about the newly created tab. |
| if (has_callback()) { |
| result_.reset(ExtensionTabUtil::CreateTabValue( |
| params.target_contents->tab_contents(), |
| params.browser->tabstrip_model(), |
| params.browser->tabstrip_model()->GetIndexOfTabContents( |
| params.target_contents))); |
| } |
| |
| return true; |
| } |
| |
| bool GetTabFunction::RunImpl() { |
| int tab_id; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); |
| |
| TabStripModel* tab_strip = NULL; |
| TabContentsWrapper* contents = NULL; |
| int tab_index = -1; |
| if (!GetTabById(tab_id, profile(), include_incognito(), |
| NULL, &tab_strip, &contents, &tab_index, &error_)) |
| return false; |
| |
| result_.reset(ExtensionTabUtil::CreateTabValue(contents->tab_contents(), |
| tab_strip, |
| tab_index)); |
| return true; |
| } |
| |
| bool GetCurrentTabFunction::RunImpl() { |
| DCHECK(dispatcher()); |
| |
| TabContents* contents = dispatcher()->delegate()->GetAssociatedTabContents(); |
| if (contents) |
| result_.reset(ExtensionTabUtil::CreateTabValue(contents)); |
| |
| return true; |
| } |
| |
| bool HighlightTabsFunction::RunImpl() { |
| DictionaryValue* info = NULL; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &info)); |
| |
| // Get the window id from the params. |
| int window_id = -1; |
| EXTENSION_FUNCTION_VALIDATE( |
| info->GetInteger(keys::kWindowIdKey, &window_id)); |
| |
| Browser* browser = GetBrowserInProfileWithId( |
| profile(), window_id, include_incognito(), &error_); |
| |
| if (!browser || !browser->window()) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage( |
| keys::kWindowNotFoundError, base::IntToString(window_id)); |
| return false; |
| } |
| |
| TabStripModel* tabstrip = browser->tabstrip_model(); |
| TabStripSelectionModel selection; |
| int active_index = -1; |
| |
| Value* tab_value = NULL; |
| EXTENSION_FUNCTION_VALIDATE(info->Get(keys::kTabsKey, &tab_value)); |
| |
| std::vector<int> tab_indices; |
| EXTENSION_FUNCTION_VALIDATE(ReadOneOrMoreIntegers(tab_value, &tab_indices)); |
| |
| // Create a new selection model as we read the list of tab indices. |
| for (size_t i = 0; i < tab_indices.size(); ++i) { |
| int index = tab_indices[i]; |
| |
| // Make sure the index is in range. |
| if (!tabstrip->ContainsIndex(index)) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage( |
| keys::kTabIndexNotFoundError, base::IntToString(index)); |
| return false; |
| } |
| |
| // By default, we make the first tab in the list active. |
| if (active_index == -1) |
| active_index = index; |
| |
| selection.AddIndexToSelection(index); |
| } |
| |
| // Make sure they actually specified tabs to select. |
| if (selection.empty()) { |
| error_ = keys::kNoHighlightedTabError; |
| return false; |
| } |
| |
| selection.set_active(active_index); |
| browser->tabstrip_model()->SetSelectionFromModel(selection); |
| result_.reset(ExtensionTabUtil::CreateWindowValue(browser, true)); |
| return true; |
| } |
| |
| UpdateTabFunction::UpdateTabFunction() { |
| } |
| |
| bool UpdateTabFunction::RunImpl() { |
| DictionaryValue* update_props; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props)); |
| |
| Value* tab_value = NULL; |
| if (HasOptionalArgument(0)) { |
| EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &tab_value)); |
| } |
| |
| int tab_id = -1; |
| TabContentsWrapper* contents = NULL; |
| if (tab_value == NULL || tab_value->IsType(Value::TYPE_NULL)) { |
| Browser* browser = GetCurrentBrowser(); |
| if (!browser) { |
| error_ = keys::kNoCurrentWindowError; |
| return false; |
| } |
| contents = browser->tabstrip_model()->GetActiveTabContents(); |
| if (!contents) { |
| error_ = keys::kNoSelectedTabError; |
| return false; |
| } |
| tab_id = ExtensionTabUtil::GetTabId(contents->tab_contents()); |
| } else { |
| EXTENSION_FUNCTION_VALIDATE(tab_value->GetAsInteger(&tab_id)); |
| } |
| |
| int tab_index = -1; |
| TabStripModel* tab_strip = NULL; |
| if (!GetTabById(tab_id, profile(), include_incognito(), |
| NULL, &tab_strip, &contents, &tab_index, &error_)) { |
| return false; |
| } |
| NavigationController& controller = contents->controller(); |
| |
| // TODO(rafaelw): handle setting remaining tab properties: |
| // -title |
| // -favIconUrl |
| |
| // Navigate the tab to a new location if the url is different. |
| std::string url_string; |
| if (update_props->HasKey(keys::kUrlKey)) { |
| EXTENSION_FUNCTION_VALIDATE(update_props->GetString( |
| keys::kUrlKey, &url_string)); |
| GURL url = ResolvePossiblyRelativeURL(url_string, GetExtension()); |
| |
| if (!url.is_valid()) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, |
| url_string); |
| return false; |
| } |
| |
| // Don't let the extension crash the browser or renderers. |
| if (IsCrashURL(url)) { |
| error_ = keys::kNoCrashBrowserError; |
| return false; |
| } |
| |
| // JavaScript URLs can do the same kinds of things as cross-origin XHR, so |
| // we need to check host permissions before allowing them. |
| if (url.SchemeIs(chrome::kJavaScriptScheme)) { |
| if (!GetExtension()->CanExecuteScriptOnPage( |
| contents->tab_contents()->GetURL(), NULL, &error_)) { |
| return false; |
| } |
| |
| ExtensionMsg_ExecuteCode_Params params; |
| params.request_id = request_id(); |
| params.extension_id = extension_id(); |
| params.is_javascript = true; |
| params.code = url.path(); |
| params.all_frames = false; |
| params.in_main_world = true; |
| |
| RenderViewHost* render_view_host = |
| contents->tab_contents()->render_view_host(); |
| render_view_host->Send( |
| new ExtensionMsg_ExecuteCode(render_view_host->routing_id(), |
| params)); |
| |
| Observe(contents->tab_contents()); |
| AddRef(); // balanced in Observe() |
| |
| return true; |
| } |
| |
| controller.LoadURL( |
| url, GURL(), content::PAGE_TRANSITION_LINK, std::string()); |
| |
| // The URL of a tab contents never actually changes to a JavaScript URL, so |
| // this check only makes sense in other cases. |
| if (!url.SchemeIs(chrome::kJavaScriptScheme)) |
| DCHECK_EQ(url.spec(), contents->tab_contents()->GetURL().spec()); |
| } |
| |
| bool active = false; |
| // TODO(rafaelw): Setting |active| from js doesn't make much sense. |
| // Move tab selection management up to window. |
| if (update_props->HasKey(keys::kSelectedKey)) |
| EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean( |
| keys::kSelectedKey, &active)); |
| |
| // The 'active' property has replaced 'selected'. |
| if (update_props->HasKey(keys::kActiveKey)) |
| EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean( |
| keys::kActiveKey, &active)); |
| |
| if (active) { |
| if (tab_strip->active_index() != tab_index) { |
| tab_strip->ActivateTabAt(tab_index, false); |
| DCHECK_EQ(contents, tab_strip->GetActiveTabContents()); |
| } |
| contents->tab_contents()->Focus(); |
| } |
| |
| bool highlighted = false; |
| if (update_props->HasKey(keys::kHighlightedKey)) { |
| EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean( |
| keys::kHighlightedKey, &highlighted)); |
| if (highlighted != tab_strip->IsTabSelected(tab_index)) |
| tab_strip->ToggleSelectionAt(tab_index); |
| } |
| |
| bool pinned = false; |
| if (update_props->HasKey(keys::kPinnedKey)) { |
| EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean(keys::kPinnedKey, |
| &pinned)); |
| tab_strip->SetTabPinned(tab_index, pinned); |
| |
| // Update the tab index because it may move when being pinned. |
| tab_index = tab_strip->GetIndexOfTabContents(contents); |
| } |
| |
| if (has_callback()) { |
| if (GetExtension()->HasAPIPermission(ExtensionAPIPermission::kTab)) { |
| result_.reset(ExtensionTabUtil::CreateTabValue(contents->tab_contents(), |
| tab_strip, |
| tab_index)); |
| } else { |
| result_.reset(Value::CreateNullValue()); |
| } |
| } |
| |
| SendResponse(true); |
| return true; |
| } |
| |
| bool UpdateTabFunction::OnMessageReceived(const IPC::Message& message) { |
| if (message.type() != ExtensionHostMsg_ExecuteCodeFinished::ID) |
| return false; |
| |
| int message_request_id; |
| void* iter = NULL; |
| if (!message.ReadInt(&iter, &message_request_id)) { |
| NOTREACHED() << "malformed extension message"; |
| return true; |
| } |
| |
| if (message_request_id != request_id()) |
| return false; |
| |
| IPC_BEGIN_MESSAGE_MAP(UpdateTabFunction, message) |
| IPC_MESSAGE_HANDLER(ExtensionHostMsg_ExecuteCodeFinished, |
| OnExecuteCodeFinished) |
| IPC_END_MESSAGE_MAP() |
| return true; |
| } |
| |
| void UpdateTabFunction::OnExecuteCodeFinished(int request_id, |
| bool success, |
| const std::string& error) { |
| if (!error.empty()) { |
| CHECK(!success); |
| error_ = error; |
| } |
| |
| SendResponse(success); |
| |
| Observe(NULL); |
| Release(); // balanced in Execute() |
| } |
| |
| bool MoveTabsFunction::RunImpl() { |
| Value* tab_value = NULL; |
| EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &tab_value)); |
| |
| std::vector<int> tab_ids; |
| EXTENSION_FUNCTION_VALIDATE(ReadOneOrMoreIntegers(tab_value, &tab_ids)); |
| |
| DictionaryValue* update_props; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props)); |
| |
| int new_index; |
| EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( |
| keys::kIndexKey, &new_index)); |
| EXTENSION_FUNCTION_VALIDATE(new_index >= 0); |
| |
| ListValue tab_values; |
| for (size_t i = 0; i < tab_ids.size(); ++i) { |
| Browser* source_browser = NULL; |
| TabStripModel* source_tab_strip = NULL; |
| TabContentsWrapper* contents = NULL; |
| int tab_index = -1; |
| if (!GetTabById(tab_ids[i], profile(), include_incognito(), |
| &source_browser, &source_tab_strip, &contents, |
| &tab_index, &error_)) |
| return false; |
| |
| // Don't let the extension move the tab if the user is dragging tabs. |
| if (!source_browser->IsTabStripEditable()) { |
| error_ = keys::kTabStripNotEditableError; |
| return false; |
| } |
| |
| // Insert the tabs one after another. |
| new_index += i; |
| |
| if (update_props->HasKey(keys::kWindowIdKey)) { |
| Browser* target_browser; |
| int window_id; |
| EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( |
| keys::kWindowIdKey, &window_id)); |
| target_browser = GetBrowserInProfileWithId(profile(), window_id, |
| include_incognito(), &error_); |
| if (!target_browser) |
| return false; |
| |
| if (!target_browser->IsTabStripEditable()) { |
| error_ = keys::kTabStripNotEditableError; |
| return false; |
| } |
| |
| if (!target_browser->is_type_tabbed()) { |
| error_ = keys::kCanOnlyMoveTabsWithinNormalWindowsError; |
| return false; |
| } |
| |
| if (target_browser->profile() != source_browser->profile()) { |
| error_ = keys::kCanOnlyMoveTabsWithinSameProfileError; |
| return false; |
| } |
| |
| // If windowId is different from the current window, move between windows. |
| if (ExtensionTabUtil::GetWindowId(target_browser) != |
| ExtensionTabUtil::GetWindowId(source_browser)) { |
| TabStripModel* target_tab_strip = target_browser->tabstrip_model(); |
| contents = source_tab_strip->DetachTabContentsAt(tab_index); |
| if (!contents) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage( |
| keys::kTabNotFoundError, base::IntToString(tab_ids[i])); |
| return false; |
| } |
| |
| // Clamp move location to the last position. |
| // This is ">" because it can append to a new index position. |
| if (new_index > target_tab_strip->count()) |
| new_index = target_tab_strip->count(); |
| |
| target_tab_strip->InsertTabContentsAt( |
| new_index, contents, TabStripModel::ADD_NONE); |
| |
| if (has_callback()) |
| tab_values.Append(ExtensionTabUtil::CreateTabValue( |
| contents->tab_contents(), target_tab_strip, new_index)); |
| |
| continue; |
| } |
| } |
| |
| // Perform a simple within-window move. |
| // Clamp move location to the last position. |
| // This is ">=" because the move must be to an existing location. |
| if (new_index >= source_tab_strip->count()) |
| new_index = source_tab_strip->count() - 1; |
| |
| if (new_index != tab_index) |
| source_tab_strip->MoveTabContentsAt(tab_index, new_index, false); |
| |
| if (has_callback()) |
| tab_values.Append(ExtensionTabUtil::CreateTabValue( |
| contents->tab_contents(), source_tab_strip, new_index)); |
| } |
| |
| if (!has_callback()) |
| return true; |
| |
| // Only return the results as an array if there are multiple tabs. |
| if (tab_ids.size() > 1) { |
| result_.reset(tab_values.DeepCopy()); |
| } else if (tab_ids.size() == 1) { |
| Value* value = NULL; |
| CHECK(tab_values.Get(0, &value)); |
| result_.reset(value->DeepCopy()); |
| } |
| return true; |
| } |
| |
| bool ReloadTabFunction::RunImpl() { |
| bool bypass_cache = false; |
| if (HasOptionalArgument(1)) { |
| DictionaryValue* reload_props = NULL; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &reload_props)); |
| |
| if (reload_props->HasKey(keys::kBypassCache)) { |
| EXTENSION_FUNCTION_VALIDATE(reload_props->GetBoolean( |
| keys::kBypassCache, |
| &bypass_cache)); |
| } |
| } |
| |
| TabContentsWrapper* contents = NULL; |
| |
| // If |tab_id| is specified, look for it. Otherwise default to selected tab |
| // in the current window. |
| Value* tab_value = NULL; |
| if (HasOptionalArgument(0)) |
| EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &tab_value)); |
| |
| if (tab_value == NULL || tab_value->IsType(Value::TYPE_NULL)) { |
| Browser* browser = GetCurrentBrowser(); |
| if (!browser) { |
| error_ = keys::kNoCurrentWindowError; |
| return false; |
| } |
| |
| if (!ExtensionTabUtil::GetDefaultTab(browser, &contents, NULL)) |
| return false; |
| } else { |
| int tab_id; |
| EXTENSION_FUNCTION_VALIDATE(tab_value->GetAsInteger(&tab_id)); |
| |
| Browser* browser = NULL; |
| if (!GetTabById(tab_id, profile(), include_incognito(), |
| &browser, NULL, &contents, NULL, &error_)) |
| return false; |
| } |
| |
| TabContents* tab_contents = contents->tab_contents(); |
| if (tab_contents->showing_interstitial_page()) { |
| // This does as same as Browser::ReloadInternal. |
| NavigationEntry* entry = tab_contents->controller().GetActiveEntry(); |
| GetCurrentBrowser()->OpenURL(entry->url(), GURL(), CURRENT_TAB, |
| content::PAGE_TRANSITION_RELOAD); |
| } else if (bypass_cache) { |
| tab_contents->controller().ReloadIgnoringCache(true); |
| } else { |
| tab_contents->controller().Reload(true); |
| } |
| |
| return true; |
| } |
| |
| bool RemoveTabsFunction::RunImpl() { |
| Value* tab_value = NULL; |
| EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &tab_value)); |
| |
| std::vector<int> tab_ids; |
| EXTENSION_FUNCTION_VALIDATE(ReadOneOrMoreIntegers(tab_value, &tab_ids)); |
| |
| for (size_t i = 0; i < tab_ids.size(); ++i) { |
| Browser* browser = NULL; |
| TabContentsWrapper* contents = NULL; |
| if (!GetTabById(tab_ids[i], profile(), include_incognito(), |
| &browser, NULL, &contents, NULL, &error_)) |
| return false; |
| |
| // Don't let the extension remove a tab if the user is dragging tabs around. |
| if (!browser->IsTabStripEditable()) { |
| error_ = keys::kTabStripNotEditableError; |
| return false; |
| } |
| |
| // Close the tab in this convoluted way, since there's a chance that the tab |
| // is being dragged, or we're in some other nested event loop. This code |
| // path should ensure that the tab is safely closed under such |
| // circumstances, whereas |Browser::CloseTabContents()| does not. |
| RenderViewHost* render_view_host = contents->render_view_host(); |
| render_view_host->delegate()->Close(render_view_host); |
| } |
| return true; |
| } |
| |
| bool CaptureVisibleTabFunction::RunImpl() { |
| Browser* browser; |
| // windowId defaults to "current" window. |
| int window_id = -1; |
| |
| if (HasOptionalArgument(0)) { |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id)); |
| browser = GetBrowserInProfileWithId(profile(), window_id, |
| include_incognito(), &error_); |
| } else { |
| browser = GetCurrentBrowser(); |
| } |
| |
| if (!browser) { |
| error_ = keys::kNoCurrentWindowError; |
| return false; |
| } |
| |
| image_format_ = FORMAT_JPEG; // Default format is JPEG. |
| image_quality_ = kDefaultQuality; // Default quality setting. |
| |
| if (HasOptionalArgument(1)) { |
| DictionaryValue* options; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &options)); |
| |
| if (options->HasKey(keys::kFormatKey)) { |
| std::string format; |
| EXTENSION_FUNCTION_VALIDATE( |
| options->GetString(keys::kFormatKey, &format)); |
| |
| if (format == keys::kFormatValueJpeg) { |
| image_format_ = FORMAT_JPEG; |
| } else if (format == keys::kFormatValuePng) { |
| image_format_ = FORMAT_PNG; |
| } else { |
| // Schema validation should make this unreachable. |
| EXTENSION_FUNCTION_VALIDATE(0); |
| } |
| } |
| |
| if (options->HasKey(keys::kQualityKey)) { |
| EXTENSION_FUNCTION_VALIDATE( |
| options->GetInteger(keys::kQualityKey, &image_quality_)); |
| } |
| } |
| |
| TabContents* tab_contents = browser->GetSelectedTabContents(); |
| if (!tab_contents) { |
| error_ = keys::kInternalVisibleTabCaptureError; |
| return false; |
| } |
| |
| // captureVisibleTab() can return an image containing sensitive information |
| // that the browser would otherwise protect. Ensure the extension has |
| // permission to do this. |
| if (!GetExtension()->CanCaptureVisiblePage(tab_contents->GetURL(), &error_)) |
| return false; |
| |
| RenderViewHost* render_view_host = tab_contents->render_view_host(); |
| |
| // If a backing store is cached for the tab we want to capture, |
| // and it can be copied into a bitmap, then use it to generate the image. |
| BackingStore* backing_store = render_view_host->GetBackingStore(false); |
| if (backing_store && CaptureSnapshotFromBackingStore(backing_store)) |
| return true; |
| |
| // Ask the renderer for a snapshot of the tab. |
| TabContentsWrapper* wrapper = browser->GetSelectedTabContentsWrapper(); |
| wrapper->CaptureSnapshot(); |
| registrar_.Add(this, |
| chrome::NOTIFICATION_TAB_SNAPSHOT_TAKEN, |
| content::Source<TabContentsWrapper>(wrapper)); |
| AddRef(); // Balanced in CaptureVisibleTabFunction::Observe(). |
| |
| return true; |
| } |
| |
| // Build the image of a tab's contents out of a backing store. |
| // This may fail if we can not copy a backing store into a bitmap. |
| // For example, some uncommon X11 visual modes are not supported by |
| // CopyFromBackingStore(). |
| bool CaptureVisibleTabFunction::CaptureSnapshotFromBackingStore( |
| BackingStore* backing_store) { |
| |
| skia::PlatformCanvas temp_canvas; |
| if (!backing_store->CopyFromBackingStore(gfx::Rect(backing_store->size()), |
| &temp_canvas)) { |
| return false; |
| } |
| VLOG(1) << "captureVisibleTab() got image from backing store."; |
| |
| SendResultFromBitmap( |
| skia::GetTopDevice(temp_canvas)->accessBitmap(false)); |
| return true; |
| } |
| |
| // If a backing store was not available in CaptureVisibleTabFunction::RunImpl, |
| // than the renderer was asked for a snapshot. Listen for a notification |
| // that the snapshot is available. |
| void CaptureVisibleTabFunction::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| DCHECK(type == chrome::NOTIFICATION_TAB_SNAPSHOT_TAKEN); |
| |
| const SkBitmap *screen_capture = |
| content::Details<const SkBitmap>(details).ptr(); |
| const bool error = screen_capture->empty(); |
| |
| if (error) { |
| error_ = keys::kInternalVisibleTabCaptureError; |
| SendResponse(false); |
| } else { |
| VLOG(1) << "captureVisibleTab() got image from renderer."; |
| SendResultFromBitmap(*screen_capture); |
| } |
| |
| Release(); // Balanced in CaptureVisibleTabFunction::RunImpl(). |
| } |
| |
| // Turn a bitmap of the screen into an image, set that image as the result, |
| // and call SendResponse(). |
| void CaptureVisibleTabFunction::SendResultFromBitmap( |
| const SkBitmap& screen_capture) { |
| std::vector<unsigned char> data; |
| SkAutoLockPixels screen_capture_lock(screen_capture); |
| bool encoded = false; |
| std::string mime_type; |
| switch (image_format_) { |
| case FORMAT_JPEG: |
| encoded = gfx::JPEGCodec::Encode( |
| reinterpret_cast<unsigned char*>(screen_capture.getAddr32(0, 0)), |
| gfx::JPEGCodec::FORMAT_SkBitmap, |
| screen_capture.width(), |
| screen_capture.height(), |
| static_cast<int>(screen_capture.rowBytes()), |
| image_quality_, |
| &data); |
| mime_type = keys::kMimeTypeJpeg; |
| break; |
| case FORMAT_PNG: |
| encoded = gfx::PNGCodec::EncodeBGRASkBitmap( |
| screen_capture, |
| true, // Discard transparency. |
| &data); |
| mime_type = keys::kMimeTypePng; |
| break; |
| default: |
| NOTREACHED() << "Invalid image format."; |
| } |
| |
| if (!encoded) { |
| error_ = keys::kInternalVisibleTabCaptureError; |
| SendResponse(false); |
| return; |
| } |
| |
| std::string base64_result; |
| base::StringPiece stream_as_string( |
| reinterpret_cast<const char*>(vector_as_array(&data)), data.size()); |
| |
| base::Base64Encode(stream_as_string, &base64_result); |
| base64_result.insert(0, base::StringPrintf("data:%s;base64,", |
| mime_type.c_str())); |
| result_.reset(new StringValue(base64_result)); |
| SendResponse(true); |
| } |
| |
| bool DetectTabLanguageFunction::RunImpl() { |
| int tab_id = 0; |
| Browser* browser = NULL; |
| TabContentsWrapper* contents = NULL; |
| |
| // If |tab_id| is specified, look for it. Otherwise default to selected tab |
| // in the current window. |
| if (HasOptionalArgument(0)) { |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); |
| if (!GetTabById(tab_id, profile(), include_incognito(), |
| &browser, NULL, &contents, NULL, &error_)) { |
| return false; |
| } |
| if (!browser || !contents) |
| return false; |
| } else { |
| browser = GetCurrentBrowser(); |
| if (!browser) |
| return false; |
| contents = browser->tabstrip_model()->GetActiveTabContents(); |
| if (!contents) |
| return false; |
| } |
| |
| if (contents->controller().needs_reload()) { |
| // If the tab hasn't been loaded, don't wait for the tab to load. |
| error_ = keys::kCannotDetermineLanguageOfUnloadedTab; |
| return false; |
| } |
| |
| AddRef(); // Balanced in GotLanguage() |
| |
| TranslateTabHelper* helper = contents->translate_tab_helper(); |
| if (!helper->language_state().original_language().empty()) { |
| // Delay the callback invocation until after the current JS call has |
| // returned. |
| MessageLoop::current()->PostTask(FROM_HERE, base::Bind( |
| &DetectTabLanguageFunction::GotLanguage, this, |
| helper->language_state().original_language())); |
| return true; |
| } |
| // The tab contents does not know its language yet. Let's wait until it |
| // receives it, or until the tab is closed/navigates to some other page. |
| registrar_.Add(this, chrome::NOTIFICATION_TAB_LANGUAGE_DETERMINED, |
| content::Source<TabContents>(contents->tab_contents())); |
| registrar_.Add( |
| this, content::NOTIFICATION_TAB_CLOSING, |
| content::Source<NavigationController>(&(contents->controller()))); |
| registrar_.Add( |
| this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, |
| content::Source<NavigationController>(&(contents->controller()))); |
| return true; |
| } |
| |
| void DetectTabLanguageFunction::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| std::string language; |
| if (type == chrome::NOTIFICATION_TAB_LANGUAGE_DETERMINED) |
| language = *content::Details<std::string>(details).ptr(); |
| |
| registrar_.RemoveAll(); |
| |
| // Call GotLanguage in all cases as we want to guarantee the callback is |
| // called for every API call the extension made. |
| GotLanguage(language); |
| } |
| |
| void DetectTabLanguageFunction::GotLanguage(const std::string& language) { |
| result_.reset(Value::CreateStringValue(language.c_str())); |
| SendResponse(true); |
| |
| Release(); // Balanced in Run() |
| } |