summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoraa@google.com <aa@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-11-03 19:53:13 +0000
committeraa@google.com <aa@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-11-03 19:53:13 +0000
commite66546fb07e258ee2cce63978dccddca70e46aa1 (patch)
tree31e10e0c16e0825a37814f75cbb3ae69d461cd42
parent592ea8ca5746b0f4630ea3313a8bc85dbeaba389 (diff)
downloadchromium_src-e66546fb07e258ee2cce63978dccddca70e46aa1.zip
chromium_src-e66546fb07e258ee2cce63978dccddca70e46aa1.tar.gz
chromium_src-e66546fb07e258ee2cce63978dccddca70e46aa1.tar.bz2
Add support for @include in Greasemonkey scripts.
Review URL: http://codereview.chromium.org/8020 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@4478 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/renderer/greasemonkey_slave.cc117
-rw-r--r--chrome/renderer/greasemonkey_slave.h37
-rw-r--r--chrome/renderer/greasemonkey_slave_unittest.cc74
-rw-r--r--chrome/test/unit/unittests.vcproj6
4 files changed, 220 insertions, 14 deletions
diff --git a/chrome/renderer/greasemonkey_slave.cc b/chrome/renderer/greasemonkey_slave.cc
index cba48d1..5a4072a 100644
--- a/chrome/renderer/greasemonkey_slave.cc
+++ b/chrome/renderer/greasemonkey_slave.cc
@@ -7,7 +7,109 @@
#include "base/logging.h"
#include "base/pickle.h"
#include "base/shared_memory.h"
+#include "googleurl/src/gurl.h"
+
+// GreasemonkeyScript
+
+void GreasemonkeyScript::Parse(const StringPiece& script_text) {
+ ParseMetadata(script_text);
+
+ // TODO(aa): Set body to just the part after the metadata block? This would
+ // significantly cut down on the size of the injected script in some cases.
+ // Would require remembering the line number the body begins at, for correct
+ // error line number reporting.
+ body_ = script_text;
+}
+
+bool GreasemonkeyScript::MatchesUrl(const GURL& url) {
+ for (std::vector<std::string>::iterator pattern = include_patterns_.begin();
+ pattern != include_patterns_.end(); ++pattern) {
+ if (MatchPattern(url.spec(), *pattern)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void GreasemonkeyScript::ParseMetadata(const StringPiece& script_text) {
+ // http://wiki.greasespot.net/Metadata_block
+ StringPiece line;
+ size_t line_start = 0;
+ size_t line_end = 0;
+ bool in_metadata = false;
+
+ static const StringPiece kUserScriptBegin("// ==UserScript==");
+ static const StringPiece kUserScriptEng("// ==/UserScript==");
+ static const StringPiece kIncludeDeclaration("// @include ");
+
+ while (line_start < script_text.length()) {
+ line_end = script_text.find('\n', line_start);
+
+ // Handle the case where there is no trailing newline in the file.
+ if (line_end == std::string::npos) {
+ line_end = script_text.length() - 1;
+ }
+
+ line.set(script_text.data() + line_start, line_end - line_start);
+
+ if (!in_metadata) {
+ if (line.starts_with(kUserScriptBegin)) {
+ in_metadata = true;
+ }
+ } else {
+ if (line.starts_with(kUserScriptEng)) {
+ break;
+ }
+
+ if (line.starts_with(kIncludeDeclaration)) {
+ std::string pattern(line.data() + kIncludeDeclaration.length(),
+ line.length() - kIncludeDeclaration.length());
+ std::string pattern_trimmed;
+ TrimWhitespace(pattern, TRIM_ALL, &pattern_trimmed);
+ AddInclude(pattern_trimmed);
+ }
+
+ // TODO(aa): Handle more types of metadata.
+ }
+
+ line_start = line_end + 1;
+ }
+
+ // If no @include patterns were specified, default to @include *.
+ // This is what Greasemonkey for Firefox does.
+ if (include_patterns_.size() == 0) {
+ AddInclude("*");
+ }
+}
+
+void GreasemonkeyScript::AddInclude(const std::string &glob_pattern) {
+ include_patterns_.push_back(EscapeGlob(glob_pattern));
+}
+
+std::string GreasemonkeyScript::EscapeGlob(const std::string& input_pattern) {
+ std::string output_pattern;
+
+ for (size_t i = 0; i < input_pattern.length(); ++i) {
+ switch (input_pattern[i]) {
+ // These characters have special meaning to the MatchPattern() function,
+ // so we escape them.
+ case '\\':
+ case '?':
+ output_pattern += '\\';
+ // fall through
+
+ default:
+ output_pattern += input_pattern[i];
+ }
+ }
+
+ return output_pattern;
+}
+
+
+// GreasemonkeySlave
GreasemonkeySlave::GreasemonkeySlave() : shared_memory_(NULL) {
}
@@ -47,22 +149,21 @@ bool GreasemonkeySlave::UpdateScripts(SharedMemoryHandle shared_memory) {
pickle.ReadData(&iter, &url, &url_length);
pickle.ReadData(&iter, &body, &body_length);
- GreasemonkeyScript script(StringPiece(url, url_length));
- if (script.Parse(StringPiece(body, body_length))) {
- scripts_.push_back(script);
- }
+ scripts_.push_back(GreasemonkeyScript(StringPiece(url, url_length)));
+ GreasemonkeyScript& script = scripts_.back();
+ script.Parse(StringPiece(body, body_length));
}
return true;
}
bool GreasemonkeySlave::InjectScripts(WebFrame* frame) {
- // TODO(aa): Check script patterns here
-
for (std::vector<GreasemonkeyScript>::iterator script = scripts_.begin();
script != scripts_.end(); ++script) {
- frame->ExecuteJavaScript(script->GetBody().as_string(),
- script->GetURL().as_string());
+ if (script->MatchesUrl(frame->GetURL())) {
+ frame->ExecuteJavaScript(script->GetBody().as_string(),
+ script->GetURL().as_string());
+ }
}
return true;
diff --git a/chrome/renderer/greasemonkey_slave.h b/chrome/renderer/greasemonkey_slave.h
index a6bec2f2..5be3697 100644
--- a/chrome/renderer/greasemonkey_slave.h
+++ b/chrome/renderer/greasemonkey_slave.h
@@ -8,29 +8,51 @@
#include "base/scoped_ptr.h"
#include "base/shared_memory.h"
#include "base/string_piece.h"
+#include "base/string_util.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
#include "webkit/glue/webframe.h"
+
// Parsed representation of a Greasemonkey script.
class GreasemonkeyScript {
public:
GreasemonkeyScript(const StringPiece& script_url)
: url_(script_url) {}
+ // Gets the script body that should be injected into matching content.
const StringPiece& GetBody() const {
return body_;
}
+ // Gets a URL where this script can be found.
const StringPiece& GetURL() const {
return url_;
}
- bool Parse(const StringPiece& script_text) {
- // TODO(aa): Parse out includes, convert to regexes.
- body_ = script_text;
- return true;
- }
+ // Parses the text content of a user script file.
+ void Parse(const StringPiece& script_text);
+
+ // Returns true if the script should be applied to the specified URL, false
+ // otherwise.
+ bool MatchesUrl(const GURL& url);
private:
+ FRIEND_TEST(GreasemonkeySlaveTest, EscapeGlob);
+ FRIEND_TEST(GreasemonkeySlaveTest, Parse1);
+ FRIEND_TEST(GreasemonkeySlaveTest, Parse2);
+ FRIEND_TEST(GreasemonkeySlaveTest, Parse3);
+
+ // Helper function to convert the Greasemonkey glob format to the patterns
+ // used internally to test URLs.
+ static std::string EscapeGlob(const std::string& glob);
+
+ // Parses the metadata block from the script.
+ void ParseMetadata(const StringPiece& script_text);
+
+ // Adds an include pattern that will be checked to determine whether to
+ // include a script on a given page.
+ void AddInclude(const std::string &glob_pattern);
+
// The body of the script, which will be injected into content pages. This
// references shared_memory_, and is valid until that memory is either
// deleted or Unmap()'d.
@@ -39,6 +61,11 @@ class GreasemonkeyScript {
// The url of the file the script came from. This references shared_memory_,
// and is valid until that memory is either deleted or Unmap()'d.
StringPiece url_;
+
+ // List of patterns to test URLs against for this script. These patterns have
+ // been escaped for use with MatchPattern() in string_utils ('?' and '\' are
+ // escaped).
+ std::vector<std::string> include_patterns_;
};
diff --git a/chrome/renderer/greasemonkey_slave_unittest.cc b/chrome/renderer/greasemonkey_slave_unittest.cc
new file mode 100644
index 0000000..06c48c2
--- /dev/null
+++ b/chrome/renderer/greasemonkey_slave_unittest.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+#include "chrome/renderer/greasemonkey_slave.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class GreasemonkeySlaveTest : public testing::Test {
+};
+
+TEST(GreasemonkeySlaveTest, EscapeGlob) {
+ EXPECT_EQ("", GreasemonkeyScript::EscapeGlob(""));
+ EXPECT_EQ("*", GreasemonkeyScript::EscapeGlob("*"));
+ EXPECT_EQ("www.google.com", GreasemonkeyScript::EscapeGlob("www.google.com"));
+ EXPECT_EQ("*google.com*", GreasemonkeyScript::EscapeGlob("*google.com*"));
+ EXPECT_EQ("foo\\\\bar\\?hot=dog",
+ GreasemonkeyScript::EscapeGlob("foo\\bar?hot=dog"));
+}
+
+TEST(GreasemonkeySlaveTest, Parse1) {
+ const std::string text(
+ "// This is my awesome script\n"
+ "// It does stuff.\n"
+ "// ==UserScript== trailing garbage\n"
+ "// @name foobar script\n"
+ "// @namespace http://www.google.com/\n"
+ "// @include *mail.google.com*\n"
+ "// \n"
+ "// @othergarbage\n"
+ "// @include *mail.yahoo.com*\r\n"
+ "// @include \t *mail.msn.com*\n" // extra spaces after "@include" OK
+ "//@include not-recognized\n" // must have one space after "//"
+ "// ==/UserScript== trailing garbage\n"
+ "\n"
+ "\n"
+ "alert('hoo!');\n");
+
+ GreasemonkeyScript script("foo");
+ script.Parse(text);
+ EXPECT_EQ(3, script.include_patterns_.size());
+ EXPECT_EQ(text, script.GetBody());
+ EXPECT_TRUE(script.MatchesUrl(GURL("http://mail.google.com")));
+ EXPECT_TRUE(script.MatchesUrl(GURL("http://mail.google.com/foo")));
+ EXPECT_TRUE(script.MatchesUrl(GURL("http://mail.yahoo.com/bar")));
+ EXPECT_TRUE(script.MatchesUrl(GURL("http://mail.msn.com/baz")));
+ EXPECT_FALSE(script.MatchesUrl(GURL("http://www.hotmail.com")));
+}
+
+TEST(GreasemonkeySlaveTest, Parse2) {
+ const std::string text("default to @include *");
+
+ GreasemonkeyScript script("foo");
+ script.Parse(text);
+ EXPECT_EQ(1, script.include_patterns_.size());
+ EXPECT_EQ(text, script.GetBody());
+ EXPECT_TRUE(script.MatchesUrl(GURL("foo")));
+ EXPECT_TRUE(script.MatchesUrl(GURL("bar")));
+}
+
+TEST(GreasemonkeySlaveTest, Parse3) {
+ const std::string text(
+ "// ==UserScript==\n"
+ "// @include *foo*\n"
+ "// ==/UserScript=="); // no trailing newline
+
+ GreasemonkeyScript script("foo");
+ script.Parse(text);
+ EXPECT_EQ(1, script.include_patterns_.size());
+ EXPECT_EQ(text, script.GetBody());
+ EXPECT_TRUE(script.MatchesUrl(GURL("http://foo.com/bar")));
+ EXPECT_FALSE(script.MatchesUrl(GURL("http://baz.org")));
+}
diff --git a/chrome/test/unit/unittests.vcproj b/chrome/test/unit/unittests.vcproj
index 9f685e9..2c9a057 100644
--- a/chrome/test/unit/unittests.vcproj
+++ b/chrome/test/unit/unittests.vcproj
@@ -1007,12 +1007,16 @@
</File>
</Filter>
<Filter
- Name="TestGreasemonkeyMaster"
+ Name="TestGreasemonkey"
>
<File
RelativePath="..\..\browser\greasemonkey_master_unittest.cc"
>
</File>
+ <File
+ RelativePath="..\..\renderer\greasemonkey_slave_unittest.cc"
+ >
+ </File>
</Filter>
</Files>
<Globals>