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_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
;
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";
47 // The following are concrete actions.
50 // Action that instructs to show an extension's page action.
51 class ShowPageAction
: public ContentAction
{
54 ~ShowPageAction() override
{}
56 static scoped_ptr
<ContentAction
> Create(
57 content::BrowserContext
* browser_context
,
58 const Extension
* extension
,
59 const base::DictionaryValue
* dict
,
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
);
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
{
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
,
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
);
118 action
->DeclarativeSetIcon(ExtensionTabUtil::GetTabId(apply_info
.tab
),
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
);
133 action
->UndoDeclarativeSetIcon(
134 ExtensionTabUtil::GetTabId(apply_info
.tab
),
137 ExtensionActionAPI::Get(apply_info
.browser_context
)
138 ->NotifyChange(action
, apply_info
.tab
, profile
);
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
);
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();
170 if ((*it
)->GetAsString(&value
)) {
171 append_to
->push_back(value
);
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
] =
203 base::LazyInstance
<ContentActionFactory
>::Leaky
204 g_content_action_factory
= LAZY_INSTANCE_INITIALIZER
;
209 // RequestContentScript
212 struct RequestContentScript::ScriptData
{
216 std::vector
<std::string
> css_file_names
;
217 std::vector
<std::string
> js_file_names
;
219 bool match_about_blank
;
222 RequestContentScript::ScriptData::ScriptData()
224 match_about_blank(false) {}
225 RequestContentScript::ScriptData::~ScriptData() {}
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
,
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.
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
,
269 bool RequestContentScript::InitScriptData(const base::DictionaryValue
* dict
,
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");
278 if (dict
->HasKey(keys::kCss
)) {
279 if (!dict
->GetList(keys::kCss
, &list_value
) ||
280 !AppendJSStringsToCPPStrings(*list_value
,
281 &script_data
->css_file_names
)) {
285 if (dict
->HasKey(keys::kJs
)) {
286 if (!dict
->GetList(keys::kJs
, &list_value
) ||
287 !AppendJSStringsToCPPStrings(*list_value
,
288 &script_data
->js_file_names
)) {
292 if (dict
->HasKey(keys::kAllFrames
)) {
293 if (!dict
->GetBoolean(keys::kAllFrames
, &script_data
->all_frames
))
296 if (dict
->HasKey(keys::kMatchAboutBlank
)) {
297 if (!dict
->GetBoolean(keys::kMatchAboutBlank
,
298 &script_data
->match_about_blank
)) {
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
);
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
);
329 RequestContentScript::~RequestContentScript() {
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
),
379 contents
->GetLastCommittedURL()));
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
;
395 *error
= kNoPageOrBrowserAction
;
396 return scoped_ptr
<ContentAction
>();
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
));
413 ContentAction::~ContentAction() {}
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
) {
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