diff --git a/CHANGELOG.md b/CHANGELOG.md index 33f0a9f..410fda3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Do not URL-encode `=` and `,` characters when rewriting document URIs, fixing YouTube JS player breakage (#316) +- Fix two sources of memory leaks in JS and CSS rewriting (#322) ### Changed diff --git a/src/zimscraperlib/rewriting/css.py b/src/zimscraperlib/rewriting/css.py index 0544e03..507a2ca 100644 --- a/src/zimscraperlib/rewriting/css.py +++ b/src/zimscraperlib/rewriting/css.py @@ -39,8 +39,8 @@ class FallbackRegexCssRewriter(RxRewriter): parsing issue, not necessarily a bad CSS rule) """ + @staticmethod def __simple_transform( - self, m_object: re.Match[str], _opts: dict[str, Any] | None, url_rewriter: ArticleUrlRewriter, diff --git a/src/zimscraperlib/rewriting/js.py b/src/zimscraperlib/rewriting/js.py index 34d6f39..8d27a91 100644 --- a/src/zimscraperlib/rewriting/js.py +++ b/src/zimscraperlib/rewriting/js.py @@ -341,13 +341,21 @@ def rewrite(self, text: str | bytes, opts: dict[str, Any] | None = None) -> str: return new_text def _get_esm_import_rule(self) -> TransformationRule: + # Capture plain local values instead of closing over `self`: a closure that + # references `self` here would end up stored in `self.rules`, creating a + # self -> self.rules -> closure -> self reference cycle that only the cyclic + # GC (not refcounting) can collect. + url_rewriter = self.url_rewriter + base_href = self.base_href + notify_js_module = self.notify_js_module + def get_rewriten_import_url(url: str) -> str: """Rewrite the import URL This takes into account that the result must be a relative URL, i.e. it cannot be 'vendor.module.js' but must be './vendor.module.js'. """ - url = self.url_rewriter(url, base_href=self.base_href).rewriten_url + url = url_rewriter(url, base_href=base_href).rewriten_url if not ( url.startswith("/") or url.startswith("./") or url.startswith("../") ): @@ -359,10 +367,10 @@ def func( m_object: re.Match[str], _opts: dict[str, Any] | None = None ) -> str: def sub_funct(match: re.Match[str]) -> str: - if self.notify_js_module: - self.notify_js_module( - self.url_rewriter.get_item_path( - match.group(2), base_href=self.base_href + if notify_js_module: + notify_js_module( + url_rewriter.get_item_path( + match.group(2), base_href=base_href ) ) return (