Remove unused parameter.
[chromium-blink-merge.git] / extensions / browser / user_script_loader.cc
blob1a0571870782474a1c94e1bc64150f1659793002
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"
7 #include <set>
8 #include <string>
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 {
31 namespace {
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())
42 return nullptr;
43 return file_util::LoadMessageBundleSubstitutionMap(
44 iter->second.first, host_id.id(), iter->second.second);
47 void LoadUserScripts(
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();
55 ++script) {
56 if (added_script_ids.count(script->id()) == 0)
57 continue;
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) {
76 Pickle pickle;
77 pickle.WriteSizeT(scripts.size());
78 for (UserScriptList::const_iterator script = scripts.begin();
79 script != scripts.end();
80 ++script) {
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(),
114 &readonly_handle))
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,
130 verifier, function);
131 scoped_ptr<base::SharedMemory> memory = Serialize(*user_scripts);
132 BrowserThread::PostTask(
133 BrowserThread::UI,
134 FROM_HERE,
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)
144 return false;
146 std::string temp(line.data() + index + prefix.length(),
147 line.length() - index - prefix.length());
149 if (temp.empty() || !IsWhitespace(temp[0]))
150 return false;
152 base::TrimWhitespaceASCII(temp, base::TRIM_ALL, value);
153 return true;
156 } // namespace
158 // static
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);
191 if (!in_metadata) {
192 if (line.starts_with(kUserScriptBegin))
193 in_metadata = true;
194 } else {
195 if (line.starts_with(kUserScriptEng))
196 break;
198 std::string value;
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))
221 return false;
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))
226 return false;
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);
235 else
236 return false;
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("*");
250 return true;
253 void UserScriptLoader::LoadScriptsForTest(UserScriptList* user_scripts) {
254 HostsInfo info;
255 std::set<int> added_script_ids;
256 for (UserScriptList::iterator it = user_scripts->begin();
257 it != user_scripts->end();
258 ++it) {
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),
272 ready_(false),
273 pending_load_(false),
274 browser_context_(browser_context),
275 host_id_(host_id),
276 content_verifier_(content_verifier),
277 weak_factory_(this) {
278 registrar_.Add(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();
288 it != scripts.end();
289 ++it) {
290 removed_scripts_.erase(*it);
291 added_scripts_.insert(*it);
293 AttemptLoad();
296 void UserScriptLoader::RemoveScripts(const std::set<UserScript>& scripts) {
297 for (std::set<UserScript>::const_iterator it = scripts.begin();
298 it != scripts.end();
299 ++it) {
300 added_scripts_.erase(*it);
301 removed_scripts_.insert(*it);
303 AttemptLoad();
306 void UserScriptLoader::ClearScripts() {
307 clear_scripts_ = true;
308 added_scripts_.clear();
309 removed_scripts_.clear();
310 AttemptLoad();
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()))
321 return;
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() ||
336 (clear_scripts_ &&
337 (is_loading() || user_scripts_->size())));
340 void UserScriptLoader::AttemptLoad() {
341 if (ready_ && ScriptsMayHaveChanged()) {
342 if (is_loading())
343 pending_load_ = true;
344 else
345 StartLoad();
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
354 // them.
355 if (clear_scripts_) {
356 user_scripts_->clear();
357 } else {
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);
362 else
363 ++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();
373 ++it) {
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())
405 return;
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_;
415 ready_ = ready;
416 if (ready_ && !was_ready)
417 AttemptLoad();
420 void UserScriptLoader::OnScriptsLoaded(
421 scoped_ptr<UserScriptList> user_scripts,
422 scoped_ptr<base::SharedMemory> shared_memory) {
423 user_scripts_.reset(user_scripts.release());
424 if (pending_load_) {
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;
428 StartLoad();
429 return;
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.
441 return;
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);
458 if (host)
459 SendUpdate(host, shared_memory_.get(), changed_hosts_);
460 } else {
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())
480 return;
482 // Make sure we only send user scripts to processes in our browser_context.
483 if (!ExtensionsBrowserClient::Get()->IsSameContext(
484 browser_context_, process->GetBrowserContext()))
485 return;
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();
490 if (!handle)
491 return;
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