예제 #1
0
def _parse_yaml_backends_dict(name, node):
    """Parse a dict definition for backends.

    Example:

    backends:
      QtWebKit: true
      QtWebEngine: Qt 5.9
    """
    str_to_backend = {
        'QtWebKit': usertypes.Backend.QtWebKit,
        'QtWebEngine': usertypes.Backend.QtWebEngine,
    }

    if node.keys() != str_to_backend.keys():
        _raise_invalid_node(name, 'backends', node)

    backends = []

    # The value associated to the key, and whether we should add that backend
    # or not.
    conditionals = {
        True: True,
        False: False,
        'Qt 5.8': qtutils.version_check('5.8'),
        'Qt 5.9': qtutils.version_check('5.9'),
    }
    for key in sorted(node.keys()):
        if conditionals[node[key]]:
            backends.append(str_to_backend[key])

    return backends
예제 #2
0
def _qtwebengine_args() -> typing.Iterator[str]:
    """Get the QtWebEngine arguments to use based on the config."""
    if not qtutils.version_check('5.11', compiled=False):
        # WORKAROUND equivalent to
        # https://codereview.qt-project.org/#/c/217932/
        # Needed for Qt < 5.9.5 and < 5.10.1
        yield '--disable-shared-workers'

    settings = {
        'qt.force_software_rendering': {
            'software-opengl': None,
            'qt-quick': None,
            'chromium': '--disable-gpu',
            'none': None,
        },
        'content.canvas_reading': {
            True: None,
            False: '--disable-reading-from-canvas',
        },
        'content.webrtc_ip_handling_policy': {
            'all-interfaces': None,
            'default-public-and-private-interfaces':
                '--force-webrtc-ip-handling-policy='
                'default_public_and_private_interfaces',
            'default-public-interface-only':
                '--force-webrtc-ip-handling-policy='
                'default_public_interface_only',
            'disable-non-proxied-udp':
                '--force-webrtc-ip-handling-policy='
                'disable_non_proxied_udp',
        },
        'qt.process_model': {
            'process-per-site-instance': None,
            'process-per-site': '--process-per-site',
            'single-process': '--single-process',
        },
        'qt.low_end_device_mode': {
            'auto': None,
            'always': '--enable-low-end-device-mode',
            'never': '--disable-low-end-device-mode',
        },
        'content.headers.referer': {
            'always': None,
            'never': '--no-referrers',
            'same-domain': '--reduced-referrer-granularity',
        }
    }  # type: typing.Dict[str, typing.Dict[typing.Any, typing.Optional[str]]]

    if not qtutils.version_check('5.11'):
        # On Qt 5.11, we can control this via QWebEngineSettings
        settings['content.autoplay'] = {
            True: None,
            False: '--autoplay-policy=user-gesture-required',
        }

    for setting, args in sorted(settings.items()):
        arg = args[config.instance.get(setting)]
        if arg is not None:
            yield arg
예제 #3
0
 def _on_load_started(self):
     """Clear search when a new load is started if needed."""
     if (qtutils.version_check('5.9') and
             not qtutils.version_check('5.9.2')):
         # WORKAROUND for
         # https://bugreports.qt.io/browse/QTBUG-61506
         self.search.clear()
     super()._on_load_started()
    def install(self, profile):
        """Install the handler for qute:// URLs on the given profile."""
        if QWebEngineUrlScheme is not None:
            assert QWebEngineUrlScheme.schemeByName(b'qute') is not None

        profile.installUrlSchemeHandler(b'qute', self)
        if (qtutils.version_check('5.11', compiled=False) and
                not qtutils.version_check('5.12', compiled=False)):
            # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
            profile.installUrlSchemeHandler(b'chrome-error', self)
            profile.installUrlSchemeHandler(b'chrome-extension', self)
예제 #5
0
def check_qt_version(args):
    """Check if the Qt version is recent enough."""
    from PyQt5.QtCore import qVersion
    from qutebrowser.utils import qtutils
    if qtutils.version_check('5.2.0', operator.lt):
        text = ("Fatal error: Qt and PyQt >= 5.2.0 are required, but {} is "
                "installed.".format(qVersion()))
        _die(text)
    elif args.backend == 'webengine' and qtutils.version_check('5.6.0',
                                                               operator.lt):
        text = ("Fatal error: Qt and PyQt >= 5.6.0 are required for "
                "QtWebEngine support, but {} is installed.".format(qVersion()))
        _die(text)
예제 #6
0
def _apply_platform_markers(config, item):
    """Apply a skip marker to a given item."""
    markers = [
        ('posix', not utils.is_posix, "Requires a POSIX os"),
        ('windows', not utils.is_windows, "Requires Windows"),
        ('linux', not utils.is_linux, "Requires Linux"),
        ('mac', not utils.is_mac, "Requires macOS"),
        ('not_mac', utils.is_mac, "Skipped on macOS"),
        ('not_frozen', getattr(sys, 'frozen', False),
         "Can't be run when frozen"),
        ('frozen', not getattr(sys, 'frozen', False),
         "Can only run when frozen"),
        ('ci', not ON_CI, "Only runs on CI."),
        ('no_ci', ON_CI, "Skipped on CI."),
        ('issue2478', utils.is_windows and config.webengine,
         "Broken with QtWebEngine on Windows"),
        ('issue3572',
         (qtutils.version_check('5.10', compiled=False, exact=True) or
          qtutils.version_check('5.10.1', compiled=False, exact=True)) and
         config.webengine and 'TRAVIS' in os.environ,
         "Broken with QtWebEngine with Qt 5.10 on Travis"),
        ('qtbug60673',
         qtutils.version_check('5.8') and
         not qtutils.version_check('5.10') and
         config.webengine,
         "Broken on webengine due to "
         "https://bugreports.qt.io/browse/QTBUG-60673"),
        ('unicode_locale', sys.getfilesystemencoding() == 'ascii',
         "Skipped because of ASCII locale"),
        ('qtwebkit6021_skip',
         version.qWebKitVersion and
         version.qWebKitVersion() == '602.1',
         "Broken on WebKit 602.1")
    ]

    for searched_marker, condition, default_reason in markers:
        marker = item.get_closest_marker(searched_marker)
        if not marker or not condition:
            continue

        if 'reason' in marker.kwargs:
            reason = '{}: {}'.format(default_reason, marker.kwargs['reason'])
            del marker.kwargs['reason']
        else:
            reason = default_reason + '.'
        skipif_marker = pytest.mark.skipif(condition, *marker.args,
                                           reason=reason, **marker.kwargs)
        item.add_marker(skipif_marker)
예제 #7
0
def get_fatal_crash_dialog(debug, data):
    """Get a fatal crash dialog based on a crash log.

    If the crash is a segfault in qt_mainloop and we're on an old Qt version
    this is a simple error dialog which lets the user know they should upgrade
    if possible.

    If it's anything else, it's a normal FatalCrashDialog with the possibility
    to report the crash.

    Args:
        debug: Whether the debug flag (--debug) was given.
        data: The crash log data.
    """
    errtype, frame = parse_fatal_stacktrace(data)
    if (qtutils.version_check('5.4') or errtype != 'Segmentation fault' or
            frame != 'qt_mainloop'):
        return FatalCrashDialog(debug, data)
    else:
        title = "qutebrowser was restarted after a fatal crash!"
        text = ("<b>qutebrowser was restarted after a fatal crash!</b><br/>"
                "Unfortunately, this crash occurred in Qt (the library "
                "qutebrowser uses), and your version ({}) is outdated - "
                "Qt 5.4 or later is recommended. Unfortuntately Debian and "
                "Ubuntu don't ship a newer version (yet?)...".format(
                    qVersion()))
        return QMessageBox(QMessageBox.Critical, title, text, QMessageBox.Ok)
예제 #8
0
def pytest_collection_modifyitems(config, items):
    """Apply @qtwebengine_* markers; skip unittests with QUTE_BDD_WEBENGINE."""
    vercheck = qtutils.version_check
    qtbug_54419_fixed = ((vercheck('5.6.2') and not vercheck('5.7.0')) or
                         qtutils.version_check('5.7.1') or
                         os.environ.get('QUTE_QTBUG54419_PATCHED', ''))

    markers = [
        ('qtwebengine_createWindow', 'Skipped because of QTBUG-54419',
         pytest.mark.skipif, not qtbug_54419_fixed and config.webengine),
        ('qtwebengine_todo', 'QtWebEngine TODO', pytest.mark.xfail,
         config.webengine),
        ('qtwebengine_skip', 'Skipped with QtWebEngine', pytest.mark.skipif,
         config.webengine),
        ('qtwebkit_skip', 'Skipped with QtWebKit', pytest.mark.skipif,
         not config.webengine),
        ('qtwebengine_flaky', 'Flaky with QtWebEngine', pytest.mark.skipif,
         config.webengine),
        ('qtwebengine_osx_xfail', 'Fails on OS X with QtWebEngine',
         pytest.mark.xfail, config.webengine and sys.platform == 'darwin'),
    ]

    for item in items:
        for name, prefix, pytest_mark, condition in markers:
            marker = item.get_marker(name)
            if marker and condition:
                if marker.args:
                    text = '{}: {}'.format(prefix, marker.args[0])
                else:
                    text = prefix
                item.add_marker(pytest_mark(condition, reason=text,
                                            **marker.kwargs))
