Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 79 additions & 9 deletions tbselenium/tbbinary.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,86 @@
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
"""
TBBinary — lightweight wrapper around the Tor Browser (Firefox) process.

In Selenium < 4.40 this module subclassed ``FirefoxBinary``. That class was
deprecated in Selenium 4.x and fully removed in 4.40.0 (see
https://github.com/SeleniumHQ/selenium/blob/trunk/py/CHANGES).

class TBBinary(FirefoxBinary):
'''
Extend FirefoxBinary to better handle terminated browser processes.
'''
The modern Selenium API no longer requires a ``FirefoxBinary`` object:
``Options.binary`` accepts a plain filesystem path (str / ``pathlib.Path``),
and the ``Service`` object owns the geckodriver life-cycle. ``TBBinary`` is
therefore re-implemented as a standalone helper that only manages the *browser*
process itself — primarily to provide the ``kill()`` method that some callers
rely on when the browser becomes unresponsive.
"""

def kill(self):
"""Kill the browser.
from __future__ import annotations

This is useful when the browser is stuck.
import subprocess
from typing import IO, Optional


class TBBinary:
"""Manage the Tor Browser (Firefox) process independently of Selenium.

This class replaces the former ``FirefoxBinary``-based implementation and
is compatible with Selenium >= 4.40.0.

Parameters
----------
firefox_path:
Absolute path to the ``firefox`` binary inside the Tor Browser bundle.
log_file:
Optional writable file-like object that receives the browser's stdout
and stderr output. Pass ``None`` (the default) to discard output.
"""

def __init__(
self,
firefox_path: str,
log_file: Optional[IO[str]] = None,
) -> None:
self.firefox_path = firefox_path
self.log_file = log_file
self.process: Optional[subprocess.Popen] = None # type: ignore[type-arg]

# ------------------------------------------------------------------
# Process control
# ------------------------------------------------------------------

def launch_browser(self, profile: str, timeout: int = 30) -> None:
"""Start the Tor Browser process with a given Firefox profile.

Parameters
----------
profile:
Path to the Firefox profile directory that should be used.
timeout:
Unused; kept for API compatibility with callers that previously
passed a timeout to the legacy ``FirefoxBinary.launch_browser()``.
"""
sink = self.log_file or subprocess.DEVNULL
self.process = subprocess.Popen(
[self.firefox_path, "--profile", profile],
stdout=sink,
stderr=sink,
)

def kill(self) -> None:
"""Forcefully terminate the browser process.

Safe to call even if the process has already exited or was never
started — in those cases the method is a no-op.
"""
if self.process and self.process.poll() is None:
if self.process is not None and self.process.poll() is None:
self.process.kill()
self.process.wait()

# ------------------------------------------------------------------
# Context-manager support
# ------------------------------------------------------------------

def __enter__(self) -> "TBBinary":
return self

def __exit__(self, *args: object) -> None:
self.kill()
14 changes: 9 additions & 5 deletions tbselenium/tbdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,9 @@ def __init__(self,
log_path=tbb_logfile_path,
port=geckodriver_port
)
# options.binary is path to the Firefox binary and it can be a string
# or a FirefoxBinary object. If it's a string, it will be converted to
# a FirefoxBinary object.
# https://github.com/SeleniumHQ/selenium/blob/7cfd137085fcde932cd71af78642a15fd56fe1f1/py/selenium/webdriver/firefox/options.py#L54
# options.binary accepts a filesystem path (str) to the Firefox binary.
# FirefoxBinary was removed in Selenium 4.40.0; a plain string path is
# the correct way to configure the binary in modern Selenium.
self.options.binary = self.tbb_fx_binary_path
self.options.add_argument('--class')
self.options.add_argument('"Tor Browser"')
Expand Down Expand Up @@ -309,7 +308,12 @@ def export_env_vars(self):
prepend_to_env_var("PATH", self.tbb_browser_dir)

def get_tb_binary(self, logfile=None):
"""Return FirefoxBinary pointing to the TBB's firefox binary."""
"""Return a TBBinary instance pointing to the Tor Browser's Firefox binary.

Note: this method is not called during normal driver initialisation.
It is provided as a convenience for callers that need to manage the
browser process directly (e.g. to forcefully kill a stuck browser).
"""
tbb_logfile = open(logfile, 'a+') if logfile else None
return TBBinary(firefox_path=self.tbb_fx_binary_path,
log_file=tbb_logfile)
Expand Down