Exemplo n.º 1
0
def _qtwebengine_args(
    namespace: argparse.Namespace,
    special_flags: Sequence[str],
) -> Iterator[str]:
    """Get the QtWebEngine arguments to use based on the config."""
    versions = version.qtwebengine_versions(avoid_init=True)

    qt_514_ver = utils.VersionNumber(5, 14)
    qt_515_ver = utils.VersionNumber(5, 15)
    if qt_514_ver <= versions.webengine < qt_515_ver:
        # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-82105
        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 versions.webengine >= utils.VersionNumber(5, 12, 3):
        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'

    lang_override = _get_lang_override(
        webengine_version=versions.webengine,
        locale_name=QLocale().bcp47Name(),
    )
    if lang_override is not None:
        yield f'--lang={lang_override}'

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

    if 'wait-renderer-process' in namespace.debug_flags:
        yield '--renderer-startup-dialog'

    from qutebrowser.browser.webengine import darkmode
    darkmode_settings = darkmode.settings(
        versions=versions,
        special_flags=special_flags,
    )
    for switch_name, values in darkmode_settings.items():
        # If we need to use other switches (say, --enable-features), we might need to
        # refactor this so values still get combined with existing ones.
        assert switch_name in ['dark-mode-settings',
                               'blink-settings'], switch_name
        yield f'--{switch_name}=' + ','.join(f'{k}={v}' for k, v in values)

    enabled_features, disabled_features = _qtwebengine_features(
        versions, special_flags)
    if enabled_features:
        yield _ENABLE_FEATURES + ','.join(enabled_features)
    if disabled_features:
        yield _DISABLE_FEATURES + ','.join(disabled_features)

    yield from _qtwebengine_settings_args(versions)
Exemplo n.º 2
0
def test_preferred_colorscheme_with_dark_mode(request, quteproc_new,
                                              webengine_versions):
    """Test interaction between preferred-color-scheme and dark mode."""
    if not request.config.webengine:
        pytest.skip("Skipped with QtWebKit")

    args = _base_args(request.config) + [
        '--temp-basedir',
        '-s',
        'colors.webpage.preferred_color_scheme',
        'dark',
        '-s',
        'colors.webpage.darkmode.enabled',
        'true',
        '-s',
        'colors.webpage.darkmode.algorithm',
        'brightness-rgb',
    ]
    quteproc_new.start(args)

    quteproc_new.open_path('data/darkmode/prefers-color-scheme.html')
    content = quteproc_new.get_content()

    qtwe_version = webengine_versions.webengine
    xfail = None
    if utils.VersionNumber(5, 15, 3) <= qtwe_version <= utils.VersionNumber(6):
        # https://bugs.chromium.org/p/chromium/issues/detail?id=1177973
        # No workaround known.
        expected_text = 'Light preference detected.'
        # light website color, inverted by darkmode
        expected_color = (testutils.Color(123, 125, 123)
                          if IS_ARM else testutils.Color(127, 127, 127))
        xfail = "Chromium bug 1177973"
    elif qtwe_version == utils.VersionNumber(5, 15, 2):
        # Our workaround breaks when dark mode is enabled...
        # Also, for some reason, dark mode doesn't work on that page either!
        expected_text = 'No preference detected.'
        expected_color = testutils.Color(0, 170, 0)  # green
        xfail = "QTBUG-89753"
    else:
        # Qt 5.14 and 5.15.0/.1 work correctly.
        # Hopefully, so does Qt 6.x in the future?
        expected_text = 'Dark preference detected.'
        expected_color = (testutils.Color(33, 32, 33) if IS_ARM else
                          testutils.Color(34, 34, 34))  # dark website color
        xfail = False

    pos = QPoint(0, 0)
    img = quteproc_new.get_screenshot(probe_pos=pos,
                                      probe_color=expected_color)
    color = testutils.Color(img.pixelColor(pos))

    assert content == expected_text
    assert color == expected_color
    if xfail:
        # We still do some checks, but we want to mark the test outcome as xfail.
        pytest.xfail(xfail)
Exemplo n.º 3
0
def _get_lang_override(
        webengine_version: utils.VersionNumber,
        locale_name: str
) -> Optional[str]:
    """Get a --lang switch to override Qt's locale handling.

    This is needed as a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-91715
    There is no fix yet, but we assume it'll be fixed with QtWebEngine 5.15.4.
    """
    if not config.val.qt.workarounds.locale:
        return None

    if webengine_version != utils.VersionNumber(5, 15, 3) or not utils.is_linux:
        return None

    locales_path = pathlib.Path(
        QLibraryInfo.location(QLibraryInfo.TranslationsPath)) / 'qtwebengine_locales'
    if not locales_path.exists():
        log.init.debug(f"{locales_path} not found, skipping workaround!")
        return None

    pak_path = _get_locale_pak_path(locales_path, locale_name)
    if pak_path.exists():
        log.init.debug(f"Found {pak_path}, skipping workaround")
        return None

    pak_name = _get_pak_name(locale_name)
    pak_path = _get_locale_pak_path(locales_path, pak_name)
    if pak_path.exists():
        log.init.debug(f"Found {pak_path}, applying workaround")
        return pak_name

    log.init.debug(f"Can't find pak in {locales_path} for {locale_name} or {pak_name}")
    return 'en-US'
Exemplo n.º 4
0
def gentoo_version_patch(monkeypatch):
    versions = version.WebEngineVersions(
        webengine=utils.VersionNumber(5, 15, 2),
        chromium='87.0.4280.144',
        source='faked',
    )
    monkeypatch.setattr(version, 'qtwebengine_versions', lambda avoid_init: versions)
Exemplo n.º 5
0
def init(parent=None):
    """Initialize sessions.

    Args:
        parent: The parent to use for the SessionManager.
    """
    base_path = pathlib.Path(standarddir.data()) / 'sessions'

    # WORKAROUND for https://github.com/qutebrowser/qutebrowser/issues/5359
    backup_path = base_path / 'before-qt-515'

    if objects.backend == usertypes.Backend.QtWebEngine:
        webengine_version = version.qtwebengine_versions().webengine
        do_backup = webengine_version >= utils.VersionNumber(5, 15)
    else:
        do_backup = False

    if base_path.exists() and not backup_path.exists() and do_backup:
        backup_path.mkdir()
        for path in base_path.glob('*.yml'):
            shutil.copy(path, backup_path)

    base_path.mkdir(exist_ok=True)

    global session_manager
    session_manager = SessionManager(str(base_path), parent)
Exemplo n.º 6
0
 def test_from_pyqt(self):
     expected = version.WebEngineVersions(
         webengine=utils.VersionNumber(5, 15, 2),
         chromium='83.0.4103.122',
         source='PyQt',
     )
     assert version.WebEngineVersions.from_pyqt('5.15.2') == expected
Exemplo n.º 7
0
 def test_from_elf(self):
     elf_version = elf.Versions(webengine='5.15.2', chromium='83.0.4103.122')
     expected = version.WebEngineVersions(
         webengine=utils.VersionNumber(5, 15, 2),
         chromium='83.0.4103.122',
         source='ELF',
     )
     assert version.WebEngineVersions.from_elf(elf_version) == expected