def _get_suggested_filename(path):
    """Convert a path we got from chromium to a suggested filename.

    Chromium thinks we want to download stuff to ~/Download, so even if we
    don't, we get downloads with a suffix like (1) for files existing there.

    We simply strip the suffix off via regex.

    See https://bugreports.qt.io/browse/QTBUG-56978
    """
    filename = os.path.basename(path)

    suffix_re = re.compile(r"""
      \ ?  # Optional space between filename and suffix
      (
        # Numerical suffix
        \([0-9]+\)
      |
        # ISO-8601 suffix
        # https://cs.chromium.org/chromium/src/base/time/time_to_iso8601.cc
        \ -\ \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z
      )
      (?=\.|$)  # Begin of extension, or filename without extension
    """, re.VERBOSE)

    filename = suffix_re.sub('', filename)
    if not qtutils.version_check('5.9', compiled=False):
        # https://bugreports.qt.io/browse/QTBUG-58155
        filename = urllib.parse.unquote(filename)
        # Doing basename a *second* time because there could be a %2F in
        # there...
        filename = os.path.basename(filename)
    return filename
예제 #10
0
 def install(self, profile):
     """Install the handler for qute:// URLs on the given profile."""
     profile.installUrlSchemeHandler(b'qute', self)
     if qtutils.version_check('5.11', compiled=False):
         # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
         profile.installUrlSchemeHandler(b'chrome-error', self)
         profile.installUrlSchemeHandler(b'chrome-extension', self)
예제 #11
0
def normalize_line(line):
    line = line.rstrip('\n')
    line = re.sub('boundary="-+(=_qute|MultipartBoundary)-[0-9a-zA-Z-]+"',
                  'boundary="---=_qute-UUID"', line)
    line = re.sub('^-+(=_qute|MultipartBoundary)-[0-9a-zA-Z-]+$',
                  '-----=_qute-UUID', line)
    line = re.sub(r'localhost:\d{1,5}', 'localhost:(port)', line)
    if line.startswith('Date: '):
        line = 'Date: today'
    if line.startswith('Content-ID: '):
        line = 'Content-ID: 42'

    # Depending on Python's mimetypes module/the system's mime files, .js
    # files could be either identified as x-javascript or just javascript
    line = line.replace('Content-Type: application/x-javascript',
                        'Content-Type: application/javascript')

    # With QtWebKit and newer Werkzeug versions, we also get an encoding
    # specified.
    line = line.replace('javascript; charset=utf-8', 'javascript')

    # Added with Qt 5.11
    if (line.startswith('Snapshot-Content-Location: ') and
            not qtutils.version_check('5.11', compiled=False)):
        line = None

    return line
예제 #12
0
    def eventFilter(self, obj, event):
        """Act on ChildAdded events."""
        if event.type() == QEvent.ChildAdded:
            child = event.child()
            log.mouse.debug("{} got new child {}, installing filter".format(
                obj, child))
            assert obj is self._widget
            child.installEventFilter(self._filter)

            if qtutils.version_check('5.11', compiled=False, exact=True):
                # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076
                pass_modes = [usertypes.KeyMode.command,
                              usertypes.KeyMode.prompt,
                              usertypes.KeyMode.yesno]
                if modeman.instance(self._win_id).mode not in pass_modes:
                    tabbed_browser = objreg.get('tabbed-browser',
                                                scope='window',
                                                window=self._win_id)
                    current_index = tabbed_browser.widget.currentIndex()
                    try:
                        widget_index = tabbed_browser.widget.indexOf(
                            self._widget.parent())
                    except RuntimeError:
                        widget_index = -1
                    if current_index == widget_index:
                        QTimer.singleShot(0, self._widget.setFocus)

        elif event.type() == QEvent.ChildRemoved:
            child = event.child()
            log.mouse.debug("{}: removed child {}".format(obj, child))

        return False
예제 #13
0
def actute_warning():
    """Display a warning about the dead_actute issue if needed."""
    # WORKAROUND (remove this when we bump the requirements to 5.3.0)
    # Non Linux OS' aren't affected
    if not sys.platform.startswith('linux'):
        return
    # If no compose file exists for some reason, we're not affected
    if not os.path.exists('/usr/share/X11/locale/en_US.UTF-8/Compose'):
        return
    # Qt >= 5.3 doesn't seem to be affected
    try:
        if qtutils.version_check('5.3.0'):
            return
    except ValueError:
        pass
    try:
        with open('/usr/share/X11/locale/en_US.UTF-8/Compose', 'r',
                  encoding='utf-8') as f:
            for line in f:
                if '<dead_actute>' in line:
                    if sys.stdout is not None:
                        sys.stdout.flush()
                    print("Note: If you got a 'dead_actute' warning above, "
                          "that is not a bug in qutebrowser! See "
                          "https://bugs.freedesktop.org/show_bug.cgi?id=69476 "
                          "for details.")
                    break
    except OSError:
        log.init.exception("Failed to read Compose file")
예제 #14
0
    def __init__(self, win_id, tab_id, tab, parent=None):
        super().__init__(parent)
        if sys.platform == 'darwin' and qtutils.version_check('5.4'):
            # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948
            # See https://github.com/The-Compiler/qutebrowser/issues/462
            self.setStyle(QStyleFactory.create('Fusion'))
        self.tab = tab
        self.win_id = win_id
        self._check_insertmode = False
        self.scroll_pos = (-1, -1)
        self._old_scroll_pos = (-1, -1)
        self._ignore_wheel_event = False
        self._set_bg_color()
        self._tab_id = tab_id

        page = self._init_page()
        hintmanager = hints.HintManager(win_id, self._tab_id, self)
        hintmanager.mouse_event.connect(self.on_mouse_event)
        hintmanager.start_hinting.connect(page.on_start_hinting)
        hintmanager.stop_hinting.connect(page.on_stop_hinting)
        objreg.register('hintmanager', hintmanager, scope='tab', window=win_id,
                        tab=tab_id)
        mode_manager = objreg.get('mode-manager', scope='window',
                                  window=win_id)
        mode_manager.entered.connect(self.on_mode_entered)
        mode_manager.left.connect(self.on_mode_left)
        if config.get('input', 'rocker-gestures'):
            self.setContextMenuPolicy(Qt.PreventContextMenu)
        objreg.get('config').changed.connect(self.on_config_changed)
예제 #15
0
def init():
    """Disable insecure SSL ciphers on old Qt versions."""
    if not qtutils.version_check("5.3.0"):
        # Disable weak SSL ciphers.
        # See https://codereview.qt-project.org/#/c/75943/
        good_ciphers = [c for c in QSslSocket.supportedCiphers() if c.usedBits() >= 128]
        QSslSocket.setDefaultCiphers(good_ciphers)
예제 #16
0
    def _check_initiator(self, job):
        """Check whether the initiator of the job should be allowed.

        Only the browser itself or qute:// pages should access any of those
        URLs. The request interceptor further locks down qute://settings/set.

        Args:
            job: QWebEngineUrlRequestJob

        Return:
            True if the initiator is allowed, False if it was blocked.
        """
        try:
            initiator = job.initiator()
        except AttributeError:
            # Added in Qt 5.11
            return True

        if initiator == QUrl('null') and not qtutils.version_check('5.12'):
            # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-70421
            return True

        if initiator.isValid() and initiator.scheme() != 'qute':
            log.misc.warning("Blocking malicious request from {} to {}".format(
                initiator.toDisplayString(),
                job.requestUrl().toDisplayString()))
            job.fail(QWebEngineUrlRequestJob.RequestDenied)
            return False

        return True
예제 #17
0
파일: spell.py 프로젝트: mehak/qutebrowser
def dictionary_dir(old=False):
    """Return the path (str) to the QtWebEngine's dictionaries directory."""
    if qtutils.version_check('5.10', compiled=False) and not old:
        datapath = standarddir.data()
    else:
        datapath = QLibraryInfo.location(QLibraryInfo.DataPath)
    return os.path.join(datapath, 'qtwebengine_dictionaries')
예제 #18
0
def inject_userscripts():
    """Register user JavaScript files with the global profiles."""
    # The Greasemonkey metadata block support in QtWebEngine only starts at
    # Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in response
    # to urlChanged.
    if not qtutils.version_check('5.8'):
        return

    # Since we are inserting scripts into profile.scripts they won't
    # just get replaced by new gm scripts like if we were injecting them
    # ourselves so we need to remove all gm scripts, while not removing
    # any other stuff that might have been added. Like the one for
    # stylesheets.
    greasemonkey = objreg.get('greasemonkey')
    for profile in [default_profile, private_profile]:
        scripts = profile.scripts()
        for script in scripts.toList():
            if script.name().startswith("GM-"):
                log.greasemonkey.debug('Removing script: {}'
                                       .format(script.name()))
                removed = scripts.remove(script)
                assert removed, script.name()

        # Then add the new scripts.
        for script in greasemonkey.all_scripts():
            # @run-at (and @include/@exclude/@match) is parsed by
            # QWebEngineScript.
            new_script = QWebEngineScript()
            new_script.setWorldId(QWebEngineScript.MainWorld)
            new_script.setSourceCode(script.code())
            new_script.setName("GM-{}".format(script.name))
            new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
            log.greasemonkey.debug('adding script: {}'
                                   .format(new_script.name()))
            scripts.insert(new_script)
예제 #19
0
    def __init__(self, socketname, parent=None):
        """Start the IPC server and listen to commands.

        Args:
            socketname: The socketname to use.
            parent: The parent to be used.
        """
        super().__init__(parent)
        self.ignored = False
        self._socketname = socketname

        self._timer = usertypes.Timer(self, 'ipc-timeout')
        self._timer.setInterval(READ_TIMEOUT)
        self._timer.timeout.connect(self.on_timeout)

        if os.name == 'nt':  # pragma: no coverage
            self._atime_timer = None
        else:
            self._atime_timer = usertypes.Timer(self, 'ipc-atime')
            self._atime_timer.setInterval(ATIME_INTERVAL)
            self._atime_timer.timeout.connect(self.update_atime)
            self._atime_timer.setTimerType(Qt.VeryCoarseTimer)

        self._server = QLocalServer(self)
        self._server.newConnection.connect(self.handle_connection)

        self._socket = None
        self._socketopts_ok = os.name == 'nt' or qtutils.version_check('5.4')
        if self._socketopts_ok:  # pragma: no cover
            # If we use setSocketOptions on Unix with Qt < 5.4, we get a
            # NameError while listening...
            log.ipc.debug("Calling setSocketOptions")
            self._server.setSocketOptions(QLocalServer.UserAccessOption)
        else:  # pragma: no cover
            log.ipc.debug("Not calling setSocketOptions")
