Qt: Open links with target in default browser, without leaking memory
Asked Answered
J

3

7

Searching through the Internet, I've come across so many ways, mostly nonfunctional, nonspecific, or partially functional, to do various things with QWebView and opening URLs.

After much swearing and cursing, I've managed to get an example to do what I want, which is open normal links normally, and open anything that requests a new window in the external browser; however, there's a hitch. It leaks memory, because I make a bunch of extra WebViews that aren't cleaned up until the process exits. How can I do this without leaking memory?

Please forgive my rather sophomoric understanding of Qt in advance. I've only been using it for a handful of hours at this point.

SSCCE:

test.hpp

#include <QMainWindow>
#include <QWebView>

class Window : public QMainWindow {
  Q_OBJECT

public:
  Window();

private:
  QWebView* m_web;

private slots:
};

class WebPage : public QWebPage {
  Q_OBJECT

public:
    bool acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type);
};

class WebView : public QWebView {
  Q_OBJECT

public:
  QWebView* createWindow(QWebPage::WebWindowType type);
};

test.cpp

#include <QApplication>
#include <QGridLayout>
#include <QNetworkRequest>
#include <QDesktopServices>

#include "test.hpp"

Window::Window() :
    QMainWindow() {
  m_web = new WebView;
  m_web->setHtml("<div align=\"center\"><a href=\"http://www.google.com/\">Same Window</a> <a href=\"http://www.google.com/\" target=\"_blank\">New Window</a></div>");

  setCentralWidget(m_web);
}

bool WebPage::acceptNavigationRequest(QWebFrame*, QNetworkRequest const& request, NavigationType) {
  QDesktopServices::openUrl(request.url());
  return false;
}

QWebView* WebView::createWindow(QWebPage::WebWindowType) {
  auto res = new WebView;
  auto page = new WebPage;
  res->setPage(page);
  return res;
}

int main(int argc, char *argv[]) {
  QApplication a(argc, argv);

  Window w;
  w.show();

  return a.exec();
}

test.pro

QT += core gui network webkitwidgets widgets

TEMPLATE = app
TARGET = test
INCLUDEPATH += .

CONFIG += c++11

# Input
SOURCES += test.cpp
HEADERS += test.hpp

To Compile and Run

qmake test.pro
make
./test
Jablon answered 1/12, 2013 at 3:14 Comment(6)
how do you know there are leaks?Housebroken
I don't! I merely suspect. I don't quite understand how Qt works, but I am newing an Object then throwing it away. If the answer is "there is no leak, stop being stupid." And you can back that up somehow, then I'll accept that.Jablon
did you try to find memory leaks, for example with valgring (if you are under linux)?Payment
anyway m_web does it's never deleted. you should specify the parent in constructor.Payment
@Jepessen, I assume the memory will never be marked as leaked in valgrind because Qt's libraries are still holding onto them. Keeping a pointer to them and then manually requesting to delete them emits a non-fatal error to the console. I just don't quite understand how to get rid of them properly, or if there's a much better approach to what I'm trying to do that avoids the problem altogether.Jablon
Using naive approach you could also override the QWebView destructor and add some logging there, Or implement some memory leak debugging macros - both would tell you if destructor of ur class is actually called. m_web is set as centralwidget - so it is indeed deleted when its parent dies.Thionate
E
4

It seems the view becomes useless after the page is rendered in the extern browser. You might just schedule the ExternalWebView for deletetion with deleteLater():

#include <iostream>

#include <QApplication>
#include <QGridLayout>
#include <QNetworkRequest>
#include <QDesktopServices>

#include <QEvent>
#include <QMainWindow>
#include <QWebView>

class ExternWebPage : public QWebPage {
    //Q_OBJECT

    public:
    ExternWebPage(QObject* parent = 0)
    :   QWebPage(parent)
    {
        std::cout << "ExternWebPage" << std::endl;
    }

    ~ExternWebPage() {
        std::cout << "Destroy ExternWebPage" << std::endl;
    }

