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