예제 #20
0
    def __init__(self, *, win_id, private, parent=None):
        if private:
            assert not qtutils.is_single_process()
        super().__init__(parent)
        self.widget = tabwidget.TabWidget(win_id, parent=self)
        self._win_id = win_id
        self._tab_insert_idx_left = 0
        self._tab_insert_idx_right = -1
        self.shutting_down = False
        self.widget.tabCloseRequested.connect(self.on_tab_close_requested)
        self.widget.new_tab_requested.connect(self.tabopen)
        self.widget.currentChanged.connect(self.on_current_changed)
        self.cur_fullscreen_requested.connect(self.widget.tabBar().maybe_hide)
        self.widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223
        if qtutils.version_check('5.10', compiled=False):
            self.cur_load_finished.connect(self._leave_modes_on_load)
        else:
            self.cur_load_started.connect(self._leave_modes_on_load)

        self._undo_stack = []
        self._filter = signalfilter.SignalFilter(win_id, self)
        self._now_focused = None
        self.search_text = None
        self.search_options = {}
        self._local_marks = {}
        self._global_marks = {}
        self.default_window_icon = self.widget.window().windowIcon()
        self.is_private = private
        config.instance.changed.connect(self._on_config_changed)
예제 #21
0
    def _on_renderer_process_terminated(self, tab, status, code):
        """Show an error when a renderer process terminated."""
        if status == browsertab.TerminationStatus.normal:
            return

        messages = {
            browsertab.TerminationStatus.abnormal:
                "Renderer process exited with status {}".format(code),
            browsertab.TerminationStatus.crashed:
                "Renderer process crashed",
            browsertab.TerminationStatus.killed:
                "Renderer process was killed",
            browsertab.TerminationStatus.unknown:
                "Renderer process did not start",
        }
        msg = messages[status]

        def show_error_page(html):
            tab.set_html(html)
            log.webview.error(msg)

        if qtutils.version_check('5.9', compiled=False):
            url_string = tab.url(requested=True).toDisplayString()
            error_page = jinja.render(
                'error.html', title="Error loading {}".format(url_string),
                url=url_string, error=msg)
            QTimer.singleShot(100, lambda: show_error_page(error_page))
        else:
            # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58698
            message.error(msg)
            self._remove_tab(tab, crashed=True)
            if self.count() == 0:
                self.tabopen(QUrl('about:blank'))
예제 #22
0
def init(args):
    """Initialize the global QWebSettings."""
    if args.enable_webengine_inspector:
        os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port())

    # Workaround for a black screen with some setups
    # https://github.com/spyder-ide/spyder/issues/3226
    if not os.environ.get('QUTE_NO_OPENGL_WORKAROUND'):
        # Hide "No OpenGL_accelerate module loaded: ..." message
        logging.getLogger('OpenGL.acceleratesupport').propagate = False
        try:
            from OpenGL import GL  # pylint: disable=unused-variable
        except ImportError:
            pass
        else:
            log.misc.debug("Imported PyOpenGL as workaround")

    _init_profiles()

    # We need to do this here as a WORKAROUND for
    # https://bugreports.qt.io/browse/QTBUG-58650
    if not qtutils.version_check('5.9'):
        PersistentCookiePolicy().set(config.get('content', 'cookies-store'))
    Attribute(QWebEngineSettings.FullScreenSupportEnabled).set(True)

    websettings.init_mappings(MAPPINGS)
    objreg.get('config').changed.connect(update_settings)
예제 #23
0
    def __init__(self, *, win_id, tab_id, tab, private, parent=None):
        super().__init__(parent)
        if sys.platform == 'darwin' and qtutils.version_check('5.4'):
            # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948
            # See https://github.com/qutebrowser/qutebrowser/issues/462
            self.setStyle(QStyleFactory.create('Fusion'))
        # FIXME:qtwebengine this is only used to set the zoom factor from
        # the QWebPage - we should get rid of it somehow (signals?)
        self.tab = tab
        self._tabdata = tab.data
        self.win_id = win_id
        self.scroll_pos = (-1, -1)
        self._old_scroll_pos = (-1, -1)
        self._set_bg_color()
        self._tab_id = tab_id

        page = webpage.BrowserPage(win_id=self.win_id, tab_id=self._tab_id,
                                   tabdata=tab.data, private=private,
                                   parent=self)

        try:
            page.setVisibilityState(
                QWebPage.VisibilityStateVisible if self.isVisible()
                else QWebPage.VisibilityStateHidden)
        except AttributeError:
            pass

        self.setPage(page)

        mode_manager = objreg.get('mode-manager', scope='window',
                                  window=win_id)
        mode_manager.entered.connect(self.on_mode_entered)
        mode_manager.left.connect(self.on_mode_left)
        objreg.get('config').changed.connect(self._set_bg_color)
예제 #24
0
def check_qt_version():
    """Check if the Qt version is recent enough."""
    from PyQt5.QtCore import qVersion
    from qutebrowser.utils import qtutils

    if qtutils.version_check("5.2.0", operator.lt):
        text = "Fatal error: Qt and PyQt >= 5.2.0 are required, but {} is " "installed.".format(qVersion())
        _die(text)
예제 #25
0
def check_qt_version(backend):
    """Check if the Qt version is recent enough."""
    from PyQt5.QtCore import PYQT_VERSION, PYQT_VERSION_STR
    from qutebrowser.utils import qtutils, version
    if (not qtutils.version_check('5.2.0', strict=True) or
            PYQT_VERSION < 0x050200):
        text = ("Fatal error: Qt and PyQt >= 5.2.0 are required, but Qt {} / "
                "PyQt {} is installed.".format(version.qt_version(),
                                               PYQT_VERSION_STR))
        _die(text)
    elif (backend == 'webengine' and (
            not qtutils.version_check('5.7.1', strict=True) or
            PYQT_VERSION < 0x050700)):
        text = ("Fatal error: Qt >= 5.7.1 and PyQt >= 5.7 are required for "
                "QtWebEngine support, but Qt {} / PyQt {} is installed."
                .format(version.qt_version(), PYQT_VERSION_STR))
        _die(text)
예제 #26
0
 def shutdown(self):
     self.shutting_down.emit()
     self.action.exit_fullscreen()
     if qtutils.version_check('5.8', exact=True):
         # WORKAROUND for
         # https://bugreports.qt.io/browse/QTBUG-58563
         self.search.clear()
     self._widget.shutdown()
    def _check_initiator(self, job):
        """Check whether the initiator of the job should be allowed.

        Only the browser itself or qute:// pages should access any of those
        URLs. The request interceptor further locks down qute://settings/set.

        Args:
            job: QWebEngineUrlRequestJob

        Return:
            True if the initiator is allowed, False if it was blocked.
        """
        try:
            initiator = job.initiator()
            request_url = job.requestUrl()
        except AttributeError:
            # Added in Qt 5.11
            return True

        # https://codereview.qt-project.org/#/c/234849/
        is_opaque = initiator == QUrl('null')
        target = request_url.scheme(), request_url.host()

        if is_opaque and not qtutils.version_check('5.12'):
            # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-70421
            # When we don't register the qute:// scheme, all requests are
            # flagged as opaque.
            return True

        if (target == ('qute', 'testdata') and
                is_opaque and
                qtutils.version_check('5.12')):
            # Allow requests to qute://testdata, as this is needed in Qt 5.12
            # for all tests to work properly. No qute://testdata handler is
            # installed outside of tests.
            return True

        if initiator.isValid() and initiator.scheme() != 'qute':
            log.misc.warning("Blocking malicious request from {} to {}".format(
                initiator.toDisplayString(),
                request_url.toDisplayString()))
            job.fail(QWebEngineUrlRequestJob.RequestDenied)
            return False

        return True
예제 #28
0
 def _set_cache_size(self):
     """Set the cache size based on the config."""
     size = config.val.content.cache.size
     if size is None:
         size = 1024 * 1024 * 50  # default from QNetworkDiskCachePrivate
     # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-59909
     if not qtutils.version_check('5.9', compiled=False):
         size = 0  # pragma: no cover
     self.setMaximumCacheSize(size)
예제 #29
0
    def expose(self, web_tab):
        """Expose the web view if needed.

        On QtWebKit, or Qt < 5.11 on QtWebEngine, we need to show the tab for
        selections to work properly.
        """
        if (web_tab.backend == usertypes.Backend.QtWebKit or
                not qtutils.version_check('5.11', compiled=False)):
            web_tab.container.expose()
