blob: 3201c96797a0395711afbbabd64611a945cc6e82 [file] [log] [blame]
// Copyright (c) 2012 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/api/tabs/tabs_api.h"
#include <algorithm>
#include <limits>
#include <vector>
#include "base/base64.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/ref_counted_memory.h"
#include "base/message_loop.h"
#include "base/prefs/pref_service.h"
#include "base/stl_util.h"
#include "base/string16.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/strings/string_number_conversions.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/extensions/api/tabs/tabs_constants.h"
#include "chrome/browser/extensions/extension_function_dispatcher.h"
#include "chrome/browser/extensions/extension_function_util.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/script_executor.h"
#include "chrome/browser/extensions/tab_helper.h"
#include "chrome/browser/extensions/window_controller.h"
#include "chrome/browser/extensions/window_controller_list.h"
#include "chrome/browser/prefs/incognito_mode_prefs.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/translate/translate_tab_helper.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_iterator.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/extensions/shell_window.h"
#include "chrome/browser/ui/host_desktop.h"
#include "chrome/browser/ui/panels/panel_manager.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/window_sizer/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/api/i18n/default_locale_handler.h"
#include "chrome/common/extensions/api/tabs.h"
#include "chrome/common/extensions/api/windows.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_file_util.h"
#include "chrome/common/extensions/extension_l10n_util.h"
#include "chrome/common/extensions/extension_manifest_constants.h"
#include "chrome/common/extensions/extension_messages.h"
#include "chrome/common/extensions/incognito_handler.h"
#include "chrome/common/extensions/message_bundle.h"
#include "chrome/common/extensions/user_script.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "components/user_prefs/pref_registry_syncable.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "content/public/common/url_constants.h"
#include "extensions/browser/file_reader.h"
#include "extensions/common/constants.h"
#include "extensions/common/error_utils.h"
#include "skia/ext/image_operations.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/models/list_selection_model.h"
#include "ui/base/ui_base_types.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/codec/png_codec.h"
#if defined(OS_WIN)
#include "win8/util/win8_util.h"
#endif // OS_WIN
#if defined(USE_ASH)
#include "ash/ash_switches.h"
#include "chrome/browser/extensions/api/tabs/ash_panel_contents.h"
#include "chrome/browser/extensions/shell_window_registry.h"
#endif
namespace Get = extensions::api::windows::Get;
namespace GetAll = extensions::api::windows::GetAll;
namespace GetCurrent = extensions::api::windows::GetCurrent;
namespace GetLastFocused = extensions::api::windows::GetLastFocused;
namespace errors = extension_manifest_errors;
namespace keys = extensions::tabs_constants;
using content::BrowserThread;
using content::NavigationController;
using content::NavigationEntry;
using content::OpenURLParams;
using content::Referrer;
using content::RenderViewHost;
using content::WebContents;
using extensions::ErrorUtils;
using extensions::ScriptExecutor;
using extensions::UserScript;
using extensions::WindowController;
using extensions::WindowControllerList;
using extensions::api::tabs::InjectDetails;
const int TabsCaptureVisibleTabFunction::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 (chrome::BrowserIterator it; !it.done(); it.Next()) {
Browser* browser = *it;
if ((browser->profile() == profile ||
browser->profile() == incognito_profile) &&
ExtensionTabUtil::GetWindowId(browser) == window_id &&
browser->window()) {
return browser;
}
}
if (error_message)
*error_message = ErrorUtils::FormatErrorMessage(
keys::kWindowNotFoundError, base::IntToString(window_id));
return NULL;
}
bool GetBrowserFromWindowID(
UIThreadExtensionFunction* function, int window_id, Browser** browser) {
if (window_id == extension_misc::kCurrentWindowId) {
*browser = function->GetCurrentBrowser();
if (!(*browser) || !(*browser)->window()) {
function->SetError(keys::kNoCurrentWindowError);
return false;
}
} else {
std::string error;
*browser = GetBrowserInProfileWithId(
function->profile(), window_id, function->include_incognito(), &error);
if (!*browser) {
function->SetError(error);
return false;
}
}
return true;
}
bool GetWindowFromWindowID(UIThreadExtensionFunction* function,
int window_id,
WindowController** controller) {
if (window_id == extension_misc::kCurrentWindowId) {
WindowController* extension_window_controller =
function->dispatcher()->delegate()->GetExtensionWindowController();
// If there is a window controller associated with this extension, use that.
if (extension_window_controller) {
*controller = extension_window_controller;
} else {
// Otherwise get the focused or most recently added window.
*controller = WindowControllerList::GetInstance()->
CurrentWindowForFunction(function);
}
if (!(*controller)) {
function->SetError(keys::kNoCurrentWindowError);
return false;
}
} else {
*controller = WindowControllerList::GetInstance()->
FindWindowForFunctionById(function, window_id);
if (!(*controller)) {
function->SetError(ErrorUtils::FormatErrorMessage(
keys::kWindowNotFoundError, base::IntToString(window_id)));
return false;
}
}
return true;
}
// |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,
content::WebContents** 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 = ErrorUtils::FormatErrorMessage(
keys::kTabNotFoundError, base::IntToString(tab_id));
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;
}
Browser* CreateBrowserWindow(const Browser::CreateParams& params,
Profile* profile,
const std::string& extension_id) {
bool use_existing_browser_window = false;
#if defined(OS_WIN)
// In windows 8 metro mode we don't allow windows to be created.
if (win8::IsSingleWindowMetroMode())
use_existing_browser_window = true;
#endif // OS_WIN
Browser* new_window = NULL;
if (use_existing_browser_window)
// The false parameter passed below is to ensure that we find a browser
// object matching the profile passed in, instead of the original profile
new_window = chrome::FindTabbedBrowser(profile, false,
params.host_desktop_type);
if (!new_window)
new_window = new Browser(params);
return new_window;
}
} // namespace
// Windows ---------------------------------------------------------------------
bool WindowsGetFunction::RunImpl() {
scoped_ptr<Get::Params> params(Get::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
bool populate_tabs = false;
if (params->get_info.get() && params->get_info->populate.get())
populate_tabs = *params->get_info->populate;
WindowController* controller;
if (!GetWindowFromWindowID(this, params->window_id, &controller))
return false;
if (populate_tabs)
SetResult(controller->CreateWindowValueWithTabs(GetExtension()));
else
SetResult(controller->CreateWindowValue());
return true;
}
bool WindowsGetCurrentFunction::RunImpl() {
scoped_ptr<GetCurrent::Params> params(GetCurrent::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
bool populate_tabs = false;
if (params->get_info.get() && params->get_info->populate.get())
populate_tabs = *params->get_info->populate;
WindowController* controller;
if (!GetWindowFromWindowID(this,
extension_misc::kCurrentWindowId,
&controller)) {
return false;
}
if (populate_tabs)
SetResult(controller->CreateWindowValueWithTabs(GetExtension()));
else
SetResult(controller->CreateWindowValue());
return true;
}
bool WindowsGetLastFocusedFunction::RunImpl() {
scoped_ptr<GetLastFocused::Params> params(
GetLastFocused::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
bool populate_tabs = false;
if (params->get_info.get() && params->get_info->populate.get())
populate_tabs = *params->get_info->populate;
// Note: currently this returns the last active browser. If we decide to
// include other window types (e.g. panels), we will need to add logic to
// WindowControllerList that mirrors the active behavior of BrowserList.
Browser* browser = chrome::FindAnyBrowser(
profile(), include_incognito(), chrome::GetActiveDesktop());
if (!browser || !browser->window()) {
error_ = keys::kNoLastFocusedWindowError;
return false;
}
WindowController* controller =
browser->extension_window_controller();
if (populate_tabs)
SetResult(controller->CreateWindowValueWithTabs(GetExtension()));
else
SetResult(controller->CreateWindowValue());
return true;
}
bool WindowsGetAllFunction::RunImpl() {
scoped_ptr<GetAll::Params> params(GetAll::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
bool populate_tabs = false;
if (params->get_info.get() && params->get_info->populate.get())
populate_tabs = *params->get_info->populate;
ListValue* window_list = new ListValue();
const WindowControllerList::ControllerList& windows =
WindowControllerList::GetInstance()->windows();
for (WindowControllerList::ControllerList::const_iterator iter =
windows.begin();
iter != windows.end(); ++iter) {
if (!this->CanOperateOnWindow(*iter))
continue;
if (populate_tabs)
window_list->Append((*iter)->CreateWindowValueWithTabs(GetExtension()));
else
window_list->Append((*iter)->CreateWindowValue());
}
SetResult(window_list);
return true;
}
bool WindowsCreateFunction::ShouldOpenIncognitoWindow(
const base::DictionaryValue* args,
std::vector<GURL>* urls,
bool* is_error) {
*is_error = false;
const IncognitoModePrefs::Availability incognito_availability =
IncognitoModePrefs::GetAvailability(profile_->GetPrefs());
bool incognito = false;
if (args && args->HasKey(keys::kIncognitoKey)) {
EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kIncognitoKey,
&incognito));
if (incognito && incognito_availability == IncognitoModePrefs::DISABLED) {
error_ = keys::kIncognitoModeIsDisabled;
*is_error = true;
return false;
}
if (!incognito && incognito_availability == IncognitoModePrefs::FORCED) {
error_ = keys::kIncognitoModeIsForced;
*is_error = true;
return false;
}
} else if (incognito_availability == IncognitoModePrefs::FORCED) {
// If incognito argument is not specified explicitly, we default to
// incognito when forced so by policy.
incognito = true;
}
// Remove all URLs that are not allowed in an incognito session. Note that a
// ChromeOS guest session is not considered incognito in this case.
if (incognito && !profile_->IsGuestSession()) {
std::string first_url_erased;
for (size_t i = 0; i < urls->size();) {
if (chrome::IsURLAllowedInIncognito((*urls)[i], profile())) {
i++;
} else {
if (first_url_erased.empty())
first_url_erased = (*urls)[i].spec();
urls->erase(urls->begin() + i);
}
}
if (urls->empty() && !first_url_erased.empty()) {
error_ = ErrorUtils::FormatErrorMessage(
keys::kURLsNotAllowedInIncognitoError, first_url_erased);
*is_error = true;
return false;
}
}
return incognito;
}
bool WindowsCreateFunction::RunImpl() {
DictionaryValue* args = NULL;
std::vector<GURL> urls;
WebContents* 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 = ExtensionTabUtil::ResolvePossiblyRelativeURL(
*i, GetExtension());
if (!url.is_valid()) {
error_ = ErrorUtils::FormatErrorMessage(
keys::kInvalidUrlError, *i);
return false;
}
// Don't let the extension crash the browser or renderers.
if (ExtensionTabUtil::IsCrashURL(url)) {
error_ = keys::kNoCrashBrowserError;
return false;
}
urls.push_back(url);
}
}
}
// Look for optional tab id.
if (args) {
int tab_id = -1;
if (args->HasKey(keys::kTabIdKey)) {
EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kTabIdKey, &tab_id));
// Find the tab and detach it from the original window.
TabStripModel* source_tab_strip = NULL;
int tab_index = -1;
if (!GetTabById(tab_id, profile(), include_incognito(),
NULL, &source_tab_strip,
NULL, &tab_index, &error_))
return false;
contents = source_tab_strip->DetachWebContentsAt(tab_index);
if (!contents) {
error_ = ErrorUtils::FormatErrorMessage(
keys::kTabNotFoundError, base::IntToString(tab_id));
return false;
}
}
}
Profile* window_profile = profile();
Browser::Type window_type = Browser::TYPE_TABBED;
// panel_create_mode only applies if window is TYPE_PANEL.
PanelManager::CreateMode panel_create_mode = PanelManager::CREATE_AS_DOCKED;
gfx::Rect window_bounds;
bool focused = true;
bool saw_focus_key = false;
std::string extension_id;
// Decide whether we are opening a normal window or an incognito window.
bool is_error = true;
bool open_incognito_window = ShouldOpenIncognitoWindow(args, &urls,
&is_error);
if (is_error) {
// error_ member variable is set inside of ShouldOpenIncognitoWindow.
return false;
}
if (open_incognito_window) {
window_profile = window_profile->GetOffTheRecordProfile();
}
if (args) {
// Figure out window type before figuring out bounds so that default
// bounds can be set according to the window type.
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 ||
type_str == keys::kWindowTypeValueDetachedPanel) {
extension_id = GetExtension()->id();
bool use_panels = false;
#if !defined(OS_ANDROID)
use_panels = PanelManager::ShouldUsePanels(extension_id);
#endif
if (use_panels) {
window_type = Browser::TYPE_PANEL;
#if !defined(OS_CHROMEOS)
// Non-ChromeOS has both docked and detached panel types.
if (type_str == keys::kWindowTypeValueDetachedPanel)
panel_create_mode = PanelManager::CREATE_AS_DETACHED;
#endif
} else {
window_type = Browser::TYPE_POPUP;
}
} else if (type_str != keys::kWindowTypeValueNormal) {
error_ = keys::kInvalidWindowTypeError;
return false;
}
}
// Initialize default window bounds according to window type.
if (Browser::TYPE_TABBED == window_type ||
Browser::TYPE_POPUP == window_type ||
Browser::TYPE_PANEL == window_type) {
// Try to position the new browser relative to its originating
// browser window. 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.
ui::WindowShowState show_state = ui::SHOW_STATE_DEFAULT;
WindowSizer::GetBrowserWindowBoundsAndShowState(std::string(),
gfx::Rect(),
GetCurrentBrowser(),
&window_bounds,
&show_state);
}
if (Browser::TYPE_PANEL == window_type &&
PanelManager::CREATE_AS_DETACHED == panel_create_mode) {
window_bounds.set_origin(
PanelManager::GetInstance()->GetDefaultDetachedPanelOrigin());
}
// Any part of the bounds can optionally be set by the caller.
int bounds_val = -1;
if (args->HasKey(keys::kLeftKey)) {
EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kLeftKey,
&bounds_val));
window_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);
}
if (args->HasKey(keys::kWidthKey)) {
EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kWidthKey,
&bounds_val));
window_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);
}
if (args->HasKey(keys::kFocusedKey)) {
EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kFocusedKey,
&focused));
saw_focus_key = true;
}
}
if (window_type == Browser::TYPE_PANEL) {
if (urls.empty())
urls.push_back(GURL(chrome::kChromeUINewTabURL));
#if defined(OS_CHROMEOS)
if (PanelManager::ShouldUsePanels(extension_id)) {
ShellWindow::CreateParams create_params;
create_params.window_type = ShellWindow::WINDOW_TYPE_V1_PANEL;
create_params.bounds = window_bounds;
create_params.minimum_size = window_bounds.size();
create_params.maximum_size = window_bounds.size();
ShellWindow* shell_window =
new ShellWindow(window_profile, GetExtension());
AshPanelContents* ash_panel_contents = new AshPanelContents(shell_window);
shell_window->Init(urls[0], ash_panel_contents, create_params);
SetResult(ash_panel_contents->GetExtensionWindowController()->
CreateWindowValueWithTabs(GetExtension()));
// Add the panel to the shell window registry so that it shows up in
// the launcher and as an active render process.
extensions::ShellWindowRegistry::Get(window_profile)->AddShellWindow(
shell_window);
return true;
}
#else
std::string title =
web_app::GenerateApplicationNameFromExtensionId(extension_id);
// Note: Panels ignore all but the first url provided.
Panel* panel = PanelManager::GetInstance()->CreatePanel(
title, window_profile, urls[0], window_bounds, panel_create_mode);
// Unlike other window types, Panels do not take focus by default.
if (!saw_focus_key || !focused)
panel->ShowInactive();
else
panel->Show();
SetResult(
panel->extension_window_controller()->CreateWindowValueWithTabs(
GetExtension()));
return true;
#endif
}
// Create a new BrowserWindow.
chrome::HostDesktopType host_desktop_type = chrome::GetActiveDesktop();
Browser::CreateParams create_params(window_type, window_profile,
host_desktop_type);
if (extension_id.empty()) {
create_params.initial_bounds = window_bounds;
} else {
create_params = Browser::CreateParams::CreateForApp(
window_type,
web_app::GenerateApplicationNameFromExtensionId(extension_id),
window_bounds,
window_profile,
host_desktop_type);
}
create_params.initial_show_state = ui::SHOW_STATE_NORMAL;
create_params.host_desktop_type = chrome::GetActiveDesktop();
Browser* new_window = CreateBrowserWindow(create_params, window_profile,
extension_id);
for (std::vector<GURL>::iterator i = urls.begin(); i != urls.end(); ++i) {
WebContents* tab = chrome::AddSelectedTabWithURL(
new_window, *i, content::PAGE_TRANSITION_LINK);
if (window_type == Browser::TYPE_PANEL) {
extensions::TabHelper::FromWebContents(tab)->
SetExtensionAppIconById(extension_id);
}
}
if (contents) {
TabStripModel* target_tab_strip = new_window->tab_strip_model();
target_tab_strip->InsertWebContentsAt(urls.size(), contents,
TabStripModel::ADD_NONE);
} else if (urls.empty()) {
chrome::NewTab(new_window);
}
chrome::SelectNumberedTab(new_window, 0);
// Unlike other window types, Panels do not take focus by default.
if (!saw_focus_key && window_type == Browser::TYPE_PANEL)
focused = false;
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.
SetResult(Value::CreateNullValue());
} else {
SetResult(
new_window->extension_window_controller()->CreateWindowValueWithTabs(
GetExtension()));
}
return true;
}
bool WindowsUpdateFunction::RunImpl() {
int window_id = extension_misc::kUnknownWindowId;
EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
DictionaryValue* update_props;
EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props));
WindowController* controller;
if (!GetWindowFromWindowID(this, window_id, &controller))
return false;
#if defined(OS_WIN)
// Silently ignore changes on the window for metro mode.
if (win8::IsSingleWindowMetroMode()) {
SetResult(controller->CreateWindowValue());
return true;
}
#endif
ui::WindowShowState show_state = ui::SHOW_STATE_DEFAULT; // No change.
std::string state_str;
if (update_props->HasKey(keys::kShowStateKey)) {
EXTENSION_FUNCTION_VALIDATE(update_props->GetString(keys::kShowStateKey,
&state_str));
if (state_str == keys::kShowStateValueNormal) {
show_state = ui::SHOW_STATE_NORMAL;
} else if (state_str == keys::kShowStateValueMinimized) {
show_state = ui::SHOW_STATE_MINIMIZED;
} else if (state_str == keys::kShowStateValueMaximized) {
show_state = ui::SHOW_STATE_MAXIMIZED;
} else if (state_str == keys::kShowStateValueFullscreen) {
show_state = ui::SHOW_STATE_FULLSCREEN;
} else {
error_ = keys::kInvalidWindowStateError;
return false;
}
}
if (show_state != ui::SHOW_STATE_FULLSCREEN &&
show_state != ui::SHOW_STATE_DEFAULT)
controller->SetFullscreenMode(false, GetExtension()->url());
switch (show_state) {
case ui::SHOW_STATE_MINIMIZED:
controller->window()->Minimize();
break;
case ui::SHOW_STATE_MAXIMIZED:
controller->window()->Maximize();
break;
case ui::SHOW_STATE_FULLSCREEN:
if (controller->window()->IsMinimized() ||
controller->window()->IsMaximized())
controller->window()->Restore();
controller->SetFullscreenMode(true, GetExtension()->url());
break;
case ui::SHOW_STATE_NORMAL:
controller->window()->Restore();
break;
default:
break;
}
gfx::Rect bounds;
if (controller->window()->IsMinimized())
bounds = controller->window()->GetRestoredBounds();
else
bounds = controller->window()->GetBounds();
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) {
if (show_state == ui::SHOW_STATE_MINIMIZED ||
show_state == ui::SHOW_STATE_MAXIMIZED ||
show_state == ui::SHOW_STATE_FULLSCREEN) {
error_ = keys::kInvalidWindowStateError;
return false;
}
controller->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) {
if (show_state == ui::SHOW_STATE_MINIMIZED) {
error_ = keys::kInvalidWindowStateError;
return false;
}
controller->window()->Activate();
} else {
if (show_state == ui::SHOW_STATE_MAXIMIZED ||
show_state == ui::SHOW_STATE_FULLSCREEN) {
error_ = keys::kInvalidWindowStateError;
return false;
}
controller->window()->Deactivate();
}
}
bool draw_attention = false;
if (update_props->HasKey(keys::kDrawAttentionKey)) {
EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean(
keys::kDrawAttentionKey, &draw_attention));
controller->window()->FlashFrame(draw_attention);
}
SetResult(controller->CreateWindowValue());
return true;
}
bool WindowsRemoveFunction::RunImpl() {
int window_id = -1;
EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
WindowController* controller;
if (!GetWindowFromWindowID(this, window_id, &controller))
return false;
#if defined(OS_WIN)
// In Windows 8 metro mode, an existing Browser instance is reused for
// hosting the extension tab. We should not be closing it as we don't own it.
if (win8::IsSingleWindowMetroMode())
return false;
#endif
WindowController::Reason reason;
if (!controller->CanClose(&reason)) {
if (reason == WindowController::REASON_NOT_EDITABLE)
error_ = keys::kTabStripNotEditableError;
return false;
}
controller->window()->Close();
return true;
}
// Tabs ------------------------------------------------------------------------
bool TabsGetSelectedFunction::RunImpl() {
// windowId defaults to "current" window.
int window_id = extension_misc::kCurrentWindowId;
if (HasOptionalArgument(0))
EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
Browser* browser = NULL;
if (!GetBrowserFromWindowID(this, window_id, &browser))
return false;
TabStripModel* tab_strip = browser->tab_strip_model();
WebContents* contents = tab_strip->GetActiveWebContents();
if (!contents) {
error_ = keys::kNoSelectedTabError;
return false;
}
SetResult(ExtensionTabUtil::CreateTabValue(contents,
tab_strip,
tab_strip->active_index(),
GetExtension()));
return true;
}
bool TabsGetAllInWindowFunction::RunImpl() {
// windowId defaults to "current" window.
int window_id = extension_misc::kCurrentWindowId;
if (HasOptionalArgument(0))
EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
Browser* browser = NULL;
if (!GetBrowserFromWindowID(this, window_id, &browser))
return false;
SetResult(ExtensionTabUtil::CreateTabList(browser, GetExtension()));
return true;
}
bool TabsQueryFunction::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 current_window = ParseBoolQueryArg(query, keys::kCurrentWindowKey);
QueryArg focused_window =
ParseBoolQueryArg(query, keys::kLastFocusedWindowKey);
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;
}
// It is o.k. to use URLPattern::SCHEME_ALL here because this function does
// not grant access to the content of the tabs, only to seeing their URLs and
// meta data.
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 = extension_misc::kUnknownWindowId;
if (query->HasKey(keys::kWindowIdKey))
EXTENSION_FUNCTION_VALIDATE(
query->GetInteger(keys::kWindowIdKey, &window_id));
int index = -1;
if (query->HasKey(keys::kIndexKey))
EXTENSION_FUNCTION_VALIDATE(
query->GetInteger(keys::kIndexKey, &index));
std::string window_type;
if (query->HasKey(keys::kWindowTypeLongKey))
EXTENSION_FUNCTION_VALIDATE(
query->GetString(keys::kWindowTypeLongKey, &window_type));
ListValue* result = new ListValue();
for (chrome::BrowserIterator it; !it.done(); it.Next()) {
Browser* browser = *it;
if (!profile()->IsSameProfile(browser->profile()))
continue;
if (!browser->window())
continue;
if (!include_incognito() && profile() != browser->profile())
continue;
if (window_id >= 0 && window_id != ExtensionTabUtil::GetWindowId(browser))
continue;
if (window_id == extension_misc::kCurrentWindowId &&
browser != GetCurrentBrowser())
continue;
if (!MatchesQueryArg(current_window, browser == GetCurrentBrowser()))
continue;
if (!MatchesQueryArg(focused_window, browser->window()->IsActive()))
continue;
if (!window_type.empty() &&
window_type !=
browser->extension_window_controller()->GetWindowTypeText())
continue;
TabStripModel* tab_strip = browser->tab_strip_model();
for (int i = 0; i < tab_strip->count(); ++i) {
const WebContents* web_contents = tab_strip->GetWebContentsAt(i);
if (index > -1 && i != index)
continue;
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(web_contents->GetTitle(),
UTF8ToUTF16(title)))
continue;
if (!url_pattern.MatchesURL(web_contents->GetURL()))
continue;
if (!MatchesQueryArg(loading, web_contents->IsLoading()))
continue;
result->Append(ExtensionTabUtil::CreateTabValue(
web_contents, tab_strip, i, GetExtension()));
}
}
SetResult(result);
return true;
}
bool TabsCreateFunction::RunImpl() {
DictionaryValue* args = NULL;
EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));
// windowId defaults to "current" window.
int window_id = extension_misc::kCurrentWindowId;
if (args->HasKey(keys::kWindowIdKey))
EXTENSION_FUNCTION_VALIDATE(args->GetInteger(
keys::kWindowIdKey, &window_id));
Browser* browser = NULL;
if (!GetBrowserFromWindowID(this, window_id, &browser))
return false;
// Ensure the selected browser is tabbed.
if (!browser->is_type_tabbed() && browser->IsAttemptingToCloseBrowser())
browser = chrome::FindTabbedBrowser(profile(), include_incognito(),
browser->host_desktop_type());
if (!browser || !browser->window())
return false;
// TODO(jstritar): Add a constant, chrome.tabs.TAB_ID_ACTIVE, that
// represents the active tab.
WebContents* opener = NULL;
if (args->HasKey(keys::kOpenerTabIdKey)) {
int opener_id = -1;
EXTENSION_FUNCTION_VALIDATE(args->GetInteger(
keys::kOpenerTabIdKey, &opener_id));
if (!ExtensionTabUtil::GetTabById(
opener_id, profile(), include_incognito(),
NULL, NULL, &opener, NULL))
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 = ExtensionTabUtil::ResolvePossiblyRelativeURL(url_string,
GetExtension());
if (!url.is_valid()) {
error_ = ErrorUtils::FormatErrorMessage(keys::kInvalidUrlError,
url_string);
return false;
}
}
// Don't let extensions crash the browser or renderers.
if (ExtensionTabUtil::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(extensions::kExtensionScheme) &&
!extensions::IncognitoInfo::IsSplitMode(GetExtension()) &&
browser->profile()->IsOffTheRecord()) {
Profile* profile = browser->profile()->GetOriginalProfile();
chrome::HostDesktopType desktop_type = browser->host_desktop_type();
browser = chrome::FindTabbedBrowser(profile, false, desktop_type);
if (!browser) {
browser = new Browser(Browser::CreateParams(Browser::TYPE_TABBED,
profile, desktop_type));
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->tab_strip_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;
chrome::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;
chrome::Navigate(&params);
// The tab may have been created in a different window, so make sure we look
// at the right tab strip.
tab_strip = params.browser->tab_strip_model();
int new_index = tab_strip->GetIndexOfWebContents(params.target_contents);
if (opener)
tab_strip->SetOpenerOfWebContentsAt(new_index, opener);
if (active) {
params.target_contents->GetDelegate()->ActivateContents(
params.target_contents);
}
// Return data about the newly created tab.
if (has_callback()) {
SetResult(ExtensionTabUtil::CreateTabValue(
params.target_contents,
tab_strip, new_index, GetExtension()));
}
return true;
}
bool TabsDuplicateFunction::RunImpl() {
int tab_id = -1;
EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
Browser* browser = NULL;
TabStripModel* tab_strip = NULL;
int tab_index = -1;
if (!GetTabById(tab_id, profile(), include_incognito(),
&browser, &tab_strip, NULL, &tab_index, &error_)) {
return false;
}
WebContents* new_contents = chrome::DuplicateTabAt(browser, tab_index);
if (!has_callback())
return true;
// Duplicated tab may not be in the same window as the original, so find
// the window and the tab.
TabStripModel* new_tab_strip = NULL;
int new_tab_index = -1;
ExtensionTabUtil::GetTabStripModel(new_contents,
&new_tab_strip,
&new_tab_index);
if (!new_tab_strip || new_tab_index == -1) {
return false;
}
// Return data about the newly created tab.
SetResult(ExtensionTabUtil::CreateTabValue(
new_contents,
new_tab_strip, new_tab_index, GetExtension()));
return true;
}
bool TabsGetFunction::RunImpl() {
int tab_id = -1;
EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
TabStripModel* tab_strip = NULL;
WebContents* contents = NULL;
int tab_index = -1;
if (!GetTabById(tab_id, profile(), include_incognito(),
NULL, &tab_strip, &contents, &tab_index, &error_))
return false;
SetResult(ExtensionTabUtil::CreateTabValue(contents,
tab_strip,
tab_index,
GetExtension()));
return true;
}
bool TabsGetCurrentFunction::RunImpl() {
DCHECK(dispatcher());
WebContents* contents = dispatcher()->delegate()->GetAssociatedWebContents();
if (contents)
SetResult(ExtensionTabUtil::CreateTabValue(contents, GetExtension()));
return true;
}
bool TabsHighlightFunction::RunImpl() {
DictionaryValue* info = NULL;
EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &info));
// Get the window id from the params; default to current window if omitted.
int window_id = extension_misc::kCurrentWindowId;
if (info->HasKey(keys::kWindowIdKey))
EXTENSION_FUNCTION_VALIDATE(
info->GetInteger(keys::kWindowIdKey, &window_id));
Browser* browser = NULL;
if (!GetBrowserFromWindowID(this, window_id, &browser))
return false;
TabStripModel* tabstrip = browser->tab_strip_model();
ui::ListSelectionModel 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(extensions::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_ = ErrorUtils::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->tab_strip_model()->SetSelectionFromModel(selection);
SetResult(
browser->extension_window_controller()->CreateWindowValueWithTabs(
GetExtension()));
return true;
}
TabsUpdateFunction::TabsUpdateFunction() : web_contents_(NULL) {
}
bool TabsUpdateFunction::RunImpl() {
DictionaryValue* update_props = NULL;
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;
WebContents* contents = NULL;
if (tab_value == NULL || tab_value->IsType(Value::TYPE_NULL)) {
Browser* browser = GetCurrentBrowser();
if (!browser) {
error_ = keys::kNoCurrentWindowError;
return false;
}
contents = browser->tab_strip_model()->GetActiveWebContents();
if (!contents) {
error_ = keys::kNoSelectedTabError;
return false;
}
tab_id = SessionID::IdForTab(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;
}
web_contents_ = contents;
// TODO(rafaelw): handle setting remaining tab properties:
// -title
// -favIconUrl
// Navigate the tab to a new location if the url is different.
bool is_async = false;
if (!UpdateURLIfPresent(update_props, tab_id, &is_async))
return false;
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->GetActiveWebContents());
}
web_contents_->GetDelegate()->ActivateContents(web_contents_);
}
if (update_props->HasKey(keys::kHighlightedKey)) {
bool highlighted = false;
EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean(
keys::kHighlightedKey, &highlighted));
if (highlighted != tab_strip->IsTabSelected(tab_index))
tab_strip->ToggleSelectionAt(tab_index);
}
if (update_props->HasKey(keys::kPinnedKey)) {
bool pinned = false;
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->GetIndexOfWebContents(contents);
}
if (update_props->HasKey(keys::kOpenerTabIdKey)) {
int opener_id = -1;
EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger(
keys::kOpenerTabIdKey, &opener_id));
WebContents* opener_contents = NULL;
if (!ExtensionTabUtil::GetTabById(
opener_id, profile(), include_incognito(),
NULL, NULL, &opener_contents, NULL))
return false;
tab_strip->SetOpenerOfWebContentsAt(tab_index, opener_contents);
}
if (!is_async) {
PopulateResult();
SendResponse(true);
}
return true;
}
bool TabsUpdateFunction::UpdateURLIfPresent(DictionaryValue* update_props,
int tab_id,
bool* is_async) {
if (!update_props->HasKey(keys::kUrlKey))
return true;
std::string url_string;
EXTENSION_FUNCTION_VALIDATE(update_props->GetString(
keys::kUrlKey, &url_string));
GURL url = ExtensionTabUtil::ResolvePossiblyRelativeURL(
url_string, GetExtension());
if (!url.is_valid()) {
error_ = ErrorUtils::FormatErrorMessage(
keys::kInvalidUrlError, url_string);
return false;
}
// Don't let the extension crash the browser or renderers.
if (ExtensionTabUtil::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(
web_contents_->GetURL(),
web_contents_->GetURL(),
tab_id,
NULL,
&error_)) {
return false;
}
extensions::TabHelper::FromWebContents(web_contents_)->
script_executor()->ExecuteScript(
extension_id(),
ScriptExecutor::JAVASCRIPT,
url.path(),
ScriptExecutor::TOP_FRAME,
extensions::UserScript::DOCUMENT_IDLE,
ScriptExecutor::MAIN_WORLD,
false /* is_web_view */,
base::Bind(&TabsUpdateFunction::OnExecuteCodeFinished, this));
*is_async = true;
return true;
}
web_contents_->GetController().LoadURL(
url, content::Referrer(), 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(), web_contents_->GetURL().spec());
return true;
}
void TabsUpdateFunction::PopulateResult() {
if (!has_callback())
return;
SetResult(ExtensionTabUtil::CreateTabValue(web_contents_, GetExtension()));
}
void TabsUpdateFunction::OnExecuteCodeFinished(const std::string& error,
int32 on_page_id,
const GURL& url,
const ListValue& script_result) {
if (error.empty())
PopulateResult();
else
error_ = error;
SendResponse(error.empty());
}
bool TabsMoveFunction::RunImpl() {
Value* tab_value = NULL;
EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &tab_value));
std::vector<int> tab_ids;
EXTENSION_FUNCTION_VALIDATE(extensions::ReadOneOrMoreIntegers(
tab_value, &tab_ids));
DictionaryValue* update_props = NULL;
int new_index;
EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props));
EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger(keys::kIndexKey,
&new_index));
ListValue tab_values;
for (size_t i = 0; i < tab_ids.size(); ++i) {
Browser* source_browser = NULL;
TabStripModel* source_tab_strip = NULL;
WebContents* 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->window()->IsTabStripEditable()) {
error_ = keys::kTabStripNotEditableError;
return false;
}
// Insert the tabs one after another.
new_index += i;
if (update_props->HasKey(keys::kWindowIdKey)) {
Browser* target_browser = NULL;
int window_id = extension_misc::kUnknownWindowId;
EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger(
keys::kWindowIdKey, &window_id));
if (!GetBrowserFromWindowID(this, window_id, &target_browser))
return false;
if (!target_browser->window()->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->tab_strip_model();
WebContents* web_contents =
source_tab_strip->DetachWebContentsAt(tab_index);
if (!web_contents) {
error_ = ErrorUtils::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.
// -1 means set the move location to the last position.
if (new_index > target_tab_strip->count() || new_index < 0)
new_index = target_tab_strip->count();
target_tab_strip->InsertWebContentsAt(
new_index, web_contents, TabStripModel::ADD_NONE);
if (has_callback()) {
tab_values.Append(ExtensionTabUtil::CreateTabValue(
web_contents,
target_tab_strip,
new_index,
GetExtension()));
}
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.
// -1 means set the move location to the last position.
if (new_index >= source_tab_strip->count() || new_index < 0)
new_index = source_tab_strip->count() - 1;
if (new_index != tab_index)
source_tab_strip->MoveWebContentsAt(tab_index, new_index, false);
if (has_callback()) {
tab_values.Append(ExtensionTabUtil::CreateTabValue(
contents, source_tab_strip, new_index, GetExtension()));
}
}
if (!has_callback())
return true;
// Only return the results as an array if there are multiple tabs.
if (tab_ids.size() > 1) {
SetResult(tab_values.DeepCopy());
} else if (tab_ids.size() == 1) {
Value* value = NULL;
CHECK(tab_values.Get(0, &value));
SetResult(value->DeepCopy());
}
return true;
}
bool TabsReloadFunction::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));
}
}
content::WebContents* web_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, &web_contents, NULL))
return false;
} else {
int tab_id = -1;
EXTENSION_FUNCTION_VALIDATE(tab_value->GetAsInteger(&tab_id));
Browser* browser = NULL;
if (!GetTabById(tab_id, profile(), include_incognito(),
&browser, NULL, &web_contents, NULL, &error_))
return false;
}
if (web_contents->ShowingInterstitialPage()) {
// This does as same as Browser::ReloadInternal.
NavigationEntry* entry = web_contents->GetController().GetActiveEntry();
OpenURLParams params(entry->GetURL(), Referrer(), CURRENT_TAB,
content::PAGE_TRANSITION_RELOAD, false);
GetCurrentBrowser()->OpenURL(params);
} else if (bypass_cache) {
web_contents->GetController().ReloadIgnoringCache(true);
} else {
web_contents->GetController().Reload(true);
}
return true;
}
bool TabsRemoveFunction::RunImpl() {
Value* tab_value = NULL;
EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &tab_value));
std::vector<int> tab_ids;
EXTENSION_FUNCTION_VALIDATE(extensions::ReadOneOrMoreIntegers(
tab_value, &tab_ids));
for (size_t i = 0; i < tab_ids.size(); ++i) {
Browser* browser = NULL;
WebContents* 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->window()->IsTabStripEditable()) {
error_ = keys::kTabStripNotEditableError;
return false;
}
// There's a chance that the tab is being dragged, or we're in some other
// nested event loop. This code path ensures that the tab is safely closed
// under such circumstances, whereas |TabStripModel::CloseWebContentsAt()|
// does not.
contents->Close();
}
return true;
}
bool TabsCaptureVisibleTabFunction::GetTabToCapture(
WebContents** web_contents) {
Browser* browser = NULL;
// windowId defaults to "current" window.
int window_id = extension_misc::kCurrentWindowId;
if (HasOptionalArgument(0))
EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &window_id));
if (!GetBrowserFromWindowID(this, window_id, &browser))
return false;
*web_contents = browser->tab_strip_model()->GetActiveWebContents();
if (*web_contents == NULL) {
error_ = keys::kInternalVisibleTabCaptureError;
return false;
}
return true;
};
bool TabsCaptureVisibleTabFunction::RunImpl() {
PrefService* service = profile()->GetPrefs();
if (service->GetBoolean(prefs::kDisableScreenshots)) {
error_ = keys::kScreenshotsDisabled;
return false;
}
WebContents* web_contents = NULL;
if (!GetTabToCapture(&web_contents))
return false;
image_format_ = FORMAT_JPEG; // Default format is JPEG.
image_quality_ = kDefaultQuality; // Default quality setting.
if (HasOptionalArgument(1)) {
DictionaryValue* options = NULL;
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_));
}
}
// 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(
web_contents->GetURL(),
SessionID::IdForTab(web_contents),
&error_)) {
return false;
}
RenderViewHost* render_view_host = web_contents->GetRenderViewHost();
content::RenderWidgetHostView* view = render_view_host->GetView();
if (!view) {
error_ = keys::kInternalVisibleTabCaptureError;
return false;
}
render_view_host->CopyFromBackingStore(
gfx::Rect(),
view->GetViewBounds().size(),
base::Bind(&TabsCaptureVisibleTabFunction::CopyFromBackingStoreComplete,
this));
return true;
}
void TabsCaptureVisibleTabFunction::CopyFromBackingStoreComplete(
bool succeeded,
const SkBitmap& bitmap) {
if (succeeded) {
VLOG(1) << "captureVisibleTab() got image from backing store.";
SendResultFromBitmap(bitmap);
return;
}
WebContents* web_contents = NULL;
if (!GetTabToCapture(&web_contents)) {
SendInternalError();
return;
}
// Ask the renderer for a snapshot of the tab.
content::RenderWidgetHost* render_widget_host =
web_contents->GetRenderViewHost();
if (!render_widget_host) {
SendInternalError();
return;
}
render_widget_host->GetSnapshotFromRenderer(
gfx::Rect(),
base::Bind(
&TabsCaptureVisibleTabFunction::GetSnapshotFromRendererComplete,
this));
}
// If a backing store was not available in
// TabsCaptureVisibleTabFunction::RunImpl, than the renderer was asked for a
// snapshot.
void TabsCaptureVisibleTabFunction::GetSnapshotFromRendererComplete(
bool succeeded,
const SkBitmap& bitmap) {
if (!succeeded) {
SendInternalError();
} else {
VLOG(1) << "captureVisibleTab() got image from renderer.";
SendResultFromBitmap(bitmap);
}
}
void TabsCaptureVisibleTabFunction::SendInternalError() {
error_ = keys::kInternalVisibleTabCaptureError;
SendResponse(false);
}
// Turn a bitmap of the screen into an image, set that image as the result,
// and call SendResponse().
void TabsCaptureVisibleTabFunction::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()));
SetResult(new StringValue(base64_result));
SendResponse(true);
}
void TabsCaptureVisibleTabFunction::RegisterUserPrefs(
PrefRegistrySyncable* registry) {
registry->RegisterBooleanPref(prefs::kDisableScreenshots, false,
PrefRegistrySyncable::UNSYNCABLE_PREF);
}
bool TabsDetectLanguageFunction::RunImpl() {
int tab_id = 0;
Browser* browser = NULL;
WebContents* 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->tab_strip_model()->GetActiveWebContents();
if (!contents)
return false;
}
if (contents->GetController().NeedsReload()) {
// 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* translate_tab_helper =
TranslateTabHelper::FromWebContents(contents);
if (!translate_tab_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(
&TabsDetectLanguageFunction::GotLanguage, this,
translate_tab_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<WebContents>(contents));
registrar_.Add(
this, chrome::NOTIFICATION_TAB_CLOSING,
content::Source<NavigationController>(&(contents->GetController())));
registrar_.Add(
this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::Source<NavigationController>(&(contents->GetController())));
return true;
}
void TabsDetectLanguageFunction::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 TabsDetectLanguageFunction::GotLanguage(const std::string& language) {
SetResult(Value::CreateStringValue(language.c_str()));
SendResponse(true);
Release(); // Balanced in Run()
}
ExecuteCodeInTabFunction::ExecuteCodeInTabFunction()
: execute_tab_id_(-1) {
}
ExecuteCodeInTabFunction::~ExecuteCodeInTabFunction() {}
bool ExecuteCodeInTabFunction::HasPermission() {
if (Init() &&
extension_->HasAPIPermissionForTab(execute_tab_id_,
extensions::APIPermission::kTab)) {
return true;
}
return ExtensionFunction::HasPermission();
}
bool ExecuteCodeInTabFunction::RunImpl() {
EXTENSION_FUNCTION_VALIDATE(Init());
if (!details_->code.get() && !details_->file.get()) {
error_ = keys::kNoCodeOrFileToExecuteError;
return false;
}
if (details_->code.get() && details_->file.get()) {
error_ = keys::kMoreThanOneValuesError;
return false;
}
content::WebContents* contents = NULL;
// If |tab_id| is specified, look for the tab. Otherwise default to selected
// tab in the current window.
CHECK_GE(execute_tab_id_, 0);
if (!GetTabById(execute_tab_id_, profile(),
include_incognito(),
NULL, NULL, &contents, NULL, &error_)) {
return false;
}
// NOTE: This can give the wrong answer due to race conditions, but it is OK,
// we check again in the renderer.
CHECK(contents);
if (!GetExtension()->CanExecuteScriptOnPage(contents->GetURL(),
contents->GetURL(),
execute_tab_id_,
NULL,
&error_)) {
return false;
}
if (details_->code.get())
return Execute(*details_->code);
CHECK(details_->file.get());
resource_ = GetExtension()->GetResource(*details_->file);
if (resource_.extension_root().empty() || resource_.relative_path().empty()) {
error_ = keys::kNoCodeOrFileToExecuteError;
return false;
}
scoped_refptr<FileReader> file_reader(new FileReader(
resource_, base::Bind(&ExecuteCodeInTabFunction::DidLoadFile, this)));
file_reader->Start();
return true;
}
void ExecuteCodeInTabFunction::OnExecuteCodeFinished(const std::string& error,
int32 on_page_id,
const GURL& on_url,
const ListValue& result) {
if (!error.empty())
SetError(error);
SendResponse(error.empty());
}
void TabsExecuteScriptFunction::OnExecuteCodeFinished(const std::string& error,
int32 on_page_id,
const GURL& on_url,
const ListValue& result) {
if (error.empty())
SetResult(result.DeepCopy());
ExecuteCodeInTabFunction::OnExecuteCodeFinished(error, on_page_id, on_url,
result);
}
bool ExecuteCodeInTabFunction::Init() {
if (details_.get())
return true;
// |tab_id| is optional so it's ok if it's not there.
int tab_id = -1;
if (args_->GetInteger(0, &tab_id))
EXTENSION_FUNCTION_VALIDATE(tab_id >= 0);
// |details| are not optional.
DictionaryValue* details_value = NULL;
if (!args_->GetDictionary(1, &details_value))
return false;
scoped_ptr<InjectDetails> details(new InjectDetails());
if (!InjectDetails::Populate(*details_value, details.get()))
return false;
// If the tab ID wasn't given then it needs to be converted to the
// currently active tab's ID.
if (tab_id == -1) {
Browser* browser = GetCurrentBrowser();
if (!browser)
return false;
content::WebContents* web_contents = NULL;
if (!ExtensionTabUtil::GetDefaultTab(browser, &web_contents, &tab_id))
return false;
}
execute_tab_id_ = tab_id;
details_ = details.Pass();
return true;
}
void ExecuteCodeInTabFunction::DidLoadFile(bool success,
const std::string& data) {
std::string function_name = name();
const extensions::Extension* extension = GetExtension();
// Check if the file is CSS and needs localization.
if (success &&
function_name == TabsInsertCSSFunction::function_name() &&
extension != NULL &&
data.find(
extensions::MessageBundle::kMessageBegin) != std::string::npos) {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&ExecuteCodeInTabFunction::LocalizeCSS, this,
data,
extension->id(),
extension->path(),
extensions::LocaleInfo::GetDefaultLocale(extension)));
} else {
DidLoadAndLocalizeFile(success, data);
}
}
void ExecuteCodeInTabFunction::LocalizeCSS(
const std::string& data,
const std::string& extension_id,
const base::FilePath& extension_path,
const std::string& extension_default_locale) {
scoped_ptr<SubstitutionMap> localization_messages(
extension_file_util::LoadMessageBundleSubstitutionMap(
extension_path, extension_id, extension_default_locale));
// We need to do message replacement on the data, so it has to be mutable.
std::string css_data = data;
std::string error;
extensions::MessageBundle::ReplaceMessagesWithExternalDictionary(
*localization_messages, &css_data, &error);
// Call back DidLoadAndLocalizeFile on the UI thread. The success parameter
// is always true, because if loading had failed, we wouldn't have had
// anything to localize.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&ExecuteCodeInTabFunction::DidLoadAndLocalizeFile, this,
true, css_data));
}
void ExecuteCodeInTabFunction::DidLoadAndLocalizeFile(bool success,
const std::string& data) {
if (success) {
if (!Execute(data))
SendResponse(false);
} else {
#if defined(OS_POSIX)
// TODO(viettrungluu): bug: there's no particular reason the path should be
// UTF-8, in which case this may fail.
error_ = ErrorUtils::FormatErrorMessage(keys::kLoadFileError,
resource_.relative_path().value());
#elif defined(OS_WIN)
error_ = ErrorUtils::FormatErrorMessage(keys::kLoadFileError,
WideToUTF8(resource_.relative_path().value()));
#endif // OS_WIN
SendResponse(false);
}
}
bool ExecuteCodeInTabFunction::Execute(const std::string& code_string) {
content::WebContents* contents = NULL;
Browser* browser = NULL;
bool success = GetTabById(
execute_tab_id_, profile(), include_incognito(), &browser, NULL,
&contents, NULL, &error_) && contents && browser;
if (!success)
return false;
const extensions::Extension* extension = GetExtension();
if (!extension)
return false;
ScriptExecutor::ScriptType script_type = ScriptExecutor::JAVASCRIPT;
std::string function_name = name();
if (function_name == TabsInsertCSSFunction::function_name()) {
script_type = ScriptExecutor::CSS;
} else if (function_name != TabsExecuteScriptFunction::function_name()) {
NOTREACHED();
}
ScriptExecutor::FrameScope frame_scope =
details_->all_frames.get() && *details_->all_frames ?
ScriptExecutor::ALL_FRAMES :
ScriptExecutor::TOP_FRAME;
UserScript::RunLocation run_at = UserScript::UNDEFINED;
switch (details_->run_at) {
case InjectDetails::RUN_AT_NONE:
case InjectDetails::RUN_AT_DOCUMENT_IDLE:
run_at = UserScript::DOCUMENT_IDLE;
break;
case InjectDetails::RUN_AT_DOCUMENT_START:
run_at = UserScript::DOCUMENT_START;
break;
case InjectDetails::RUN_AT_DOCUMENT_END:
run_at = UserScript::DOCUMENT_END;
break;
}
CHECK_NE(UserScript::UNDEFINED, run_at);
extensions::TabHelper::FromWebContents(contents)->
script_executor()->ExecuteScript(
extension->id(),
script_type,
code_string,
frame_scope,
run_at,
ScriptExecutor::ISOLATED_WORLD,
false /* is_web_view */,
base::Bind(&ExecuteCodeInTabFunction::OnExecuteCodeFinished, this));
return true;
}