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