Commit 83846957 authored by Ben Kelly's avatar Ben Kelly Committed by Chromium LUCI CQ

URLPattern: Implement exec().

This CL provides an initial implementation of the URLPattern.exec()
method.  It mainly differs from test() in that it returns a full
URLPatternResult object instead of just a boolean.  The result object
looks something like:

  {
    input: <input string or object passed to exec()>,
    protocol: <component result>,
    username: <component result>,
    password: <component result>,
    hostname: <component result>,
    port: <component result>,
    pathname: <component result>,
    search: <component result>,
    hash: <component result>
  }

Each URLPatternComponentResult looks like:

  {
    input: <input component string>,
    groups: {
      <group name or numeric number string>: <matched group value>
    }
  }

To support extracting matched group values this CL enhances ScriptRegexp
to populate an optional vector of strings.

In addition, this CL enhances the WPT tests to process expected result
objects.  In general it is only necessary to provide component results
for components that match non-empty inputs.  The test runner will
automatically add expected results for empty components.

This CL also contains a fix in ApplyInit()'s handling of baseURL.  The
intent was to treat non-specified components as the explicit empty
string.  The code now does this.  This also happens to handle the
unexpected behavior of KURL::GetPass() which always returns a null
string in place of an empty string.

Finally, this CL removes the toRegex() stub as well.  That API is
unlikely to land in the current form, so lets just remove it until
it can be properly designed.

