diff options
author | rdevlin.cronin@chromium.org <rdevlin.cronin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-05 21:56:22 +0000 |
---|---|---|
committer | rdevlin.cronin@chromium.org <rdevlin.cronin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-05 21:56:22 +0000 |
commit | fa5fed3316ecc4f13efa1319e646cb6b974e858d (patch) | |
tree | 75c38c014130a0e917df950b3f556c68eb924127 /extensions | |
parent | 9db89a7513b69546b5d28790c1f065a22a8f07a1 (diff) | |
download | chromium_src-fa5fed3316ecc4f13efa1319e646cb6b974e858d.zip chromium_src-fa5fed3316ecc4f13efa1319e646cb6b974e858d.tar.gz chromium_src-fa5fed3316ecc4f13efa1319e646cb6b974e858d.tar.bz2 |
Add Error Console UI for install warnings to the chrome:extensions page (hidden behind the error console switch).
Snazzy Images: http://imgur.com/a/7QnMo#0 (updated 9/3)
BUG=21734
Review URL: https://chromiumcodereview.appspot.com/22938005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@221527 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'extensions')
-rw-r--r-- | extensions/browser/extension_error.cc | 60 | ||||
-rw-r--r-- | extensions/browser/extension_error.h | 23 | ||||
-rw-r--r-- | extensions/browser/manifest_highlighter.cc | 166 | ||||
-rw-r--r-- | extensions/browser/manifest_highlighter.h | 74 | ||||
-rw-r--r-- | extensions/browser/manifest_highlighter_unittest.cc | 98 |
5 files changed, 401 insertions, 20 deletions
diff --git a/extensions/browser/extension_error.cc b/extensions/browser/extension_error.cc index b372885..272028e 100644 --- a/extensions/browser/extension_error.cc +++ b/extensions/browser/extension_error.cc @@ -11,30 +11,20 @@ #include "url/gurl.h" using base::string16; +using base::DictionaryValue; namespace extensions { -namespace { - -const char kLineNumberKey[] = "lineNumber"; -const char kColumnNumberKey[] = "columnNumber"; -const char kURLKey[] = "url"; -const char kFunctionNameKey[] = "functionName"; -const char kExecutionContextURLKey[] = "executionContextURL"; -const char kStackTraceKey[] = "stackTrace"; - -// Try to retrieve an extension ID from a |url|. On success, returns true and -// populates |extension_id| with the ID. On failure, returns false and leaves -// extension_id untouched. -bool GetExtensionIDFromGURL(const GURL& url, std::string* extension_id) { - if (url.SchemeIs(kExtensionScheme)) { - *extension_id = url.host(); - return true; - } - return false; -} +//////////////////////////////////////////////////////////////////////////////// +// ExtensionError -} // namespace +// Static JSON keys. +const char ExtensionError::kExtensionIdKey[] = "extensionId"; +const char ExtensionError::kFromIncognitoKey[] = "fromIncognito"; +const char ExtensionError::kLevelKey[] = "level"; +const char ExtensionError::kMessageKey[] = "message"; +const char ExtensionError::kSourceKey[] = "source"; +const char ExtensionError::kTypeKey[] = "type"; ExtensionError::ExtensionError(Type type, const std::string& extension_id, @@ -54,6 +44,20 @@ ExtensionError::ExtensionError(Type type, ExtensionError::~ExtensionError() { } +scoped_ptr<DictionaryValue> ExtensionError::ToValue() const { + // TODO(rdevlin.cronin): Use ValueBuilder when it's moved from + // chrome/common/extensions. + scoped_ptr<DictionaryValue> value(new DictionaryValue); + value->SetInteger(kTypeKey, static_cast<int>(type_)); + value->SetString(kExtensionIdKey, extension_id_); + value->SetBoolean(kFromIncognitoKey, from_incognito_); + value->SetInteger(kLevelKey, static_cast<int>(level_)); + value->SetString(kSourceKey, source_); + value->SetString(kMessageKey, message_); + + return value.Pass(); +} + std::string ExtensionError::PrintForTest() const { return std::string("Extension Error:") + "\n OTR: " + std::string(from_incognito_ ? "true" : "false") + @@ -72,6 +76,13 @@ bool ExtensionError::IsEqual(const ExtensionError* rhs) const { IsEqualImpl(rhs); } +//////////////////////////////////////////////////////////////////////////////// +// ManifestError + +// Static JSON keys. +const char ManifestError::kManifestKeyKey[] = "manifestKey"; +const char ManifestError::kManifestSpecificKey[] = "manifestSpecific"; + ManifestError::ManifestError(const std::string& extension_id, const string16& message, const string16& manifest_key, @@ -89,6 +100,15 @@ ManifestError::ManifestError(const std::string& extension_id, ManifestError::~ManifestError() { } +scoped_ptr<DictionaryValue> ManifestError::ToValue() const { + scoped_ptr<DictionaryValue> value = ExtensionError::ToValue(); + if (!manifest_key_.empty()) + value->SetString(kManifestKeyKey, manifest_key_); + if (!manifest_specific_.empty()) + value->SetString(kManifestSpecificKey, manifest_specific_); + return value.Pass(); +} + std::string ManifestError::PrintForTest() const { return ExtensionError::PrintForTest() + "\n Type: ManifestError"; diff --git a/extensions/browser/extension_error.h b/extensions/browser/extension_error.h index ceb1504..7e02bb3 100644 --- a/extensions/browser/extension_error.h +++ b/extensions/browser/extension_error.h @@ -10,10 +10,15 @@ #include "base/compiler_specific.h" #include "base/logging.h" +#include "base/memory/scoped_ptr.h" #include "base/strings/string16.h" #include "extensions/common/stack_frame.h" #include "url/gurl.h" +namespace base { +class DictionaryValue; +} + namespace extensions { class ExtensionError { @@ -25,6 +30,9 @@ class ExtensionError { virtual ~ExtensionError(); + // Serializes the ExtensionError into JSON format. + virtual scoped_ptr<base::DictionaryValue> ToValue() const; + virtual std::string PrintForTest() const; // Return true if this error and |rhs| are considered equal, and should be @@ -40,6 +48,14 @@ class ExtensionError { size_t occurrences() const { return occurrences_; } void set_occurrences(size_t occurrences) { occurrences_ = occurrences; } + // Keys used for retrieving JSON values. + static const char kExtensionIdKey[]; + static const char kFromIncognitoKey[]; + static const char kLevelKey[]; + static const char kMessageKey[]; + static const char kSourceKey[]; + static const char kTypeKey[]; + protected: ExtensionError(Type type, const std::string& extension_id, @@ -78,10 +94,17 @@ class ManifestError : public ExtensionError { const base::string16& manifest_specific); virtual ~ManifestError(); + virtual scoped_ptr<base::DictionaryValue> ToValue() const OVERRIDE; + virtual std::string PrintForTest() const OVERRIDE; const base::string16& manifest_key() const { return manifest_key_; } const base::string16& manifest_specific() const { return manifest_specific_; } + + // Keys used for retrieving JSON values. + static const char kManifestKeyKey[]; + static const char kManifestSpecificKey[]; + private: virtual bool IsEqualImpl(const ExtensionError* rhs) const OVERRIDE; diff --git a/extensions/browser/manifest_highlighter.cc b/extensions/browser/manifest_highlighter.cc new file mode 100644 index 0000000..5afdc46 --- /dev/null +++ b/extensions/browser/manifest_highlighter.cc @@ -0,0 +1,166 @@ +// Copyright 2013 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 <stack> + +#include "extensions/browser/manifest_highlighter.h" + +namespace extensions { + +namespace { + +// Increment |index| to the position of the next quote ('"') in |str|, skipping +// over any escaped quotes. If no next quote is found, |index| is set to +// std::string::npos. Assumes |index| currently points to a quote. +void QuoteIncrement(const std::string& str, size_t* index) { + size_t i = *index + 1; // Skip over the first quote. + bool found = false; + while (!found && i < str.size()) { + if (str[i] == '\\') + i += 2; // if we find an escaped character, skip it. + else if (str[i] == '"') + found = true; + else + ++i; + } + *index = found ? i : std::string::npos; +} + +// Increment |index| by one if the next character is not a comment. Increment +// index until the end of the comment if it is a comment. +void CommentSafeIncrement(const std::string& str, size_t* index) { + size_t i = *index; + if (str[i] == '/' && i + 1 < str.size()) { + // Eat a single-line comment. + if (str[i + 1] == '/') { + i += 2; // Eat the '//'. + while (i < str.size() && str[i] != '\n' && str[i] != '\r') + ++i; + } else if (str[i + 1] == '*') { // Eat a multi-line comment. + i += 3; // Advance to the first possible comment end. + while (i < str.size() && !(str[i - 1] == '*' && str[i] == '/')) + ++i; + } + } + *index = i + 1; +} + +// Increment index until the end of the current "chunk"; a "chunk" is a JSON- +// style list, object, or string literal, without exceeding |end|. Assumes +// |index| currently points to a chunk's starting character ('{', '[', or '"'). +void ChunkIncrement(const std::string& str, size_t* index, size_t end) { + char c = str[*index]; + std::stack<char> stack; + do { + if (c == '"') + QuoteIncrement(str, index); + else if (c == '[') + stack.push(']'); + else if (c == '{') + stack.push('}'); + else if (!stack.empty() && c == stack.top()) + stack.pop(); + CommentSafeIncrement(str, index); + c = str[*index]; + } while (!stack.empty() && *index < end); +} + +} // namespace + +ManifestHighlighter::ManifestHighlighter(const std::string& manifest, + const std::string& key, + const std::string& specific) + : manifest_(manifest), + start_(manifest_.find('{') + 1), + end_(manifest_.rfind('}')) { + Parse(key, specific); +} + +ManifestHighlighter::~ManifestHighlighter() { +} + +std::string ManifestHighlighter::GetBeforeFeature() const { + return manifest_.substr(0, start_); +} + +std::string ManifestHighlighter::GetFeature() const { + return manifest_.substr(start_, end_ - start_); +} + +std::string ManifestHighlighter::GetAfterFeature() const { + return manifest_.substr(end_); +} + +void ManifestHighlighter::Parse(const std::string& key, + const std::string& specific) { + // First, try to find the bounds of the full key. + if (FindBounds(key, true) /* enforce at top level */ ) { + // If we succeed, and we have a specific location, find the bounds of the + // specific. + if (!specific.empty()) + FindBounds(specific, false /* don't enforce at top level */ ); + + // We may have found trailing whitespace. Don't use base::TrimWhitespace, + // because we want to keep any whitespace we find - just not highlight it. + size_t trim = manifest_.find_last_not_of(" \t\n\r", end_ - 1); + if (trim < end_ && trim > start_) + end_ = trim + 1; + } else { + // If we fail, then we set start to end so that the highlighted portion is + // empty. + start_ = end_; + } +} + +bool ManifestHighlighter::FindBounds(const std::string& feature, + bool enforce_at_top_level) { + char c = '\0'; + while (start_ < end_) { + c = manifest_[start_]; + if (c == '"') { + // The feature may be quoted. + size_t quote_end = start_; + QuoteIncrement(manifest_, "e_end); + if (manifest_.substr(start_ + 1, quote_end - 1 - start_) == feature) { + FindBoundsEnd(feature, quote_end + 1); + return true; + } else { + // If it's not the feature, then we can skip the quoted section. + start_ = quote_end + 1; + } + } else if (manifest_.substr(start_, feature.size()) == feature) { + FindBoundsEnd(feature, start_ + feature.size() + 1); + return true; + } else if (enforce_at_top_level && (c == '{' || c == '[')) { + // If we don't have to be at the top level, then we can skip any chunks + // we find. + ChunkIncrement(manifest_, &start_, end_); + } else { + CommentSafeIncrement(manifest_, &start_); + } + } + return false; +} + +void ManifestHighlighter::FindBoundsEnd(const std::string& feature, + size_t local_start) { + char c = '\0'; + while (local_start < end_) { + c = manifest_[local_start]; + // We're done when we find a terminating character (i.e., either a comma or + // an ending bracket. + if (c == ',' || c == '}' || c == ']') { + end_ = local_start; + return; + } + // We can skip any chunks we find, since we are looking for the end of the + // current feature, and don't want to go any deeper. + if (c == '"' || c == '{' || c == '[') + ChunkIncrement(manifest_, &local_start, end_); + else + CommentSafeIncrement(manifest_, &local_start); + } +} + +} // namespace extensions diff --git a/extensions/browser/manifest_highlighter.h b/extensions/browser/manifest_highlighter.h new file mode 100644 index 0000000..bb20fef --- /dev/null +++ b/extensions/browser/manifest_highlighter.h @@ -0,0 +1,74 @@ +// Copyright 2013 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. + +#ifndef EXTENSIONS_BROWSER_MANIFEST_HIGHLIGHTER_H_ +#define EXTENSIONS_BROWSER_MANIFEST_HIGHLIGHTER_H_ + +#include <string> + +#include "base/basictypes.h" + +namespace extensions { + +// Use the ManifestHighlighter class to find the bounds of a feature in the +// manifest. The manifest is parsed for the feature upon construction of the +// object. +// A feature can be at any level in the hierarchy. The "start" of a feature is +// the first character of the feature name, or the beginning quote of the name, +// if present. The "end" of a feature is wherever the next item at the same +// level starts. +// For instance, the bounds for the 'permissions' feature at the top level could +// be '"permissions": { "tabs", "history", "downloads" }', but the feature for +// 'tabs' within 'permissions' would just be '"tabs"'. +// We can't use the JSONParser to do this, because we want to display the actual +// manifest, and once we parse it into Values, we lose any formatting the user +// may have had. +// If a feature cannot be found, the feature will have zero-length. +class ManifestHighlighter { + public: + ManifestHighlighter(const std::string& manifest, + const std::string& key, + const std::string& specific /* optional */); + ~ManifestHighlighter(); + + // Get the portion of the manifest which should not be highlighted and is + // before the feature. + std::string GetBeforeFeature() const; + + // Get the feature portion of the manifest, which should be highlighted. + std::string GetFeature() const; + + // Get the portion of the manifest which should not be highlighted and is + // after the feature. + std::string GetAfterFeature() const; + + private: + // Called from the constructor; determine the start and end bounds of a + // feature, using both the key and specific information. + void Parse(const std::string& key, const std::string& specific); + + // Find the bounds of any feature, either a full key or a specific item within + // the key. |enforce_at_top_level| means that the feature we find must be at + // the same level as |start_| (i.e., ignore nested elements). + // Returns true on success. + bool FindBounds(const std::string& feature, bool enforce_at_top_level); + + // Finds the end of the feature. + void FindBoundsEnd(const std::string& feature, size_t local_start); + + // The manifest we are parsing. + std::string manifest_; + + // The start of the feature. + size_t start_; + + // The end of the feature. + size_t end_; + + DISALLOW_COPY_AND_ASSIGN(ManifestHighlighter); +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_MANIFEST_HIGHLIGHTER_H_ diff --git a/extensions/browser/manifest_highlighter_unittest.cc b/extensions/browser/manifest_highlighter_unittest.cc new file mode 100644 index 0000000..ab6e780 --- /dev/null +++ b/extensions/browser/manifest_highlighter_unittest.cc @@ -0,0 +1,98 @@ +// Copyright 2013 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 "extensions/browser/manifest_highlighter.h" + +#include <string> + +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace extensions { + +namespace { + +const char kManifest[] = +"{\n" +" \"name\": \"Content Scripts\",\n" +" \"version\": \"2.0\",\n" +" // this is a comment with the word permissions.\n" +" /* This is a multine\n" +" comment with the word permissions\n" +" that shouldn't be highlighted */\n" +" \"permissions\": [\n" +" /* This is a tricky comment because it has brackets }]*/\n" +" \"tabs\"\n" +" ],\n" +" \"content_scripts\": [\n" +" {\n" +" \"matches\": [\"*://aaronboodman.com/*\", \"*://rdcronin.com/*\"],\n" +" \"js\": [\"myscript.js\"]\n" +" }\n" +" ],\n" +" \"test_key\": {\n" +" \"escaped_quoted\\\"\",\n" +" \"/*foo*/\"\n" +" },\n" +" \"manifest_version\": 2,\n" +" \"international_key\": \"還是不要\"\n" +"}"; + +} // namespace + +TEST(ManifestHighlighterUnitTest, ManifestHighlighterUnitTest) { + // Get a full key. + const char kPermissionsFeature[] = + "\"permissions\": [\n" + " /* This is a tricky comment because it has brackets }]*/\n" + " \"tabs\"\n" + " ]"; + ManifestHighlighter permissions(kManifest, "permissions", EmptyString()); + EXPECT_EQ(kPermissionsFeature, permissions.GetFeature()); + + // Get a specific portion of a key. + const char kTabsFeature[] = "\"tabs\""; + ManifestHighlighter tabs(kManifest, "permissions", "tabs"); + EXPECT_EQ(kTabsFeature, tabs.GetFeature()); + + // Get a single-character, non-quoted entity of a key. + const char kManifestVersionFeature[] = "2"; + ManifestHighlighter version(kManifest, "manifest_version", "2"); + EXPECT_EQ(kManifestVersionFeature, version.GetFeature()); + + // Get a compound portion of a key, including quoted '//' (which shouldn't be + // mistaken for comments). + const char kMatchesFeature[] = + "\"matches\": [\"*://aaronboodman.com/*\", \"*://rdcronin.com/*\"]"; + ManifestHighlighter matches(kManifest, "content_scripts", "matches"); + EXPECT_EQ(kMatchesFeature, matches.GetFeature()); + + // If a feature isn't present, we should get an empty string. + ManifestHighlighter not_present(kManifest, "a_fake_feature", EmptyString()); + EXPECT_EQ(EmptyString(), not_present.GetFeature()); + + // If we request a specific portion of a key which is not found, we should + // get an empty string. + ManifestHighlighter specific_portion_not_present( + kManifest, "permissions", "a_fake_feature"); + EXPECT_EQ(EmptyString(), specific_portion_not_present.GetFeature()); + + const char kEscapedQuotedFeature[] = "\"escaped_quoted\\\"\""; + ManifestHighlighter escaped_quoted( + kManifest, "test_key", "escaped_quoted\\\""); + EXPECT_EQ(kEscapedQuotedFeature, escaped_quoted.GetFeature()); + + const char kFeatureWithComment[] = "\"/*foo*/\""; + ManifestHighlighter feature_with_comment(kManifest, "test_key", "/*foo*/"); + EXPECT_EQ(kFeatureWithComment, feature_with_comment.GetFeature()); + + // Check with non-ascii characters. + const char kInternationalFeature[] = "\"international_key\": \"還是不要\""; + ManifestHighlighter international_feature( + kManifest, "international_key", EmptyString()); + EXPECT_EQ(kInternationalFeature, international_feature.GetFeature()); +} + +} // namespace extensions |