Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / user_script_master.cc
1 // Copyright (c) 2012 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_master.h"
6
7 #include <string>
8
9 #include "base/bind.h"
10 #include "base/file_util.h"
11 #include "base/files/file_path.h"
12 #include "base/version.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/extensions/extension_service.h"
15 #include "chrome/browser/extensions/extension_util.h"
16 #include "chrome/browser/extensions/image_loader.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/common/extensions/api/i18n/default_locale_handler.h"
19 #include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h"
20 #include "content/public/browser/notification_service.h"
21 #include "content/public/browser/render_process_host.h"
22 #include "extensions/browser/content_verifier.h"
23 #include "extensions/browser/extension_registry.h"
24 #include "extensions/browser/extension_system.h"
25 #include "extensions/common/file_util.h"
26 #include "extensions/common/message_bundle.h"
27 #include "ui/base/resource/resource_bundle.h"
28
29 using content::BrowserThread;
30
31 namespace extensions {
32
33 // Helper function to parse greasesmonkey headers
34 static bool GetDeclarationValue(const base::StringPiece& line,
35                                 const base::StringPiece& prefix,
36                                 std::string* value) {
37   base::StringPiece::size_type index = line.find(prefix);
38   if (index == base::StringPiece::npos)
39     return false;
40
41   std::string temp(line.data() + index + prefix.length(),
42                    line.length() - index - prefix.length());
43
44   if (temp.empty() || !IsWhitespace(temp[0]))
45     return false;
46
47   base::TrimWhitespaceASCII(temp, base::TRIM_ALL, value);
48   return true;
49 }
50
51 UserScriptMaster::ScriptReloader::ScriptReloader(UserScriptMaster* master)
52     : master_(master) {
53   CHECK(BrowserThread::GetCurrentThreadIdentifier(&master_thread_id_));
54 }
55
56 // static
57 bool UserScriptMaster::ScriptReloader::ParseMetadataHeader(
58       const base::StringPiece& script_text, UserScript* script) {
59   // http://wiki.greasespot.net/Metadata_block
60   base::StringPiece line;
61   size_t line_start = 0;
62   size_t line_end = line_start;
63   bool in_metadata = false;
64
65   static const base::StringPiece kUserScriptBegin("// ==UserScript==");
66   static const base::StringPiece kUserScriptEng("// ==/UserScript==");
67   static const base::StringPiece kNamespaceDeclaration("// @namespace");
68   static const base::StringPiece kNameDeclaration("// @name");
69   static const base::StringPiece kVersionDeclaration("// @version");
70   static const base::StringPiece kDescriptionDeclaration("// @description");
71   static const base::StringPiece kIncludeDeclaration("// @include");
72   static const base::StringPiece kExcludeDeclaration("// @exclude");
73   static const base::StringPiece kMatchDeclaration("// @match");
74   static const base::StringPiece kExcludeMatchDeclaration("// @exclude_match");
75   static const base::StringPiece kRunAtDeclaration("// @run-at");
76   static const base::StringPiece kRunAtDocumentStartValue("document-start");
77   static const base::StringPiece kRunAtDocumentEndValue("document-end");
78   static const base::StringPiece kRunAtDocumentIdleValue("document-idle");
79
80   while (line_start < script_text.length()) {
81     line_end = script_text.find('\n', line_start);
82
83     // Handle the case where there is no trailing newline in the file.
84     if (line_end == std::string::npos)
85       line_end = script_text.length() - 1;
86
87     line.set(script_text.data() + line_start, line_end - line_start);
88
89     if (!in_metadata) {
90       if (line.starts_with(kUserScriptBegin))
91         in_metadata = true;
92     } else {
93       if (line.starts_with(kUserScriptEng))
94         break;
95
96       std::string value;
97       if (GetDeclarationValue(line, kIncludeDeclaration, &value)) {
98         // We escape some characters that MatchPattern() considers special.
99         ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
100         ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
101         script->add_glob(value);
102       } else if (GetDeclarationValue(line, kExcludeDeclaration, &value)) {
103         ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
104         ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
105         script->add_exclude_glob(value);
106       } else if (GetDeclarationValue(line, kNamespaceDeclaration, &value)) {
107         script->set_name_space(value);
108       } else if (GetDeclarationValue(line, kNameDeclaration, &value)) {
109         script->set_name(value);
110       } else if (GetDeclarationValue(line, kVersionDeclaration, &value)) {
111         Version version(value);
112         if (version.IsValid())
113           script->set_version(version.GetString());
114       } else if (GetDeclarationValue(line, kDescriptionDeclaration, &value)) {
115         script->set_description(value);
116       } else if (GetDeclarationValue(line, kMatchDeclaration, &value)) {
117         URLPattern pattern(UserScript::ValidUserScriptSchemes());
118         if (URLPattern::PARSE_SUCCESS != pattern.Parse(value))
119           return false;
120         script->add_url_pattern(pattern);
121       } else if (GetDeclarationValue(line, kExcludeMatchDeclaration, &value)) {
122         URLPattern exclude(UserScript::ValidUserScriptSchemes());
123         if (URLPattern::PARSE_SUCCESS != exclude.Parse(value))
124           return false;
125         script->add_exclude_url_pattern(exclude);
126       } else if (GetDeclarationValue(line, kRunAtDeclaration, &value)) {
127         if (value == kRunAtDocumentStartValue)
128           script->set_run_location(UserScript::DOCUMENT_START);
129         else if (value == kRunAtDocumentEndValue)
130           script->set_run_location(UserScript::DOCUMENT_END);
131         else if (value == kRunAtDocumentIdleValue)
132           script->set_run_location(UserScript::DOCUMENT_IDLE);
133         else
134           return false;
135       }
136
137       // TODO(aa): Handle more types of metadata.
138     }
139
140     line_start = line_end + 1;
141   }
142
143   // If no patterns were specified, default to @include *. This is what
144   // Greasemonkey does.
145   if (script->globs().empty() && script->url_patterns().is_empty())
146     script->add_glob("*");
147
148   return true;
149 }
150
151 void UserScriptMaster::ScriptReloader::StartLoad(
152     const UserScriptList& user_scripts,
153     const ExtensionsInfo& extensions_info) {
154   // Add a reference to ourselves to keep ourselves alive while we're running.
155   // Balanced by NotifyMaster().
156   AddRef();
157
158   verifier_ = master_->content_verifier();
159   this->extensions_info_ = extensions_info;
160   BrowserThread::PostTask(
161       BrowserThread::FILE, FROM_HERE,
162       base::Bind(
163           &UserScriptMaster::ScriptReloader::RunLoad, this, user_scripts));
164 }
165
166 UserScriptMaster::ScriptReloader::~ScriptReloader() {}
167
168 void UserScriptMaster::ScriptReloader::NotifyMaster(
169     base::SharedMemory* memory) {
170   // The master went away, so these new scripts aren't useful anymore.
171   if (!master_)
172     delete memory;
173   else
174     master_->NewScriptsAvailable(memory);
175
176   // Drop our self-reference.
177   // Balances StartLoad().
178   Release();
179 }
180
181 static void VerifyContent(ContentVerifier* verifier,
182                           const std::string& extension_id,
183                           const base::FilePath& extension_root,
184                           const base::FilePath& relative_path,
185                           const std::string& content) {
186   scoped_refptr<ContentVerifyJob> job(
187       verifier->CreateJobFor(extension_id, extension_root, relative_path));
188   if (job.get()) {
189     job->Start();
190     job->BytesRead(content.size(), content.data());
191     job->DoneReading();
192   }
193 }
194
195 static bool LoadScriptContent(const std::string& extension_id,
196                               UserScript::File* script_file,
197                               const SubstitutionMap* localization_messages,
198                               ContentVerifier* verifier) {
199   std::string content;
200   const base::FilePath& path = ExtensionResource::GetFilePath(
201       script_file->extension_root(), script_file->relative_path(),
202       ExtensionResource::SYMLINKS_MUST_RESOLVE_WITHIN_ROOT);
203   if (path.empty()) {
204     int resource_id;
205     if (extensions::ImageLoader::IsComponentExtensionResource(
206             script_file->extension_root(), script_file->relative_path(),
207             &resource_id)) {
208       const ResourceBundle& rb = ResourceBundle::GetSharedInstance();
209       content = rb.GetRawDataResource(resource_id).as_string();
210     } else {
211       LOG(WARNING) << "Failed to get file path to "
212                    << script_file->relative_path().value() << " from "
213                    << script_file->extension_root().value();
214       return false;
215     }
216   } else {
217     if (!base::ReadFileToString(path, &content)) {
218       LOG(WARNING) << "Failed to load user script file: " << path.value();
219       return false;
220     }
221     if (verifier) {
222       VerifyContent(verifier,
223                     extension_id,
224                     script_file->extension_root(),
225                     script_file->relative_path(),
226                     content);
227     }
228   }
229
230   // Localize the content.
231   if (localization_messages) {
232     std::string error;
233     MessageBundle::ReplaceMessagesWithExternalDictionary(
234         *localization_messages, &content, &error);
235     if (!error.empty()) {
236       LOG(WARNING) << "Failed to replace messages in script: " << error;
237     }
238   }
239
240   // Remove BOM from the content.
241   std::string::size_type index = content.find(base::kUtf8ByteOrderMark);
242   if (index == 0) {
243     script_file->set_content(content.substr(strlen(base::kUtf8ByteOrderMark)));
244   } else {
245     script_file->set_content(content);
246   }
247
248   return true;
249 }
250
251 void UserScriptMaster::ScriptReloader::LoadUserScripts(
252     UserScriptList* user_scripts) {
253   for (size_t i = 0; i < user_scripts->size(); ++i) {
254     UserScript& script = user_scripts->at(i);
255     scoped_ptr<SubstitutionMap> localization_messages(
256         GetLocalizationMessages(script.extension_id()));
257     for (size_t k = 0; k < script.js_scripts().size(); ++k) {
258       UserScript::File& script_file = script.js_scripts()[k];
259       if (script_file.GetContent().empty())
260         LoadScriptContent(
261             script.extension_id(), &script_file, NULL, verifier_.get());
262     }
263     for (size_t k = 0; k < script.css_scripts().size(); ++k) {
264       UserScript::File& script_file = script.css_scripts()[k];
265       if (script_file.GetContent().empty())
266         LoadScriptContent(script.extension_id(),
267                           &script_file,
268                           localization_messages.get(),
269                           verifier_.get());
270     }
271   }
272 }
273
274 SubstitutionMap* UserScriptMaster::ScriptReloader::GetLocalizationMessages(
275     const std::string& extension_id) {
276   if (extensions_info_.find(extension_id) == extensions_info_.end()) {
277     return NULL;
278   }
279
280   return file_util::LoadMessageBundleSubstitutionMap(
281       extensions_info_[extension_id].first,
282       extension_id,
283       extensions_info_[extension_id].second);
284 }
285
286 // Pickle user scripts and return pointer to the shared memory.
287 static base::SharedMemory* Serialize(const UserScriptList& scripts) {
288   Pickle pickle;
289   pickle.WriteUInt64(scripts.size());
290   for (size_t i = 0; i < scripts.size(); i++) {
291     const UserScript& script = scripts[i];
292     // TODO(aa): This can be replaced by sending content script metadata to
293     // renderers along with other extension data in ExtensionMsg_Loaded.
294     // See crbug.com/70516.
295     script.Pickle(&pickle);
296     // Write scripts as 'data' so that we can read it out in the slave without
297     // allocating a new string.
298     for (size_t j = 0; j < script.js_scripts().size(); j++) {
299       base::StringPiece contents = script.js_scripts()[j].GetContent();
300       pickle.WriteData(contents.data(), contents.length());
301     }
302     for (size_t j = 0; j < script.css_scripts().size(); j++) {
303       base::StringPiece contents = script.css_scripts()[j].GetContent();
304       pickle.WriteData(contents.data(), contents.length());
305     }
306   }
307
308   // Create the shared memory object.
309   base::SharedMemory shared_memory;
310
311   base::SharedMemoryCreateOptions options;
312   options.size = pickle.size();
313   options.share_read_only = true;
314   if (!shared_memory.Create(options))
315     return NULL;
316
317   if (!shared_memory.Map(pickle.size()))
318     return NULL;
319
320   // Copy the pickle to shared memory.
321   memcpy(shared_memory.memory(), pickle.data(), pickle.size());
322
323   base::SharedMemoryHandle readonly_handle;
324   if (!shared_memory.ShareReadOnlyToProcess(base::GetCurrentProcessHandle(),
325                                             &readonly_handle))
326     return NULL;
327
328   return new base::SharedMemory(readonly_handle, /*read_only=*/true);
329 }
330
331 // This method will be called on the file thread.
332 void UserScriptMaster::ScriptReloader::RunLoad(
333     const UserScriptList& user_scripts) {
334   LoadUserScripts(const_cast<UserScriptList*>(&user_scripts));
335
336   // Scripts now contains list of up-to-date scripts. Load the content in the
337   // shared memory and let the master know it's ready. We need to post the task
338   // back even if no scripts ware found to balance the AddRef/Release calls.
339   BrowserThread::PostTask(
340       master_thread_id_, FROM_HERE,
341       base::Bind(
342           &ScriptReloader::NotifyMaster, this, Serialize(user_scripts)));
343 }
344
345 UserScriptMaster::UserScriptMaster(Profile* profile)
346     : extensions_service_ready_(false),
347       pending_load_(false),
348       profile_(profile),
349       extension_registry_observer_(this) {
350   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
351   registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
352                  content::Source<Profile>(profile_));
353   registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED,
354                  content::NotificationService::AllBrowserContextsAndSources());
355 }
356
357 UserScriptMaster::~UserScriptMaster() {
358   if (script_reloader_.get())
359     script_reloader_->DisownMaster();
360 }
361
362 void UserScriptMaster::NewScriptsAvailable(base::SharedMemory* handle) {
363   // Ensure handle is deleted or released.
364   scoped_ptr<base::SharedMemory> handle_deleter(handle);
365
366   if (pending_load_) {
367     // While we were loading, there were further changes.  Don't bother
368     // notifying about these scripts and instead just immediately reload.
369     pending_load_ = false;
370     StartLoad();
371   } else {
372     // We're no longer loading.
373     script_reloader_ = NULL;
374
375     if (handle == NULL) {
376       // This can happen if we run out of file descriptors.  In that case, we
377       // have a choice between silently omitting all user scripts for new tabs,
378       // by nulling out shared_memory_, or only silently omitting new ones by
379       // leaving the existing object in place. The second seems less bad, even
380       // though it removes the possibility that freeing the shared memory block
381       // would open up enough FDs for long enough for a retry to succeed.
382
383       // Pretend the extension change didn't happen.
384       return;
385     }
386
387     // We've got scripts ready to go.
388     shared_memory_.swap(handle_deleter);
389
390     for (content::RenderProcessHost::iterator i(
391             content::RenderProcessHost::AllHostsIterator());
392          !i.IsAtEnd(); i.Advance()) {
393       SendUpdate(i.GetCurrentValue(), handle);
394     }
395
396     content::NotificationService::current()->Notify(
397         chrome::NOTIFICATION_USER_SCRIPTS_UPDATED,
398         content::Source<Profile>(profile_),
399         content::Details<base::SharedMemory>(handle));
400   }
401 }
402
403 ContentVerifier* UserScriptMaster::content_verifier() {
404   ExtensionSystem* system = ExtensionSystem::Get(profile_);
405   return system->content_verifier();
406 }
407
408 void UserScriptMaster::OnExtensionLoaded(
409     content::BrowserContext* browser_context,
410     const Extension* extension) {
411   // Add any content scripts inside the extension.
412   extensions_info_[extension->id()] =
413       ExtensionSet::ExtensionPathAndDefaultLocale(
414           extension->path(), LocaleInfo::GetDefaultLocale(extension));
415   bool incognito_enabled = util::IsIncognitoEnabled(extension->id(), profile_);
416   const UserScriptList& scripts =
417       ContentScriptsInfo::GetContentScripts(extension);
418   for (UserScriptList::const_iterator iter = scripts.begin();
419        iter != scripts.end();
420        ++iter) {
421     user_scripts_.push_back(*iter);
422     user_scripts_.back().set_incognito_enabled(incognito_enabled);
423   }
424   if (extensions_service_ready_) {
425     if (script_reloader_.get()) {
426       pending_load_ = true;
427     } else {
428       StartLoad();
429     }
430   }
431 }
432
433 void UserScriptMaster::OnExtensionUnloaded(
434     content::BrowserContext* browser_context,
435     const Extension* extension,
436     UnloadedExtensionInfo::Reason reason) {
437   // Remove any content scripts.
438   extensions_info_.erase(extension->id());
439   UserScriptList new_user_scripts;
440   for (UserScriptList::iterator iter = user_scripts_.begin();
441        iter != user_scripts_.end();
442        ++iter) {
443     if (iter->extension_id() != extension->id())
444       new_user_scripts.push_back(*iter);
445   }
446   user_scripts_ = new_user_scripts;
447   if (script_reloader_.get()) {
448     pending_load_ = true;
449   } else {
450     StartLoad();
451   }
452 }
453
454 void UserScriptMaster::Observe(int type,
455                                const content::NotificationSource& source,
456                                const content::NotificationDetails& details) {
457   bool should_start_load = false;
458   switch (type) {
459     case chrome::NOTIFICATION_EXTENSIONS_READY:
460       extensions_service_ready_ = true;
461       should_start_load = true;
462       break;
463     case content::NOTIFICATION_RENDERER_PROCESS_CREATED: {
464       content::RenderProcessHost* process =
465           content::Source<content::RenderProcessHost>(source).ptr();
466       Profile* profile = Profile::FromBrowserContext(
467           process->GetBrowserContext());
468       if (!profile_->IsSameProfile(profile))
469         return;
470       if (ScriptsReady())
471         SendUpdate(process, GetSharedMemory());
472       break;
473     }
474     default:
475       DCHECK(false);
476   }
477
478   if (should_start_load) {
479     if (script_reloader_.get()) {
480       pending_load_ = true;
481     } else {
482       StartLoad();
483     }
484   }
485 }
486
487 void UserScriptMaster::StartLoad() {
488   if (!script_reloader_.get())
489     script_reloader_ = new ScriptReloader(this);
490
491   script_reloader_->StartLoad(user_scripts_, extensions_info_);
492 }
493
494 void UserScriptMaster::SendUpdate(content::RenderProcessHost* process,
495                                   base::SharedMemory* shared_memory) {
496   // Don't allow injection of content scripts into <webview>.
497   if (process->IsGuest())
498     return;
499
500   Profile* profile = Profile::FromBrowserContext(process->GetBrowserContext());
501   // Make sure we only send user scripts to processes in our profile.
502   if (!profile_->IsSameProfile(profile))
503     return;
504
505   // If the process is being started asynchronously, early return.  We'll end up
506   // calling InitUserScripts when it's created which will call this again.
507   base::ProcessHandle handle = process->GetHandle();
508   if (!handle)
509     return;
510
511   base::SharedMemoryHandle handle_for_process;
512   if (!shared_memory->ShareToProcess(handle, &handle_for_process))
513     return;  // This can legitimately fail if the renderer asserts at startup.
514
515   if (base::SharedMemory::IsHandleValid(handle_for_process))
516     process->Send(new ExtensionMsg_UpdateUserScripts(handle_for_process));
517 }
518
519 }  // namespace extensions