1

I'm working on a project where I use a modified QWebView. I'm getting this error:

Traceback (most recent call last):
  File "/home/jorge/coders/universal-scraper/src/customwebview.py", line 63, in mouseMoveEvent            
    hittestresult = self.currentframe.hitTestContent(event.pos())
RuntimeError: Internal C++ object (PySide.QtWebKit.QWebFrame) already deleted.

I have read about PySide pitfalls already, and I've been saving that QtWebKit.QWebFrame object as an attribute of my modified QWebView with the method setframeafterloadfinished which is called when the page finishes loading, the problem raised after some majors changes that I needed on my modified QWebView happened, before that, everything went just fine.

Functional and minimal example is here (just be sure to put the file webelementinfo.py in the same directory as this code before running a test):

#!/usr/bin/env python2
# coding: utf-8
#                        VENI, SANCTE SPIRITUS

from PySide.QtWebKit import QWebView
from PySide import QtCore, QtGui
try:
    from . import webelementinfo
except ValueError:
    import webelementinfo


class CustomQWebView(QWebView):

    def __init__(self, *args, **kwargs):
        """ Init the custom class
        """
        super(CustomQWebView, self).__init__(*args, **kwargs)
        self.colors = {0: QtGui.QColor(255, 165, 0, 127),
                       1: QtGui.QColor(135, 206, 235, 127),
                       2: QtGui.QColor(135, 235, 164, 127),
                       3: QtGui.QColor(235, 135, 206, 127),
                       4: QtGui.QColor(235, 164, 135, 127)}
        self.color = None
        self.currentframe = None
        self.element = None
        self.loadFinished.connect(self.setframeafterloadfinished)
        self.selectCommentsArea()

    @QtCore.Slot()
    def selectCommentsArea(self):
        """ For selecting the comment area
        """
        self.setup_rectcolor_area(0)

    @QtCore.Slot(QtGui.QMouseEvent)
    def mouseMoveEvent(self, event):
        super(CustomQWebView, self).mouseMoveEvent(event)

        if self.drawrects:
            if self.currentframe:
                hittestresult = self.currentframe.hitTestContent(event.pos())
                element = webelementinfo.WebElement(
                    hittestresult, self.color, self)
                if not self.element:
                    self.element = element
                elif self.element != element:
                    self.element = element

                # FIXME: self.update should draw rects from WebElements too.
                self.update()

    @QtCore.Slot(QtGui.QPaintEvent)
    def paintEvent(self, event):
        # draw the content first
        super(CustomQWebView, self).paintEvent(event)

        if self.drawrects:
            # then the rectangle
            if self.element:
                self.element.update()

    def setframeafterloadfinished(self):
        self.currentframe = self.page().mainFrame()

    def setup_rectcolor_area(self, forarea):
        """Called when we want to select certain area of a web site

        This method set-up the painter to a giving color so web elements are
        drawn with a rect on top. Also activates the flag to allow painting
        inside CustomQWebView.

        :param int forarea: For which area we are going to set the painter\\
        valid values are: 0 for Comments area, 1 for comment box, 2 for\\
        commentator's user name, 3 for comment date and time, 4 for\\
        commentary text.
        """
        self.drawrects = True
        self.color = self.colors[forarea]

        # defines what we are looking to select
        self.selecttype = forarea

if __name__ == "__main__":
    app = QtGui.QApplication([])
    mainwn = QtGui.QMainWindow()
    mainwn.resize(800, 696)
    centralwidget = QtGui.QWidget(mainwn)
    centralwidget.resize(800, 600)
    gridlayout = QtGui.QGridLayout(centralwidget)
    web = CustomQWebView(parent=centralwidget)
    gridlayout.addWidget(web, 0, 0, 1)
    web.setUrl(QtCore.QUrl("http://duckduckgo.com"))
    mainwn.show()

    app.exec_()

Here is the other file that have the definition of that new class WebElement that I write and start to use:

#!/usr/bin/env python2
# coding: utf-8
#                        VENI, SANCTE SPIRITUS

from PySide.QtWebKit import QWebElement, QWebHitTestResult
from PySide import QtGui
from PySide import QtCore


