Exemple #1
0
 def _find_prevnext(self, frame, prev=False):
     """Find a prev/next element in frame."""
     # First check for <link rel="prev(ious)|next">
     elems = frame.findAllElements(webelem.SELECTORS[webelem.Group.links])
     rel_values = ('prev', 'previous') if prev else ('next')
     for e in elems:
         e = webelem.WebElementWrapper(e)
         try:
             rel_attr = e['rel']
         except KeyError:
             continue
         if rel_attr in rel_values:
             log.hints.debug("Found '{}' with rel={}".format(
                 e.debug_text(), rel_attr))
             return e
     # Then check for regular links/buttons.
     elems = frame.findAllElements(
         webelem.SELECTORS[webelem.Group.prevnext])
     option = 'prev-regexes' if prev else 'next-regexes'
     if not elems:
         return None
     for regex in config.get('hints', option):
         log.hints.vdebug("== Checking regex '{}'.".format(regex.pattern))
         for e in elems:
             e = webelem.WebElementWrapper(e)
             text = str(e)
             if not text:
                 continue
             if regex.search(text):
                 log.hints.debug("Regex '{}' matched on '{}'.".format(
                     regex.pattern, text))
                 return e
             else:
                 log.hints.vdebug("No match on '{}'!".format(text))
     return None
Exemple #2
0
    def _init_elements(self, mainframe, group):
        """Initialize the elements and labels based on the context set.

        Args:
            mainframe: The main QWebFrame.
            group: A Group enum member (which elements to find).
        """
        elems = []
        for f in self._context.frames:
            elems += f.findAllElements(webelem.SELECTORS[group])
        elems = [e for e in elems if webelem.is_visible(e, mainframe)]
        # We wrap the elements late for performance reasons, as wrapping 1000s
        # of elements (with ~50 methods each) just takes too much time...
        elems = [webelem.WebElementWrapper(e) for e in elems]
        filterfunc = webelem.FILTERS.get(group, lambda e: True)
        elems = [e for e in elems if filterfunc(e)]
        if not elems:
            raise cmdexc.CommandError("No elements found.")
        strings = self._hint_strings(elems)
        for e, string in zip(elems, strings):
            label = self._draw_label(e, string)
            self._context.elems[string] = ElemTuple(e, label)
        keyparsers = objreg.get('keyparsers',
                                scope='window',
                                window=self._win_id)
        keyparser = keyparsers[usertypes.KeyMode.hint]
        keyparser.update_bindings(strings)
Exemple #3
0
 def on_load_finished(self, ok):
     """Handle auto-insert-mode after loading finished."""
     if ok and not self._has_ssl_errors:
         self._set_load_status(LoadStatus.success)
     elif ok:
         self._set_load_status(LoadStatus.warn)
     else:
         self._set_load_status(LoadStatus.error)
     if not config.get('input', 'auto-insert-mode'):
         return
     mode_manager = objreg.get('mode-manager',
                               scope='window',
                               window=self._win_id)
     cur_mode = mode_manager.mode()
     if cur_mode == usertypes.KeyMode.insert or not ok:
         return
     frame = self.page().currentFrame()
     try:
         elem = webelem.WebElementWrapper(frame.findFirstElement(':focus'))
     except webelem.IsNullError:
         log.webview.debug("Focused element is null!")
         return
     log.modes.debug("focus element: {}".format(repr(elem)))
     if elem.is_editable():
         modeman.maybe_enter(self._win_id, usertypes.KeyMode.insert,
                             'load finished')
Exemple #4
0
    def _draw_label(self, elem, string):
        """Draw a hint label over an element.

        Args:
            elem: The QWebElement to use.
            string: The hint string to print.

        Return:
            The newly created label element
        """
        doc = elem.webFrame().documentElement()
        # It seems impossible to create an empty QWebElement for which isNull()
        # is false so we can work with it.
        # As a workaround, we use appendInside() with markup as argument, and
        # then use lastChild() to get a reference to it.
        # See: http://stackoverflow.com/q/7364852/2085149
        body = doc.findFirst('body')
        if not body.isNull():
            parent = body
        else:
            parent = doc
        parent.appendInside('<span></span>')
        label = webelem.WebElementWrapper(parent.lastChild())
        label['class'] = 'qutehint'
        self._set_style_properties(elem, label)
        label.setPlainText(string)
        return label
Exemple #5
0
    def on_load_finished(self):
        """Handle auto-insert-mode after loading finished.

        We don't take loadFinished's ok argument here as it always seems to be
        true when the QWebPage has an ErrorPageExtension implemented.
        See https://github.com/The-Compiler/qutebrowser/issues/84
        """
        ok = not self.page().error_occured
        if ok and not self._has_ssl_errors:
            self._set_load_status(LoadStatus.success)
        elif ok:
            self._set_load_status(LoadStatus.warn)
        else:
            self._set_load_status(LoadStatus.error)
        if not config.get('input', 'auto-insert-mode'):
            return
        mode_manager = objreg.get('mode-manager',
                                  scope='window',
                                  window=self._win_id)
        cur_mode = mode_manager.mode()
        if cur_mode == usertypes.KeyMode.insert or not ok:
            return
        frame = self.page().currentFrame()
        try:
            elem = webelem.WebElementWrapper(frame.findFirstElement(':focus'))
        except webelem.IsNullError:
            log.webview.debug("Focused element is null!")
            return
        log.modes.debug("focus element: {}".format(repr(elem)))
        if elem.is_editable():
            modeman.maybe_enter(self._win_id, usertypes.KeyMode.insert,
                                'load finished')
Exemple #6
0
 def _init_elements(self):
     """Initialize the elements and labels based on the context set."""
     elems = []
     for f in self._context.frames:
         elems += f.findAllElements(webelem.SELECTORS[self._context.group])
     elems = [
         e for e in elems if webelem.is_visible(e, self._context.mainframe)
     ]
     # We wrap the elements late for performance reasons, as wrapping 1000s
     # of elements (with ~50 methods each) just takes too much time...
     elems = [webelem.WebElementWrapper(e) for e in elems]
     filterfunc = webelem.FILTERS.get(self._context.group, lambda e: True)
     elems = [e for e in elems if filterfunc(e)]
     if not elems:
         raise cmdexc.CommandError("No elements found.")
     strings = self._hint_strings(elems)
     log.hints.debug("hints: {}".format(', '.join(strings)))
     for e, string in zip(elems, strings):
         label = self._draw_label(e, string)
         elem = ElemTuple(e, label)
         self._context.all_elems.append(elem)
         self._context.elems[string] = elem
     keyparsers = objreg.get('keyparsers',
                             scope='window',
                             window=self._win_id)
     keyparser = keyparsers[usertypes.KeyMode.hint]
     keyparser.update_bindings(strings)
Exemple #7
0
def get_webelem(geometry=None,
                frame=None,
                null=False,
                visibility='',
                display='',
                attributes=None,
                tagname=None,
                classes=None):
    """Factory for WebElementWrapper objects based on a mock.

    Args:
        geometry: The geometry of the QWebElement as QRect.
        frame: The QWebFrame the element is in.
        null: Whether the element is null or not.
        visibility: The CSS visibility style property value.
        display: The CSS display style property value.
        attributes: Boolean HTML attributes to be added.
        tagname: The tag name.
        classes: HTML classes to be added.
    """
    elem = mock.Mock()
    elem.isNull.return_value = null
    elem.geometry.return_value = geometry
    elem.webFrame.return_value = frame
    elem.tagName.return_value = tagname
    elem.toOuterXml.return_value = '<fakeelem/>'
    if attributes is not None:
        if not isinstance(attributes, collections.abc.Mapping):
            attributes = {e: None for e in attributes}
        elem.hasAttribute.side_effect = lambda k: k in attributes
        elem.attribute.side_effect = lambda k: attributes.get(k, '')
        elem.attributeNames.return_value = list(attributes)
    else:
        elem.hasAttribute.return_value = False
        elem.attribute.return_value = ''
        elem.attributeNames.return_value = []
    if classes is not None:
        elem.classes.return_value = classes.split(' ')
    else:
        elem.classes.return_value = []

    def _style_property(name, strategy):
        """Helper function to act as styleProperty method."""
        if strategy != QWebElement.ComputedStyle:
            raise ValueError("styleProperty called with strategy != "
                             "ComputedStyle ({})!".format(strategy))
        if name == 'visibility':
            return visibility
        elif name == 'display':
            return display
        else:
            raise ValueError("styleProperty called with unknown name "
                             "'{}'".format(name))

    elem.styleProperty.side_effect = _style_property
    wrapped = webelem.WebElementWrapper(elem)
    if attributes is not None:
        wrapped.update(attributes)
    return wrapped
Exemple #8
0
 def test_selectors(self, webframe, group, val, matching):
     webframe.setHtml('<html><body>{}</body></html>'.format(val))
     # Make sure setting HTML succeeded and there's a new element
     assert len(webframe.findAllElements('*')) == 3
     elems = webframe.findAllElements(webelem.SELECTORS[group])
     elems = [webelem.WebElementWrapper(e) for e in elems]
     filterfunc = webelem.FILTERS.get(group, lambda e: True)
     elems = [e for e in elems if filterfunc(e)]
     assert bool(elems) == matching
Exemple #9
0
def get_webelem(geometry=None, frame=None, null=False, style=None,
                display='', attributes=None, tagname=None, classes=None):
    """Factory for WebElementWrapper objects based on a mock.

    Args:
        geometry: The geometry of the QWebElement as QRect.
        frame: The QWebFrame the element is in.
        null: Whether the element is null or not.
        style: A dict with the styleAttributes of the element.
        attributes: Boolean HTML attributes to be added.
        tagname: The tag name.
        classes: HTML classes to be added.
    """
    elem = mock.Mock()
    elem.isNull.return_value = null
    elem.geometry.return_value = geometry
    elem.webFrame.return_value = frame
    elem.tagName.return_value = tagname
    elem.toOuterXml.return_value = '<fakeelem/>'
    elem.toPlainText.return_value = 'text'

    attribute_dict = {}
    if attributes is None:
        pass
    elif not isinstance(attributes, collections.abc.Mapping):
        attribute_dict.update({e: None for e in attributes})
    else:
        attribute_dict.update(attributes)

    elem.hasAttribute.side_effect = lambda k: k in attribute_dict
    elem.attribute.side_effect = lambda k: attribute_dict.get(k, '')
    elem.setAttribute.side_effect = (lambda k, v:
        operator.setitem(attribute_dict, k, v))
    elem.removeAttribute.side_effect = attribute_dict.pop
    elem.attributeNames.return_value = list(attribute_dict)

    if classes is not None:
        elem.classes.return_value = classes.split(' ')
    else:
        elem.classes.return_value = []

    style_dict = {'visibility': '', 'display': ''}
    if style is not None:
        style_dict.update(style)

    def _style_property(name, strategy):
        """Helper function to act as styleProperty method."""
        if strategy != QWebElement.ComputedStyle:
            raise ValueError("styleProperty called with strategy != "
                             "ComputedStyle ({})!".format(strategy))
        return style_dict[name]

    elem.styleProperty.side_effect = _style_property
    wrapped = webelem.WebElementWrapper(elem)
    return wrapped
Exemple #10
0
    def _mousepress_insertmode(self, e):
        """Switch to insert mode when an editable element was clicked.

        Args:
            e: The QMouseEvent.
        """
        pos = e.pos()
        frame = self.page().frameAt(pos)
        if frame is None:
            # This happens when we click inside the webview, but not actually
            # on the QWebPage - for example when clicking the scrollbar
            # sometimes.
            log.mouse.debug("Clicked at {} but frame is None!".format(pos))
            return
        # You'd think we have to subtract frame.geometry().topLeft() from the
        # position, but it seems QWebFrame::hitTestContent wants a position
        # relative to the QWebView, not to the frame. This makes no sense to
        # me, but it works this way.
        hitresult = frame.hitTestContent(pos)
        if hitresult.isNull():
            # For some reason, the whole hit result can be null sometimes (e.g.
            # on doodle menu links). If this is the case, we schedule a check
            # later (in mouseReleaseEvent) which uses webelem.focus_elem.
            log.mouse.debug("Hitresult is null!")
            self._check_insertmode = True
            return
        try:
            elem = webelem.WebElementWrapper(hitresult.element())
        except webelem.IsNullError:
            # For some reason, the hit result element can be a null element
            # sometimes (e.g. when clicking the timetable fields on
            # http://www.sbb.ch/ ). If this is the case, we schedule a check
            # later (in mouseReleaseEvent) which uses webelem.focus_elem.
            log.mouse.debug("Hitresult element is null!")
            self._check_insertmode = True
            return
        if ((hitresult.isContentEditable() and elem.is_writable())
                or elem.is_editable()):
            log.mouse.debug("Clicked editable element!")
            modeman.enter(self.win_id,
                          usertypes.KeyMode.insert,
                          'click',
                          only_if_normal=True)
        else:
            log.mouse.debug("Clicked non-editable element!")
            if config.get('input', 'auto-leave-insert-mode'):
                modeman.maybe_leave(self.win_id, usertypes.KeyMode.insert,
                                    'click')
Exemple #11
0
 def _handle_auto_insert_mode(self, ok):
     """Handle auto-insert-mode after loading finished."""
     if not config.get('input', 'auto-insert-mode'):
         return
     mode_manager = objreg.get('mode-manager', scope='window',
                               window=self.win_id)
     cur_mode = mode_manager.mode
     if cur_mode == usertypes.KeyMode.insert or not ok:
         return
     frame = self.page().currentFrame()
     try:
         elem = webelem.WebElementWrapper(frame.findFirstElement(':focus'))
     except webelem.IsNullError:
         log.webview.debug("Focused element is null!")
         return
     log.modes.debug("focus element: {}".format(repr(elem)))
     if elem.is_editable():
         modeman.enter(self.win_id, usertypes.KeyMode.insert,
                       'load finished', only_if_normal=True)
Exemple #12
0
 def test_double_wrap(self, elem):
     """Test wrapping a WebElementWrapper."""
     with pytest.raises(TypeError) as excinfo:
         webelem.WebElementWrapper(elem)
     assert str(excinfo.value) == "Trying to wrap a wrapper!"
Exemple #13
0
    def run(self):
        """Download and save the page.

        The object must not be reused, you should create a new one if
        you want to download another page.
        """
        if self._used:
            raise ValueError("Downloader already used")
        self._used = True
        web_url = self.web_view.url()
        web_frame = self.web_view.page().mainFrame()

        self.writer = MHTMLWriter(
            web_frame.toHtml().encode('utf-8'),
            content_location=urlutils.encoded_url(web_url),
            # I've found no way of getting the content type of a QWebView, but
            # since we're using .toHtml, it's probably safe to say that the
            # content-type is HTML
            content_type='text/html; charset="UTF-8"',
        )
        # Currently only downloading <link> (stylesheets), <script>
        # (javascript) and <img> (image) elements.
        elements = web_frame.findAllElements('link, script, img')

        for element in elements:
            element = webelem.WebElementWrapper(element)
            # Websites are free to set whatever rel=... attribute they want.
            # We just care about stylesheets and icons.
            if not _check_rel(element):
                continue
            if 'src' in element:
                element_url = element['src']
            elif 'href' in element:
                element_url = element['href']
            else:
                # Might be a local <script> tag or something else
                continue
            absolute_url = web_url.resolved(QUrl(element_url))
            self._fetch_url(absolute_url)

        styles = web_frame.findAllElements('style')
        for style in styles:
            style = webelem.WebElementWrapper(style)
            # The Mozilla Developer Network says:
            # type: This attribute defines the styling language as a MIME type
            # (charset should not be specified). This attribute is optional and
            # default to text/css if it's missing.
            # https://developer.mozilla.org/en/docs/Web/HTML/Element/style
            if 'type' in style and style['type'] != 'text/css':
                continue
            for element_url in _get_css_imports(str(style)):
                self._fetch_url(web_url.resolved(QUrl(element_url)))

        # Search for references in inline styles
        for element in web_frame.findAllElements('[style]'):
            element = webelem.WebElementWrapper(element)
            style = element['style']
            for element_url in _get_css_imports(style, inline=True):
                self._fetch_url(web_url.resolved(QUrl(element_url)))

        # Shortcut if no assets need to be downloaded, otherwise the file would
        # never be saved. Also might happen if the downloads are fast enough to
        # complete before connecting their finished signal.
        self._collect_zombies()
        if not self.pending_downloads and not self._finished_file:
            self._finish_file()
Exemple #14
0
def get_webelem(geometry=None, frame=None, *, null=False, style=None,
                attributes=None, tagname=None, classes=None,
                parent=None, js_rect_return=None, zoom_text_only=False):
    """Factory for WebElementWrapper objects based on a mock.

    Args:
        geometry: The geometry of the QWebElement as QRect.
        frame: The QWebFrame the element is in.
        null: Whether the element is null or not.
        style: A dict with the styleAttributes of the element.
        attributes: Boolean HTML attributes to be added.
        tagname: The tag name.
        classes: HTML classes to be added.
        js_rect_return: If None, what evaluateJavaScript returns is based on
                        geometry. If set, the return value of
                        evaluateJavaScript.
        zoom_text_only: Whether zoom-text-only is set in the config
    """
    # pylint: disable=too-many-locals,too-many-branches
    elem = mock.Mock()
    elem.isNull.return_value = null
    elem.geometry.return_value = geometry
    elem.webFrame.return_value = frame
    elem.tagName.return_value = tagname
    elem.toOuterXml.return_value = '<fakeelem/>'
    elem.toPlainText.return_value = 'text'
    elem.parent.return_value = parent

    if geometry is not None:
        if frame is None:
            scroll_x = 0
            scroll_y = 0
        else:
            scroll_x = frame.scrollPosition().x()
            scroll_y = frame.scrollPosition().y()
        if js_rect_return is None:
            if frame is None or zoom_text_only:
                zoom = 1.0
            else:
                zoom = frame.zoomFactor()
            elem.evaluateJavaScript.return_value = {
                "length": 1,
                "0": {
                    "left": (geometry.left() - scroll_x) / zoom,
                    "top": (geometry.top() - scroll_y) / zoom,
                    "right": (geometry.right() - scroll_x) / zoom,
                    "bottom": (geometry.bottom() - scroll_y) / zoom,
                    "width": geometry.width() / zoom,
                    "height": geometry.height() / zoom,
                }
            }
        else:
            elem.evaluateJavaScript.return_value = js_rect_return

    attribute_dict = {}
    if attributes is None:
        pass
    elif not isinstance(attributes, collections.abc.Mapping):
        attribute_dict.update({e: None for e in attributes})
    else:
        attribute_dict.update(attributes)

    elem.hasAttribute.side_effect = lambda k: k in attribute_dict
    elem.attribute.side_effect = lambda k: attribute_dict.get(k, '')
    elem.setAttribute.side_effect = (lambda k, v:
                                     operator.setitem(attribute_dict, k, v))
    elem.removeAttribute.side_effect = attribute_dict.pop
    elem.attributeNames.return_value = list(attribute_dict)

    if classes is not None:
        elem.classes.return_value = classes.split(' ')
    else:
        elem.classes.return_value = []

    style_dict = {'visibility': '', 'display': ''}
    if style is not None:
        style_dict.update(style)

    def _style_property(name, strategy):
        """Helper function to act as styleProperty method."""
        if strategy != QWebElement.ComputedStyle:
            raise ValueError("styleProperty called with strategy != "
                             "ComputedStyle ({})!".format(strategy))
        return style_dict[name]

    elem.styleProperty.side_effect = _style_property
    wrapped = webelem.WebElementWrapper(elem)
    return wrapped