From 5ee5110e292bb8e1e1393e76d3335ec7695487b9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 9 Sep 2026 01:52:15 +0000 Subject: [PATCH] fix(browser): drop OptimizationHints from --disable-features Chrome-for-Testing SEGV_ACCERRs under --headless=new on macOS arm64 when OptimizationHints is disabled. Keep MediaRouter and DialMediaRouteProvider. Fixes #2239 Co-authored-by: Zsanz3 --- crawl4ai/browser_manager.py | 12 +++-- tests/async/test_browser_memory.py | 20 ++++--- tests/regression/test_reg_browser.py | 45 ++++++++++++++++ tests/unit/test_browser_disable_features.py | 59 +++++++++++++++++++++ 4 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 tests/unit/test_browser_disable_features.py diff --git a/crawl4ai/browser_manager.py b/crawl4ai/browser_manager.py index f4ab0aa32..17d6f5eb3 100644 --- a/crawl4ai/browser_manager.py +++ b/crawl4ai/browser_manager.py @@ -85,8 +85,10 @@ def build_browser_flags(config: BrowserConfig) -> List[str]: "--force-color-profile=srgb", "--mute-audio", "--disable-background-timer-throttling", - # Memory-saving flags: disable unused Chrome features - "--disable-features=OptimizationHints,MediaRouter,DialMediaRouteProvider", + # Memory-saving flags: disable unused Chrome features. + # Do not disable OptimizationHints — it SEGV_ACCERRs Chrome-for-Testing + # under --headless=new on macOS arm64 (issue #2239). + "--disable-features=MediaRouter,DialMediaRouteProvider", "--disable-component-update", "--disable-domain-reliability", ] @@ -1102,8 +1104,10 @@ def _build_browser_args(self) -> dict: "--force-color-profile=srgb", "--mute-audio", "--disable-background-timer-throttling", - # Memory-saving flags: disable unused Chrome features - "--disable-features=OptimizationHints,MediaRouter,DialMediaRouteProvider", + # Memory-saving flags: disable unused Chrome features. + # Do not disable OptimizationHints — it SEGV_ACCERRs Chrome-for-Testing + # under --headless=new on macOS arm64 (issue #2239). + "--disable-features=MediaRouter,DialMediaRouteProvider", "--disable-component-update", "--disable-domain-reliability", # "--single-process", diff --git a/tests/async/test_browser_memory.py b/tests/async/test_browser_memory.py index cd1685d04..ee6ab58e1 100644 --- a/tests/async/test_browser_memory.py +++ b/tests/async/test_browser_memory.py @@ -187,8 +187,11 @@ async def test_memory_saving_flags_applied(test_server): assert any("max-old-space-size" in a for a in args_list), ( "memory_saving_mode=True should add V8 heap cap" ) - # Always-on flags should be present regardless - assert any("OptimizationHints" in a for a in args_list) + # Always-on flags should be present regardless. + # OptimizationHints must stay off the disable-list (issue #2239). + assert any("MediaRouter" in a for a in args_list) + assert any("DialMediaRouteProvider" in a for a in args_list) + assert not any("OptimizationHints" in a for a in args_list) async with AsyncWebCrawler(config=config_off) as crawler: bm = _bm(crawler) @@ -201,7 +204,9 @@ async def test_memory_saving_flags_applied(test_server): "memory_saving_mode=False should NOT add V8 heap cap" ) # Always-on flags should still be there - assert any("OptimizationHints" in a for a in args_list) + assert any("MediaRouter" in a for a in args_list) + assert any("DialMediaRouteProvider" in a for a in args_list) + assert not any("OptimizationHints" in a for a in args_list) # =========================================================================== @@ -210,15 +215,18 @@ async def test_memory_saving_flags_applied(test_server): @pytest.mark.asyncio async def test_always_on_flags_present(test_server): - """The 3 always-on memory flags should appear in _build_browser_args - even with default BrowserConfig.""" + """The always-on memory flags should appear in _build_browser_args + even with default BrowserConfig. OptimizationHints must not be disabled + (issue #2239 / Chrome-for-Testing SEGV_ACCERR under --headless=new).""" config = BrowserConfig(headless=True, verbose=False) async with AsyncWebCrawler(config=config) as crawler: browser_args = _bm(crawler)._build_browser_args() args_list = browser_args.get("args", browser_args) if isinstance(browser_args, dict) else browser_args assert any("disable-component-update" in a for a in args_list) assert any("disable-domain-reliability" in a for a in args_list) - assert any("OptimizationHints" in a for a in args_list) + assert any("MediaRouter" in a for a in args_list) + assert any("DialMediaRouteProvider" in a for a in args_list) + assert not any("OptimizationHints" in a for a in args_list) # =========================================================================== diff --git a/tests/regression/test_reg_browser.py b/tests/regression/test_reg_browser.py index dac55a841..cab2b28ed 100644 --- a/tests/regression/test_reg_browser.py +++ b/tests/regression/test_reg_browser.py @@ -131,6 +131,51 @@ async def test_browser_context_manager(local_server): # If we get here without exception, cleanup succeeded +# --------------------------------------------------------------------------- +# Launch flags +# --------------------------------------------------------------------------- + + +def _disable_feature_names(args): + """Every name listed in any --disable-features switch (not last-wins).""" + names = [] + for arg in args: + if arg.startswith("--disable-features="): + names.extend( + name.strip() + for name in arg.split("=", 1)[1].split(",") + if name.strip() + ) + return names + + +def test_disable_features_omits_optimization_hints(): + """Issue #2239: OptimizationHints in --disable-features crashes + Chrome-for-Testing under --headless=new on macOS arm64 (SEGV_ACCERR). + + MediaRouter and DialMediaRouteProvider must remain. #2219 merges + repeated --disable-features lists and previously asserted + OptimizationHints as a default that must survive; after this fix the + expected defaults are MediaRouter and DialMediaRouteProvider only. + """ + from crawl4ai.browser_manager import BrowserManager, ManagedBrowser + + for light_mode in (False, True): + config = BrowserConfig(headless=True, light_mode=light_mode) + for flags in ( + ManagedBrowser.build_browser_flags(config), + BrowserManager(browser_config=config)._build_browser_args()["args"], + ): + names = _disable_feature_names(flags) + assert "OptimizationHints" not in names, ( + f"light_mode={light_mode}: OptimizationHints must not be disabled" + ) + for expected in ("MediaRouter", "DialMediaRouteProvider"): + assert expected in names, ( + f"light_mode={light_mode} lost {expected}" + ) + + # --------------------------------------------------------------------------- # Viewport configuration # --------------------------------------------------------------------------- diff --git a/tests/unit/test_browser_disable_features.py b/tests/unit/test_browser_disable_features.py new file mode 100644 index 000000000..1b99249f4 --- /dev/null +++ b/tests/unit/test_browser_disable_features.py @@ -0,0 +1,59 @@ +"""Issue #2239: OptimizationHints must not appear in built --disable-features. + +Chrome-for-Testing 151+ SEGV_ACCERRs under --headless=new on macOS arm64 when +OptimizationHints is disabled. MediaRouter and DialMediaRouteProvider stay. + +These are flag-construction checks only — no browser or network required. + +#2219 merges repeated --disable-features switches and currently expects +OptimizationHints to survive that merge. After this change the surviving +defaults are MediaRouter and DialMediaRouteProvider only. +""" + +from crawl4ai import BrowserConfig +from crawl4ai.browser_manager import BrowserManager, ManagedBrowser + + +def _disable_feature_names(args): + """Every name listed in any --disable-features switch (not last-wins).""" + names = [] + for arg in args: + if arg.startswith("--disable-features="): + names.extend( + name.strip() + for name in arg.split("=", 1)[1].split(",") + if name.strip() + ) + return names + + +def test_build_browser_flags_omits_optimization_hints(): + config = BrowserConfig(headless=True) + names = _disable_feature_names(ManagedBrowser.build_browser_flags(config)) + assert "OptimizationHints" not in names + assert "MediaRouter" in names + assert "DialMediaRouteProvider" in names + + +def test_build_browser_args_omits_optimization_hints(): + config = BrowserConfig(headless=True) + args = BrowserManager(browser_config=config)._build_browser_args()["args"] + names = _disable_feature_names(args) + assert "OptimizationHints" not in names + assert "MediaRouter" in names + assert "DialMediaRouteProvider" in names + + +def test_light_mode_still_omits_optimization_hints(): + """light_mode appends BROWSER_DISABLE_OPTIONS; OptimizationHints must + not reappear there either. Aligns with #2219's merge expectations: + MediaRouter / DialMediaRouteProvider remain the crawl4ai defaults.""" + config = BrowserConfig(headless=True, light_mode=True) + for flags in ( + ManagedBrowser.build_browser_flags(config), + BrowserManager(browser_config=config)._build_browser_args()["args"], + ): + names = _disable_feature_names(flags) + assert "OptimizationHints" not in names + assert "MediaRouter" in names + assert "DialMediaRouteProvider" in names