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/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
,
35 base::StringPiece::size_type index
= line
.find(prefix
);
36 if (index
== base::StringPiece::npos
)
39 std::string
temp(line
.data() + index
+ prefix
.length(),
40 line
.length() - index
- prefix
.length());
42 if (temp
.empty() || !IsWhitespace(temp
[0]))
45 base::TrimWhitespaceASCII(temp
, base::TRIM_ALL
, value
);
49 UserScriptMaster::ScriptReloader::ScriptReloader(UserScriptMaster
* master
)
51 CHECK(BrowserThread::GetCurrentThreadIdentifier(&master_thread_id_
));
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
);
88 if (line
.starts_with(kUserScriptBegin
))
91 if (line
.starts_with(kUserScriptEng
))
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
))
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
))
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
);
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("*");
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().
156 this->extensions_info_
= extensions_info_
;
157 BrowserThread::PostTask(
158 BrowserThread::FILE, FROM_HERE
,
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.
171 master_
->NewScriptsAvailable(memory
);
173 // Drop our self-reference.
174 // Balances StartLoad().
178 static bool LoadScriptContent(UserScript::File
* script_file
,
179 const SubstitutionMap
* localization_messages
) {
181 const base::FilePath
& path
= ExtensionResource::GetFilePath(
182 script_file
->extension_root(), script_file
->relative_path(),
183 ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT
);
186 if (extensions::ImageLoader::IsComponentExtensionResource(
187 script_file
->extension_root(), script_file
->relative_path(),
189 const ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
190 content
= rb
.GetRawDataResource(resource_id
).as_string();
192 LOG(WARNING
) << "Failed to get file path to "
193 << script_file
->relative_path().value() << " from "
194 << script_file
->extension_root().value();
198 if (!base::ReadFileToString(path
, &content
)) {
199 LOG(WARNING
) << "Failed to load user script file: " << path
.value();
204 // Localize the content.
205 if (localization_messages
) {
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
);
217 script_file
->set_content(content
.substr(strlen(base::kUtf8ByteOrderMark
)));
219 script_file
->set_content(content
);
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()) {
250 return file_util::LoadMessageBundleSubstitutionMap(
251 extensions_info_
[extension_id
].first
,
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
) {
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
))
287 if (!shared_memory
.Map(pickle
.size()))
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(),
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
,
312 &ScriptReloader::NotifyMaster
, this, Serialize(user_scripts
)));
315 UserScriptMaster::UserScriptMaster(Profile
* profile
)
316 : extensions_service_ready_(false),
317 pending_load_(false),
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
);
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;
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.
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();
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;
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();
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;
419 void UserScriptMaster::Observe(int type
,
420 const content::NotificationSource
& source
,
421 const content::NotificationDetails
& details
) {
422 bool should_start_load
= false;
424 case chrome::NOTIFICATION_EXTENSIONS_READY
:
425 extensions_service_ready_
= true;
426 should_start_load
= true;
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
))
436 SendUpdate(process
, GetSharedMemory());
443 if (should_start_load
) {
444 if (script_reloader_
.get()) {
445 pending_load_
= true;
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())
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
))
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();
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