    virtual bool event(QEvent *e) {
        static unsigned counter;
        std::cout << ++counter << " ExternWebPage: " << e->type() << std::endl;
        return QWebPage::event(e);
    }

    bool acceptNavigationRequest(QWebFrame *, const QNetworkRequest &request, NavigationType) {
        QDesktopServices::openUrl(request.url());
        return false;
    }
};


class ExternWebView : public QWebView {
    //Q_OBJECT

    public:
    ExternWebView(QWidget* parent = 0)
    :   QWebView(parent)
    {
        std::cout << "ExternWebView" << std::endl;
    }
    ~ExternWebView() { std::cout << "Destroy ExternWebView" << std::endl; }
    virtual bool event(QEvent *e) {
        static unsigned counter;
        std::cout << ++counter << " ExternWebView: " << e->type() << std::endl;
        return QWebView::event(e);
    }
};


class InternalWebView : public QWebView {
    //Q_OBJECT

    public:
    InternalWebView(QWidget* parent = 0)
    :   QWebView(parent)
    {}
    QWebView* createWindow(QWebPage::WebWindowType) {
        auto res = new ExternWebView();
        res->setPage(new ExternWebPage(res));
        res->deleteLater();
        return res;
    }
};


class Window : public QMainWindow {
    //Q_OBJECT

    public:
    Window()
    :   QMainWindow()
    {
        std::cout << "Window" << std::endl;
        auto web = new InternalWebView(this);
        web->setHtml("<div align=\"center\"><a href=\"http://www.google.com/\">Same Window</a> <a href=\"http://www.google.com/\" target=\"_blank\">New Window</a></div>");
        setCentralWidget(web);
    }
    ~Window() { std::cout << "Destroy Window" << std::endl; }

};


int main(int argc, char *argv[]) {
  QApplication a(argc, argv);
  Window w;
  w.show();
  return a.exec();
}

Test without deletion:

Window
ExternWebView
1 ExternWebView: 68
ExternWebPage
2 ExternWebView: 74
3 ExternWebView: 75
1 ExternWebPage: 43
2 ExternWebPage: 43
3 ExternWebPage: 43
4 ExternWebPage: 43
5 ExternWebPage: 43
Destroy Window

Test with delete later:

ExternWebView
1 ExternWebView: 68
ExternWebPage
2 ExternWebView: 74
3 ExternWebView: 75
4 ExternWebView: 52
Destroy ExternWebView
Destroy ExternWebPage
Destroy Window
Embolden answered 3/12, 2013 at 16:59 Comment(3)
Thank you. I think this is exactly what I need. I had previously attempted something like this, but ran into an issue where it was giving me an error. I cannot test this today, but tomorrow afternoonish, I'll give it a shot, and accept this answer and apply bounty if it works.Jablon
A little bit of an update, things have been very crazy for me lately, I'm going to try this out today. Sorry about the delay.Jablon
Ugh, I haven't actually had a chance to test this, but rather than screwing you over, I went ahead and awarded the bounty. I'm sure it'll work.Jablon
P
1
QDesktopServices::openUrl(QUrl("http://stackoverflow.com/"));

Since this is the in-built function, there shouldn't be any memory leaks.

Propagandist answered 3/12, 2013 at 15:12 Comment(1)
Sorry if it's unclear, this isn't why I think it's leaking memory. The reason why I think it's leaking memory is I'm creating a QWebView and applying a QWebPage, returning them, then not using or showing them, rejecting the navigation request. So the program just holds another invisible copy of a QWebView for every new link I click within my application.Jablon
G
0

There are no leaks in the program, because the qt memory management system is going to take care of the objects created on the heap.

First, setPage is going to make your view object parent of the page object. That means that the page object will be deleted when the view object gets destroyed.

Second, since the view has no parent, you are always going to get a window. It will be released when you close the window, or end the program. That is why I said it is going to be taken cared by the qt's memory management system.

Now, when you run your program using memory profiler program (like valgrind), you may get leaks, which may or may not be real leaks. You need to identify them, and filter them out.

Glenn answered 4/12, 2013 at 7:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.