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/user_script_master.h" 6 7#include <map> 8#include <string> 9#include <vector> 10 11#include "base/bind.h" 12#include "base/file_util.h" 13#include "base/files/file_path.h" 14#include "base/pickle.h" 15#include "base/stl_util.h" 16#include "base/strings/string_util.h" 17#include "base/threading/thread.h" 18#include "base/version.h" 19#include "chrome/browser/chrome_notification_types.h" 20#include "chrome/browser/extensions/extension_service.h" 21#include "chrome/browser/extensions/extension_system.h" 22#include "chrome/browser/extensions/extension_util.h" 23#include "chrome/browser/extensions/image_loader.h" 24#include "chrome/browser/profiles/profile.h" 25#include "chrome/common/extensions/api/i18n/default_locale_handler.h" 26#include "chrome/common/extensions/extension_file_util.h" 27#include "chrome/common/extensions/extension_set.h" 28#include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h" 29#include "chrome/common/extensions/message_bundle.h" 30#include "content/public/browser/notification_service.h" 31#include "content/public/browser/render_process_host.h" 32#include "extensions/common/extension.h" 33#include "extensions/common/extension_resource.h" 34#include "ui/base/resource/resource_bundle.h" 35 36using content::BrowserThread; 37 38namespace extensions { 39 40// Helper function to parse greasesmonkey headers 41static bool GetDeclarationValue(const base::StringPiece& line, 42 const base::StringPiece& prefix, 43 std::string* value) { 44 base::StringPiece::size_type index = line.find(prefix); 45 if (index == base::StringPiece::npos) 46 return false; 47 48 std::string temp(line.data() + index + prefix.length(), 49 line.length() - index - prefix.length()); 50 51 if (temp.empty() || !IsWhitespace(temp[0])) 52 return false; 53 54 TrimWhitespaceASCII(temp, TRIM_ALL, value); 55 return true; 56} 57 58UserScriptMaster::ScriptReloader::ScriptReloader(UserScriptMaster* master) 59 : master_(master) { 60 CHECK(BrowserThread::GetCurrentThreadIdentifier(&master_thread_id_)); 61} 62 63// static 64bool UserScriptMaster::ScriptReloader::ParseMetadataHeader( 65 const base::StringPiece& script_text, UserScript* script) { 66 // http://wiki.greasespot.net/Metadata_block 67 base::StringPiece line; 68 size_t line_start = 0; 69 size_t line_end = line_start; 70 bool in_metadata = false; 71 72 static const base::StringPiece kUserScriptBegin("// ==UserScript=="); 73 static const base::StringPiece kUserScriptEng("// ==/UserScript=="); 74 static const base::StringPiece kNamespaceDeclaration("// @namespace"); 75 static const base::StringPiece kNameDeclaration("// @name"); 76 static const base::StringPiece kVersionDeclaration("// @version"); 77 static const base::StringPiece kDescriptionDeclaration("// @description"); 78 static const base::StringPiece kIncludeDeclaration("// @include"); 79 static const base::StringPiece kExcludeDeclaration("// @exclude"); 80 static const base::StringPiece kMatchDeclaration("// @match"); 81 static const base::StringPiece kExcludeMatchDeclaration("// @exclude_match"); 82 static const base::StringPiece kRunAtDeclaration("// @run-at"); 83 static const base::StringPiece kRunAtDocumentStartValue("document-start"); 84 static const base::StringPiece kRunAtDocumentEndValue("document-end"); 85 static const base::StringPiece kRunAtDocumentIdleValue("document-idle"); 86 87 while (line_start < script_text.length()) { 88 line_end = script_text.find('\n', line_start); 89 90 // Handle the case where there is no trailing newline in the file. 91 if (line_end == std::string::npos) 92 line_end = script_text.length() - 1; 93 94 line.set(script_text.data() + line_start, line_end - line_start); 95 96 if (!in_metadata) { 97 if (line.starts_with(kUserScriptBegin)) 98 in_metadata = true; 99 } else { 100 if (line.starts_with(kUserScriptEng)) 101 break; 102 103 std::string value; 104 if (GetDeclarationValue(line, kIncludeDeclaration, &value)) { 105 // We escape some characters that MatchPattern() considers special. 106 ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\"); 107 ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?"); 108 script->add_glob(value); 109 } else if (GetDeclarationValue(line, kExcludeDeclaration, &value)) { 110 ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\"); 111 ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?"); 112 script->add_exclude_glob(value); 113 } else if (GetDeclarationValue(line, kNamespaceDeclaration, &value)) { 114 script->set_name_space(value); 115 } else if (GetDeclarationValue(line, kNameDeclaration, &value)) { 116 script->set_name(value); 117 } else if (GetDeclarationValue(line, kVersionDeclaration, &value)) { 118 Version version(value); 119 if (version.IsValid()) 120 script->set_version(version.GetString()); 121 } else if (GetDeclarationValue(line, kDescriptionDeclaration, &value)) { 122 script->set_description(value); 123 } else if (GetDeclarationValue(line, kMatchDeclaration, &value)) { 124 URLPattern pattern(UserScript::ValidUserScriptSchemes()); 125 if (URLPattern::PARSE_SUCCESS != pattern.Parse(value)) 126 return false; 127 script->add_url_pattern(pattern); 128 } else if (GetDeclarationValue(line, kExcludeMatchDeclaration, &value)) { 129 URLPattern exclude(UserScript::ValidUserScriptSchemes()); 130 if (URLPattern::PARSE_SUCCESS != exclude.Parse(value)) 131 return false; 132 script->add_exclude_url_pattern(exclude); 133 } else if (GetDeclarationValue(line, kRunAtDeclaration, &value)) { 134 if (value == kRunAtDocumentStartValue) 135 script->set_run_location(UserScript::DOCUMENT_START); 136 else if (value == kRunAtDocumentEndValue) 137 script->set_run_location(UserScript::DOCUMENT_END); 138 else if (value == kRunAtDocumentIdleValue) 139 script->set_run_location(UserScript::DOCUMENT_IDLE); 140 else 141 return false; 142 } 143 144 // TODO(aa): Handle more types of metadata. 145 } 146 147 line_start = line_end + 1; 148 } 149 150 // If no patterns were specified, default to @include *. This is what 151 // Greasemonkey does. 152 if (script->globs().empty() && script->url_patterns().is_empty()) 153 script->add_glob("*"); 154 155 return true; 156} 157 158void UserScriptMaster::ScriptReloader::StartLoad( 159 const UserScriptList& user_scripts, 160 const ExtensionsInfo& extensions_info_) { 161 // Add a reference to ourselves to keep ourselves alive while we're running. 162 // Balanced by NotifyMaster(). 163 AddRef(); 164 165 this->extensions_info_ = extensions_info_; 166 BrowserThread::PostTask( 167 BrowserThread::FILE, FROM_HERE, 168 base::Bind( 169 &UserScriptMaster::ScriptReloader::RunLoad, this, user_scripts)); 170} 171 172UserScriptMaster::ScriptReloader::~ScriptReloader() {} 173 174void UserScriptMaster::ScriptReloader::NotifyMaster( 175 base::SharedMemory* memory) { 176 // The master went away, so these new scripts aren't useful anymore. 177 if (!master_) 178 delete memory; 179 else 180 master_->NewScriptsAvailable(memory); 181 182 // Drop our self-reference. 183 // Balances StartLoad(). 184 Release(); 185} 186 187static bool LoadScriptContent(UserScript::File* script_file, 188 const SubstitutionMap* localization_messages) { 189 std::string content; 190 const base::FilePath& path = ExtensionResource::GetFilePath( 191 script_file->extension_root(), script_file->relative_path(), 192 ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT); 193 if (path.empty()) { 194 int resource_id; 195 if (extensions::ImageLoader::IsComponentExtensionResource( 196 script_file->extension_root(), script_file->relative_path(), 197 &resource_id)) { 198 const ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 199 content = rb.GetRawDataResource(resource_id).as_string(); 200 } else { 201 LOG(WARNING) << "Failed to get file path to " 202 << script_file->relative_path().value() << " from " 203 << script_file->extension_root().value(); 204 return false; 205 } 206 } else { 207 if (!base::ReadFileToString(path, &content)) { 208 LOG(WARNING) << "Failed to load user script file: " << path.value(); 209 return false; 210 } 211 } 212 213 // Localize the content. 214 if (localization_messages) { 215 std::string error; 216 MessageBundle::ReplaceMessagesWithExternalDictionary( 217 *localization_messages, &content, &error); 218 if (!error.empty()) { 219 LOG(WARNING) << "Failed to replace messages in script: " << error; 220 } 221 } 222 223 // Remove BOM from the content. 224 std::string::size_type index = content.find(base::kUtf8ByteOrderMark); 225 if (index == 0) { 226 script_file->set_content(content.substr(strlen(base::kUtf8ByteOrderMark))); 227 } else { 228 script_file->set_content(content); 229 } 230 231 return true; 232} 233 234void UserScriptMaster::ScriptReloader::LoadUserScripts( 235 UserScriptList* user_scripts) { 236 for (size_t i = 0; i < user_scripts->size(); ++i) { 237 UserScript& script = user_scripts->at(i); 238 scoped_ptr<SubstitutionMap> localization_messages( 239 GetLocalizationMessages(script.extension_id())); 240 for (size_t k = 0; k < script.js_scripts().size(); ++k) { 241 UserScript::File& script_file = script.js_scripts()[k]; 242 if (script_file.GetContent().empty()) 243 LoadScriptContent(&script_file, NULL); 244 } 245 for (size_t k = 0; k < script.css_scripts().size(); ++k) { 246 UserScript::File& script_file = script.css_scripts()[k]; 247 if (script_file.GetContent().empty()) 248 LoadScriptContent(&script_file, localization_messages.get()); 249 } 250 } 251} 252 253SubstitutionMap* UserScriptMaster::ScriptReloader::GetLocalizationMessages( 254 std::string extension_id) { 255 if (extensions_info_.find(extension_id) == extensions_info_.end()) { 256 return NULL; 257 } 258 259 return extension_file_util::LoadMessageBundleSubstitutionMap( 260 extensions_info_[extension_id].first, 261 extension_id, 262 extensions_info_[extension_id].second); 263} 264 265// Pickle user scripts and return pointer to the shared memory. 266static base::SharedMemory* Serialize(const UserScriptList& scripts) { 267 Pickle pickle; 268 pickle.WriteUInt64(scripts.size()); 269 for (size_t i = 0; i < scripts.size(); i++) { 270 const UserScript& script = scripts[i]; 271 // TODO(aa): This can be replaced by sending content script metadata to 272 // renderers along with other extension data in ExtensionMsg_Loaded. 273 // See crbug.com/70516. 274 script.Pickle(&pickle); 275 // Write scripts as 'data' so that we can read it out in the slave without 276 // allocating a new string. 277 for (size_t j = 0; j < script.js_scripts().size(); j++) { 278 base::StringPiece contents = script.js_scripts()[j].GetContent(); 279 pickle.WriteData(contents.data(), contents.length()); 280 } 281 for (size_t j = 0; j < script.css_scripts().size(); j++) { 282 base::StringPiece contents = script.css_scripts()[j].GetContent(); 283 pickle.WriteData(contents.data(), contents.length()); 284 } 285 } 286 287 // Create the shared memory object. 288 base::SharedMemory shared_memory; 289 290 if (!shared_memory.CreateAndMapAnonymous(pickle.size())) 291 return NULL; 292 293 // Copy the pickle to shared memory. 294 memcpy(shared_memory.memory(), pickle.data(), pickle.size()); 295 296 base::SharedMemoryHandle readonly_handle; 297 if (!shared_memory.ShareReadOnlyToProcess(base::GetCurrentProcessHandle(), 298 &readonly_handle)) 299 return NULL; 300 301 return new base::SharedMemory(readonly_handle, /*read_only=*/true); 302} 303 304// This method will be called on the file thread. 305void UserScriptMaster::ScriptReloader::RunLoad( 306 const UserScriptList& user_scripts) { 307 LoadUserScripts(const_cast<UserScriptList*>(&user_scripts)); 308 309 // Scripts now contains list of up-to-date scripts. Load the content in the 310 // shared memory and let the master know it's ready. We need to post the task 311 // back even if no scripts ware found to balance the AddRef/Release calls. 312 BrowserThread::PostTask( 313 master_thread_id_, FROM_HERE, 314 base::Bind( 315 &ScriptReloader::NotifyMaster, this, Serialize(user_scripts))); 316} 317 318 319UserScriptMaster::UserScriptMaster(Profile* profile) 320 : extensions_service_ready_(false), 321 pending_load_(false), 322 profile_(profile) { 323 registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY, 324 content::Source<Profile>(profile_)); 325 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, 326 content::Source<Profile>(profile_)); 327 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, 328 content::Source<Profile>(profile_)); 329 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED, 330 content::NotificationService::AllBrowserContextsAndSources()); 331} 332 333UserScriptMaster::~UserScriptMaster() { 334 if (script_reloader_.get()) 335 script_reloader_->DisownMaster(); 336} 337 338void UserScriptMaster::NewScriptsAvailable(base::SharedMemory* handle) { 339 // Ensure handle is deleted or released. 340 scoped_ptr<base::SharedMemory> handle_deleter(handle); 341 342 if (pending_load_) { 343 // While we were loading, there were further changes. Don't bother 344 // notifying about these scripts and instead just immediately reload. 345 pending_load_ = false; 346 StartLoad(); 347 } else { 348 // We're no longer loading. 349 script_reloader_ = NULL; 350 // We've got scripts ready to go. 351 shared_memory_.swap(handle_deleter); 352 353 for (content::RenderProcessHost::iterator i( 354 content::RenderProcessHost::AllHostsIterator()); 355 !i.IsAtEnd(); i.Advance()) { 356 SendUpdate(i.GetCurrentValue(), handle); 357 } 358 359 content::NotificationService::current()->Notify( 360 chrome::NOTIFICATION_USER_SCRIPTS_UPDATED, 361 content::Source<Profile>(profile_), 362 content::Details<base::SharedMemory>(handle)); 363 } 364} 365 366void UserScriptMaster::Observe(int type, 367 const content::NotificationSource& source, 368 const content::NotificationDetails& details) { 369 bool should_start_load = false; 370 switch (type) { 371 case chrome::NOTIFICATION_EXTENSIONS_READY: 372 extensions_service_ready_ = true; 373 should_start_load = true; 374 break; 375 case chrome::NOTIFICATION_EXTENSION_LOADED: { 376 // Add any content scripts inside the extension. 377 const Extension* extension = 378 content::Details<const Extension>(details).ptr(); 379 extensions_info_[extension->id()] = 380 ExtensionSet::ExtensionPathAndDefaultLocale( 381 extension->path(), LocaleInfo::GetDefaultLocale(extension)); 382 bool incognito_enabled = extension_util::IsIncognitoEnabled( 383 extension->id(), 384 extensions::ExtensionSystem::Get(profile_)->extension_service()); 385 const UserScriptList& scripts = 386 ContentScriptsInfo::GetContentScripts(extension); 387 for (UserScriptList::const_iterator iter = scripts.begin(); 388 iter != scripts.end(); ++iter) { 389 user_scripts_.push_back(*iter); 390 user_scripts_.back().set_incognito_enabled(incognito_enabled); 391 } 392 if (extensions_service_ready_) 393 should_start_load = true; 394 break; 395 } 396 case chrome::NOTIFICATION_EXTENSION_UNLOADED: { 397 // Remove any content scripts. 398 const Extension* extension = 399 content::Details<UnloadedExtensionInfo>(details)->extension; 400 extensions_info_.erase(extension->id()); 401 UserScriptList new_user_scripts; 402 for (UserScriptList::iterator iter = user_scripts_.begin(); 403 iter != user_scripts_.end(); ++iter) { 404 if (iter->extension_id() != extension->id()) 405 new_user_scripts.push_back(*iter); 406 } 407 user_scripts_ = new_user_scripts; 408 should_start_load = true; 409 break; 410 } 411 case content::NOTIFICATION_RENDERER_PROCESS_CREATED: { 412 content::RenderProcessHost* process = 413 content::Source<content::RenderProcessHost>(source).ptr(); 414 Profile* profile = Profile::FromBrowserContext( 415 process->GetBrowserContext()); 416 if (!profile_->IsSameProfile(profile)) 417 return; 418 if (ScriptsReady()) 419 SendUpdate(process, GetSharedMemory()); 420 break; 421 } 422 default: 423 DCHECK(false); 424 } 425 426 if (should_start_load) { 427 if (script_reloader_.get()) { 428 pending_load_ = true; 429 } else { 430 StartLoad(); 431 } 432 } 433} 434 435void UserScriptMaster::StartLoad() { 436 if (!script_reloader_.get()) 437 script_reloader_ = new ScriptReloader(this); 438 439 script_reloader_->StartLoad(user_scripts_, extensions_info_); 440} 441 442void UserScriptMaster::SendUpdate(content::RenderProcessHost* process, 443 base::SharedMemory* shared_memory) { 444 // Don't allow injection of content scripts into <webview>. 445 if (process->IsGuest()) 446 return; 447 448 Profile* profile = Profile::FromBrowserContext(process->GetBrowserContext()); 449 // Make sure we only send user scripts to processes in our profile. 450 if (!profile_->IsSameProfile(profile)) 451 return; 452 453 // If the process is being started asynchronously, early return. We'll end up 454 // calling InitUserScripts when it's created which will call this again. 455 base::ProcessHandle handle = process->GetHandle(); 456 if (!handle) 457 return; 458 459 base::SharedMemoryHandle handle_for_process; 460 if (!shared_memory->ShareToProcess(handle, &handle_for_process)) 461 return; // This can legitimately fail if the renderer asserts at startup. 462 463 if (base::SharedMemory::IsHandleValid(handle_for_process)) 464 process->Send(new ExtensionMsg_UpdateUserScripts(handle_for_process)); 465} 466 467} // namespace extensions 468