Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / extensions / user_script_loader.cc
blobbbef730877df0b949db5b977a0e98602fedd7a97
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.
5 #include "chrome/browser/extensions/user_script_loader.h"
7 #include <set>
8 #include <string>
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/memory/shared_memory.h"
15 #include "base/version.h"
16 #include "chrome/browser/chrome_notification_types.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/common/extensions/api/i18n/default_locale_handler.h"
19 #include "content/public/browser/browser_thread.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/extension_messages.h"
28 #include "extensions/common/file_util.h"
29 #include "extensions/common/message_bundle.h"
30 #include "extensions/common/one_shot_event.h"
31 #include "ui/base/resource/resource_bundle.h"
33 using content::BrowserThread;
34 using extensions::ExtensionsBrowserClient;
36 namespace extensions {
38 namespace {
40 typedef base::Callback<
41 void(scoped_ptr<UserScriptList>, scoped_ptr<base::SharedMemory>)>
42 LoadScriptsCallback;
44 void VerifyContent(scoped_refptr<ContentVerifier> verifier,
45 const ExtensionId& extension_id,
46 const base::FilePath& extension_root,
47 const base::FilePath& relative_path,
48 const std::string& content) {
49 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
50 scoped_refptr<ContentVerifyJob> job(
51 verifier->CreateJobFor(extension_id, extension_root, relative_path));
52 if (job.get()) {
53 job->Start();
54 job->BytesRead(content.size(), content.data());
55 job->DoneReading();
59 bool LoadScriptContent(const ExtensionId& extension_id,
60 UserScript::File* script_file,
61 const SubstitutionMap* localization_messages,
62 scoped_refptr<ContentVerifier> verifier) {
63 std::string content;
64 const base::FilePath& path = ExtensionResource::GetFilePath(
65 script_file->extension_root(),
66 script_file->relative_path(),
67 ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT);
68 if (path.empty()) {
69 int resource_id;
70 if (ExtensionsBrowserClient::Get()->GetComponentExtensionResourceManager()->
71 IsComponentExtensionResource(script_file->extension_root(),
72 script_file->relative_path(),
73 &resource_id)) {
74 const ResourceBundle& rb = ResourceBundle::GetSharedInstance();
75 content = rb.GetRawDataResource(resource_id).as_string();
76 } else {
77 LOG(WARNING) << "Failed to get file path to "
78 << script_file->relative_path().value() << " from "
79 << script_file->extension_root().value();
80 return false;
82 } else {
83 if (!base::ReadFileToString(path, &content)) {
84 LOG(WARNING) << "Failed to load user script file: " << path.value();
85 return false;
87 if (verifier.get()) {
88 content::BrowserThread::PostTask(content::BrowserThread::IO,
89 FROM_HERE,
90 base::Bind(&VerifyContent,
91 verifier,
92 extension_id,
93 script_file->extension_root(),
94 script_file->relative_path(),
95 content));
99 // Localize the content.
100 if (localization_messages) {
101 std::string error;
102 MessageBundle::ReplaceMessagesWithExternalDictionary(
103 *localization_messages, &content, &error);
104 if (!error.empty()) {
105 LOG(WARNING) << "Failed to replace messages in script: " << error;
109 // Remove BOM from the content.
110 std::string::size_type index = content.find(base::kUtf8ByteOrderMark);
111 if (index == 0) {
112 script_file->set_content(content.substr(strlen(base::kUtf8ByteOrderMark)));
113 } else {
114 script_file->set_content(content);
117 return true;
120 SubstitutionMap* GetLocalizationMessages(const ExtensionsInfo& extensions_info,
121 const ExtensionId& extension_id) {
122 ExtensionsInfo::const_iterator iter = extensions_info.find(extension_id);
123 if (iter == extensions_info.end())
124 return NULL;
125 return file_util::LoadMessageBundleSubstitutionMap(
126 iter->second.first, extension_id, iter->second.second);
129 void LoadUserScripts(UserScriptList* user_scripts,
130 const ExtensionsInfo& extensions_info,
131 const std::set<int>& added_script_ids,
132 ContentVerifier* verifier) {
133 for (UserScriptList::iterator script = user_scripts->begin();
134 script != user_scripts->end();
135 ++script) {
136 if (added_script_ids.count(script->id()) == 0)
137 continue;
138 scoped_ptr<SubstitutionMap> localization_messages(
139 GetLocalizationMessages(extensions_info, script->extension_id()));
140 for (size_t k = 0; k < script->js_scripts().size(); ++k) {
141 UserScript::File& script_file = script->js_scripts()[k];
142 if (script_file.GetContent().empty())
143 LoadScriptContent(script->extension_id(), &script_file, NULL, verifier);
145 for (size_t k = 0; k < script->css_scripts().size(); ++k) {
146 UserScript::File& script_file = script->css_scripts()[k];
147 if (script_file.GetContent().empty())
148 LoadScriptContent(script->extension_id(),
149 &script_file,
150 localization_messages.get(),
151 verifier);
156 // Pickle user scripts and return pointer to the shared memory.
157 scoped_ptr<base::SharedMemory> Serialize(const UserScriptList& scripts) {
158 Pickle pickle;
159 pickle.WriteSizeT(scripts.size());
160 for (UserScriptList::const_iterator script = scripts.begin();
161 script != scripts.end();
162 ++script) {
163 // TODO(aa): This can be replaced by sending content script metadata to
164 // renderers along with other extension data in ExtensionMsg_Loaded.
165 // See crbug.com/70516.
166 script->Pickle(&pickle);
167 // Write scripts as 'data' so that we can read it out in the slave without
168 // allocating a new string.
169 for (size_t j = 0; j < script->js_scripts().size(); j++) {
170 base::StringPiece contents = script->js_scripts()[j].GetContent();
171 pickle.WriteData(contents.data(), contents.length());
173 for (size_t j = 0; j < script->css_scripts().size(); j++) {
174 base::StringPiece contents = script->css_scripts()[j].GetContent();
175 pickle.WriteData(contents.data(), contents.length());
179 // Create the shared memory object.
180 base::SharedMemory shared_memory;
182 base::SharedMemoryCreateOptions options;
183 options.size = pickle.size();
184 options.share_read_only = true;
185 if (!shared_memory.Create(options))
186 return scoped_ptr<base::SharedMemory>();
188 if (!shared_memory.Map(pickle.size()))
189 return scoped_ptr<base::SharedMemory>();
191 // Copy the pickle to shared memory.
192 memcpy(shared_memory.memory(), pickle.data(), pickle.size());
194 base::SharedMemoryHandle readonly_handle;
195 if (!shared_memory.ShareReadOnlyToProcess(base::GetCurrentProcessHandle(),
196 &readonly_handle))
197 return scoped_ptr<base::SharedMemory>();
199 return make_scoped_ptr(new base::SharedMemory(readonly_handle,
200 /*read_only=*/true));
203 void LoadScriptsOnFileThread(scoped_ptr<UserScriptList> user_scripts,
204 const ExtensionsInfo& extensions_info,
205 const std::set<int>& added_script_ids,
206 scoped_refptr<ContentVerifier> verifier,
207 LoadScriptsCallback callback) {
208 DCHECK(user_scripts.get());
209 LoadUserScripts(
210 user_scripts.get(), extensions_info, added_script_ids, verifier.get());
211 scoped_ptr<base::SharedMemory> memory = Serialize(*user_scripts);
212 BrowserThread::PostTask(
213 BrowserThread::UI,
214 FROM_HERE,
215 base::Bind(callback, base::Passed(&user_scripts), base::Passed(&memory)));
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;
226 std::string temp(line.data() + index + prefix.length(),
227 line.length() - index - prefix.length());
229 if (temp.empty() || !IsWhitespace(temp[0]))
230 return false;
232 base::TrimWhitespaceASCII(temp, base::TRIM_ALL, value);
233 return true;
236 } // namespace
238 // static
239 bool UserScriptLoader::ParseMetadataHeader(const base::StringPiece& script_text,
240 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;
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");
262 while (line_start < script_text.length()) {
263 line_end = script_text.find('\n', line_start);
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;
269 line.set(script_text.data() + line_start, line_end - line_start);
271 if (!in_metadata) {
272 if (line.starts_with(kUserScriptBegin))
273 in_metadata = true;
274 } else {
275 if (line.starts_with(kUserScriptEng))
276 break;
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;
319 // TODO(aa): Handle more types of metadata.
322 line_start = line_end + 1;
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("*");
330 return true;
333 // static
334 void UserScriptLoader::LoadScriptsForTest(UserScriptList* user_scripts) {
335 ExtensionsInfo info;
336 std::set<int> added_script_ids;
337 for (UserScriptList::iterator it = user_scripts->begin();
338 it != user_scripts->end();
339 ++it) {
340 added_script_ids.insert(it->id());
342 LoadUserScripts(
343 user_scripts, info, added_script_ids, NULL /* no verifier for testing */);
346 UserScriptLoader::UserScriptLoader(Profile* profile,
347 const ExtensionId& owner_extension_id,
348 bool listen_for_extension_system_loaded)
349 : user_scripts_(new UserScriptList()),
350 clear_scripts_(false),
351 extension_system_ready_(false),
352 pending_load_(false),
353 profile_(profile),
354 owner_extension_id_(owner_extension_id),
355 extension_registry_observer_(this),
356 weak_factory_(this) {
357 extension_registry_observer_.Add(ExtensionRegistry::Get(profile));
358 if (listen_for_extension_system_loaded) {
359 ExtensionSystem::Get(profile_)->ready().Post(
360 FROM_HERE,
361 base::Bind(&UserScriptLoader::OnExtensionSystemReady,
362 weak_factory_.GetWeakPtr()));
363 } else {
364 extension_system_ready_ = true;
366 registrar_.Add(this,
367 content::NOTIFICATION_RENDERER_PROCESS_CREATED,
368 content::NotificationService::AllBrowserContextsAndSources());
371 UserScriptLoader::~UserScriptLoader() {
374 void UserScriptLoader::AddScripts(const std::set<UserScript>& scripts) {
375 for (std::set<UserScript>::const_iterator it = scripts.begin();
376 it != scripts.end();
377 ++it) {
378 removed_scripts_.erase(*it);
379 added_scripts_.insert(*it);
381 AttemptLoad();
384 void UserScriptLoader::RemoveScripts(const std::set<UserScript>& scripts) {
385 for (std::set<UserScript>::const_iterator it = scripts.begin();
386 it != scripts.end();
387 ++it) {
388 added_scripts_.erase(*it);
389 removed_scripts_.insert(*it);
391 AttemptLoad();
394 void UserScriptLoader::ClearScripts() {
395 clear_scripts_ = true;
396 added_scripts_.clear();
397 removed_scripts_.clear();
398 AttemptLoad();
401 void UserScriptLoader::Observe(int type,
402 const content::NotificationSource& source,
403 const content::NotificationDetails& details) {
404 DCHECK_EQ(type, content::NOTIFICATION_RENDERER_PROCESS_CREATED);
405 content::RenderProcessHost* process =
406 content::Source<content::RenderProcessHost>(source).ptr();
407 Profile* profile = Profile::FromBrowserContext(process->GetBrowserContext());
408 if (!profile_->IsSameProfile(profile))
409 return;
410 if (scripts_ready()) {
411 SendUpdate(process,
412 shared_memory_.get(),
413 std::set<ExtensionId>()); // Include all extensions.
417 void UserScriptLoader::OnExtensionUnloaded(
418 content::BrowserContext* browser_context,
419 const Extension* extension,
420 UnloadedExtensionInfo::Reason reason) {
421 extensions_info_.erase(extension->id());
424 void UserScriptLoader::OnExtensionSystemReady() {
425 extension_system_ready_ = true;
426 AttemptLoad();
429 bool UserScriptLoader::ScriptsMayHaveChanged() const {
430 // Scripts may have changed if there are scripts added, scripts removed, or
431 // if scripts were cleared and either:
432 // (1) A load is in progress (which may result in a non-zero number of
433 // scripts that need to be cleared), or
434 // (2) The current set of scripts is non-empty (so they need to be cleared).
435 return (added_scripts_.size() ||
436 removed_scripts_.size() ||
437 (clear_scripts_ &&
438 (is_loading() || user_scripts_->size())));
441 void UserScriptLoader::AttemptLoad() {
442 if (extension_system_ready_ && ScriptsMayHaveChanged()) {
443 if (is_loading())
444 pending_load_ = true;
445 else
446 StartLoad();
450 void UserScriptLoader::StartLoad() {
451 DCHECK_CURRENTLY_ON(BrowserThread::UI);
452 DCHECK(!is_loading());
454 // If scripts were marked for clearing before adding and removing, then clear
455 // them.
456 if (clear_scripts_) {
457 user_scripts_->clear();
458 } else {
459 for (UserScriptList::iterator it = user_scripts_->begin();
460 it != user_scripts_->end();) {
461 if (removed_scripts_.count(*it))
462 it = user_scripts_->erase(it);
463 else
464 ++it;
468 user_scripts_->insert(
469 user_scripts_->end(), added_scripts_.begin(), added_scripts_.end());
471 std::set<int> added_script_ids;
472 for (std::set<UserScript>::const_iterator it = added_scripts_.begin();
473 it != added_scripts_.end();
474 ++it) {
475 added_script_ids.insert(it->id());
478 // Expand |changed_extensions_| for OnScriptsLoaded, which will use it in
479 // its IPC message. This must be done before we clear |added_scripts_| and
480 // |removed_scripts_| below.
481 std::set<UserScript> changed_scripts(added_scripts_);
482 changed_scripts.insert(removed_scripts_.begin(), removed_scripts_.end());
483 ExpandChangedExtensions(changed_scripts);
485 // Update |extensions_info_| to contain info from every extension in
486 // |changed_extensions_| before passing it to LoadScriptsOnFileThread.
487 UpdateExtensionsInfo();
489 BrowserThread::PostTask(
490 BrowserThread::FILE,
491 FROM_HERE,
492 base::Bind(&LoadScriptsOnFileThread,
493 base::Passed(&user_scripts_),
494 extensions_info_,
495 added_script_ids,
496 make_scoped_refptr(
497 ExtensionSystem::Get(profile_)->content_verifier()),
498 base::Bind(&UserScriptLoader::OnScriptsLoaded,
499 weak_factory_.GetWeakPtr())));
501 clear_scripts_ = false;
502 added_scripts_.clear();
503 removed_scripts_.clear();
504 user_scripts_.reset(NULL);
507 void UserScriptLoader::OnScriptsLoaded(
508 scoped_ptr<UserScriptList> user_scripts,
509 scoped_ptr<base::SharedMemory> shared_memory) {
510 user_scripts_.reset(user_scripts.release());
511 if (pending_load_) {
512 // While we were loading, there were further changes. Don't bother
513 // notifying about these scripts and instead just immediately reload.
514 pending_load_ = false;
515 StartLoad();
516 return;
519 if (shared_memory.get() == NULL) {
520 // This can happen if we run out of file descriptors. In that case, we
521 // have a choice between silently omitting all user scripts for new tabs,
522 // by nulling out shared_memory_, or only silently omitting new ones by
523 // leaving the existing object in place. The second seems less bad, even
524 // though it removes the possibility that freeing the shared memory block
525 // would open up enough FDs for long enough for a retry to succeed.
527 // Pretend the extension change didn't happen.
528 return;
531 // We've got scripts ready to go.
532 shared_memory_.reset(shared_memory.release());
534 for (content::RenderProcessHost::iterator i(
535 content::RenderProcessHost::AllHostsIterator());
536 !i.IsAtEnd();
537 i.Advance()) {
538 SendUpdate(i.GetCurrentValue(), shared_memory_.get(), changed_extensions_);
540 changed_extensions_.clear();
542 content::NotificationService::current()->Notify(
543 extensions::NOTIFICATION_USER_SCRIPTS_UPDATED,
544 content::Source<Profile>(profile_),
545 content::Details<base::SharedMemory>(shared_memory_.get()));
548 void UserScriptLoader::SendUpdate(
549 content::RenderProcessHost* process,
550 base::SharedMemory* shared_memory,
551 const std::set<ExtensionId>& changed_extensions) {
552 // Don't allow injection of content scripts into <webview>.
553 if (process->IsIsolatedGuest())
554 return;
556 Profile* profile = Profile::FromBrowserContext(process->GetBrowserContext());
557 // Make sure we only send user scripts to processes in our profile.
558 if (!profile_->IsSameProfile(profile))
559 return;
561 // If the process is being started asynchronously, early return. We'll end up
562 // calling InitUserScripts when it's created which will call this again.
563 base::ProcessHandle handle = process->GetHandle();
564 if (!handle)
565 return;
567 base::SharedMemoryHandle handle_for_process;
568 if (!shared_memory->ShareToProcess(handle, &handle_for_process))
569 return; // This can legitimately fail if the renderer asserts at startup.
571 if (base::SharedMemory::IsHandleValid(handle_for_process)) {
572 process->Send(new ExtensionMsg_UpdateUserScripts(
573 handle_for_process, owner_extension_id_, changed_extensions));
577 void UserScriptLoader::ExpandChangedExtensions(
578 const std::set<UserScript>& scripts) {
579 for (std::set<UserScript>::const_iterator it = scripts.begin();
580 it != scripts.end();
581 ++it) {
582 changed_extensions_.insert(it->extension_id());
586 void UserScriptLoader::UpdateExtensionsInfo() {
587 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
588 for (std::set<ExtensionId>::const_iterator it = changed_extensions_.begin();
589 it != changed_extensions_.end();
590 ++it) {
591 if (extensions_info_.find(*it) == extensions_info_.end()) {
592 const Extension* extension =
593 registry->GetExtensionById(*it, ExtensionRegistry::EVERYTHING);
594 // |changed_extensions_| may include extensions that have been removed,
595 // which leads to the above lookup failing. In this case, just continue.
596 if (!extension)
597 continue;
598 extensions_info_[*it] = ExtensionSet::ExtensionPathAndDefaultLocale(
599 extension->path(), LocaleInfo::GetDefaultLocale(extension));
604 } // namespace extensions