예제 #30
0
 def __init__(self, win_id, parent=None):
     super().__init__(parent)
     if sys.platform == 'darwin' and qtutils.version_check('5.4'):
         # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948
         # See https://github.com/The-Compiler/qutebrowser/issues/462
         self.setStyle(QStyleFactory.create('Fusion'))
     self.win_id = win_id
     self.load_status = LoadStatus.none
     self._check_insertmode = False
     self.inspector = None
     self.scroll_pos = (-1, -1)
     self.statusbar_message = ''
     self._old_scroll_pos = (-1, -1)
     self._zoom = None
     self._has_ssl_errors = False
     self._ignore_wheel_event = False
     self.keep_icon = False
     self.search_text = None
     self.search_flags = 0
     self.selection_enabled = False
     self.init_neighborlist()
     self._set_bg_color()
     cfg = objreg.get('config')
     cfg.changed.connect(self.init_neighborlist)
     # For some reason, this signal doesn't get disconnected automatically
     # when the WebView is destroyed on older PyQt versions.
     # See https://github.com/The-Compiler/qutebrowser/issues/390
     self.destroyed.connect(functools.partial(
         cfg.changed.disconnect, self.init_neighborlist))
     self._cur_url = None
     self.cur_url = QUrl()
     self.progress = 0
     self.registry = objreg.ObjectRegistry()
     self.tab_id = next(tab_id_gen)
     tab_registry = objreg.get('tab-registry', scope='window',
                               window=win_id)
     tab_registry[self.tab_id] = self
     objreg.register('webview', self, registry=self.registry)
     page = self._init_page()
     hintmanager = hints.HintManager(win_id, self.tab_id, self)
     hintmanager.mouse_event.connect(self.on_mouse_event)
     hintmanager.start_hinting.connect(page.on_start_hinting)
     hintmanager.stop_hinting.connect(page.on_stop_hinting)
     objreg.register('hintmanager', hintmanager, registry=self.registry)
     mode_manager = objreg.get('mode-manager', scope='window',
                               window=win_id)
     mode_manager.entered.connect(self.on_mode_entered)
     mode_manager.left.connect(self.on_mode_left)
     self.viewing_source = False
     self.setZoomFactor(float(config.get('ui', 'default-zoom')) / 100)
     self._default_zoom_changed = False
     if config.get('input', 'rocker-gestures'):
         self.setContextMenuPolicy(Qt.PreventContextMenu)
     self.urlChanged.connect(self.on_url_changed)
     self.loadProgress.connect(lambda p: setattr(self, 'progress', p))
     objreg.get('config').changed.connect(self.on_config_changed)
예제 #31
0
    'fonts.web.family.cursive':
    FontFamilySetter(QWebEngineSettings.CursiveFont),
    'fonts.web.family.fantasy':
    FontFamilySetter(QWebEngineSettings.FantasyFont),
    'fonts.web.size.minimum':
    Setter(QWebEngineSettings.setFontSize,
           args=[QWebEngineSettings.MinimumFontSize]),
    'fonts.web.size.minimum_logical':
    Setter(QWebEngineSettings.setFontSize,
           args=[QWebEngineSettings.MinimumLogicalFontSize]),
    'fonts.web.size.default':
    Setter(QWebEngineSettings.setFontSize,
           args=[QWebEngineSettings.DefaultFontSize]),
    'fonts.web.size.default_fixed':
    Setter(QWebEngineSettings.setFontSize,
           args=[QWebEngineSettings.DefaultFixedFontSize]),
    'scrolling.smooth':
    Attribute(QWebEngineSettings.ScrollAnimatorEnabled),
}

try:
    MAPPINGS['content.print_element_backgrounds'] = Attribute(
        QWebEngineSettings.PrintElementBackgrounds)
except AttributeError:
    # Added in Qt 5.8
    pass

if qtutils.version_check('5.9'):
    # https://bugreports.qt.io/browse/QTBUG-58650
    MAPPINGS['content.cookies.store'] = PersistentCookiePolicy()
예제 #32
0
def pytest_collection_modifyitems(config, items):
    """Handle custom markers.

    pytest hook called after collection has been performed.

    Adds a marker named "gui" which can be used to filter gui tests from the
    command line.

    For example:

        pytest -m "not gui"  # run all tests except gui tests
        pytest -m "gui"  # run only gui tests

    It also handles the platform specific markers by translating them to skipif
    markers.

    Args:
        items: list of _pytest.main.Node items, where each item represents
               a python test that will be executed.

    Reference:
        http://pytest.org/latest/plugins.html
    """
    remaining_items = []
    deselected_items = []

    for item in items:
        deselected = False

        if 'qapp' in getattr(item, 'fixturenames', ()):
            item.add_marker('gui')

        if hasattr(item, 'module'):
            module_path = os.path.relpath(
                item.module.__file__,
                os.path.commonprefix([__file__, item.module.__file__]))

            module_root_dir = module_path.split(os.sep)[0]
            assert module_root_dir in ['end2end', 'unit', 'helpers',
                                       'test_conftest.py']
            if module_root_dir == 'end2end':
                item.add_marker(pytest.mark.end2end)
            elif os.environ.get('QUTE_BDD_WEBENGINE', ''):
                deselected = True

        _apply_platform_markers(item)
        if item.get_marker('xfail_norun'):
            item.add_marker(pytest.mark.xfail(run=False))
        if item.get_marker('js_prompt'):
            if config.webengine:
                js_prompt_pyqt_version = 0x050700
            else:
                js_prompt_pyqt_version = 0x050300
            item.add_marker(pytest.mark.skipif(
                PYQT_VERSION <= js_prompt_pyqt_version,
                reason='JS prompts are not supported with this PyQt version'))
        if item.get_marker('issue2183'):
            item.add_marker(pytest.mark.xfail(
                config.webengine and qtutils.version_check('5.7.1'),
                reason='https://github.com/The-Compiler/qutebrowser/issues/'
                       '2183'))

        if deselected:
            deselected_items.append(item)
        else:
            remaining_items.append(item)

    config.hook.pytest_deselected(items=deselected_items)
    items[:] = remaining_items
예제 #33
0
class RequestInterceptor(QWebEngineUrlRequestInterceptor):
    """Handle ad blocking and custom headers."""

    def __init__(self, parent=None):
        super().__init__(parent)
        # This dict should be from QWebEngine Resource Types to qutebrowser
        # extension ResourceTypes. If a ResourceType is added to Qt, this table
        # should be updated too.
        self._resource_types = {
            QWebEngineUrlRequestInfo.ResourceTypeMainFrame:
                interceptors.ResourceType.main_frame,
            QWebEngineUrlRequestInfo.ResourceTypeSubFrame:
                interceptors.ResourceType.sub_frame,
            QWebEngineUrlRequestInfo.ResourceTypeStylesheet:
                interceptors.ResourceType.stylesheet,
            QWebEngineUrlRequestInfo.ResourceTypeScript:
                interceptors.ResourceType.script,
            QWebEngineUrlRequestInfo.ResourceTypeImage:
                interceptors.ResourceType.image,
            QWebEngineUrlRequestInfo.ResourceTypeFontResource:
                interceptors.ResourceType.font_resource,
            QWebEngineUrlRequestInfo.ResourceTypeSubResource:
                interceptors.ResourceType.sub_resource,
            QWebEngineUrlRequestInfo.ResourceTypeObject:
                interceptors.ResourceType.object,
            QWebEngineUrlRequestInfo.ResourceTypeMedia:
                interceptors.ResourceType.media,
            QWebEngineUrlRequestInfo.ResourceTypeWorker:
                interceptors.ResourceType.worker,
            QWebEngineUrlRequestInfo.ResourceTypeSharedWorker:
                interceptors.ResourceType.shared_worker,
            QWebEngineUrlRequestInfo.ResourceTypePrefetch:
                interceptors.ResourceType.prefetch,
            QWebEngineUrlRequestInfo.ResourceTypeFavicon:
                interceptors.ResourceType.favicon,
            QWebEngineUrlRequestInfo.ResourceTypeXhr:
                interceptors.ResourceType.xhr,
            QWebEngineUrlRequestInfo.ResourceTypePing:
                interceptors.ResourceType.ping,
            QWebEngineUrlRequestInfo.ResourceTypeServiceWorker:
                interceptors.ResourceType.service_worker,
            QWebEngineUrlRequestInfo.ResourceTypeCspReport:
                interceptors.ResourceType.csp_report,
            QWebEngineUrlRequestInfo.ResourceTypePluginResource:
                interceptors.ResourceType.plugin_resource,
            QWebEngineUrlRequestInfo.ResourceTypeUnknown:
                interceptors.ResourceType.unknown,
        }

        try:
            preload_main_frame = (QWebEngineUrlRequestInfo.
                                  ResourceTypeNavigationPreloadMainFrame)
            preload_sub_frame = (QWebEngineUrlRequestInfo.
                                 ResourceTypeNavigationPreloadSubFrame)
        except AttributeError:
            # Added in Qt 5.14
            pass
        else:
            self._resource_types[preload_main_frame] = (
                interceptors.ResourceType.preload_main_frame)
            self._resource_types[preload_sub_frame] = (
                interceptors.ResourceType.preload_sub_frame)

    def install(self, profile):
        """Install the interceptor on the given QWebEngineProfile."""
        try:
            # Qt >= 5.13, GUI thread
            profile.setUrlRequestInterceptor(self)
        except AttributeError:
            # Qt 5.12, IO thread
            profile.setRequestInterceptor(self)

    # Gets called in the IO thread -> showing crash window will fail
    @utils.prevent_exceptions(None, not qtutils.version_check('5.13'))
    def interceptRequest(self, info):
        """Handle the given request.

        Reimplementing this virtual function and setting the interceptor on a
        profile makes it possible to intercept URL requests.

        On Qt < 5.13, this function is executed on the IO thread, and therefore
        running long tasks here will block networking.

        info contains the information about the URL request and will track
        internally whether its members have been altered.

        Args:
            info: QWebEngineUrlRequestInfo &info
        """
        if 'log-requests' in objects.debug_flags:
            resource_type_str = debug.qenum_key(QWebEngineUrlRequestInfo,
                                                info.resourceType())
            navigation_type_str = debug.qenum_key(QWebEngineUrlRequestInfo,
                                                  info.navigationType())
            log.network.debug("{} {}, first-party {}, resource {}, "
                              "navigation {}".format(
                                  bytes(info.requestMethod()).decode('ascii'),
                                  info.requestUrl().toDisplayString(),
                                  info.firstPartyUrl().toDisplayString(),
                                  resource_type_str, navigation_type_str))

        url = info.requestUrl()
        first_party = info.firstPartyUrl()
        if not url.isValid():
            log.network.debug("Ignoring invalid intercepted URL: {}".format(
                url.errorString()))
            return

        # Per QWebEngineUrlRequestInfo::ResourceType documentation, if we fail
        # our lookup, we should fall back to ResourceTypeUnknown
        try:
            resource_type = self._resource_types[info.resourceType()]
        except KeyError:
            log.network.warning(
                "Resource type {} not found in RequestInterceptor dict."
                .format(debug.qenum_key(QWebEngineUrlRequestInfo,
                                        info.resourceType())))
            resource_type = interceptors.ResourceType.unknown

        if ((url.scheme(), url.host(), url.path()) ==
                ('qute', 'settings', '/set')):
            if (first_party != QUrl('qute://settings/') or
                    info.resourceType() !=
                    QWebEngineUrlRequestInfo.ResourceTypeXhr):
                log.network.warning("Blocking malicious request from {} to {}"
                                    .format(first_party.toDisplayString(),
                                            url.toDisplayString()))
                info.block(True)
                return

        # FIXME:qtwebengine only block ads for NavigationTypeOther?
        request = WebEngineRequest(
            first_party_url=first_party,
            request_url=url,
            resource_type=resource_type,
            webengine_info=info)

        interceptors.run(request)
        if request.is_blocked:
            info.block(True)

        for header, value in shared.custom_headers(url=url):
            info.setHttpHeader(header, value)

        user_agent = websettings.user_agent(url)
        info.setHttpHeader(b'User-Agent', user_agent.encode('ascii'))
