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() {