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

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: Fix nit and rebase from master 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/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
OLDNEW
« no previous file with comments | « chrome/browser/extensions/user_script_master.h ('k') | chrome/browser/extensions/user_script_master_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698