pythonpysideqwebviewqwebkitqwebpage

Error: Internal C++ object (PySide.QtWebKit.QWebFrame) already deleted; but I'm saving it as an attribute to avoid this exact issue


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?


Solution

  • 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.