Skip to content

Commit dceab4e

Browse files
committed
Pull request 745: AG-45206 Fix scriptlets compilation error in Safari 15 due to unsupported regex lookbehind
Squashed commit of the following: commit de181f4 Author: Maxim Topciu <[email protected]> Date: Thu Aug 14 18:10:22 2025 +0300 AG-45206 remove redundant variable commit 20aacfa Author: Maxim Topciu <[email protected]> Date: Thu Aug 14 18:08:50 2025 +0300 AG-45206 Fix scriptlets compilation error in Safari 15 due to unsupported regex lookbehind
1 parent e0410fb commit dceab4e

File tree

2 files changed

+93
-5
lines changed

2 files changed

+93
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
2020

2121
- Incorrectly escaped quotes in `trusted-replace-node-text` scriptlet [#517].
2222
- `TrustedScriptURL` in `prevent-element-src-loading` scriptlet [#514].
23+
- Fix scriptlets compilation error in Safari 15 due to unsupported regex lookbehind [#519].
2324

2425
[Unreleased]: https://github.com/AdguardTeam/Scriptlets/compare/v2.2.8...HEAD
2526
[#405]: https://github.com/AdguardTeam/Scriptlets/issues/405
2627
[#514]: https://github.com/AdguardTeam/Scriptlets/issues/514
2728
[#517]: https://github.com/AdguardTeam/Scriptlets/issues/517
29+
[#519]: https://github.com/AdguardTeam/Scriptlets/issues/519
2830

2931
## [v2.2.8] - 2025-07-08
3032

src/helpers/prune-utils.ts

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,97 @@ export const getPrunePath = (props: unknown) => {
197197
&& props !== '';
198198

199199
if (validPropsString) {
200-
// Regular expression to split the properties string by spaces,
201-
// but it should not split if there is space inside value, like in:
202-
// 'foo.[=]./foo bar baz/ bar' or 'foo.[=]./foo bar \/ baz/ bar'
203-
const splitRegexp = /(?<!\.\[=\]\.\/(?:[^/]|\\.)*)\s+/;
204-
const parts = props.split(splitRegexp).map((part) => {
200+
/**
201+
* Safari 15 does not support lookbehind, so we need to use a custom splitter.
202+
*
203+
* Legacy approach (for engines with lookbehind) that this replaces:
204+
*
205+
* // Regular expression to split the properties string by spaces,
206+
* // but it should not split if there is space inside value, like in:
207+
* // 'foo.[=]./foo bar baz/ bar' or 'foo.[=]./foo bar \/ baz/ bar'
208+
* // const splitRegexp = /(?<!\.\[=\]\.\/(?:[^/]|\\.)*)\s+/;
209+
*
210+
* @param str splitted string
211+
* @returns array of parts
212+
*/
213+
// We ignore the rule here because we need to define the function inside the function,
214+
// so that we do not have to import it additionally from the scriptlets,
215+
// also we need to use the VALUE_MARKER variable.
216+
// eslint-disable-next-line no-inner-declarations
217+
function splitProps(str: string) {
218+
const parts: string[] = [];
219+
let current = '';
220+
let i = 0;
221+
let insideRegex = false;
222+
let escapeActive = false;
223+
224+
while (i < str.length) {
225+
const ch = str[i];
226+
227+
if (!insideRegex) {
228+
// split on whitespace (treat runs of whitespace as a single separator)
229+
if (
230+
ch === ' '
231+
|| ch === '\n'
232+
|| ch === '\t'
233+
|| ch === '\r'
234+
|| ch === '\f'
235+
|| ch === '\v'
236+
) {
237+
// skip consecutive whitespace
238+
while (i < str.length && /\s/.test(str[i])) {
239+
i += 1;
240+
}
241+
if (current !== '') {
242+
parts.push(current);
243+
current = '';
244+
}
245+
continue;
246+
}
247+
248+
// detect VALUE_MARKER followed by '/'
249+
if (str.startsWith(VALUE_MARKER, i)) {
250+
current += VALUE_MARKER;
251+
i += VALUE_MARKER.length;
252+
if (str[i] === '/') {
253+
// enter regex mode and consume opening '/'
254+
insideRegex = true;
255+
escapeActive = false;
256+
current += '/';
257+
i += 1;
258+
continue;
259+
}
260+
// no regex begins; continue as normal
261+
continue;
262+
}
263+
264+
current += ch;
265+
i += 1;
266+
continue;
267+
}
268+
269+
// inside regex body: copy until we hit an unescaped '/'
270+
current += ch;
271+
if (ch === '\\') {
272+
escapeActive = !escapeActive;
273+
} else if (ch === '/' && !escapeActive) {
274+
insideRegex = false;
275+
escapeActive = false;
276+
} else {
277+
escapeActive = false;
278+
}
279+
i += 1;
280+
}
281+
282+
if (current !== '') {
283+
parts.push(current);
284+
}
285+
286+
return parts;
287+
}
288+
289+
const rawParts = splitProps(props);
290+
const parts = rawParts.map((part) => {
205291
const splitPart = part.split(VALUE_MARKER);
206292
const path = splitPart[0];
207293
let value = splitPart[1] as any;

0 commit comments

Comments
 (0)