Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(12)

Side by Side Diff: chrome/browser/extensions/user_script_master.cc

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

Powered by Google App Engine
This is Rietveld 408576698