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"
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_view_host.h"
21 #include "content/public/browser/web_contents.h"
22 #include "extensions/browser/extension_registry.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;
35 const char kInvalidIconDictionary[] =
36 "Icon dictionary must be of the form {\"19\": ImageData1, \"38\": "
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";
46 #define INPUT_FORMAT_VALIDATE(test) do { \
48 *bad_message = true; \
54 // The following are concrete actions.
57 // Action that instructs to show an extension's page action.
58 class ShowPageAction : public ContentAction {
62 static scoped_refptr<ContentAction> Create(
63 content::BrowserContext* browser_context,
64 const Extension* extension,
65 const base::DictionaryValue* dict,
68 // We can't show a page action if the extension doesn't have one.
69 if (ActionInfo::GetPageActionInfo(extension) == NULL) {
70 *error = kNoPageAction;
71 return scoped_refptr<ContentAction>();
73 return scoped_refptr<ContentAction>(new ShowPageAction);
76 // Implementation of ContentAction:
77 virtual Type GetType() const OVERRIDE { return ACTION_SHOW_PAGE_ACTION; }
78 virtual void Apply(const std::string& extension_id,
79 const base::Time& extension_install_time,
80 ApplyInfo* apply_info) const OVERRIDE {
81 ExtensionAction* action =
82 GetPageAction(apply_info->browser_context, extension_id);
83 action->DeclarativeShow(ExtensionTabUtil::GetTabId(apply_info->tab));
84 ExtensionActionAPI::Get(apply_info->browser_context)->NotifyChange(
85 action, apply_info->tab, apply_info->browser_context);
87 // The page action is already showing, so nothing needs to be done here.
88 virtual void Reapply(const std::string& extension_id,
89 const base::Time& extension_install_time,
90 ApplyInfo* apply_info) const OVERRIDE {}
91 virtual void Revert(const std::string& extension_id,
92 const base::Time& extension_install_time,
93 ApplyInfo* apply_info) const OVERRIDE {
94 if (ExtensionAction* action =
95 GetPageAction(apply_info->browser_context, extension_id)) {
96 action->UndoDeclarativeShow(ExtensionTabUtil::GetTabId(apply_info->tab));
97 ExtensionActionAPI::Get(apply_info->browser_context)->NotifyChange(
98 action, apply_info->tab, apply_info->browser_context);
103 static ExtensionAction* GetPageAction(
104 content::BrowserContext* browser_context,
105 const std::string& extension_id) {
106 const Extension* extension =
107 ExtensionRegistry::Get(browser_context)
108 ->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
111 return ExtensionActionManager::Get(browser_context)
112 ->GetPageAction(*extension);
114 virtual ~ShowPageAction() {}
116 DISALLOW_COPY_AND_ASSIGN(ShowPageAction);
119 // Action that sets an extension's action icon.
120 class SetIcon : public ContentAction {
122 SetIcon(const gfx::Image& icon, ActionInfo::Type action_type)
123 : icon_(icon), action_type_(action_type) {}
125 static scoped_refptr<ContentAction> Create(
126 content::BrowserContext* browser_context,
127 const Extension* extension,
128 const base::DictionaryValue* dict,
132 // Implementation of ContentAction:
133 virtual Type GetType() const OVERRIDE { return ACTION_SET_ICON; }
134 virtual void Apply(const std::string& extension_id,
135 const base::Time& extension_install_time,
136 ApplyInfo* apply_info) const OVERRIDE {
137 Profile* profile = Profile::FromBrowserContext(apply_info->browser_context);
138 ExtensionAction* action = GetExtensionAction(profile, extension_id);
140 action->DeclarativeSetIcon(ExtensionTabUtil::GetTabId(apply_info->tab),
141 apply_info->priority,
143 ExtensionActionAPI::Get(profile)
144 ->NotifyChange(action, apply_info->tab, profile);
148 virtual void Reapply(const std::string& extension_id,
149 const base::Time& extension_install_time,
150 ApplyInfo* apply_info) const OVERRIDE {}
152 virtual void Revert(const std::string& extension_id,
153 const base::Time& extension_install_time,
154 ApplyInfo* apply_info) const OVERRIDE {
155 Profile* profile = Profile::FromBrowserContext(apply_info->browser_context);
156 ExtensionAction* action = GetExtensionAction(profile, extension_id);
158 action->UndoDeclarativeSetIcon(
159 ExtensionTabUtil::GetTabId(apply_info->tab),
160 apply_info->priority,
162 ExtensionActionAPI::Get(apply_info->browser_context)
163 ->NotifyChange(action, apply_info->tab, profile);
168 ExtensionAction* GetExtensionAction(Profile* profile,
169 const std::string& extension_id) const {
170 const Extension* extension =
171 ExtensionRegistry::Get(profile)
172 ->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
175 switch (action_type_) {
176 case ActionInfo::TYPE_BROWSER:
177 return ExtensionActionManager::Get(profile)
178 ->GetBrowserAction(*extension);
179 case ActionInfo::TYPE_PAGE:
180 return ExtensionActionManager::Get(profile)->GetPageAction(*extension);
186 virtual ~SetIcon() {}
189 ActionInfo::Type action_type_;
191 DISALLOW_COPY_AND_ASSIGN(SetIcon);
194 // Helper for getting JS collections into C++.
195 static bool AppendJSStringsToCPPStrings(const base::ListValue& append_strings,
196 std::vector<std::string>* append_to) {
197 for (base::ListValue::const_iterator it = append_strings.begin();
198 it != append_strings.end();
201 if ((*it)->GetAsString(&value)) {
202 append_to->push_back(value);
211 struct ContentActionFactory {
212 // Factory methods for ContentAction instances. |extension| is the extension
213 // for which the action is being created. |dict| contains the json dictionary
214 // that describes the action. |error| is used to return error messages in case
215 // the extension passed an action that was syntactically correct but
216 // semantically incorrect. |bad_message| is set to true in case |dict| does
217 // not confirm to the validated JSON specification.
218 typedef scoped_refptr<ContentAction>(*FactoryMethod)(
219 content::BrowserContext* /* browser_context */,
220 const Extension* /* extension */,
221 const base::DictionaryValue* /* dict */,
222 std::string* /* error */,
223 bool* /* bad_message */);
224 // Maps the name of a declarativeContent action type to the factory
225 // function creating it.
226 std::map<std::string, FactoryMethod> factory_methods;
228 ContentActionFactory() {
229 factory_methods[keys::kShowPageAction] =
230 &ShowPageAction::Create;
231 factory_methods[keys::kRequestContentScript] =
232 &RequestContentScript::Create;
233 factory_methods[keys::kSetIcon] =
238 base::LazyInstance<ContentActionFactory>::Leaky
239 g_content_action_factory = LAZY_INSTANCE_INITIALIZER;
244 // RequestContentScript
247 struct RequestContentScript::ScriptData {
251 std::vector<std::string> css_file_names;
252 std::vector<std::string> js_file_names;
254 bool match_about_blank;
257 RequestContentScript::ScriptData::ScriptData()
259 match_about_blank(false) {}
260 RequestContentScript::ScriptData::~ScriptData() {}
263 scoped_refptr<ContentAction> RequestContentScript::Create(
264 content::BrowserContext* browser_context,
265 const Extension* extension,
266 const base::DictionaryValue* dict,
269 ScriptData script_data;
270 if (!InitScriptData(dict, error, bad_message, &script_data))
271 return scoped_refptr<ContentAction>();
273 return scoped_refptr<ContentAction>(new RequestContentScript(
280 scoped_refptr<ContentAction> RequestContentScript::CreateForTest(
281 DeclarativeUserScriptMaster* master,
282 const Extension* extension,
283 const base::Value& json_action,
286 // Simulate ContentAction-level initialization. Check that instance type is
287 // RequestContentScript.
288 ContentAction::ResetErrorData(error, bad_message);
289 const base::DictionaryValue* action_dict = NULL;
290 std::string instance_type;
291 if (!ContentAction::Validate(
297 instance_type != std::string(keys::kRequestContentScript))
298 return scoped_refptr<ContentAction>();
300 // Normal RequestContentScript data initialization.
301 ScriptData script_data;
302 if (!InitScriptData(action_dict, error, bad_message, &script_data))
303 return scoped_refptr<ContentAction>();
305 // Inject provided DeclarativeUserScriptMaster, rather than looking it up
306 // using a BrowserContext.
307 return scoped_refptr<ContentAction>(new RequestContentScript(
314 bool RequestContentScript::InitScriptData(const base::DictionaryValue* dict,
317 ScriptData* script_data) {
318 const base::ListValue* list_value = NULL;
320 if (!dict->HasKey(keys::kCss) && !dict->HasKey(keys::kJs)) {
321 *error = base::StringPrintf(kMissingParameter, "css or js");
324 if (dict->HasKey(keys::kCss)) {
325 INPUT_FORMAT_VALIDATE(dict->GetList(keys::kCss, &list_value));
326 INPUT_FORMAT_VALIDATE(
327 AppendJSStringsToCPPStrings(*list_value, &script_data->css_file_names));
329 if (dict->HasKey(keys::kJs)) {
330 INPUT_FORMAT_VALIDATE(dict->GetList(keys::kJs, &list_value));
331 INPUT_FORMAT_VALIDATE(
332 AppendJSStringsToCPPStrings(*list_value, &script_data->js_file_names));
334 if (dict->HasKey(keys::kAllFrames)) {
335 INPUT_FORMAT_VALIDATE(
336 dict->GetBoolean(keys::kAllFrames, &script_data->all_frames));
338 if (dict->HasKey(keys::kMatchAboutBlank)) {
339 INPUT_FORMAT_VALIDATE(
341 keys::kMatchAboutBlank,
342 &script_data->match_about_blank));
348 RequestContentScript::RequestContentScript(
349 content::BrowserContext* browser_context,
350 const Extension* extension,
351 const ScriptData& script_data) {
352 InitScript(extension, script_data);
355 ExtensionSystem::Get(browser_context)->
356 GetDeclarativeUserScriptMasterByExtension(extension->id());
360 RequestContentScript::RequestContentScript(
361 DeclarativeUserScriptMaster* master,
362 const Extension* extension,
363 const ScriptData& script_data) {
364 InitScript(extension, script_data);
370 RequestContentScript::~RequestContentScript() {
372 master_->RemoveScript(script_);
375 void RequestContentScript::InitScript(const Extension* extension,
376 const ScriptData& script_data) {
377 script_.set_id(UserScript::GenerateUserScriptID());
378 script_.set_extension_id(extension->id());
379 script_.set_run_location(UserScript::BROWSER_DRIVEN);
380 script_.set_match_all_frames(script_data.all_frames);
381 script_.set_match_about_blank(script_data.match_about_blank);
382 for (std::vector<std::string>::const_iterator it =
383 script_data.css_file_names.begin();
384 it != script_data.css_file_names.end(); ++it) {
385 GURL url = extension->GetResourceURL(*it);
386 ExtensionResource resource = extension->GetResource(*it);
387 script_.css_scripts().push_back(UserScript::File(
388 resource.extension_root(), resource.relative_path(), url));
390 for (std::vector<std::string>::const_iterator it =
391 script_data.js_file_names.begin();
392 it != script_data.js_file_names.end(); ++it) {
393 GURL url = extension->GetResourceURL(*it);
394 ExtensionResource resource = extension->GetResource(*it);
395 script_.js_scripts().push_back(UserScript::File(
396 resource.extension_root(), resource.relative_path(), url));
400 ContentAction::Type RequestContentScript::GetType() const {
401 return ACTION_REQUEST_CONTENT_SCRIPT;
404 void RequestContentScript::Apply(const std::string& extension_id,
405 const base::Time& extension_install_time,
406 ApplyInfo* apply_info) const {
407 InstructRenderProcessToInject(apply_info->tab, extension_id);
410 void RequestContentScript::Reapply(const std::string& extension_id,
411 const base::Time& extension_install_time,
412 ApplyInfo* apply_info) const {
413 InstructRenderProcessToInject(apply_info->tab, extension_id);
416 void RequestContentScript::Revert(const std::string& extension_id,
417 const base::Time& extension_install_time,
418 ApplyInfo* apply_info) const {}
420 void RequestContentScript::InstructRenderProcessToInject(
421 content::WebContents* contents,
422 const std::string& extension_id) const {
423 content::RenderViewHost* render_view_host = contents->GetRenderViewHost();
424 render_view_host->Send(new ExtensionMsg_ExecuteDeclarativeScript(
425 render_view_host->GetRoutingID(),
426 SessionTabHelper::IdForTab(contents),
429 contents->GetLastCommittedURL()));
433 scoped_refptr<ContentAction> SetIcon::Create(
434 content::BrowserContext* browser_context,
435 const Extension* extension,
436 const base::DictionaryValue* dict,
439 // We can't set a page or action's icon if the extension doesn't have one.
440 ActionInfo::Type type;
441 if (ActionInfo::GetPageActionInfo(extension) != NULL) {
442 type = ActionInfo::TYPE_PAGE;
443 } else if (ActionInfo::GetBrowserActionInfo(extension) != NULL) {
444 type = ActionInfo::TYPE_BROWSER;
446 *error = kNoPageOrBrowserAction;
447 return scoped_refptr<ContentAction>();
451 const base::DictionaryValue* canvas_set = NULL;
452 if (dict->GetDictionary("imageData", &canvas_set) &&
453 !ExtensionAction::ParseIconFromCanvasDictionary(*canvas_set, &icon)) {
454 *error = kInvalidIconDictionary;
456 return scoped_refptr<ContentAction>();
458 return scoped_refptr<ContentAction>(new SetIcon(gfx::Image(icon), type));
465 ContentAction::ContentAction() {}
467 ContentAction::~ContentAction() {}
470 scoped_refptr<ContentAction> ContentAction::Create(
471 content::BrowserContext* browser_context,
472 const Extension* extension,
473 const base::Value& json_action,
476 ResetErrorData(error, bad_message);
477 const base::DictionaryValue* action_dict = NULL;
478 std::string instance_type;
479 if (!Validate(json_action, error, bad_message, &action_dict, &instance_type))
480 return scoped_refptr<ContentAction>();
482 ContentActionFactory& factory = g_content_action_factory.Get();
483 std::map<std::string, ContentActionFactory::FactoryMethod>::iterator
484 factory_method_iter = factory.factory_methods.find(instance_type);
485 if (factory_method_iter != factory.factory_methods.end())
486 return (*factory_method_iter->second)(
487 browser_context, extension, action_dict, error, bad_message);
489 *error = base::StringPrintf(kInvalidInstanceTypeError, instance_type.c_str());
490 return scoped_refptr<ContentAction>();
493 bool ContentAction::Validate(const base::Value& json_action,
496 const base::DictionaryValue** action_dict,
497 std::string* instance_type) {
498 INPUT_FORMAT_VALIDATE(json_action.GetAsDictionary(action_dict));
499 INPUT_FORMAT_VALIDATE(
500 (*action_dict)->GetString(keys::kInstanceType, instance_type));
504 } // namespace extensions