diff --git a/src/core/renderer/user_resource_controller.cpp b/src/core/renderer/user_resource_controller.cpp index bae1d95dcdf3288e60bde2a6d0248d0a9dc6c98d..eed5208766f003ec57feb53c4870ff963582339d 100644 --- a/src/core/renderer/user_resource_controller.cpp +++ b/src/core/renderer/user_resource_controller.cpp @@ -58,6 +58,8 @@ #include "type_conversion.h" #include "user_script.h" +#include <QRegularExpression> + Q_GLOBAL_STATIC(UserResourceController, qt_webengine_userResourceController) static content::RenderView * const globalScriptsIndex = 0; @@ -65,6 +67,28 @@ 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 regexMatchesURL(const std::string &pat, const GURL &url) { + QRegularExpression qre(QtWebEngineCore::toQt(pat)); + qre.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + if (!qre.isValid()) + return false; + return qre.match(QtWebEngineCore::toQt(url.spec())).hasMatch(); +} + +static bool includeRuleMatchesURL(const std::string &pat, const GURL &url) +{ + // Match patterns for greasemonkey's @include and @exclude rules which can + // be either strings with wildcards or regular expressions. + if (pat.front() == '/' && pat.back() == '/') { + std::string re(++pat.cbegin(), --pat.cend()); + if (regexMatchesURL(re, url)) + return true; + } else if (base::MatchPattern(url.spec(), pat)) { + return true; + } + return false; +} + static bool scriptMatchesURL(const UserScriptData &scriptData, const GURL &url) { // Logic taken from Chromium (extensions/common/user_script.cc) bool matchFound; @@ -82,7 +106,7 @@ static bool scriptMatchesURL(const UserScriptData &scriptData, const GURL &url) 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)) + if (includeRuleMatchesURL(*it, url)) matchFound = true; } if (!matchFound) @@ -91,7 +115,7 @@ static bool scriptMatchesURL(const UserScriptData &scriptData, const GURL &url) if (!scriptData.excludeGlobs.empty()) { for (auto it = scriptData.excludeGlobs.begin(), end = scriptData.excludeGlobs.end(); it != end; ++it) { - if (base::MatchPattern(url.spec(), *it)) + if (includeRuleMatchesURL(*it, url)) return false; } } diff --git a/src/core/user_script.cpp b/src/core/user_script.cpp index b33dd6a7db34c6138acec1662791145cf583c203..9b9d66d556780ac5a0983b410d921e8d6e500d1c 100644 --- a/src/core/user_script.cpp +++ b/src/core/user_script.cpp @@ -244,13 +244,20 @@ void UserScript::parseMetadataHeader() 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, "?", "\\?"); + if (value.front() != '/' || value.back() != '/') { + // The greasemonkey spec only allows for wildcards (*), so + // escape the additional things which MatchPattern allows. + 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, "?", "\\?"); + if (value.front() != '/' || value.back() != '/') { + // The greasemonkey spec only allows for wildcards (*), so + // escape the additional things which MatchPattern allows. + 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)) diff --git a/tests/auto/quick/qmltests/data/script-with-metadata.js b/tests/auto/quick/qmltests/data/script-with-metadata.js index 4dcf50f554b2908598afcbe4cdcd1cc72a234c14..de2e3974c22a0e376f08776e9f59b2e6c473fdff 100644 --- a/tests/auto/quick/qmltests/data/script-with-metadata.js +++ b/tests/auto/quick/qmltests/data/script-with-metadata.js @@ -2,8 +2,10 @@ // @name Test script // @homepageURL http://www.qt.io/ // @description Test script with metadata block -// @include *test*.html +// @include *data/test*.html +// @include /favicon.html?$/ // @exclude *test2.html +// @exclude /test[-]iframe/ // @run-at document-end // ==/UserScript== diff --git a/tests/auto/quick/qmltests/data/tst_userScripts.qml b/tests/auto/quick/qmltests/data/tst_userScripts.qml index e9a4eba994030295137c575d4516ddc5166dc796..d7c7d59832bd5186cd40bfa54c42ec2aa077c6f9 100644 --- a/tests/auto/quick/qmltests/data/tst_userScripts.qml +++ b/tests/auto/quick/qmltests/data/tst_userScripts.qml @@ -170,7 +170,7 @@ Item { webEngineView.userScripts = [ scriptWithMetadata ]; - // @include *test*.html + // @include *data/test*.html webEngineView.url = Qt.resolvedUrl("test1.html"); webEngineView.waitForLoadSucceeded(); tryCompare(webEngineView, "title", "New title"); @@ -179,6 +179,16 @@ Item { webEngineView.url = Qt.resolvedUrl("test2.html"); webEngineView.waitForLoadSucceeded(); tryCompare(webEngineView, "title", "Test page with huge link area"); + + // @include /favicon.html?$/ + webEngineView.url = Qt.resolvedUrl("favicon.html"); + webEngineView.waitForLoadSucceeded(); + tryCompare(webEngineView, "title", "New title"); + + // @exclude /test[-]iframe/ + webEngineView.url = Qt.resolvedUrl("test-iframe.html"); + webEngineView.waitForLoadSucceeded(); + tryCompare(webEngineView, "title", "Test page with huge link area and iframe"); } function test_profileWideScript() {