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/version.h"
15 #include "chrome/browser/chrome_notification_types.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/browser/notification_service.h"
19 #include "content/public/browser/render_process_host.h"
20 #include "extensions/browser/content_verifier.h"
21 #include "extensions/common/extension_messages.h"
22 #include "extensions/common/file_util.h"
24 using content::BrowserThread
;
26 namespace extensions
{
30 using LoadScriptsCallback
=
31 base::Callback
<void(scoped_ptr
<UserScriptList
>,
32 scoped_ptr
<base::SharedMemory
>)>;
34 UserScriptLoader::SubstitutionMap
* GetLocalizationMessages(
35 const UserScriptLoader::HostsInfo
& hosts_info
,
36 const HostID
& host_id
) {
37 UserScriptLoader::HostsInfo::const_iterator iter
= hosts_info
.find(host_id
);
38 if (iter
== hosts_info
.end())
40 return file_util::LoadMessageBundleSubstitutionMap(
41 iter
->second
.first
, host_id
.id(), iter
->second
.second
);
45 UserScriptList
* user_scripts
,
46 const UserScriptLoader::HostsInfo
& hosts_info
,
47 const std::set
<int>& added_script_ids
,
48 const scoped_refptr
<ContentVerifier
>& verifier
,
49 UserScriptLoader::LoadUserScriptsContentFunction callback
) {
50 for (UserScriptList::iterator script
= user_scripts
->begin();
51 script
!= user_scripts
->end();
53 if (added_script_ids
.count(script
->id()) == 0)
55 scoped_ptr
<UserScriptLoader::SubstitutionMap
> localization_messages(
56 GetLocalizationMessages(hosts_info
, script
->host_id()));
57 for (size_t k
= 0; k
< script
->js_scripts().size(); ++k
) {
58 UserScript::File
& script_file
= script
->js_scripts()[k
];
59 if (script_file
.GetContent().empty())
60 callback
.Run(script
->host_id(), &script_file
, NULL
, verifier
);
62 for (size_t k
= 0; k
< script
->css_scripts().size(); ++k
) {
63 UserScript::File
& script_file
= script
->css_scripts()[k
];
64 if (script_file
.GetContent().empty())
65 callback
.Run(script
->host_id(), &script_file
,
66 localization_messages
.get(), verifier
);
71 // Pickle user scripts and return pointer to the shared memory.
72 scoped_ptr
<base::SharedMemory
> Serialize(const UserScriptList
& scripts
) {
74 pickle
.WriteSizeT(scripts
.size());
75 for (UserScriptList::const_iterator script
= scripts
.begin();
76 script
!= scripts
.end();
78 // TODO(aa): This can be replaced by sending content script metadata to
79 // renderers along with other extension data in ExtensionMsg_Loaded.
80 // See crbug.com/70516.
81 script
->Pickle(&pickle
);
82 // Write scripts as 'data' so that we can read it out in the slave without
83 // allocating a new string.
84 for (size_t j
= 0; j
< script
->js_scripts().size(); j
++) {
85 base::StringPiece contents
= script
->js_scripts()[j
].GetContent();
86 pickle
.WriteData(contents
.data(), contents
.length());
88 for (size_t j
= 0; j
< script
->css_scripts().size(); j
++) {
89 base::StringPiece contents
= script
->css_scripts()[j
].GetContent();
90 pickle
.WriteData(contents
.data(), contents
.length());
94 // Create the shared memory object.
95 base::SharedMemory shared_memory
;
97 base::SharedMemoryCreateOptions options
;
98 options
.size
= pickle
.size();
99 options
.share_read_only
= true;
100 if (!shared_memory
.Create(options
))
101 return scoped_ptr
<base::SharedMemory
>();
103 if (!shared_memory
.Map(pickle
.size()))
104 return scoped_ptr
<base::SharedMemory
>();
106 // Copy the pickle to shared memory.
107 memcpy(shared_memory
.memory(), pickle
.data(), pickle
.size());
109 base::SharedMemoryHandle readonly_handle
;
110 if (!shared_memory
.ShareReadOnlyToProcess(base::GetCurrentProcessHandle(),
112 return scoped_ptr
<base::SharedMemory
>();
114 return make_scoped_ptr(new base::SharedMemory(readonly_handle
,
115 /*read_only=*/true));
118 void LoadScriptsOnFileThread(
119 scoped_ptr
<UserScriptList
> user_scripts
,
120 const UserScriptLoader::HostsInfo
& hosts_info
,
121 const std::set
<int>& added_script_ids
,
122 const scoped_refptr
<ContentVerifier
>& verifier
,
123 UserScriptLoader::LoadUserScriptsContentFunction function
,
124 LoadScriptsCallback callback
) {
125 DCHECK(user_scripts
.get());
126 LoadUserScripts(user_scripts
.get(), hosts_info
, added_script_ids
,
128 scoped_ptr
<base::SharedMemory
> memory
= Serialize(*user_scripts
);
129 BrowserThread::PostTask(
132 base::Bind(callback
, base::Passed(&user_scripts
), base::Passed(&memory
)));
135 // Helper function to parse greasesmonkey headers
136 bool GetDeclarationValue(const base::StringPiece
& line
,
137 const base::StringPiece
& prefix
,
138 std::string
* value
) {
139 base::StringPiece::size_type index
= line
.find(prefix
);
140 if (index
== base::StringPiece::npos
)
143 std::string
temp(line
.data() + index
+ prefix
.length(),
144 line
.length() - index
- prefix
.length());
146 if (temp
.empty() || !IsWhitespace(temp
[0]))
149 base::TrimWhitespaceASCII(temp
, base::TRIM_ALL
, value
);
156 bool UserScriptLoader::ParseMetadataHeader(const base::StringPiece
& script_text
,
157 UserScript
* script
) {
158 // http://wiki.greasespot.net/Metadata_block
159 base::StringPiece line
;
160 size_t line_start
= 0;
161 size_t line_end
= line_start
;
162 bool in_metadata
= false;
164 static const base::StringPiece
kUserScriptBegin("// ==UserScript==");
165 static const base::StringPiece
kUserScriptEng("// ==/UserScript==");
166 static const base::StringPiece
kNamespaceDeclaration("// @namespace");
167 static const base::StringPiece
kNameDeclaration("// @name");
168 static const base::StringPiece
kVersionDeclaration("// @version");
169 static const base::StringPiece
kDescriptionDeclaration("// @description");
170 static const base::StringPiece
kIncludeDeclaration("// @include");
171 static const base::StringPiece
kExcludeDeclaration("// @exclude");
172 static const base::StringPiece
kMatchDeclaration("// @match");
173 static const base::StringPiece
kExcludeMatchDeclaration("// @exclude_match");
174 static const base::StringPiece
kRunAtDeclaration("// @run-at");
175 static const base::StringPiece
kRunAtDocumentStartValue("document-start");
176 static const base::StringPiece
kRunAtDocumentEndValue("document-end");
177 static const base::StringPiece
kRunAtDocumentIdleValue("document-idle");
179 while (line_start
< script_text
.length()) {
180 line_end
= script_text
.find('\n', line_start
);
182 // Handle the case where there is no trailing newline in the file.
183 if (line_end
== std::string::npos
)
184 line_end
= script_text
.length() - 1;
186 line
.set(script_text
.data() + line_start
, line_end
- line_start
);
189 if (line
.starts_with(kUserScriptBegin
))
192 if (line
.starts_with(kUserScriptEng
))
196 if (GetDeclarationValue(line
, kIncludeDeclaration
, &value
)) {
197 // We escape some characters that MatchPattern() considers special.
198 ReplaceSubstringsAfterOffset(&value
, 0, "\\", "\\\\");
199 ReplaceSubstringsAfterOffset(&value
, 0, "?", "\\?");
200 script
->add_glob(value
);
201 } else if (GetDeclarationValue(line
, kExcludeDeclaration
, &value
)) {
202 ReplaceSubstringsAfterOffset(&value
, 0, "\\", "\\\\");
203 ReplaceSubstringsAfterOffset(&value
, 0, "?", "\\?");
204 script
->add_exclude_glob(value
);
205 } else if (GetDeclarationValue(line
, kNamespaceDeclaration
, &value
)) {
206 script
->set_name_space(value
);
207 } else if (GetDeclarationValue(line
, kNameDeclaration
, &value
)) {
208 script
->set_name(value
);
209 } else if (GetDeclarationValue(line
, kVersionDeclaration
, &value
)) {
210 Version
version(value
);
211 if (version
.IsValid())
212 script
->set_version(version
.GetString());
213 } else if (GetDeclarationValue(line
, kDescriptionDeclaration
, &value
)) {
214 script
->set_description(value
);
215 } else if (GetDeclarationValue(line
, kMatchDeclaration
, &value
)) {
216 URLPattern
pattern(UserScript::ValidUserScriptSchemes());
217 if (URLPattern::PARSE_SUCCESS
!= pattern
.Parse(value
))
219 script
->add_url_pattern(pattern
);
220 } else if (GetDeclarationValue(line
, kExcludeMatchDeclaration
, &value
)) {
221 URLPattern
exclude(UserScript::ValidUserScriptSchemes());
222 if (URLPattern::PARSE_SUCCESS
!= exclude
.Parse(value
))
224 script
->add_exclude_url_pattern(exclude
);
225 } else if (GetDeclarationValue(line
, kRunAtDeclaration
, &value
)) {
226 if (value
== kRunAtDocumentStartValue
)
227 script
->set_run_location(UserScript::DOCUMENT_START
);
228 else if (value
== kRunAtDocumentEndValue
)
229 script
->set_run_location(UserScript::DOCUMENT_END
);
230 else if (value
== kRunAtDocumentIdleValue
)
231 script
->set_run_location(UserScript::DOCUMENT_IDLE
);
236 // TODO(aa): Handle more types of metadata.
239 line_start
= line_end
+ 1;
242 // If no patterns were specified, default to @include *. This is what
243 // Greasemonkey does.
244 if (script
->globs().empty() && script
->url_patterns().is_empty())
245 script
->add_glob("*");
250 void UserScriptLoader::LoadScriptsForTest(UserScriptList
* user_scripts
) {
252 std::set
<int> added_script_ids
;
253 for (UserScriptList::iterator it
= user_scripts
->begin();
254 it
!= user_scripts
->end();
256 added_script_ids
.insert(it
->id());
258 LoadUserScripts(user_scripts
, info
, added_script_ids
,
259 NULL
/* no verifier for testing */,
260 GetLoadUserScriptsFunction());
263 UserScriptLoader::UserScriptLoader(
265 const HostID
& host_id
,
266 const scoped_refptr
<ContentVerifier
>& content_verifier
)
267 : user_scripts_(new UserScriptList()),
268 clear_scripts_(false),
270 pending_load_(false),
273 content_verifier_(content_verifier
),
274 weak_factory_(this) {
276 content::NOTIFICATION_RENDERER_PROCESS_CREATED
,
277 content::NotificationService::AllBrowserContextsAndSources());
280 UserScriptLoader::~UserScriptLoader() {
283 void UserScriptLoader::AddScripts(const std::set
<UserScript
>& scripts
) {
284 for (std::set
<UserScript
>::const_iterator it
= scripts
.begin();
287 removed_scripts_
.erase(*it
);
288 added_scripts_
.insert(*it
);
293 void UserScriptLoader::RemoveScripts(const std::set
<UserScript
>& scripts
) {
294 for (std::set
<UserScript
>::const_iterator it
= scripts
.begin();
297 added_scripts_
.erase(*it
);
298 removed_scripts_
.insert(*it
);
303 void UserScriptLoader::ClearScripts() {
304 clear_scripts_
= true;
305 added_scripts_
.clear();
306 removed_scripts_
.clear();
310 void UserScriptLoader::Observe(int type
,
311 const content::NotificationSource
& source
,
312 const content::NotificationDetails
& details
) {
313 DCHECK_EQ(type
, content::NOTIFICATION_RENDERER_PROCESS_CREATED
);
314 content::RenderProcessHost
* process
=
315 content::Source
<content::RenderProcessHost
>(source
).ptr();
316 Profile
* profile
= Profile::FromBrowserContext(process
->GetBrowserContext());
317 if (!profile_
->IsSameProfile(profile
))
319 if (scripts_ready()) {
320 SendUpdate(process
, shared_memory_
.get(),
321 std::set
<HostID
>()); // Include all hosts.
325 bool UserScriptLoader::ScriptsMayHaveChanged() const {
326 // Scripts may have changed if there are scripts added, scripts removed, or
327 // if scripts were cleared and either:
328 // (1) A load is in progress (which may result in a non-zero number of
329 // scripts that need to be cleared), or
330 // (2) The current set of scripts is non-empty (so they need to be cleared).
331 return (added_scripts_
.size() ||
332 removed_scripts_
.size() ||
334 (is_loading() || user_scripts_
->size())));
337 void UserScriptLoader::AttemptLoad() {
338 if (ready_
&& ScriptsMayHaveChanged()) {
340 pending_load_
= true;
346 void UserScriptLoader::StartLoad() {
347 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
348 DCHECK(!is_loading());
350 // If scripts were marked for clearing before adding and removing, then clear
352 if (clear_scripts_
) {
353 user_scripts_
->clear();
355 for (UserScriptList::iterator it
= user_scripts_
->begin();
356 it
!= user_scripts_
->end();) {
357 if (removed_scripts_
.count(*it
))
358 it
= user_scripts_
->erase(it
);
364 user_scripts_
->insert(
365 user_scripts_
->end(), added_scripts_
.begin(), added_scripts_
.end());
367 std::set
<int> added_script_ids
;
368 for (std::set
<UserScript
>::const_iterator it
= added_scripts_
.begin();
369 it
!= added_scripts_
.end();
371 added_script_ids
.insert(it
->id());
374 // Expand |changed_hosts_| for OnScriptsLoaded, which will use it in
375 // its IPC message. This must be done before we clear |added_scripts_| and
376 // |removed_scripts_| below.
377 std::set
<UserScript
> changed_scripts(added_scripts_
);
378 changed_scripts
.insert(removed_scripts_
.begin(), removed_scripts_
.end());
379 for (const UserScript
& script
: changed_scripts
)
380 changed_hosts_
.insert(script
.host_id());
382 // |changed_hosts_| before passing it to LoadScriptsOnFileThread.
383 UpdateHostsInfo(changed_hosts_
);
385 BrowserThread::PostTask(
386 BrowserThread::FILE, FROM_HERE
,
387 base::Bind(&LoadScriptsOnFileThread
, base::Passed(&user_scripts_
),
388 hosts_info_
, added_script_ids
, content_verifier_
,
389 GetLoadUserScriptsFunction(),
390 base::Bind(&UserScriptLoader::OnScriptsLoaded
,
391 weak_factory_
.GetWeakPtr())));
393 clear_scripts_
= false;
394 added_scripts_
.clear();
395 removed_scripts_
.clear();
396 user_scripts_
.reset(NULL
);
399 void UserScriptLoader::AddHostInfo(const HostID
& host_id
,
400 const PathAndDefaultLocale
& location
) {
401 if (hosts_info_
.find(host_id
) != hosts_info_
.end())
403 hosts_info_
[host_id
] = location
;
406 void UserScriptLoader::RemoveHostInfo(const HostID
& host_id
) {
407 hosts_info_
.erase(host_id
);
410 void UserScriptLoader::SetReady(bool ready
) {
411 bool was_ready
= ready_
;
413 if (ready_
&& !was_ready
)
417 void UserScriptLoader::OnScriptsLoaded(
418 scoped_ptr
<UserScriptList
> user_scripts
,
419 scoped_ptr
<base::SharedMemory
> shared_memory
) {
420 user_scripts_
.reset(user_scripts
.release());
422 // While we were loading, there were further changes. Don't bother
423 // notifying about these scripts and instead just immediately reload.
424 pending_load_
= false;
429 if (shared_memory
.get() == NULL
) {
430 // This can happen if we run out of file descriptors. In that case, we
431 // have a choice between silently omitting all user scripts for new tabs,
432 // by nulling out shared_memory_, or only silently omitting new ones by
433 // leaving the existing object in place. The second seems less bad, even
434 // though it removes the possibility that freeing the shared memory block
435 // would open up enough FDs for long enough for a retry to succeed.
437 // Pretend the extension change didn't happen.
441 // We've got scripts ready to go.
442 shared_memory_
.reset(shared_memory
.release());
444 for (content::RenderProcessHost::iterator
i(
445 content::RenderProcessHost::AllHostsIterator());
448 SendUpdate(i
.GetCurrentValue(), shared_memory_
.get(), changed_hosts_
);
450 changed_hosts_
.clear();
452 content::NotificationService::current()->Notify(
453 extensions::NOTIFICATION_USER_SCRIPTS_UPDATED
,
454 content::Source
<Profile
>(profile_
),
455 content::Details
<base::SharedMemory
>(shared_memory_
.get()));
458 void UserScriptLoader::SendUpdate(content::RenderProcessHost
* process
,
459 base::SharedMemory
* shared_memory
,
460 const std::set
<HostID
>& changed_hosts
) {
461 // Don't allow injection of content scripts into <webview>.
462 if (process
->IsIsolatedGuest())
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 // TODO(hanxi): update the IPC message to send a set of HostIDs to render.
481 // Also, remove this function when the refactor is done on render side.
482 std::set
<std::string
> changed_ids_set
;
483 for (const HostID
& id
: changed_hosts
)
484 changed_ids_set
.insert(id
.id());
486 if (base::SharedMemory::IsHandleValid(handle_for_process
)) {
487 process
->Send(new ExtensionMsg_UpdateUserScripts(
488 handle_for_process
, host_id().id(), changed_ids_set
));
492 } // namespace extensions