Exemplo n.º 8
0
    def handle_download(self, qt_item):
        """Start a download coming from a QWebEngineProfile."""
        qt_filename = os.path.basename(qt_item.path())  # FIXME use 5.14 API
        mime_type = qt_item.mimeType()
        url = qt_item.url()

        # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-90355
        if version.qtwebengine_versions().webengine >= utils.VersionNumber(
                5, 15, 3):
            needs_workaround = False
        elif url.scheme().lower() == 'data':
            if '/' in url.path().split(',')[-1]:  # e.g. a slash in base64
                wrong_filename = url.path().split('/')[-1]
            else:
                wrong_filename = mime_type.split('/')[1]

            needs_workaround = qt_filename == wrong_filename
        else:
            needs_workaround = False

        if needs_workaround:
            suggested_filename = urlutils.filename_from_url(
                url, fallback='qutebrowser-download')
        else:
            suggested_filename = _strip_suffix(qt_filename)

        use_pdfjs = pdfjs.should_use_pdfjs(mime_type, url)

        download = DownloadItem(qt_item, manager=self)
        self._init_item(download,
                        auto_remove=use_pdfjs,
                        suggested_filename=suggested_filename)

        if self._mhtml_target is not None:
            download.set_target(self._mhtml_target)
            self._mhtml_target = None
            return
        if use_pdfjs:
            download.set_target(downloads.PDFJSDownloadTarget())
            return

        filename = downloads.immediate_download_path()
        if filename is not None:
            # User doesn't want to be asked, so just use the download_dir
            target = downloads.FileDownloadTarget(filename)
            download.set_target(target)
            return

        if download.cancel_for_origin():
            return

        # Ask the user for a filename - needs to be blocking!
        question = downloads.get_filename_question(
            suggested_filename=suggested_filename,
            url=qt_item.url(),
            parent=self)
        self._init_filename_question(question, download)
        message.global_bridge.ask(question, blocking=True)
Exemplo n.º 9
0
def _variant(versions: version.WebEngineVersions) -> Variant:
    """Get the dark mode variant based on the underlying Qt version."""
    env_var = os.environ.get('QUTE_DARKMODE_VARIANT')
    if env_var is not None:
        try:
            return Variant[env_var]
        except KeyError:
            log.init.warning(
                f"Ignoring invalid QUTE_DARKMODE_VARIANT={env_var}")

    if (versions.webengine == utils.VersionNumber(5, 15, 2)
            and versions.chromium_major == 87):
        # WORKAROUND for Gentoo packaging something newer as 5.15.2...
        return Variant.qt_515_3
    elif versions.webengine >= utils.VersionNumber(5, 15, 3):
        return Variant.qt_515_3
    elif versions.webengine >= utils.VersionNumber(5, 15, 2):
        return Variant.qt_515_2
    elif versions.webengine == utils.VersionNumber(5, 15, 1):
        return Variant.qt_515_1
    elif versions.webengine == utils.VersionNumber(5, 15):
        return Variant.qt_515_0
    elif versions.webengine >= utils.VersionNumber(5, 14):
        return Variant.qt_514
    elif versions.webengine >= utils.VersionNumber(5, 11):
        return Variant.qt_511_to_513
    raise utils.Unreachable(versions.webengine)
Exemplo n.º 10
0
    def from_pyqt(cls, pyqt_webengine_version: str) -> 'WebEngineVersions':
        """Get the versions based on the PyQtWebEngine version.

        This is the "last resort" if we don't want to fully initialize QtWebEngine (so
        from_ua isn't possible), we're not on Linux (or ELF parsing failed), and
        PyQtWebEngine-Qt{5,} isn't available from PyPI.

        Here, we assume that the PyQtWebEngine version is the same as the QtWebEngine
        version, and infer the Chromium version from that. This assumption isn't
        generally true, but good enough for some scenarios, especially the prebuilt
        Windows/macOS releases.
        """
        parsed = utils.VersionNumber.parse(pyqt_webengine_version)
        if utils.VersionNumber(5, 15, 3) <= parsed < utils.VersionNumber(6):
            # If we land here, we're in a tricky situation where we are forced to guess:
            #
            # PyQt 5.15.3 and 5.15.4 from PyPI come with QtWebEngine 5.15.2 (Chromium
            # 83), not 5.15.3 (Chromium 87). Given that there was no binary release of
            # QtWebEngine 5.15.3, this is unlikely to change before Qt 6.
            #
            # However, at this point:
            #
            # - ELF parsing failed
            #   (so we're likely on macOS or Windows, but not definitely)
            #
            # - Getting infos from a PyPI-installed PyQtWebEngine failed
            #   (so we're either in a PyInstaller-deployed qutebrowser, or a self-built
            #   or distribution-installed Qt)
            #
            # PyQt 5.15.3 and 5.15.4 come with QtWebEngine 5.15.2 (83-based), but if
            # someone lands here with the last Qt/PyQt installed from source, they might
            # be using QtWebEngine 5.15.3 (87-based). For now, we play it safe, and only
            # do this kind of "downgrade" when we know we're using PyInstaller.
            frozen = hasattr(sys, 'frozen')
            log.misc.debug(f"PyQt5 >= 5.15.3, frozen {frozen}")
            if frozen:
                parsed = utils.VersionNumber(5, 15, 2)

        return cls(
            webengine=parsed,
            chromium=cls._infer_chromium_version(parsed),
            source='PyQt',
        )
Exemplo n.º 11
0
    def _infer_chromium_version(
        cls,
        pyqt_webengine_version: utils.VersionNumber,
    ) -> Optional[str]:
        """Infer the Chromium version based on the PyQtWebEngine version."""
        chromium_version = cls._CHROMIUM_VERSIONS.get(pyqt_webengine_version)
        if chromium_version is not None:
            return chromium_version

        # 5.15 patch versions change their QtWebEngine version, but no changes are
        # expected after 5.15.3.
        v5_15_3 = utils.VersionNumber(5, 15, 3)
        if v5_15_3 <= pyqt_webengine_version < utils.VersionNumber(6):
            minor_version = v5_15_3
        else:
            # e.g. 5.14.2 -> 5.14
            minor_version = pyqt_webengine_version.strip_patch()

        return cls._CHROMIUM_VERSIONS.get(minor_version)
Exemplo n.º 12
0
 def test_from_ua(self):
     ua = websettings.UserAgent(
         os_info='X11; Linux x86_64',
         webkit_version='537.36',
         upstream_browser_key='Chrome',
         upstream_browser_version='83.0.4103.122',
         qt_key='QtWebEngine',
         qt_version='5.15.2',
     )
     expected = version.WebEngineVersions(
         webengine=utils.VersionNumber(5, 15, 2),
         chromium='83.0.4103.122',
         source='UA',
     )
     assert version.WebEngineVersions.from_ua(ua) == expected
Exemplo n.º 13
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",
            browsertab.TerminationStatus.crashed: "Renderer process crashed",
            browsertab.TerminationStatus.killed: "Renderer process was killed",
            browsertab.TerminationStatus.unknown:
            "Renderer process did not start",
        }
        msg = messages[status] + f" (status {code})"

        # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-91715
        versions = version.qtwebengine_versions()
        is_qtbug_91715 = (status == browsertab.TerminationStatus.unknown
                          and code == 1002
                          and versions.webengine == utils.VersionNumber(
                              5, 15, 3))

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

        if is_qtbug_91715:
            log.webview.error(msg)
            log.webview.error('')
            log.webview.error(
                'NOTE: If you see this and "Network service crashed, restarting '
                'service.", please see:')
            log.webview.error(
                'https://github.com/qutebrowser/qutebrowser/issues/6235')
            log.webview.error(
                'You can set the "qt.workarounds.locale" setting in qutebrowser to '
                'work around the issue.')
            log.webview.error(
                'A proper fix is likely available in QtWebEngine soon (which is why '
                'the workaround is disabled by default).')
            log.webview.error('')
        else:
            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))
Exemplo n.º 14
0
    def expected_names(self, webengine_versions, pdf_bytes):
        """Get the expected filenames before/after the workaround.

        With QtWebEngine 5.15.3, this is handled correctly inside QtWebEngine
        and we get a qwe_download.pdf instead.
        """
        if webengine_versions.webengine >= utils.VersionNumber(5, 15, 3):
            return _ExpectedNames(before='qwe_download.pdf',
                                  after='qwe_download.pdf')

        with_slash = b'% ?' in pdf_bytes
        base64_data = base64.b64encode(pdf_bytes).decode('ascii')

        if with_slash:
            assert '/' in base64_data
            before = base64_data.split('/')[1]
        else:
            assert '/' not in base64_data
            before = 'pdf'  # from the mimetype

        return _ExpectedNames(before=before, after='download.pdf')
Exemplo n.º 15
0
class WebEngineVersions:
    """Version numbers for QtWebEngine and the underlying Chromium."""

    webengine: utils.VersionNumber
    chromium: Optional[str]
    source: str
    chromium_major: Optional[int] = dataclasses.field(init=False)

    _CHROMIUM_VERSIONS: ClassVar[Dict[utils.VersionNumber, str]] = {
        # Qt 5.12: Chromium 69
        # (LTS)    69.0.3497.128 (~2018-09-11)
        #          5.12.0: Security fixes up to 70.0.3538.102 (~2018-10-24)
        #          5.12.1: Security fixes up to 71.0.3578.94  (2018-12-12)
        #          5.12.2: Security fixes up to 72.0.3626.121 (2019-03-01)
        #          5.12.3: Security fixes up to 73.0.3683.75  (2019-03-12)
        #          5.12.4: Security fixes up to 74.0.3729.157 (2019-05-14)
        #          5.12.5: Security fixes up to 76.0.3809.87  (2019-07-30)
        #          5.12.6: Security fixes up to 77.0.3865.120 (~2019-09-10)
        #          5.12.7: Security fixes up to 79.0.3945.130 (2020-01-16)
        #          5.12.8: Security fixes up to 80.0.3987.149 (2020-03-18)
        #          5.12.9: Security fixes up to 83.0.4103.97  (2020-06-03)
        #          5.12.10: Security fixes up to 86.0.4240.75 (2020-10-06)
        utils.VersionNumber(5, 12):
        '69.0.3497.128',

        # Qt 5.13: Chromium 73
        #          73.0.3683.105 (~2019-02-28)
        #          5.13.0: Security fixes up to 74.0.3729.157 (2019-05-14)
        #          5.13.1: Security fixes up to 76.0.3809.87  (2019-07-30)
        #          5.13.2: Security fixes up to 77.0.3865.120 (2019-10-10)
        utils.VersionNumber(5, 13):
        '73.0.3683.105',

        # Qt 5.14: Chromium 77
        #          77.0.3865.129 (~2019-10-10)
        #          5.14.0: Security fixes up to 77.0.3865.129 (~2019-09-10)
        #          5.14.1: Security fixes up to 79.0.3945.117 (2020-01-07)
        #          5.14.2: Security fixes up to 80.0.3987.132 (2020-03-03)
        utils.VersionNumber(5, 14):
        '77.0.3865.129',

        # Qt 5.15: Chromium 80
        #          80.0.3987.163 (2020-04-02)
        #          5.15.0: Security fixes up to 81.0.4044.138 (2020-05-05)
        #          5.15.1: Security fixes up to 85.0.4183.83  (2020-08-25)
        #          5.15.2: Updated to 83.0.4103.122           (~2020-06-24)
        #                  Security fixes up to 86.0.4240.183 (2020-11-02)
        #          5.15.3: Updated to 87.0.4280.144           (~2020-12-02)
        #                  Security fixes up to 88.0.4324.150 (2021-02-04)
        utils.VersionNumber(5, 15):
        '80.0.3987.163',
        utils.VersionNumber(5, 15, 2):
        '83.0.4103.122',
        utils.VersionNumber(5, 15, 3):
        '87.0.4280.144',
    }

    def __post_init__(self) -> None:
        """Set the major Chromium version."""
        if self.chromium is None:
            self.chromium_major = None
        else:
            self.chromium_major = int(self.chromium.split('.')[0])

    def __str__(self) -> str:
        s = f'QtWebEngine {self.webengine}'
        if self.chromium is not None:
            s += f', based on Chromium {self.chromium}'
        if self.source != 'UA':
            s += f' (from {self.source})'
        return s

    @classmethod
    def from_ua(cls, ua: 'websettings.UserAgent') -> 'WebEngineVersions':
        """Get the versions parsed from a user agent.

        This is the most reliable and "default" way to get this information (at least
        until QtWebEngine adds an API for it). However, it needs a fully initialized
        QtWebEngine, and we sometimes need this information before that is available.
        """
        assert ua.qt_version is not None, ua
        return cls(
            webengine=utils.VersionNumber.parse(ua.qt_version),
            chromium=ua.upstream_browser_version,
            source='UA',
        )

    @classmethod
    def from_elf(cls, versions: elf.Versions) -> 'WebEngineVersions':
        """Get the versions based on an ELF file.

        This only works on Linux, and even there, depends on various assumption on how
        QtWebEngine is built (e.g. that the version string is in the .rodata section).

        On Windows/macOS, we instead rely on from_pyqt, but especially on Linux, people
        sometimes mix and match Qt/QtWebEngine versions, so this is a more reliable
        (though hackish) way to get a more accurate result.
        """
        return cls(
            webengine=utils.VersionNumber.parse(versions.webengine),
            chromium=versions.chromium,
            source='ELF',
        )

    @classmethod
    def _infer_chromium_version(
        cls,
        pyqt_webengine_version: utils.VersionNumber,
    ) -> Optional[str]:
        """Infer the Chromium version based on the PyQtWebEngine version."""
        chromium_version = cls._CHROMIUM_VERSIONS.get(pyqt_webengine_version)
        if chromium_version is not None:
            return chromium_version

        # 5.15 patch versions change their QtWebEngine version, but no changes are
        # expected after 5.15.3.
        v5_15_3 = utils.VersionNumber(5, 15, 3)
        if v5_15_3 <= pyqt_webengine_version < utils.VersionNumber(6):
            minor_version = v5_15_3
        else:
            # e.g. 5.14.2 -> 5.14
            minor_version = pyqt_webengine_version.strip_patch()

        return cls._CHROMIUM_VERSIONS.get(minor_version)

    @classmethod
    def from_importlib(cls,
                       pyqt_webengine_qt_version: str) -> 'WebEngineVersions':
        """Get the versions based on the PyQtWebEngine version.

        This is called if we don't want to fully initialize QtWebEngine (so
        from_ua isn't possible), we're not on Linux (or ELF parsing failed), but we have
        a PyQtWebEngine-Qt{,5} package from PyPI, so we could query its exact version.
        """
        parsed = utils.VersionNumber.parse(pyqt_webengine_qt_version)
        return cls(
            webengine=parsed,
            chromium=cls._infer_chromium_version(parsed),
            source='importlib',
        )

    @classmethod
    def from_pyqt(cls, pyqt_webengine_version: str) -> 'WebEngineVersions':
        """Get the versions based on the PyQtWebEngine version.

        This is the "last resort" if we don't want to fully initialize QtWebEngine (so
        from_ua isn't possible), we're not on Linux (or ELF parsing failed), and
        PyQtWebEngine-Qt{5,} isn't available from PyPI.

        Here, we assume that the PyQtWebEngine version is the same as the QtWebEngine
        version, and infer the Chromium version from that. This assumption isn't
        generally true, but good enough for some scenarios, especially the prebuilt
        Windows/macOS releases.
        """
        parsed = utils.VersionNumber.parse(pyqt_webengine_version)
        if utils.VersionNumber(5, 15, 3) <= parsed < utils.VersionNumber(6):
            # If we land here, we're in a tricky situation where we are forced to guess:
            #
            # PyQt 5.15.3 and 5.15.4 from PyPI come with QtWebEngine 5.15.2 (Chromium
            # 83), not 5.15.3 (Chromium 87). Given that there was no binary release of
            # QtWebEngine 5.15.3, this is unlikely to change before Qt 6.
            #
            # However, at this point:
            #
            # - ELF parsing failed
            #   (so we're likely on macOS or Windows, but not definitely)
            #
            # - Getting infos from a PyPI-installed PyQtWebEngine failed
            #   (so we're either in a PyInstaller-deployed qutebrowser, or a self-built
            #   or distribution-installed Qt)
            #
            # PyQt 5.15.3 and 5.15.4 come with QtWebEngine 5.15.2 (83-based), but if
            # someone lands here with the last Qt/PyQt installed from source, they might
            # be using QtWebEngine 5.15.3 (87-based). For now, we play it safe, and only
            # do this kind of "downgrade" when we know we're using PyInstaller.
            frozen = hasattr(sys, 'frozen')
            log.misc.debug(f"PyQt5 >= 5.15.3, frozen {frozen}")
            if frozen:
                parsed = utils.VersionNumber(5, 15, 2)

        return cls(
            webengine=parsed,
            chromium=cls._infer_chromium_version(parsed),
            source='PyQt',
        )

    @classmethod
    def from_qt(cls,
                qt_version: str,
                *,
                source: str = 'Qt') -> 'WebEngineVersions':
        """Get the versions based on the Qt version.

        This is called if we don't have PYQT_WEBENGINE_VERSION, i.e. with PyQt 5.12.
        """
        parsed = utils.VersionNumber.parse(qt_version)
        return cls(
            webengine=parsed,
            chromium=cls._infer_chromium_version(parsed),
            source=source,
        )
Exemplo n.º 16
0
def test_version_info(params, stubs, monkeypatch, config_stub):
    """Test version.version_info()."""
    config.instance.config_py_loaded = params.config_py_loaded
    import_path = pathlib.Path('/IMPORTPATH').resolve()

    patches = {
        'qutebrowser.__file__': str(import_path / '__init__.py'),
        'qutebrowser.__version__': 'VERSION',
        '_git_str': lambda: ('GIT COMMIT' if params.git_commit else None),
        'platform.python_implementation': lambda: 'PYTHON IMPLEMENTATION',
        'platform.python_version': lambda: 'PYTHON VERSION',
        'sys.executable': 'EXECUTABLE PATH',
        'PYQT_VERSION_STR': 'PYQT VERSION',
        'earlyinit.qt_version': lambda: 'QT VERSION',
        '_module_versions': lambda: ['MODULE VERSION 1', 'MODULE VERSION 2'],
        '_pdfjs_version': lambda: 'PDFJS VERSION',
        'QSslSocket': FakeQSslSocket('SSL VERSION', params.ssl_support),
        'platform.platform': lambda: 'PLATFORM',
        'platform.architecture': lambda: ('ARCHITECTURE', ''),
        '_os_info': lambda: ['OS INFO 1', 'OS INFO 2'],
        '_path_info': lambda: {'PATH DESC': 'PATH NAME'},
        'objects.qapp': (stubs.FakeQApplication(style='STYLE', platform_name='PLATFORM')
                         if params.qapp else None),
        'QLibraryInfo.location': (lambda _loc: 'QT PATH'),
        'sql.version': lambda: 'SQLITE VERSION',
        '_uptime': lambda: datetime.timedelta(hours=1, minutes=23, seconds=45),
        'config.instance.yaml_loaded': params.autoconfig_loaded,
    }

    version.opengl_info.cache_clear()
    monkeypatch.setenv('QUTE_FAKE_OPENGL', 'VENDOR, 1.0 VERSION')

    substitutions = {
        'git_commit': '\nGit commit: GIT COMMIT' if params.git_commit else '',
        'style': '\nStyle: STYLE' if params.qapp else '',
        'platform_plugin': ('\nPlatform plugin: PLATFORM' if params.qapp
                            else ''),
        'opengl': '\nOpenGL: VENDOR, 1.0 VERSION' if params.qapp else '',
        'qt': 'QT VERSION',
        'frozen': str(params.frozen),
        'import_path': import_path,
        'python_path': 'EXECUTABLE PATH',
        'uptime': "1:23:45",
        'autoconfig_loaded': "yes" if params.autoconfig_loaded else "no",
    }

    patches['qtwebengine_versions'] = (
        lambda avoid_init: version.WebEngineVersions(
            webengine=utils.VersionNumber(1, 2, 3),
            chromium=None,
            source='faked',
        )
    )

    if params.config_py_loaded:
        substitutions["config_py_loaded"] = "{} has been loaded".format(
            standarddir.config_py())
    else:
        substitutions["config_py_loaded"] = "no config.py was loaded"

    if params.with_webkit:
        patches['qWebKitVersion'] = lambda: 'WEBKIT VERSION'
        patches['objects.backend'] = usertypes.Backend.QtWebKit
        substitutions['backend'] = 'new QtWebKit (WebKit WEBKIT VERSION)'
    else:
        monkeypatch.delattr(version, 'qtutils.qWebKitVersion', raising=False)
        patches['objects.backend'] = usertypes.Backend.QtWebEngine
        substitutions['backend'] = 'QtWebEngine 1.2.3 (from faked)'

    if params.known_distribution:
        patches['distribution'] = lambda: version.DistributionInfo(
            parsed=version.Distribution.arch, version=None,
            pretty='LINUX DISTRIBUTION', id='arch')
        substitutions['linuxdist'] = ('\nLinux distribution: '
                                      'LINUX DISTRIBUTION (arch)')
        substitutions['osinfo'] = ''
    else:
        patches['distribution'] = lambda: None
        substitutions['linuxdist'] = ''
        substitutions['osinfo'] = 'OS INFO 1\nOS INFO 2\n'

    substitutions['ssl'] = 'SSL VERSION' if params.ssl_support else 'no'

    for name, val in patches.items():
        monkeypatch.setattr(f'qutebrowser.utils.version.{name}', val)

    if params.frozen:
        monkeypatch.setattr(sys, 'frozen', True, raising=False)
    else:
        monkeypatch.delattr(sys, 'frozen', raising=False)

    template = version._LOGO.lstrip('\n') + textwrap.dedent("""
        qutebrowser vVERSION{git_commit}
        Backend: {backend}
        Qt: {qt}

        PYTHON IMPLEMENTATION: PYTHON VERSION
        PyQt: PYQT VERSION

        MODULE VERSION 1
        MODULE VERSION 2
        pdf.js: PDFJS VERSION
        sqlite: SQLITE VERSION
        QtNetwork SSL: {ssl}
        {style}{platform_plugin}{opengl}
        Platform: PLATFORM, ARCHITECTURE{linuxdist}
        Frozen: {frozen}
        Imported from {import_path}
        Using Python from {python_path}
        Qt library executable path: QT PATH, data path: QT PATH
        {osinfo}
        Paths:
        PATH DESC: PATH NAME

        Autoconfig loaded: {autoconfig_loaded}
        Config.py: {config_py_loaded}
        Uptime: {uptime}
    """.lstrip('\n'))

    expected = template.rstrip('\n').format(**substitutions)
    assert version.version_info() == expected
Exemplo n.º 17
0
     version.DistributionInfo(id='arch',
                              parsed=version.Distribution.arch,
                              version=None,
                              pretty='Arch Linux')),
    # Ubuntu 14.04
    ("""
    NAME="Ubuntu"
    VERSION="14.04.5 LTS, Trusty Tahr"
    ID=ubuntu
    ID_LIKE=debian
    PRETTY_NAME="Ubuntu 14.04.5 LTS"
    VERSION_ID="14.04"
 """,
     version.DistributionInfo(id='ubuntu',
                              parsed=version.Distribution.ubuntu,
                              version=utils.VersionNumber(14, 4, 5),
                              pretty='Ubuntu 14.04.5 LTS')),
    # Ubuntu 17.04
    ("""
    NAME="Ubuntu"
    VERSION="17.04 (Zesty Zapus)"
    ID=ubuntu
    ID_LIKE=debian
    PRETTY_NAME="Ubuntu 17.04"
    VERSION_ID="17.04"
 """,
     version.DistributionInfo(id='ubuntu',
                              parsed=version.Distribution.ubuntu,
                              version=utils.VersionNumber(17, 4),
                              pretty='Ubuntu 17.04')),
    # Debian Jessie
Exemplo n.º 18
0
class WebEngineVersions:
    """Version numbers for QtWebEngine and the underlying Chromium."""

    webengine: utils.VersionNumber
    chromium: Optional[str]
    source: str
    chromium_major: Optional[int] = dataclasses.field(init=False)

    _CHROMIUM_VERSIONS: ClassVar[Dict[utils.VersionNumber, str]] = {
        # Qt 5.12: Chromium 69
        # (LTS)    69.0.3497.128 (~2018-09-11)
        #          5.12.0: Security fixes up to 70.0.3538.102 (~2018-10-24)
        #          5.12.1: Security fixes up to 71.0.3578.94  (2018-12-12)
        #          5.12.2: Security fixes up to 72.0.3626.121 (2019-03-01)
        #          5.12.3: Security fixes up to 73.0.3683.75  (2019-03-12)
        #          5.12.4: Security fixes up to 74.0.3729.157 (2019-05-14)
        #          5.12.5: Security fixes up to 76.0.3809.87  (2019-07-30)
        #          5.12.6: Security fixes up to 77.0.3865.120 (~2019-09-10)
        #          5.12.7: Security fixes up to 79.0.3945.130 (2020-01-16)
        #          5.12.8: Security fixes up to 80.0.3987.149 (2020-03-18)
        #          5.12.9: Security fixes up to 83.0.4103.97  (2020-06-03)
        #          5.12.10: Security fixes up to 86.0.4240.75 (2020-10-06)
        utils.VersionNumber(5, 12):
        '69.0.3497.128',

        # Qt 5.13: Chromium 73
        #          73.0.3683.105 (~2019-02-28)
        #          5.13.0: Security fixes up to 74.0.3729.157 (2019-05-14)
        #          5.13.1: Security fixes up to 76.0.3809.87  (2019-07-30)
        #          5.13.2: Security fixes up to 77.0.3865.120 (2019-10-10)
        utils.VersionNumber(5, 13):
        '73.0.3683.105',

        # Qt 5.14: Chromium 77
        #          77.0.3865.129 (~2019-10-10)
        #          5.14.0: Security fixes up to 77.0.3865.129 (~2019-09-10)
        #          5.14.1: Security fixes up to 79.0.3945.117 (2020-01-07)
        #          5.14.2: Security fixes up to 80.0.3987.132 (2020-03-03)
        utils.VersionNumber(5, 14):
        '77.0.3865.129',

        # Qt 5.15: Chromium 80
        #          80.0.3987.163 (2020-04-02)
        #          5.15.0: Security fixes up to 81.0.4044.138 (2020-05-05)
        #          5.15.1: Security fixes up to 85.0.4183.83  (2020-08-25)
        #          5.15.2: Updated to 83.0.4103.122           (~2020-06-24)
        #                  Security fixes up to 86.0.4240.183 (2020-11-02)
        #          5.15.3: Updated to 87.0.4280.144           (~2020-12-02)
        #                  Security fixes up to 88.0.4324.150 (2021-02-04)
        utils.VersionNumber(5, 15):
        '80.0.3987.163',
        utils.VersionNumber(5, 15, 2):
        '83.0.4103.122',
        utils.VersionNumber(5, 15, 3):
        '87.0.4280.144',
    }

    def __post_init__(self) -> None:
        """Set the major Chromium version."""
        if self.chromium is None:
            self.chromium_major = None
        else:
            self.chromium_major = int(self.chromium.split('.')[0])

    def __str__(self) -> str:
        s = f'QtWebEngine {self.webengine}'
        if self.chromium is not None:
            s += f', Chromium {self.chromium}'
        if self.source != 'UA':
            s += f' (from {self.source})'
        return s

    @classmethod
    def from_ua(cls, ua: websettings.UserAgent) -> 'WebEngineVersions':
        """Get the versions parsed from a user agent.

        This is the most reliable and "default" way to get this information (at least
        until QtWebEngine adds an API for it). However, it needs a fully initialized
        QtWebEngine, and we sometimes need this information before that is available.
        """
        assert ua.qt_version is not None, ua
        return cls(
            webengine=utils.VersionNumber.parse(ua.qt_version),
            chromium=ua.upstream_browser_version,
            source='UA',
        )

    @classmethod
    def from_elf(cls, versions: elf.Versions) -> 'WebEngineVersions':
        """Get the versions based on an ELF file.

        This only works on Linux, and even there, depends on various assumption on how
        QtWebEngine is built (e.g. that the version string is in the .rodata section).

        On Windows/macOS, we instead rely on from_pyqt, but especially on Linux, people
        sometimes mix and match Qt/QtWebEngine versions, so this is a more reliable
        (though hackish) way to get a more accurate result.
        """
        return cls(
            webengine=utils.VersionNumber.parse(versions.webengine),
            chromium=versions.chromium,
            source='ELF',
        )

    @classmethod
    def _infer_chromium_version(
        cls,
        pyqt_webengine_version: utils.VersionNumber,
    ) -> Optional[str]:
        """Infer the Chromium version based on the PyQtWebEngine version."""
        chromium_version = cls._CHROMIUM_VERSIONS.get(pyqt_webengine_version)
        if chromium_version is not None:
            return chromium_version

        # 5.15 patch versions change their QtWebEngine version, but no changes are
        # expected after 5.15.3.
        v5_15_3 = utils.VersionNumber(5, 15, 3)
        if v5_15_3 <= pyqt_webengine_version < utils.VersionNumber(6):
            minor_version = v5_15_3
        else:
            # e.g. 5.14.2 -> 5.14
            minor_version = pyqt_webengine_version.strip_patch()

        return cls._CHROMIUM_VERSIONS.get(minor_version)

    @classmethod
    def from_pyqt(
        cls,
        pyqt_webengine_version: str,
        source: str = 'PyQt',
    ) -> 'WebEngineVersions':
        """Get the versions based on the PyQtWebEngine version.

        This is the "last resort" if we don't want to fully initialize QtWebEngine (so
        from_ua isn't possible) and we're not on Linux (or ELF parsing failed).

        Here, we assume that the PyQtWebEngine version is the same as the QtWebEngine
        version, and infer the Chromium version from that. This assumption isn't
        generally true, but good enough for some scenarios, especially the prebuilt
        Windows/macOS releases.

        Note that we only can get the PyQtWebEngine version with PyQt 5.13 or newer.
        With Qt 5.12, we instead rely on qVersion().
        """
        parsed = utils.VersionNumber.parse(pyqt_webengine_version)
        return cls(
            webengine=parsed,
            chromium=cls._infer_chromium_version(parsed),
            source=source,
        )
Exemplo n.º 19
0
def qtwe_version():
    """A version number needing the workaround."""
    return utils.VersionNumber(5, 15, 3)
Exemplo n.º 20
0
        locale_name=locale_name,
    )

    locales_path = qtargs._webengine_locales_path()
    original_path = qtargs._get_locale_pak_path(locales_path, locale_name)

    if override is None:
        assert original_path.exists()
    else:
        assert override == expected
        assert not original_path.exists()
        assert qtargs._get_locale_pak_path(locales_path, override).exists()


@pytest.mark.parametrize('version', [
    utils.VersionNumber(5, 14, 2),
    utils.VersionNumber(5, 15, 2),
    utils.VersionNumber(5, 15, 4),
    utils.VersionNumber(6),
])
@pytest.mark.fake_os('linux')
def test_different_qt_version(version):
    assert qtargs._get_lang_override(version, "de-CH") is None


@pytest.mark.fake_os('windows')
def test_non_linux(qtwe_version):
    assert qtargs._get_lang_override(qtwe_version, "de-CH") is None


@pytest.mark.fake_os('linux')
Exemplo n.º 21
0
 def _find_quirks(  # noqa: C901 ("too complex"
     self,
     name: str,
     vendor: str,
     ver: str,
 ) -> Optional[_ServerQuirks]:
     """Find quirks to use based on the server information."""
     if (name, vendor) == ("notify-osd", "Canonical Ltd"):
         # Shows a dialog box instead of a notification bubble as soon as a
         # notification has an action (even if only a default one). Dialog boxes are
         # buggy and return a notification with ID 0.
         # https://wiki.ubuntu.com/NotificationDevelopmentGuidelines#Avoiding_actions
         return _ServerQuirks(avoid_actions=True, spec_version="1.1")
     elif (name, vendor) == ("Notification Daemon", "MATE"):
         # Still in active development but doesn't implement spec 1.2:
         # https://github.com/mate-desktop/mate-notification-daemon/issues/132
         quirks = _ServerQuirks(spec_version="1.1")
         if utils.VersionNumber.parse(ver) <= utils.VersionNumber(1, 24):
             # https://github.com/mate-desktop/mate-notification-daemon/issues/118
             quirks.avoid_body_hyperlinks = True
         return quirks
     elif (name, vendor) == ("naughty", "awesome") and ver != "devel":
         # Still in active development but spec 1.0/1.2 support isn't
         # released yet:
         # https://github.com/awesomeWM/awesome/commit/e076bc664e0764a3d3a0164dabd9b58d334355f4
         parsed_version = utils.VersionNumber.parse(ver.lstrip('v'))
         if parsed_version <= utils.VersionNumber(4, 3):
             return _ServerQuirks(spec_version="1.0")
     elif (name, vendor) == ("twmnd", "twmnd"):
         # https://github.com/sboli/twmn/pull/96
         return _ServerQuirks(spec_version="0")
     elif (name, vendor) == ("tiramisu", "Sweets"):
         if utils.VersionNumber.parse(ver) < utils.VersionNumber(2, 0):
             # https://github.com/Sweets/tiramisu/issues/20
             return _ServerQuirks(skip_capabilities=True)
     elif (name, vendor) == ("lxqt-notificationd", "lxqt.org"):
         quirks = _ServerQuirks()
         parsed_version = utils.VersionNumber.parse(ver)
         if parsed_version <= utils.VersionNumber(0, 16):
             # https://github.com/lxqt/lxqt-notificationd/issues/253
             quirks.escape_title = True
         if parsed_version < utils.VersionNumber(0, 16):
             # https://github.com/lxqt/lxqt-notificationd/commit/c23e254a63c39837fb69d5c59c5e2bc91e83df8c
             quirks.icon_key = 'image_data'
         return quirks
     elif (name, vendor) == ("haskell-notification-daemon",
                             "abc"):  # aka "deadd"
         return _ServerQuirks(
             # https://github.com/phuhl/linux_notification_center/issues/160
             spec_version="1.0",
             # https://github.com/phuhl/linux_notification_center/issues/161
             wrong_replaces_id=True,
         )
     elif (name, vendor) == ("ninomiya", "deifactor"):
         return _ServerQuirks(
             no_padded_images=True,
             wrong_replaces_id=True,
         )
     elif (name, vendor) == ("Raven", "Budgie Desktop Developers"):
         # Before refactor
         return _ServerQuirks(
             # https://github.com/solus-project/budgie-desktop/issues/2114
             escape_title=True,
             # https://github.com/solus-project/budgie-desktop/issues/2115
             wrong_replaces_id=True,
         )
     elif (name, vendor) == ("Budgie Notification Server",
                             "Budgie Desktop Developers"):
         # After refactor: https://github.com/BuddiesOfBudgie/budgie-desktop/pull/36
         if utils.VersionNumber.parse(ver) < utils.VersionNumber(10, 6, 2):
             return _ServerQuirks(
                 # https://github.com/BuddiesOfBudgie/budgie-desktop/issues/118
                 wrong_closes_type=True, )
     return None
Exemplo n.º 22
0
def _notifications_supported() -> bool:
    """Check whether the current QtWebEngine version has notification support."""
    versions = version.qtwebengine_versions(avoid_init=True)
    return versions.webengine >= utils.VersionNumber(5, 14)
