| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 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_loader.h" | |
| 6 | |
| 7 #include <set> | |
| 8 #include <string> | |
| 9 | |
| 10 #include "base/bind.h" | |
| 11 #include "base/bind_helpers.h" | |
| 12 #include "base/files/file_path.h" | |
| 13 #include "base/files/file_util.h" | |
| 14 #include "base/version.h" | |
| 15 #include "chrome/browser/chrome_notification_types.h" | |
| 16 #include "chrome/browser/profiles/profile.h" | |
| 17 #include "content/public/browser/browser_thread.h" | |
| 18 #include "content/public/browser/notification_service.h" | |
| 19 #include "content/public/browser/render_process_host.h" | |
| 20 #include "extensions/browser/content_verifier.h" | |
| 21 #include "extensions/common/extension_messages.h" | |
| 22 #include "extensions/common/file_util.h" | |
| 23 | |
| 24 using content::BrowserThread; | |
| 25 | |
| 26 namespace extensions { | |
| 27 | |
| 28 namespace { | |
| 29 | |
| 30 using LoadScriptsCallback = | |
| 31 base::Callback<void(scoped_ptr<UserScriptList>, | |
| 32 scoped_ptr<base::SharedMemory>)>; | |
| 33 | |
| 34 UserScriptLoader::SubstitutionMap* GetLocalizationMessages( | |
| 35 const UserScriptLoader::HostsInfo& hosts_info, | |
| 36 const HostID& host_id) { | |
| 37 UserScriptLoader::HostsInfo::const_iterator iter = hosts_info.find(host_id); | |
| 38 if (iter == hosts_info.end()) | |
| 39 return nullptr; | |
| 40 return file_util::LoadMessageBundleSubstitutionMap( | |
| 41 iter->second.first, host_id.id(), iter->second.second); | |
| 42 } | |
| 43 | |
| 44 void LoadUserScripts( | |
| 45 UserScriptList* user_scripts, | |
| 46 const UserScriptLoader::HostsInfo& hosts_info, | |
| 47 const std::set<int>& added_script_ids, | |
| 48 const scoped_refptr<ContentVerifier>& verifier, | |
| 49 UserScriptLoader::LoadUserScriptsContentFunction callback) { | |
| 50 for (UserScriptList::iterator script = user_scripts->begin(); | |
| 51 script != user_scripts->end(); | |
| 52 ++script) { | |
| 53 if (added_script_ids.count(script->id()) == 0) | |
| 54 continue; | |
| 55 scoped_ptr<UserScriptLoader::SubstitutionMap> localization_messages( | |
| 56 GetLocalizationMessages(hosts_info, script->host_id())); | |
| 57 for (size_t k = 0; k < script->js_scripts().size(); ++k) { | |
| 58 UserScript::File& script_file = script->js_scripts()[k]; | |
| 59 if (script_file.GetContent().empty()) | |
| 60 callback.Run(script->host_id(), &script_file, NULL, verifier); | |
| 61 } | |
| 62 for (size_t k = 0; k < script->css_scripts().size(); ++k) { | |
| 63 UserScript::File& script_file = script->css_scripts()[k]; | |
| 64 if (script_file.GetContent().empty()) | |
| 65 callback.Run(script->host_id(), &script_file, | |
| 66 localization_messages.get(), verifier); | |
| 67 } | |
| 68 } | |
| 69 } | |
| 70 | |
| 71 // Pickle user scripts and return pointer to the shared memory. | |
| 72 scoped_ptr<base::SharedMemory> Serialize(const UserScriptList& scripts) { | |
| 73 Pickle pickle; | |
| 74 pickle.WriteSizeT(scripts.size()); | |
| 75 for (UserScriptList::const_iterator script = scripts.begin(); | |
| 76 script != scripts.end(); | |
| 77 ++script) { | |
| 78 // TODO(aa): This can be replaced by sending content script metadata to | |
| 79 // renderers along with other extension data in ExtensionMsg_Loaded. | |
| 80 // See crbug.com/70516. | |
| 81 script->Pickle(&pickle); | |
| 82 // Write scripts as 'data' so that we can read it out in the slave without | |
| 83 // allocating a new string. | |
| 84 for (size_t j = 0; j < script->js_scripts().size(); j++) { | |
| 85 base::StringPiece contents = script->js_scripts()[j].GetContent(); | |
| 86 pickle.WriteData(contents.data(), contents.length()); | |
| 87 } | |
| 88 for (size_t j = 0; j < script->css_scripts().size(); j++) { | |
| 89 base::StringPiece contents = script->css_scripts()[j].GetContent(); | |
| 90 pickle.WriteData(contents.data(), contents.length()); | |
| 91 } | |
| 92 } | |
| 93 | |
| 94 // Create the shared memory object. | |
| 95 base::SharedMemory shared_memory; | |
| 96 | |
| 97 base::SharedMemoryCreateOptions options; | |
| 98 options.size = pickle.size(); | |
| 99 options.share_read_only = true; | |
| 100 if (!shared_memory.Create(options)) | |
| 101 return scoped_ptr<base::SharedMemory>(); | |
| 102 | |
| 103 if (!shared_memory.Map(pickle.size())) | |
| 104 return scoped_ptr<base::SharedMemory>(); | |
| 105 | |
| 106 // Copy the pickle to shared memory. | |
| 107 memcpy(shared_memory.memory(), pickle.data(), pickle.size()); | |
| 108 | |
| 109 base::SharedMemoryHandle readonly_handle; | |
| 110 if (!shared_memory.ShareReadOnlyToProcess(base::GetCurrentProcessHandle(), | |
| 111 &readonly_handle)) | |
| 112 return scoped_ptr<base::SharedMemory>(); | |
| 113 | |
| 114 return make_scoped_ptr(new base::SharedMemory(readonly_handle, | |
| 115 /*read_only=*/true)); | |
| 116 } | |
| 117 | |
| 118 void LoadScriptsOnFileThread( | |
| 119 scoped_ptr<UserScriptList> user_scripts, | |
| 120 const UserScriptLoader::HostsInfo& hosts_info, | |
| 121 const std::set<int>& added_script_ids, | |
| 122 const scoped_refptr<ContentVerifier>& verifier, | |
| 123 UserScriptLoader::LoadUserScriptsContentFunction function, | |
| 124 LoadScriptsCallback callback) { | |
| 125 DCHECK(user_scripts.get()); | |
| 126 LoadUserScripts(user_scripts.get(), hosts_info, added_script_ids, | |
| 127 verifier, function); | |
| 128 scoped_ptr<base::SharedMemory> memory = Serialize(*user_scripts); | |
| 129 BrowserThread::PostTask( | |
| 130 BrowserThread::UI, | |
| 131 FROM_HERE, | |
| 132 base::Bind(callback, base::Passed(&user_scripts), base::Passed(&memory))); | |
| 133 } | |
| 134 | |
| 135 // Helper function to parse greasesmonkey headers | |
| 136 bool GetDeclarationValue(const base::StringPiece& line, | |
| 137 const base::StringPiece& prefix, | |
| 138 std::string* value) { | |
| 139 base::StringPiece::size_type index = line.find(prefix); | |
| 140 if (index == base::StringPiece::npos) | |
| 141 return false; | |
| 142 | |
| 143 std::string temp(line.data() + index + prefix.length(), | |
| 144 line.length() - index - prefix.length()); | |
| 145 | |
| 146 if (temp.empty() || !IsWhitespace(temp[0])) | |
| 147 return false; | |
| 148 | |
| 149 base::TrimWhitespaceASCII(temp, base::TRIM_ALL, value); | |
| 150 return true; | |
| 151 } | |
| 152 | |
| 153 } // namespace | |
| 154 | |
| 155 // static | |
| 156 bool UserScriptLoader::ParseMetadataHeader(const base::StringPiece& script_text, | |
| 157 UserScript* script) { | |
| 158 // http://wiki.greasespot.net/Metadata_block | |
| 159 base::StringPiece line; | |
| 160 size_t line_start = 0; | |
| 161 size_t line_end = line_start; | |
| 162 bool in_metadata = false; | |
| 163 | |
| 164 static const base::StringPiece kUserScriptBegin("// ==UserScript=="); | |
| 165 static const base::StringPiece kUserScriptEng("// ==/UserScript=="); | |
| 166 static const base::StringPiece kNamespaceDeclaration("// @namespace"); | |
| 167 static const base::StringPiece kNameDeclaration("// @name"); | |
| 168 static const base::StringPiece kVersionDeclaration("// @version"); | |
| 169 static const base::StringPiece kDescriptionDeclaration("// @description"); | |
| 170 static const base::StringPiece kIncludeDeclaration("// @include"); | |
| 171 static const base::StringPiece kExcludeDeclaration("// @exclude"); | |
| 172 static const base::StringPiece kMatchDeclaration("// @match"); | |
| 173 static const base::StringPiece kExcludeMatchDeclaration("// @exclude_match"); | |
| 174 static const base::StringPiece kRunAtDeclaration("// @run-at"); | |
| 175 static const base::StringPiece kRunAtDocumentStartValue("document-start"); | |
| 176 static const base::StringPiece kRunAtDocumentEndValue("document-end"); | |
| 177 static const base::StringPiece kRunAtDocumentIdleValue("document-idle"); | |
| 178 | |
| 179 while (line_start < script_text.length()) { | |
| 180 line_end = script_text.find('\n', line_start); | |
| 181 | |
| 182 // Handle the case where there is no trailing newline in the file. | |
| 183 if (line_end == std::string::npos) | |
| 184 line_end = script_text.length() - 1; | |
| 185 | |
| 186 line.set(script_text.data() + line_start, line_end - line_start); | |
| 187 | |
| 188 if (!in_metadata) { | |
| 189 if (line.starts_with(kUserScriptBegin)) | |
| 190 in_metadata = true; | |
| 191 } else { | |
| 192 if (line.starts_with(kUserScriptEng)) | |
| 193 break; | |
| 194 | |
| 195 std::string value; | |
| 196 if (GetDeclarationValue(line, kIncludeDeclaration, &value)) { | |
| 197 // We escape some characters that MatchPattern() considers special. | |
| 198 ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\"); | |
| 199 ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?"); | |
| 200 script->add_glob(value); | |
| 201 } else if (GetDeclarationValue(line, kExcludeDeclaration, &value)) { | |
| 202 ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\"); | |
| 203 ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?"); | |
| 204 script->add_exclude_glob(value); | |
| 205 } else if (GetDeclarationValue(line, kNamespaceDeclaration, &value)) { | |
| 206 script->set_name_space(value); | |
| 207 } else if (GetDeclarationValue(line, kNameDeclaration, &value)) { | |
| 208 script->set_name(value); | |
| 209 } else if (GetDeclarationValue(line, kVersionDeclaration, &value)) { | |
| 210 Version version(value); | |
| 211 if (version.IsValid()) | |
| 212 script->set_version(version.GetString()); | |
| 213 } else if (GetDeclarationValue(line, kDescriptionDeclaration, &value)) { | |
| 214 script->set_description(value); | |
| 215 } else if (GetDeclarationValue(line, kMatchDeclaration, &value)) { | |
| 216 URLPattern pattern(UserScript::ValidUserScriptSchemes()); | |
| 217 if (URLPattern::PARSE_SUCCESS != pattern.Parse(value)) | |
| 218 return false; | |
| 219 script->add_url_pattern(pattern); | |
| 220 } else if (GetDeclarationValue(line, kExcludeMatchDeclaration, &value)) { | |
| 221 URLPattern exclude(UserScript::ValidUserScriptSchemes()); | |
| 222 if (URLPattern::PARSE_SUCCESS != exclude.Parse(value)) | |
| 223 return false; | |
| 224 script->add_exclude_url_pattern(exclude); | |
| 225 } else if (GetDeclarationValue(line, kRunAtDeclaration, &value)) { | |
| 226 if (value == kRunAtDocumentStartValue) | |
| 227 script->set_run_location(UserScript::DOCUMENT_START); | |
| 228 else if (value == kRunAtDocumentEndValue) | |
| 229 script->set_run_location(UserScript::DOCUMENT_END); | |
| 230 else if (value == kRunAtDocumentIdleValue) | |
| 231 script->set_run_location(UserScript::DOCUMENT_IDLE); | |
| 232 else | |
| 233 return false; | |
| 234 } | |
| 235 | |
| 236 // TODO(aa): Handle more types of metadata. | |
| 237 } | |
| 238 | |
| 239 line_start = line_end + 1; | |
| 240 } | |
| 241 | |
| 242 // If no patterns were specified, default to @include *. This is what | |
| 243 // Greasemonkey does. | |
| 244 if (script->globs().empty() && script->url_patterns().is_empty()) | |
| 245 script->add_glob("*"); | |
| 246 | |
| 247 return true; | |
| 248 } | |
| 249 | |
| 250 void UserScriptLoader::LoadScriptsForTest(UserScriptList* user_scripts) { | |
| 251 HostsInfo info; | |
| 252 std::set<int> added_script_ids; | |
| 253 for (UserScriptList::iterator it = user_scripts->begin(); | |
| 254 it != user_scripts->end(); | |
| 255 ++it) { | |
| 256 added_script_ids.insert(it->id()); | |
| 257 } | |
| 258 LoadUserScripts(user_scripts, info, added_script_ids, | |
| 259 NULL /* no verifier for testing */, | |
| 260 GetLoadUserScriptsFunction()); | |
| 261 } | |
| 262 | |
| 263 UserScriptLoader::UserScriptLoader( | |
| 264 Profile* profile, | |
| 265 const HostID& host_id, | |
| 266 const scoped_refptr<ContentVerifier>& content_verifier) | |
| 267 : user_scripts_(new UserScriptList()), | |
| 268 clear_scripts_(false), | |
| 269 ready_(false), | |
| 270 pending_load_(false), | |
| 271 profile_(profile), | |
| 272 host_id_(host_id), | |
| 273 content_verifier_(content_verifier), | |
| 274 weak_factory_(this) { | |
| 275 registrar_.Add(this, | |
| 276 content::NOTIFICATION_RENDERER_PROCESS_CREATED, | |
| 277 content::NotificationService::AllBrowserContextsAndSources()); | |
| 278 } | |
| 279 | |
| 280 UserScriptLoader::~UserScriptLoader() { | |
| 281 } | |
| 282 | |
| 283 void UserScriptLoader::AddScripts(const std::set<UserScript>& scripts) { | |
| 284 for (std::set<UserScript>::const_iterator it = scripts.begin(); | |
| 285 it != scripts.end(); | |
| 286 ++it) { | |
| 287 removed_scripts_.erase(*it); | |
| 288 added_scripts_.insert(*it); | |
| 289 } | |
| 290 AttemptLoad(); | |
| 291 } | |
| 292 | |
| 293 void UserScriptLoader::RemoveScripts(const std::set<UserScript>& scripts) { | |
| 294 for (std::set<UserScript>::const_iterator it = scripts.begin(); | |
| 295 it != scripts.end(); | |
| 296 ++it) { | |
| 297 added_scripts_.erase(*it); | |
| 298 removed_scripts_.insert(*it); | |
| 299 } | |
| 300 AttemptLoad(); | |
| 301 } | |
| 302 | |
| 303 void UserScriptLoader::ClearScripts() { | |
| 304 clear_scripts_ = true; | |
| 305 added_scripts_.clear(); | |
| 306 removed_scripts_.clear(); | |
| 307 AttemptLoad(); | |
| 308 } | |
| 309 | |
| 310 void UserScriptLoader::Observe(int type, | |
| 311 const content::NotificationSource& source, | |
| 312 const content::NotificationDetails& details) { | |
| 313 DCHECK_EQ(type, content::NOTIFICATION_RENDERER_PROCESS_CREATED); | |
| 314 content::RenderProcessHost* process = | |
| 315 content::Source<content::RenderProcessHost>(source).ptr(); | |
| 316 Profile* profile = Profile::FromBrowserContext(process->GetBrowserContext()); | |
| 317 if (!profile_->IsSameProfile(profile)) | |
| 318 return; | |
| 319 if (scripts_ready()) { | |
| 320 SendUpdate(process, shared_memory_.get(), | |
| 321 std::set<HostID>()); // Include all hosts. | |
| 322 } | |
| 323 } | |
| 324 | |
| 325 bool UserScriptLoader::ScriptsMayHaveChanged() const { | |
| 326 // Scripts may have changed if there are scripts added, scripts removed, or | |
| 327 // if scripts were cleared and either: | |
| 328 // (1) A load is in progress (which may result in a non-zero number of | |
| 329 // scripts that need to be cleared), or | |
| 330 // (2) The current set of scripts is non-empty (so they need to be cleared). | |
| 331 return (added_scripts_.size() || | |
| 332 removed_scripts_.size() || | |
| 333 (clear_scripts_ && | |
| 334 (is_loading() || user_scripts_->size()))); | |
| 335 } | |
| 336 | |
| 337 void UserScriptLoader::AttemptLoad() { | |
| 338 if (ready_ && ScriptsMayHaveChanged()) { | |
| 339 if (is_loading()) | |
| 340 pending_load_ = true; | |
| 341 else | |
| 342 StartLoad(); | |
| 343 } | |
| 344 } | |
| 345 | |
| 346 void UserScriptLoader::StartLoad() { | |
| 347 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 348 DCHECK(!is_loading()); | |
| 349 | |
| 350 // If scripts were marked for clearing before adding and removing, then clear | |
| 351 // them. | |
| 352 if (clear_scripts_) { | |
| 353 user_scripts_->clear(); | |
| 354 } else { | |
| 355 for (UserScriptList::iterator it = user_scripts_->begin(); | |
| 356 it != user_scripts_->end();) { | |
| 357 if (removed_scripts_.count(*it)) | |
| 358 it = user_scripts_->erase(it); | |
| 359 else | |
| 360 ++it; | |
| 361 } | |
| 362 } | |
| 363 | |
| 364 user_scripts_->insert( | |
| 365 user_scripts_->end(), added_scripts_.begin(), added_scripts_.end()); | |
| 366 | |
| 367 std::set<int> added_script_ids; | |
| 368 for (std::set<UserScript>::const_iterator it = added_scripts_.begin(); | |
| 369 it != added_scripts_.end(); | |
| 370 ++it) { | |
| 371 added_script_ids.insert(it->id()); | |
| 372 } | |
| 373 | |
| 374 // Expand |changed_hosts_| for OnScriptsLoaded, which will use it in | |
| 375 // its IPC message. This must be done before we clear |added_scripts_| and | |
| 376 // |removed_scripts_| below. | |
| 377 std::set<UserScript> changed_scripts(added_scripts_); | |
| 378 changed_scripts.insert(removed_scripts_.begin(), removed_scripts_.end()); | |
| 379 for (const UserScript& script : changed_scripts) | |
| 380 changed_hosts_.insert(script.host_id()); | |
| 381 | |
| 382 // |changed_hosts_| before passing it to LoadScriptsOnFileThread. | |
| 383 UpdateHostsInfo(changed_hosts_); | |
| 384 | |
| 385 BrowserThread::PostTask( | |
| 386 BrowserThread::FILE, FROM_HERE, | |
| 387 base::Bind(&LoadScriptsOnFileThread, base::Passed(&user_scripts_), | |
| 388 hosts_info_, added_script_ids, content_verifier_, | |
| 389 GetLoadUserScriptsFunction(), | |
| 390 base::Bind(&UserScriptLoader::OnScriptsLoaded, | |
| 391 weak_factory_.GetWeakPtr()))); | |
| 392 | |
| 393 clear_scripts_ = false; | |
| 394 added_scripts_.clear(); | |
| 395 removed_scripts_.clear(); | |
| 396 user_scripts_.reset(NULL); | |
| 397 } | |
| 398 | |
| 399 void UserScriptLoader::AddHostInfo(const HostID& host_id, | |
| 400 const PathAndDefaultLocale& location) { | |
| 401 if (hosts_info_.find(host_id) != hosts_info_.end()) | |
| 402 return; | |
| 403 hosts_info_[host_id] = location; | |
| 404 } | |
| 405 | |
| 406 void UserScriptLoader::RemoveHostInfo(const HostID& host_id) { | |
| 407 hosts_info_.erase(host_id); | |
| 408 } | |
| 409 | |
| 410 void UserScriptLoader::SetReady(bool ready) { | |
| 411 bool was_ready = ready_; | |
| 412 ready_ = ready; | |
| 413 if (ready_ && !was_ready) | |
| 414 AttemptLoad(); | |
| 415 } | |
| 416 | |
| 417 void UserScriptLoader::OnScriptsLoaded( | |
| 418 scoped_ptr<UserScriptList> user_scripts, | |
| 419 scoped_ptr<base::SharedMemory> shared_memory) { | |
| 420 user_scripts_.reset(user_scripts.release()); | |
| 421 if (pending_load_) { | |
| 422 // While we were loading, there were further changes. Don't bother | |
| 423 // notifying about these scripts and instead just immediately reload. | |
| 424 pending_load_ = false; | |
| 425 StartLoad(); | |
| 426 return; | |
| 427 } | |
| 428 | |
| 429 if (shared_memory.get() == NULL) { | |
| 430 // This can happen if we run out of file descriptors. In that case, we | |
| 431 // have a choice between silently omitting all user scripts for new tabs, | |
| 432 // by nulling out shared_memory_, or only silently omitting new ones by | |
| 433 // leaving the existing object in place. The second seems less bad, even | |
| 434 // though it removes the possibility that freeing the shared memory block | |
| 435 // would open up enough FDs for long enough for a retry to succeed. | |
| 436 | |
| 437 // Pretend the extension change didn't happen. | |
| 438 return; | |
| 439 } | |
| 440 | |
| 441 // We've got scripts ready to go. | |
| 442 shared_memory_.reset(shared_memory.release()); | |
| 443 | |
| 444 for (content::RenderProcessHost::iterator i( | |
| 445 content::RenderProcessHost::AllHostsIterator()); | |
| 446 !i.IsAtEnd(); | |
| 447 i.Advance()) { | |
| 448 SendUpdate(i.GetCurrentValue(), shared_memory_.get(), changed_hosts_); | |
| 449 } | |
| 450 changed_hosts_.clear(); | |
| 451 | |
| 452 content::NotificationService::current()->Notify( | |
| 453 extensions::NOTIFICATION_USER_SCRIPTS_UPDATED, | |
| 454 content::Source<Profile>(profile_), | |
| 455 content::Details<base::SharedMemory>(shared_memory_.get())); | |
| 456 } | |
| 457 | |
| 458 void UserScriptLoader::SendUpdate(content::RenderProcessHost* process, | |
| 459 base::SharedMemory* shared_memory, | |
| 460 const std::set<HostID>& changed_hosts) { | |
| 461 // Don't allow injection of content scripts into <webview>. | |
| 462 if (process->IsIsolatedGuest()) | |
| 463 return; | |
| 464 | |
| 465 Profile* profile = Profile::FromBrowserContext(process->GetBrowserContext()); | |
| 466 // Make sure we only send user scripts to processes in our profile. | |
| 467 if (!profile_->IsSameProfile(profile)) | |
| 468 return; | |
| 469 | |
| 470 // If the process is being started asynchronously, early return. We'll end up | |
| 471 // calling InitUserScripts when it's created which will call this again. | |
| 472 base::ProcessHandle handle = process->GetHandle(); | |
| 473 if (!handle) | |
| 474 return; | |
| 475 | |
| 476 base::SharedMemoryHandle handle_for_process; | |
| 477 if (!shared_memory->ShareToProcess(handle, &handle_for_process)) | |
| 478 return; // This can legitimately fail if the renderer asserts at startup. | |
| 479 | |
| 480 // TODO(hanxi): update the IPC message to send a set of HostIDs to render. | |
| 481 // Also, remove this function when the refactor is done on render side. | |
| 482 std::set<std::string> changed_ids_set; | |
| 483 for (const HostID& id : changed_hosts) | |
| 484 changed_ids_set.insert(id.id()); | |
| 485 | |
| 486 if (base::SharedMemory::IsHandleValid(handle_for_process)) { | |
| 487 process->Send(new ExtensionMsg_UpdateUserScripts( | |
| 488 handle_for_process, host_id().id(), changed_ids_set)); | |
| 489 } | |
| 490 } | |
| 491 | |
| 492 } // namespace extensions | |
| OLD | NEW |