Home | History | Annotate | Download | only in extensions
      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/file_util.h"
     10 #include "base/files/file_path.h"
     11 #include "base/files/scoped_temp_dir.h"
     12 #include "base/message_loop/message_loop.h"
     13 #include "base/path_service.h"
     14 #include "base/strings/string_util.h"
     15 #include "chrome/browser/chrome_notification_types.h"
     16 #include "chrome/test/base/testing_profile.h"
     17 #include "content/public/browser/notification_registrar.h"
     18 #include "content/public/browser/notification_service.h"
     19 #include "content/public/test/test_browser_thread.h"
     20 #include "testing/gtest/include/gtest/gtest.h"
     21 
     22 using content::BrowserThread;
     23 using extensions::URLPatternSet;
     24 
     25 namespace {
     26 
     27 static void AddPattern(URLPatternSet* extent, const std::string& pattern) {
     28   int schemes = URLPattern::SCHEME_ALL;
     29   extent->AddPattern(URLPattern(schemes, pattern));
     30 }
     31 
     32 }
     33 
     34 namespace extensions {
     35 
     36 // Test bringing up a master on a specific directory, putting a script
     37 // in there, etc.
     38 
     39 class UserScriptMasterTest : public testing::Test,
     40                              public content::NotificationObserver {
     41  public:
     42   UserScriptMasterTest() : shared_memory_(NULL) {
     43   }
     44 
     45   virtual void SetUp() {
     46     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
     47 
     48     // Register for all user script notifications.
     49     registrar_.Add(this, chrome::NOTIFICATION_USER_SCRIPTS_UPDATED,
     50                    content::NotificationService::AllSources());
     51 
     52     // UserScriptMaster posts tasks to the file thread so make the current
     53     // thread look like one.
     54     file_thread_.reset(new content::TestBrowserThread(
     55         BrowserThread::FILE, base::MessageLoop::current()));
     56     ui_thread_.reset(new content::TestBrowserThread(
     57         BrowserThread::UI, base::MessageLoop::current()));
     58   }
     59 
     60   virtual void TearDown() {
     61     file_thread_.reset();
     62     ui_thread_.reset();
     63   }
     64 
     65   virtual void Observe(int type,
     66                        const content::NotificationSource& source,
     67                        const content::NotificationDetails& details) OVERRIDE {
     68     DCHECK(type == chrome::NOTIFICATION_USER_SCRIPTS_UPDATED);
     69 
     70     shared_memory_ = content::Details<base::SharedMemory>(details).ptr();
     71     if (base::MessageLoop::current() == &message_loop_)
     72       base::MessageLoop::current()->Quit();
     73   }
     74 
     75   // Directory containing user scripts.
     76   base::ScopedTempDir temp_dir_;
     77 
     78   content::NotificationRegistrar registrar_;
     79 
     80   // MessageLoop used in tests.
     81   base::MessageLoopForUI message_loop_;
     82 
     83   scoped_ptr<content::TestBrowserThread> file_thread_;
     84   scoped_ptr<content::TestBrowserThread> ui_thread_;
     85 
     86   // Updated to the script shared memory when we get notified.
     87   base::SharedMemory* shared_memory_;
     88 };
     89 
     90 // Test that we get notified even when there are no scripts.
     91 TEST_F(UserScriptMasterTest, NoScripts) {
     92   TestingProfile profile;
     93   scoped_refptr<UserScriptMaster> master(new UserScriptMaster(&profile));
     94   master->StartLoad();
     95   message_loop_.PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
     96   message_loop_.Run();
     97 
     98   ASSERT_TRUE(shared_memory_ != NULL);
     99 }
    100 
    101 TEST_F(UserScriptMasterTest, Parse1) {
    102   const std::string text(
    103     "// This is my awesome script\n"
    104     "// It does stuff.\n"
    105     "// ==UserScript==   trailing garbage\n"
    106     "// @name foobar script\n"
    107     "// @namespace http://www.google.com/\n"
    108     "// @include *mail.google.com*\n"
    109     "// \n"
    110     "// @othergarbage\n"
    111     "// @include *mail.yahoo.com*\r\n"
    112     "// @include  \t *mail.msn.com*\n" // extra spaces after "@include" OK
    113     "//@include not-recognized\n" // must have one space after "//"
    114     "// ==/UserScript==  trailing garbage\n"
    115     "\n"
    116     "\n"
    117     "alert('hoo!');\n");
    118 
    119   UserScript script;
    120   EXPECT_TRUE(UserScriptMaster::ScriptReloader::ParseMetadataHeader(
    121       text, &script));
    122   ASSERT_EQ(3U, script.globs().size());
    123   EXPECT_EQ("*mail.google.com*", script.globs()[0]);
    124   EXPECT_EQ("*mail.yahoo.com*", script.globs()[1]);
    125   EXPECT_EQ("*mail.msn.com*", script.globs()[2]);
    126 }
    127 
    128 TEST_F(UserScriptMasterTest, Parse2) {
    129   const std::string text("default to @include *");
    130 
    131   UserScript script;
    132   EXPECT_TRUE(UserScriptMaster::ScriptReloader::ParseMetadataHeader(
    133       text, &script));
    134   ASSERT_EQ(1U, script.globs().size());
    135   EXPECT_EQ("*", script.globs()[0]);
    136 }
    137 
    138 TEST_F(UserScriptMasterTest, Parse3) {
    139   const std::string text(
    140     "// ==UserScript==\n"
    141     "// @include *foo*\n"
    142     "// ==/UserScript=="); // no trailing newline
    143 
    144   UserScript script;
    145   UserScriptMaster::ScriptReloader::ParseMetadataHeader(text, &script);
    146   ASSERT_EQ(1U, script.globs().size());
    147   EXPECT_EQ("*foo*", script.globs()[0]);
    148 }
    149 
    150 TEST_F(UserScriptMasterTest, Parse4) {
    151   const std::string text(
    152     "// ==UserScript==\n"
    153     "// @match http://*.mail.google.com/*\n"
    154     "// @match  \t http://mail.yahoo.com/*\n"
    155     "// ==/UserScript==\n");
    156 
    157   URLPatternSet expected_patterns;
    158   AddPattern(&expected_patterns, "http://*.mail.google.com/*");
    159   AddPattern(&expected_patterns, "http://mail.yahoo.com/*");
    160 
    161   UserScript script;
    162   EXPECT_TRUE(UserScriptMaster::ScriptReloader::ParseMetadataHeader(
    163       text, &script));
    164   EXPECT_EQ(0U, script.globs().size());
    165   EXPECT_EQ(expected_patterns, script.url_patterns());
    166 }
    167 
    168 TEST_F(UserScriptMasterTest, Parse5) {
    169   const std::string text(
    170     "// ==UserScript==\n"
    171     "// @match http://*mail.google.com/*\n"
    172     "// ==/UserScript==\n");
    173 
    174   // Invalid @match value.
    175   UserScript script;
    176   EXPECT_FALSE(UserScriptMaster::ScriptReloader::ParseMetadataHeader(
    177       text, &script));
    178 }
    179 
    180 TEST_F(UserScriptMasterTest, Parse6) {
    181   const std::string text(
    182     "// ==UserScript==\n"
    183     "// @include http://*.mail.google.com/*\n"
    184     "// @match  \t http://mail.yahoo.com/*\n"
    185     "// ==/UserScript==\n");
    186 
    187   // Allowed to match @include and @match.
    188   UserScript script;
    189   EXPECT_TRUE(UserScriptMaster::ScriptReloader::ParseMetadataHeader(
    190       text, &script));
    191 }
    192 
    193 TEST_F(UserScriptMasterTest, Parse7) {
    194   // Greasemonkey allows there to be any leading text before the comment marker.
    195   const std::string text(
    196     "// ==UserScript==\n"
    197     "adsasdfasf// @name hello\n"
    198     "  // @description\twiggity woo\n"
    199     "\t// @match  \t http://mail.yahoo.com/*\n"
    200     "// ==/UserScript==\n");
    201 
    202   UserScript script;
    203   EXPECT_TRUE(UserScriptMaster::ScriptReloader::ParseMetadataHeader(
    204       text, &script));
    205   ASSERT_EQ("hello", script.name());
    206   ASSERT_EQ("wiggity woo", script.description());
    207   ASSERT_EQ(1U, script.url_patterns().patterns().size());
    208   EXPECT_EQ("http://mail.yahoo.com/*",
    209             script.url_patterns().begin()->GetAsString());
    210 }
    211 
    212 TEST_F(UserScriptMasterTest, Parse8) {
    213   const std::string text(
    214     "// ==UserScript==\n"
    215     "// @name myscript\n"
    216     "// @match http://www.google.com/*\n"
    217     "// @exclude_match http://www.google.com/foo*\n"
    218     "// ==/UserScript==\n");
    219 
    220   UserScript script;
    221   EXPECT_TRUE(UserScriptMaster::ScriptReloader::ParseMetadataHeader(
    222       text, &script));
    223   ASSERT_EQ("myscript", script.name());
    224   ASSERT_EQ(1U, script.url_patterns().patterns().size());
    225   EXPECT_EQ("http://www.google.com/*",
    226             script.url_patterns().begin()->GetAsString());
    227   ASSERT_EQ(1U, script.exclude_url_patterns().patterns().size());
    228   EXPECT_EQ("http://www.google.com/foo*",
    229             script.exclude_url_patterns().begin()->GetAsString());
    230 }
    231 
    232 TEST_F(UserScriptMasterTest, SkipBOMAtTheBeginning) {
    233   base::FilePath path = temp_dir_.path().AppendASCII("script.user.js");
    234   const std::string content("\xEF\xBB\xBF alert('hello');");
    235   size_t written = base::WriteFile(path, content.c_str(), content.size());
    236   ASSERT_EQ(written, content.size());
    237 
    238   UserScript user_script;
    239   user_script.js_scripts().push_back(UserScript::File(
    240       temp_dir_.path(), path.BaseName(), GURL()));
    241 
    242   UserScriptList user_scripts;
    243   user_scripts.push_back(user_script);
    244 
    245   UserScriptMaster::ScriptReloader* script_reloader =
    246       new UserScriptMaster::ScriptReloader(NULL);
    247   script_reloader->AddRef();
    248   script_reloader->LoadUserScripts(&user_scripts);
    249   script_reloader->Release();
    250 
    251   EXPECT_EQ(content.substr(3),
    252             user_scripts[0].js_scripts()[0].GetContent().as_string());
    253 }
    254 
    255 TEST_F(UserScriptMasterTest, LeaveBOMNotAtTheBeginning) {
    256   base::FilePath path = temp_dir_.path().AppendASCII("script.user.js");
    257   const std::string content("alert('here's a BOOM: \xEF\xBB\xBF');");
    258   size_t written = base::WriteFile(path, content.c_str(), content.size());
    259   ASSERT_EQ(written, content.size());
    260 
    261   UserScript user_script;
    262   user_script.js_scripts().push_back(UserScript::File(
    263       temp_dir_.path(), path.BaseName(), GURL()));
    264 
    265   UserScriptList user_scripts;
    266   user_scripts.push_back(user_script);
    267 
    268   UserScriptMaster::ScriptReloader* script_reloader =
    269       new UserScriptMaster::ScriptReloader(NULL);
    270   script_reloader->AddRef();
    271   script_reloader->LoadUserScripts(&user_scripts);
    272   script_reloader->Release();
    273 
    274   EXPECT_EQ(content, user_scripts[0].js_scripts()[0].GetContent().as_string());
    275 }
    276 
    277 }  // namespace extensions
    278