tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

wait.py (6140B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this
      3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      4 
      5 import collections
      6 import sys
      7 import time
      8 
      9 from . import errors
     10 
     11 DEFAULT_TIMEOUT = 5
     12 DEFAULT_INTERVAL = 0.1
     13 
     14 
     15 class Wait:
     16    """An explicit conditional utility class for waiting until a condition
     17    evaluates to true or not null.
     18 
     19    This will repeatedly evaluate a condition in anticipation for a
     20    truthy return value, or its timeout to expire, or its waiting
     21    predicate to become true.
     22 
     23    A `Wait` instance defines the maximum amount of time to wait for a
     24    condition, as well as the frequency with which to check the
     25    condition.  Furthermore, the user may configure the wait to ignore
     26    specific types of exceptions whilst waiting, such as
     27    `errors.NoSuchElementException` when searching for an element on
     28    the page.
     29 
     30    """
     31 
     32    def __init__(
     33        self,
     34        marionette,
     35        timeout=None,
     36        interval=None,
     37        ignored_exceptions=None,
     38        clock=None,
     39    ):
     40        """Configure the Wait instance to have a custom timeout, interval, and
     41        list of ignored exceptions.  Optionally a different time
     42        implementation than the one provided by the standard library
     43        (time) can also be provided.
     44 
     45        Sample usage::
     46 
     47            # Wait 30 seconds for window to open, checking for its presence once
     48            # every 5 seconds.
     49            wait = Wait(marionette, timeout=30, interval=5,
     50                        ignored_exceptions=errors.NoSuchWindowException)
     51            window = wait.until(lambda m: m.switch_to_window(42))
     52 
     53        :param marionette: The input value to be provided to
     54            conditions, usually a Marionette instance.
     55 
     56        :param timeout: How long to wait for the evaluated condition
     57            to become true.  The default timeout is
     58            `wait.DEFAULT_TIMEOUT`.
     59 
     60        :param interval: How often the condition should be evaluated.
     61            In reality the interval may be greater as the cost of
     62            evaluating the condition function. If that is not the case the
     63            interval for the next condition function call is shortend to keep
     64            the original interval sequence as best as possible.
     65            The default polling interval is `wait.DEFAULT_INTERVAL`.
     66 
     67        :param ignored_exceptions: Ignore specific types of exceptions
     68            whilst waiting for the condition.  Any exceptions not
     69            whitelisted will be allowed to propagate, terminating the
     70            wait.
     71 
     72        :param clock: Allows overriding the use of the runtime's
     73            default time library.  See `wait.SystemClock` for
     74            implementation details.
     75 
     76        """
     77 
     78        self.marionette = marionette
     79        self.timeout = timeout if timeout is not None else DEFAULT_TIMEOUT
     80        self.interval = interval if interval is not None else DEFAULT_INTERVAL
     81        self.clock = clock or SystemClock()
     82        self.end = self.clock.now + self.timeout
     83 
     84        exceptions = []
     85        if ignored_exceptions is not None:
     86            if isinstance(ignored_exceptions, collections.abc.Iterable):
     87                exceptions.extend(iter(ignored_exceptions))
     88            else:
     89                exceptions.append(ignored_exceptions)
     90        self.exceptions = tuple(set(exceptions))
     91 
     92    def until(self, condition, is_true=None, message=""):
     93        """Repeatedly runs condition until its return value evaluates to true,
     94        or its timeout expires or the predicate evaluates to true.
     95 
     96        This will poll at the given interval until the given timeout
     97        is reached, or the predicate or conditions returns true.  A
     98        condition that returns null or does not evaluate to true will
     99        fully elapse its timeout before raising an
    100        `errors.TimeoutException`.
    101 
    102        If an exception is raised in the condition function and it's
    103        not ignored, this function will raise immediately.  If the
    104        exception is ignored, it will continue polling for the
    105        condition until it returns successfully or a
    106        `TimeoutException` is raised.
    107 
    108        :param condition: A callable function whose return value will
    109            be returned by this function if it evaluates to true.
    110 
    111        :param is_true: An optional predicate that will terminate and
    112            return when it evaluates to False.  It should be a
    113            function that will be passed clock and an end time.  The
    114            default predicate will terminate a wait when the clock
    115            elapses the timeout.
    116 
    117        :param message: An optional message to include in the
    118            exception's message if this function times out.
    119 
    120        """
    121 
    122        rv = None
    123        last_exc = None
    124        until = is_true or until_pred
    125        start = self.clock.now
    126 
    127        while not until(self.clock, self.end):
    128            try:
    129                next = self.clock.now + self.interval
    130                rv = condition(self.marionette)
    131            except (KeyboardInterrupt, SystemExit):
    132                raise
    133            except self.exceptions:
    134                last_exc = sys.exc_info()
    135 
    136            # Re-adjust the interval depending on how long the callback
    137            # took to evaluate the condition
    138            interval_new = max(next - self.clock.now, 0)
    139 
    140            if not rv:
    141                self.clock.sleep(interval_new)
    142                continue
    143 
    144            if rv is not None:
    145                return rv
    146 
    147            self.clock.sleep(interval_new)
    148 
    149        if message:
    150            message = f" with message: {message}"
    151        else:
    152            message = ""
    153 
    154        elapsed = round(self.clock.now - start, 1)
    155        raise errors.TimeoutException(
    156            f"Timed out after {elapsed:.1f} seconds{message}",
    157            cause=last_exc,
    158        )
    159 
    160 
    161 def until_pred(clock, end):
    162    return clock.now >= end
    163 
    164 
    165 class SystemClock:
    166    def __init__(self):
    167        self._time = time
    168 
    169    def sleep(self, duration):
    170        self._time.sleep(duration)
    171 
    172    @property
    173    def now(self):
    174        return self._time.time()