blob: 602a82a6f33836f303fed10b14a221709cdee593 [file] [log] [blame]
// Copyright 2014 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/extension_user_script_loader.h"
#include <stddef.h>
#include <memory>
#include <set>
#include <string>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/strings/string_util.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "extensions/common/host_id.h"
#include "testing/gtest/include/gtest/gtest.h"
using extensions::URLPatternSet;
namespace {
static void AddPattern(URLPatternSet* extent, const std::string& pattern) {
int schemes = URLPattern::SCHEME_ALL;
extent->AddPattern(URLPattern(schemes, pattern));
}
}
namespace extensions {
// Test bringing up a script loader on a specific directory, putting a script
// in there, etc.
class ExtensionUserScriptLoaderTest : public testing::Test,
public content::NotificationObserver {
public:
ExtensionUserScriptLoaderTest() : shared_memory_(NULL) {}
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
// Register for all user script notifications.
registrar_.Add(this,
extensions::NOTIFICATION_USER_SCRIPTS_UPDATED,
content::NotificationService::AllSources());
}
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override {
DCHECK(type == extensions::NOTIFICATION_USER_SCRIPTS_UPDATED);
shared_memory_ = content::Details<base::SharedMemory>(details).ptr();
}
// Directory containing user scripts.
base::ScopedTempDir temp_dir_;
// Updated to the script shared memory when we get notified.
base::SharedMemory* shared_memory_;
private:
content::TestBrowserThreadBundle thread_bundle_;
content::NotificationRegistrar registrar_;
DISALLOW_COPY_AND_ASSIGN(ExtensionUserScriptLoaderTest);
};
// Test that we get notified even when there are no scripts.
TEST_F(ExtensionUserScriptLoaderTest, NoScripts) {
TestingProfile profile;
ExtensionUserScriptLoader loader(
&profile,
HostID(),
true /* listen_for_extension_system_loaded */);
loader.StartLoad();
content::RunAllTasksUntilIdle();
ASSERT_TRUE(shared_memory_ != NULL);
}
TEST_F(ExtensionUserScriptLoaderTest, 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");
UserScript script;
EXPECT_TRUE(ExtensionUserScriptLoader::ParseMetadataHeader(text, &script));
ASSERT_EQ(3U, script.globs().size());
EXPECT_EQ("*mail.google.com*", script.globs()[0]);
EXPECT_EQ("*mail.yahoo.com*", script.globs()[1]);
EXPECT_EQ("*mail.msn.com*", script.globs()[2]);
}
TEST_F(ExtensionUserScriptLoaderTest, Parse2) {
const std::string text("default to @include *");
UserScript script;
EXPECT_TRUE(ExtensionUserScriptLoader::ParseMetadataHeader(text, &script));
ASSERT_EQ(1U, script.globs().size());
EXPECT_EQ("*", script.globs()[0]);
}
TEST_F(ExtensionUserScriptLoaderTest, Parse3) {
const std::string text(
"// ==UserScript==\n"
"// @include *foo*\n"
"// ==/UserScript=="); // no trailing newline
UserScript script;
ExtensionUserScriptLoader::ParseMetadataHeader(text, &script);
ASSERT_EQ(1U, script.globs().size());
EXPECT_EQ("*foo*", script.globs()[0]);
}
TEST_F(ExtensionUserScriptLoaderTest, Parse4) {
const std::string text(
"// ==UserScript==\n"
"// @match http://*.mail.google.com/*\n"
"// @match \t http://mail.yahoo.com/*\n"
"// ==/UserScript==\n");
URLPatternSet expected_patterns;
AddPattern(&expected_patterns, "http://*.mail.google.com/*");
AddPattern(&expected_patterns, "http://mail.yahoo.com/*");
UserScript script;
EXPECT_TRUE(ExtensionUserScriptLoader::ParseMetadataHeader(text, &script));
EXPECT_EQ(0U, script.globs().size());
EXPECT_EQ(expected_patterns, script.url_patterns());
}
TEST_F(ExtensionUserScriptLoaderTest, Parse5) {
const std::string text(
"// ==UserScript==\n"
"// @match http://*mail.google.com/*\n"
"// ==/UserScript==\n");
// Invalid @match value.
UserScript script;
EXPECT_FALSE(ExtensionUserScriptLoader::ParseMetadataHeader(text, &script));
}
TEST_F(ExtensionUserScriptLoaderTest, Parse6) {
const std::string text(
"// ==UserScript==\n"
"// @include http://*.mail.google.com/*\n"
"// @match \t http://mail.yahoo.com/*\n"
"// ==/UserScript==\n");
// Allowed to match @include and @match.
UserScript script;
EXPECT_TRUE(ExtensionUserScriptLoader::ParseMetadataHeader(text, &script));
}
TEST_F(ExtensionUserScriptLoaderTest, Parse7) {
// Greasemonkey allows there to be any leading text before the comment marker.
const std::string text(
"// ==UserScript==\n"
"adsasdfasf// @name hello\n"
" // @description\twiggity woo\n"
"\t// @match \t http://mail.yahoo.com/*\n"
"// ==/UserScript==\n");
UserScript script;
EXPECT_TRUE(ExtensionUserScriptLoader::ParseMetadataHeader(text, &script));
ASSERT_EQ("hello", script.name());
ASSERT_EQ("wiggity woo", script.description());
ASSERT_EQ(1U, script.url_patterns().patterns().size());
EXPECT_EQ("http://mail.yahoo.com/*",
script.url_patterns().begin()->GetAsString());
}
TEST_F(ExtensionUserScriptLoaderTest, Parse8) {
const std::string text(
"// ==UserScript==\n"
"// @name myscript\n"
"// @match http://www.google.com/*\n"
"// @exclude_match http://www.google.com/foo*\n"
"// ==/UserScript==\n");
UserScript script;
EXPECT_TRUE(ExtensionUserScriptLoader::ParseMetadataHeader(text, &script));
ASSERT_EQ("myscript", script.name());
ASSERT_EQ(1U, script.url_patterns().patterns().size());
EXPECT_EQ("http://www.google.com/*",
script.url_patterns().begin()->GetAsString());
ASSERT_EQ(1U, script.exclude_url_patterns().patterns().size());
EXPECT_EQ("http://www.google.com/foo*",
script.exclude_url_patterns().begin()->GetAsString());
}
TEST_F(ExtensionUserScriptLoaderTest, SkipBOMAtTheBeginning) {
base::FilePath path = temp_dir_.GetPath().AppendASCII("script.user.js");
const std::string content("\xEF\xBB\xBF alert('hello');");
size_t written = base::WriteFile(path, content.c_str(), content.size());
ASSERT_EQ(written, content.size());
std::unique_ptr<UserScript> user_script(new UserScript());
user_script->js_scripts().push_back(std::make_unique<UserScript::File>(
temp_dir_.GetPath(), path.BaseName(), GURL()));
UserScriptList user_scripts;
user_scripts.push_back(std::move(user_script));
TestingProfile profile;
ExtensionUserScriptLoader loader(
&profile,
HostID(),
true /* listen_for_extension_system_loaded */);
loader.LoadScriptsForTest(&user_scripts);
EXPECT_EQ(content.substr(3),
user_scripts[0]->js_scripts()[0]->GetContent().as_string());
}
TEST_F(ExtensionUserScriptLoaderTest, LeaveBOMNotAtTheBeginning) {
base::FilePath path = temp_dir_.GetPath().AppendASCII("script.user.js");
const std::string content("alert('here's a BOOM: \xEF\xBB\xBF');");
size_t written = base::WriteFile(path, content.c_str(), content.size());
ASSERT_EQ(written, content.size());
std::unique_ptr<UserScript> user_script(new UserScript());
user_script->js_scripts().push_back(std::make_unique<UserScript::File>(
temp_dir_.GetPath(), path.BaseName(), GURL()));
UserScriptList user_scripts;
user_scripts.push_back(std::move(user_script));
TestingProfile profile;
ExtensionUserScriptLoader loader(
&profile,
HostID(),
true /* listen_for_extension_system_loaded */);
loader.LoadScriptsForTest(&user_scripts);
EXPECT_EQ(content,
user_scripts[0]->js_scripts()[0]->GetContent().as_string());
}
} // namespace extensions