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/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
;
36 const char kInvalidIconDictionary
[] =
37 "Icon dictionary must be of the form {\"19\": ImageData1, \"38\": "
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 { \
49 *bad_message = true; \
55 // The following are concrete actions.
58 // Action that instructs to show an extension's page action.
59 class ShowPageAction
: public ContentAction
{
63 static scoped_refptr
<ContentAction
> Create(
64 content::BrowserContext
* browser_context
,
65 const Extension
* extension
,
66 const base::DictionaryValue
* dict
,
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
);
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
);
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
{
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
,
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
);
141 action
->DeclarativeSetIcon(ExtensionTabUtil::GetTabId(apply_info
->tab
),
142 apply_info
->priority
,
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
);
159 action
->UndoDeclarativeSetIcon(
160 ExtensionTabUtil::GetTabId(apply_info
->tab
),
161 apply_info
->priority
,
163 ExtensionActionAPI::Get(apply_info
->browser_context
)
164 ->NotifyChange(action
, apply_info
->tab
, profile
);
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
);
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
);
187 ~SetIcon() override
{}
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();
202 if ((*it
)->GetAsString(&value
)) {
203 append_to
->push_back(value
);
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
] =
239 base::LazyInstance
<ContentActionFactory
>::Leaky
240 g_content_action_factory
= LAZY_INSTANCE_INITIALIZER
;
245 // RequestContentScript
248 struct RequestContentScript::ScriptData
{
252 std::vector
<std::string
> css_file_names
;
253 std::vector
<std::string
> js_file_names
;
255 bool match_about_blank
;
258 RequestContentScript::ScriptData::ScriptData()
260 match_about_blank(false) {}
261 RequestContentScript::ScriptData::~ScriptData() {}
264 scoped_refptr
<ContentAction
> RequestContentScript::Create(
265 content::BrowserContext
* browser_context
,
266 const Extension
* extension
,
267 const base::DictionaryValue
* dict
,
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(
281 scoped_refptr
<ContentAction
> RequestContentScript::CreateForTest(
282 DeclarativeUserScriptMaster
* master
,
283 const Extension
* extension
,
284 const base::Value
& json_action
,
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(
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(
315 bool RequestContentScript::InitScriptData(const base::DictionaryValue
* dict
,
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");
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(
342 keys::kMatchAboutBlank
,
343 &script_data
->match_about_blank
));
349 RequestContentScript::RequestContentScript(
350 content::BrowserContext
* browser_context
,
351 const Extension
* extension
,
352 const ScriptData
& script_data
) {
353 InitScript(extension
, script_data
);
355 master_
= ExtensionSystem::Get(browser_context
)
356 ->declarative_user_script_manager()
357 ->GetDeclarativeUserScriptMasterByID(extension
->id());
361 RequestContentScript::RequestContentScript(
362 DeclarativeUserScriptMaster
* master
,
363 const Extension
* extension
,
364 const ScriptData
& script_data
) {
365 InitScript(extension
, script_data
);
371 RequestContentScript::~RequestContentScript() {
373 master_
->RemoveScript(script_
);
376 void RequestContentScript::InitScript(const Extension
* extension
,
377 const ScriptData
& script_data
) {
378 script_
.set_id(UserScript::GenerateUserScriptID());
379 script_
.set_extension_id(extension
->id());
380 script_
.set_run_location(UserScript::BROWSER_DRIVEN
);
381 script_
.set_match_all_frames(script_data
.all_frames
);
382 script_
.set_match_about_blank(script_data
.match_about_blank
);
383 for (std::vector
<std::string
>::const_iterator it
=
384 script_data
.css_file_names
.begin();
385 it
!= script_data
.css_file_names
.end(); ++it
) {
386 GURL url
= extension
->GetResourceURL(*it
);
387 ExtensionResource resource
= extension
->GetResource(*it
);
388 script_
.css_scripts().push_back(UserScript::File(
389 resource
.extension_root(), resource
.relative_path(), url
));
391 for (std::vector
<std::string
>::const_iterator it
=
392 script_data
.js_file_names
.begin();
393 it
!= script_data
.js_file_names
.end(); ++it
) {
394 GURL url
= extension
->GetResourceURL(*it
);
395 ExtensionResource resource
= extension
->GetResource(*it
);
396 script_
.js_scripts().push_back(UserScript::File(
397 resource
.extension_root(), resource
.relative_path(), url
));
401 ContentAction::Type
RequestContentScript::GetType() const {
402 return ACTION_REQUEST_CONTENT_SCRIPT
;
405 void RequestContentScript::Apply(const std::string
& extension_id
,
406 const base::Time
& extension_install_time
,
407 ApplyInfo
* apply_info
) const {
408 InstructRenderProcessToInject(apply_info
->tab
, extension_id
);
411 void RequestContentScript::Reapply(const std::string
& extension_id
,
412 const base::Time
& extension_install_time
,
413 ApplyInfo
* apply_info
) const {
414 InstructRenderProcessToInject(apply_info
->tab
, extension_id
);
417 void RequestContentScript::Revert(const std::string
& extension_id
,
418 const base::Time
& extension_install_time
,
419 ApplyInfo
* apply_info
) const {}
421 void RequestContentScript::InstructRenderProcessToInject(
422 content::WebContents
* contents
,
423 const std::string
& extension_id
) const {
424 content::RenderViewHost
* render_view_host
= contents
->GetRenderViewHost();
425 render_view_host
->Send(new ExtensionMsg_ExecuteDeclarativeScript(
426 render_view_host
->GetRoutingID(),
427 SessionTabHelper::IdForTab(contents
),
430 contents
->GetLastCommittedURL()));
434 scoped_refptr
<ContentAction
> SetIcon::Create(
435 content::BrowserContext
* browser_context
,
436 const Extension
* extension
,
437 const base::DictionaryValue
* dict
,
440 // We can't set a page or action's icon if the extension doesn't have one.
441 ActionInfo::Type type
;
442 if (ActionInfo::GetPageActionInfo(extension
) != NULL
) {
443 type
= ActionInfo::TYPE_PAGE
;
444 } else if (ActionInfo::GetBrowserActionInfo(extension
) != NULL
) {
445 type
= ActionInfo::TYPE_BROWSER
;
447 *error
= kNoPageOrBrowserAction
;
448 return scoped_refptr
<ContentAction
>();
452 const base::DictionaryValue
* canvas_set
= NULL
;
453 if (dict
->GetDictionary("imageData", &canvas_set
) &&
454 !ExtensionAction::ParseIconFromCanvasDictionary(*canvas_set
, &icon
)) {
455 *error
= kInvalidIconDictionary
;
457 return scoped_refptr
<ContentAction
>();
459 return scoped_refptr
<ContentAction
>(new SetIcon(gfx::Image(icon
), type
));
466 ContentAction::ContentAction() {}
468 ContentAction::~ContentAction() {}
471 scoped_refptr
<ContentAction
> ContentAction::Create(
472 content::BrowserContext
* browser_context
,
473 const Extension
* extension
,
474 const base::Value
& json_action
,
477 ResetErrorData(error
, bad_message
);
478 const base::DictionaryValue
* action_dict
= NULL
;
479 std::string instance_type
;
480 if (!Validate(json_action
, error
, bad_message
, &action_dict
, &instance_type
))
481 return scoped_refptr
<ContentAction
>();
483 ContentActionFactory
& factory
= g_content_action_factory
.Get();
484 std::map
<std::string
, ContentActionFactory::FactoryMethod
>::iterator
485 factory_method_iter
= factory
.factory_methods
.find(instance_type
);
486 if (factory_method_iter
!= factory
.factory_methods
.end())
487 return (*factory_method_iter
->second
)(
488 browser_context
, extension
, action_dict
, error
, bad_message
);
490 *error
= base::StringPrintf(kInvalidInstanceTypeError
, instance_type
.c_str());
491 return scoped_refptr
<ContentAction
>();
494 bool ContentAction::Validate(const base::Value
& json_action
,
497 const base::DictionaryValue
** action_dict
,
498 std::string
* instance_type
) {
499 INPUT_FORMAT_VALIDATE(json_action
.GetAsDictionary(action_dict
));
500 INPUT_FORMAT_VALIDATE(
501 (*action_dict
)->GetString(keys::kInstanceType
, instance_type
));
505 } // namespace extensions