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"
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
{
40 typedef base::Callback
<
41 void(scoped_ptr
<UserScriptList
>, scoped_ptr
<base::SharedMemory
>)>
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
));
54 job
->BytesRead(content
.size(), content
.data());
59 bool LoadScriptContent(const ExtensionId
& extension_id
,
60 UserScript::File
* script_file
,
61 const SubstitutionMap
* localization_messages
,
62 scoped_refptr
<ContentVerifier
> verifier
) {
64 const base::FilePath
& path
= ExtensionResource::GetFilePath(
65 script_file
->extension_root(),
66 script_file
->relative_path(),
67 ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT
);
70 if (ExtensionsBrowserClient::Get()->GetComponentExtensionResourceManager()->
71 IsComponentExtensionResource(script_file
->extension_root(),
72 script_file
->relative_path(),
74 const ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
75 content
= rb
.GetRawDataResource(resource_id
).as_string();
77 LOG(WARNING
) << "Failed to get file path to "
78 << script_file
->relative_path().value() << " from "
79 << script_file
->extension_root().value();
83 if (!base::ReadFileToString(path
, &content
)) {
84 LOG(WARNING
) << "Failed to load user script file: " << path
.value();
88 content::BrowserThread::PostTask(content::BrowserThread::IO
,
90 base::Bind(&VerifyContent
,
93 script_file
->extension_root(),
94 script_file
->relative_path(),
99 // Localize the content.
100 if (localization_messages
) {
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
);
112 script_file
->set_content(content
.substr(strlen(base::kUtf8ByteOrderMark
)));
114 script_file
->set_content(content
);
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())
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();
136 if (added_script_ids
.count(script
->id()) == 0)
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(),
150 localization_messages
.get(),
156 // Pickle user scripts and return pointer to the shared memory.
157 scoped_ptr
<base::SharedMemory
> Serialize(const UserScriptList
& scripts
) {
159 pickle
.WriteSizeT(scripts
.size());
160 for (UserScriptList::const_iterator script
= scripts
.begin();
161 script
!= scripts
.end();
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(),
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());
210 user_scripts
.get(), extensions_info
, added_script_ids
, verifier
.get());
211 scoped_ptr
<base::SharedMemory
> memory
= Serialize(*user_scripts
);
212 BrowserThread::PostTask(
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
)
226 std::string
temp(line
.data() + index
+ prefix
.length(),
227 line
.length() - index
- prefix
.length());
229 if (temp
.empty() || !IsWhitespace(temp
[0]))
232 base::TrimWhitespaceASCII(temp
, base::TRIM_ALL
, value
);
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
);
272 if (line
.starts_with(kUserScriptBegin
))
275 if (line
.starts_with(kUserScriptEng
))
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
))
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
))
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
);
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("*");
334 void UserScriptLoader::LoadScriptsForTest(UserScriptList
* user_scripts
) {
336 std::set
<int> added_script_ids
;
337 for (UserScriptList::iterator it
= user_scripts
->begin();
338 it
!= user_scripts
->end();
340 added_script_ids
.insert(it
->id());
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),
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(
361 base::Bind(&UserScriptLoader::OnExtensionSystemReady
,
362 weak_factory_
.GetWeakPtr()));
364 extension_system_ready_
= true;
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();
378 removed_scripts_
.erase(*it
);
379 added_scripts_
.insert(*it
);
384 void UserScriptLoader::RemoveScripts(const std::set
<UserScript
>& scripts
) {
385 for (std::set
<UserScript
>::const_iterator it
= scripts
.begin();
388 added_scripts_
.erase(*it
);
389 removed_scripts_
.insert(*it
);
394 void UserScriptLoader::ClearScripts() {
395 clear_scripts_
= true;
396 added_scripts_
.clear();
397 removed_scripts_
.clear();
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
))
410 if (scripts_ready()) {
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;
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() ||
438 (is_loading() || user_scripts_
->size())));
441 void UserScriptLoader::AttemptLoad() {
442 if (extension_system_ready_
&& ScriptsMayHaveChanged()) {
444 pending_load_
= true;
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
456 if (clear_scripts_
) {
457 user_scripts_
->clear();
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
);
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();
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(
492 base::Bind(&LoadScriptsOnFileThread
,
493 base::Passed(&user_scripts_
),
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());
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;
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.
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());
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())
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
))
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();
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();
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();
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.
598 extensions_info_
[*it
] = ExtensionSet::ExtensionPathAndDefaultLocale(
599 extension
->path(), LocaleInfo::GetDefaultLocale(extension
));
604 } // namespace extensions