30a7675f48f151641c29a197783e6903d1265b2b
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / declarative_content / content_action.cc
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.
4
5 #include "chrome/browser/extensions/api/declarative_content/content_action.h"
6
7 #include <map>
8
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"
28
29 namespace extensions {
30
31 namespace keys = declarative_content_constants;
32
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";
45
46 #define INPUT_FORMAT_VALIDATE(test) do { \
47     if (!(test)) { \
48       *bad_message = true; \
49       return false; \
50     } \
51   } while (0)
52
53 //
54 // The following are concrete actions.
55 //
56
57 // Action that instructs to show an extension's page action.
58 class ShowPageAction : public ContentAction {
59  public:
60   ShowPageAction() {}
61
62   static scoped_refptr<ContentAction> Create(
63       content::BrowserContext* browser_context,
64       const Extension* extension,
65       const base::DictionaryValue* dict,
66       std::string* error,
67       bool* bad_message) {
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>();
72     }
73     return scoped_refptr<ContentAction>(new ShowPageAction);
74   }
75
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);
86   }
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);
99     }
100   }
101
102  private:
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);
109     if (!extension)
110       return NULL;
111     return ExtensionActionManager::Get(browser_context)
112         ->GetPageAction(*extension);
113   }
114   virtual ~ShowPageAction() {}
115
116   DISALLOW_COPY_AND_ASSIGN(ShowPageAction);
117 };
118
119 // Action that sets an extension's action icon.
120 class SetIcon : public ContentAction {
121  public:
122   SetIcon(const gfx::Image& icon, ActionInfo::Type action_type)
123       : icon_(icon), action_type_(action_type) {}
124
125   static scoped_refptr<ContentAction> Create(
126       content::BrowserContext* browser_context,
127       const Extension* extension,
128       const base::DictionaryValue* dict,
129       std::string* error,
130       bool* bad_message);
131
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);
139     if (action) {
140       action->DeclarativeSetIcon(ExtensionTabUtil::GetTabId(apply_info->tab),
141                                  apply_info->priority,
142                                  icon_);
143       ExtensionActionAPI::Get(profile)
144           ->NotifyChange(action, apply_info->tab, profile);
145     }
146   }
147
148   virtual void Reapply(const std::string& extension_id,
149                        const base::Time& extension_install_time,
150                        ApplyInfo* apply_info) const OVERRIDE {}
151
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);
157     if (action) {
158       action->UndoDeclarativeSetIcon(
159           ExtensionTabUtil::GetTabId(apply_info->tab),
160           apply_info->priority,
161           icon_);
162       ExtensionActionAPI::Get(apply_info->browser_context)
163           ->NotifyChange(action, apply_info->tab, profile);
164     }
165   }
166
167  private:
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);
173     if (!extension)
174       return NULL;
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);
181       default:
182         NOTREACHED();
183     }
184     return NULL;
185   }
186   virtual ~SetIcon() {}
187
188   gfx::Image icon_;
189   ActionInfo::Type action_type_;
190
191   DISALLOW_COPY_AND_ASSIGN(SetIcon);
192 };
193
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();
199        ++it) {
200     std::string value;
201     if ((*it)->GetAsString(&value)) {
202       append_to->push_back(value);
203     } else {
204       return false;
205     }
206   }
207
208   return true;
209 }
210
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;
227
228   ContentActionFactory() {
229     factory_methods[keys::kShowPageAction] =
230         &ShowPageAction::Create;
231     factory_methods[keys::kRequestContentScript] =
232         &RequestContentScript::Create;
233     factory_methods[keys::kSetIcon] =
234         &SetIcon::Create;
235   }
236 };
237
238 base::LazyInstance<ContentActionFactory>::Leaky
239     g_content_action_factory = LAZY_INSTANCE_INITIALIZER;
240
241 }  // namespace
242
243 //
244 // RequestContentScript
245 //
246
247 struct RequestContentScript::ScriptData {
248   ScriptData();
249   ~ScriptData();
250
251   std::vector<std::string> css_file_names;
252   std::vector<std::string> js_file_names;
253   bool all_frames;
254   bool match_about_blank;
255 };
256
257 RequestContentScript::ScriptData::ScriptData()
258     : all_frames(false),
259       match_about_blank(false) {}
260 RequestContentScript::ScriptData::~ScriptData() {}
261
262 // static
263 scoped_refptr<ContentAction> RequestContentScript::Create(
264     content::BrowserContext* browser_context,
265     const Extension* extension,
266     const base::DictionaryValue* dict,
267     std::string* error,
268     bool* bad_message) {
269   ScriptData script_data;
270   if (!InitScriptData(dict, error, bad_message, &script_data))
271     return scoped_refptr<ContentAction>();
272
273   return scoped_refptr<ContentAction>(new RequestContentScript(
274       browser_context,
275       extension,
276       script_data));
277 }
278
279 // static
280 scoped_refptr<ContentAction> RequestContentScript::CreateForTest(
281     DeclarativeUserScriptMaster* master,
282     const Extension* extension,
283     const base::Value& json_action,
284     std::string* error,
285     bool* bad_message) {
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(
292           json_action,
293           error,
294           bad_message,
295           &action_dict,
296           &instance_type) ||
297       instance_type != std::string(keys::kRequestContentScript))
298     return scoped_refptr<ContentAction>();
299
300   // Normal RequestContentScript data initialization.
301   ScriptData script_data;
302   if (!InitScriptData(action_dict, error, bad_message, &script_data))
303     return scoped_refptr<ContentAction>();
304
305   // Inject provided DeclarativeUserScriptMaster, rather than looking it up
306   // using a BrowserContext.
307   return scoped_refptr<ContentAction>(new RequestContentScript(
308       master,
309       extension,
310       script_data));
311 }
312
313 // static
314 bool RequestContentScript::InitScriptData(const base::DictionaryValue* dict,
315                                           std::string* error,
316                                           bool* bad_message,
317                                           ScriptData* script_data) {
318   const base::ListValue* list_value = NULL;
319
320   if (!dict->HasKey(keys::kCss) && !dict->HasKey(keys::kJs)) {
321     *error = base::StringPrintf(kMissingParameter, "css or js");
322     return false;
323   }
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));
328   }
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));
333   }
334   if (dict->HasKey(keys::kAllFrames)) {
335     INPUT_FORMAT_VALIDATE(
336         dict->GetBoolean(keys::kAllFrames, &script_data->all_frames));
337   }
338   if (dict->HasKey(keys::kMatchAboutBlank)) {
339     INPUT_FORMAT_VALIDATE(
340         dict->GetBoolean(
341             keys::kMatchAboutBlank,
342             &script_data->match_about_blank));
343   }
344
345   return true;
346 }
347
348 RequestContentScript::RequestContentScript(
349     content::BrowserContext* browser_context,
350     const Extension* extension,
351     const ScriptData& script_data) {
352   InitScript(extension, script_data);
353
354   master_ =
355       ExtensionSystem::Get(browser_context)->
356       GetDeclarativeUserScriptMasterByExtension(extension->id());
357   AddScript();
358 }
359
360 RequestContentScript::RequestContentScript(
361     DeclarativeUserScriptMaster* master,
362     const Extension* extension,
363     const ScriptData& script_data) {
364   InitScript(extension, script_data);
365
366   master_ = master;
367   AddScript();
368 }
369
370 RequestContentScript::~RequestContentScript() {
371   DCHECK(master_);
372   master_->RemoveScript(script_);
373 }
374
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));
389   }
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));
397   }
398 }
399
400 ContentAction::Type RequestContentScript::GetType() const {
401   return ACTION_REQUEST_CONTENT_SCRIPT;
402 }
403
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);
408 }
409
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);
414 }
415
416 void RequestContentScript::Revert(const std::string& extension_id,
417                       const base::Time& extension_install_time,
418                       ApplyInfo* apply_info) const {}
419
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),
427       extension_id,
428       script_.id(),
429       contents->GetLastCommittedURL()));
430 }
431
432 // static
433 scoped_refptr<ContentAction> SetIcon::Create(
434     content::BrowserContext* browser_context,
435     const Extension* extension,
436     const base::DictionaryValue* dict,
437     std::string* error,
438     bool* bad_message) {
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;
445   } else {
446     *error = kNoPageOrBrowserAction;
447     return scoped_refptr<ContentAction>();
448   }
449
450   gfx::ImageSkia icon;
451   const base::DictionaryValue* canvas_set = NULL;
452   if (dict->GetDictionary("imageData", &canvas_set) &&
453       !ExtensionAction::ParseIconFromCanvasDictionary(*canvas_set, &icon)) {
454     *error = kInvalidIconDictionary;
455     *bad_message = true;
456     return scoped_refptr<ContentAction>();
457   }
458   return scoped_refptr<ContentAction>(new SetIcon(gfx::Image(icon), type));
459 }
460
461 //
462 // ContentAction
463 //
464
465 ContentAction::ContentAction() {}
466
467 ContentAction::~ContentAction() {}
468
469 // static
470 scoped_refptr<ContentAction> ContentAction::Create(
471     content::BrowserContext* browser_context,
472     const Extension* extension,
473     const base::Value& json_action,
474     std::string* error,
475     bool* bad_message) {
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>();
481
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);
488
489   *error = base::StringPrintf(kInvalidInstanceTypeError, instance_type.c_str());
490   return scoped_refptr<ContentAction>();
491 }
492
493 bool ContentAction::Validate(const base::Value& json_action,
494                              std::string* error,
495                              bool* bad_message,
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));
501   return true;
502 }
503
504 }  // namespace extensions