///|
/// Page methods
///|
/// Navigate to URL
/// See: https://playwright.dev/docs/api/class-page#page-goto
pub async fn Page::goto(
self : Page,
url : String,
timeout? : Int,
wait_until? : String,
referer? : String,
) -> Response? {
consume((timeout, wait_until, referer))
let state = self.state.val
let result = state.browser.state.val.client.browsing_context_navigate(
state.context_id,
url,
)
match result {
Ok(nav) => {
state.url = nav.url
Some(make_response(nav.url))
}
Err(_) => None
}
}
///|
/// Reload page
pub async fn Page::reload(
self : Page,
timeout? : Int,
wait_until? : String,
) -> Response? {
consume((timeout, wait_until))
let state = self.state.val
let _ = state.browser.state.val.client.browsing_context_reload(
state.context_id,
)
Some(make_response(state.url))
}
///|
/// Go back in history
pub async fn Page::go_back(
self : Page,
timeout? : Int,
wait_until? : String,
) -> Response? {
consume((timeout, wait_until))
let _ = self.evaluate("history.back()")
None
}
///|
/// Go forward in history
pub async fn Page::go_forward(
self : Page,
timeout? : Int,
wait_until? : String,
) -> Response? {
consume((timeout, wait_until))
let _ = self.evaluate("history.forward()")
None
}
///|
/// Get page URL
pub fn Page::url(self : Page) -> String {
self.state.val.url
}
///|
/// Get page title
pub async fn Page::title(self : Page) -> String {
match self.eval_string("document.title") {
Some(value) => value
None => ""
}
}
///|
/// Get page content (HTML)
pub async fn Page::content(self : Page) -> String {
match self.eval_string("document.documentElement.outerHTML") {
Some(value) => value
None => ""
}
}
///|
/// Set page content
pub async fn Page::set_content(
self : Page,
html : String,
timeout? : Int,
wait_until? : String,
) -> Unit {
consume((timeout, wait_until))
let expr = "(html) => { document.open(); document.write(html); document.close(); }"
let args : Array[Json] = [
Json::object({ "type": Json::string("string"), "value": Json::string(html) }),
]
let _ = self.call_function(expr, args)
}
///|
/// Get locator for CSS selector or text
pub fn Page::locator(self : Page, selector : String) -> Locator {
let state : LocatorState = {
page: self,
selector,
index: None,
has_text: None,
has_text_exact: false,
has_not_text: None,
label_text: None,
label_text_exact: false,
label_scope_selector: None,
}
{ state: { val: state } }
}
///|
/// Get locator by role
pub fn Page::get_by_role(
self : Page,
role : String,
name? : String,
exact? : Bool,
) -> Locator {
consume(exact)
let selector = "[role=" + js_string_literal(role) + "]"
let base = self.locator(selector)
match name {
Some(text) => base.get_by_text(text, exact?)
None => base
}
}
///|
/// Get locator by text
pub fn Page::get_by_text(self : Page, text : String, exact? : Bool) -> Locator {
let exact_flag = match exact {
Some(true) => true
_ => false
}
let state : LocatorState = {
page: self,
selector: "*",
index: None,
has_text: Some(text),
has_text_exact: exact_flag,
has_not_text: None,
label_text: None,
label_text_exact: false,
label_scope_selector: None,
}
{ state: { val: state } }
}
///|
/// Get locator by label
pub fn Page::get_by_label(self : Page, text : String, exact? : Bool) -> Locator {
let exact_flag = match exact {
Some(true) => true
_ => false
}
let state : LocatorState = {
page: self,
selector: "label",
index: None,
has_text: None,
has_text_exact: false,
has_not_text: None,
label_text: Some(text),
label_text_exact: exact_flag,
label_scope_selector: None,
}
{ state: { val: state } }
}
///|
/// Get locator by placeholder
pub fn Page::get_by_placeholder(
self : Page,
text : String,
exact? : Bool,
) -> Locator {
consume(exact)
let selector = "[placeholder=" + js_string_literal(text) + "]"
let state : LocatorState = {
page: self,
selector,
index: None,
has_text: None,
has_text_exact: false,
has_not_text: None,
label_text: None,
label_text_exact: false,
label_scope_selector: None,
}
{ state: { val: state } }
}
///|
/// Get locator by alt text
pub fn Page::get_by_alt_text(
self : Page,
text : String,
exact? : Bool,
) -> Locator {
consume(exact)
let selector = "[alt=" + js_string_literal(text) + "]"
let state : LocatorState = {
page: self,
selector,
index: None,
has_text: None,
has_text_exact: false,
has_not_text: None,
label_text: None,
label_text_exact: false,
label_scope_selector: None,
}
{ state: { val: state } }
}
///|
/// Get locator by title
pub fn Page::get_by_title(self : Page, text : String, exact? : Bool) -> Locator {
consume(exact)
let selector = "[title=" + js_string_literal(text) + "]"
let state : LocatorState = {
page: self,
selector,
index: None,
has_text: None,
has_text_exact: false,
has_not_text: None,
label_text: None,
label_text_exact: false,
label_scope_selector: None,
}
{ state: { val: state } }
}
///|
/// Get locator by test ID
pub fn Page::get_by_test_id(self : Page, testId : String) -> Locator {
let selector = "[data-testid=" + js_string_literal(testId) + "]"
let state : LocatorState = {
page: self,
selector,
index: None,
has_text: None,
has_text_exact: false,
has_not_text: None,
label_text: None,
label_text_exact: false,
label_scope_selector: None,
}
{ state: { val: state } }
}
///|
/// Subscribe network event
pub fn Page::on_network_event(
self : Page,
handler : (NetworkEvent) -> Unit,
) -> Unit {
self.state.val.network_listeners.push(handler)
}
///|
/// Subscribe request event
pub fn Page::on_request(self : Page, handler : (Request) -> Unit) -> Unit {
self.on_network_event(fn(evt : NetworkEvent) {
match evt {
NetworkEvent::Request(req) => handler(req)
_ => ()
}
})
}
///|
/// Subscribe response event
pub fn Page::on_response(self : Page, handler : (Response) -> Unit) -> Unit {
self.on_network_event(fn(evt : NetworkEvent) {
match evt {
NetworkEvent::Response(resp) => handler(resp)
_ => ()
}
})
}
///|
/// Subscribe response completed event
pub fn Page::on_response_completed(
self : Page,
handler : (Response) -> Unit,
) -> Unit {
self.on_network_event(fn(evt : NetworkEvent) {
match evt {
NetworkEvent::ResponseCompleted(resp) => handler(resp)
_ => ()
}
})
}
///|
/// Click on element
pub async fn Page::click(
self : Page,
selector : String,
button? : String,
click_count? : Int,
delay? : Int,
force? : Bool,
modifiers? : Array[String],
no_wait_after? : Bool,
position? : (Double, Double),
timeout? : Int,
trial? : Bool,
) -> Unit {
consume(
(
button, click_count, delay, force, modifiers, no_wait_after, position, timeout,
trial,
),
)
let locator = self.locator(selector)
locator.click()
}
///|
/// Double click on element
pub async fn Page::dblclick(
self : Page,
selector : String,
timeout? : Int,
force? : Bool,
) -> Unit {
consume((timeout, force))
let locator = self.locator(selector)
locator.dblclick()
}
///|
/// Fill input
pub async fn Page::fill(
self : Page,
selector : String,
value : String,
force? : Bool,
no_wait_after? : Bool,
timeout? : Int,
) -> Unit {
consume((force, no_wait_after, timeout))
let locator = self.locator(selector)
locator.fill(value)
}
///|
/// Type text (keystroke by keystroke)
pub async fn Page::type_(
self : Page,
selector : String,
text : String,
delay? : Int,
no_wait_after? : Bool,
timeout? : Int,
) -> Unit {
consume((delay, no_wait_after, timeout))
let locator = self.locator(selector)
locator.type_(text)
}
///|
/// Press key
pub async fn Page::press(
self : Page,
selector : String,
key : String,
delay? : Int,
no_wait_after? : Bool,
timeout? : Int,
) -> Unit {
consume((delay, no_wait_after, timeout))
let locator = self.locator(selector)
locator.press(key)
}
///|
/// Check checkbox
pub async fn Page::check(
self : Page,
selector : String,
force? : Bool,
no_wait_after? : Bool,
timeout? : Int,
) -> Unit {
consume((force, no_wait_after, timeout))
let locator = self.locator(selector)
locator.check()
}
///|
/// Uncheck checkbox
pub async fn Page::uncheck(
self : Page,
selector : String,
force? : Bool,
no_wait_after? : Bool,
timeout? : Int,
) -> Unit {
consume((force, no_wait_after, timeout))
let locator = self.locator(selector)
locator.uncheck()
}
///|
/// Select option(s) in dropdown
pub async fn Page::select_option(
self : Page,
selector : String,
values : Array[SelectOption],
force? : Bool,
no_wait_after? : Bool,
timeout? : Int,
) -> Array[String] {
consume((self, selector, values, force, no_wait_after, timeout))
async_noop()
[]
}
///|
/// Hover over element
pub async fn Page::hover(
self : Page,
selector : String,
force? : Bool,
modifiers? : Array[String],
position? : (Double, Double),
timeout? : Int,
trial? : Bool,
) -> Unit {
consume((force, modifiers, position, timeout, trial))
let locator = self.locator(selector)
locator.hover()
}
///|
/// Focus element
pub async fn Page::focus(
self : Page,
selector : String,
timeout? : Int,
) -> Unit {
consume(timeout)
let locator = self.locator(selector)
locator.focus()
}
///|
/// Wait for selector
pub async fn Page::wait_for_selector(
self : Page,
selector : String,
state? : String,
timeout? : Int,
strict? : Bool,
) -> ElementHandle? {
let strict_mode = match strict {
Some(true) => true
_ => false
}
let desired = match state {
Some("attached") => "attached"
Some("detached") => "detached"
Some("hidden") => "hidden"
Some("visible") => "visible"
_ => "visible"
}
let timeout_ms = match timeout {
Some(value) => value
None => 30_000
}
let start = @async.now()
let result = for attempt = 0 {
let nodes = self.locate_nodes(selector)
let found = nodes.length() > 0
if strict_mode && nodes.length() > 1 {
raise PlaywrightError::StrictModeViolation(
selector~,
count=nodes.length(),
)
}
match desired {
"attached" =>
if found {
let handle = JSHandle::from_page(self, nodes[0])
break handle.as_element()
}
"detached" => if not(found) { break None }
"hidden" =>
if not(found) {
break None
} else {
match self.is_node_visible(nodes[0]) {
Some(true) => ()
Some(false) => break None
None => ()
}
}
"visible" =>
if found {
match self.is_node_visible(nodes[0]) {
Some(true) => {
let handle = JSHandle::from_page(self, nodes[0])
break handle.as_element()
}
_ => ()
}
}
_ => ()
}
if timeout_ms <= 0 {
break None
}
let elapsed = @async.now() - start
if elapsed >= timeout_ms.to_int64() {
break None
}
let remaining = timeout_ms - elapsed.to_int()
let base = 10
let cap = 200
let step = if attempt > 6 { 6 } else { attempt }
let delay = base * (1 << step)
let wait = if delay > cap { cap } else { delay }
let wait_ms = if wait > remaining { remaining } else { wait }
sleep_ms(wait_ms)
continue attempt + 1
}
result
}
///|
/// Wait for load state
pub async fn Page::wait_for_load_state(
self : Page,
state? : String,
timeout? : Int,
) -> Unit {
let desired = match state {
Some("domcontentloaded") => "domcontentloaded"
Some("load") => "load"
Some("networkidle") => "load"
Some(value) => value
None => "load"
}
match self.eval_string("document.readyState") {
Some("complete") =>
if desired == "load" || desired == "networkidle" {
return
}
Some("interactive") => if desired == "domcontentloaded" { return }
_ => ()
}
match timeout {
Some(value) => {
let _ = self.wait_for_navigation(timeout=value, wait_until=desired)
}
None => {
let _ = self.wait_for_navigation(wait_until=desired)
}
}
}
///|
/// Wait for navigation
pub async fn Page::wait_for_navigation(
self : Page,
timeout? : Int,
url? : String,
wait_until? : String,
) -> Response? {
let wait_method = match wait_until {
Some("domcontentloaded") => "browsingContext.domContentLoaded"
Some("load") => "browsingContext.load"
Some("networkidle") => "browsingContext.load"
Some(value) => value
None => "browsingContext.load"
}
let timeout_ms = match timeout {
Some(value) => value
None => 30_000
}
let start = @async.now()
let result = for attempt = 0 {
match self.take_navigation_event(Some(wait_method), url) {
Some(evt) => {
self.state.val.url = evt.url
break Some(make_response(evt.url))
}
None => ()
}
match url {
Some(target) =>
if self.state.val.url == target {
break Some(make_response(target))
}
None => ()
}
if timeout_ms <= 0 {
break None
}
let elapsed = @async.now() - start
if elapsed >= timeout_ms.to_int64() {
break None
}
let remaining = timeout_ms - elapsed.to_int()
let base = 10
let cap = 200
let step = if attempt > 6 { 6 } else { attempt }
let delay = base * (1 << step)
let wait = if delay > cap { cap } else { delay }
let wait_ms = if wait > remaining { remaining } else { wait }
sleep_ms(wait_ms)
continue attempt + 1
}
result
}
///|
/// Wait for URL
pub async fn Page::wait_for_url(
self : Page,
url : String,
timeout? : Int,
wait_until? : String,
) -> Unit {
consume(wait_until)
if self.state.val.url == url {
return
}
let timeout_ms = match timeout {
Some(value) => value
None => 30_000
}
let start = @async.now()
let _ = for attempt = 0 {
match self.take_navigation_event(None, Some(url)) {
Some(evt) => {
self.state.val.url = evt.url
break ()
}
None => ()
}
if self.state.val.url == url {
break ()
}
if timeout_ms <= 0 {
break ()
}
let elapsed = @async.now() - start
if elapsed >= timeout_ms.to_int64() {
break ()
}
let remaining = timeout_ms - elapsed.to_int()
let base = 10
let cap = 200
let step = if attempt > 6 { 6 } else { attempt }
let delay = base * (1 << step)
let wait = if delay > cap { cap } else { delay }
let wait_ms = if wait > remaining { remaining } else { wait }
sleep_ms(wait_ms)
continue attempt + 1
}
}
///|
/// Wait for network request
pub async fn Page::wait_for_request(
self : Page,
url : String,
timeout? : Int,
match_mode? : String,
predicate? : (Request) -> Bool,
) -> Request? {
let match_mode = resolve_match_mode(url, match_mode)
let timeout_ms = match timeout {
Some(value) => value
None => 30_000
}
let start = @async.now()
let result = for attempt = 0 {
match self.take_request_event(url, match_mode, predicate) {
Some(req) => break Some(req)
None => ()
}
if timeout_ms <= 0 {
break None
}
let elapsed = @async.now() - start
if elapsed >= timeout_ms.to_int64() {
break None
}
let remaining = timeout_ms - elapsed.to_int()
let base = 10
let cap = 200
let step = if attempt > 6 { 6 } else { attempt }
let delay = base * (1 << step)
let wait = if delay > cap { cap } else { delay }
let wait_ms = if wait > remaining { remaining } else { wait }
sleep_ms(wait_ms)
continue attempt + 1
}
result
}
///|
/// Wait for network response
pub async fn Page::wait_for_response(
self : Page,
url : String,
timeout? : Int,
match_mode? : String,
stage? : String,
predicate? : (Response) -> Bool,
) -> Response? {
let match_mode = resolve_match_mode(url, match_mode)
let resolved_stage = resolve_response_stage(stage)
let timeout_ms = match timeout {
Some(value) => value
None => 30_000
}
let start = @async.now()
let result = for attempt = 0 {
let matched = if resolved_stage == "completed" {
self.take_response_completed_event(url, match_mode, predicate)
} else {
self.take_response_event(url, match_mode, predicate)
}
match matched {
Some(resp) => break Some(resp)
None => ()
}
if timeout_ms <= 0 {
break None
}
let elapsed = @async.now() - start
if elapsed >= timeout_ms.to_int64() {
break None
}
let remaining = timeout_ms - elapsed.to_int()
let base = 10
let cap = 200
let step = if attempt > 6 { 6 } else { attempt }
let delay = base * (1 << step)
let wait = if delay > cap { cap } else { delay }
let wait_ms = if wait > remaining { remaining } else { wait }
sleep_ms(wait_ms)
continue attempt + 1
}
result
}
///|
/// Wait for timeout
pub async fn Page::wait_for_timeout(self : Page, timeout : Int) -> Unit {
consume(self)
sleep_ms(timeout)
}
///|
/// Wait for function to return truthy value
pub async fn Page::wait_for_function(
self : Page,
page_function : String,
arg? : Json,
polling? : Int,
timeout? : Int,
) -> Json {
consume((polling, timeout))
match arg {
Some(value) => self.evaluate(page_function, arg=value)
None => self.evaluate(page_function)
}
}
///|
/// Evaluate JavaScript in page context
pub async fn Page::evaluate(
self : Page,
page_function : String,
arg? : Json,
) -> Json {
consume(arg)
let state = self.state.val
let result = state.browser.state.val.client.script_evaluate(
page_function,
state.context_id,
)
match result {
Ok(json) =>
match json_get_object(json, "result") {
Some(value) => remote_value_to_json(value)
None => Json::null()
}
Err(_) => Json::null()
}
}
///|
/// Evaluate JavaScript and return handle
pub async fn Page::evaluate_handle(
self : Page,
page_function : String,
arg? : Json,
) -> JSHandle {
consume(arg)
let state = self.state.val
let result = state.browser.state.val.client.script_evaluate(
page_function,
state.context_id,
)
match result {
Ok(json) =>
match json_get_object(json, "result") {
Some(value) => JSHandle::from_page(self, value)
None => JSHandle::from_page(self, Json::null())
}
Err(_) => JSHandle::from_page(self, Json::null())
}
}
///|
/// Take screenshot
pub async fn Page::screenshot(
self : Page,
path? : String,
type_? : String,
quality? : Int,
full_page? : Bool,
clip? : Clip,
omit_background? : Bool,
timeout? : Int,
scale? : String,
) -> ResponseBody {
consume(
(
self, path, type_, quality, full_page, clip, omit_background, timeout, scale,
),
)
async_noop()
ResponseBody::bytes(b"")
}
///|
/// Generate PDF (Chromium only)
pub async fn Page::pdf(
self : Page,
path? : String,
scale? : Double,
display_header_footer? : Bool,
header_template? : String,
footer_template? : String,
print_background? : Bool,
landscape? : Bool,
page_ranges? : String,
format? : String,
width? : String,
height? : String,
margin? : Margin,
prefer_css_page_size? : Bool,
) -> ResponseBody {
consume(
(
self, path, scale, display_header_footer, header_template, footer_template,
print_background, landscape, page_ranges, format, width, height, margin, prefer_css_page_size,
),
)
async_noop()
ResponseBody::bytes(b"")
}
///|
/// Close page
pub async fn Page::close(self : Page, run_before_unload? : Bool) -> Unit {
consume(run_before_unload)
let state = self.state.val
let _ = state.browser.state.val.client.browsing_context_close(
state.context_id,
)
let _ = state.context
}
///|
/// Close page with error callback (for use with defer)
/// Note: This initiates close but doesn't wait for completion
pub fn Page::close_with_error_callback(
self : Page,
on_error : (Error) -> Unit,
) -> Unit {
consume((self, on_error))
}
///|
/// Check if page is closed
pub fn Page::is_closed(self : Page) -> Bool {
consume(self)
false
}
///|
/// Bring page to front
pub async fn Page::bring_to_front(self : Page) -> Unit {
consume(self)
async_noop()
}
///|
/// Set viewport size
pub async fn Page::set_viewport_size(
self : Page,
width : Int,
height : Int,
) -> Unit {
consume((self, width, height))
async_noop()
}
///|
/// Get viewport size
pub fn Page::viewport_size(self : Page) -> (Int, Int)? {
consume(self)
None
}
///|
/// Set extra HTTP headers
pub async fn Page::set_extra_http_headers(
self : Page,
headers : Headers,
) -> Unit {
consume((self, headers))
async_noop()
}
///|
/// Add script tag
pub async fn Page::add_script_tag(
self : Page,
url? : String,
path? : String,
content? : String,
type_? : String,
) -> Json {
consume((self, url, path, content, type_))
async_noop()
Json::null()
}
///|
/// Add style tag
pub async fn Page::add_style_tag(
self : Page,
url? : String,
path? : String,
content? : String,
) -> Json {
consume((self, url, path, content))
async_noop()
Json::null()
}
///|
/// Expose function to page
pub async fn Page::expose_function(
self : Page,
name : String,
callback : Json,
) -> Unit {
consume((self, name, callback))
async_noop()
}
///|
/// Set default navigation timeout
pub fn Page::set_default_navigation_timeout(self : Page, timeout : Int) -> Unit {
consume((self, timeout))
}
///|
/// Set default timeout
pub fn Page::set_default_timeout(self : Page, timeout : Int) -> Unit {
consume((self, timeout))
}
///|
async fn Page::eval_string(self : Page, expression : String) -> String? {
let state = self.state.val
let result = state.browser.state.val.client.script_evaluate(
expression,
state.context_id,
)
match result {
Ok(json) =>
match json_get_object(json, "result") {
Some(value) => remote_value_to_string(value)
None => None
}
Err(_) => None
}
}
///|
async fn Page::call_function(
self : Page,
function_declaration : String,
args : Array[Json],
) -> Unit {
let state = self.state.val
let _ = state.browser.state.val.client.script_call_function(
function_declaration,
state.context_id,
arguments=args,
)
}
///|
async fn Page::locate_nodes(self : Page, selector : String) -> Array[Json] {
let state = self.state.val
let locator = @bidi.Locator::css(selector)
let result = state.browser.state.val.client.browsing_context_locate_nodes_typed(
state.context_id,
locator,
)
match result {
Ok(json) => json_get_array(json, "nodes").unwrap_or([])
Err(_) => []
}
}
///|
async fn Page::is_node_visible(self : Page, node : Json) -> Bool? {
let state = self.state.val
let args : Array[Json] = [node]
let result = state.browser.state.val.client.script_call_function(
"(node) => { if (!node || !(node instanceof Element)) return false; if (node.hidden) return false; let el = node; while (el) { if (el.hidden) return false; if (el.get_attribute && el.get_attribute('aria-hidden') === 'true') return false; const style = getComputedStyle(el); if (!style) return false; const opacity = parseFloat(style.opacity || '1'); if (opacity <= 0) return false; if (style.visibility === 'hidden' || style.visibility === 'collapse' || style.display === 'none' || style.contentVisibility === 'hidden') return false; if (el instanceof HTMLDetailsElement && !el.open) { const summary = el.querySelector('summary'); if (summary && !summary.contains(node)) return false; } el = el.parentElement; } if (node.getClientRects().length === 0) return false; const rect = node.getBoundingClientRect(); return rect.width > 0 && rect.height > 0; }",
state.context_id,
arguments=args,
)
match result {
Ok(json) =>
match json_get_object(json, "result") {
Some(value) =>
match remote_value_to_json(value) {
True => Some(true)
False => Some(false)
_ => None
}
None => None
}
Err(_) => None
}
}
///|
fn Page::take_navigation_event(
self : Page,
event_name : String?,
url : String?,
) -> NavigationEvent? {
let events = self.state.val.navigation_events
let mut index = 0
while index < events.length() {
let evt = events[index]
let event_ok = match event_name {
Some(value) => evt.event_name == value
None => true
}
let url_ok = match url {
Some(value) => evt.url == value
None => true
}
if event_ok && url_ok {
let _ = events.remove(index)
return Some(evt)
}
index = index + 1
}
None
}
///|
fn Page::take_request_event(
self : Page,
pattern : String,
match_mode : String,
predicate : ((Request) -> Bool)?,
) -> Request? {
let events = self.state.val.request_events
let mut index = 0
while index < events.length() {
let req = events[index]
let url_ok = url_matches(req.url(), pattern, match_mode)
let predicate_ok = match predicate {
Some(pred) => pred(req)
None => true
}
if url_ok && predicate_ok {
let _ = events.remove(index)
return Some(req)
}
index = index + 1
}
None
}
///|
fn Page::take_response_event(
self : Page,
pattern : String,
match_mode : String,
predicate : ((Response) -> Bool)?,
) -> Response? {
let events = self.state.val.response_events
let mut index = 0
while index < events.length() {
let resp = events[index]
let url_ok = url_matches(resp.url(), pattern, match_mode)
let predicate_ok = match predicate {
Some(pred) => pred(resp)
None => true
}
if url_ok && predicate_ok {
let _ = events.remove(index)
return Some(resp)
}
index = index + 1
}
None
}
///|
fn Page::take_response_completed_event(
self : Page,
pattern : String,
match_mode : String,
predicate : ((Response) -> Bool)?,
) -> Response? {
let events = self.state.val.response_completed_events
let mut index = 0
while index < events.length() {
let resp = events[index]
let url_ok = url_matches(resp.url(), pattern, match_mode)
let predicate_ok = match predicate {
Some(pred) => pred(resp)
None => true
}
if url_ok && predicate_ok {
let _ = events.remove(index)
return Some(resp)
}
index = index + 1
}
None
}
///|
fn resolve_match_mode(pattern : String, match_mode : String?) -> String {
match match_mode {
Some(mode) => {
let lower = mode.to_lower()
if lower == "contains" || lower == "include" || lower == "substring" {
"contains"
} else if lower == "startswith" || lower == "prefix" {
"starts_with"
} else if lower == "endswith" || lower == "suffix" {
"ends_with"
} else if lower == "glob" || lower == "wildcard" {
"glob"
} else if lower == "regex" || lower == "re" {
"regex"
} else {
"exact"
}
}
None =>
if pattern.length() >= 2 &&
pattern.has_prefix("/") &&
pattern.has_suffix("/") {
"regex"
} else if pattern.contains_char('*') || pattern.contains_char('?') {
"glob"
} else {
"exact"
}
}
}
///|
fn resolve_response_stage(stage : String?) -> String {
match stage {
Some(value) => {
let lower = value.to_lower()
if lower == "complete" || lower == "completed" {
"completed"
} else {
"started"
}
}
None => "started"
}
}
///|
fn url_matches(url : String, pattern : String, match_mode : String) -> Bool {
match match_mode {
"contains" => url.contains(pattern)
"starts_with" => url.has_prefix(pattern)
"ends_with" => url.has_suffix(pattern)
"glob" => glob_match(url, pattern)
"regex" => regex_match(url, normalize_regex_pattern(pattern))
_ => url == pattern
}
}
///|
fn normalize_regex_pattern(pattern : String) -> String {
if pattern.length() >= 2 && pattern.has_prefix("/") && pattern.has_suffix("/") {
let view = pattern.sub(start=1, end=pattern.length() - 1) catch {
_ => return pattern
}
view.to_string()
} else {
pattern
}
}
///|
fn glob_match(text : String, pattern : String) -> Bool {
let text_chars = text.to_array()
let pattern_chars = pattern.to_array()
let mut ti = 0
let mut pi = 0
let mut star = -1
let mut match_index = 0
let tlen = text_chars.length()
let plen = pattern_chars.length()
while ti < tlen {
if pi < plen &&
(pattern_chars[pi] == '?' || pattern_chars[pi] == text_chars[ti]) {
ti = ti + 1
pi = pi + 1
continue
}
if pi < plen && pattern_chars[pi] == '*' {
star = pi
match_index = ti
pi = pi + 1
continue
}
if star >= 0 {
pi = star + 1
match_index = match_index + 1
ti = match_index
continue
}
return false
}
while pi < plen && pattern_chars[pi] == '*' {
pi = pi + 1
}
pi == plen
}