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