mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
use v8 RegExp for Input.suffersPatternMismatch
This commit is contained in:
71
src/browser/js/RegExp.zig
Normal file
71
src/browser/js/RegExp.zig
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const js = @import("js.zig");
|
||||
|
||||
const v8 = js.v8;
|
||||
|
||||
const RegExp = @This();
|
||||
|
||||
local: *const js.Local,
|
||||
handle: *const v8.RegExp,
|
||||
|
||||
// Mirrors v8::RegExp::Flags. Combine with bitwise OR.
|
||||
pub const Flag = struct {
|
||||
pub const none: c_int = v8.kRegExpNone;
|
||||
pub const global: c_int = v8.kRegExpGlobal;
|
||||
pub const ignore_case: c_int = v8.kRegExpIgnoreCase;
|
||||
pub const multiline: c_int = v8.kRegExpMultiline;
|
||||
pub const sticky: c_int = v8.kRegExpSticky;
|
||||
pub const unicode: c_int = v8.kRegExpUnicode;
|
||||
pub const dot_all: c_int = v8.kRegExpDotAll;
|
||||
pub const linear: c_int = v8.kRegExpLinear;
|
||||
pub const has_inSelfdices: c_int = v8.kRegExpHasIndices;
|
||||
pub const unicode_sets: c_int = v8.kRegExpUnicodeSets;
|
||||
};
|
||||
|
||||
pub fn init(local: *const js.Local, pattern: []const u8, flags: c_int) !RegExp {
|
||||
const pattern_handle = local.isolate.initStringHandle(pattern);
|
||||
const handle = v8.v8__RegExp__New(local.handle, pattern_handle, flags) orelse return error.JsException;
|
||||
return .{ .local = local, .handle = handle };
|
||||
}
|
||||
|
||||
// Runs the pattern against `subject`. Returns the result Array (as a generic
|
||||
// Object) on match, or null on no match. Returns error.JsException if V8
|
||||
// throws — typically when the pattern is malformed for the current flags.
|
||||
pub fn exec(self: RegExp, subject: []const u8) !?js.Object {
|
||||
const local = self.local;
|
||||
const subject_handle = local.isolate.initStringHandle(subject);
|
||||
const handle = v8.v8__RegExp__Exec(self.handle, local.handle, subject_handle) orelse return error.JsException;
|
||||
if (v8.v8__Value__IsNullOrUndefined(@ptrCast(handle))) return null;
|
||||
return .{ .local = local, .handle = handle };
|
||||
}
|
||||
|
||||
// Equivalent to `RegExp.prototype.test()` — true iff the pattern matches.
|
||||
pub fn match(self: RegExp, subject: []const u8) !bool {
|
||||
return (try self.exec(subject)) != null;
|
||||
}
|
||||
|
||||
pub fn toValue(self: RegExp) js.Value {
|
||||
return .{
|
||||
.local = self.local,
|
||||
.handle = @ptrCast(self.handle),
|
||||
};
|
||||
}
|
||||
@@ -42,6 +42,7 @@ pub const Object = @import("Object.zig");
|
||||
pub const TryCatch = @import("TryCatch.zig");
|
||||
pub const Function = @import("Function.zig");
|
||||
pub const Promise = @import("Promise.zig");
|
||||
pub const RegExp = @import("RegExp.zig");
|
||||
pub const Module = @import("Module.zig");
|
||||
pub const BigInt = @import("BigInt.zig");
|
||||
pub const Number = @import("Number.zig");
|
||||
|
||||
@@ -307,30 +307,23 @@ pub fn suffersPatternMismatch(self: *const Input, frame: *Frame) bool {
|
||||
const pattern = self.asConstElement().getAttributeSafe(comptime .wrap("pattern")) orelse return false;
|
||||
if (pattern.len == 0) return false;
|
||||
|
||||
// Evaluate `new RegExp("^(?:" + pattern + ")$", "v").test(value)` via V8.
|
||||
// Per spec, an invalid pattern is ignored — the catch arm returns null and
|
||||
// we treat that as "no mismatch".
|
||||
// Per HTML spec, anchor the pattern with ^(?:...)$ and compile under the
|
||||
// "v" (Unicode sets) flag. An invalid pattern is ignored — V8 throws and
|
||||
// we treat that as "no mismatch". TryCatch absorbs the exception so it
|
||||
// doesn't linger in the isolate.
|
||||
var ls: js.Local.Scope = undefined;
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
const arena = frame.call_arena;
|
||||
const pattern_json = std.json.Stringify.valueAlloc(arena, pattern, .{}) catch return false;
|
||||
const value_json = std.json.Stringify.valueAlloc(arena, value, .{}) catch return false;
|
||||
var try_catch: js.TryCatch = undefined;
|
||||
try_catch.init(&ls.local);
|
||||
defer try_catch.deinit();
|
||||
|
||||
const expr = std.fmt.allocPrint(arena,
|
||||
\\(function() {{
|
||||
\\ try {{
|
||||
\\ return new RegExp("^(?:" + {s} + ")$", "v").test({s});
|
||||
\\ }} catch (_) {{
|
||||
\\ return null;
|
||||
\\ }}
|
||||
\\}})()
|
||||
, .{ pattern_json, value_json }) catch return false;
|
||||
const wrapped = std.fmt.allocPrint(frame.call_arena, "^(?:{s})$", .{pattern}) catch return false;
|
||||
const re = js.RegExp.init(&ls.local, wrapped, js.RegExp.Flag.unicode_sets) catch return false;
|
||||
const matched = re.match(value) catch return false;
|
||||
|
||||
const result = ls.local.exec(expr, "Input.suffersPatternMismatch") catch return false;
|
||||
if (result.isNullOrUndefined()) return false;
|
||||
return !result.toBool();
|
||||
return !matched;
|
||||
}
|
||||
|
||||
pub fn suffersTooLong(self: *const Input) bool {
|
||||
|
||||
Reference in New Issue
Block a user