Home | History | Annotate | Download | only in declarative_content
      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
    505