class WebElement(QtCore.QObject):

    """ Holds information of webelements
    """

    def __eq__(self, other):
        if isinstance(other, WebElement):
            return (self.web_element == other.web_element and
                    self.getrect() == other.getrect())
        else:
            raise ValueError("Not same objects")

    def __ne__(self, other):
        if isinstance(other, WebElement):
            return (self.web_element != other.web_element and
                    self.getrect() != other.getrect())
        else:
            raise ValueError("Not same objects")

    def __init__(self, hittestresult, color, parent=None):
        super(WebElement, self).__init__(parent)

        if (not isinstance(hittestresult, QWebHitTestResult) and
                not isinstance(hittestresult, QWebElement)):
            raise ValueError(
                "Argument passed for 'hittestresult' is not"
                " QtWebkit.QWenHitTestResult or QtWebkit.QWebElement instance"
            )
        if not isinstance(color, QtGui.QColor):
            raise ValueError(
                "Argument passed for 'color' is not QtGui.QColor instance"
            )

        try:
            self.frame = hittestresult.frame()
        except AttributeError:
            self.frame = hittestresult.webFrame()

        self.frame_scroll_x = self.frame.scrollPosition().x()
        self.frame_scroll_y = self.frame.scrollPosition().y()

        try:
            rect = hittestresult.boundingRect()
        except AttributeError:
            rect = hittestresult.geometry()

        self.element_rect_x = rect.x()
        self.element_rect_y = rect.y()
        self.element_rect_w = rect.width()
        self.element_rect_h = rect.height()

        try:
            self.web_element = hittestresult.element()
        except AttributeError:
            self.web_element = hittestresult

        self.color = color
        self.color_darker = color.darker()
        self.color_darker.setAlpha(255)
        self.pen = QtGui.QPen(self.color_darker)
        self.pen.setWidth(2)
        #self.painter = QtGui.QPainter(self.parent)
        self.painter = QtGui.QPainter()
        self.painter.setPen(self.pen)

    def update(self):
        """ draw the rect for this element in the CustomQWebView
        """
        rect = self.getrect()
        rectf = QtCore.QRectF(rect)
        self.painter.fillRect(rectf, self.color)
        self.painter.drawRect(rectf)

    def getrect(self):
        """ Return the rect for this WebElement
        """
        self.frame_scroll_x = self.frame.scrollPosition().x()
        self.frame_scroll_y = self.frame.scrollPosition().y()
        rect = QtCore.QRect()
        rect.setRect(self.element_rect_x - self.frame_scroll_x,
                     self.element_rect_y - self.frame_scroll_y,
                     self.element_rect_w, self.element_rect_h)
        return rect

My project should work right as if I didn't change anything, however, with these changes it doesn't. What am I doing wrong? Am I missing something about QWebFrames?

shackra
  • 277
  • 3
  • 16
  • 56
  • This is not a [Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve). Nine times out of ten, if you go to the trouble of trying to produce an MCVE, you will find the problem before you finish writing it. – ekhumoro Nov 04 '14 at 16:37
  • I edited my question and include the complete of the relevant source code – shackra Nov 04 '14 at 17:01
  • That is still not an MCVE. – ekhumoro Nov 04 '14 at 17:42
  • 1
    Please read the linked advice in my first comment. Your examples currently do nothing, and will not produce the error at the beginning of your question. – ekhumoro Nov 04 '14 at 18:44
  • Ok, ok, I modified the file that contains the modified Qt class and it shows the widget, reproducing the exact same error I'm experiencing (but occurring in two different places in the same code), it only requires to have PySide installed and available to the application, as well the file defining the class WebElement – shackra Nov 04 '14 at 19:20
  • Just a note; If you create a QWidget and don't provide it with a parent, you're still leaving control of when the underlying object is deleted to Qt, not PySide. It's hard to tell if this is exactly what is happening (there is a lot to trace through) but it's something to check. – aruisdante Nov 04 '14 at 19:25
  • @JorgeArayaNavarro. It's a PySide bug. If you do as I suggested in my first comment above, you'll easily find it. Happy hunting! – ekhumoro Nov 04 '14 at 23:16
  • ekhumoro, I don't think I can make it more simpler, the error still exists even if I instance my custom `QWebView` and use the `show` method. – shackra Nov 07 '14 at 23:57

1 Answers1

2

First of all, your example is not minimal: the file "webelementinfo.py" is irrelevant (as well as many other parts of your classes), what's causing the problem is the QWebHitTestResult.frame() method. The code that is sufficient for the error to occur is as follows:

@QtCore.Slot(QtGui.QMouseEvent)
def mouseMoveEvent(self, event):
    if self.currentframe:
        hittestresult = self.currentframe.hitTestContent(event.pos())
        hittestresult.frame() # <- will cause the crash on next mouseMoveEvent

As noted by ekhumoro this looks like a bug in PySide, related to object ownership. You need to avoid calling the frame() method - it seems it's not really essential in your code. Change WebElement's constructor to this:

def __init__(self, frame, hittestresult, color, parent=None):

and then:

#try:
#    self.frame = hittestresult.frame()     <-- DO NOT CALL THIS
#except AttributeError:
#    self.frame = hittestresult.webFrame()

self.frame = frame

And pass the frame explicitly:

hittestresult = self.currentframe.hitTestContent(event.pos())

element = webelementinfo.WebElement(
    self.currentframe, 
    hittestresult,
    self.color,
    self)

With these fixes the "Internal C++ object (PySide.QtWebKit.QWebFrame) already deleted." error does not occur.

Community
  • 1
  • 1
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
  • Thanks for the answer. Maybe my understanding of *functional* conflicts with my understanding of *minimal*... – shackra Nov 08 '14 at 21:07