예제 #34
0
def _apply_platform_markers(config, item):
    """Apply a skip marker to a given item."""
    markers = [
        ('posix',
         pytest.mark.skipif,
         not utils.is_posix,
         "Requires a POSIX os"),
        ('windows',
         pytest.mark.skipif,
         not utils.is_windows,
         "Requires Windows"),
        ('linux',
         pytest.mark.skipif,
         not utils.is_linux,
         "Requires Linux"),
        ('mac',
         pytest.mark.skipif,
         not utils.is_mac,
         "Requires macOS"),
        ('not_mac',
         pytest.mark.skipif,
         utils.is_mac,
         "Skipped on macOS"),
        ('not_frozen',
         pytest.mark.skipif,
         getattr(sys, 'frozen', False),
         "Can't be run when frozen"),
        ('frozen',
         pytest.mark.skipif,
         not getattr(sys, 'frozen', False),
         "Can only run when frozen"),
        ('ci',
         pytest.mark.skipif,
         not testutils.ON_CI,
         "Only runs on CI."),
        ('no_ci',
         pytest.mark.skipif,
         testutils.ON_CI,
         "Skipped on CI."),
        ('unicode_locale',
         pytest.mark.skipif,
         sys.getfilesystemencoding() == 'ascii',
         "Skipped because of ASCII locale"),

        ('qtbug60673',
         pytest.mark.xfail,
         qtutils.version_check('5.8') and
         not qtutils.version_check('5.10') and
         config.webengine,
         "Broken on webengine due to "
         "https://bugreports.qt.io/browse/QTBUG-60673"),
        ('qtwebkit6021_xfail',
         pytest.mark.xfail,
         version.qWebKitVersion and  # type: ignore[unreachable]
         version.qWebKitVersion() == '602.1',
         "Broken on WebKit 602.1")
    ]

    for searched_marker, new_marker_kind, condition, default_reason in markers:
        marker = item.get_closest_marker(searched_marker)
        if not marker or not condition:
            continue

        if 'reason' in marker.kwargs:
            reason = '{}: {}'.format(default_reason, marker.kwargs['reason'])
            del marker.kwargs['reason']
        else:
            reason = default_reason + '.'
        new_marker = new_marker_kind(condition, *marker.args,
                                     reason=reason, **marker.kwargs)
        item.add_marker(new_marker)
예제 #35
0
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser.  If not, see <http://www.gnu.org/licenses/>.

import pytest
from PyQt5.QtCore import QUrl, QDateTime
from PyQt5.QtNetwork import QNetworkDiskCache, QNetworkCacheMetaData

from qutebrowser.browser.webkit import cache
from qutebrowser.utils import qtutils

pytestmark = pytest.mark.skipif(
    qtutils.version_check('5.7.1', compiled=False)
    and not qtutils.version_check('5.9', compiled=False),
    reason="QNetworkDiskCache is broken on Qt 5.7.1 and 5.8")


@pytest.fixture
def disk_cache(tmpdir, config_stub):
    return cache.DiskCache(str(tmpdir))


def preload_cache(cache, url='http://www.example.com/', content=b'foobar'):
    metadata = QNetworkCacheMetaData()
    metadata.setUrl(QUrl(url))
    assert metadata.isValid()
    device = cache.prepare(metadata)
    assert device is not None
예제 #36
0
        Backend: {backend}

        PYTHON IMPLEMENTATION: PYTHON VERSION
        Qt: {qt}
        PyQt: PYQT VERSION

        MODULE VERSION 1
        MODULE VERSION 2
        pdf.js: PDFJS VERSION
        QtNetwork SSL: {ssl}
        {style}
        Platform: PLATFORM, ARCHITECTURE{linuxdist}
        Frozen: {frozen}
        Imported from {import_path}
        Qt library executable path: QT PATH, data path: QT PATH
        {osinfo}
        Paths:
        PATH DESC: PATH NAME
    """.lstrip('\n'))

    expected = template.rstrip('\n').format(**substitutions)
    assert version.version() == expected


@pytest.mark.skipif(not qtutils.version_check('5.4'),
                    reason="Needs Qt >= 5.4.")
def test_opengl_vendor():
    """Simply call version.opengl_vendor() and see if it doesn't crash."""
    pytest.importorskip("PyQt5.QtOpenGL")
    return version.opengl_vendor()
예제 #37
0
def _qtwebengine_features(
    feature_flags: Sequence[str], ) -> Tuple[Sequence[str], Sequence[str]]:
    """Get a tuple of --enable-features/--disable-features flags for QtWebEngine.

    Args:
        feature_flags: Existing flags passed via the commandline.
    """
    enabled_features = []
    disabled_features = []

    for flag in feature_flags:
        if flag.startswith(_ENABLE_FEATURES):
            flag = flag[len(_ENABLE_FEATURES):]
            enabled_features += flag.split(',')
        elif flag.startswith(_DISABLE_FEATURES):
            flag = flag[len(_DISABLE_FEATURES):]
            disabled_features += flag.split(',')
        else:
            raise utils.Unreachable(flag)

    if qtutils.version_check('5.15', compiled=False) and utils.is_linux:
        # Enable WebRTC PipeWire for screen capturing on Wayland.
        #
        # This is disabled in Chromium by default because of the "dialog hell":
        # https://bugs.chromium.org/p/chromium/issues/detail?id=682122#c50
        # https://github.com/flatpak/xdg-desktop-portal-gtk/issues/204
        #
        # However, we don't have Chromium's confirmation dialog in qutebrowser,
        # so we should only get qutebrowser's permission dialog.
        #
        # In theory this would be supported with Qt 5.13 already, but
        # QtWebEngine only started picking up PipeWire correctly with Qt
        # 5.15.1. Checking for 5.15 here to pick up Archlinux' patched package
        # as well.
        #
        # This only should be enabled on Wayland, but it's too early to check
        # that, as we don't have a QApplication available at this point. Thus,
        # just turn it on unconditionally on Linux, which shouldn't hurt.
        enabled_features.append('WebRTCPipeWireCapturer')

    if not utils.is_mac:
        # Enable overlay scrollbars.
        #
        # There are two additional flags in Chromium:
        #
        # - OverlayScrollbarFlashAfterAnyScrollUpdate
        # - OverlayScrollbarFlashWhenMouseEnter
        #
        # We don't expose/activate those, but the changes they introduce are
        # quite subtle: The former seems to show the scrollbar handle even if
        # there was a 0px scroll (though no idea how that can happen...). The
        # latter flashes *all* scrollbars when a scrollable area was entered,
        # which doesn't seem to make much sense.
        if config.val.scrolling.bar == 'overlay':
            enabled_features.append('OverlayScrollbar')

    if (qtutils.version_check('5.14', compiled=False)
            and config.val.content.headers.referer == 'same-domain'):
        # Handling of reduced-referrer-granularity in Chromium 76+
        # https://chromium-review.googlesource.com/c/chromium/src/+/1572699
        #
        # Note that this is removed entirely (and apparently the default) starting with
        # Chromium 89 (Qt 5.15.x or 6.x):
        # https://chromium-review.googlesource.com/c/chromium/src/+/2545444
        enabled_features.append('ReducedReferrerGranularity')

    if qtutils.version_check('5.15.2', compiled=False, exact=True):
        # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-89740
        # FIXME Not needed anymore with QtWebEngne 5.15.3 (or Qt 6), but we'll probably
        # have no way to detect that...
        disabled_features.append('InstalledApp')

    return (enabled_features, disabled_features)
예제 #38
0
class TestSendOrListen:
    @attr.s
    class Args:

        no_err_windows = attr.ib()
        basedir = attr.ib()
        command = attr.ib()
        target = attr.ib()

    @pytest.fixture
    def args(self):
        return self.Args(no_err_windows=True,
                         basedir='/basedir/for/testing',
                         command=['test'],
                         target=None)

    @pytest.fixture
    def qlocalserver_mock(self, mocker):
        m = mocker.patch('qutebrowser.misc.ipc.QLocalServer', autospec=True)
        m().errorString.return_value = "Error string"
        m().newConnection = stubs.FakeSignal()
        return m

    @pytest.fixture
    def qlocalsocket_mock(self, mocker):
        m = mocker.patch('qutebrowser.misc.ipc.QLocalSocket', autospec=True)
        m().errorString.return_value = "Error string"
        for name in [
                'UnknownSocketError', 'UnconnectedState',
                'ConnectionRefusedError', 'ServerNotFoundError',
                'PeerClosedError'
        ]:
            setattr(m, name, getattr(QLocalSocket, name))
        return m

    @pytest.mark.linux(reason="Flaky on Windows and macOS")
    def test_normal_connection(self, caplog, qtbot, args):
        ret_server = ipc.send_or_listen(args)
        assert isinstance(ret_server, ipc.IPCServer)
        assert "Starting IPC server..." in caplog.messages
        assert ret_server is ipc.server

        with qtbot.waitSignal(ret_server.got_args):
            ret_client = ipc.send_or_listen(args)

        assert ret_client is None

    @pytest.mark.posix(reason="Unneeded on Windows")
    @pytest.mark.xfail(qtutils.version_check('5.12', compiled=False)
                       and utils.is_mac,
                       reason="Broken, see #4471")
    def test_correct_socket_name(self, args):
        server = ipc.send_or_listen(args)
        expected_dir = ipc._get_socketname(args.basedir)
        assert '/' in expected_dir
        assert server._socketname == expected_dir

    def test_address_in_use_ok(self, qlocalserver_mock, qlocalsocket_mock,
                               stubs, caplog, args):
        """Test the following scenario.

        - First call to send_to_running_instance:
            -> could not connect (server not found)
        - Trying to set up a server and listen
            -> AddressInUseError
        - Second call to send_to_running_instance:
            -> success
        """
        qlocalserver_mock().listen.return_value = False
        err = QAbstractSocket.AddressInUseError
        qlocalserver_mock().serverError.return_value = err

        qlocalsocket_mock().waitForConnected.side_effect = [False, True]
        qlocalsocket_mock().error.side_effect = [
            QLocalSocket.ServerNotFoundError,
            QLocalSocket.UnknownSocketError,
            QLocalSocket.UnknownSocketError,  # error() gets called twice
        ]

        ret = ipc.send_or_listen(args)
        assert ret is None
        assert "Got AddressInUseError, trying again." in caplog.messages

    @pytest.mark.parametrize('has_error, exc_name, exc_msg', [
        (True, 'SocketError',
         'Error while writing to running instance: Error string (error 0)'),
        (False, 'AddressInUseError',
         'Error while listening to IPC server: Error string (error 8)'),
    ])
    def test_address_in_use_error(self, qlocalserver_mock, qlocalsocket_mock,
                                  stubs, caplog, args, has_error, exc_name,
                                  exc_msg):
        """Test the following scenario.

        - First call to send_to_running_instance:
            -> could not connect (server not found)
        - Trying to set up a server and listen
            -> AddressInUseError
        - Second call to send_to_running_instance:
            -> not sent / error
        """
        qlocalserver_mock().listen.return_value = False
        err = QAbstractSocket.AddressInUseError
        qlocalserver_mock().serverError.return_value = err

        # If the second connection succeeds, we will have an error later.
        # If it fails, that's the "not sent" case above.
        qlocalsocket_mock().waitForConnected.side_effect = [False, has_error]
        qlocalsocket_mock().error.side_effect = [
            QLocalSocket.ServerNotFoundError,
            QLocalSocket.ServerNotFoundError,
            QLocalSocket.ConnectionRefusedError,
            QLocalSocket.ConnectionRefusedError,  # error() gets called twice
        ]

        with caplog.at_level(logging.ERROR):
            with pytest.raises(ipc.Error):
                ipc.send_or_listen(args)

        error_msgs = [
            'Handling fatal misc.ipc.{} with --no-err-windows!'.format(
                exc_name),
            '',
            'title: Error while connecting to running instance!',
            'pre_text: ',
            'post_text: Maybe another instance is running but frozen?',
            'exception text: {}'.format(exc_msg),
        ]
        assert caplog.messages == ['\n'.join(error_msgs)]

    @pytest.mark.posix(reason="Flaky on Windows")
    def test_error_while_listening(self, qlocalserver_mock, caplog, args):
        """Test an error with the first listen call."""
        qlocalserver_mock().listen.return_value = False
        err = QAbstractSocket.SocketResourceError
        qlocalserver_mock().serverError.return_value = err

        with caplog.at_level(logging.ERROR):
            with pytest.raises(ipc.Error):
                ipc.send_or_listen(args)

        error_msgs = [
            'Handling fatal misc.ipc.ListenError with --no-err-windows!',
            '',
            'title: Error while connecting to running instance!',
            'pre_text: ',
            'post_text: Maybe another instance is running but frozen?',
            ('exception text: Error while listening to IPC server: Error '
             'string (error 4)'),
        ]
        assert caplog.messages[-1] == '\n'.join(error_msgs)
예제 #39
0
               args=[QWebEngineSettings.MinimumFontSize]),
    'fonts.web.size.minimum_logical':
        Setter(QWebEngineSettings.setFontSize,
               args=[QWebEngineSettings.MinimumLogicalFontSize]),
    'fonts.web.size.default':
        Setter(QWebEngineSettings.setFontSize,
               args=[QWebEngineSettings.DefaultFontSize]),
    'fonts.web.size.default_fixed':
        Setter(QWebEngineSettings.setFontSize,
               args=[QWebEngineSettings.DefaultFixedFontSize]),

    'scrolling.smooth':
        Attribute(QWebEngineSettings.ScrollAnimatorEnabled),
}

try:
    MAPPINGS['content.print_element_backgrounds'] = Attribute(
        QWebEngineSettings.PrintElementBackgrounds)
except AttributeError:
    # Added in Qt 5.8
    pass


if qtutils.version_check('5.8'):
    MAPPINGS['spellcheck.languages'] = DictionaryLanguageSetter()


if qtutils.version_check('5.9', compiled=False):
    # https://bugreports.qt.io/browse/QTBUG-58650
    MAPPINGS['content.cookies.store'] = PersistentCookiePolicy()
예제 #40
0
"""Partial comparison of dicts/lists."""


import re
import pprint
import os.path
import contextlib

import pytest

from qutebrowser.utils import qtutils, log


qt58 = pytest.mark.skipif(
    qtutils.version_check('5.9'), reason="Needs Qt 5.8 or earlier")
qt59 = pytest.mark.skipif(
    not qtutils.version_check('5.9'), reason="Needs Qt 5.9 or newer")
qt510 = pytest.mark.skipif(
    not qtutils.version_check('5.10'), reason="Needs Qt 5.10 or newer")
skip_qt511 = pytest.mark.skipif(
    qtutils.version_check('5.11'), reason="Needs Qt 5.10 or earlier")


class PartialCompareOutcome:

    """Storage for a partial_compare error.

    Evaluates to False if an error was found.

    Attributes:
예제 #41
0
def _qtwebengine_args(namespace: argparse.Namespace) -> typing.Iterator[str]:
    """Get the QtWebEngine arguments to use based on the config."""
    if not qtutils.version_check('5.11', compiled=False):
        # WORKAROUND equivalent to
        # https://codereview.qt-project.org/#/c/217932/
        # Needed for Qt < 5.9.5 and < 5.10.1
        yield '--disable-shared-workers'

    # WORKAROUND equivalent to
    # https://codereview.qt-project.org/c/qt/qtwebengine/+/256786
    # also see:
    # https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/265753
    if qtutils.version_check('5.12.3', compiled=False):
        if 'stack' in namespace.debug_flags:
            # Only actually available in Qt 5.12.5, but let's save another
            # check, as passing the option won't hurt.
            yield '--enable-in-process-stack-traces'
    else:
        if 'stack' not in namespace.debug_flags:
            yield '--disable-in-process-stack-traces'

    if 'chromium' in namespace.debug_flags:
        yield '--enable-logging'
        yield '--v=1'

    settings = {
        'qt.force_software_rendering': {
            'software-opengl': None,
            'qt-quick': None,
            'chromium': '--disable-gpu',
            'none': None,
        },
        'content.canvas_reading': {
            True: None,
            False: '--disable-reading-from-canvas',
        },
        'content.webrtc_ip_handling_policy': {
            'all-interfaces':
            None,
            'default-public-and-private-interfaces':
            '--force-webrtc-ip-handling-policy='
            'default_public_and_private_interfaces',
            'default-public-interface-only':
            '--force-webrtc-ip-handling-policy='
            'default_public_interface_only',
            'disable-non-proxied-udp':
            '--force-webrtc-ip-handling-policy='
            'disable_non_proxied_udp',
        },
        'qt.process_model': {
            'process-per-site-instance': None,
            'process-per-site': '--process-per-site',
            'single-process': '--single-process',
        },
        'qt.low_end_device_mode': {
            'auto': None,
            'always': '--enable-low-end-device-mode',
            'never': '--disable-low-end-device-mode',
        },
        'content.headers.referer': {
            'always': None,
            'never': '--no-referrers',
            'same-domain': '--reduced-referrer-granularity',
        }
    }  # type: typing.Dict[str, typing.Dict[typing.Any, typing.Optional[str]]]

    if not qtutils.version_check('5.11'):
        # On Qt 5.11, we can control this via QWebEngineSettings
        settings['content.autoplay'] = {
            True: None,
            False: '--autoplay-policy=user-gesture-required',
        }

    for setting, args in sorted(settings.items()):
        arg = args[config.instance.get(setting)]
        if arg is not None:
            yield arg
예제 #42
0
def _init_modules(args, crash_handler):
    """Initialize all 'modules' which need to be initialized.

    Args:
        args: The argparse namespace.
        crash_handler: The CrashHandler instance.
    """
    # pylint: disable=too-many-statements
    log.init.debug("Initializing prompts...")
    prompt.init()

    log.init.debug("Initializing save manager...")
    save_manager = savemanager.SaveManager(qApp)
    objreg.register('save-manager', save_manager)
    save_manager.add_saveable('version', _save_version)

    log.init.debug("Initializing network...")
    networkmanager.init()

    if qtutils.version_check('5.8'):
        # Otherwise we can only initialize it for QtWebKit because of crashes
        log.init.debug("Initializing proxy...")
        proxy.init()

    log.init.debug("Initializing readline-bridge...")
    readline_bridge = readline.ReadlineBridge()
    objreg.register('readline-bridge', readline_bridge)

    log.init.debug("Initializing config...")
    config.init(qApp)
    save_manager.init_autosave()

    log.init.debug("Initializing web history...")
    history.init(qApp)

    log.init.debug("Initializing crashlog...")
    if not args.no_err_windows:
        crash_handler.handle_segfault()

    log.init.debug("Initializing sessions...")
    sessions.init(qApp)

    log.init.debug("Initializing websettings...")
    websettings.init(args)

    log.init.debug("Initializing adblock...")
    host_blocker = adblock.HostBlocker()
    host_blocker.read_hosts()
    objreg.register('host-blocker', host_blocker)

    log.init.debug("Initializing quickmarks...")
    quickmark_manager = urlmarks.QuickmarkManager(qApp)
    objreg.register('quickmark-manager', quickmark_manager)

    log.init.debug("Initializing bookmarks...")
    bookmark_manager = urlmarks.BookmarkManager(qApp)
    objreg.register('bookmark-manager', bookmark_manager)

    log.init.debug("Initializing cookies...")
    cookie_jar = cookies.CookieJar(qApp)
    ram_cookie_jar = cookies.RAMCookieJar(qApp)
    objreg.register('cookie-jar', cookie_jar)
    objreg.register('ram-cookie-jar', ram_cookie_jar)

    log.init.debug("Initializing cache...")
    diskcache = cache.DiskCache(standarddir.cache(), parent=qApp)
    objreg.register('cache', diskcache)

    log.init.debug("Initializing completions...")
    completionmodels.init()

    log.init.debug("Misc initialization...")
    if config.get('ui', 'hide-wayland-decoration'):
        os.environ['QT_WAYLAND_DISABLE_WINDOWDECORATION'] = '1'
    else:
        os.environ.pop('QT_WAYLAND_DISABLE_WINDOWDECORATION', None)
    macros.init()
    # Init backend-specific stuff
    browsertab.init()
예제 #43
0
파일: utils.py 프로젝트: thoemu/qutebrowser
"""Various utilities used inside tests."""

import io
import re
import gzip
import pprint
import os.path
import contextlib

import pytest

from qutebrowser.utils import qtutils, log

ON_CI = 'CI' in os.environ

qt58 = pytest.mark.skipif(qtutils.version_check('5.9'),
                          reason="Needs Qt 5.8 or earlier")
qt59 = pytest.mark.skipif(not qtutils.version_check('5.9'),
                          reason="Needs Qt 5.9 or newer")
qt510 = pytest.mark.skipif(not qtutils.version_check('5.10'),
                           reason="Needs Qt 5.10 or newer")
qt514 = pytest.mark.skipif(not qtutils.version_check('5.14'),
                           reason="Needs Qt 5.14 or newer")
skip_qt511 = pytest.mark.skipif(qtutils.version_check('5.11'),
                                reason="Needs Qt 5.10 or earlier")


class PartialCompareOutcome:
    """Storage for a partial_compare error.

    Evaluates to False if an error was found.
예제 #44
0
import importlib.util
import importlib.machinery

import pytest

from PyQt5.QtCore import qVersion
try:
    from PyQt5.QtWebEngine import PYQT_WEBENGINE_VERSION_STR
except ImportError:
    PYQT_WEBENGINE_VERSION_STR = None

from qutebrowser.utils import qtutils, log

ON_CI = 'CI' in os.environ

qt514 = pytest.mark.skipif(not qtutils.version_check('5.14'),
                           reason="Needs Qt 5.14 or newer")


class PartialCompareOutcome:
    """Storage for a partial_compare error.

    Evaluates to False if an error was found.

    Attributes:
        error: A string describing an error or None.
    """
    def __init__(self, error=None):
        self.error = error

    def __bool__(self):
예제 #45
0
        (False, False, False, ''),
        (False, True, False, 'onlyscheme:'),
        (False, True, False, 'http:foo:0'),
        # Not URLs
        (False, True, False, 'foo bar'),  # no DNS because of space
        (False, True, False, 'localhost test'),  # no DNS because of space
        (False, True, False, 'another . test'),  # no DNS because of space
        (False, True, True, 'foo'),
        (False, True, False, 'this is: not a URL'),  # no DNS because of space
        (False, True, False, '23.42'),  # no DNS because bogus-IP
        (False, True, False, '1337'),  # no DNS because bogus-IP
        (False, True, True, 'deadbeef'),
        (False, True, True, 'hello.'),
        (False, True, False, 'site:cookies.com oatmeal raisin'),
        # no DNS because bogus-IP
        pytest.mark.xfail(qtutils.version_check('5.6.1'),
                          reason='Qt behavior changed')
        (False, True, False, '31c3'),
        (False, True, False, 'foo::bar'),  # no DNS because of no host
        # Valid search term with autosearch
        (False, False, False, 'test foo'),
        # autosearch = False
        (False, True, False, 'This is a URL without autosearch'),
    ])
@pytest.mark.parametrize('auto_search', ['dns', 'naive', False])
def test_is_url(urlutils_config_stub, fake_dns, is_url, is_url_no_autosearch,
                uses_dns, url, auto_search):
    """Test is_url().

    Args:
        is_url: Whether the given string is a URL with auto-search dns/naive.
예제 #46
0
def _qtwebengine_settings_args() -> Iterator[str]:
    settings: Dict[str, Dict[Any, Optional[str]]] = {
        'qt.force_software_rendering': {
            'software-opengl': None,
            'qt-quick': None,
            'chromium': '--disable-gpu',
            'none': None,
        },
        'content.canvas_reading': {
            True: None,
            False: '--disable-reading-from-canvas',
        },
        'content.webrtc_ip_handling_policy': {
            'all-interfaces':
            None,
            'default-public-and-private-interfaces':
            '--force-webrtc-ip-handling-policy='
            'default_public_and_private_interfaces',
            'default-public-interface-only':
            '--force-webrtc-ip-handling-policy='
            'default_public_interface_only',
            'disable-non-proxied-udp':
            '--force-webrtc-ip-handling-policy='
            'disable_non_proxied_udp',
        },
        'qt.process_model': {
            'process-per-site-instance': None,
            'process-per-site': '--process-per-site',
            'single-process': '--single-process',
        },
        'qt.low_end_device_mode': {
            'auto': None,
            'always': '--enable-low-end-device-mode',
            'never': '--disable-low-end-device-mode',
        },
        'content.headers.referer': {
            'always': None,
        }
    }

    if (qtutils.version_check('5.14', compiled=False)
            and not qtutils.version_check('5.15.2', compiled=False)):
        # In Qt 5.14 to 5.15.1, `--force-dark-mode` is used to set the
        # preferred colorscheme. In Qt 5.15.2, this is handled by a
        # blink-setting in browser/webengine/darkmode.py instead.
        settings['colors.webpage.prefers_color_scheme_dark'] = {
            True: '--force-dark-mode',
            False: None,
        }

    referrer_setting = settings['content.headers.referer']
    if qtutils.version_check('5.14', compiled=False):
        # Starting with Qt 5.14, this is handled via --enable-features
        referrer_setting['same-domain'] = None
    else:
        referrer_setting['same-domain'] = '--reduced-referrer-granularity'

    can_override_referer = (
        qtutils.version_check('5.12.4', compiled=False)
        and not qtutils.version_check('5.13.0', compiled=False, exact=True))
    # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-60203
    referrer_setting[
        'never'] = None if can_override_referer else '--no-referrers'

    for setting, args in sorted(settings.items()):
        arg = args[config.instance.get(setting)]
        if arg is not None:
            yield arg
예제 #47
0
def _darkmode_settings() -> typing.Iterator[typing.Tuple[str, str]]:
    """Get necessary blink settings to configure dark mode for QtWebEngine."""
    if not config.val.colors.webpage.darkmode.enabled:
        return

    # Mapping from a colors.webpage.darkmode.algorithm setting value to
    # Chromium's DarkModeInversionAlgorithm enum values.
    algorithms = {
        # 0: kOff (not exposed)
        # 1: kSimpleInvertForTesting (not exposed)
        'brightness-rgb': 2,  # kInvertBrightness
        'lightness-hsl': 3,  # kInvertLightness
        'lightness-cielab': 4,  # kInvertLightnessLAB
    }

    # Mapping from a colors.webpage.darkmode.policy.images setting value to
    # Chromium's DarkModeImagePolicy enum values.
    image_policies = {
        'always': 0,  # kFilterAll
        'never': 1,  # kFilterNone
        'smart': 2,  # kFilterSmart
    }

    # Mapping from a colors.webpage.darkmode.policy.page setting value to
    # Chromium's DarkModePagePolicy enum values.
    page_policies = {
        'always': 0,  # kFilterAll
        'smart': 1,  # kFilterByBackground
    }

    bools = {
        True: 'true',
        False: 'false',
    }

    _setting_description_type = typing.Tuple[
        str,  # qutebrowser option name
        str,  # darkmode setting name
        # Mapping from the config value to a string (or something convertable
        # to a string) which gets passed to Chromium.
        typing.Optional[typing.Mapping[typing.Any, typing.Union[str, int]]], ]
    if qtutils.version_check('5.15', compiled=False):
        settings = [
            ('enabled', 'Enabled', bools),
            ('algorithm', 'InversionAlgorithm', algorithms),
        ]  # type: typing.List[_setting_description_type]
        mandatory_setting = 'enabled'
    else:
        settings = [
            ('algorithm', '', algorithms),
        ]
        mandatory_setting = 'algorithm'

    settings += [
        ('contrast', 'Contrast', None),
        ('policy.images', 'ImagePolicy', image_policies),
        ('policy.page', 'PagePolicy', page_policies),
        ('threshold.text', 'TextBrightnessThreshold', None),
        ('threshold.background', 'BackgroundBrightnessThreshold', None),
        ('grayscale.all', 'Grayscale', bools),
        ('grayscale.images', 'ImageGrayscale', None),
    ]

    for setting, key, mapping in settings:
        # To avoid blowing up the commandline length, we only pass modified
        # settings to Chromium, as our defaults line up with Chromium's.
        # However, we always pass enabled/algorithm to make sure dark mode gets
        # actually turned on.
        value = config.instance.get('colors.webpage.darkmode.' + setting,
                                    fallback=setting == mandatory_setting)
        if isinstance(value, usertypes.Unset):
            continue

        if mapping is not None:
            value = mapping[value]

        # FIXME: This is "forceDarkMode" starting with Chromium 83
        prefix = 'darkMode'

        yield prefix + key, str(value)
예제 #48
0
 def install(self, profile):
     """Install the handler for qute:// URLs on the given profile."""
     profile.installUrlSchemeHandler(b'qute', self)
     if qtutils.version_check('5.11', compiled=False):
         # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
         profile.installUrlSchemeHandler(b'chrome-error', self)
