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 "extensions/browser/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/version.h"
15 #include "content/public/browser/browser_context.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/notification_service.h"
18 #include "content/public/browser/notification_types.h"
19 #include "content/public/browser/render_process_host.h"
20 #include "extensions/browser/content_verifier.h"
21 #include "extensions/browser/extensions_browser_client.h"
22 #include "extensions/browser/notification_types.h"
23 #include "extensions/common/extension_messages.h"
24 #include "extensions/common/file_util.h"
26 using content::BrowserThread
;
27 using content::BrowserContext
;
29 namespace extensions
{
33 using LoadScriptsCallback
=
34 base::Callback
<void(scoped_ptr
<UserScriptList
>,
35 scoped_ptr
<base::SharedMemory
>)>;
37 UserScriptLoader::SubstitutionMap
* GetLocalizationMessages(
38 const UserScriptLoader::HostsInfo
& hosts_info
,
39 const HostID
& host_id
) {
40 UserScriptLoader::HostsInfo::const_iterator iter
= hosts_info
.find(host_id
);
41 if (iter
== hosts_info
.end())
43 return file_util::LoadMessageBundleSubstitutionMap(
44 iter
->second
.first
, host_id
.id(), iter
->second
.second
);
48 UserScriptList
* user_scripts
,
49 const UserScriptLoader::HostsInfo
& hosts_info
,
50 const std::set
<int>& added_script_ids
,
51 const scoped_refptr
<ContentVerifier
>& verifier
,
52 UserScriptLoader::LoadUserScriptsContentFunction callback
) {
53 for (UserScriptList::iterator script
= user_scripts
->begin();
54 script
!= user_scripts
->end();
56 if (added_script_ids
.count(script
->id()) == 0)
58 scoped_ptr
<UserScriptLoader::SubstitutionMap
> localization_messages(
59 GetLocalizationMessages(hosts_info
, script
->host_id()));
60 for (size_t k
= 0; k
< script
->js_scripts().size(); ++k
) {
61 UserScript::File
& script_file
= script
->js_scripts()[k
];
62 if (script_file
.GetContent().empty())
63 callback
.Run(script
->host_id(), &script_file
, NULL
, verifier
);
65 for (size_t k
= 0; k
< script
->css_scripts().size(); ++k
) {
66 UserScript::File
& script_file
= script
->css_scripts()[k
];
67 if (script_file
.GetContent().empty())
68 callback
.Run(script
->host_id(), &script_file
,
69 localization_messages
.get(), verifier
);
74 // Pickle user scripts and return pointer to the shared memory.
75 scoped_ptr
<base::SharedMemory
> Serialize(const UserScriptList
& scripts
) {
77 pickle
.WriteSizeT(scripts
.size());
78 for (UserScriptList::const_iterator script
= scripts
.begin();
79 script
!= scripts
.end();
81 // TODO(aa): This can be replaced by sending content script metadata to
82 // renderers along with other extension data in ExtensionMsg_Loaded.
83 // See crbug.com/70516.
84 script
->Pickle(&pickle
);
85 // Write scripts as 'data' so that we can read it out in the slave without
86 // allocating a new string.
87 for (size_t j
= 0; j
< script
->js_scripts().size(); j
++) {
88 base::StringPiece contents
= script
->js_scripts()[j
].GetContent();
89 pickle
.WriteData(contents
.data(), contents
.length());
91 for (size_t j
= 0; j
< script
->css_scripts().size(); j
++) {
92 base::StringPiece contents
= script
->css_scripts()[j
].GetContent();
93 pickle
.WriteData(contents
.data(), contents
.length());
97 // Create the shared memory object.
98 base::SharedMemory shared_memory
;
100 base::SharedMemoryCreateOptions options
;
101 options
.size
= pickle
.size();
102 options
.share_read_only
= true;
103 if (!shared_memory
.Create(options
))
104 return scoped_ptr
<base::SharedMemory
>();
106 if (!shared_memory
.Map(pickle
.size()))
107 return scoped_ptr
<base::SharedMemory
>();
109 // Copy the pickle to shared memory.
110 memcpy(shared_memory
.memory(), pickle
.data(), pickle
.size());
112 base::SharedMemoryHandle readonly_handle
;
113 if (!shared_memory
.ShareReadOnlyToProcess(base::GetCurrentProcessHandle(),
115 return scoped_ptr
<base::SharedMemory
>();
117 return make_scoped_ptr(new base::SharedMemory(readonly_handle
,
118 /*read_only=*/true));
121 void LoadScriptsOnFileThread(
122 scoped_ptr
<UserScriptList
> user_scripts
,
123 const UserScriptLoader::HostsInfo
& hosts_info
,
124 const std::set
<int>& added_script_ids
,
125 const scoped_refptr
<ContentVerifier
>& verifier
,
126 UserScriptLoader::LoadUserScriptsContentFunction function
,
127 LoadScriptsCallback callback
) {
128 DCHECK(user_scripts
.get());
129 LoadUserScripts(user_scripts
.get(), hosts_info
, added_script_ids
,
131 scoped_ptr
<base::SharedMemory
> memory
= Serialize(*user_scripts
);
132 BrowserThread::PostTask(
135 base::Bind(callback
, base::Passed(&user_scripts
), base::Passed(&memory
)));
138 // Helper function to parse greasesmonkey headers
139 bool GetDeclarationValue(const base::StringPiece
& line
,
140 const base::StringPiece
& prefix
,
141 std::string
* value
) {
142 base::StringPiece::size_type index
= line
.find(prefix
);
143 if (index
== base::StringPiece::npos
)
146 std::string
temp(line
.data() + index
+ prefix
.length(),
147 line
.length() - index
- prefix
.length());
149 if (temp
.empty() || !IsWhitespace(temp
[0]))
152 base::TrimWhitespaceASCII(temp
, base::TRIM_ALL
, value
);
159 bool UserScriptLoader::ParseMetadataHeader(const base::StringPiece
& script_text
,
160 UserScript
* script
) {
161 // http://wiki.greasespot.net/Metadata_block
162 base::StringPiece line
;
163 size_t line_start
= 0;
164 size_t line_end
= line_start
;
165 bool in_metadata
= false;
167 static const base::StringPiece
kUserScriptBegin("// ==UserScript==");
168 static const base::StringPiece
kUserScriptEng("// ==/UserScript==");
169 static const base::StringPiece
kNamespaceDeclaration("// @namespace");
170 static const base::StringPiece
kNameDeclaration("// @name");
171 static const base::StringPiece
kVersionDeclaration("// @version");
172 static const base::StringPiece
kDescriptionDeclaration("// @description");
173 static const base::StringPiece
kIncludeDeclaration("// @include");
174 static const base::StringPiece
kExcludeDeclaration("// @exclude");
175 static const base::StringPiece
kMatchDeclaration("// @match");
176 static const base::StringPiece
kExcludeMatchDeclaration("// @exclude_match");
177 static const base::StringPiece
kRunAtDeclaration("// @run-at");
178 static const base::StringPiece
kRunAtDocumentStartValue("document-start");
179 static const base::StringPiece
kRunAtDocumentEndValue("document-end");
180 static const base::StringPiece
kRunAtDocumentIdleValue("document-idle");
182 while (line_start
< script_text
.length()) {
183 line_end
= script_text
.find('\n', line_start
);
185 // Handle the case where there is no trailing newline in the file.
186 if (line_end
== std::string::npos
)
187 line_end
= script_text
.length() - 1;
189 line
.set(script_text
.data() + line_start
, line_end
- line_start
);
192 if (line
.starts_with(kUserScriptBegin
))
195 if (line
.starts_with(kUserScriptEng
))
199 if (GetDeclarationValue(line
, kIncludeDeclaration
, &value
)) {
200 // We escape some characters that MatchPattern() considers special.
201 ReplaceSubstringsAfterOffset(&value
, 0, "\\", "\\\\");
202 ReplaceSubstringsAfterOffset(&value
, 0, "?", "\\?");
203 script
->add_glob(value
);
204 } else if (GetDeclarationValue(line
, kExcludeDeclaration
, &value
)) {
205 ReplaceSubstringsAfterOffset(&value
, 0, "\\", "\\\\");
206 ReplaceSubstringsAfterOffset(&value
, 0, "?", "\\?");
207 script
->add_exclude_glob(value
);
208 } else if (GetDeclarationValue(line
, kNamespaceDeclaration
, &value
)) {
209 script
->set_name_space(value
);
210 } else if (GetDeclarationValue(line
, kNameDeclaration
, &value
)) {
211 script
->set_name(value
);
212 } else if (GetDeclarationValue(line
, kVersionDeclaration
, &value
)) {
213 Version
version(value
);
214 if (version
.IsValid())
215 script
->set_version(version
.GetString());
216 } else if (GetDeclarationValue(line
, kDescriptionDeclaration
, &value
)) {
217 script
->set_description(value
);
218 } else if (GetDeclarationValue(line
, kMatchDeclaration
, &value
)) {
219 URLPattern
pattern(UserScript::ValidUserScriptSchemes());
220 if (URLPattern::PARSE_SUCCESS
!= pattern
.Parse(value
))
222 script
->add_url_pattern(pattern
);
223 } else if (GetDeclarationValue(line
, kExcludeMatchDeclaration
, &value
)) {
224 URLPattern
exclude(UserScript::ValidUserScriptSchemes());
225 if (URLPattern::PARSE_SUCCESS
!= exclude
.Parse(value
))
227 script
->add_exclude_url_pattern(exclude
);
228 } else if (GetDeclarationValue(line
, kRunAtDeclaration
, &value
)) {
229 if (value
== kRunAtDocumentStartValue
)
230 script
->set_run_location(UserScript::DOCUMENT_START
);
231 else if (value
== kRunAtDocumentEndValue
)
232 script
->set_run_location(UserScript::DOCUMENT_END
);
233 else if (value
== kRunAtDocumentIdleValue
)
234 script
->set_run_location(UserScript::DOCUMENT_IDLE
);
239 // TODO(aa): Handle more types of metadata.
242 line_start
= line_end
+ 1;
245 // If no patterns were specified, default to @include *. This is what
246 // Greasemonkey does.
247 if (script
->globs().empty() && script
->url_patterns().is_empty())
248 script
->add_glob("*");
253 void UserScriptLoader::LoadScriptsForTest(UserScriptList
* user_scripts
) {
255 std::set
<int> added_script_ids
;
256 for (UserScriptList::iterator it
= user_scripts
->begin();
257 it
!= user_scripts
->end();
259 added_script_ids
.insert(it
->id());
261 LoadUserScripts(user_scripts
, info
, added_script_ids
,
262 NULL
/* no verifier for testing */,
263 GetLoadUserScriptsFunction());
266 UserScriptLoader::UserScriptLoader(
267 BrowserContext
* browser_context
,
268 const HostID
& host_id
,
269 const scoped_refptr
<ContentVerifier
>& content_verifier
)
270 : user_scripts_(new UserScriptList()),
271 clear_scripts_(false),
273 pending_load_(false),
274 browser_context_(browser_context
),
276 content_verifier_(content_verifier
),
277 weak_factory_(this) {
279 content::NOTIFICATION_RENDERER_PROCESS_CREATED
,
280 content::NotificationService::AllBrowserContextsAndSources());
283 UserScriptLoader::~UserScriptLoader() {
286 void UserScriptLoader::AddScripts(const std::set
<UserScript
>& scripts
) {
287 for (std::set
<UserScript
>::const_iterator it
= scripts
.begin();
290 removed_scripts_
.erase(*it
);
291 added_scripts_
.insert(*it
);
296 void UserScriptLoader::RemoveScripts(const std::set
<UserScript
>& scripts
) {
297 for (std::set
<UserScript
>::const_iterator it
= scripts
.begin();
300 added_scripts_
.erase(*it
);
301 removed_scripts_
.insert(*it
);
306 void UserScriptLoader::ClearScripts() {
307 clear_scripts_
= true;
308 added_scripts_
.clear();
309 removed_scripts_
.clear();
313 void UserScriptLoader::Observe(int type
,
314 const content::NotificationSource
& source
,
315 const content::NotificationDetails
& details
) {
316 DCHECK_EQ(type
, content::NOTIFICATION_RENDERER_PROCESS_CREATED
);
317 content::RenderProcessHost
* process
=
318 content::Source
<content::RenderProcessHost
>(source
).ptr();
319 if (!ExtensionsBrowserClient::Get()->IsSameContext(
320 browser_context_
, process
->GetBrowserContext()))
322 if (scripts_ready()) {
323 SendUpdate(process
, shared_memory_
.get(),
324 std::set
<HostID
>()); // Include all hosts.
328 bool UserScriptLoader::ScriptsMayHaveChanged() const {
329 // Scripts may have changed if there are scripts added, scripts removed, or
330 // if scripts were cleared and either:
331 // (1) A load is in progress (which may result in a non-zero number of
332 // scripts that need to be cleared), or
333 // (2) The current set of scripts is non-empty (so they need to be cleared).
334 return (added_scripts_
.size() ||
335 removed_scripts_
.size() ||
337 (is_loading() || user_scripts_
->size())));
340 void UserScriptLoader::AttemptLoad() {
341 if (ready_
&& ScriptsMayHaveChanged()) {
343 pending_load_
= true;
349 void UserScriptLoader::StartLoad() {
350 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
351 DCHECK(!is_loading());
353 // If scripts were marked for clearing before adding and removing, then clear
355 if (clear_scripts_
) {
356 user_scripts_
->clear();
358 for (UserScriptList::iterator it
= user_scripts_
->begin();
359 it
!= user_scripts_
->end();) {
360 if (removed_scripts_
.count(*it
))
361 it
= user_scripts_
->erase(it
);
367 user_scripts_
->insert(
368 user_scripts_
->end(), added_scripts_
.begin(), added_scripts_
.end());
370 std::set
<int> added_script_ids
;
371 for (std::set
<UserScript
>::const_iterator it
= added_scripts_
.begin();
372 it
!= added_scripts_
.end();
374 added_script_ids
.insert(it
->id());
377 // Expand |changed_hosts_| for OnScriptsLoaded, which will use it in
378 // its IPC message. This must be done before we clear |added_scripts_| and
379 // |removed_scripts_| below.
380 std::set
<UserScript
> changed_scripts(added_scripts_
);
381 changed_scripts
.insert(removed_scripts_
.begin(), removed_scripts_
.end());
382 for (const UserScript
& script
: changed_scripts
)
383 changed_hosts_
.insert(script
.host_id());
385 // |changed_hosts_| before passing it to LoadScriptsOnFileThread.
386 UpdateHostsInfo(changed_hosts_
);
388 BrowserThread::PostTask(
389 BrowserThread::FILE, FROM_HERE
,
390 base::Bind(&LoadScriptsOnFileThread
, base::Passed(&user_scripts_
),
391 hosts_info_
, added_script_ids
, content_verifier_
,
392 GetLoadUserScriptsFunction(),
393 base::Bind(&UserScriptLoader::OnScriptsLoaded
,
394 weak_factory_
.GetWeakPtr())));
396 clear_scripts_
= false;
397 added_scripts_
.clear();
398 removed_scripts_
.clear();
399 user_scripts_
.reset(NULL
);
402 void UserScriptLoader::AddHostInfo(const HostID
& host_id
,
403 const PathAndDefaultLocale
& location
) {
404 if (hosts_info_
.find(host_id
) != hosts_info_
.end())
406 hosts_info_
[host_id
] = location
;
409 void UserScriptLoader::RemoveHostInfo(const HostID
& host_id
) {
410 hosts_info_
.erase(host_id
);
413 void UserScriptLoader::SetReady(bool ready
) {
414 bool was_ready
= ready_
;
416 if (ready_
&& !was_ready
)
420 void UserScriptLoader::OnScriptsLoaded(
421 scoped_ptr
<UserScriptList
> user_scripts
,
422 scoped_ptr
<base::SharedMemory
> shared_memory
) {
423 user_scripts_
.reset(user_scripts
.release());
425 // While we were loading, there were further changes. Don't bother
426 // notifying about these scripts and instead just immediately reload.
427 pending_load_
= false;
432 if (shared_memory
.get() == NULL
) {
433 // This can happen if we run out of file descriptors. In that case, we
434 // have a choice between silently omitting all user scripts for new tabs,
435 // by nulling out shared_memory_, or only silently omitting new ones by
436 // leaving the existing object in place. The second seems less bad, even
437 // though it removes the possibility that freeing the shared memory block
438 // would open up enough FDs for long enough for a retry to succeed.
440 // Pretend the extension change didn't happen.
444 // We've got scripts ready to go.
445 shared_memory_
.reset(shared_memory
.release());
447 // If user scripts are coming from a <webview>, will only notify the
448 // RenderProcessHost of that <webview>; otherwise will notify all of the
449 // RenderProcessHosts.
450 if (user_scripts_
&& !user_scripts_
->empty() &&
451 (*user_scripts_
)[0].consumer_instance_type() ==
452 UserScript::ConsumerInstanceType::WEBVIEW
) {
453 DCHECK_EQ(1u, user_scripts_
->size());
454 int render_process_id
=
455 (*user_scripts_
)[0].routing_info().render_process_id
;
456 content::RenderProcessHost
* host
=
457 content::RenderProcessHost::FromID(render_process_id
);
459 SendUpdate(host
, shared_memory_
.get(), changed_hosts_
);
461 for (content::RenderProcessHost::iterator
i(
462 content::RenderProcessHost::AllHostsIterator());
463 !i
.IsAtEnd(); i
.Advance()) {
464 SendUpdate(i
.GetCurrentValue(), shared_memory_
.get(), changed_hosts_
);
467 changed_hosts_
.clear();
469 content::NotificationService::current()->Notify(
470 extensions::NOTIFICATION_USER_SCRIPTS_UPDATED
,
471 content::Source
<BrowserContext
>(browser_context_
),
472 content::Details
<base::SharedMemory
>(shared_memory_
.get()));
475 void UserScriptLoader::SendUpdate(content::RenderProcessHost
* process
,
476 base::SharedMemory
* shared_memory
,
477 const std::set
<HostID
>& changed_hosts
) {
478 // Don't allow injection of content scripts into <webview>.
479 if (process
->IsIsolatedGuest())
482 // Make sure we only send user scripts to processes in our browser_context.
483 if (!ExtensionsBrowserClient::Get()->IsSameContext(
484 browser_context_
, process
->GetBrowserContext()))
487 // If the process is being started asynchronously, early return. We'll end up
488 // calling InitUserScripts when it's created which will call this again.
489 base::ProcessHandle handle
= process
->GetHandle();
493 base::SharedMemoryHandle handle_for_process
;
494 if (!shared_memory
->ShareToProcess(handle
, &handle_for_process
))
495 return; // This can legitimately fail if the renderer asserts at startup.
497 if (base::SharedMemory::IsHandleValid(handle_for_process
)) {
498 process
->Send(new ExtensionMsg_UpdateUserScripts(handle_for_process
,
499 host_id(), changed_hosts
));
503 } // namespace extensions