@btraut/browser-bridge 0.13.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,403 @@
1
1
  "use strict";
2
2
  (() => {
3
+ // packages/extension/src/popup-trigger-state.ts
4
+ var normalizeAttr = (value) => {
5
+ if (typeof value !== "string") {
6
+ return void 0;
7
+ }
8
+ const trimmed = value.trim();
9
+ return trimmed.length > 0 ? trimmed : void 0;
10
+ };
11
+ var readPopupTriggerState = (target) => {
12
+ if (!(target instanceof HTMLElement)) {
13
+ return null;
14
+ }
15
+ const ariaHasPopup = normalizeAttr(target.getAttribute("aria-haspopup"));
16
+ const ariaExpanded = normalizeAttr(target.getAttribute("aria-expanded"));
17
+ const dataState = normalizeAttr(target.getAttribute("data-state"));
18
+ const open = "open" in target && typeof target.open === "boolean" ? target.open ?? false : void 0;
19
+ const qualifiesAsPopupTrigger = Boolean(ariaHasPopup) || ariaExpanded !== void 0 || open !== void 0;
20
+ if (!qualifiesAsPopupTrigger && dataState === void 0) {
21
+ return null;
22
+ }
23
+ if (!qualifiesAsPopupTrigger) {
24
+ return null;
25
+ }
26
+ return {
27
+ kind: "popup_trigger",
28
+ ariaHasPopup,
29
+ ariaExpanded,
30
+ dataState,
31
+ open
32
+ };
33
+ };
34
+ var popupTriggerStateChanged = (before, after) => {
35
+ if (!before || !after) {
36
+ return before !== after;
37
+ }
38
+ return before.ariaExpanded !== after.ariaExpanded || before.dataState !== after.dataState || before.open !== after.open;
39
+ };
40
+
41
+ // packages/extension/src/click-strategies/index.ts
42
+ var selectClickStrategy = (target) => {
43
+ const popupTriggerState = readPopupTriggerState(target);
44
+ if (popupTriggerState) {
45
+ return {
46
+ kind: "popup_trigger",
47
+ state: popupTriggerState
48
+ };
49
+ }
50
+ return { kind: "generic" };
51
+ };
52
+
53
+ // packages/extension/src/click-strategies/generic-click.ts
54
+ var executeGenericClick = (options) => {
55
+ const { target, clickCount } = options;
56
+ window.setTimeout(() => {
57
+ try {
58
+ if (target instanceof HTMLElement) {
59
+ try {
60
+ target.focus({ preventScroll: true });
61
+ } catch {
62
+ target.focus();
63
+ }
64
+ }
65
+ for (let i = 0; i < clickCount; i += 1) {
66
+ target.click();
67
+ }
68
+ } catch {
69
+ }
70
+ }, 0);
71
+ return { ok: true };
72
+ };
73
+
74
+ // packages/extension/src/click-strategies/popup-trigger-click.ts
75
+ var executePopupTriggerClick = async (options) => {
76
+ const popupTarget = options.target;
77
+ try {
78
+ popupTarget.focus({ preventScroll: true });
79
+ } catch {
80
+ popupTarget.focus();
81
+ }
82
+ for (let i = 0; i < options.clickCount; i += 1) {
83
+ popupTarget.click();
84
+ }
85
+ await options.sleep(options.settleMs ?? 50);
86
+ if (window.location.href !== options.locationBefore || !options.target.isConnected) {
87
+ return { ok: true };
88
+ }
89
+ const popupTriggerAfter = readPopupTriggerState(options.target);
90
+ if (!popupTriggerStateChanged(options.beforeState, popupTriggerAfter ?? null)) {
91
+ return {
92
+ ok: false,
93
+ error: {
94
+ code: "FAILED_PRECONDITION",
95
+ message: "Click focused the popup trigger but did not change its open state.",
96
+ retryable: false,
97
+ details: {
98
+ reason: "click_state_unchanged",
99
+ control: options.beforeState.kind,
100
+ aria_haspopup: options.beforeState.ariaHasPopup,
101
+ aria_expanded_before: options.beforeState.ariaExpanded,
102
+ aria_expanded_after: popupTriggerAfter?.ariaExpanded,
103
+ data_state_before: options.beforeState.dataState,
104
+ data_state_after: popupTriggerAfter?.dataState
105
+ }
106
+ }
107
+ };
108
+ }
109
+ return { ok: true };
110
+ };
111
+
112
+ // packages/extension/src/locator-point.ts
113
+ var pointHitsTarget = (target, x, y, options) => {
114
+ if (typeof document.elementFromPoint !== "function") {
115
+ return true;
116
+ }
117
+ const hit = document.elementFromPoint(x, y);
118
+ if (!hit) {
119
+ return false;
120
+ }
121
+ if (options?.directOnly) {
122
+ return target === hit;
123
+ }
124
+ return target === hit || target.contains(hit);
125
+ };
126
+ var getHittablePoint = (target, options) => {
127
+ const rect = target.getBoundingClientRect();
128
+ const insetX = Math.min(Math.max(rect.width * 0.25, 1), rect.width / 2);
129
+ const insetY = Math.min(Math.max(rect.height * 0.25, 1), rect.height / 2);
130
+ const candidatePoints = [
131
+ { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 },
132
+ { x: rect.left + insetX, y: rect.top + insetY },
133
+ { x: rect.right - insetX, y: rect.top + insetY },
134
+ { x: rect.left + insetX, y: rect.bottom - insetY },
135
+ { x: rect.right - insetX, y: rect.bottom - insetY },
136
+ { x: rect.left + rect.width / 2, y: rect.top + insetY },
137
+ { x: rect.left + rect.width / 2, y: rect.bottom - insetY },
138
+ { x: rect.left + insetX, y: rect.top + rect.height / 2 },
139
+ { x: rect.right - insetX, y: rect.top + rect.height / 2 }
140
+ ];
141
+ if (options?.preferDirectHit) {
142
+ for (const point of candidatePoints) {
143
+ if (pointHitsTarget(target, point.x, point.y, { directOnly: true })) {
144
+ return point;
145
+ }
146
+ }
147
+ }
148
+ for (const point of candidatePoints) {
149
+ if (pointHitsTarget(target, point.x, point.y)) {
150
+ return point;
151
+ }
152
+ }
153
+ return candidatePoints[0] ?? { x: rect.left, y: rect.top };
154
+ };
155
+
156
+ // packages/extension/src/locator-ranking.ts
157
+ var collectVisibleText = (node, isVisible2) => {
158
+ if (node.nodeType === Node.TEXT_NODE) {
159
+ return node.textContent || "";
160
+ }
161
+ if (!(node instanceof Element)) {
162
+ return "";
163
+ }
164
+ if (!isVisible2(node)) {
165
+ return "";
166
+ }
167
+ if (node instanceof HTMLElement) {
168
+ const { innerText } = node;
169
+ if (typeof innerText === "string" && innerText.length > 0) {
170
+ return innerText;
171
+ }
172
+ }
173
+ return Array.from(node.childNodes).map((child) => collectVisibleText(child, isVisible2)).join("");
174
+ };
175
+ var normalizeText = (value) => value.replace(/\s+/g, " ").trim();
176
+ var isClickable = (element) => element instanceof HTMLElement && element.matches(
177
+ 'a,button,input,textarea,select,summary,label,[role="button"],[tabindex]'
178
+ );
179
+ var getNodeDepth = (element) => {
180
+ let depth = 0;
181
+ let current = element;
182
+ while (current) {
183
+ depth += 1;
184
+ current = current.parentElement;
185
+ }
186
+ return depth;
187
+ };
188
+ var isVisible = (element) => {
189
+ if (!(element instanceof HTMLElement)) {
190
+ return false;
191
+ }
192
+ const style = window.getComputedStyle(element);
193
+ if (style.visibility === "hidden" || style.display === "none") {
194
+ return false;
195
+ }
196
+ const rect = element.getBoundingClientRect();
197
+ if (rect.width === 0 && rect.height === 0) {
198
+ return false;
199
+ }
200
+ if (element.offsetWidth === 0 && element.offsetHeight === 0 && element.getClientRects().length === 0) {
201
+ return false;
202
+ }
203
+ let current = element;
204
+ while (current) {
205
+ const currentStyle = window.getComputedStyle(current);
206
+ if (currentStyle.display === "none") {
207
+ return false;
208
+ }
209
+ if (currentStyle.visibility === "hidden" || currentStyle.visibility === "collapse") {
210
+ return false;
211
+ }
212
+ const opacity = Number.parseFloat(currentStyle.opacity ?? "1");
213
+ if (Number.isFinite(opacity) && opacity <= 0) {
214
+ return false;
215
+ }
216
+ current = current.parentElement;
217
+ }
218
+ return true;
219
+ };
220
+ var isOnScreen = (element) => {
221
+ if (!(element instanceof HTMLElement) || !isVisible(element)) {
222
+ return false;
223
+ }
224
+ const rect = element.getBoundingClientRect();
225
+ return rect.right > 0 && rect.bottom > 0 && rect.left < window.innerWidth && rect.top < window.innerHeight;
226
+ };
227
+ var getRenderedText = (element) => normalizeText(collectVisibleText(element, isVisible));
228
+ var getRoleAccessibleName = (element) => normalizeText(
229
+ element.getAttribute("aria-label") ?? element.getAttribute("title") ?? getRenderedText(element)
230
+ );
231
+ var scoreCandidates = (candidates, options) => {
232
+ const queryText = options?.exactText ? normalizeText(options.exactText) : "";
233
+ const queryHref = options?.exactHref ?? "";
234
+ if (candidates.length === 0) {
235
+ return null;
236
+ }
237
+ const scored = candidates.filter(isVisible).map((candidate) => {
238
+ const text = getRenderedText(candidate);
239
+ const href = candidate.getAttribute("href") ?? "";
240
+ return {
241
+ candidate,
242
+ exactText: queryText.length > 0 && text === queryText,
243
+ exactHref: queryHref.length > 0 && (href === queryHref || candidate instanceof HTMLAnchorElement && candidate.href === queryHref),
244
+ onScreen: isOnScreen(candidate),
245
+ clickable: isClickable(candidate),
246
+ textLength: text.length,
247
+ depth: getNodeDepth(candidate)
248
+ };
249
+ }).sort((a, b) => {
250
+ if (a.exactHref !== b.exactHref) {
251
+ return a.exactHref ? -1 : 1;
252
+ }
253
+ if (a.exactText !== b.exactText) {
254
+ return a.exactText ? -1 : 1;
255
+ }
256
+ if (a.onScreen !== b.onScreen) {
257
+ return a.onScreen ? -1 : 1;
258
+ }
259
+ if (a.clickable !== b.clickable) {
260
+ return a.clickable ? -1 : 1;
261
+ }
262
+ if (a.textLength !== b.textLength) {
263
+ return a.textLength - b.textLength;
264
+ }
265
+ return b.depth - a.depth;
266
+ });
267
+ return scored[0]?.candidate ?? candidates[0] ?? null;
268
+ };
269
+ var findByText = (text) => {
270
+ const query = normalizeText(text);
271
+ if (query.length === 0) {
272
+ return null;
273
+ }
274
+ const candidateMap = /* @__PURE__ */ new Map();
275
+ const tree = document.createTreeWalker(
276
+ document.body,
277
+ NodeFilter.SHOW_ELEMENT
278
+ );
279
+ let node = tree.nextNode();
280
+ while (node) {
281
+ const element = node;
282
+ if (!isVisible(element)) {
283
+ node = tree.nextNode();
284
+ continue;
285
+ }
286
+ const elementText = getRenderedText(element);
287
+ if (!elementText.includes(query)) {
288
+ node = tree.nextNode();
289
+ continue;
290
+ }
291
+ let preferredTarget = element;
292
+ let current = element;
293
+ while (current) {
294
+ if (current !== element && isVisible(current) && isClickable(current) && getRenderedText(current).includes(query)) {
295
+ preferredTarget = current;
296
+ break;
297
+ }
298
+ current = current.parentElement;
299
+ }
300
+ const preferredText = getRenderedText(preferredTarget);
301
+ const currentBest = candidateMap.get(preferredTarget);
302
+ const nextScore = {
303
+ exact: preferredText === query || elementText === query,
304
+ onScreen: isOnScreen(preferredTarget),
305
+ clickable: isClickable(preferredTarget),
306
+ textLength: preferredText.length || elementText.length,
307
+ depth: getNodeDepth(preferredTarget)
308
+ };
309
+ if (!currentBest || Number(nextScore.exact) > Number(currentBest.exact) || nextScore.exact === currentBest.exact && Number(nextScore.onScreen) > Number(currentBest.onScreen) || nextScore.exact === currentBest.exact && nextScore.onScreen === currentBest.onScreen && Number(nextScore.clickable) > Number(currentBest.clickable) || nextScore.exact === currentBest.exact && nextScore.onScreen === currentBest.onScreen && nextScore.clickable === currentBest.clickable && nextScore.textLength < currentBest.textLength || nextScore.exact === currentBest.exact && nextScore.onScreen === currentBest.onScreen && nextScore.clickable === currentBest.clickable && nextScore.textLength === currentBest.textLength && nextScore.depth > currentBest.depth) {
310
+ candidateMap.set(preferredTarget, nextScore);
311
+ }
312
+ node = tree.nextNode();
313
+ }
314
+ const candidates = Array.from(candidateMap.entries()).sort((a, b) => {
315
+ const [, left] = a;
316
+ const [, right] = b;
317
+ if (left.exact !== right.exact) {
318
+ return left.exact ? -1 : 1;
319
+ }
320
+ if (left.onScreen !== right.onScreen) {
321
+ return left.onScreen ? -1 : 1;
322
+ }
323
+ if (left.clickable !== right.clickable) {
324
+ return left.clickable ? -1 : 1;
325
+ }
326
+ if (left.textLength !== right.textLength) {
327
+ return left.textLength - right.textLength;
328
+ }
329
+ return right.depth - left.depth;
330
+ });
331
+ return candidates[0]?.[0] ?? null;
332
+ };
333
+
334
+ // packages/extension/src/snapshot-ref-recovery.ts
335
+ var SNAPSHOT_REF_REGISTRY_ID = "__bb_snapshot_ref_registry__";
336
+ var readSnapshotRefRegistry = () => {
337
+ const registry = /* @__PURE__ */ new Map();
338
+ const el = document.getElementById(SNAPSHOT_REF_REGISTRY_ID);
339
+ const raw = el?.textContent;
340
+ if (!raw) {
341
+ return registry;
342
+ }
343
+ try {
344
+ const parsed = JSON.parse(raw);
345
+ if (!Array.isArray(parsed)) {
346
+ return registry;
347
+ }
348
+ for (const entry of parsed) {
349
+ if (!entry || typeof entry !== "object") {
350
+ continue;
351
+ }
352
+ const ref = entry.ref;
353
+ if (typeof ref !== "string" || ref.length === 0) {
354
+ continue;
355
+ }
356
+ registry.set(ref, {
357
+ role: typeof entry.role === "string" ? entry.role ?? void 0 : void 0,
358
+ name: typeof entry.name === "string" ? entry.name ?? void 0 : void 0,
359
+ url: typeof entry.url === "string" ? entry.url ?? void 0 : void 0
360
+ });
361
+ }
362
+ } catch {
363
+ return /* @__PURE__ */ new Map();
364
+ }
365
+ return registry;
366
+ };
367
+ var recoverElementBySnapshotRef = (ref, options) => {
368
+ const metadata = readSnapshotRefRegistry().get(ref);
369
+ if (!metadata) {
370
+ return null;
371
+ }
372
+ if (typeof metadata.url === "string" && metadata.url.length > 0) {
373
+ const linkCandidates = Array.from(
374
+ document.querySelectorAll('a[href],[role="link"]')
375
+ );
376
+ const bestLink = scoreCandidates(linkCandidates, {
377
+ exactHref: metadata.url,
378
+ exactText: metadata.name
379
+ });
380
+ if (bestLink) {
381
+ return bestLink;
382
+ }
383
+ }
384
+ if (typeof metadata.role === "string" && metadata.role.length > 0) {
385
+ const roleMatch = options.findByRole({
386
+ role: {
387
+ name: metadata.role,
388
+ ...metadata.name ? { value: metadata.name } : {}
389
+ }
390
+ });
391
+ if (roleMatch) {
392
+ return roleMatch;
393
+ }
394
+ }
395
+ if (typeof metadata.name === "string" && metadata.name.length > 0) {
396
+ return findByText(metadata.name);
397
+ }
398
+ return null;
399
+ };
400
+
3
401
  // packages/extension/src/content.ts
4
402
  var AGENT_TAB_BRANDING_ACTION = "drive.agent_tab_branding";
5
403
  var AGENT_TAB_FAVICON_MARKER_ATTR = "data-bb-agent-favicon";
@@ -47,38 +445,6 @@
47
445
  }
48
446
  return value.replace(/[\\"']/g, "\\$&");
49
447
  };
50
- const isVisible = (element) => {
51
- if (!(element instanceof HTMLElement)) {
52
- return false;
53
- }
54
- const style = window.getComputedStyle(element);
55
- if (style.visibility === "hidden" || style.display === "none") {
56
- return false;
57
- }
58
- const rect = element.getBoundingClientRect();
59
- if (rect.width === 0 && rect.height === 0) {
60
- return false;
61
- }
62
- if (element.offsetWidth === 0 && element.offsetHeight === 0 && element.getClientRects().length === 0) {
63
- return false;
64
- }
65
- let current = element;
66
- while (current) {
67
- const style2 = window.getComputedStyle(current);
68
- if (style2.display === "none") {
69
- return false;
70
- }
71
- if (style2.visibility === "hidden" || style2.visibility === "collapse") {
72
- return false;
73
- }
74
- const opacity = Number.parseFloat(style2.opacity ?? "1");
75
- if (Number.isFinite(opacity) && opacity <= 0) {
76
- return false;
77
- }
78
- current = current.parentElement;
79
- }
80
- return true;
81
- };
82
448
  const buildUrlMatcher = (pattern) => {
83
449
  const maxLength = 256;
84
450
  if (pattern.length > maxLength) {
@@ -108,20 +474,17 @@
108
474
  return { ok: true, matcher: (url) => url.includes(pattern) };
109
475
  }
110
476
  };
111
- const findByText = (text) => {
112
- const tree = document.createTreeWalker(
113
- document.body,
114
- NodeFilter.SHOW_ELEMENT
115
- );
116
- let node = tree.nextNode();
117
- while (node) {
118
- const element = node;
119
- if (element.textContent && element.textContent.includes(text)) {
120
- return element;
121
- }
122
- node = tree.nextNode();
123
- }
124
- return null;
477
+ const getRoleCandidates = (roleName) => {
478
+ const selectorMap = {
479
+ button: 'button,[role="button"],input[type="button"],input[type="submit"],input[type="reset"],summary',
480
+ link: 'a[href],[role="link"]',
481
+ checkbox: 'input[type="checkbox"],[role="checkbox"]',
482
+ radio: 'input[type="radio"],[role="radio"]',
483
+ textbox: 'textarea,input:not([type]),input[type="text"],input[type="search"],input[type="email"],input[type="url"],input[type="tel"],input[type="password"],[role="textbox"]',
484
+ combobox: 'select,input[list],[role="combobox"]'
485
+ };
486
+ const selector = selectorMap[roleName] ?? `[role="${escapeSelector(roleName)}"]`;
487
+ return Array.from(document.querySelectorAll(selector)).filter(isVisible);
125
488
  };
126
489
  const findByRole = (locator) => {
127
490
  const role = locator.role;
@@ -133,17 +496,23 @@
133
496
  if (typeof roleName !== "string" || roleName.length === 0) {
134
497
  return null;
135
498
  }
136
- const candidates = Array.from(
137
- document.querySelectorAll(`[role="${escapeSelector(roleName)}"]`)
138
- );
499
+ const candidates = getRoleCandidates(roleName);
139
500
  if (typeof roleValue !== "string" || roleValue.length === 0) {
140
501
  return candidates[0] ?? null;
141
502
  }
142
- return candidates.find((candidate) => {
143
- const label = candidate.getAttribute("aria-label") ?? "";
144
- const text = candidate.textContent ?? "";
145
- return label.includes(roleValue) || text.includes(roleValue);
146
- }) ?? null;
503
+ const query = normalizeText(roleValue);
504
+ const matching = candidates.filter(
505
+ (candidate) => getRoleAccessibleName(candidate).includes(query)
506
+ );
507
+ if (matching.length === 0) {
508
+ return null;
509
+ }
510
+ const exactMatches = matching.filter(
511
+ (candidate) => getRoleAccessibleName(candidate) === query
512
+ );
513
+ return scoreCandidates(exactMatches.length > 0 ? exactMatches : matching, {
514
+ exactText: query
515
+ });
147
516
  };
148
517
  const resolveLocator = (locator) => {
149
518
  if (!locator) {
@@ -157,18 +526,26 @@
157
526
  if (found) {
158
527
  return found;
159
528
  }
529
+ const fallback = recoverElementBySnapshotRef(normalized, {
530
+ findByRole
531
+ });
532
+ if (fallback) {
533
+ return fallback;
534
+ }
160
535
  }
161
536
  const testid = locator.testid;
162
537
  if (typeof testid === "string" && testid.length > 0) {
163
538
  const selector = `[data-testid="${escapeSelector(testid)}"]`;
164
- const found = document.querySelector(selector);
539
+ const found = scoreCandidates(
540
+ Array.from(document.querySelectorAll(selector))
541
+ );
165
542
  if (found) {
166
543
  return found;
167
544
  }
168
545
  }
169
546
  const css = locator.css;
170
547
  if (typeof css === "string" && css.length > 0) {
171
- const found = document.querySelector(css);
548
+ const found = scoreCandidates(Array.from(document.querySelectorAll(css)));
172
549
  if (found) {
173
550
  return found;
174
551
  }
@@ -401,12 +778,29 @@
401
778
  if (!target) {
402
779
  return buildError("LOCATOR_NOT_FOUND", "Failed to resolve locator.");
403
780
  }
404
- const rect = target.getBoundingClientRect();
781
+ const targetState = readPopupTriggerState(target);
782
+ const point = getHittablePoint(target, {
783
+ preferDirectHit: targetState !== null
784
+ });
405
785
  return ok({
406
- x: rect.left + rect.width / 2,
407
- y: rect.top + rect.height / 2
786
+ x: point.x,
787
+ y: point.y,
788
+ ...targetState ? { target_state: targetState } : {}
408
789
  });
409
790
  }
791
+ case "drive.focus_locator": {
792
+ const { locator } = parseParams();
793
+ const target = resolveLocator(locator);
794
+ if (!target || !(target instanceof HTMLElement)) {
795
+ return buildError("LOCATOR_NOT_FOUND", "Failed to resolve locator.");
796
+ }
797
+ try {
798
+ target.focus({ preventScroll: true });
799
+ } catch {
800
+ target.focus();
801
+ }
802
+ return ok();
803
+ }
410
804
  case "drive.snapshot_html": {
411
805
  const html = document.documentElement?.outerHTML ?? "";
412
806
  return ok({ format: "html", snapshot: html });
@@ -467,22 +861,20 @@
467
861
  return buildError("LOCATOR_NOT_FOUND", "Failed to resolve locator.");
468
862
  }
469
863
  const count = typeof click_count === "number" && Number.isFinite(click_count) ? Math.max(1, Math.floor(click_count)) : 1;
470
- window.setTimeout(() => {
471
- try {
472
- if (target instanceof HTMLElement) {
473
- try {
474
- target.focus({ preventScroll: true });
475
- } catch {
476
- target.focus();
477
- }
478
- }
479
- for (let i = 0; i < count; i += 1) {
480
- target.click();
481
- }
482
- } catch {
483
- }
484
- }, 0);
485
- return ok();
864
+ const strategy = selectClickStrategy(target);
865
+ if (strategy.kind === "popup_trigger") {
866
+ return await executePopupTriggerClick({
867
+ target,
868
+ beforeState: strategy.state,
869
+ clickCount: count,
870
+ locationBefore: window.location.href,
871
+ sleep
872
+ });
873
+ }
874
+ return executeGenericClick({
875
+ target,
876
+ clickCount: count
877
+ });
486
878
  }
487
879
  case "drive.hover": {
488
880
  const { locator, delay_ms } = parseParams();
@@ -905,7 +1297,9 @@
905
1297
  }
906
1298
  const checkCondition = () => {
907
1299
  if (kind === "text_present") {
908
- return (document.body?.innerText ?? "").includes(value);
1300
+ return getRenderedText(document.body).includes(
1301
+ normalizeText(value)
1302
+ );
909
1303
  }
910
1304
  if (kind === "url_matches") {
911
1305
  return urlMatcher ? urlMatcher.matcher(window.location.href) : window.location.href.includes(value);