summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSzabolcs David <davidsz@inf.u-szeged.hu>2016-06-03 02:41:21 -0700
committerSzabolcs David <davidsz@inf.u-szeged.hu>2016-07-07 08:41:35 +0000
commit9bb9076c50048913075f11692f784ebb959d63cf (patch)
tree4a27e9eb3c7bd767963505f14ac0c703c811338c
parent800365f6faad962a4dd2e71173527d285a3f62b5 (diff)
Parse metadata block in user scripts
This allows WebEngine to build up user script objects from the metadata section of their source code. It also implements @include, @exclude and @match rules for restricting script injection to given URL patterns. Change-Id: Ic7b3023c0143643bfbf71adbfa25a8022b223fcf Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
-rw-r--r--src/core/chrome_qt.gyp12
-rw-r--r--src/core/common/qt_messages.h3
-rw-r--r--src/core/common/user_script_data.h3
-rw-r--r--src/core/renderer/user_resource_controller.cpp41
-rw-r--r--src/core/user_script.cpp105
-rw-r--r--src/core/user_script.h3
-rw-r--r--tests/auto/quick/qmltests/data/script-with-metadata.js10
-rw-r--r--tests/auto/quick/qmltests/data/tst_userScripts.qml22
8 files changed, 195 insertions, 4 deletions
diff --git a/src/core/chrome_qt.gyp b/src/core/chrome_qt.gyp
index 394e72733..ff7988f09 100644
--- a/src/core/chrome_qt.gyp
+++ b/src/core/chrome_qt.gyp
@@ -78,8 +78,16 @@
'<(DEPTH)/chrome/browser/media/desktop_streams_registry.h',
'<(DEPTH)/chrome/common/chrome_switches.cc',
'<(DEPTH)/chrome/common/chrome_switches.h',
+ '<(DEPTH)/extensions/common/constants.cc',
+ '<(DEPTH)/extensions/common/constants.h',
+ '<(DEPTH)/extensions/common/url_pattern.cc',
+ '<(DEPTH)/extensions/common/url_pattern.h',
],
'conditions': [
+ ['OS == "win"', {
+ # crbug.com/167187 fix size_t to int truncations
+ 'msvs_disabled_warnings': [4267, ],
+ }],
['enable_spellcheck==1', {
'sources': [ '<@(chrome_spellchecker_sources)' ],
'include_dirs': [
@@ -99,10 +107,6 @@
'__STDC_FORMAT_MACROS',
],
'conditions': [
- ['OS == "win"', {
- # crbug.com/167187 fix size_t to int truncations
- 'msvs_disabled_warnings': [4267, ],
- }],
[ 'OS != "mac"', {
'sources/': [
['exclude', '_mac\\.(cc|cpp|mm?)$'],
diff --git a/src/core/common/qt_messages.h b/src/core/common/qt_messages.h
index b8991a2b3..2c971aab2 100644
--- a/src/core/common/qt_messages.h
+++ b/src/core/common/qt_messages.h
@@ -16,6 +16,9 @@ IPC_STRUCT_TRAITS_BEGIN(UserScriptData)
IPC_STRUCT_TRAITS_MEMBER(injectForSubframes)
IPC_STRUCT_TRAITS_MEMBER(worldId)
IPC_STRUCT_TRAITS_MEMBER(scriptId)
+ IPC_STRUCT_TRAITS_MEMBER(globs)
+ IPC_STRUCT_TRAITS_MEMBER(excludeGlobs)
+ IPC_STRUCT_TRAITS_MEMBER(urlPatterns)
IPC_STRUCT_TRAITS_END()
diff --git a/src/core/common/user_script_data.h b/src/core/common/user_script_data.h
index 943d61798..8d98890e3 100644
--- a/src/core/common/user_script_data.h
+++ b/src/core/common/user_script_data.h
@@ -60,6 +60,9 @@ struct UserScriptData {
bool injectForSubframes;
uint worldId;
uint64_t scriptId;
+ std::vector<std::string> globs;
+ std::vector<std::string> excludeGlobs;
+ std::vector<std::string> urlPatterns;
};
QT_BEGIN_NAMESPACE
diff --git a/src/core/renderer/user_resource_controller.cpp b/src/core/renderer/user_resource_controller.cpp
index 68b5f3d40..8c603b805 100644
--- a/src/core/renderer/user_resource_controller.cpp
+++ b/src/core/renderer/user_resource_controller.cpp
@@ -39,9 +39,12 @@
#include "user_resource_controller.h"
+#include "base/strings/pattern.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_view.h"
#include "content/public/renderer/render_view_observer.h"
+#include "extensions/common/url_pattern.h"
+#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebScriptSource.h"
#include "third_party/WebKit/public/web/WebView.h"
@@ -49,6 +52,8 @@
#include "common/qt_messages.h"
#include "common/user_script_data.h"
+#include "type_conversion.h"
+#include "user_script.h"
Q_GLOBAL_STATIC(UserResourceController, qt_webengine_userResourceController)
@@ -57,6 +62,40 @@ static content::RenderView * const globalScriptsIndex = 0;
// Scripts meant to run after the load event will be run 500ms after DOMContentLoaded if the load event doesn't come within that delay.
static const int afterLoadTimeout = 500;
+static bool scriptMatchesURL(const UserScriptData &scriptData, const GURL &url) {
+ // Logic taken from Chromium (extensions/common/user_script.cc)
+ bool matchFound;
+ if (!scriptData.urlPatterns.empty()) {
+ matchFound = false;
+ for (auto it = scriptData.urlPatterns.begin(), end = scriptData.urlPatterns.end(); it != end; ++it) {
+ URLPattern urlPattern(QtWebEngineCore::UserScript::validUserScriptSchemes(), *it);
+ if (urlPattern.MatchesURL(url))
+ matchFound = true;
+ }
+ if (!matchFound)
+ return false;
+ }
+
+ if (!scriptData.globs.empty()) {
+ matchFound = false;
+ for (auto it = scriptData.globs.begin(), end = scriptData.globs.end(); it != end; ++it) {
+ if (base::MatchPattern(url.spec(), *it))
+ matchFound = true;
+ }
+ if (!matchFound)
+ return false;
+ }
+
+ if (!scriptData.excludeGlobs.empty()) {
+ for (auto it = scriptData.excludeGlobs.begin(), end = scriptData.excludeGlobs.end(); it != end; ++it) {
+ if (base::MatchPattern(url.spec(), *it))
+ return false;
+ }
+ }
+
+ return true;
+}
+
class UserResourceController::RenderViewObserverHelper : public content::RenderViewObserver
{
public:
@@ -99,6 +138,8 @@ void UserResourceController::runScripts(UserScriptData::InjectionPoint p, blink:
if (script.injectionPoint != p
|| (!script.injectForSubframes && !isMainFrame))
continue;
+ if (!scriptMatchesURL(script, frame->document().url()))
+ continue;
blink::WebScriptSource source(blink::WebString::fromUTF8(script.source), script.url);
if (script.worldId)
frame->executeScriptInIsolatedWorld(script.worldId, &source, /*numSources = */1, /*contentScriptExtentsionGroup = */ 0);
diff --git a/src/core/user_script.cpp b/src/core/user_script.cpp
index 839eff366..b33dd6a7d 100644
--- a/src/core/user_script.cpp
+++ b/src/core/user_script.cpp
@@ -38,11 +38,39 @@
****************************************************************************/
#include "common/user_script_data.h"
+#include "extensions/common/url_pattern.h"
#include "user_script.h"
#include "type_conversion.h"
+namespace {
+
+// Helper function to parse Greasemonkey headers
+bool GetDeclarationValue(const base::StringPiece& line,
+ const base::StringPiece& prefix,
+ std::string* value) {
+ base::StringPiece::size_type index = line.find(prefix);
+ if (index == base::StringPiece::npos)
+ return false;
+
+ std::string temp(line.data() + index + prefix.length(),
+ line.length() - index - prefix.length());
+
+ if (temp.empty() || !base::IsUnicodeWhitespace(temp[0]))
+ return false;
+
+ base::TrimWhitespaceASCII(temp, base::TRIM_ALL, value);
+ return true;
+}
+
+} // namespace
+
namespace QtWebEngineCore {
+int UserScript::validUserScriptSchemes()
+{
+ return URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS | URLPattern::SCHEME_FILE;
+}
+
ASSERT_ENUMS_MATCH(UserScript::AfterLoad, UserScriptData::AfterLoad)
ASSERT_ENUMS_MATCH(UserScript::DocumentLoadFinished, UserScriptData::DocumentLoadFinished)
ASSERT_ENUMS_MATCH(UserScript::DocumentElementCreation, UserScriptData::DocumentElementCreation)
@@ -100,6 +128,7 @@ void UserScript::setSourceCode(const QString &source)
{
initData();
scriptData->source = source.toStdString();
+ parseMetadataHeader();
}
UserScript::InjectionPoint UserScript::injectionPoint() const
@@ -169,4 +198,80 @@ UserScriptData &UserScript::data() const
return *(scriptData.data());
}
+void UserScript::parseMetadataHeader()
+{
+ // Logic taken from Chromium (extensions/browser/user_script_loader.cc)
+ // http://wiki.greasespot.net/Metadata_block
+ const std::string &script_text = scriptData->source;
+ base::StringPiece line;
+ size_t line_start = 0;
+ size_t line_end = line_start;
+ bool in_metadata = false;
+
+ static const base::StringPiece kUserScriptBegin("// ==UserScript==");
+ static const base::StringPiece kUserScriptEnd("// ==/UserScript==");
+ static const base::StringPiece kNameDeclaration("// @name");
+ static const base::StringPiece kIncludeDeclaration("// @include");
+ static const base::StringPiece kExcludeDeclaration("// @exclude");
+ static const base::StringPiece kMatchDeclaration("// @match");
+ static const base::StringPiece kRunAtDeclaration("// @run-at");
+ static const base::StringPiece kRunAtDocumentStartValue("document-start");
+ static const base::StringPiece kRunAtDocumentEndValue("document-end");
+ static const base::StringPiece kRunAtDocumentIdleValue("document-idle");
+ // FIXME: Scripts don't run in subframes by default. If we would like to
+ // support @noframes rule, we have to change the current default behavior.
+ // static const base::StringPiece kNoFramesDeclaration("// @noframes");
+
+ static URLPattern urlPatternParser(validUserScriptSchemes());
+
+ 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(kUserScriptEnd))
+ break;
+
+ std::string value;
+ if (GetDeclarationValue(line, kNameDeclaration, &value)) {
+ setName(toQt(value));
+ } else if (GetDeclarationValue(line, kIncludeDeclaration, &value)) {
+ // We escape some characters that MatchPattern() considers special.
+ base::ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
+ base::ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
+ scriptData->globs.push_back(value);
+ } else if (GetDeclarationValue(line, kExcludeDeclaration, &value)) {
+ base::ReplaceSubstringsAfterOffset(&value, 0, "\\", "\\\\");
+ base::ReplaceSubstringsAfterOffset(&value, 0, "?", "\\?");
+ scriptData->excludeGlobs.push_back(value);
+ } else if (GetDeclarationValue(line, kMatchDeclaration, &value)) {
+ if (URLPattern::PARSE_SUCCESS == urlPatternParser.Parse(value))
+ scriptData->urlPatterns.push_back(value);
+ } else if (GetDeclarationValue(line, kRunAtDeclaration, &value)) {
+ if (value == kRunAtDocumentStartValue)
+ scriptData->injectionPoint = DocumentElementCreation;
+ else if (value == kRunAtDocumentEndValue)
+ scriptData->injectionPoint = DocumentLoadFinished;
+ else if (value == kRunAtDocumentIdleValue)
+ scriptData->injectionPoint = AfterLoad;
+ }
+ }
+
+ line_start = line_end + 1;
+ }
+
+ // If no patterns were specified, default to @include *. This is what
+ // Greasemonkey does.
+ if (scriptData->globs.empty() && scriptData->urlPatterns.empty())
+ scriptData->globs.push_back("*");
+}
+
} // namespace QtWebEngineCore
diff --git a/src/core/user_script.h b/src/core/user_script.h
index 9d7d66a58..e44efd3e9 100644
--- a/src/core/user_script.h
+++ b/src/core/user_script.h
@@ -85,9 +85,12 @@ public:
bool operator==(const UserScript &) const;
+ static int validUserScriptSchemes();
+
private:
void initData();
UserScriptData &data() const;
+ void parseMetadataHeader();
friend class UserResourceControllerHost;
QScopedPointer<UserScriptData> scriptData;
diff --git a/tests/auto/quick/qmltests/data/script-with-metadata.js b/tests/auto/quick/qmltests/data/script-with-metadata.js
new file mode 100644
index 000000000..4dcf50f55
--- /dev/null
+++ b/tests/auto/quick/qmltests/data/script-with-metadata.js
@@ -0,0 +1,10 @@
+// ==UserScript==
+// @name Test script
+// @homepageURL http://www.qt.io/
+// @description Test script with metadata block
+// @include *test*.html
+// @exclude *test2.html
+// @run-at document-end
+// ==/UserScript==
+
+document.title = "New title";
diff --git a/tests/auto/quick/qmltests/data/tst_userScripts.qml b/tests/auto/quick/qmltests/data/tst_userScripts.qml
index d4f1222f9..dbad8cd56 100644
--- a/tests/auto/quick/qmltests/data/tst_userScripts.qml
+++ b/tests/auto/quick/qmltests/data/tst_userScripts.qml
@@ -49,6 +49,11 @@ Item {
injectionPoint: WebEngineScript.DocumentReady
}
+ WebEngineScript {
+ id: scriptWithMetadata
+ sourceUrl: Qt.resolvedUrl("script-with-metadata.js")
+ }
+
TestWebEngineView {
id: webEngineView
width: 400
@@ -151,5 +156,22 @@ Item {
webEngineView.waitForLoadSucceeded();
compare(webEngineView.title, "Big user script changed title");
}
+
+ function test_parseMetadataHeader() {
+ compare(scriptWithMetadata.name, "Test script");
+ compare(scriptWithMetadata.injectionPoint, WebEngineScript.DocumentReady);
+
+ webEngineView.userScripts = [ scriptWithMetadata ];
+
+ // @include *test*.html
+ webEngineView.url = Qt.resolvedUrl("test1.html");
+ webEngineView.waitForLoadSucceeded();
+ compare(webEngineView.title, "New title");
+
+ // @exclude *test2.html
+ webEngineView.url = Qt.resolvedUrl("test2.html");
+ webEngineView.waitForLoadSucceeded();
+ compare(webEngineView.title, "Test page with huge link area");
+ }
}
}