diff options
author | aa@google.com <aa@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-11-03 19:53:13 +0000 |
---|---|---|
committer | aa@google.com <aa@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-11-03 19:53:13 +0000 |
commit | e66546fb07e258ee2cce63978dccddca70e46aa1 (patch) | |
tree | 31e10e0c16e0825a37814f75cbb3ae69d461cd42 | |
parent | 592ea8ca5746b0f4630ea3313a8bc85dbeaba389 (diff) | |
download | chromium_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.cc | 117 | ||||
-rw-r--r-- | chrome/renderer/greasemonkey_slave.h | 37 | ||||
-rw-r--r-- | chrome/renderer/greasemonkey_slave_unittest.cc | 74 | ||||
-rw-r--r-- | chrome/test/unit/unittests.vcproj | 6 |
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> |