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