Skip to content

Commit 28b8e9b

Browse files
committed
Backport wabac.JS change around rewriting: detect when JS is in 'strict' mode and avoid using 'arguments'
1 parent a2145be commit 28b8e9b

File tree

3 files changed

+61
-15
lines changed

3 files changed

+61
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
### Fixed
1616

1717
- Add proper typing @overload to `zimscraperlib.image.optimize_xxx` methods (#273)
18+
- Backport wabac.JS change around rewriting: detect when JS is in 'strict' mode and avoid using 'arguments' (#286)
1819

1920
## [5.3.0] - 2025-11-14
2021

src/zimscraperlib/rewriting/js.py

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
ZIM at `_zim_static/__wb_module_decl.js`
1414
1515
This code is based on https://github.com/webrecorder/wabac.js/blob/main/src/rewrite/jsrewriter.ts
16-
Last backport of upstream changes is from Oct 12, 2025
17-
Commit 1849552c3dbcbc065c05afac2dd80061db37b64d
16+
Last backport of upstream changes is from wabac.js commit:
17+
Feb 20, 2026 - 25061cb53ff113d5cff28f2f1354819f6c41034b
1818
"""
1919

2020
import re
@@ -67,17 +67,36 @@
6767
this_rw = "_____WB$wombat$check$this$function_____(this)"
6868

6969

70+
def remove_args_if_strict(
71+
target: str, opts: dict[str, Any] | None, offset: int, full_string: str
72+
) -> str:
73+
"""
74+
Replace 'arguments' with '[]' if the code is in strict mode.
75+
In strict mode, the arguments keyword is not allowed.
76+
"""
77+
opts = opts or {}
78+
79+
# Detect strict mode if not already set by checking for class declaration
80+
if "isStrict" not in opts:
81+
opts["isStrict"] = full_string[:offset].find("class ") >= 0
82+
if opts.get("isStrict"):
83+
return target.replace("arguments", "[]")
84+
return target
85+
86+
7087
def add_suffix_non_prop(suffix: str) -> TransformationAction:
7188
"""
7289
Create a rewrite_function which add a `suffix` to the match str.
7390
The suffix is added only if the match is not preceded by `.` or `$`.
91+
Applies strict mode transformation to handle 'arguments' keyword.
7492
"""
7593

76-
def f(m_object: re.Match[str], _opts: dict[str, Any] | None) -> str:
94+
def f(m_object: re.Match[str], opts: dict[str, Any] | None) -> str:
7795
offset = m_object.start()
78-
if offset > 0 and m_object.string[offset - 1] in ".$":
96+
full_string = m_object.string
97+
if offset > 0 and full_string[offset - 1] in ".$":
7998
return m_object[0]
80-
return m_object[0] + suffix
99+
return m_object[0] + remove_args_if_strict(suffix, opts, offset, full_string)
81100

82101
return f
83102

@@ -145,7 +164,7 @@ def create_js_rules() -> list[TransformationRule]:
145164
# be set.
146165
check_loc = (
147166
"((self.__WB_check_loc && self.__WB_check_loc(location, arguments)) || "
148-
"{}).href = "
167+
"{}).maybeHref = "
149168
)
150169

151170
# This will replace `eval(...)`.
@@ -258,6 +277,30 @@ def _get_module_decl(self, local_decls: Iterable[str]) -> str:
258277
f"""import {{ {", ".join(local_decls)} }} from "{wb_module_decl_url}";\n"""
259278
)
260279

280+
def _detect_strict_mode(self, text: str) -> bool:
281+
"""
282+
Detect if the JavaScript code is in strict mode.
283+
284+
Returns True if the code contains:
285+
- "use strict"; directive
286+
- import statements
287+
- export statements
288+
- class declarations
289+
"""
290+
# Check for "use strict"; directive
291+
if '"use strict";' in text or "'use strict';" in text:
292+
return True
293+
294+
# Check for import or export statements
295+
if re.search(r"(?:^|\s)(?:im|ex)port\s+", text):
296+
return True
297+
298+
# Check for class declaration
299+
if re.search(r"\bclass\s+", text):
300+
return True
301+
302+
return False
303+
261304
def rewrite(self, text: str | bytes, opts: dict[str, Any] | None = None) -> str:
262305
"""
263306
Rewrite the js code in `text`.
@@ -269,6 +312,14 @@ def rewrite(self, text: str | bytes, opts: dict[str, Any] | None = None) -> str:
269312

270313
is_module = opts.get("isModule", False)
271314

315+
# Detect and set strict mode
316+
# Modules are always strict mode
317+
if is_module:
318+
opts["isStrict"] = True
319+
elif "isStrict" not in opts:
320+
# Detect strict mode from the code itself
321+
opts["isStrict"] = self._detect_strict_mode(text)
322+
272323
rules = REWRITE_JS_RULES[:]
273324

274325
if is_module:

tests/rewriting/test_js_rewriting.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def wrap_script(text: str) -> str:
184184
input_="location = http://example.com/",
185185
expected="location = ((self.__WB_check_loc && "
186186
"self.__WB_check_loc(location, argument"
187-
"s)) || {}).href = http://example.com/",
187+
"s)) || {}).maybeHref = http://example.com/",
188188
),
189189
WrappedTestContent(
190190
input_='location => "http://example.com/"',
@@ -193,16 +193,10 @@ def wrap_script(text: str) -> str:
193193
WrappedTestContent(
194194
input_=" location = http://example.com/2",
195195
expected=" location = ((self.__WB_check_loc && "
196-
"self.__WB_check_loc(location, arguments)) || {}).href = "
196+
"self.__WB_check_loc(location, arguments)) || {}).maybeHref = "
197197
"http://example.com/2",
198198
),
199199
WrappedTestContent(input_="func(location = 0)", expected="func(location = 0)"),
200-
WrappedTestContent(
201-
input_=" location = http://example.com/2",
202-
expected=" location = ((self.__WB_check_loc && "
203-
"self.__WB_check_loc(location, arguments)) || {}).href = "
204-
"http://example.com/2",
205-
),
206200
WrappedTestContent(input_="window.eval(a)", expected="window.eval(a)"),
207201
WrappedTestContent(
208202
input_="x = window.eval; x(a);", expected="x = window.eval; x(a);"
@@ -239,7 +233,7 @@ class A {}
239233
var D = 3;
240234
241235
location = ((self.__WB_check_loc && """
242-
"self.__WB_check_loc(location, arguments)) || {}).href "
236+
"self.__WB_check_loc(location, [])) || {}).maybeHref "
243237
"""= "http://example.com/2" """,
244238
),
245239
WrappedTestContent(input_=" var self ", expected=" let self "),

0 commit comments

Comments
 (0)