Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2011 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 #include <vector>
      9 
     10 #include "base/file_path.h"
     11 #include "base/file_util.h"
     12 #include "base/path_service.h"
     13 #include "base/pickle.h"
     14 #include "base/stl_util-inl.h"
     15 #include "base/string_util.h"
     16 #include "base/threading/thread.h"
     17 #include "base/version.h"
     18 #include "chrome/browser/extensions/extension_service.h"
     19 #include "chrome/browser/profiles/profile.h"
     20 #include "chrome/common/extensions/extension.h"
     21 #include "chrome/common/extensions/extension_resource.h"
     22 #include "chrome/common/url_constants.h"
     23 #include "content/common/notification_service.h"
     24 #include "net/base/net_util.h"
     25 
     26 
     27 // Helper function to parse greasesmonkey headers
     28 static bool GetDeclarationValue(const base::StringPiece& line,
     29                                 const base::StringPiece& prefix,
     30                                 std::string* value) {
     31   base::StringPiece::size_type index = line.find(prefix);
     32   if (index == base::StringPiece::npos)
     33     return false;
     34 
     35   std::string temp(line.data() + index + prefix.length(),
     36                    line.length() - index - prefix.length());
     37 
     38   if (temp.empty() || !IsWhitespace(temp[0]))
     39     return false;
     40 
     41   TrimWhitespaceASCII(temp, TRIM_ALL, value);
     42   return true;
     43 }
     44 
     45 UserScriptMaster::ScriptReloader::ScriptReloader(UserScriptMaster* master)
     46     : master_(master) {
     47   CHECK(BrowserThread::GetCurrentThreadIdentifier(&master_thread_id_));
     48 }
     49 
     50 // static
     51 bool UserScriptMaster::ScriptReloader::ParseMetadataHeader(
     52       const base::StringPiece& script_text, UserScript* script) {
     53   // http://wiki.greasespot.net/Metadata_block
     54   base::StringPiece line;
     55   size_t line_start = 0;
     56   size_t line_end = line_start;
     57   bool in_metadata = false;
     58 
     59   static const base::StringPiece kUserScriptBegin("// ==UserScript==");
     60   static const base::StringPiece kUserScriptEng("// ==/UserScript==");
     61   static const base::StringPiece kNamespaceDeclaration("// @namespace");
     62   static const base::StringPiece kNameDeclaration("// @name");
     63   static const base::StringPiece kVersionDeclaration("// @version");
     64   static const base::StringPiece kDescriptionDeclaration("// @description");
     65   static const base::StringPiece kIncludeDeclaration("// @include");
     66   static const base::StringPiece kExcludeDeclaration("// @exclude");
     67   static const base::StringPiece kMatchDeclaration("// @match");
     68   static const base::StringPiece kRunAtDeclaration("// @run-at");
     69   static const base::StringPiece kRunAtDocumentStartValue("document-start");
     70   static const base::StringPiece kRunAtDocumentEndValue("document-end");
     71 
     72   while (line_start < script_text.length()) {
     73     line_end = script_text.find('\n', line_start);
     74 
     75     // Handle the case where there is no trailing newline in the file.
     76     if (line_end == std::string::npos)
     77       line_end = script_text.length() - 1;
     78 
     79     line.set(script_text.data() + line_start, line_end - line_start);
     80 
     81     if (!in_metadata) {
     82       if (line.starts_with(kUserScriptBegin))
     83         in_metadata = true;
     84     } else {
     85       if (line.starts_with(kUserScriptEng))
     86         break;
     87 
     88       std::string value;
     89       if (GetDeclarationValue(line, kIncludeDeclaration, &value)) {
     90         // We escape some characters that MatchPattern() considers special.
     91         ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
     92         ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
     93         script->add_glob(value);
     94       } else if (GetDeclarationValue(line, kExcludeDeclaration, &value)) {
     95         ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
     96         ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
     97         script->add_exclude_glob(value);
     98       } else if (GetDeclarationValue(line, kNamespaceDeclaration, &value)) {
     99         script->set_name_space(value);
    100       } else if (GetDeclarationValue(line, kNameDeclaration, &value)) {
    101         script->set_name(value);
    102       } else if (GetDeclarationValue(line, kVersionDeclaration, &value)) {
    103         scoped_ptr<Version> version(Version::GetVersionFromString(value));
    104         if (version.get())
    105           script->set_version(version->GetString());
    106       } else if (GetDeclarationValue(line, kDescriptionDeclaration, &value)) {
    107         script->set_description(value);
    108       } else if (GetDeclarationValue(line, kMatchDeclaration, &value)) {
    109         URLPattern pattern(UserScript::kValidUserScriptSchemes);
    110         if (URLPattern::PARSE_SUCCESS !=
    111             pattern.Parse(value, URLPattern::PARSE_LENIENT))
    112           return false;
    113         script->add_url_pattern(pattern);
    114       } else if (GetDeclarationValue(line, kRunAtDeclaration, &value)) {
    115         if (value == kRunAtDocumentStartValue)
    116           script->set_run_location(UserScript::DOCUMENT_START);
    117         else if (value != kRunAtDocumentEndValue)
    118           return false;
    119       }
    120 
    121       // TODO(aa): Handle more types of metadata.
    122     }
    123 
    124     line_start = line_end + 1;
    125   }
    126 
    127   // If no patterns were specified, default to @include *. This is what
    128   // Greasemonkey does.
    129   if (script->globs().empty() && script->url_patterns().empty())
    130     script->add_glob("*");
    131 
    132   return true;
    133 }
    134 
    135 void UserScriptMaster::ScriptReloader::StartScan(
    136     const FilePath& script_dir, const UserScriptList& lone_scripts) {
    137   // Add a reference to ourselves to keep ourselves alive while we're running.
    138   // Balanced by NotifyMaster().
    139   AddRef();
    140   BrowserThread::PostTask(
    141       BrowserThread::FILE, FROM_HERE,
    142       NewRunnableMethod(
    143           this, &UserScriptMaster::ScriptReloader::RunScan, script_dir,
    144           lone_scripts));
    145 }
    146 
    147 void UserScriptMaster::ScriptReloader::NotifyMaster(
    148     base::SharedMemory* memory) {
    149   // The master went away, so these new scripts aren't useful anymore.
    150   if (!master_)
    151     delete memory;
    152   else
    153     master_->NewScriptsAvailable(memory);
    154 
    155   // Drop our self-reference.
    156   // Balances StartScan().
    157   Release();
    158 }
    159 
    160 static bool LoadScriptContent(UserScript::File* script_file) {
    161   std::string content;
    162   const FilePath& path = ExtensionResource::GetFilePath(
    163       script_file->extension_root(), script_file->relative_path());
    164   if (path.empty() || !file_util::ReadFileToString(path, &content)) {
    165     LOG(WARNING) << "Failed to load user script file: " << path.value();
    166     return false;
    167   }
    168 
    169   // Remove BOM from the content.
    170   std::string::size_type index = content.find(kUtf8ByteOrderMark);
    171   if (index == 0) {
    172     script_file->set_content(content.substr(strlen(kUtf8ByteOrderMark)));
    173   } else {
    174     script_file->set_content(content);
    175   }
    176 
    177   return true;
    178 }
    179 
    180 // static
    181 void UserScriptMaster::ScriptReloader::LoadScriptsFromDirectory(
    182     const FilePath& script_dir, UserScriptList* result) {
    183   // Clear the list. We will populate it with the scripts found in script_dir.
    184   result->clear();
    185 
    186   // Find all the scripts in |script_dir|.
    187   if (!script_dir.value().empty()) {
    188     // Create the "<Profile>/User Scripts" directory if it doesn't exist
    189     if (!file_util::DirectoryExists(script_dir))
    190       file_util::CreateDirectory(script_dir);
    191 
    192     file_util::FileEnumerator enumerator(script_dir, false,
    193                                          file_util::FileEnumerator::FILES,
    194                                          FILE_PATH_LITERAL("*.user.js"));
    195     for (FilePath file = enumerator.Next(); !file.value().empty();
    196          file = enumerator.Next()) {
    197       result->push_back(UserScript());
    198       UserScript& user_script = result->back();
    199 
    200       // We default standalone user scripts to document-end for better
    201       // Greasemonkey compatibility.
    202       user_script.set_run_location(UserScript::DOCUMENT_END);
    203 
    204       // Push single js file in this UserScript.
    205       GURL url(std::string(chrome::kUserScriptScheme) + ":/" +
    206           net::FilePathToFileURL(file).ExtractFileName());
    207       user_script.js_scripts().push_back(UserScript::File(
    208           script_dir, file.BaseName(), url));
    209       UserScript::File& script_file = user_script.js_scripts().back();
    210       if (!LoadScriptContent(&script_file))
    211         result->pop_back();
    212       else
    213         ParseMetadataHeader(script_file.GetContent(), &user_script);
    214     }
    215   }
    216 }
    217 
    218 static void LoadLoneScripts(UserScriptList* lone_scripts) {
    219   for (size_t i = 0; i < lone_scripts->size(); ++i) {
    220     UserScript& script = lone_scripts->at(i);
    221     for (size_t k = 0; k < script.js_scripts().size(); ++k) {
    222       UserScript::File& script_file = script.js_scripts()[k];
    223       if (script_file.GetContent().empty())
    224         LoadScriptContent(&script_file);
    225     }
    226     for (size_t k = 0; k < script.css_scripts().size(); ++k) {
    227       UserScript::File& script_file = script.css_scripts()[k];
    228       if (script_file.GetContent().empty())
    229         LoadScriptContent(&script_file);
    230     }
    231   }
    232 }
    233 
    234 // Pickle user scripts and return pointer to the shared memory.
    235 static base::SharedMemory* Serialize(const UserScriptList& scripts) {
    236   Pickle pickle;
    237   pickle.WriteSize(scripts.size());
    238   for (size_t i = 0; i < scripts.size(); i++) {
    239     const UserScript& script = scripts[i];
    240     // TODO(aa): This can be replaced by sending content script metadata to
    241     // renderers along with other extension data in ExtensionMsg_Loaded.
    242     // See crbug.com/70516.
    243     script.Pickle(&pickle);
    244     // Write scripts as 'data' so that we can read it out in the slave without
    245     // allocating a new string.
    246     for (size_t j = 0; j < script.js_scripts().size(); j++) {
    247       base::StringPiece contents = script.js_scripts()[j].GetContent();
    248       pickle.WriteData(contents.data(), contents.length());
    249     }
    250     for (size_t j = 0; j < script.css_scripts().size(); j++) {
    251       base::StringPiece contents = script.css_scripts()[j].GetContent();
    252       pickle.WriteData(contents.data(), contents.length());
    253     }
    254   }
    255 
    256   // Create the shared memory object.
    257   scoped_ptr<base::SharedMemory> shared_memory(new base::SharedMemory());
    258 
    259   if (!shared_memory->CreateAndMapAnonymous(pickle.size()))
    260     return NULL;
    261 
    262   // Copy the pickle to shared memory.
    263   memcpy(shared_memory->memory(), pickle.data(), pickle.size());
    264 
    265   return shared_memory.release();
    266 }
    267 
    268 // This method will be called from the file thread
    269 void UserScriptMaster::ScriptReloader::RunScan(
    270     const FilePath script_dir, UserScriptList lone_script) {
    271   UserScriptList scripts;
    272   // Get list of user scripts.
    273   if (!script_dir.empty())
    274     LoadScriptsFromDirectory(script_dir, &scripts);
    275 
    276   LoadLoneScripts(&lone_script);
    277 
    278   // Merge with the explicit scripts
    279   scripts.reserve(scripts.size() + lone_script.size());
    280   scripts.insert(scripts.end(),
    281       lone_script.begin(), lone_script.end());
    282 
    283   // Scripts now contains list of up-to-date scripts. Load the content in the
    284   // shared memory and let the master know it's ready. We need to post the task
    285   // back even if no scripts ware found to balance the AddRef/Release calls
    286   BrowserThread::PostTask(
    287       master_thread_id_, FROM_HERE,
    288       NewRunnableMethod(
    289           this, &ScriptReloader::NotifyMaster, Serialize(scripts)));
    290 }
    291 
    292 
    293 UserScriptMaster::UserScriptMaster(const FilePath& script_dir, Profile* profile)
    294     : user_script_dir_(script_dir),
    295       extensions_service_ready_(false),
    296       pending_scan_(false),
    297       profile_(profile) {
    298   registrar_.Add(this, NotificationType::EXTENSIONS_READY,
    299                  Source<Profile>(profile_));
    300   registrar_.Add(this, NotificationType::EXTENSION_LOADED,
    301                  Source<Profile>(profile_));
    302   registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
    303                  Source<Profile>(profile_));
    304   registrar_.Add(this, NotificationType::EXTENSION_USER_SCRIPTS_UPDATED,
    305                  Source<Profile>(profile_));
    306 }
    307 
    308 UserScriptMaster::~UserScriptMaster() {
    309   if (script_reloader_)
    310     script_reloader_->DisownMaster();
    311 }
    312 
    313 void UserScriptMaster::NewScriptsAvailable(base::SharedMemory* handle) {
    314   // Ensure handle is deleted or released.
    315   scoped_ptr<base::SharedMemory> handle_deleter(handle);
    316 
    317   if (pending_scan_) {
    318     // While we were scanning, there were further changes.  Don't bother
    319     // notifying about these scripts and instead just immediately rescan.
    320     pending_scan_ = false;
    321     StartScan();
    322   } else {
    323     // We're no longer scanning.
    324     script_reloader_ = NULL;
    325     // We've got scripts ready to go.
    326     shared_memory_.swap(handle_deleter);
    327 
    328     NotificationService::current()->Notify(
    329         NotificationType::USER_SCRIPTS_UPDATED,
    330         Source<Profile>(profile_),
    331         Details<base::SharedMemory>(handle));
    332   }
    333 }
    334 
    335 void UserScriptMaster::Observe(NotificationType type,
    336                                const NotificationSource& source,
    337                                const NotificationDetails& details) {
    338   switch (type.value) {
    339     case NotificationType::EXTENSIONS_READY:
    340       extensions_service_ready_ = true;
    341       StartScan();
    342       break;
    343     case NotificationType::EXTENSION_LOADED: {
    344       // Add any content scripts inside the extension.
    345       const Extension* extension = Details<const Extension>(details).ptr();
    346       bool incognito_enabled = profile_->GetExtensionService()->
    347           IsIncognitoEnabled(extension->id());
    348       const UserScriptList& scripts = extension->content_scripts();
    349       for (UserScriptList::const_iterator iter = scripts.begin();
    350            iter != scripts.end(); ++iter) {
    351         lone_scripts_.push_back(*iter);
    352         lone_scripts_.back().set_incognito_enabled(incognito_enabled);
    353       }
    354       if (extensions_service_ready_)
    355         StartScan();
    356       break;
    357     }
    358     case NotificationType::EXTENSION_UNLOADED: {
    359       // Remove any content scripts.
    360       const Extension* extension =
    361           Details<UnloadedExtensionInfo>(details)->extension;
    362       UserScriptList new_lone_scripts;
    363       for (UserScriptList::iterator iter = lone_scripts_.begin();
    364            iter != lone_scripts_.end(); ++iter) {
    365         if (iter->extension_id() != extension->id())
    366           new_lone_scripts.push_back(*iter);
    367       }
    368       lone_scripts_ = new_lone_scripts;
    369       StartScan();
    370 
    371       // TODO(aa): Do we want to do something smarter for the scripts that have
    372       // already been injected?
    373 
    374       break;
    375     }
    376     case NotificationType::EXTENSION_USER_SCRIPTS_UPDATED: {
    377       const Extension* extension = Details<const Extension>(details).ptr();
    378       UserScriptList new_lone_scripts;
    379       bool incognito_enabled = profile_->GetExtensionService()->
    380           IsIncognitoEnabled(extension->id());
    381       for (UserScriptList::iterator iter = lone_scripts_.begin();
    382            iter != lone_scripts_.end(); ++iter) {
    383         if (iter->extension_id() == extension->id()) {
    384           iter->set_incognito_enabled(incognito_enabled);
    385         }
    386       }
    387       StartScan();
    388       break;
    389     }
    390 
    391     default:
    392       DCHECK(false);
    393   }
    394 }
    395 
    396 void UserScriptMaster::StartScan() {
    397   if (!script_reloader_)
    398     script_reloader_ = new ScriptReloader(this);
    399 
    400   script_reloader_->StartScan(user_script_dir_, lone_scripts_);
    401 }
    402