Extract code handling PrinterProviderAPI from PrintPreviewHandler
[chromium-blink-merge.git] / chrome / browser / extensions / user_script_loader.cc
blob24e042ba48284f2202405c3f71331797f9392ce4
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"
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 "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 {
28 namespace {
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())
39 return nullptr;
40 return file_util::LoadMessageBundleSubstitutionMap(
41 iter->second.first, host_id.id(), iter->second.second);
44 void LoadUserScripts(
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();
52 ++script) {
53 if (added_script_ids.count(script->id()) == 0)
54 continue;
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) {
73 Pickle pickle;
74 pickle.WriteSizeT(scripts.size());
75 for (UserScriptList::const_iterator script = scripts.begin();
76 script != scripts.end();
77 ++script) {
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(),
111 &readonly_handle))
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,
127 verifier, function);
128 scoped_ptr<base::SharedMemory> memory = Serialize(*user_scripts);
129 BrowserThread::PostTask(
130 BrowserThread::UI,
131 FROM_HERE,
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)
141 return false;
143 std::string temp(line.data() + index + prefix.length(),
144 line.length() - index - prefix.length());
146 if (temp.empty() || !IsWhitespace(temp[0]))
147 return false;
149 base::TrimWhitespaceASCII(temp, base::TRIM_ALL, value);
150 return true;
153 } // namespace
155 // static
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);
188 if (!in_metadata) {
189 if (line.starts_with(kUserScriptBegin))
190 in_metadata = true;
191 } else {
192 if (line.starts_with(kUserScriptEng))
193 break;
195 std::string value;
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))
218 return false;
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))
223 return false;
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);
232 else
233 return false;
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("*");
247 return true;
250 void UserScriptLoader::LoadScriptsForTest(UserScriptList* user_scripts) {
251 HostsInfo info;
252 std::set<int> added_script_ids;
253 for (UserScriptList::iterator it = user_scripts->begin();
254 it != user_scripts->end();
255 ++it) {
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(
264 Profile* profile,
265 const HostID& host_id,
266 const scoped_refptr<ContentVerifier>& content_verifier)
267 : user_scripts_(new UserScriptList()),
268 clear_scripts_(false),
269 ready_(false),
270 pending_load_(false),
271 profile_(profile),
272 host_id_(host_id),
273 content_verifier_(content_verifier),
274 weak_factory_(this) {
275 registrar_.Add(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();
285 it != scripts.end();
286 ++it) {
287 removed_scripts_.erase(*it);
288 added_scripts_.insert(*it);
290 AttemptLoad();
293 void UserScriptLoader::RemoveScripts(const std::set<UserScript>& scripts) {
294 for (std::set<UserScript>::const_iterator it = scripts.begin();
295 it != scripts.end();
296 ++it) {
297 added_scripts_.erase(*it);
298 removed_scripts_.insert(*it);
300 AttemptLoad();
303 void UserScriptLoader::ClearScripts() {
304 clear_scripts_ = true;
305 added_scripts_.clear();
306 removed_scripts_.clear();
307 AttemptLoad();
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))
318 return;
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() ||
333 (clear_scripts_ &&
334 (is_loading() || user_scripts_->size())));
337 void UserScriptLoader::AttemptLoad() {
338 if (ready_ && ScriptsMayHaveChanged()) {
339 if (is_loading())
340 pending_load_ = true;
341 else
342 StartLoad();
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
351 // them.
352 if (clear_scripts_) {
353 user_scripts_->clear();
354 } else {
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);
359 else
360 ++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();
370 ++it) {
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())
402 return;
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_;
412 ready_ = ready;
413 if (ready_ && !was_ready)
414 AttemptLoad();
417 void UserScriptLoader::OnScriptsLoaded(
418 scoped_ptr<UserScriptList> user_scripts,
419 scoped_ptr<base::SharedMemory> shared_memory) {
420 user_scripts_.reset(user_scripts.release());
421 if (pending_load_) {
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;
425 StartLoad();
426 return;
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.
438 return;
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());
446 !i.IsAtEnd();
447 i.Advance()) {
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())
463 return;
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))
468 return;
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();
473 if (!handle)
474 return;
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