예제 #49
0
def normalize_whole(s, webengine):
    if qtutils.version_check('5.12', compiled=False) and webengine:
        s = s.replace('\n\n-----=_qute-UUID', '\n-----=_qute-UUID')
    return s
예제 #50
0
    def _needs_recreate(self) -> bool:
        """Recreate the inspector when detaching to a window.

        WORKAROUND for what's likely an unknown Qt bug.
        """
        return qtutils.version_check('5.12')
예제 #51
0
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser.  If not, see <http://www.gnu.org/licenses/>.

import pytest
from PyQt5.QtCore import QUrl, QDateTime
from PyQt5.QtNetwork import QNetworkDiskCache, QNetworkCacheMetaData

from qutebrowser.browser.webkit import cache
from qutebrowser.utils import qtutils

pytestmark = pytest.mark.skipif(
    qtutils.version_check('5.7.1') and not qtutils.version_check('5.9'),
    reason="QNetworkDiskCache is broken on Qt 5.7.1 and 5.8")


@pytest.fixture
def disk_cache(tmpdir, config_stub):
    return cache.DiskCache(str(tmpdir))


def preload_cache(cache, url='http://www.example.com/', content=b'foobar'):
    metadata = QNetworkCacheMetaData()
    metadata.setUrl(QUrl(url))
    assert metadata.isValid()
    device = cache.prepare(metadata)
    assert device is not None
    device.write(content)
예제 #52
0
def test_version_check_compiled_and_exact():
    with pytest.raises(ValueError):
        qtutils.version_check('1.2.3', exact=True, compiled=True)
예제 #53
0
파일: app.py 프로젝트: s3lph/qutebrowser
def _open_special_pages(args):
    """Open special notification pages which are only shown once.

    Args:
        args: The argparse namespace.
    """
    if args.basedir is not None:
        # With --basedir given, don't open anything.
        return

    general_sect = configfiles.state['general']
    tabbed_browser = objreg.get('tabbed-browser',
                                scope='window',
                                window='last-focused')

    pages = [
        # state, condition, URL
        ('quickstart-done', True, 'https://www.qutebrowser.org/quickstart.html'
         ),
        ('config-migration-shown',
         os.path.exists(os.path.join(standarddir.config(),
                                     'qutebrowser.conf')),
         'qute://help/configuring.html'),
        ('webkit-warning-shown', objects.backend == usertypes.Backend.QtWebKit,
         'qute://warning/webkit'),
        ('session-warning-shown', qtutils.version_check('5.15',
                                                        compiled=False),
         'qute://warning/sessions'),
    ]

    if 'quickstart-done' not in general_sect:
        # New users aren't going to be affected by the Qt 5.15 session change much, as
        # they aren't used to qutebrowser saving the full back/forward history in
        # sessions.
        general_sect['session-warning-shown'] = '1'

    for state, condition, url in pages:
        if general_sect.get(state) != '1' and condition:
            tabbed_browser.tabopen(QUrl(url), background=False)
            general_sect[state] = '1'

    # Show changelog on new releases
    change = configfiles.state.qutebrowser_version_changed
    if change == configfiles.VersionChange.equal:
        return

    setting = config.val.changelog_after_upgrade
    if not change.matches_filter(setting):
        log.init.debug(
            f"Showing changelog is disabled (setting {setting}, change {change})"
        )
        return

    try:
        changelog = resources.read_file('html/doc/changelog.html')
    except OSError as e:
        log.init.warning(f"Not showing changelog due to {e}")
        return

    qbversion = qutebrowser.__version__
    if f'id="v{qbversion}"' not in changelog:
        log.init.warning("Not showing changelog (anchor not found)")
        return

    message.info(
        f"Showing changelog after upgrade to qutebrowser v{qbversion}.")
    changelog_url = f'qute://help/changelog.html#v{qbversion}'
    tabbed_browser.tabopen(QUrl(changelog_url), background=False)
예제 #54
0
class TestStandardDir:

    @pytest.mark.parametrize('func, init_func, varname', [
        (standarddir.data, standarddir._init_data, 'XDG_DATA_HOME'),
        (standarddir.config, standarddir._init_config, 'XDG_CONFIG_HOME'),
        (lambda: standarddir.config(auto=True),
         standarddir._init_config, 'XDG_CONFIG_HOME'),
        (standarddir.cache, standarddir._init_cache, 'XDG_CACHE_HOME'),
        (standarddir.runtime, standarddir._init_runtime, 'XDG_RUNTIME_DIR'),
    ])
    @pytest.mark.linux
    def test_linux_explicit(self, monkeypatch, tmpdir,
                            func, init_func, varname):
        """Test dirs with XDG environment variables explicitly set.

        Args:
            func: The function to test.
            init_func: The initialization function to call.
            varname: The environment variable which should be set.
        """
        monkeypatch.setenv(varname, str(tmpdir))
        if varname == 'XDG_RUNTIME_DIR':
            tmpdir.chmod(0o0700)

        init_func(args=None)
        assert func() == str(tmpdir / APPNAME)

    @pytest.mark.parametrize('func, subdirs', [
        (standarddir.data, ['.local', 'share', APPNAME]),
        (standarddir.config, ['.config', APPNAME]),
        (lambda: standarddir.config(auto=True), ['.config', APPNAME]),
        (standarddir.cache, ['.cache', APPNAME]),
        (standarddir.download, ['Downloads']),
    ])
    @pytest.mark.linux
    def test_linux_normal(self, monkeypatch, tmpdir, func, subdirs):
        """Test dirs with XDG_*_HOME not set."""
        monkeypatch.setenv('HOME', str(tmpdir))
        for var in ['DATA', 'CONFIG', 'CACHE']:
            monkeypatch.delenv('XDG_{}_HOME'.format(var), raising=False)
        standarddir._init_dirs()
        assert func() == str(tmpdir.join(*subdirs))

    @pytest.mark.linux
    @pytest.mark.qt_log_ignore(r'^QStandardPaths: ')
    @pytest.mark.skipif(
        qtutils.version_check('5.14', compiled=False),
        reason="Qt 5.14 automatically creates missing runtime dirs")
    def test_linux_invalid_runtimedir(self, monkeypatch, tmpdir):
        """With invalid XDG_RUNTIME_DIR, fall back to TempLocation."""
        tmpdir_env = tmpdir / 'temp'
        tmpdir_env.ensure(dir=True)
        monkeypatch.setenv('XDG_RUNTIME_DIR', str(tmpdir / 'does-not-exist'))
        monkeypatch.setenv('TMPDIR', str(tmpdir_env))

        standarddir._init_runtime(args=None)
        assert standarddir.runtime() == str(tmpdir_env / APPNAME)

    @pytest.mark.fake_os('windows')
    def test_runtimedir_empty_tempdir(self, monkeypatch, tmpdir):
        """With an empty tempdir on non-Linux, we should raise."""
        monkeypatch.setattr(standarddir.QStandardPaths, 'writableLocation',
                            lambda typ: '')
        with pytest.raises(standarddir.EmptyValueError):
            standarddir._init_runtime(args=None)

    @pytest.mark.parametrize('func, elems, expected', [
        (standarddir.data, 2, [APPNAME, 'data']),
        (standarddir.config, 2, [APPNAME, 'config']),
        (lambda: standarddir.config(auto=True), 2, [APPNAME, 'config']),
        (standarddir.cache, 2, [APPNAME, 'cache']),
        (standarddir.download, 1, ['Downloads']),
    ])
    @pytest.mark.windows
    def test_windows(self, func, elems, expected):
        standarddir._init_dirs()
        assert func().split(os.sep)[-elems:] == expected

    @pytest.mark.parametrize('func, elems, expected', [
        (standarddir.data, 2, ['Application Support', APPNAME]),
        (lambda: standarddir.config(auto=True), 1, [APPNAME]),
        (standarddir.config, 0,
         os.path.expanduser('~').split(os.sep) + ['.qute_test']),
        (standarddir.cache, 2, ['Caches', APPNAME]),
        (standarddir.download, 1, ['Downloads']),
    ])
    @pytest.mark.mac
    def test_mac(self, func, elems, expected):
        standarddir._init_dirs()
        assert func().split(os.sep)[-elems:] == expected
예제 #55
0
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser.  If not, see <http://www.gnu.org/licenses/>.
"""Partial comparison of dicts/lists."""

import re
import pprint
import os.path

import pytest

from qutebrowser.utils import qtutils

qt58 = pytest.mark.skipif(qtutils.version_check('5.9'),
                          reason="Needs Qt 5.8 or earlier")
qt59 = pytest.mark.skipif(not qtutils.version_check('5.9'),
                          reason="Needs Qt 5.9 or newer")


class PartialCompareOutcome:
    """Storage for a partial_compare error.

    Evaluates to False if an error was found.

    Attributes:
        error: A string describing an error or None.
    """
    def __init__(self, error=None):
        self.error = error