Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / extensions / api / declarative_content / content_action.cc
blob56cfc2643ac44c34a78dcc3db639f01fa4180808
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/api/declarative_content/content_action.h"
7 #include <map>
9 #include "base/lazy_instance.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/values.h"
12 #include "chrome/browser/extensions/api/declarative_content/content_constants.h"
13 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
14 #include "chrome/browser/extensions/extension_action.h"
15 #include "chrome/browser/extensions/extension_action_manager.h"
16 #include "chrome/browser/extensions/extension_tab_util.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/sessions/session_tab_helper.h"
19 #include "content/public/browser/invalidate_type.h"
20 #include "content/public/browser/render_frame_host.h"
21 #include "content/public/browser/web_contents.h"
22 #include "extensions/browser/declarative_user_script_manager.h"
23 #include "extensions/browser/extension_system.h"
24 #include "extensions/common/extension.h"
25 #include "extensions/common/extension_messages.h"
26 #include "ui/gfx/image/image.h"
27 #include "ui/gfx/image/image_skia.h"
29 namespace extensions {
31 namespace keys = declarative_content_constants;
33 namespace {
34 // Error messages.
35 const char kInvalidIconDictionary[] =
36 "Icon dictionary must be of the form {\"19\": ImageData1, \"38\": "
37 "ImageData2}";
38 const char kInvalidInstanceTypeError[] =
39 "An action has an invalid instanceType: %s";
40 const char kMissingParameter[] = "Missing parameter is required: %s";
41 const char kNoPageAction[] =
42 "Can't use declarativeContent.ShowPageAction without a page action";
43 const char kNoPageOrBrowserAction[] =
44 "Can't use declarativeContent.SetIcon without a page or browser action";
47 // The following are concrete actions.
50 // Action that instructs to show an extension's page action.
51 class ShowPageAction : public ContentAction {
52 public:
53 ShowPageAction() {}
54 ~ShowPageAction() override {}
56 static scoped_ptr<ContentAction> Create(
57 content::BrowserContext* browser_context,
58 const Extension* extension,
59 const base::DictionaryValue* dict,
60 std::string* error) {
61 // We can't show a page action if the extension doesn't have one.
62 if (ActionInfo::GetPageActionInfo(extension) == NULL) {
63 *error = kNoPageAction;
64 return scoped_ptr<ContentAction>();
66 return make_scoped_ptr(new ShowPageAction);
69 // Implementation of ContentAction:
70 void Apply(const ApplyInfo& apply_info) const override {
71 ExtensionAction* action =
72 GetPageAction(apply_info.browser_context, apply_info.extension);
73 action->DeclarativeShow(ExtensionTabUtil::GetTabId(apply_info.tab));
74 ExtensionActionAPI::Get(apply_info.browser_context)->NotifyChange(
75 action, apply_info.tab, apply_info.browser_context);
77 // The page action is already showing, so nothing needs to be done here.
78 void Reapply(const ApplyInfo& apply_info) const override {}
79 void Revert(const ApplyInfo& apply_info) const override {
80 if (ExtensionAction* action =
81 GetPageAction(apply_info.browser_context, apply_info.extension)) {
82 action->UndoDeclarativeShow(ExtensionTabUtil::GetTabId(apply_info.tab));
83 ExtensionActionAPI::Get(apply_info.browser_context)->NotifyChange(
84 action, apply_info.tab, apply_info.browser_context);
88 private:
89 static ExtensionAction* GetPageAction(
90 content::BrowserContext* browser_context,
91 const Extension* extension) {
92 return ExtensionActionManager::Get(browser_context)
93 ->GetPageAction(*extension);
96 DISALLOW_COPY_AND_ASSIGN(ShowPageAction);
99 // Action that sets an extension's action icon.
100 class SetIcon : public ContentAction {
101 public:
102 SetIcon(const gfx::Image& icon, ActionInfo::Type action_type)
103 : icon_(icon), action_type_(action_type) {}
104 ~SetIcon() override {}
106 static scoped_ptr<ContentAction> Create(
107 content::BrowserContext* browser_context,
108 const Extension* extension,
109 const base::DictionaryValue* dict,
110 std::string* error);
112 // Implementation of ContentAction:
113 void Apply(const ApplyInfo& apply_info) const override {
114 Profile* profile = Profile::FromBrowserContext(apply_info.browser_context);
115 ExtensionAction* action = GetExtensionAction(profile,
116 apply_info.extension);
117 if (action) {
118 action->DeclarativeSetIcon(ExtensionTabUtil::GetTabId(apply_info.tab),
119 apply_info.priority,
120 icon_);
121 ExtensionActionAPI::Get(profile)
122 ->NotifyChange(action, apply_info.tab, profile);
126 void Reapply(const ApplyInfo& apply_info) const override {}
128 void Revert(const ApplyInfo& apply_info) const override {
129 Profile* profile = Profile::FromBrowserContext(apply_info.browser_context);
130 ExtensionAction* action = GetExtensionAction(profile,
131 apply_info.extension);
132 if (action) {
133 action->UndoDeclarativeSetIcon(
134 ExtensionTabUtil::GetTabId(apply_info.tab),
135 apply_info.priority,
136 icon_);
137 ExtensionActionAPI::Get(apply_info.browser_context)
138 ->NotifyChange(action, apply_info.tab, profile);
142 private:
143 ExtensionAction* GetExtensionAction(Profile* profile,
144 const Extension* extension) const {
145 switch (action_type_) {
146 case ActionInfo::TYPE_BROWSER:
147 return ExtensionActionManager::Get(profile)
148 ->GetBrowserAction(*extension);
149 case ActionInfo::TYPE_PAGE:
150 return ExtensionActionManager::Get(profile)->GetPageAction(*extension);
151 default:
152 NOTREACHED();
154 return NULL;
157 gfx::Image icon_;
158 ActionInfo::Type action_type_;
160 DISALLOW_COPY_AND_ASSIGN(SetIcon);
163 // Helper for getting JS collections into C++.
164 static bool AppendJSStringsToCPPStrings(const base::ListValue& append_strings,
165 std::vector<std::string>* append_to) {
166 for (base::ListValue::const_iterator it = append_strings.begin();
167 it != append_strings.end();
168 ++it) {
169 std::string value;
170 if ((*it)->GetAsString(&value)) {
171 append_to->push_back(value);
172 } else {
173 return false;
177 return true;
180 struct ContentActionFactory {
181 // Factory methods for ContentAction instances. |extension| is the extension
182 // for which the action is being created. |dict| contains the json dictionary
183 // that describes the action. |error| is used to return error messages.
184 using FactoryMethod = scoped_ptr<ContentAction>(*)(
185 content::BrowserContext* /* browser_context */,
186 const Extension* /* extension */,
187 const base::DictionaryValue* /* dict */,
188 std::string* /* error */);
189 // Maps the name of a declarativeContent action type to the factory
190 // function creating it.
191 std::map<std::string, FactoryMethod> factory_methods;
193 ContentActionFactory() {
194 factory_methods[keys::kShowPageAction] =
195 &ShowPageAction::Create;
196 factory_methods[keys::kRequestContentScript] =
197 &RequestContentScript::Create;
198 factory_methods[keys::kSetIcon] =
199 &SetIcon::Create;
203 base::LazyInstance<ContentActionFactory>::Leaky
204 g_content_action_factory = LAZY_INSTANCE_INITIALIZER;
206 } // namespace
209 // RequestContentScript
212 struct RequestContentScript::ScriptData {
213 ScriptData();
214 ~ScriptData();
216 std::vector<std::string> css_file_names;
217 std::vector<std::string> js_file_names;
218 bool all_frames;
219 bool match_about_blank;
222 RequestContentScript::ScriptData::ScriptData()
223 : all_frames(false),
224 match_about_blank(false) {}
225 RequestContentScript::ScriptData::~ScriptData() {}
227 // static
228 scoped_ptr<ContentAction> RequestContentScript::Create(
229 content::BrowserContext* browser_context,
230 const Extension* extension,
231 const base::DictionaryValue* dict,
232 std::string* error) {
233 ScriptData script_data;
234 if (!InitScriptData(dict, error, &script_data))
235 return scoped_ptr<ContentAction>();
237 return make_scoped_ptr(new RequestContentScript(browser_context, extension,
238 script_data));
241 // static
242 scoped_ptr<ContentAction> RequestContentScript::CreateForTest(
243 DeclarativeUserScriptMaster* master,
244 const Extension* extension,
245 const base::Value& json_action,
246 std::string* error) {
247 // Simulate ContentAction-level initialization. Check that instance type is
248 // RequestContentScript.
249 error->clear();
250 const base::DictionaryValue* action_dict = NULL;
251 std::string instance_type;
252 if (!(json_action.GetAsDictionary(&action_dict) &&
253 action_dict->GetString(keys::kInstanceType, &instance_type) &&
254 instance_type == std::string(keys::kRequestContentScript)))
255 return scoped_ptr<ContentAction>();
257 // Normal RequestContentScript data initialization.
258 ScriptData script_data;
259 if (!InitScriptData(action_dict, error, &script_data))
260 return scoped_ptr<ContentAction>();
262 // Inject provided DeclarativeUserScriptMaster, rather than looking it up
263 // using a BrowserContext.
264 return make_scoped_ptr(new RequestContentScript(master, extension,
265 script_data));
268 // static
269 bool RequestContentScript::InitScriptData(const base::DictionaryValue* dict,
270 std::string* error,
271 ScriptData* script_data) {
272 const base::ListValue* list_value = NULL;
274 if (!dict->HasKey(keys::kCss) && !dict->HasKey(keys::kJs)) {
275 *error = base::StringPrintf(kMissingParameter, "css or js");
276 return false;
278 if (dict->HasKey(keys::kCss)) {
279 if (!dict->GetList(keys::kCss, &list_value) ||
280 !AppendJSStringsToCPPStrings(*list_value,
281 &script_data->css_file_names)) {
282 return false;
285 if (dict->HasKey(keys::kJs)) {
286 if (!dict->GetList(keys::kJs, &list_value) ||
287 !AppendJSStringsToCPPStrings(*list_value,
288 &script_data->js_file_names)) {
289 return false;
292 if (dict->HasKey(keys::kAllFrames)) {
293 if (!dict->GetBoolean(keys::kAllFrames, &script_data->all_frames))
294 return false;
296 if (dict->HasKey(keys::kMatchAboutBlank)) {
297 if (!dict->GetBoolean(keys::kMatchAboutBlank,
298 &script_data->match_about_blank)) {
299 return false;
303 return true;
306 RequestContentScript::RequestContentScript(
307 content::BrowserContext* browser_context,
308 const Extension* extension,
309 const ScriptData& script_data) {
310 HostID host_id(HostID::EXTENSIONS, extension->id());
311 InitScript(host_id, extension, script_data);
313 master_ = DeclarativeUserScriptManager::Get(browser_context)
314 ->GetDeclarativeUserScriptMasterByID(host_id);
315 AddScript();
318 RequestContentScript::RequestContentScript(
319 DeclarativeUserScriptMaster* master,
320 const Extension* extension,
321 const ScriptData& script_data) {
322 HostID host_id(HostID::EXTENSIONS, extension->id());
323 InitScript(host_id, extension, script_data);
325 master_ = master;
326 AddScript();
329 RequestContentScript::~RequestContentScript() {
330 DCHECK(master_);
331 master_->RemoveScript(script_);
334 void RequestContentScript::InitScript(const HostID& host_id,
335 const Extension* extension,
336 const ScriptData& script_data) {
337 script_.set_id(UserScript::GenerateUserScriptID());
338 script_.set_host_id(host_id);
339 script_.set_run_location(UserScript::BROWSER_DRIVEN);
340 script_.set_match_all_frames(script_data.all_frames);
341 script_.set_match_about_blank(script_data.match_about_blank);
342 for (std::vector<std::string>::const_iterator it =
343 script_data.css_file_names.begin();
344 it != script_data.css_file_names.end(); ++it) {
345 GURL url = extension->GetResourceURL(*it);
346 ExtensionResource resource = extension->GetResource(*it);
347 script_.css_scripts().push_back(UserScript::File(
348 resource.extension_root(), resource.relative_path(), url));
350 for (std::vector<std::string>::const_iterator it =
351 script_data.js_file_names.begin();
352 it != script_data.js_file_names.end(); ++it) {
353 GURL url = extension->GetResourceURL(*it);
354 ExtensionResource resource = extension->GetResource(*it);
355 script_.js_scripts().push_back(UserScript::File(
356 resource.extension_root(), resource.relative_path(), url));
360 void RequestContentScript::Apply(const ApplyInfo& apply_info) const {
361 InstructRenderProcessToInject(apply_info.tab, apply_info.extension);
364 void RequestContentScript::Reapply(const ApplyInfo& apply_info) const {
365 InstructRenderProcessToInject(apply_info.tab, apply_info.extension);
368 void RequestContentScript::Revert(const ApplyInfo& apply_info) const {}
370 void RequestContentScript::InstructRenderProcessToInject(
371 content::WebContents* contents,
372 const Extension* extension) const {
373 content::RenderFrameHost* render_frame_host = contents->GetMainFrame();
374 render_frame_host->Send(new ExtensionMsg_ExecuteDeclarativeScript(
375 render_frame_host->GetRoutingID(),
376 SessionTabHelper::IdForTab(contents),
377 extension->id(),
378 script_.id(),
379 contents->GetLastCommittedURL()));
382 // static
383 scoped_ptr<ContentAction> SetIcon::Create(
384 content::BrowserContext* browser_context,
385 const Extension* extension,
386 const base::DictionaryValue* dict,
387 std::string* error) {
388 // We can't set a page or action's icon if the extension doesn't have one.
389 ActionInfo::Type type;
390 if (ActionInfo::GetPageActionInfo(extension) != NULL) {
391 type = ActionInfo::TYPE_PAGE;
392 } else if (ActionInfo::GetBrowserActionInfo(extension) != NULL) {
393 type = ActionInfo::TYPE_BROWSER;
394 } else {
395 *error = kNoPageOrBrowserAction;
396 return scoped_ptr<ContentAction>();
399 gfx::ImageSkia icon;
400 const base::DictionaryValue* canvas_set = NULL;
401 if (dict->GetDictionary("imageData", &canvas_set) &&
402 !ExtensionAction::ParseIconFromCanvasDictionary(*canvas_set, &icon)) {
403 *error = kInvalidIconDictionary;
404 return scoped_ptr<ContentAction>();
406 return make_scoped_ptr(new SetIcon(gfx::Image(icon), type));
410 // ContentAction
413 ContentAction::~ContentAction() {}
415 // static
416 scoped_ptr<ContentAction> ContentAction::Create(
417 content::BrowserContext* browser_context,
418 const Extension* extension,
419 const base::Value& json_action,
420 std::string* error) {
421 error->clear();
422 const base::DictionaryValue* action_dict = NULL;
423 std::string instance_type;
424 if (!(json_action.GetAsDictionary(&action_dict) &&
425 action_dict->GetString(keys::kInstanceType, &instance_type)))
426 return scoped_ptr<ContentAction>();
428 ContentActionFactory& factory = g_content_action_factory.Get();
429 std::map<std::string, ContentActionFactory::FactoryMethod>::iterator
430 factory_method_iter = factory.factory_methods.find(instance_type);
431 if (factory_method_iter != factory.factory_methods.end())
432 return (*factory_method_iter->second)(
433 browser_context, extension, action_dict, error);
435 *error = base::StringPrintf(kInvalidInstanceTypeError, instance_type.c_str());
436 return scoped_ptr<ContentAction>();
439 ContentAction::ContentAction() {}
441 } // namespace extensions