Clean up of plumbing HostID in declarative content API.
[chromium-blink-merge.git] / chrome / browser / extensions / api / declarative_content / content_action.cc
blobb5d76aa42f748b466031555d1d14f3e1dd00d2ec
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/declarative_user_script_manager.h"
15 #include "chrome/browser/extensions/extension_action.h"
16 #include "chrome/browser/extensions/extension_action_manager.h"
17 #include "chrome/browser/extensions/extension_tab_util.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/sessions/session_tab_helper.h"
20 #include "content/public/browser/invalidate_type.h"
21 #include "content/public/browser/render_view_host.h"
22 #include "content/public/browser/web_contents.h"
23 #include "extensions/browser/extension_registry.h"
24 #include "extensions/browser/extension_system.h"
25 #include "extensions/common/extension.h"
26 #include "extensions/common/extension_messages.h"
27 #include "ui/gfx/image/image.h"
28 #include "ui/gfx/image/image_skia.h"
30 namespace extensions {
32 namespace keys = declarative_content_constants;
34 namespace {
35 // Error messages.
36 const char kInvalidIconDictionary[] =
37 "Icon dictionary must be of the form {\"19\": ImageData1, \"38\": "
38 "ImageData2}";
39 const char kInvalidInstanceTypeError[] =
40 "An action has an invalid instanceType: %s";
41 const char kMissingParameter[] = "Missing parameter is required: %s";
42 const char kNoPageAction[] =
43 "Can't use declarativeContent.ShowPageAction without a page action";
44 const char kNoPageOrBrowserAction[] =
45 "Can't use declarativeContent.SetIcon without a page or browser action";
47 #define INPUT_FORMAT_VALIDATE(test) do { \
48 if (!(test)) { \
49 *bad_message = true; \
50 return false; \
51 } \
52 } while (0)
55 // The following are concrete actions.
58 // Action that instructs to show an extension's page action.
59 class ShowPageAction : public ContentAction {
60 public:
61 ShowPageAction() {}
63 static scoped_refptr<ContentAction> Create(
64 content::BrowserContext* browser_context,
65 const Extension* extension,
66 const base::DictionaryValue* dict,
67 std::string* error,
68 bool* bad_message) {
69 // We can't show a page action if the extension doesn't have one.
70 if (ActionInfo::GetPageActionInfo(extension) == NULL) {
71 *error = kNoPageAction;
72 return scoped_refptr<ContentAction>();
74 return scoped_refptr<ContentAction>(new ShowPageAction);
77 // Implementation of ContentAction:
78 Type GetType() const override { return ACTION_SHOW_PAGE_ACTION; }
79 void Apply(const std::string& extension_id,
80 const base::Time& extension_install_time,
81 ApplyInfo* apply_info) const override {
82 ExtensionAction* action =
83 GetPageAction(apply_info->browser_context, extension_id);
84 action->DeclarativeShow(ExtensionTabUtil::GetTabId(apply_info->tab));
85 ExtensionActionAPI::Get(apply_info->browser_context)->NotifyChange(
86 action, apply_info->tab, apply_info->browser_context);
88 // The page action is already showing, so nothing needs to be done here.
89 void Reapply(const std::string& extension_id,
90 const base::Time& extension_install_time,
91 ApplyInfo* apply_info) const override {}
92 void Revert(const std::string& extension_id,
93 const base::Time& extension_install_time,
94 ApplyInfo* apply_info) const override {
95 if (ExtensionAction* action =
96 GetPageAction(apply_info->browser_context, extension_id)) {
97 action->UndoDeclarativeShow(ExtensionTabUtil::GetTabId(apply_info->tab));
98 ExtensionActionAPI::Get(apply_info->browser_context)->NotifyChange(
99 action, apply_info->tab, apply_info->browser_context);
103 private:
104 static ExtensionAction* GetPageAction(
105 content::BrowserContext* browser_context,
106 const std::string& extension_id) {
107 const Extension* extension =
108 ExtensionRegistry::Get(browser_context)
109 ->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
110 if (!extension)
111 return NULL;
112 return ExtensionActionManager::Get(browser_context)
113 ->GetPageAction(*extension);
115 ~ShowPageAction() override {}
117 DISALLOW_COPY_AND_ASSIGN(ShowPageAction);
120 // Action that sets an extension's action icon.
121 class SetIcon : public ContentAction {
122 public:
123 SetIcon(const gfx::Image& icon, ActionInfo::Type action_type)
124 : icon_(icon), action_type_(action_type) {}
126 static scoped_refptr<ContentAction> Create(
127 content::BrowserContext* browser_context,
128 const Extension* extension,
129 const base::DictionaryValue* dict,
130 std::string* error,
131 bool* bad_message);
133 // Implementation of ContentAction:
134 Type GetType() const override { return ACTION_SET_ICON; }
135 void Apply(const std::string& extension_id,
136 const base::Time& extension_install_time,
137 ApplyInfo* apply_info) const override {
138 Profile* profile = Profile::FromBrowserContext(apply_info->browser_context);
139 ExtensionAction* action = GetExtensionAction(profile, extension_id);
140 if (action) {
141 action->DeclarativeSetIcon(ExtensionTabUtil::GetTabId(apply_info->tab),
142 apply_info->priority,
143 icon_);
144 ExtensionActionAPI::Get(profile)
145 ->NotifyChange(action, apply_info->tab, profile);
149 void Reapply(const std::string& extension_id,
150 const base::Time& extension_install_time,
151 ApplyInfo* apply_info) const override {}
153 void Revert(const std::string& extension_id,
154 const base::Time& extension_install_time,
155 ApplyInfo* apply_info) const override {
156 Profile* profile = Profile::FromBrowserContext(apply_info->browser_context);
157 ExtensionAction* action = GetExtensionAction(profile, extension_id);
158 if (action) {
159 action->UndoDeclarativeSetIcon(
160 ExtensionTabUtil::GetTabId(apply_info->tab),
161 apply_info->priority,
162 icon_);
163 ExtensionActionAPI::Get(apply_info->browser_context)
164 ->NotifyChange(action, apply_info->tab, profile);
168 private:
169 ExtensionAction* GetExtensionAction(Profile* profile,
170 const std::string& extension_id) const {
171 const Extension* extension =
172 ExtensionRegistry::Get(profile)
173 ->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
174 if (!extension)
175 return NULL;
176 switch (action_type_) {
177 case ActionInfo::TYPE_BROWSER:
178 return ExtensionActionManager::Get(profile)
179 ->GetBrowserAction(*extension);
180 case ActionInfo::TYPE_PAGE:
181 return ExtensionActionManager::Get(profile)->GetPageAction(*extension);
182 default:
183 NOTREACHED();
185 return NULL;
187 ~SetIcon() override {}
189 gfx::Image icon_;
190 ActionInfo::Type action_type_;
192 DISALLOW_COPY_AND_ASSIGN(SetIcon);
195 // Helper for getting JS collections into C++.
196 static bool AppendJSStringsToCPPStrings(const base::ListValue& append_strings,
197 std::vector<std::string>* append_to) {
198 for (base::ListValue::const_iterator it = append_strings.begin();
199 it != append_strings.end();
200 ++it) {
201 std::string value;
202 if ((*it)->GetAsString(&value)) {
203 append_to->push_back(value);
204 } else {
205 return false;
209 return true;
212 struct ContentActionFactory {
213 // Factory methods for ContentAction instances. |extension| is the extension
214 // for which the action is being created. |dict| contains the json dictionary
215 // that describes the action. |error| is used to return error messages in case
216 // the extension passed an action that was syntactically correct but
217 // semantically incorrect. |bad_message| is set to true in case |dict| does
218 // not confirm to the validated JSON specification.
219 typedef scoped_refptr<ContentAction>(*FactoryMethod)(
220 content::BrowserContext* /* browser_context */,
221 const Extension* /* extension */,
222 const base::DictionaryValue* /* dict */,
223 std::string* /* error */,
224 bool* /* bad_message */);
225 // Maps the name of a declarativeContent action type to the factory
226 // function creating it.
227 std::map<std::string, FactoryMethod> factory_methods;
229 ContentActionFactory() {
230 factory_methods[keys::kShowPageAction] =
231 &ShowPageAction::Create;
232 factory_methods[keys::kRequestContentScript] =
233 &RequestContentScript::Create;
234 factory_methods[keys::kSetIcon] =
235 &SetIcon::Create;
239 base::LazyInstance<ContentActionFactory>::Leaky
240 g_content_action_factory = LAZY_INSTANCE_INITIALIZER;
242 } // namespace
245 // RequestContentScript
248 struct RequestContentScript::ScriptData {
249 ScriptData();
250 ~ScriptData();
252 std::vector<std::string> css_file_names;
253 std::vector<std::string> js_file_names;
254 bool all_frames;
255 bool match_about_blank;
258 RequestContentScript::ScriptData::ScriptData()
259 : all_frames(false),
260 match_about_blank(false) {}
261 RequestContentScript::ScriptData::~ScriptData() {}
263 // static
264 scoped_refptr<ContentAction> RequestContentScript::Create(
265 content::BrowserContext* browser_context,
266 const Extension* extension,
267 const base::DictionaryValue* dict,
268 std::string* error,
269 bool* bad_message) {
270 ScriptData script_data;
271 if (!InitScriptData(dict, error, bad_message, &script_data))
272 return scoped_refptr<ContentAction>();
274 return scoped_refptr<ContentAction>(new RequestContentScript(
275 browser_context,
276 extension,
277 script_data));
280 // static
281 scoped_refptr<ContentAction> RequestContentScript::CreateForTest(
282 DeclarativeUserScriptMaster* master,
283 const Extension* extension,
284 const base::Value& json_action,
285 std::string* error,
286 bool* bad_message) {
287 // Simulate ContentAction-level initialization. Check that instance type is
288 // RequestContentScript.
289 ContentAction::ResetErrorData(error, bad_message);
290 const base::DictionaryValue* action_dict = NULL;
291 std::string instance_type;
292 if (!ContentAction::Validate(
293 json_action,
294 error,
295 bad_message,
296 &action_dict,
297 &instance_type) ||
298 instance_type != std::string(keys::kRequestContentScript))
299 return scoped_refptr<ContentAction>();
301 // Normal RequestContentScript data initialization.
302 ScriptData script_data;
303 if (!InitScriptData(action_dict, error, bad_message, &script_data))
304 return scoped_refptr<ContentAction>();
306 // Inject provided DeclarativeUserScriptMaster, rather than looking it up
307 // using a BrowserContext.
308 return scoped_refptr<ContentAction>(new RequestContentScript(
309 master,
310 extension,
311 script_data));
314 // static
315 bool RequestContentScript::InitScriptData(const base::DictionaryValue* dict,
316 std::string* error,
317 bool* bad_message,
318 ScriptData* script_data) {
319 const base::ListValue* list_value = NULL;
321 if (!dict->HasKey(keys::kCss) && !dict->HasKey(keys::kJs)) {
322 *error = base::StringPrintf(kMissingParameter, "css or js");
323 return false;
325 if (dict->HasKey(keys::kCss)) {
326 INPUT_FORMAT_VALIDATE(dict->GetList(keys::kCss, &list_value));
327 INPUT_FORMAT_VALIDATE(
328 AppendJSStringsToCPPStrings(*list_value, &script_data->css_file_names));
330 if (dict->HasKey(keys::kJs)) {
331 INPUT_FORMAT_VALIDATE(dict->GetList(keys::kJs, &list_value));
332 INPUT_FORMAT_VALIDATE(
333 AppendJSStringsToCPPStrings(*list_value, &script_data->js_file_names));
335 if (dict->HasKey(keys::kAllFrames)) {
336 INPUT_FORMAT_VALIDATE(
337 dict->GetBoolean(keys::kAllFrames, &script_data->all_frames));
339 if (dict->HasKey(keys::kMatchAboutBlank)) {
340 INPUT_FORMAT_VALIDATE(
341 dict->GetBoolean(
342 keys::kMatchAboutBlank,
343 &script_data->match_about_blank));
346 return true;
349 RequestContentScript::RequestContentScript(
350 content::BrowserContext* browser_context,
351 const Extension* extension,
352 const ScriptData& script_data) {
353 HostID host_id(HostID::EXTENSIONS, extension->id());
354 InitScript(host_id, extension, script_data);
356 master_ = ExtensionSystem::Get(browser_context)
357 ->declarative_user_script_manager()
358 ->GetDeclarativeUserScriptMasterByID(host_id);
359 AddScript();
362 RequestContentScript::RequestContentScript(
363 DeclarativeUserScriptMaster* master,
364 const Extension* extension,
365 const ScriptData& script_data) {
366 HostID host_id(HostID::EXTENSIONS, extension->id());
367 InitScript(host_id, extension, script_data);
369 master_ = master;
370 AddScript();
373 RequestContentScript::~RequestContentScript() {
374 DCHECK(master_);
375 master_->RemoveScript(script_);
378 void RequestContentScript::InitScript(const HostID& host_id,
379 const Extension* extension,
380 const ScriptData& script_data) {
381 script_.set_id(UserScript::GenerateUserScriptID());
382 script_.set_host_id(host_id);
383 script_.set_run_location(UserScript::BROWSER_DRIVEN);
384 script_.set_match_all_frames(script_data.all_frames);
385 script_.set_match_about_blank(script_data.match_about_blank);
386 for (std::vector<std::string>::const_iterator it =
387 script_data.css_file_names.begin();
388 it != script_data.css_file_names.end(); ++it) {
389 GURL url = extension->GetResourceURL(*it);
390 ExtensionResource resource = extension->GetResource(*it);
391 script_.css_scripts().push_back(UserScript::File(
392 resource.extension_root(), resource.relative_path(), url));
394 for (std::vector<std::string>::const_iterator it =
395 script_data.js_file_names.begin();
396 it != script_data.js_file_names.end(); ++it) {
397 GURL url = extension->GetResourceURL(*it);
398 ExtensionResource resource = extension->GetResource(*it);
399 script_.js_scripts().push_back(UserScript::File(
400 resource.extension_root(), resource.relative_path(), url));
404 ContentAction::Type RequestContentScript::GetType() const {
405 return ACTION_REQUEST_CONTENT_SCRIPT;
408 void RequestContentScript::Apply(const std::string& extension_id,
409 const base::Time& extension_install_time,
410 ApplyInfo* apply_info) const {
411 InstructRenderProcessToInject(apply_info->tab, extension_id);
414 void RequestContentScript::Reapply(const std::string& extension_id,
415 const base::Time& extension_install_time,
416 ApplyInfo* apply_info) const {
417 InstructRenderProcessToInject(apply_info->tab, extension_id);
420 void RequestContentScript::Revert(const std::string& extension_id,
421 const base::Time& extension_install_time,
422 ApplyInfo* apply_info) const {}
424 void RequestContentScript::InstructRenderProcessToInject(
425 content::WebContents* contents,
426 const std::string& extension_id) const {
427 content::RenderViewHost* render_view_host = contents->GetRenderViewHost();
428 render_view_host->Send(new ExtensionMsg_ExecuteDeclarativeScript(
429 render_view_host->GetRoutingID(),
430 SessionTabHelper::IdForTab(contents),
431 extension_id,
432 script_.id(),
433 contents->GetLastCommittedURL()));
436 // static
437 scoped_refptr<ContentAction> SetIcon::Create(
438 content::BrowserContext* browser_context,
439 const Extension* extension,
440 const base::DictionaryValue* dict,
441 std::string* error,
442 bool* bad_message) {
443 // We can't set a page or action's icon if the extension doesn't have one.
444 ActionInfo::Type type;
445 if (ActionInfo::GetPageActionInfo(extension) != NULL) {
446 type = ActionInfo::TYPE_PAGE;
447 } else if (ActionInfo::GetBrowserActionInfo(extension) != NULL) {
448 type = ActionInfo::TYPE_BROWSER;
449 } else {
450 *error = kNoPageOrBrowserAction;
451 return scoped_refptr<ContentAction>();
454 gfx::ImageSkia icon;
455 const base::DictionaryValue* canvas_set = NULL;
456 if (dict->GetDictionary("imageData", &canvas_set) &&
457 !ExtensionAction::ParseIconFromCanvasDictionary(*canvas_set, &icon)) {
458 *error = kInvalidIconDictionary;
459 *bad_message = true;
460 return scoped_refptr<ContentAction>();
462 return scoped_refptr<ContentAction>(new SetIcon(gfx::Image(icon), type));
466 // ContentAction
469 ContentAction::ContentAction() {}
471 ContentAction::~ContentAction() {}
473 // static
474 scoped_refptr<ContentAction> ContentAction::Create(
475 content::BrowserContext* browser_context,
476 const Extension* extension,
477 const base::Value& json_action,
478 std::string* error,
479 bool* bad_message) {
480 ResetErrorData(error, bad_message);
481 const base::DictionaryValue* action_dict = NULL;
482 std::string instance_type;
483 if (!Validate(json_action, error, bad_message, &action_dict, &instance_type))
484 return scoped_refptr<ContentAction>();
486 ContentActionFactory& factory = g_content_action_factory.Get();
487 std::map<std::string, ContentActionFactory::FactoryMethod>::iterator
488 factory_method_iter = factory.factory_methods.find(instance_type);
489 if (factory_method_iter != factory.factory_methods.end())
490 return (*factory_method_iter->second)(
491 browser_context, extension, action_dict, error, bad_message);
493 *error = base::StringPrintf(kInvalidInstanceTypeError, instance_type.c_str());
494 return scoped_refptr<ContentAction>();
497 bool ContentAction::Validate(const base::Value& json_action,
498 std::string* error,
499 bool* bad_message,
500 const base::DictionaryValue** action_dict,
501 std::string* instance_type) {
502 INPUT_FORMAT_VALIDATE(json_action.GetAsDictionary(action_dict));
503 INPUT_FORMAT_VALIDATE(
504 (*action_dict)->GetString(keys::kInstanceType, instance_type));
505 return true;
508 } // namespace extensions