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
24class UserScriptMasterTest : public testing::Test,
25                             public NotificationObserver {
26 public:
27  UserScriptMasterTest()
28      : message_loop_(MessageLoop::TYPE_UI),
29        shared_memory_(NULL) {
30  }
31
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
45  virtual void TearDown() {
46    file_thread_.reset();
47  }
48
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.
74TEST_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.
86TEST_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
104TEST_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
131TEST_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
141TEST_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
153TEST_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
171TEST_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
183TEST_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
196TEST_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
215TEST_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
236TEST_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