OLD | NEW |
| (Empty) |
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 <string> | |
8 | |
9 #include "base/bind.h" | |
10 #include "base/bind_helpers.h" | |
11 #include "base/file_util.h" | |
12 #include "base/files/file_path.h" | |
13 #include "base/memory/shared_memory.h" | |
14 #include "base/version.h" | |
15 #include "chrome/browser/chrome_notification_types.h" | |
16 #include "chrome/browser/extensions/extension_util.h" | |
17 #include "chrome/browser/profiles/profile.h" | |
18 #include "chrome/common/extensions/api/i18n/default_locale_handler.h" | |
19 #include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h" | |
20 #include "content/public/browser/notification_service.h" | |
21 #include "content/public/browser/render_process_host.h" | |
22 #include "extensions/browser/component_extension_resource_manager.h" | |
23 #include "extensions/browser/content_verifier.h" | |
24 #include "extensions/browser/extension_registry.h" | |
25 #include "extensions/browser/extension_system.h" | |
26 #include "extensions/browser/extensions_browser_client.h" | |
27 #include "extensions/common/file_util.h" | |
28 #include "extensions/common/one_shot_event.h" | |
29 #include "extensions/common/message_bundle.h" | |
30 #include "ui/base/resource/resource_bundle.h" | |
31 | |
32 using content::BrowserThread; | |
33 using extensions::ExtensionsBrowserClient; | |
34 | |
35 namespace extensions { | |
36 | |
37 namespace { | |
38 | |
39 typedef base::Callback<void(scoped_ptr<UserScriptList>, | |
40 scoped_ptr<base::SharedMemory>)> | |
41 LoadScriptsCallback; | |
42 | |
43 void VerifyContent(scoped_refptr<ContentVerifier> verifier, | |
44 const std::string& extension_id, | |
45 const base::FilePath& extension_root, | |
46 const base::FilePath& relative_path, | |
47 const std::string& content) { | |
48 DCHECK_CURRENTLY_ON(content::BrowserThread::IO); | |
49 scoped_refptr<ContentVerifyJob> job( | |
50 verifier->CreateJobFor(extension_id, extension_root, relative_path)); | |
51 if (job.get()) { | |
52 job->Start(); | |
53 job->BytesRead(content.size(), content.data()); | |
54 job->DoneReading(); | |
55 } | |
56 } | |
57 | |
58 bool LoadScriptContent(const std::string& extension_id, | |
59 UserScript::File* script_file, | |
60 const SubstitutionMap* localization_messages, | |
61 scoped_refptr<ContentVerifier> verifier) { | |
62 std::string content; | |
63 const base::FilePath& path = ExtensionResource::GetFilePath( | |
64 script_file->extension_root(), script_file->relative_path(), | |
65 ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT); | |
66 if (path.empty()) { | |
67 int resource_id; | |
68 if (ExtensionsBrowserClient::Get()->GetComponentExtensionResourceManager()-> | |
69 IsComponentExtensionResource( | |
70 script_file->extension_root(), script_file->relative_path(), | |
71 &resource_id)) { | |
72 const ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
73 content = rb.GetRawDataResource(resource_id).as_string(); | |
74 } else { | |
75 LOG(WARNING) << "Failed to get file path to " | |
76 << script_file->relative_path().value() << " from " | |
77 << script_file->extension_root().value(); | |
78 return false; | |
79 } | |
80 } else { | |
81 if (!base::ReadFileToString(path, &content)) { | |
82 LOG(WARNING) << "Failed to load user script file: " << path.value(); | |
83 return false; | |
84 } | |
85 if (verifier) { | |
86 content::BrowserThread::PostTask(content::BrowserThread::IO, | |
87 FROM_HERE, | |
88 base::Bind(&VerifyContent, | |
89 verifier, | |
90 extension_id, | |
91 script_file->extension_root(), | |
92 script_file->relative_path(), | |
93 content)); | |
94 } | |
95 } | |
96 | |
97 // Localize the content. | |
98 if (localization_messages) { | |
99 std::string error; | |
100 MessageBundle::ReplaceMessagesWithExternalDictionary( | |
101 *localization_messages, &content, &error); | |
102 if (!error.empty()) { | |
103 LOG(WARNING) << "Failed to replace messages in script: " << error; | |
104 } | |
105 } | |
106 | |
107 // Remove BOM from the content. | |
108 std::string::size_type index = content.find(base::kUtf8ByteOrderMark); | |
109 if (index == 0) { | |
110 script_file->set_content(content.substr(strlen(base::kUtf8ByteOrderMark))); | |
111 } else { | |
112 script_file->set_content(content); | |
113 } | |
114 | |
115 return true; | |
116 } | |
117 | |
118 SubstitutionMap* GetLocalizationMessages(const ExtensionsInfo& extensions_info, | |
119 const std::string& extension_id) { | |
120 ExtensionsInfo::const_iterator iter = extensions_info.find(extension_id); | |
121 if (iter == extensions_info.end()) | |
122 return NULL; | |
123 return file_util::LoadMessageBundleSubstitutionMap(iter->second.first, | |
124 extension_id, | |
125 iter->second.second); | |
126 } | |
127 | |
128 void LoadUserScripts(UserScriptList* user_scripts, | |
129 const ExtensionsInfo& extensions_info, | |
130 const std::set<std::string>& new_extensions, | |
131 ContentVerifier* verifier) { | |
132 for (size_t i = 0; i < user_scripts->size(); ++i) { | |
133 UserScript& script = user_scripts->at(i); | |
134 if (new_extensions.count(script.extension_id()) == 0) | |
135 continue; | |
136 scoped_ptr<SubstitutionMap> localization_messages( | |
137 GetLocalizationMessages(extensions_info, script.extension_id())); | |
138 for (size_t k = 0; k < script.js_scripts().size(); ++k) { | |
139 UserScript::File& script_file = script.js_scripts()[k]; | |
140 if (script_file.GetContent().empty()) | |
141 LoadScriptContent( | |
142 script.extension_id(), &script_file, NULL, verifier); | |
143 } | |
144 for (size_t k = 0; k < script.css_scripts().size(); ++k) { | |
145 UserScript::File& script_file = script.css_scripts()[k]; | |
146 if (script_file.GetContent().empty()) | |
147 LoadScriptContent(script.extension_id(), | |
148 &script_file, | |
149 localization_messages.get(), | |
150 verifier); | |
151 } | |
152 } | |
153 } | |
154 | |
155 // Pickle user scripts and return pointer to the shared memory. | |
156 scoped_ptr<base::SharedMemory> Serialize(const UserScriptList& scripts) { | |
157 Pickle pickle; | |
158 pickle.WriteUInt64(scripts.size()); | |
159 for (size_t i = 0; i < scripts.size(); i++) { | |
160 const UserScript& script = scripts[i]; | |
161 // TODO(aa): This can be replaced by sending content script metadata to | |
162 // renderers along with other extension data in ExtensionMsg_Loaded. | |
163 // See crbug.com/70516. | |
164 script.Pickle(&pickle); | |
165 // Write scripts as 'data' so that we can read it out in the slave without | |
166 // allocating a new string. | |
167 for (size_t j = 0; j < script.js_scripts().size(); j++) { | |
168 base::StringPiece contents = script.js_scripts()[j].GetContent(); | |
169 pickle.WriteData(contents.data(), contents.length()); | |
170 } | |
171 for (size_t j = 0; j < script.css_scripts().size(); j++) { | |
172 base::StringPiece contents = script.css_scripts()[j].GetContent(); | |
173 pickle.WriteData(contents.data(), contents.length()); | |
174 } | |
175 } | |
176 | |
177 // Create the shared memory object. | |
178 base::SharedMemory shared_memory; | |
179 | |
180 base::SharedMemoryCreateOptions options; | |
181 options.size = pickle.size(); | |
182 options.share_read_only = true; | |
183 if (!shared_memory.Create(options)) | |
184 return scoped_ptr<base::SharedMemory>(); | |
185 | |
186 if (!shared_memory.Map(pickle.size())) | |
187 return scoped_ptr<base::SharedMemory>(); | |
188 | |
189 // Copy the pickle to shared memory. | |
190 memcpy(shared_memory.memory(), pickle.data(), pickle.size()); | |
191 | |
192 base::SharedMemoryHandle readonly_handle; | |
193 if (!shared_memory.ShareReadOnlyToProcess(base::GetCurrentProcessHandle(), | |
194 &readonly_handle)) | |
195 return scoped_ptr<base::SharedMemory>(); | |
196 | |
197 return make_scoped_ptr(new base::SharedMemory(readonly_handle, | |
198 /*read_only=*/true)); | |
199 } | |
200 | |
201 void LoadScriptsOnFileThread(scoped_ptr<UserScriptList> user_scripts, | |
202 const ExtensionsInfo& extensions_info, | |
203 const std::set<std::string>& new_extensions, | |
204 scoped_refptr<ContentVerifier> verifier, | |
205 LoadScriptsCallback callback) { | |
206 DCHECK(user_scripts.get()); | |
207 LoadUserScripts( | |
208 user_scripts.get(), extensions_info, new_extensions, verifier); | |
209 scoped_ptr<base::SharedMemory> memory = Serialize(*user_scripts); | |
210 BrowserThread::PostTask( | |
211 BrowserThread::UI, | |
212 FROM_HERE, | |
213 base::Bind(callback, | |
214 base::Passed(&user_scripts), | |
215 base::Passed(&memory))); | |
216 } | |
217 | |
218 // Helper function to parse greasesmonkey headers | |
219 bool GetDeclarationValue(const base::StringPiece& line, | |
220 const base::StringPiece& prefix, | |
221 std::string* value) { | |
222 base::StringPiece::size_type index = line.find(prefix); | |
223 if (index == base::StringPiece::npos) | |
224 return false; | |
225 | |
226 std::string temp(line.data() + index + prefix.length(), | |
227 line.length() - index - prefix.length()); | |
228 | |
229 if (temp.empty() || !IsWhitespace(temp[0])) | |
230 return false; | |
231 | |
232 base::TrimWhitespaceASCII(temp, base::TRIM_ALL, value); | |
233 return true; | |
234 } | |
235 | |
236 } // namespace | |
237 | |
238 // static | |
239 bool UserScriptMaster::ParseMetadataHeader( | |
240 const base::StringPiece& script_text, UserScript* script) { | |
241 // http://wiki.greasespot.net/Metadata_block | |
242 base::StringPiece line; | |
243 size_t line_start = 0; | |
244 size_t line_end = line_start; | |
245 bool in_metadata = false; | |
246 | |
247 static const base::StringPiece kUserScriptBegin("// ==UserScript=="); | |
248 static const base::StringPiece kUserScriptEng("// ==/UserScript=="); | |
249 static const base::StringPiece kNamespaceDeclaration("// @namespace"); | |
250 static const base::StringPiece kNameDeclaration("// @name"); | |
251 static const base::StringPiece kVersionDeclaration("// @version"); | |
252 static const base::StringPiece kDescriptionDeclaration("// @description"); | |
253 static const base::StringPiece kIncludeDeclaration("// @include"); | |
254 static const base::StringPiece kExcludeDeclaration("// @exclude"); | |
255 static const base::StringPiece kMatchDeclaration("// @match"); | |
256 static const base::StringPiece kExcludeMatchDeclaration("// @exclude_match"); | |
257 static const base::StringPiece kRunAtDeclaration("// @run-at"); | |
258 static const base::StringPiece kRunAtDocumentStartValue("document-start"); | |
259 static const base::StringPiece kRunAtDocumentEndValue("document-end"); | |
260 static const base::StringPiece kRunAtDocumentIdleValue("document-idle"); | |
261 | |
262 while (line_start < script_text.length()) { | |
263 line_end = script_text.find('\n', line_start); | |
264 | |
265 // Handle the case where there is no trailing newline in the file. | |
266 if (line_end == std::string::npos) | |
267 line_end = script_text.length() - 1; | |
268 | |
269 line.set(script_text.data() + line_start, line_end - line_start); | |
270 | |
271 if (!in_metadata) { | |
272 if (line.starts_with(kUserScriptBegin)) | |
273 in_metadata = true; | |
274 } else { | |
275 if (line.starts_with(kUserScriptEng)) | |
276 break; | |
277 | |
278 std::string value; | |
279 if (GetDeclarationValue(line, kIncludeDeclaration, &value)) { | |
280 // We escape some characters that MatchPattern() considers special. | |
281 ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\"); | |
282 ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?"); | |
283 script->add_glob(value); | |
284 } else if (GetDeclarationValue(line, kExcludeDeclaration, &value)) { | |
285 ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\"); | |
286 ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?"); | |
287 script->add_exclude_glob(value); | |
288 } else if (GetDeclarationValue(line, kNamespaceDeclaration, &value)) { | |
289 script->set_name_space(value); | |
290 } else if (GetDeclarationValue(line, kNameDeclaration, &value)) { | |
291 script->set_name(value); | |
292 } else if (GetDeclarationValue(line, kVersionDeclaration, &value)) { | |
293 Version version(value); | |
294 if (version.IsValid()) | |
295 script->set_version(version.GetString()); | |
296 } else if (GetDeclarationValue(line, kDescriptionDeclaration, &value)) { | |
297 script->set_description(value); | |
298 } else if (GetDeclarationValue(line, kMatchDeclaration, &value)) { | |
299 URLPattern pattern(UserScript::ValidUserScriptSchemes()); | |
300 if (URLPattern::PARSE_SUCCESS != pattern.Parse(value)) | |
301 return false; | |
302 script->add_url_pattern(pattern); | |
303 } else if (GetDeclarationValue(line, kExcludeMatchDeclaration, &value)) { | |
304 URLPattern exclude(UserScript::ValidUserScriptSchemes()); | |
305 if (URLPattern::PARSE_SUCCESS != exclude.Parse(value)) | |
306 return false; | |
307 script->add_exclude_url_pattern(exclude); | |
308 } else if (GetDeclarationValue(line, kRunAtDeclaration, &value)) { | |
309 if (value == kRunAtDocumentStartValue) | |
310 script->set_run_location(UserScript::DOCUMENT_START); | |
311 else if (value == kRunAtDocumentEndValue) | |
312 script->set_run_location(UserScript::DOCUMENT_END); | |
313 else if (value == kRunAtDocumentIdleValue) | |
314 script->set_run_location(UserScript::DOCUMENT_IDLE); | |
315 else | |
316 return false; | |
317 } | |
318 | |
319 // TODO(aa): Handle more types of metadata. | |
320 } | |
321 | |
322 line_start = line_end + 1; | |
323 } | |
324 | |
325 // If no patterns were specified, default to @include *. This is what | |
326 // Greasemonkey does. | |
327 if (script->globs().empty() && script->url_patterns().is_empty()) | |
328 script->add_glob("*"); | |
329 | |
330 return true; | |
331 } | |
332 | |
333 // static | |
334 void UserScriptMaster::LoadScriptsForTest(UserScriptList* user_scripts) { | |
335 ExtensionsInfo info; | |
336 std::set<std::string> new_extensions; | |
337 for (UserScriptList::const_iterator iter = user_scripts->begin(); | |
338 iter != user_scripts->end(); | |
339 ++iter) { | |
340 new_extensions.insert(iter->extension_id()); | |
341 } | |
342 LoadUserScripts( | |
343 user_scripts, info, new_extensions, NULL /* no verifier for testing */); | |
344 } | |
345 | |
346 UserScriptMaster::UserScriptMaster(Profile* profile) | |
347 : user_scripts_(new UserScriptList()), | |
348 extensions_service_ready_(false), | |
349 pending_load_(false), | |
350 profile_(profile), | |
351 extension_registry_observer_(this), | |
352 weak_factory_(this) { | |
353 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_)); | |
354 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED, | |
355 content::NotificationService::AllBrowserContextsAndSources()); | |
356 ExtensionSystem::Get(profile)->ready().Post( | |
357 FROM_HERE, | |
358 base::Bind(&UserScriptMaster::OnExtensionsReady, | |
359 weak_factory_.GetWeakPtr())); | |
360 } | |
361 | |
362 UserScriptMaster::~UserScriptMaster() { | |
363 } | |
364 | |
365 void UserScriptMaster::OnScriptsLoaded( | |
366 scoped_ptr<UserScriptList> user_scripts, | |
367 scoped_ptr<base::SharedMemory> shared_memory) { | |
368 user_scripts_.reset(user_scripts.release()); | |
369 if (pending_load_) { | |
370 // While we were loading, there were further changes. Don't bother | |
371 // notifying about these scripts and instead just immediately reload. | |
372 pending_load_ = false; | |
373 StartLoad(); | |
374 return; | |
375 } | |
376 | |
377 if (shared_memory.get() == NULL) { | |
378 // This can happen if we run out of file descriptors. In that case, we | |
379 // have a choice between silently omitting all user scripts for new tabs, | |
380 // by nulling out shared_memory_, or only silently omitting new ones by | |
381 // leaving the existing object in place. The second seems less bad, even | |
382 // though it removes the possibility that freeing the shared memory block | |
383 // would open up enough FDs for long enough for a retry to succeed. | |
384 | |
385 // Pretend the extension change didn't happen. | |
386 return; | |
387 } | |
388 | |
389 // We've got scripts ready to go. | |
390 shared_memory_.reset(shared_memory.release()); | |
391 | |
392 for (content::RenderProcessHost::iterator i( | |
393 content::RenderProcessHost::AllHostsIterator()); | |
394 !i.IsAtEnd(); i.Advance()) { | |
395 SendUpdate(i.GetCurrentValue(), | |
396 shared_memory_.get(), | |
397 changed_extensions_); | |
398 } | |
399 changed_extensions_.clear(); | |
400 | |
401 content::NotificationService::current()->Notify( | |
402 extensions::NOTIFICATION_USER_SCRIPTS_UPDATED, | |
403 content::Source<Profile>(profile_), | |
404 content::Details<base::SharedMemory>(shared_memory_.get())); | |
405 } | |
406 | |
407 void UserScriptMaster::OnExtensionLoaded( | |
408 content::BrowserContext* browser_context, | |
409 const Extension* extension) { | |
410 added_extensions_.insert(extension->id()); | |
411 removed_extensions_.erase(extension->id()); | |
412 extensions_info_[extension->id()] = | |
413 ExtensionSet::ExtensionPathAndDefaultLocale( | |
414 extension->path(), LocaleInfo::GetDefaultLocale(extension)); | |
415 if (extensions_service_ready_) { | |
416 changed_extensions_.insert(extension->id()); | |
417 if (is_loading()) | |
418 pending_load_ = true; | |
419 else | |
420 StartLoad(); | |
421 } | |
422 } | |
423 | |
424 void UserScriptMaster::OnExtensionUnloaded( | |
425 content::BrowserContext* browser_context, | |
426 const Extension* extension, | |
427 UnloadedExtensionInfo::Reason reason) { | |
428 removed_extensions_.insert(extension->id()); | |
429 added_extensions_.erase(extension->id()); | |
430 // Remove any content scripts. | |
431 extensions_info_.erase(extension->id()); | |
432 changed_extensions_.insert(extension->id()); | |
433 if (is_loading()) | |
434 pending_load_ = true; | |
435 else | |
436 StartLoad(); | |
437 } | |
438 | |
439 void UserScriptMaster::Observe(int type, | |
440 const content::NotificationSource& source, | |
441 const content::NotificationDetails& details) { | |
442 DCHECK_EQ(type, content::NOTIFICATION_RENDERER_PROCESS_CREATED); | |
443 content::RenderProcessHost* process = | |
444 content::Source<content::RenderProcessHost>(source).ptr(); | |
445 Profile* profile = Profile::FromBrowserContext( | |
446 process->GetBrowserContext()); | |
447 if (!profile_->IsSameProfile(profile)) | |
448 return; | |
449 if (ScriptsReady()) { | |
450 SendUpdate(process, | |
451 GetSharedMemory(), | |
452 std::set<std::string>()); // Include all extensions. | |
453 } | |
454 } | |
455 | |
456 void UserScriptMaster::OnExtensionsReady() { | |
457 extensions_service_ready_ = true; | |
458 if (is_loading()) | |
459 pending_load_ = true; | |
460 else | |
461 StartLoad(); | |
462 } | |
463 | |
464 void UserScriptMaster::StartLoad() { | |
465 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
466 DCHECK(!is_loading()); | |
467 | |
468 // Remove any user scripts belonging to any extension that was updated or | |
469 // removed. | |
470 for (UserScriptList::iterator iter = user_scripts_->begin(); | |
471 iter != user_scripts_->end();) { | |
472 if (removed_extensions_.count(iter->extension_id()) > 0 || | |
473 added_extensions_.count(iter->extension_id()) > 0) { | |
474 iter = user_scripts_->erase(iter); | |
475 } else { | |
476 ++iter; | |
477 } | |
478 } | |
479 | |
480 // Add any content scripts for extensions that were recently loaded. | |
481 const ExtensionSet& enabled_extensions = | |
482 ExtensionRegistry::Get(profile_)->enabled_extensions(); | |
483 for (std::set<std::string>::const_iterator iter = added_extensions_.begin(); | |
484 iter != added_extensions_.end(); ++iter) { | |
485 const Extension* extension = enabled_extensions.GetByID(*iter); | |
486 if (!extension) | |
487 continue; | |
488 bool incognito_enabled = | |
489 util::IsIncognitoEnabled(extension->id(), profile_); | |
490 const UserScriptList& scripts = | |
491 ContentScriptsInfo::GetContentScripts(extension); | |
492 for (UserScriptList::const_iterator script = scripts.begin(); | |
493 script != scripts.end(); | |
494 ++script) { | |
495 user_scripts_->push_back(*script); | |
496 user_scripts_->back().set_incognito_enabled(incognito_enabled); | |
497 } | |
498 } | |
499 | |
500 BrowserThread::PostTask( | |
501 BrowserThread::FILE, | |
502 FROM_HERE, | |
503 base::Bind(&LoadScriptsOnFileThread, | |
504 base::Passed(&user_scripts_), | |
505 extensions_info_, | |
506 added_extensions_, | |
507 make_scoped_refptr( | |
508 ExtensionSystem::Get(profile_)->content_verifier()), | |
509 base::Bind(&UserScriptMaster::OnScriptsLoaded, | |
510 weak_factory_.GetWeakPtr()))); | |
511 added_extensions_.clear(); | |
512 removed_extensions_.clear(); | |
513 user_scripts_.reset(NULL); | |
514 } | |
515 | |
516 void UserScriptMaster::SendUpdate( | |
517 content::RenderProcessHost* process, | |
518 base::SharedMemory* shared_memory, | |
519 const std::set<std::string>& changed_extensions) { | |
520 // Don't allow injection of content scripts into <webview>. | |
521 if (process->IsIsolatedGuest()) | |
522 return; | |
523 | |
524 Profile* profile = Profile::FromBrowserContext(process->GetBrowserContext()); | |
525 // Make sure we only send user scripts to processes in our profile. | |
526 if (!profile_->IsSameProfile(profile)) | |
527 return; | |
528 | |
529 // If the process is being started asynchronously, early return. We'll end up | |
530 // calling InitUserScripts when it's created which will call this again. | |
531 base::ProcessHandle handle = process->GetHandle(); | |
532 if (!handle) | |
533 return; | |
534 | |
535 base::SharedMemoryHandle handle_for_process; | |
536 if (!shared_memory->ShareToProcess(handle, &handle_for_process)) | |
537 return; // This can legitimately fail if the renderer asserts at startup. | |
538 | |
539 if (base::SharedMemory::IsHandleValid(handle_for_process)) { | |
540 process->Send(new ExtensionMsg_UpdateUserScripts( | |
541 handle_for_process, "" /* owner */, changed_extensions)); | |
542 } | |
543 } | |
544 | |
545 } // namespace extensions | |
OLD | NEW |