Exemplo n.º 23
0
  """,
  version.DistributionInfo(
      id='arch', parsed=version.Distribution.arch, version=None,
      pretty='Arch Linux')),
 # Ubuntu 14.04
 ("""
     NAME="Ubuntu"
     VERSION="14.04.5 LTS, Trusty Tahr"
     ID=ubuntu
     ID_LIKE=debian
     PRETTY_NAME="Ubuntu 14.04.5 LTS"
     VERSION_ID="14.04"
  """,
  version.DistributionInfo(
      id='ubuntu', parsed=version.Distribution.ubuntu,
      version=utils.VersionNumber(14, 4, 5),
      pretty='Ubuntu 14.04.5 LTS')),
 # Ubuntu 17.04
 ("""
     NAME="Ubuntu"
     VERSION="17.04 (Zesty Zapus)"
     ID=ubuntu
     ID_LIKE=debian
     PRETTY_NAME="Ubuntu 17.04"
     VERSION_ID="17.04"
  """,
  version.DistributionInfo(
      id='ubuntu', parsed=version.Distribution.ubuntu,
      version=utils.VersionNumber(17, 4),
      pretty='Ubuntu 17.04')),
 # Debian Jessie
Exemplo n.º 24
0
class TestChromiumVersion:

    @pytest.fixture(autouse=True)
    def clear_parsed_ua(self, monkeypatch):
        pytest.importorskip('PyQt5.QtWebEngineWidgets')
        if webenginesettings is not None:
            # Not available with QtWebKit
            monkeypatch.setattr(webenginesettings, 'parsed_user_agent', None)

    def test_fake_ua(self, monkeypatch, caplog):
        ver = '77.0.3865.98'
        webenginesettings._init_user_agent_str(_QTWE_USER_AGENT.format(ver))

        assert version.qtwebengine_versions().chromium == ver

    def test_prefers_saved_user_agent(self, monkeypatch):
        webenginesettings._init_user_agent_str(_QTWE_USER_AGENT.format('87'))

        class FakeProfile:
            def defaultProfile(self):
                raise AssertionError("Should not be called")

        monkeypatch.setattr(webenginesettings, 'QWebEngineProfile', FakeProfile())

        version.qtwebengine_versions()

    def test_unpatched(self, qapp, cache_tmpdir, data_tmpdir, config_stub):
        assert version.qtwebengine_versions().chromium is not None

    def test_avoided(self, monkeypatch):
        versions = version.qtwebengine_versions(avoid_init=True)
        assert versions.source in ['ELF', 'importlib', 'PyQt', 'Qt']

    @pytest.fixture
    def patch_elf_fail(self, monkeypatch):
        """Simulate parsing the version from ELF to fail."""
        monkeypatch.setattr(elf, 'parse_webenginecore', lambda: None)

    @pytest.fixture
    def patch_old_pyqt(self, monkeypatch):
        """Simulate an old PyQt without PYQT_WEBENGINE_VERSION_STR."""
        monkeypatch.setattr(version, 'PYQT_WEBENGINE_VERSION_STR', None)

    @pytest.fixture
    def patch_no_importlib(self, monkeypatch, stubs):
        """Simulate missing importlib modules."""
        import_fake = stubs.ImportFake({
            'importlib_metadata': False,
            'importlib.metadata': False,
        }, monkeypatch)
        import_fake.patch()

    @pytest.fixture
    def importlib_patcher(self, monkeypatch):
        """Patch the importlib module."""
        def _patch(*, qt, qt5):
            try:
                import importlib.metadata as importlib_metadata
            except ImportError:
                importlib_metadata = pytest.importorskip("importlib_metadata")

            def _fake_version(name):
                if name == 'PyQtWebEngine-Qt':
                    outcome = qt
                elif name == 'PyQtWebEngine-Qt5':
                    outcome = qt5
                else:
                    raise utils.Unreachable(outcome)

                if outcome is None:
                    raise importlib_metadata.PackageNotFoundError(name)
                return outcome

            monkeypatch.setattr(importlib_metadata, 'version', _fake_version)

        return _patch

    @pytest.fixture
    def patch_importlib_no_package(self, importlib_patcher):
        """Simulate importlib not finding PyQtWebEngine-Qt[5]."""
        importlib_patcher(qt=None, qt5=None)

    @pytest.mark.parametrize('patches, sources', [
        (['elf_fail'], ['importlib', 'PyQt', 'Qt']),
        (['elf_fail', 'old_pyqt'], ['importlib', 'Qt']),
        (['elf_fail', 'no_importlib'], ['PyQt', 'Qt']),
        (['elf_fail', 'no_importlib', 'old_pyqt'], ['Qt']),
        (['elf_fail', 'importlib_no_package'], ['PyQt', 'Qt']),
        (['elf_fail', 'importlib_no_package', 'old_pyqt'], ['Qt']),
    ], ids=','.join)
    def test_simulated(self, request, patches, sources):
        """Test various simulated error conditions.

        This dynamically gets a list of fixtures (above) to do the patching. It then
        checks whether the version it got is from one of the expected sources. Depending
        on the environment this test is run in, some sources might fail "naturally",
        i.e. without any patching related to them.
        """
        for patch in patches:
            request.getfixturevalue(f'patch_{patch}')

        versions = version.qtwebengine_versions(avoid_init=True)
        assert versions.source in sources

    @pytest.mark.parametrize('qt, qt5, expected', [
        (None, '5.15.4', utils.VersionNumber(5, 15, 4)),
        ('5.15.3', None, utils.VersionNumber(5, 15, 3)),
        ('5.15.3', '5.15.4', utils.VersionNumber(5, 15, 4)),  # -Qt5 takes precedence
    ])
    def test_importlib(self, qt, qt5, expected, patch_elf_fail, importlib_patcher):
        """Test the importlib version logic with different Qt packages.

        With PyQtWebEngine 5.15.4, PyQtWebEngine-Qt was renamed to PyQtWebEngine-Qt5.
        """
        importlib_patcher(qt=qt, qt5=qt5)
        versions = version.qtwebengine_versions(avoid_init=True)
        assert versions.source == 'importlib'
        assert versions.webengine == expected
Exemplo n.º 25
0
def _qtwebengine_settings_args(versions: version.WebEngineVersions) -> 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,
        }
    }
    qt_514_ver = utils.VersionNumber(5, 14)

    if qt_514_ver <= versions.webengine < utils.VersionNumber(5, 15, 2):
        # 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.preferred_color_scheme'] = {
            'dark': '--force-dark-mode',
            'light': None,
            'auto': None,
        }

    referrer_setting = settings['content.headers.referer']
    if versions.webengine >= qt_514_ver:
        # Starting with Qt 5.14, this is handled via --enable-features
        referrer_setting['same-domain'] = None
    else:
        referrer_setting['same-domain'] = '--reduced-referrer-granularity'

    # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-60203
    can_override_referer = (
        versions.webengine >= utils.VersionNumber(5, 12, 4) and
        versions.webengine != utils.VersionNumber(5, 13)
    )
    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
Exemplo n.º 26
0
class TestWebEngineVersions:

    @pytest.mark.parametrize('version, expected', [
        (
            version.WebEngineVersions(
                webengine=utils.VersionNumber(5, 15, 2),
                chromium=None,
                source='UA'),
            "QtWebEngine 5.15.2",
        ),
        (
            version.WebEngineVersions(
                webengine=utils.VersionNumber(5, 15, 2),
                chromium='87.0.4280.144',
                source='UA'),
            "QtWebEngine 5.15.2, Chromium 87.0.4280.144",
        ),
        (
            version.WebEngineVersions(
                webengine=utils.VersionNumber(5, 15, 2),
                chromium='87.0.4280.144',
                source='faked'),
            "QtWebEngine 5.15.2, Chromium 87.0.4280.144 (from faked)",
        ),
    ])
    def test_str(self, version, expected):
        assert str(version) == expected

    @pytest.mark.parametrize('version, expected', [
        (
            version.WebEngineVersions(
                webengine=utils.VersionNumber(5, 15, 2),
                chromium=None,
                source='test'),
            None,
        ),
        (
            version.WebEngineVersions(
                webengine=utils.VersionNumber(5, 15, 2),
                chromium='87.0.4280.144',
                source='test'),
            87,
        ),
    ])
    def test_chromium_major(self, version, expected):
        assert version.chromium_major == expected

    def test_from_ua(self):
        ua = websettings.UserAgent(
            os_info='X11; Linux x86_64',
            webkit_version='537.36',
            upstream_browser_key='Chrome',
            upstream_browser_version='83.0.4103.122',
            qt_key='QtWebEngine',
            qt_version='5.15.2',
        )
        expected = version.WebEngineVersions(
            webengine=utils.VersionNumber(5, 15, 2),
            chromium='83.0.4103.122',
            source='UA',
        )
        assert version.WebEngineVersions.from_ua(ua) == expected

    def test_from_elf(self):
        elf_version = elf.Versions(webengine='5.15.2', chromium='83.0.4103.122')
        expected = version.WebEngineVersions(
            webengine=utils.VersionNumber(5, 15, 2),
            chromium='83.0.4103.122',
            source='ELF',
        )
        assert version.WebEngineVersions.from_elf(elf_version) == expected

    @pytest.mark.parametrize('pyqt_version, chromium_version', [
        ('5.12.10', '69.0.3497.128'),
        ('5.14.2', '77.0.3865.129'),
        ('5.15.1', '80.0.3987.163'),
        ('5.15.2', '83.0.4103.122'),
        ('5.15.3', '87.0.4280.144'),
        ('5.15.4', '87.0.4280.144'),
        ('5.15.5', '87.0.4280.144'),
    ])
    def test_from_pyqt(self, freezer, pyqt_version, chromium_version):
        if freezer and pyqt_version in ['5.15.3', '5.15.4', '5.15.5']:
            chromium_version = '83.0.4103.122'
            expected_pyqt_version = '5.15.2'
        else:
            expected_pyqt_version = pyqt_version

        expected = version.WebEngineVersions(
            webengine=utils.VersionNumber.parse(expected_pyqt_version),
            chromium=chromium_version,
            source='PyQt',
        )
        assert version.WebEngineVersions.from_pyqt(pyqt_version) == expected

    def test_real_chromium_version(self, qapp):
        """Compare the inferred Chromium version with the real one."""
        if '.dev' in PYQT_VERSION_STR:
            pytest.skip("dev version of PyQt5")

        try:
            from PyQt5.QtWebEngine import PYQT_WEBENGINE_VERSION_STR
        except ImportError as e:
            # QtWebKit or QtWebEngine < 5.1'../3
            pytest.skip(str(e))

        pyqt_webengine_version = version._get_pyqt_webengine_qt_version()
        if pyqt_webengine_version is None:
            pyqt_webengine_version = PYQT_WEBENGINE_VERSION_STR

        versions = version.WebEngineVersions.from_pyqt(pyqt_webengine_version)

        webenginesettings.init_user_agent()
        expected = webenginesettings.parsed_user_agent.upstream_browser_version

        assert versions.chromium == expected
Exemplo n.º 27
0
def _qtwebengine_features(
        versions: version.WebEngineVersions,
        special_flags: Sequence[str],
) -> Tuple[Sequence[str], Sequence[str]]:
    """Get a tuple of --enable-features/--disable-features flags for QtWebEngine.

    Args:
        versions: The WebEngineVersions to get flags for.
        special_flags: Existing flags passed via the commandline.
    """
    enabled_features = []
    disabled_features = []

    for flag in special_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(',')
        elif flag.startswith(_BLINK_SETTINGS):
            pass
        else:
            raise utils.Unreachable(flag)

    if versions.webengine >= utils.VersionNumber(5, 15, 1) 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.
        #
        # 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 (versions.webengine >= utils.VersionNumber(5, 14) 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 (presumably arriving with Qt 6.2):
        # https://chromium-review.googlesource.com/c/chromium/src/+/2545444
        enabled_features.append('ReducedReferrerGranularity')

    if versions.webengine == utils.VersionNumber(5, 15, 2):
        # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-89740
        disabled_features.append('InstalledApp')

    if not config.val.input.media_keys:
        disabled_features.append('HardwareMediaKeyHandling')

    return (enabled_features, disabled_features)
Exemplo n.º 28
0
def gentoo_versions():
    return version.WebEngineVersions(
        webengine=utils.VersionNumber(5, 15, 2),
        chromium='87.0.4280.144',
        source='faked',
    )
Exemplo n.º 29
0
class TestWebEngineVersions:

    @pytest.mark.parametrize('version, expected', [
        (
            version.WebEngineVersions(
                webengine=utils.VersionNumber(5, 15, 2),
                chromium=None,
                source='UA'),
            "QtWebEngine 5.15.2",
        ),
        (
            version.WebEngineVersions(
                webengine=utils.VersionNumber(5, 15, 2),
                chromium='87.0.4280.144',
                source='UA'),
            "QtWebEngine 5.15.2, Chromium 87.0.4280.144",
        ),
        (
            version.WebEngineVersions(
                webengine=utils.VersionNumber(5, 15, 2),
                chromium='87.0.4280.144',
                source='faked'),
            "QtWebEngine 5.15.2, Chromium 87.0.4280.144 (from faked)",
        ),
    ])
    def test_str(self, version, expected):
        assert str(version) == expected

    def test_from_ua(self):
        ua = websettings.UserAgent(
            os_info='X11; Linux x86_64',
            webkit_version='537.36',
            upstream_browser_key='Chrome',
            upstream_browser_version='83.0.4103.122',
            qt_key='QtWebEngine',
            qt_version='5.15.2',
        )
        expected = version.WebEngineVersions(
            webengine=utils.VersionNumber(5, 15, 2),
            chromium='83.0.4103.122',
            source='UA',
        )
        assert version.WebEngineVersions.from_ua(ua) == expected

    def test_from_elf(self):
        elf_version = elf.Versions(webengine='5.15.2', chromium='83.0.4103.122')
        expected = version.WebEngineVersions(
            webengine=utils.VersionNumber(5, 15, 2),
            chromium='83.0.4103.122',
            source='ELF',
        )
        assert version.WebEngineVersions.from_elf(elf_version) == expected

    def test_from_pyqt(self):
        expected = version.WebEngineVersions(
            webengine=utils.VersionNumber(5, 15, 2),
            chromium='83.0.4103.122',
            source='PyQt',
        )
        assert version.WebEngineVersions.from_pyqt('5.15.2') == expected

    def test_real_chromium_version(self, qapp):
        """Compare the inferred Chromium version with the real one."""
        try:
            from PyQt5.QtWebEngine import PYQT_WEBENGINE_VERSION_STR
        except ImportError as e:
            # QtWebKit or QtWebEngine < 5.13
            pytest.skip(str(e))

        from qutebrowser.browser.webengine import webenginesettings
        webenginesettings.init_user_agent()
        expected = webenginesettings.parsed_user_agent.upstream_browser_version

        versions = version.WebEngineVersions.from_pyqt(PYQT_WEBENGINE_VERSION_STR)
        assert versions.chromium == expected