3b38bd8c1cad4634c61365a8aeaaee0b6ea54928
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / user_script_loader.cc
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.
4
5 #include "chrome/browser/extensions/user_script_loader.h"
6
7 #include <set>
8 #include <string>
9
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"
32
33 using content::BrowserThread;
34 using extensions::ExtensionsBrowserClient;
35
36 namespace extensions {
37
38 namespace {
39
40 typedef base::Callback<
41     void(scoped_ptr<UserScriptList>, scoped_ptr<base::SharedMemory>)>
42     LoadScriptsCallback;
43
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));
52   if (job.get()) {
53     job->Start();
54     job->BytesRead(content.size(), content.data());
55     job->DoneReading();
56   }
57 }
58
59 bool LoadScriptContent(const ExtensionId& extension_id,
60                        UserScript::File* script_file,
61                        const SubstitutionMap* localization_messages,
62                        scoped_refptr<ContentVerifier> verifier) {
63   std::string content;
64   const base::FilePath& path = ExtensionResource::GetFilePath(
65       script_file->extension_root(),
66       script_file->relative_path(),
67       ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT);
68   if (path.empty()) {
69     int resource_id;
70     if (ExtensionsBrowserClient::Get()->GetComponentExtensionResourceManager()->
71         IsComponentExtensionResource(script_file->extension_root(),
72                                      script_file->relative_path(),
73                                      &resource_id)) {
74       const ResourceBundle& rb = ResourceBundle::GetSharedInstance();
75       content = rb.GetRawDataResource(resource_id).as_string();
76     } else {
77       LOG(WARNING) << "Failed to get file path to "
78                    << script_file->relative_path().value() << " from "
79                    << script_file->extension_root().value();
80       return false;
81     }
82   } else {
83     if (!base::ReadFileToString(path, &content)) {
84       LOG(WARNING) << "Failed to load user script file: " << path.value();
85       return false;
86     }
87     if (verifier.get()) {
88       content::BrowserThread::PostTask(content::BrowserThread::IO,
89                                        FROM_HERE,
90                                        base::Bind(&VerifyContent,
91                                                   verifier,
92                                                   extension_id,
93                                                   script_file->extension_root(),
94                                                   script_file->relative_path(),
95                                                   content));
96     }
97   }
98
99   // Localize the content.
100   if (localization_messages) {
101     std::string error;
102     MessageBundle::ReplaceMessagesWithExternalDictionary(
103         *localization_messages, &content, &error);
104     if (!error.empty()) {
105       LOG(WARNING) << "Failed to replace messages in script: " << error;
106     }
107   }
108
109   // Remove BOM from the content.
110   std::string::size_type index = content.find(base::kUtf8ByteOrderMark);
111   if (index == 0) {
112     script_file->set_content(content.substr(strlen(base::kUtf8ByteOrderMark)));
113   } else {
114     script_file->set_content(content);
115   }
116
117   return true;
118 }
119
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())
124     return NULL;
125   return file_util::LoadMessageBundleSubstitutionMap(
126       iter->second.first, extension_id, iter->second.second);
127 }
128
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();
135        ++script) {
136     if (added_script_ids.count(script->id()) == 0)
137       continue;
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);
144     }
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(),
149                           &script_file,
150                           localization_messages.get(),
151                           verifier);
152     }
153   }
154 }
155
156 // Pickle user scripts and return pointer to the shared memory.
157 scoped_ptr<base::SharedMemory> Serialize(const UserScriptList& scripts) {
158   Pickle pickle;
159   pickle.WriteUInt64(scripts.size());
160   for (UserScriptList::const_iterator script = scripts.begin();
161        script != scripts.end();
162        ++script) {
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());
172     }
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());
176     }
177   }
178
179   // Create the shared memory object.
180   base::SharedMemory shared_memory;
181
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>();
187
188   if (!shared_memory.Map(pickle.size()))
189     return scoped_ptr<base::SharedMemory>();
190
191   // Copy the pickle to shared memory.
192   memcpy(shared_memory.memory(), pickle.data(), pickle.size());
193
194   base::SharedMemoryHandle readonly_handle;
195   if (!shared_memory.ShareReadOnlyToProcess(base::GetCurrentProcessHandle(),
196                                             &readonly_handle))
197     return scoped_ptr<base::SharedMemory>();
198
199   return make_scoped_ptr(new base::SharedMemory(readonly_handle,
200                                                 /*read_only=*/true));
201 }
202
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());
209   LoadUserScripts(
210       user_scripts.get(), extensions_info, added_script_ids, verifier.get());
211   scoped_ptr<base::SharedMemory> memory = Serialize(*user_scripts);
212   BrowserThread::PostTask(
213       BrowserThread::UI,
214       FROM_HERE,
215       base::Bind(callback, base::Passed(&user_scripts), base::Passed(&memory)));
216 }
217
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)
224     return false;
225
226   std::string temp(line.data() + index + prefix.length(),
227                    line.length() - index - prefix.length());
228
229   if (temp.empty() || !IsWhitespace(temp[0]))
230     return false;
231
232   base::TrimWhitespaceASCII(temp, base::TRIM_ALL, value);
233   return true;
234 }
235
236 }  // namespace
237
238 // static
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;
246
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");
261
262   while (line_start < script_text.length()) {
263     line_end = script_text.find('\n', line_start);
264
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;
268
269     line.set(script_text.data() + line_start, line_end - line_start);
270
271     if (!in_metadata) {
272       if (line.starts_with(kUserScriptBegin))
273         in_metadata = true;
274     } else {
275       if (line.starts_with(kUserScriptEng))
276         break;
277
278       std::string value;
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))
301           return false;
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))
306           return false;
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);
315         else
316           return false;
317       }
318
319       // TODO(aa): Handle more types of metadata.
320     }
321
322     line_start = line_end + 1;
323   }
324
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("*");
329
330   return true;
331 }
332
333 // static
334 void UserScriptLoader::LoadScriptsForTest(UserScriptList* user_scripts) {
335   ExtensionsInfo info;
336   std::set<int> added_script_ids;
337   for (UserScriptList::iterator it = user_scripts->begin();
338        it != user_scripts->end();
339        ++it) {
340     added_script_ids.insert(it->id());
341   }
342   LoadUserScripts(
343       user_scripts, info, added_script_ids, NULL /* no verifier for testing */);
344 }
345
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),
353       profile_(profile),
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(
360         FROM_HERE,
361         base::Bind(&UserScriptLoader::OnExtensionSystemReady,
362                    weak_factory_.GetWeakPtr()));
363   } else {
364     extension_system_ready_ = true;
365   }
366   registrar_.Add(this,
367                  content::NOTIFICATION_RENDERER_PROCESS_CREATED,
368                  content::NotificationService::AllBrowserContextsAndSources());
369 }
370
371 UserScriptLoader::~UserScriptLoader() {
372 }
373
374 void UserScriptLoader::AddScripts(const std::set<UserScript>& scripts) {
375   for (std::set<UserScript>::const_iterator it = scripts.begin();
376        it != scripts.end();
377        ++it) {
378     removed_scripts_.erase(*it);
379     added_scripts_.insert(*it);
380   }
381   AttemptLoad();
382 }
383
384 void UserScriptLoader::RemoveScripts(const std::set<UserScript>& scripts) {
385   for (std::set<UserScript>::const_iterator it = scripts.begin();
386        it != scripts.end();
387        ++it) {
388     added_scripts_.erase(*it);
389     removed_scripts_.insert(*it);
390   }
391   AttemptLoad();
392 }
393
394 void UserScriptLoader::ClearScripts() {
395   clear_scripts_ = true;
396   added_scripts_.clear();
397   removed_scripts_.clear();
398   AttemptLoad();
399 }
400
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))
409     return;
410   if (scripts_ready()) {
411     SendUpdate(process,
412                shared_memory_.get(),
413                std::set<ExtensionId>());  // Include all extensions.
414   }
415 }
416
417 void UserScriptLoader::OnExtensionUnloaded(
418     content::BrowserContext* browser_context,
419     const Extension* extension,
420     UnloadedExtensionInfo::Reason reason) {
421   extensions_info_.erase(extension->id());
422 }
423
424 void UserScriptLoader::OnExtensionSystemReady() {
425   extension_system_ready_ = true;
426   AttemptLoad();
427 }
428
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() ||
437           (clear_scripts_ &&
438            (is_loading() || user_scripts_->size())));
439 }
440
441 void UserScriptLoader::AttemptLoad() {
442   if (extension_system_ready_ && ScriptsMayHaveChanged()) {
443     if (is_loading())
444       pending_load_ = true;
445     else
446       StartLoad();
447   }
448 }
449
450 void UserScriptLoader::StartLoad() {
451   DCHECK_CURRENTLY_ON(BrowserThread::UI);
452   DCHECK(!is_loading());
453
454   // If scripts were marked for clearing before adding and removing, then clear
455   // them.
456   if (clear_scripts_) {
457     user_scripts_->clear();
458   } else {
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);
463       else
464         ++it;
465     }
466   }
467
468   user_scripts_->insert(
469       user_scripts_->end(), added_scripts_.begin(), added_scripts_.end());
470
471   std::set<int> added_script_ids;
472   for (std::set<UserScript>::const_iterator it = added_scripts_.begin();
473        it != added_scripts_.end();
474        ++it) {
475     added_script_ids.insert(it->id());
476   }
477
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);
484
485   // Update |extensions_info_| to contain info from every extension in
486   // |changed_extensions_| before passing it to LoadScriptsOnFileThread.
487   UpdateExtensionsInfo();
488
489   BrowserThread::PostTask(
490       BrowserThread::FILE,
491       FROM_HERE,
492       base::Bind(&LoadScriptsOnFileThread,
493                  base::Passed(&user_scripts_),
494                  extensions_info_,
495                  added_script_ids,
496                  make_scoped_refptr(
497                      ExtensionSystem::Get(profile_)->content_verifier()),
498                  base::Bind(&UserScriptLoader::OnScriptsLoaded,
499                             weak_factory_.GetWeakPtr())));
500
501   clear_scripts_ = false;
502   added_scripts_.clear();
503   removed_scripts_.clear();
504   user_scripts_.reset(NULL);
505 }
506
507 void UserScriptLoader::OnScriptsLoaded(
508     scoped_ptr<UserScriptList> user_scripts,
509     scoped_ptr<base::SharedMemory> shared_memory) {
510   user_scripts_.reset(user_scripts.release());
511   if (pending_load_) {
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;
515     StartLoad();
516     return;
517   }
518
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.
526
527     // Pretend the extension change didn't happen.
528     return;
529   }
530
531   // We've got scripts ready to go.
532   shared_memory_.reset(shared_memory.release());
533
534   for (content::RenderProcessHost::iterator i(
535            content::RenderProcessHost::AllHostsIterator());
536        !i.IsAtEnd();
537        i.Advance()) {
538     SendUpdate(i.GetCurrentValue(), shared_memory_.get(), changed_extensions_);
539   }
540   changed_extensions_.clear();
541
542   content::NotificationService::current()->Notify(
543       extensions::NOTIFICATION_USER_SCRIPTS_UPDATED,
544       content::Source<Profile>(profile_),
545       content::Details<base::SharedMemory>(shared_memory_.get()));
546 }
547
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())
554     return;
555
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))
559     return;
560
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();
564   if (!handle)
565     return;
566
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.
570
571   if (base::SharedMemory::IsHandleValid(handle_for_process)) {
572     process->Send(new ExtensionMsg_UpdateUserScripts(
573         handle_for_process, owner_extension_id_, changed_extensions));
574   }
575 }
576
577 void UserScriptLoader::ExpandChangedExtensions(
578     const std::set<UserScript>& scripts) {
579   for (std::set<UserScript>::const_iterator it = scripts.begin();
580        it != scripts.end();
581        ++it) {
582     changed_extensions_.insert(it->extension_id());
583   }
584 }
585
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();
590        ++it) {
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.
596       if (!extension)
597         continue;
598       extensions_info_[*it] = ExtensionSet::ExtensionPathAndDefaultLocale(
599           extension->path(), LocaleInfo::GetDefaultLocale(extension));
600     }
601   }
602 }
603
604 }  // namespace extensions