Bug: 1141510
Change-Id: Iedd0598a3d5e493c3e7ed0eb5dce4b09019abca3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2575343
Commit-Queue: Ben Kelly <wanderview@chromium.org>
Reviewed-by: default avatarJeremy Roman <jbroman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#834967}
parent ebcb0ff3
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_script_runner.h" #include "third_party/blink/renderer/bindings/core/v8/v8_script_runner.h"
#include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h" #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
#include "third_party/blink/renderer/platform/bindings/string_resource.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h" #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
namespace blink { namespace blink {
...@@ -70,9 +71,10 @@ ScriptRegexp::ScriptRegexp(const String& pattern, ...@@ -70,9 +71,10 @@ ScriptRegexp::ScriptRegexp(const String& pattern,
ToCoreStringWithUndefinedOrNullCheck(try_catch.Message()->Get()); ToCoreStringWithUndefinedOrNullCheck(try_catch.Message()->Get());
} }
int ScriptRegexp::Match(const String& string, int ScriptRegexp::Match(StringView string,
int start_from, int start_from,
int* match_length) const { int* match_length,
WTF::Vector<String>* group_list) const {
if (match_length) if (match_length)
*match_length = 0; *match_length = 0;
...@@ -94,7 +96,7 @@ int ScriptRegexp::Match(const String& string, ...@@ -94,7 +96,7 @@ int ScriptRegexp::Match(const String& string,
v8::Local<v8::RegExp> regex = regex_.NewLocal(isolate); v8::Local<v8::RegExp> regex = regex_.NewLocal(isolate);
v8::Local<v8::String> subject = v8::Local<v8::String> subject =
V8String(isolate, string.Substring(start_from)); V8String(isolate, StringView(string, start_from));
v8::Local<v8::Value> return_value; v8::Local<v8::Value> return_value;
if (!regex->Exec(context, subject).ToLocal(&return_value)) if (!regex->Exec(context, subject).ToLocal(&return_value))
return -1; return -1;
...@@ -122,6 +124,21 @@ int ScriptRegexp::Match(const String& string, ...@@ -122,6 +124,21 @@ int ScriptRegexp::Match(const String& string,
*match_length = match.As<v8::String>()->Length(); *match_length = match.As<v8::String>()->Length();
} }
if (group_list) {
DCHECK(group_list->IsEmpty());
for (uint32_t i = 1; i < result->Length(); ++i) {
v8::Local<v8::Value> group;
if (!result->Get(context, i).ToLocal(&group))
return -1;
String group_string;
if (group->IsString()) {
group_string =
ToBlinkString<String>(group.As<v8::String>(), kExternalize);
}
group_list->push_back(group_string);
}
}
return match_offset.As<v8::Int32>()->Value() + start_from; return match_offset.As<v8::Int32>()->Value() + start_from;
} }
......
...@@ -52,9 +52,17 @@ class CORE_EXPORT ScriptRegexp final : public GarbageCollected<ScriptRegexp> { ...@@ -52,9 +52,17 @@ class CORE_EXPORT ScriptRegexp final : public GarbageCollected<ScriptRegexp> {
MultilineMode = kMultilineDisabled, MultilineMode = kMultilineDisabled,
CharacterMode = BMP); CharacterMode = BMP);
int Match(const String&, // Attempt to match the given input string against the regexp. Returns the
// index of the match within the input string on success and -1 otherwise.
// If |match_length| is provided, then its populated with the length of the
// match on success. If |group_list| is provided its populated with the
// matched groups within the regexp. These are the values normally starting
// at index 1 within the array returned from Regexp.exec(). |group_list|
// must be empty if it is provided.
int Match(StringView,
int start_from = 0, int start_from = 0,
int* match_length = nullptr) const; int* match_length = nullptr,
WTF::Vector<String>* group_list = nullptr) const;
bool IsValid() const { return !regex_.IsEmpty(); } bool IsValid() const { return !regex_.IsEmpty(); }
// exceptionMessage is available only if !isValid(). // exceptionMessage is available only if !isValid().
......
...@@ -15,6 +15,7 @@ struct Options; ...@@ -15,6 +15,7 @@ struct Options;
namespace blink { namespace blink {
class ExceptionState; class ExceptionState;
class URLPatternComponentResult;
class URLPatternInit; class URLPatternInit;
class URLPatternResult; class URLPatternResult;
class USVStringOrURLPatternInit; class USVStringOrURLPatternInit;
...@@ -38,10 +39,9 @@ class URLPattern : public ScriptWrappable { ...@@ -38,10 +39,9 @@ class URLPattern : public ScriptWrappable {
base::PassKey<URLPattern> key); base::PassKey<URLPattern> key);
bool test(const USVStringOrURLPatternInit& input, bool test(const USVStringOrURLPatternInit& input,
ExceptionState& exception_state); ExceptionState& exception_state) const;
URLPatternResult* exec(const USVStringOrURLPatternInit& input, URLPatternResult* exec(const USVStringOrURLPatternInit& input,
ExceptionState& exception_state); ExceptionState& exception_state) const;
String toRegExp(const String& component, ExceptionState& exception_state);
// TODO: define a stringifier // TODO: define a stringifier
...@@ -60,6 +60,24 @@ class URLPattern : public ScriptWrappable { ...@@ -60,6 +60,24 @@ class URLPattern : public ScriptWrappable {
const liburlpattern::Options& options, const liburlpattern::Options& options,
ExceptionState& exception_state); ExceptionState& exception_state);
// A utility function to determine if a given |input| matches the pattern
// or not. Returns |true| if there is a match and |false| otherwise. If
// |result| is not nullptr then the URLPatternResult contents will be filled
// in as expected by the exec() method.
bool Match(const USVStringOrURLPatternInit& input,
URLPatternResult* result,
ExceptionState& exception_state) const;
// A utility function that constructs a URLPatternComponentResult for
// a given |component|, |input|, and |group_list|. The |component| may
// be nullptr. If the result is for the pathname component then you must
// set |is_pathname| to true.
static URLPatternComponentResult* MakeComponentResult(
Component* component,
const String& input,
const Vector<String>& group_list,
bool is_pathname = false);
// The compiled patterns for each URL component. If a Component member is // The compiled patterns for each URL component. If a Component member is
// nullptr then it should be treated as a wildcard matching any input. // nullptr then it should be treated as a wildcard matching any input.
Member<Component> protocol_; Member<Component> protocol_;
......
...@@ -13,5 +13,4 @@ typedef (USVString or URLPatternInit) URLPatternInput; ...@@ -13,5 +13,4 @@ typedef (USVString or URLPatternInit) URLPatternInput;
[RaisesException] constructor(URLPatternInit init); [RaisesException] constructor(URLPatternInit init);
[RaisesException] boolean test(URLPatternInput input); [RaisesException] boolean test(URLPatternInput input);
[RaisesException] URLPatternResult exec(URLPatternInput input); [RaisesException] URLPatternResult exec(URLPatternInput input);
[RaisesException] DOMString toRegExp(USVString component);
}; };
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
// https://wicg.github.io/urlpattern/ // https://wicg.github.io/urlpattern/
dictionary URLPatternResult { dictionary URLPatternResult {
USVString input; URLPatternInput input;
URLPatternComponentResult protocol; URLPatternComponentResult protocol;
URLPatternComponentResult username; URLPatternComponentResult username;
URLPatternComponentResult password; URLPatternComponentResult password;
......
...@@ -4,7 +4,72 @@ function runTests(data) { ...@@ -4,7 +4,72 @@ function runTests(data) {
for (let entry of data) { for (let entry of data) {
test(function() { test(function() {
const pattern = new URLPattern(entry.pattern); const pattern = new URLPattern(entry.pattern);
assert_equals(pattern.test(entry.input), entry.expected);
// First, validate the test() method by converting the expected result to
// a truthy value.
assert_equals(pattern.test(entry.input), !!entry.expected,
'test() result');
// Next, start validating the exec() method.
const result = pattern.exec(entry.input);
// On a failed match exec() returns null.
if (!entry.expected) {
assert_equals(result, entry.expected, 'exec() failed match result');
return;
}
// Next verify the result.input is correct. This may be a structured
// URLPatternInit dictionary object or a URL string.
if (typeof entry.expected.input === 'object') {
assert_object_equals(result.input, entry.expected.input,
'exec() result.input');
} else {
assert_equals(result.input, entry.expected.input,
'exec() result.input');
}
// Next we will compare the URLPatternComponentResult for each of these
// expected components.
const component_list = [
'protocol',
'username',
'password',
'hostname',
'password',
'pathname',
'search',
'hash',
];
for (let component of component_list) {
let expected_obj = entry.expected[component];
// If the test expectations don't include a component object, then
// we auto-generate one. This is convenient for the many cases
// where the pattern has a default wildcard or empty string pattern
// for a component and the input is essentially empty.
if (!expected_obj) {
// We must treat pathname specially since it always has at least one
// slash.
if (component === 'pathname')
expected_obj = { input: '/', groups: {} };
else
expected_obj = { input: '', groups: {} };
// Next, we must treat default wildcards differently than empty string
// patterns. The wildcard results in a capture group, but the empty
// string pattern does not. The expectation object must list which
// components should be empty instead of wildcards in
// |exactly_empty_components|.
if (!entry.expected.exactly_empty_components ||
!entry.expected.exactly_empty_components.includes(component)) {
expected_obj.groups['0'] = '';
}
}
assert_object_equals(result[component], expected_obj,
`exec() result for ${component}`);
}
}, `Pattern: ${JSON.stringify(entry.pattern)} Input: ${JSON.stringify(entry.input)}`); }, `Pattern: ${JSON.stringify(entry.pattern)} Input: ${JSON.stringify(entry.input)}`);
} }
} }
......
...@@ -1562,7 +1562,6 @@ interface URLPattern ...@@ -1562,7 +1562,6 @@ interface URLPattern
method constructor method constructor
method exec method exec
method test method test
method toRegExp
interface URLSearchParams interface URLSearchParams
attribute @@toStringTag attribute @@toStringTag
method @@iterator method @@iterator
......
...@@ -1592,7 +1592,6 @@ Starting worker: resources/global-interface-listing-worker.js ...@@ -1592,7 +1592,6 @@ Starting worker: resources/global-interface-listing-worker.js
[Worker] method constructor [Worker] method constructor
[Worker] method exec [Worker] method exec
[Worker] method test [Worker] method test
[Worker] method toRegExp
[Worker] interface URLSearchParams [Worker] interface URLSearchParams
[Worker] attribute @@toStringTag [Worker] attribute @@toStringTag
[Worker] method @@iterator [Worker] method @@iterator
......
...@@ -8594,7 +8594,6 @@ interface URLPattern ...@@ -8594,7 +8594,6 @@ interface URLPattern
method constructor method constructor
method exec method exec
method test method test
method toRegExp
interface URLSearchParams interface URLSearchParams
attribute @@toStringTag attribute @@toStringTag
method @@iterator method @@iterator
......
...@@ -1439,7 +1439,6 @@ Starting worker: resources/global-interface-listing-worker.js ...@@ -1439,7 +1439,6 @@ Starting worker: resources/global-interface-listing-worker.js
[Worker] method constructor [Worker] method constructor
[Worker] method exec [Worker] method exec
[Worker] method test [Worker] method test
[Worker] method toRegExp
[Worker] interface URLSearchParams [Worker] interface URLSearchParams
[Worker] attribute @@toStringTag [Worker] attribute @@toStringTag
[Worker] method @@iterator [Worker] method @@iterator
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment