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.
5 #include "chrome/browser/extensions/user_script_master.h"
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"
22 using content::BrowserThread;
23 using extensions::URLPatternSet;
27 static void AddPattern(URLPatternSet* extent, const std::string& pattern) {
28 int schemes = URLPattern::SCHEME_ALL;
29 extent->AddPattern(URLPattern(schemes, pattern));
34 namespace extensions {
36 // Test bringing up a master on a specific directory, putting a script
39 class UserScriptMasterTest : public testing::Test,
40 public content::NotificationObserver {
42 UserScriptMasterTest()
43 : message_loop_(base::MessageLoop::TYPE_UI),
44 shared_memory_(NULL) {
47 virtual void SetUp() {
48 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
50 // Register for all user script notifications.
51 registrar_.Add(this, chrome::NOTIFICATION_USER_SCRIPTS_UPDATED,
52 content::NotificationService::AllSources());
54 // UserScriptMaster posts tasks to the file thread so make the current
55 // thread look like one.
56 file_thread_.reset(new content::TestBrowserThread(
57 BrowserThread::FILE, base::MessageLoop::current()));
58 ui_thread_.reset(new content::TestBrowserThread(
59 BrowserThread::UI, base::MessageLoop::current()));
62 virtual void TearDown() {
67 virtual void Observe(int type,
68 const content::NotificationSource& source,
69 const content::NotificationDetails& details) OVERRIDE {
70 DCHECK(type == chrome::NOTIFICATION_USER_SCRIPTS_UPDATED);
72 shared_memory_ = content::Details<base::SharedMemory>(details).ptr();
73 if (base::MessageLoop::current() == &message_loop_)
74 base::MessageLoop::current()->Quit();
77 // Directory containing user scripts.
78 base::ScopedTempDir temp_dir_;
80 content::NotificationRegistrar registrar_;
82 // MessageLoop used in tests.
83 base::MessageLoop message_loop_;
85 scoped_ptr<content::TestBrowserThread> file_thread_;
86 scoped_ptr<content::TestBrowserThread> ui_thread_;
88 // Updated to the script shared memory when we get notified.
89 base::SharedMemory* shared_memory_;
92 // Test that we get notified even when there are no scripts.
93 TEST_F(UserScriptMasterTest, NoScripts) {
94 TestingProfile profile;
95 scoped_refptr<UserScriptMaster> master(new UserScriptMaster(&profile));
97 message_loop_.PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
100 ASSERT_TRUE(shared_memory_ != NULL);
103 TEST_F(UserScriptMasterTest, Parse1) {
104 const std::string text(
105 "// This is my awesome script\n"
106 "// It does stuff.\n"
107 "// ==UserScript== trailing garbage\n"
108 "// @name foobar script\n"
109 "// @namespace http://www.google.com/\n"
110 "// @include *mail.google.com*\n"
113 "// @include *mail.yahoo.com*\r\n"
114 "// @include \t *mail.msn.com*\n" // extra spaces after "@include" OK
115 "//@include not-recognized\n" // must have one space after "//"
116 "// ==/UserScript== trailing garbage\n"
122 EXPECT_TRUE(UserScriptMaster::ScriptReloader::ParseMetadataHeader(
124 ASSERT_EQ(3U, script.globs().size());
125 EXPECT_EQ("*mail.google.com*", script.globs()[0]);
126 EXPECT_EQ("*mail.yahoo.com*", script.globs()[1]);
127 EXPECT_EQ("*mail.msn.com*", script.globs()[2]);
130 TEST_F(UserScriptMasterTest, Parse2) {
131 const std::string text("default to @include *");
134 EXPECT_TRUE(UserScriptMaster::ScriptReloader::ParseMetadataHeader(
136 ASSERT_EQ(1U, script.globs().size());
137 EXPECT_EQ("*", script.globs()[0]);
140 TEST_F(UserScriptMasterTest, Parse3) {
141 const std::string text(
142 "// ==UserScript==\n"
143 "// @include *foo*\n"
144 "// ==/UserScript=="); // no trailing newline
147 UserScriptMaster::ScriptReloader::ParseMetadataHeader(text, &script);
148 ASSERT_EQ(1U, script.globs().size());
149 EXPECT_EQ("*foo*", script.globs()[0]);
152 TEST_F(UserScriptMasterTest, Parse4) {
153 const std::string text(
154 "// ==UserScript==\n"
155 "// @match http://*.mail.google.com/*\n"
156 "// @match \t http://mail.yahoo.com/*\n"
157 "// ==/UserScript==\n");
159 URLPatternSet expected_patterns;
160 AddPattern(&expected_patterns, "http://*.mail.google.com/*");
161 AddPattern(&expected_patterns, "http://mail.yahoo.com/*");
164 EXPECT_TRUE(UserScriptMaster::ScriptReloader::ParseMetadataHeader(
166 EXPECT_EQ(0U, script.globs().size());
167 EXPECT_EQ(expected_patterns, script.url_patterns());
170 TEST_F(UserScriptMasterTest, Parse5) {
171 const std::string text(
172 "// ==UserScript==\n"
173 "// @match http://*mail.google.com/*\n"
174 "// ==/UserScript==\n");
176 // Invalid @match value.
178 EXPECT_FALSE(UserScriptMaster::ScriptReloader::ParseMetadataHeader(
182 TEST_F(UserScriptMasterTest, Parse6) {
183 const std::string text(
184 "// ==UserScript==\n"
185 "// @include http://*.mail.google.com/*\n"
186 "// @match \t http://mail.yahoo.com/*\n"
187 "// ==/UserScript==\n");
189 // Allowed to match @include and @match.
191 EXPECT_TRUE(UserScriptMaster::ScriptReloader::ParseMetadataHeader(
195 TEST_F(UserScriptMasterTest, Parse7) {
196 // Greasemonkey allows there to be any leading text before the comment marker.
197 const std::string text(
198 "// ==UserScript==\n"
199 "adsasdfasf// @name hello\n"
200 " // @description\twiggity woo\n"
201 "\t// @match \t http://mail.yahoo.com/*\n"
202 "// ==/UserScript==\n");
205 EXPECT_TRUE(UserScriptMaster::ScriptReloader::ParseMetadataHeader(
207 ASSERT_EQ("hello", script.name());
208 ASSERT_EQ("wiggity woo", script.description());
209 ASSERT_EQ(1U, script.url_patterns().patterns().size());
210 EXPECT_EQ("http://mail.yahoo.com/*",
211 script.url_patterns().begin()->GetAsString());
214 TEST_F(UserScriptMasterTest, Parse8) {
215 const std::string text(
216 "// ==UserScript==\n"
217 "// @name myscript\n"
218 "// @match http://www.google.com/*\n"
219 "// @exclude_match http://www.google.com/foo*\n"
220 "// ==/UserScript==\n");
223 EXPECT_TRUE(UserScriptMaster::ScriptReloader::ParseMetadataHeader(
225 ASSERT_EQ("myscript", script.name());
226 ASSERT_EQ(1U, script.url_patterns().patterns().size());
227 EXPECT_EQ("http://www.google.com/*",
228 script.url_patterns().begin()->GetAsString());
229 ASSERT_EQ(1U, script.exclude_url_patterns().patterns().size());
230 EXPECT_EQ("http://www.google.com/foo*",
231 script.exclude_url_patterns().begin()->GetAsString());
234 TEST_F(UserScriptMasterTest, SkipBOMAtTheBeginning) {
235 base::FilePath path = temp_dir_.path().AppendASCII("script.user.js");
236 const std::string content("\xEF\xBB\xBF alert('hello');");
237 size_t written = file_util::WriteFile(path, content.c_str(), content.size());
238 ASSERT_EQ(written, content.size());
240 UserScript user_script;
241 user_script.js_scripts().push_back(UserScript::File(
242 temp_dir_.path(), path.BaseName(), GURL()));
244 UserScriptList user_scripts;
245 user_scripts.push_back(user_script);
247 UserScriptMaster::ScriptReloader* script_reloader =
248 new UserScriptMaster::ScriptReloader(NULL);
249 script_reloader->AddRef();
250 script_reloader->LoadUserScripts(&user_scripts);
251 script_reloader->Release();
253 EXPECT_EQ(content.substr(3),
254 user_scripts[0].js_scripts()[0].GetContent().as_string());
257 TEST_F(UserScriptMasterTest, LeaveBOMNotAtTheBeginning) {
258 base::FilePath path = temp_dir_.path().AppendASCII("script.user.js");
259 const std::string content("alert('here's a BOOM: \xEF\xBB\xBF');");
260 size_t written = file_util::WriteFile(path, content.c_str(), content.size());
261 ASSERT_EQ(written, content.size());
263 UserScript user_script;
264 user_script.js_scripts().push_back(UserScript::File(
265 temp_dir_.path(), path.BaseName(), GURL()));
267 UserScriptList user_scripts;
268 user_scripts.push_back(user_script);
270 UserScriptMaster::ScriptReloader* script_reloader =
271 new UserScriptMaster::ScriptReloader(NULL);
272 script_reloader->AddRef();
273 script_reloader->LoadUserScripts(&user_scripts);
274 script_reloader->Release();
276 EXPECT_EQ(content, user_scripts[0].js_scripts()[0].GetContent().as_string());
279 } // namespace extensions