Cast: Stop logging kVideoFrameSentToEncoder and rename a couple events.
[chromium-blink-merge.git] / chrome / browser / extensions / user_script_master.cc
blob8a56f45d9156dcd2d7f7d83b15305ccb779ba13d
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"
7 #include <string>
9 #include "base/bind.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/extension_registry.h"
23 #include "extensions/common/file_util.h"
24 #include "extensions/common/message_bundle.h"
25 #include "ui/base/resource/resource_bundle.h"
27 using content::BrowserThread;
29 namespace extensions {
31 // Helper function to parse greasesmonkey headers
32 static bool GetDeclarationValue(const base::StringPiece& line,
33 const base::StringPiece& prefix,
34 std::string* value) {
35 base::StringPiece::size_type index = line.find(prefix);
36 if (index == base::StringPiece::npos)
37 return false;
39 std::string temp(line.data() + index + prefix.length(),
40 line.length() - index - prefix.length());
42 if (temp.empty() || !IsWhitespace(temp[0]))
43 return false;
45 base::TrimWhitespaceASCII(temp, base::TRIM_ALL, value);
46 return true;
49 UserScriptMaster::ScriptReloader::ScriptReloader(UserScriptMaster* master)
50 : master_(master) {
51 CHECK(BrowserThread::GetCurrentThreadIdentifier(&master_thread_id_));
54 // static
55 bool UserScriptMaster::ScriptReloader::ParseMetadataHeader(
56 const base::StringPiece& script_text, UserScript* script) {
57 // http://wiki.greasespot.net/Metadata_block
58 base::StringPiece line;
59 size_t line_start = 0;
60 size_t line_end = line_start;
61 bool in_metadata = false;
63 static const base::StringPiece kUserScriptBegin("// ==UserScript==");
64 static const base::StringPiece kUserScriptEng("// ==/UserScript==");
65 static const base::StringPiece kNamespaceDeclaration("// @namespace");
66 static const base::StringPiece kNameDeclaration("// @name");
67 static const base::StringPiece kVersionDeclaration("// @version");
68 static const base::StringPiece kDescriptionDeclaration("// @description");
69 static const base::StringPiece kIncludeDeclaration("// @include");
70 static const base::StringPiece kExcludeDeclaration("// @exclude");
71 static const base::StringPiece kMatchDeclaration("// @match");
72 static const base::StringPiece kExcludeMatchDeclaration("// @exclude_match");
73 static const base::StringPiece kRunAtDeclaration("// @run-at");
74 static const base::StringPiece kRunAtDocumentStartValue("document-start");
75 static const base::StringPiece kRunAtDocumentEndValue("document-end");
76 static const base::StringPiece kRunAtDocumentIdleValue("document-idle");
78 while (line_start < script_text.length()) {
79 line_end = script_text.find('\n', line_start);
81 // Handle the case where there is no trailing newline in the file.
82 if (line_end == std::string::npos)
83 line_end = script_text.length() - 1;
85 line.set(script_text.data() + line_start, line_end - line_start);
87 if (!in_metadata) {
88 if (line.starts_with(kUserScriptBegin))
89 in_metadata = true;
90 } else {
91 if (line.starts_with(kUserScriptEng))
92 break;
94 std::string value;
95 if (GetDeclarationValue(line, kIncludeDeclaration, &value)) {
96 // We escape some characters that MatchPattern() considers special.
97 ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
98 ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
99 script->add_glob(value);
100 } else if (GetDeclarationValue(line, kExcludeDeclaration, &value)) {
101 ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
102 ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
103 script->add_exclude_glob(value);
104 } else if (GetDeclarationValue(line, kNamespaceDeclaration, &value)) {
105 script->set_name_space(value);
106 } else if (GetDeclarationValue(line, kNameDeclaration, &value)) {
107 script->set_name(value);
108 } else if (GetDeclarationValue(line, kVersionDeclaration, &value)) {
109 Version version(value);
110 if (version.IsValid())
111 script->set_version(version.GetString());
112 } else if (GetDeclarationValue(line, kDescriptionDeclaration, &value)) {
113 script->set_description(value);
114 } else if (GetDeclarationValue(line, kMatchDeclaration, &value)) {
115 URLPattern pattern(UserScript::ValidUserScriptSchemes());
116 if (URLPattern::PARSE_SUCCESS != pattern.Parse(value))
117 return false;
118 script->add_url_pattern(pattern);
119 } else if (GetDeclarationValue(line, kExcludeMatchDeclaration, &value)) {
120 URLPattern exclude(UserScript::ValidUserScriptSchemes());
121 if (URLPattern::PARSE_SUCCESS != exclude.Parse(value))
122 return false;
123 script->add_exclude_url_pattern(exclude);
124 } else if (GetDeclarationValue(line, kRunAtDeclaration, &value)) {
125 if (value == kRunAtDocumentStartValue)
126 script->set_run_location(UserScript::DOCUMENT_START);
127 else if (value == kRunAtDocumentEndValue)
128 script->set_run_location(UserScript::DOCUMENT_END);
129 else if (value == kRunAtDocumentIdleValue)
130 script->set_run_location(UserScript::DOCUMENT_IDLE);
131 else
132 return false;
135 // TODO(aa): Handle more types of metadata.
138 line_start = line_end + 1;
141 // If no patterns were specified, default to @include *. This is what
142 // Greasemonkey does.
143 if (script->globs().empty() && script->url_patterns().is_empty())
144 script->add_glob("*");
146 return true;
149 void UserScriptMaster::ScriptReloader::StartLoad(
150 const UserScriptList& user_scripts,
151 const ExtensionsInfo& extensions_info_) {
152 // Add a reference to ourselves to keep ourselves alive while we're running.
153 // Balanced by NotifyMaster().
154 AddRef();
156 this->extensions_info_ = extensions_info_;
157 BrowserThread::PostTask(
158 BrowserThread::FILE, FROM_HERE,
159 base::Bind(
160 &UserScriptMaster::ScriptReloader::RunLoad, this, user_scripts));
163 UserScriptMaster::ScriptReloader::~ScriptReloader() {}
165 void UserScriptMaster::ScriptReloader::NotifyMaster(
166 base::SharedMemory* memory) {
167 // The master went away, so these new scripts aren't useful anymore.
168 if (!master_)
169 delete memory;
170 else
171 master_->NewScriptsAvailable(memory);
173 // Drop our self-reference.
174 // Balances StartLoad().
175 Release();
178 static bool LoadScriptContent(UserScript::File* script_file,
179 const SubstitutionMap* localization_messages) {
180 std::string content;
181 const base::FilePath& path = ExtensionResource::GetFilePath(
182 script_file->extension_root(), script_file->relative_path(),
183 ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT);
184 if (path.empty()) {
185 int resource_id;
186 if (extensions::ImageLoader::IsComponentExtensionResource(
187 script_file->extension_root(), script_file->relative_path(),
188 &resource_id)) {
189 const ResourceBundle& rb = ResourceBundle::GetSharedInstance();
190 content = rb.GetRawDataResource(resource_id).as_string();
191 } else {
192 LOG(WARNING) << "Failed to get file path to "
193 << script_file->relative_path().value() << " from "
194 << script_file->extension_root().value();
195 return false;
197 } else {
198 if (!base::ReadFileToString(path, &content)) {
199 LOG(WARNING) << "Failed to load user script file: " << path.value();
200 return false;
204 // Localize the content.
205 if (localization_messages) {
206 std::string error;
207 MessageBundle::ReplaceMessagesWithExternalDictionary(
208 *localization_messages, &content, &error);
209 if (!error.empty()) {
210 LOG(WARNING) << "Failed to replace messages in script: " << error;
214 // Remove BOM from the content.
215 std::string::size_type index = content.find(base::kUtf8ByteOrderMark);
216 if (index == 0) {
217 script_file->set_content(content.substr(strlen(base::kUtf8ByteOrderMark)));
218 } else {
219 script_file->set_content(content);
222 return true;
225 void UserScriptMaster::ScriptReloader::LoadUserScripts(
226 UserScriptList* user_scripts) {
227 for (size_t i = 0; i < user_scripts->size(); ++i) {
228 UserScript& script = user_scripts->at(i);
229 scoped_ptr<SubstitutionMap> localization_messages(
230 GetLocalizationMessages(script.extension_id()));
231 for (size_t k = 0; k < script.js_scripts().size(); ++k) {
232 UserScript::File& script_file = script.js_scripts()[k];
233 if (script_file.GetContent().empty())
234 LoadScriptContent(&script_file, NULL);
236 for (size_t k = 0; k < script.css_scripts().size(); ++k) {
237 UserScript::File& script_file = script.css_scripts()[k];
238 if (script_file.GetContent().empty())
239 LoadScriptContent(&script_file, localization_messages.get());
244 SubstitutionMap* UserScriptMaster::ScriptReloader::GetLocalizationMessages(
245 const std::string& extension_id) {
246 if (extensions_info_.find(extension_id) == extensions_info_.end()) {
247 return NULL;
250 return file_util::LoadMessageBundleSubstitutionMap(
251 extensions_info_[extension_id].first,
252 extension_id,
253 extensions_info_[extension_id].second);
256 // Pickle user scripts and return pointer to the shared memory.
257 static base::SharedMemory* Serialize(const UserScriptList& scripts) {
258 Pickle pickle;
259 pickle.WriteUInt64(scripts.size());
260 for (size_t i = 0; i < scripts.size(); i++) {
261 const UserScript& script = scripts[i];
262 // TODO(aa): This can be replaced by sending content script metadata to
263 // renderers along with other extension data in ExtensionMsg_Loaded.
264 // See crbug.com/70516.
265 script.Pickle(&pickle);
266 // Write scripts as 'data' so that we can read it out in the slave without
267 // allocating a new string.
268 for (size_t j = 0; j < script.js_scripts().size(); j++) {
269 base::StringPiece contents = script.js_scripts()[j].GetContent();
270 pickle.WriteData(contents.data(), contents.length());
272 for (size_t j = 0; j < script.css_scripts().size(); j++) {
273 base::StringPiece contents = script.css_scripts()[j].GetContent();
274 pickle.WriteData(contents.data(), contents.length());
278 // Create the shared memory object.
279 base::SharedMemory shared_memory;
281 base::SharedMemoryCreateOptions options;
282 options.size = pickle.size();
283 options.share_read_only = true;
284 if (!shared_memory.Create(options))
285 return NULL;
287 if (!shared_memory.Map(pickle.size()))
288 return NULL;
290 // Copy the pickle to shared memory.
291 memcpy(shared_memory.memory(), pickle.data(), pickle.size());
293 base::SharedMemoryHandle readonly_handle;
294 if (!shared_memory.ShareReadOnlyToProcess(base::GetCurrentProcessHandle(),
295 &readonly_handle))
296 return NULL;
298 return new base::SharedMemory(readonly_handle, /*read_only=*/true);
301 // This method will be called on the file thread.
302 void UserScriptMaster::ScriptReloader::RunLoad(
303 const UserScriptList& user_scripts) {
304 LoadUserScripts(const_cast<UserScriptList*>(&user_scripts));
306 // Scripts now contains list of up-to-date scripts. Load the content in the
307 // shared memory and let the master know it's ready. We need to post the task
308 // back even if no scripts ware found to balance the AddRef/Release calls.
309 BrowserThread::PostTask(
310 master_thread_id_, FROM_HERE,
311 base::Bind(
312 &ScriptReloader::NotifyMaster, this, Serialize(user_scripts)));
315 UserScriptMaster::UserScriptMaster(Profile* profile)
316 : extensions_service_ready_(false),
317 pending_load_(false),
318 profile_(profile),
319 extension_registry_observer_(this) {
320 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
321 registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
322 content::Source<Profile>(profile_));
323 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED,
324 content::NotificationService::AllBrowserContextsAndSources());
327 UserScriptMaster::~UserScriptMaster() {
328 if (script_reloader_.get())
329 script_reloader_->DisownMaster();
332 void UserScriptMaster::NewScriptsAvailable(base::SharedMemory* handle) {
333 // Ensure handle is deleted or released.
334 scoped_ptr<base::SharedMemory> handle_deleter(handle);
336 if (pending_load_) {
337 // While we were loading, there were further changes. Don't bother
338 // notifying about these scripts and instead just immediately reload.
339 pending_load_ = false;
340 StartLoad();
341 } else {
342 // We're no longer loading.
343 script_reloader_ = NULL;
345 if (handle == NULL) {
346 // This can happen if we run out of file descriptors. In that case, we
347 // have a choice between silently omitting all user scripts for new tabs,
348 // by nulling out shared_memory_, or only silently omitting new ones by
349 // leaving the existing object in place. The second seems less bad, even
350 // though it removes the possibility that freeing the shared memory block
351 // would open up enough FDs for long enough for a retry to succeed.
353 // Pretend the extension change didn't happen.
354 return;
357 // We've got scripts ready to go.
358 shared_memory_.swap(handle_deleter);
360 for (content::RenderProcessHost::iterator i(
361 content::RenderProcessHost::AllHostsIterator());
362 !i.IsAtEnd(); i.Advance()) {
363 SendUpdate(i.GetCurrentValue(), handle);
366 content::NotificationService::current()->Notify(
367 chrome::NOTIFICATION_USER_SCRIPTS_UPDATED,
368 content::Source<Profile>(profile_),
369 content::Details<base::SharedMemory>(handle));
373 void UserScriptMaster::OnExtensionLoaded(
374 content::BrowserContext* browser_context,
375 const Extension* extension) {
376 // Add any content scripts inside the extension.
377 extensions_info_[extension->id()] =
378 ExtensionSet::ExtensionPathAndDefaultLocale(
379 extension->path(), LocaleInfo::GetDefaultLocale(extension));
380 bool incognito_enabled = util::IsIncognitoEnabled(extension->id(), profile_);
381 const UserScriptList& scripts =
382 ContentScriptsInfo::GetContentScripts(extension);
383 for (UserScriptList::const_iterator iter = scripts.begin();
384 iter != scripts.end();
385 ++iter) {
386 user_scripts_.push_back(*iter);
387 user_scripts_.back().set_incognito_enabled(incognito_enabled);
389 if (extensions_service_ready_) {
390 if (script_reloader_.get()) {
391 pending_load_ = true;
392 } else {
393 StartLoad();
398 void UserScriptMaster::OnExtensionUnloaded(
399 content::BrowserContext* browser_context,
400 const Extension* extension,
401 UnloadedExtensionInfo::Reason reason) {
402 // Remove any content scripts.
403 extensions_info_.erase(extension->id());
404 UserScriptList new_user_scripts;
405 for (UserScriptList::iterator iter = user_scripts_.begin();
406 iter != user_scripts_.end();
407 ++iter) {
408 if (iter->extension_id() != extension->id())
409 new_user_scripts.push_back(*iter);
411 user_scripts_ = new_user_scripts;
412 if (script_reloader_.get()) {
413 pending_load_ = true;
414 } else {
415 StartLoad();
419 void UserScriptMaster::Observe(int type,
420 const content::NotificationSource& source,
421 const content::NotificationDetails& details) {
422 bool should_start_load = false;
423 switch (type) {
424 case chrome::NOTIFICATION_EXTENSIONS_READY:
425 extensions_service_ready_ = true;
426 should_start_load = true;
427 break;
428 case content::NOTIFICATION_RENDERER_PROCESS_CREATED: {
429 content::RenderProcessHost* process =
430 content::Source<content::RenderProcessHost>(source).ptr();
431 Profile* profile = Profile::FromBrowserContext(
432 process->GetBrowserContext());
433 if (!profile_->IsSameProfile(profile))
434 return;
435 if (ScriptsReady())
436 SendUpdate(process, GetSharedMemory());
437 break;
439 default:
440 DCHECK(false);
443 if (should_start_load) {
444 if (script_reloader_.get()) {
445 pending_load_ = true;
446 } else {
447 StartLoad();
452 void UserScriptMaster::StartLoad() {
453 if (!script_reloader_.get())
454 script_reloader_ = new ScriptReloader(this);
456 script_reloader_->StartLoad(user_scripts_, extensions_info_);
459 void UserScriptMaster::SendUpdate(content::RenderProcessHost* process,
460 base::SharedMemory* shared_memory) {
461 // Don't allow injection of content scripts into <webview>.
462 if (process->IsGuest())
463 return;
465 Profile* profile = Profile::FromBrowserContext(process->GetBrowserContext());
466 // Make sure we only send user scripts to processes in our profile.
467 if (!profile_->IsSameProfile(profile))
468 return;
470 // If the process is being started asynchronously, early return. We'll end up
471 // calling InitUserScripts when it's created which will call this again.
472 base::ProcessHandle handle = process->GetHandle();
473 if (!handle)
474 return;
476 base::SharedMemoryHandle handle_for_process;
477 if (!shared_memory->ShareToProcess(handle, &handle_for_process))
478 return; // This can legitimately fail if the renderer asserts at startup.
480 if (base::SharedMemory::IsHandleValid(handle_for_process))
481 process->Send(new ExtensionMsg_UpdateUserScripts(handle_for_process));
484 } // namespace extensions