blob: 6da57a16b916dc7249e752525e0a4b7801d53355 [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/declarative_content/content_action.h"
#include <map>
#include "base/lazy_instance.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/extensions/api/declarative_content/content_constants.h"
#include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
#include "chrome/browser/extensions/extension_action.h"
#include "chrome/browser/extensions/extension_action_manager.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "content/public/browser/invalidate_type.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/declarative_user_script_manager.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_messages.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
namespace extensions {
namespace keys = declarative_content_constants;
namespace {
// Error messages.
const char kInvalidIconDictionary[] =
"Icon dictionary must be of the form {\"19\": ImageData1, \"38\": "
"ImageData2}";
const char kInvalidInstanceTypeError[] =
"An action has an invalid instanceType: %s";
const char kMissingInstanceTypeError[] = "Action is missing instanceType";
const char kMissingParameter[] = "Missing parameter is required: %s";
const char kNoPageAction[] =
"Can't use declarativeContent.ShowPageAction without a page action";
const char kNoPageOrBrowserAction[] =
"Can't use declarativeContent.SetIcon without a page or browser action";
//
// The following are concrete actions.
//
// Action that instructs to show an extension's page action.
class ShowPageAction : public ContentAction {
public:
ShowPageAction() {}
~ShowPageAction() override {}
static std::unique_ptr<ContentAction> Create(
content::BrowserContext* browser_context,
const Extension* extension,
const base::DictionaryValue* dict,
std::string* error) {
// We can't show a page action if the extension doesn't have one.
if (ActionInfo::GetPageActionInfo(extension) == NULL) {
*error = kNoPageAction;
return std::unique_ptr<ContentAction>();
}
return base::WrapUnique(new ShowPageAction);
}
// Implementation of ContentAction:
void Apply(const ApplyInfo& apply_info) const override {
ExtensionAction* action =
GetPageAction(apply_info.browser_context, apply_info.extension);
action->DeclarativeShow(ExtensionTabUtil::GetTabId(apply_info.tab));
ExtensionActionAPI::Get(apply_info.browser_context)->NotifyChange(
action, apply_info.tab, apply_info.browser_context);
}
// The page action is already showing, so nothing needs to be done here.
void Reapply(const ApplyInfo& apply_info) const override {}
void Revert(const ApplyInfo& apply_info) const override {
if (ExtensionAction* action =
GetPageAction(apply_info.browser_context, apply_info.extension)) {
action->UndoDeclarativeShow(ExtensionTabUtil::GetTabId(apply_info.tab));
ExtensionActionAPI::Get(apply_info.browser_context)->NotifyChange(
action, apply_info.tab, apply_info.browser_context);
}
}
private:
static ExtensionAction* GetPageAction(
content::BrowserContext* browser_context,
const Extension* extension) {
return ExtensionActionManager::Get(browser_context)
->GetPageAction(*extension);
}
DISALLOW_COPY_AND_ASSIGN(ShowPageAction);
};
// Action that sets an extension's action icon.
class SetIcon : public ContentAction {
public:
SetIcon(const gfx::Image& icon, ActionInfo::Type action_type)
: icon_(icon), action_type_(action_type) {}
~SetIcon() override {}
static std::unique_ptr<ContentAction> Create(
content::BrowserContext* browser_context,
const Extension* extension,
const base::DictionaryValue* dict,
std::string* error);
// Implementation of ContentAction:
void Apply(const ApplyInfo& apply_info) const override {
Profile* profile = Profile::FromBrowserContext(apply_info.browser_context);
ExtensionAction* action = GetExtensionAction(profile,
apply_info.extension);
if (action) {
action->DeclarativeSetIcon(ExtensionTabUtil::GetTabId(apply_info.tab),
apply_info.priority,
icon_);
ExtensionActionAPI::Get(profile)
->NotifyChange(action, apply_info.tab, profile);
}
}
void Reapply(const ApplyInfo& apply_info) const override {}
void Revert(const ApplyInfo& apply_info) const override {
Profile* profile = Profile::FromBrowserContext(apply_info.browser_context);
ExtensionAction* action = GetExtensionAction(profile,
apply_info.extension);
if (action) {
action->UndoDeclarativeSetIcon(
ExtensionTabUtil::GetTabId(apply_info.tab),
apply_info.priority,
icon_);
ExtensionActionAPI::Get(apply_info.browser_context)
->NotifyChange(action, apply_info.tab, profile);
}
}
private:
ExtensionAction* GetExtensionAction(Profile* profile,
const Extension* extension) const {
switch (action_type_) {
case ActionInfo::TYPE_BROWSER:
return ExtensionActionManager::Get(profile)
->GetBrowserAction(*extension);
case ActionInfo::TYPE_PAGE:
return ExtensionActionManager::Get(profile)->GetPageAction(*extension);
default:
NOTREACHED();
}
return NULL;
}
gfx::Image icon_;
ActionInfo::Type action_type_;
DISALLOW_COPY_AND_ASSIGN(SetIcon);
};
// Helper for getting JS collections into C++.
static bool AppendJSStringsToCPPStrings(const base::ListValue& append_strings,
std::vector<std::string>* append_to) {
for (base::ListValue::const_iterator it = append_strings.begin();
it != append_strings.end();
++it) {
std::string value;
if ((*it)->GetAsString(&value)) {
append_to->push_back(value);
} else {
return false;
}
}
return true;
}
struct ContentActionFactory {
// Factory methods for ContentAction instances. |extension| is the extension
// for which the action is being created. |dict| contains the json dictionary
// that describes the action. |error| is used to return error messages.
using FactoryMethod = std::unique_ptr<ContentAction> (*)(
content::BrowserContext* /* browser_context */,
const Extension* /* extension */,
const base::DictionaryValue* /* dict */,
std::string* /* error */);
// Maps the name of a declarativeContent action type to the factory
// function creating it.
std::map<std::string, FactoryMethod> factory_methods;
ContentActionFactory() {
factory_methods[keys::kShowPageAction] =
&ShowPageAction::Create;
factory_methods[keys::kRequestContentScript] =
&RequestContentScript::Create;
factory_methods[keys::kSetIcon] =
&SetIcon::Create;
}
};
base::LazyInstance<ContentActionFactory>::Leaky
g_content_action_factory = LAZY_INSTANCE_INITIALIZER;
} // namespace
//
// RequestContentScript
//
struct RequestContentScript::ScriptData {
ScriptData();
~ScriptData();
std::vector<std::string> css_file_names;
std::vector<std::string> js_file_names;
bool all_frames;
bool match_about_blank;
};
RequestContentScript::ScriptData::ScriptData()
: all_frames(false),
match_about_blank(false) {}
RequestContentScript::ScriptData::~ScriptData() {}
// static
std::unique_ptr<ContentAction> RequestContentScript::Create(
content::BrowserContext* browser_context,
const Extension* extension,
const base::DictionaryValue* dict,
std::string* error) {
ScriptData script_data;
if (!InitScriptData(dict, error, &script_data))
return std::unique_ptr<ContentAction>();
return base::WrapUnique(
new RequestContentScript(browser_context, extension, script_data));
}
// static
std::unique_ptr<ContentAction> RequestContentScript::CreateForTest(
DeclarativeUserScriptMaster* master,
const Extension* extension,
const base::Value& json_action,
std::string* error) {
// Simulate ContentAction-level initialization. Check that instance type is
// RequestContentScript.
error->clear();
const base::DictionaryValue* action_dict = NULL;
std::string instance_type;
if (!(json_action.GetAsDictionary(&action_dict) &&
action_dict->GetString(keys::kInstanceType, &instance_type) &&
instance_type == std::string(keys::kRequestContentScript)))
return std::unique_ptr<ContentAction>();
// Normal RequestContentScript data initialization.
ScriptData script_data;
if (!InitScriptData(action_dict, error, &script_data))
return std::unique_ptr<ContentAction>();
// Inject provided DeclarativeUserScriptMaster, rather than looking it up
// using a BrowserContext.
return base::WrapUnique(
new RequestContentScript(master, extension, script_data));
}
// static
bool RequestContentScript::InitScriptData(const base::DictionaryValue* dict,
std::string* error,
ScriptData* script_data) {
const base::ListValue* list_value = NULL;
if (!dict->HasKey(keys::kCss) && !dict->HasKey(keys::kJs)) {
*error = base::StringPrintf(kMissingParameter, "css or js");
return false;
}
if (dict->HasKey(keys::kCss)) {
if (!dict->GetList(keys::kCss, &list_value) ||
!AppendJSStringsToCPPStrings(*list_value,
&script_data->css_file_names)) {
return false;
}
}
if (dict->HasKey(keys::kJs)) {
if (!dict->GetList(keys::kJs, &list_value) ||
!AppendJSStringsToCPPStrings(*list_value,
&script_data->js_file_names)) {
return false;
}
}
if (dict->HasKey(keys::kAllFrames)) {
if (!dict->GetBoolean(keys::kAllFrames, &script_data->all_frames))
return false;
}
if (dict->HasKey(keys::kMatchAboutBlank)) {
if (!dict->GetBoolean(keys::kMatchAboutBlank,
&script_data->match_about_blank)) {
return false;
}
}
return true;
}
RequestContentScript::RequestContentScript(
content::BrowserContext* browser_context,
const Extension* extension,
const ScriptData& script_data) {
HostID host_id(HostID::EXTENSIONS, extension->id());
InitScript(host_id, extension, script_data);
master_ = DeclarativeUserScriptManager::Get(browser_context)
->GetDeclarativeUserScriptMasterByID(host_id);
AddScript();
}
RequestContentScript::RequestContentScript(
DeclarativeUserScriptMaster* master,
const Extension* extension,
const ScriptData& script_data) {
HostID host_id(HostID::EXTENSIONS, extension->id());
InitScript(host_id, extension, script_data);
master_ = master;
AddScript();
}
RequestContentScript::~RequestContentScript() {
DCHECK(master_);
master_->RemoveScript(UserScriptIDPair(script_.id(), script_.host_id()));
}
void RequestContentScript::InitScript(const HostID& host_id,
const Extension* extension,
const ScriptData& script_data) {
script_.set_id(UserScript::GenerateUserScriptID());
script_.set_host_id(host_id);
script_.set_run_location(UserScript::BROWSER_DRIVEN);
script_.set_match_all_frames(script_data.all_frames);
script_.set_match_about_blank(script_data.match_about_blank);
for (std::vector<std::string>::const_iterator it =
script_data.css_file_names.begin();
it != script_data.css_file_names.end(); ++it) {
GURL url = extension->GetResourceURL(*it);
ExtensionResource resource = extension->GetResource(*it);
script_.css_scripts().push_back(base::MakeUnique<UserScript::File>(
resource.extension_root(), resource.relative_path(), url));
}
for (std::vector<std::string>::const_iterator it =
script_data.js_file_names.begin();
it != script_data.js_file_names.end(); ++it) {
GURL url = extension->GetResourceURL(*it);
ExtensionResource resource = extension->GetResource(*it);
script_.js_scripts().push_back(base::MakeUnique<UserScript::File>(
resource.extension_root(), resource.relative_path(), url));
}
}
void RequestContentScript::AddScript() {
DCHECK(master_);
master_->AddScript(UserScript::CopyMetadataFrom(script_));
}
void RequestContentScript::Apply(const ApplyInfo& apply_info) const {
InstructRenderProcessToInject(apply_info.tab, apply_info.extension);
}
void RequestContentScript::Reapply(const ApplyInfo& apply_info) const {
InstructRenderProcessToInject(apply_info.tab, apply_info.extension);
}
void RequestContentScript::Revert(const ApplyInfo& apply_info) const {}
void RequestContentScript::InstructRenderProcessToInject(
content::WebContents* contents,
const Extension* extension) const {
content::RenderFrameHost* render_frame_host = contents->GetMainFrame();
render_frame_host->Send(new ExtensionMsg_ExecuteDeclarativeScript(
render_frame_host->GetRoutingID(),
SessionTabHelper::IdForTab(contents),
extension->id(),
script_.id(),
contents->GetLastCommittedURL()));
}
// static
std::unique_ptr<ContentAction> SetIcon::Create(
content::BrowserContext* browser_context,
const Extension* extension,
const base::DictionaryValue* dict,
std::string* error) {
// We can't set a page or action's icon if the extension doesn't have one.
ActionInfo::Type type;
if (ActionInfo::GetPageActionInfo(extension) != NULL) {
type = ActionInfo::TYPE_PAGE;
} else if (ActionInfo::GetBrowserActionInfo(extension) != NULL) {
type = ActionInfo::TYPE_BROWSER;
} else {
*error = kNoPageOrBrowserAction;
return std::unique_ptr<ContentAction>();
}
gfx::ImageSkia icon;
const base::DictionaryValue* canvas_set = NULL;
if (dict->GetDictionary("imageData", &canvas_set) &&
!ExtensionAction::ParseIconFromCanvasDictionary(*canvas_set, &icon)) {
*error = kInvalidIconDictionary;
return std::unique_ptr<ContentAction>();
}
return base::WrapUnique(new SetIcon(gfx::Image(icon), type));
}
//
// ContentAction
//
ContentAction::~ContentAction() {}
// static
std::unique_ptr<ContentAction> ContentAction::Create(
content::BrowserContext* browser_context,
const Extension* extension,
const base::Value& json_action,
std::string* error) {
error->clear();
const base::DictionaryValue* action_dict = NULL;
std::string instance_type;
if (!(json_action.GetAsDictionary(&action_dict) &&
action_dict->GetString(keys::kInstanceType, &instance_type))) {
*error = kMissingInstanceTypeError;
return std::unique_ptr<ContentAction>();
}
ContentActionFactory& factory = g_content_action_factory.Get();
std::map<std::string, ContentActionFactory::FactoryMethod>::iterator
factory_method_iter = factory.factory_methods.find(instance_type);
if (factory_method_iter != factory.factory_methods.end())
return (*factory_method_iter->second)(
browser_context, extension, action_dict, error);
*error = base::StringPrintf(kInvalidInstanceTypeError, instance_type.c_str());
return std::unique_ptr<ContentAction>();
}
ContentAction::ContentAction() {}
} // namespace extensions