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.
5 #include "chrome/browser/extensions/user_script_master.h"
10 #include "base/file_util.h"
11 #include "base/files/file_path.h"
12 #include "base/version.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/extensions/extension_service.h"
15 #include "chrome/browser/extensions/extension_util.h"
16 #include "chrome/browser/extensions/image_loader.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/content_verifier.h"
23 #include "extensions/browser/extension_registry.h"
24 #include "extensions/browser/extension_system.h"
25 #include "extensions/common/file_util.h"
26 #include "extensions/common/message_bundle.h"
27 #include "ui/base/resource/resource_bundle.h"
29 using content::BrowserThread;
31 namespace extensions {
33 // Helper function to parse greasesmonkey headers
34 static bool GetDeclarationValue(const base::StringPiece& line,
35 const base::StringPiece& prefix,
37 base::StringPiece::size_type index = line.find(prefix);
38 if (index == base::StringPiece::npos)
41 std::string temp(line.data() + index + prefix.length(),
42 line.length() - index - prefix.length());
44 if (temp.empty() || !IsWhitespace(temp[0]))
47 base::TrimWhitespaceASCII(temp, base::TRIM_ALL, value);
51 UserScriptMaster::ScriptReloader::ScriptReloader(UserScriptMaster* master)
53 CHECK(BrowserThread::GetCurrentThreadIdentifier(&master_thread_id_));
57 bool UserScriptMaster::ScriptReloader::ParseMetadataHeader(
58 const base::StringPiece& script_text, UserScript* script) {
59 // http://wiki.greasespot.net/Metadata_block
60 base::StringPiece line;
61 size_t line_start = 0;
62 size_t line_end = line_start;
63 bool in_metadata = false;
65 static const base::StringPiece kUserScriptBegin("// ==UserScript==");
66 static const base::StringPiece kUserScriptEng("// ==/UserScript==");
67 static const base::StringPiece kNamespaceDeclaration("// @namespace");
68 static const base::StringPiece kNameDeclaration("// @name");
69 static const base::StringPiece kVersionDeclaration("// @version");
70 static const base::StringPiece kDescriptionDeclaration("// @description");
71 static const base::StringPiece kIncludeDeclaration("// @include");
72 static const base::StringPiece kExcludeDeclaration("// @exclude");
73 static const base::StringPiece kMatchDeclaration("// @match");
74 static const base::StringPiece kExcludeMatchDeclaration("// @exclude_match");
75 static const base::StringPiece kRunAtDeclaration("// @run-at");
76 static const base::StringPiece kRunAtDocumentStartValue("document-start");
77 static const base::StringPiece kRunAtDocumentEndValue("document-end");
78 static const base::StringPiece kRunAtDocumentIdleValue("document-idle");
80 while (line_start < script_text.length()) {
81 line_end = script_text.find('\n', line_start);
83 // Handle the case where there is no trailing newline in the file.
84 if (line_end == std::string::npos)
85 line_end = script_text.length() - 1;
87 line.set(script_text.data() + line_start, line_end - line_start);
90 if (line.starts_with(kUserScriptBegin))
93 if (line.starts_with(kUserScriptEng))
97 if (GetDeclarationValue(line, kIncludeDeclaration, &value)) {
98 // We escape some characters that MatchPattern() considers special.
99 ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
100 ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
101 script->add_glob(value);
102 } else if (GetDeclarationValue(line, kExcludeDeclaration, &value)) {
103 ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
104 ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
105 script->add_exclude_glob(value);
106 } else if (GetDeclarationValue(line, kNamespaceDeclaration, &value)) {
107 script->set_name_space(value);
108 } else if (GetDeclarationValue(line, kNameDeclaration, &value)) {
109 script->set_name(value);
110 } else if (GetDeclarationValue(line, kVersionDeclaration, &value)) {
111 Version version(value);
112 if (version.IsValid())
113 script->set_version(version.GetString());
114 } else if (GetDeclarationValue(line, kDescriptionDeclaration, &value)) {
115 script->set_description(value);
116 } else if (GetDeclarationValue(line, kMatchDeclaration, &value)) {
117 URLPattern pattern(UserScript::ValidUserScriptSchemes());
118 if (URLPattern::PARSE_SUCCESS != pattern.Parse(value))
120 script->add_url_pattern(pattern);
121 } else if (GetDeclarationValue(line, kExcludeMatchDeclaration, &value)) {
122 URLPattern exclude(UserScript::ValidUserScriptSchemes());
123 if (URLPattern::PARSE_SUCCESS != exclude.Parse(value))
125 script->add_exclude_url_pattern(exclude);
126 } else if (GetDeclarationValue(line, kRunAtDeclaration, &value)) {
127 if (value == kRunAtDocumentStartValue)
128 script->set_run_location(UserScript::DOCUMENT_START);
129 else if (value == kRunAtDocumentEndValue)
130 script->set_run_location(UserScript::DOCUMENT_END);
131 else if (value == kRunAtDocumentIdleValue)
132 script->set_run_location(UserScript::DOCUMENT_IDLE);
137 // TODO(aa): Handle more types of metadata.
140 line_start = line_end + 1;
143 // If no patterns were specified, default to @include *. This is what
144 // Greasemonkey does.
145 if (script->globs().empty() && script->url_patterns().is_empty())
146 script->add_glob("*");
151 void UserScriptMaster::ScriptReloader::StartLoad(
152 const UserScriptList& user_scripts,
153 const ExtensionsInfo& extensions_info) {
154 // Add a reference to ourselves to keep ourselves alive while we're running.
155 // Balanced by NotifyMaster().
158 verifier_ = master_->content_verifier();
159 this->extensions_info_ = extensions_info;
160 BrowserThread::PostTask(
161 BrowserThread::FILE, FROM_HERE,
163 &UserScriptMaster::ScriptReloader::RunLoad, this, user_scripts));
166 UserScriptMaster::ScriptReloader::~ScriptReloader() {}
168 void UserScriptMaster::ScriptReloader::NotifyMaster(
169 base::SharedMemory* memory) {
170 // The master went away, so these new scripts aren't useful anymore.
174 master_->NewScriptsAvailable(memory);
176 // Drop our self-reference.
177 // Balances StartLoad().
181 static void VerifyContent(ContentVerifier* verifier,
182 const std::string& extension_id,
183 const base::FilePath& extension_root,
184 const base::FilePath& relative_path,
185 const std::string& content) {
186 scoped_refptr<ContentVerifyJob> job(
187 verifier->CreateJobFor(extension_id, extension_root, relative_path));
190 job->BytesRead(content.size(), content.data());
195 static bool LoadScriptContent(const std::string& extension_id,
196 UserScript::File* script_file,
197 const SubstitutionMap* localization_messages,
198 ContentVerifier* verifier) {
200 const base::FilePath& path = ExtensionResource::GetFilePath(
201 script_file->extension_root(), script_file->relative_path(),
202 ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT);
205 if (extensions::ImageLoader::IsComponentExtensionResource(
206 script_file->extension_root(), script_file->relative_path(),
208 const ResourceBundle& rb = ResourceBundle::GetSharedInstance();
209 content = rb.GetRawDataResource(resource_id).as_string();
211 LOG(WARNING) << "Failed to get file path to "
212 << script_file->relative_path().value() << " from "
213 << script_file->extension_root().value();
217 if (!base::ReadFileToString(path, &content)) {
218 LOG(WARNING) << "Failed to load user script file: " << path.value();
222 VerifyContent(verifier,
224 script_file->extension_root(),
225 script_file->relative_path(),
230 // Localize the content.
231 if (localization_messages) {
233 MessageBundle::ReplaceMessagesWithExternalDictionary(
234 *localization_messages, &content, &error);
235 if (!error.empty()) {
236 LOG(WARNING) << "Failed to replace messages in script: " << error;
240 // Remove BOM from the content.
241 std::string::size_type index = content.find(base::kUtf8ByteOrderMark);
243 script_file->set_content(content.substr(strlen(base::kUtf8ByteOrderMark)));
245 script_file->set_content(content);
251 void UserScriptMaster::ScriptReloader::LoadUserScripts(
252 UserScriptList* user_scripts) {
253 for (size_t i = 0; i < user_scripts->size(); ++i) {
254 UserScript& script = user_scripts->at(i);
255 scoped_ptr<SubstitutionMap> localization_messages(
256 GetLocalizationMessages(script.extension_id()));
257 for (size_t k = 0; k < script.js_scripts().size(); ++k) {
258 UserScript::File& script_file = script.js_scripts()[k];
259 if (script_file.GetContent().empty())
261 script.extension_id(), &script_file, NULL, verifier_.get());
263 for (size_t k = 0; k < script.css_scripts().size(); ++k) {
264 UserScript::File& script_file = script.css_scripts()[k];
265 if (script_file.GetContent().empty())
266 LoadScriptContent(script.extension_id(),
268 localization_messages.get(),
274 SubstitutionMap* UserScriptMaster::ScriptReloader::GetLocalizationMessages(
275 const std::string& extension_id) {
276 if (extensions_info_.find(extension_id) == extensions_info_.end()) {
280 return file_util::LoadMessageBundleSubstitutionMap(
281 extensions_info_[extension_id].first,
283 extensions_info_[extension_id].second);
286 // Pickle user scripts and return pointer to the shared memory.
287 static base::SharedMemory* Serialize(const UserScriptList& scripts) {
289 pickle.WriteUInt64(scripts.size());
290 for (size_t i = 0; i < scripts.size(); i++) {
291 const UserScript& script = scripts[i];
292 // TODO(aa): This can be replaced by sending content script metadata to
293 // renderers along with other extension data in ExtensionMsg_Loaded.
294 // See crbug.com/70516.
295 script.Pickle(&pickle);
296 // Write scripts as 'data' so that we can read it out in the slave without
297 // allocating a new string.
298 for (size_t j = 0; j < script.js_scripts().size(); j++) {
299 base::StringPiece contents = script.js_scripts()[j].GetContent();
300 pickle.WriteData(contents.data(), contents.length());
302 for (size_t j = 0; j < script.css_scripts().size(); j++) {
303 base::StringPiece contents = script.css_scripts()[j].GetContent();
304 pickle.WriteData(contents.data(), contents.length());
308 // Create the shared memory object.
309 base::SharedMemory shared_memory;
311 base::SharedMemoryCreateOptions options;
312 options.size = pickle.size();
313 options.share_read_only = true;
314 if (!shared_memory.Create(options))
317 if (!shared_memory.Map(pickle.size()))
320 // Copy the pickle to shared memory.
321 memcpy(shared_memory.memory(), pickle.data(), pickle.size());
323 base::SharedMemoryHandle readonly_handle;
324 if (!shared_memory.ShareReadOnlyToProcess(base::GetCurrentProcessHandle(),
328 return new base::SharedMemory(readonly_handle, /*read_only=*/true);
331 // This method will be called on the file thread.
332 void UserScriptMaster::ScriptReloader::RunLoad(
333 const UserScriptList& user_scripts) {
334 LoadUserScripts(const_cast<UserScriptList*>(&user_scripts));
336 // Scripts now contains list of up-to-date scripts. Load the content in the
337 // shared memory and let the master know it's ready. We need to post the task
338 // back even if no scripts ware found to balance the AddRef/Release calls.
339 BrowserThread::PostTask(
340 master_thread_id_, FROM_HERE,
342 &ScriptReloader::NotifyMaster, this, Serialize(user_scripts)));
345 UserScriptMaster::UserScriptMaster(Profile* profile)
346 : extensions_service_ready_(false),
347 pending_load_(false),
349 extension_registry_observer_(this) {
350 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
351 registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
352 content::Source<Profile>(profile_));
353 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED,
354 content::NotificationService::AllBrowserContextsAndSources());
357 UserScriptMaster::~UserScriptMaster() {
358 if (script_reloader_.get())
359 script_reloader_->DisownMaster();
362 void UserScriptMaster::NewScriptsAvailable(base::SharedMemory* handle) {
363 // Ensure handle is deleted or released.
364 scoped_ptr<base::SharedMemory> handle_deleter(handle);
367 // While we were loading, there were further changes. Don't bother
368 // notifying about these scripts and instead just immediately reload.
369 pending_load_ = false;
372 // We're no longer loading.
373 script_reloader_ = NULL;
375 if (handle == 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.
383 // Pretend the extension change didn't happen.
387 // We've got scripts ready to go.
388 shared_memory_.swap(handle_deleter);
390 for (content::RenderProcessHost::iterator i(
391 content::RenderProcessHost::AllHostsIterator());
392 !i.IsAtEnd(); i.Advance()) {
393 SendUpdate(i.GetCurrentValue(), handle);
396 content::NotificationService::current()->Notify(
397 chrome::NOTIFICATION_USER_SCRIPTS_UPDATED,
398 content::Source<Profile>(profile_),
399 content::Details<base::SharedMemory>(handle));
403 ContentVerifier* UserScriptMaster::content_verifier() {
404 ExtensionSystem* system = ExtensionSystem::Get(profile_);
405 return system->content_verifier();
408 void UserScriptMaster::OnExtensionLoaded(
409 content::BrowserContext* browser_context,
410 const Extension* extension) {
411 // Add any content scripts inside the extension.
412 extensions_info_[extension->id()] =
413 ExtensionSet::ExtensionPathAndDefaultLocale(
414 extension->path(), LocaleInfo::GetDefaultLocale(extension));
415 bool incognito_enabled = util::IsIncognitoEnabled(extension->id(), profile_);
416 const UserScriptList& scripts =
417 ContentScriptsInfo::GetContentScripts(extension);
418 for (UserScriptList::const_iterator iter = scripts.begin();
419 iter != scripts.end();
421 user_scripts_.push_back(*iter);
422 user_scripts_.back().set_incognito_enabled(incognito_enabled);
424 if (extensions_service_ready_) {
425 if (script_reloader_.get()) {
426 pending_load_ = true;
433 void UserScriptMaster::OnExtensionUnloaded(
434 content::BrowserContext* browser_context,
435 const Extension* extension,
436 UnloadedExtensionInfo::Reason reason) {
437 // Remove any content scripts.
438 extensions_info_.erase(extension->id());
439 UserScriptList new_user_scripts;
440 for (UserScriptList::iterator iter = user_scripts_.begin();
441 iter != user_scripts_.end();
443 if (iter->extension_id() != extension->id())
444 new_user_scripts.push_back(*iter);
446 user_scripts_ = new_user_scripts;
447 if (script_reloader_.get()) {
448 pending_load_ = true;
454 void UserScriptMaster::Observe(int type,
455 const content::NotificationSource& source,
456 const content::NotificationDetails& details) {
457 bool should_start_load = false;
459 case chrome::NOTIFICATION_EXTENSIONS_READY:
460 extensions_service_ready_ = true;
461 should_start_load = true;
463 case content::NOTIFICATION_RENDERER_PROCESS_CREATED: {
464 content::RenderProcessHost* process =
465 content::Source<content::RenderProcessHost>(source).ptr();
466 Profile* profile = Profile::FromBrowserContext(
467 process->GetBrowserContext());
468 if (!profile_->IsSameProfile(profile))
471 SendUpdate(process, GetSharedMemory());
478 if (should_start_load) {
479 if (script_reloader_.get()) {
480 pending_load_ = true;
487 void UserScriptMaster::StartLoad() {
488 if (!script_reloader_.get())
489 script_reloader_ = new ScriptReloader(this);
491 script_reloader_->StartLoad(user_scripts_, extensions_info_);
494 void UserScriptMaster::SendUpdate(content::RenderProcessHost* process,
495 base::SharedMemory* shared_memory) {
496 // Don't allow injection of content scripts into <webview>.
497 if (process->IsGuest())
500 Profile* profile = Profile::FromBrowserContext(process->GetBrowserContext());
501 // Make sure we only send user scripts to processes in our profile.
502 if (!profile_->IsSameProfile(profile))
505 // If the process is being started asynchronously, early return. We'll end up
506 // calling InitUserScripts when it's created which will call this again.
507 base::ProcessHandle handle = process->GetHandle();
511 base::SharedMemoryHandle handle_for_process;
512 if (!shared_memory->ShareToProcess(handle, &handle_for_process))
513 return; // This can legitimately fail if the renderer asserts at startup.
515 if (base::SharedMemory::IsHandleValid(handle_for_process))
516 process->Send(new ExtensionMsg_UpdateUserScripts(handle_for_process));
519 } // namespace extensions