Commit 7d39fa4a authored by Jocelyn Turcotte's avatar Jocelyn Turcotte Committed by The Qt Project
Browse files

createWindow QML API for QQuickWebEngineView


This implements adoptNewWindow for QQuickWebEngineView.
The API is only intended to be used through QML to avoid delegating
the QQuickWebEngineViewHandle ownership through a signal parameter.

Another limitation of the implementation is currently to fail the
handle adoption unless it is done synchronously within the
adoptNewWindow call. To support this we would need to delay the call
to WebContentsAdapter::initialize which will leave the adapter
without a client when returning to the event loop and would require
putting null checks everywhere it is used.
So I would prefer to keep the API limited and avoid potential crashes.
If we want to support asynchronous Loader elements or QML files
fetched from the network in the future, the API should be able to
scale to the task once we've adjusted the implementation.

This also adds basic tabs support in the quicknanobrowser example.
The url property is now set imperatively to avoid overwriting the
adopted WebContentsAdapter's loading URL.

Change-Id: Iba5c5dc3ffa21045f356be131ca15c01b9aee7c8
Reviewed-by: default avatarPierre Rossi <pierre.rossi@gmail.com>
Reviewed-by: default avatarZeno Albisser <zeno.albisser@digia.com>
Showing with 168 additions and 21 deletions
...@@ -54,7 +54,6 @@ class Utils : public QObject { ...@@ -54,7 +54,6 @@ class Utils : public QObject {
public: public:
Utils(QObject* parent = 0) : QObject(parent) { } Utils(QObject* parent = 0) : QObject(parent) { }
Q_INVOKABLE static QUrl fromUserInput(const QString& userInput) { return urlFromUserInput(userInput); } Q_INVOKABLE static QUrl fromUserInput(const QString& userInput) { return urlFromUserInput(userInput); }
Q_INVOKABLE static QUrl initialUrl() { return startupUrl(); }
}; };
#include "quickwindow.moc" #include "quickwindow.moc"
...@@ -63,4 +62,5 @@ ApplicationEngine::ApplicationEngine() ...@@ -63,4 +62,5 @@ ApplicationEngine::ApplicationEngine()
{ {
rootContext()->setContextProperty("utils", new Utils(this)); rootContext()->setContextProperty("utils", new Utils(this));
load(QUrl("qrc:/quickwindow.qml")); load(QUrl("qrc:/quickwindow.qml"));
QMetaObject::invokeMethod(rootObjects().first(), "load", Q_ARG(QVariant, startupUrl()));
} }
...@@ -40,26 +40,46 @@ ...@@ -40,26 +40,46 @@
import QtQuick 2.0 import QtQuick 2.0
import QtWebEngine 1.0 import QtWebEngine 1.0
import QtWebEngine.experimental 1.0
import QtQuick.Controls 1.0 import QtQuick.Controls 1.0
import QtQuick.Controls.Styles 1.0 import QtQuick.Controls.Styles 1.0
import QtQuick.Layouts 1.0 import QtQuick.Layouts 1.0
ApplicationWindow { ApplicationWindow {
id: browserWindow id: browserWindow
function load(url) { tabs.currentView.url = url }
function adoptHandle(viewHandle) { tabs.currentView.adoptHandle(viewHandle) }
height: 600 height: 600
width: 800 width: 800
visible: true visible: true
title: webEngineView.title title: tabs.currentView && tabs.currentView.title
// Focus and select text in URL bar
Action { Action {
id: focus id: focus
shortcut: "Ctrl+L" // How to have Cmd + L on Mac ? shortcut: "Ctrl+L"
onTriggered: { onTriggered: {
addressBar.forceActiveFocus(); addressBar.forceActiveFocus();
addressBar.selectAll(); addressBar.selectAll();
} }
} }
Action {
shortcut: "Ctrl+T"
onTriggered: {
tabs.createEmptyTab()
addressBar.forceActiveFocus();
addressBar.selectAll();
}
}
Action {
shortcut: "Ctrl+W"
onTriggered: {
if (tabs.count == 1)
browserWindow.close()
else
tabs.removeTab(tabs.currentIndex)
}
}
toolBar: ToolBar { toolBar: ToolBar {
id: navigationBar id: navigationBar
...@@ -68,19 +88,19 @@ ApplicationWindow { ...@@ -68,19 +88,19 @@ ApplicationWindow {
ToolButton { ToolButton {
id: backButton id: backButton
iconSource: "icons/go-previous.png" iconSource: "icons/go-previous.png"
onClicked: webEngineView.goBack() onClicked: tabs.currentView.goBack()
enabled: webEngineView.canGoBack enabled: tabs.currentView && tabs.currentView.canGoBack
} }
ToolButton { ToolButton {
id: forwardButton id: forwardButton
iconSource: "icons/go-next.png" iconSource: "icons/go-next.png"
onClicked: webEngineView.goForward() onClicked: tabs.currentView.goForward()
enabled: webEngineView.canGoForward enabled: tabs.currentView && tabs.currentView.canGoForward
} }
ToolButton { ToolButton {
id: reloadButton id: reloadButton
iconSource: webEngineView.loading ? "icons/process-stop.png" : "icons/view-refresh.png" iconSource: tabs.currentView && tabs.currentView.loading ? "icons/process-stop.png" : "icons/view-refresh.png"
onClicked: webEngineView.reload() onClicked: tabs.currentView.reload()
} }
TextField { TextField {
id: addressBar id: addressBar
...@@ -90,6 +110,7 @@ ApplicationWindow { ...@@ -90,6 +110,7 @@ ApplicationWindow {
z: 2 z: 2
id: faviconImage id: faviconImage
width: 16; height: 16 width: 16; height: 16
source: tabs.currentView && tabs.currentView.icon
} }
style: TextFieldStyle { style: TextFieldStyle {
padding { padding {
...@@ -98,7 +119,8 @@ ApplicationWindow { ...@@ -98,7 +119,8 @@ ApplicationWindow {
} }
focus: true focus: true
Layout.fillWidth: true Layout.fillWidth: true
onAccepted: webEngineView.url = utils.fromUserInput(text) text: tabs.currentView && tabs.currentView.url
onAccepted: tabs.currentView.url = utils.fromUserInput(text)
} }
} }
ProgressBar { ProgressBar {
...@@ -117,16 +139,46 @@ ApplicationWindow { ...@@ -117,16 +139,46 @@ ApplicationWindow {
z: -2; z: -2;
minimumValue: 0 minimumValue: 0
maximumValue: 100 maximumValue: 100
value: tabs.currentView && tabs.currentView.loadProgress
} }
} }
WebEngineView {
id: webEngineView TabView {
focus: true id: tabs
property Item currentView: currentIndex < count ? getTab(currentIndex).item : null
function createEmptyTab() {
var tab = addTab("", tabComponent)
// We must do this first to make sure that tab.active gets set so that tab.item gets instantiated immediately.
tabs.currentIndex = tabs.count - 1
tab.title = Qt.binding(function() { return tab.item.title })
return tab
}
anchors.fill: parent anchors.fill: parent
url: utils.initialUrl() Component.onCompleted: createEmptyTab()
Component {
id: tabComponent
WebEngineView {
function adoptHandle(viewHandle) { experimental.adoptHandle(viewHandle) }
focus: true
onUrlChanged: addressBar.text = url experimental {
onIconChanged: faviconImage.source = icon onCreateWindow: {
onLoadProgressChanged: progressBar.value = loadProgress if (newViewDisposition == "popup")
print("Warning: Ignored a popup window.")
else if (newViewDisposition == "tab") {
var tab = tabs.createEmptyTab()
tab.item.adoptHandle(newViewHandle)
} else {
var component = Qt.createComponent("quickwindow.qml")
var window = component.createObject()
window.adoptHandle(newViewHandle)
}
}
}
}
}
} }
} }
...@@ -47,6 +47,7 @@ ...@@ -47,6 +47,7 @@
#include <QScreen> #include <QScreen>
#include <QUrl> #include <QUrl>
#include <QQmlEngine>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
...@@ -158,9 +159,48 @@ void QQuickWebEngineViewPrivate::focusContainer() ...@@ -158,9 +159,48 @@ void QQuickWebEngineViewPrivate::focusContainer()
void QQuickWebEngineViewPrivate::adoptNewWindow(WebContentsAdapter *newWebContents, WindowOpenDisposition disposition, const QRect &) void QQuickWebEngineViewPrivate::adoptNewWindow(WebContentsAdapter *newWebContents, WindowOpenDisposition disposition, const QRect &)
{ {
Q_UNUSED(newWebContents); Q_Q(QQuickWebEngineView);
Q_UNUSED(disposition); QQmlEngine *engine = QtQml::qmlEngine(q);
Q_UNREACHABLE(); // This is currently only supported for QML instantiated WebEngineViews.
// We could emit a QObject* and set JavaScriptOwnership explicitly on it
// but this would make the signal cumbersome to use in C++ where one, and
// only one, of the connected slots would have to destroy the given handle.
// A virtual method instead of a signal would work better in this case.
if (!engine)
return;
static const QMetaMethod createWindowSignal = QMetaMethod::fromSignal(&QQuickWebEngineViewExperimental::createWindow);
if (!e->isSignalConnected(createWindowSignal))
return;
QQuickWebEngineViewHandle *handle = new QQuickWebEngineViewHandle;
// This increases the ref-count of newWebContents and will tell Chromium
// to start loading it and possibly return it to its parent page window.open().
handle->adapter = newWebContents;
// Clearly mark our wrapper as owned by JavaScript, we then depend on it
// being adopted or else eventually cleaned up by the GC.
QJSValue jsHandle = engine->newQObject(handle);
QString dispositionString;
switch (disposition) {
case WebContentsAdapterClient::NewForegroundTabDisposition:
case WebContentsAdapterClient::NewBackgroundTabDisposition:
dispositionString = QStringLiteral("tab");
break;
case WebContentsAdapterClient::NewPopupDisposition:
dispositionString = QStringLiteral("popup");
break;
case WebContentsAdapterClient::NewWindowDisposition:
dispositionString = QStringLiteral("window");
break;
default:
Q_UNREACHABLE();
}
emit e->createWindow(jsHandle, dispositionString);
// We currently require the adoption to happen within the signal handler to avoid having
// to support a null WebContentsAdapterClient for too long after having returned.
handle->adapter.reset();
} }
void QQuickWebEngineViewPrivate::close() void QQuickWebEngineViewPrivate::close()
...@@ -285,6 +325,14 @@ void QQuickWebEngineView::geometryChanged(const QRectF &newGeometry, const QRect ...@@ -285,6 +325,14 @@ void QQuickWebEngineView::geometryChanged(const QRectF &newGeometry, const QRect
} }
} }
QQuickWebEngineViewHandle::QQuickWebEngineViewHandle()
{
}
QQuickWebEngineViewHandle::~QQuickWebEngineViewHandle()
{
}
QQuickWebEngineViewExperimental::QQuickWebEngineViewExperimental(QQuickWebEngineViewPrivate *viewPrivate) QQuickWebEngineViewExperimental::QQuickWebEngineViewExperimental(QQuickWebEngineViewPrivate *viewPrivate)
: q_ptr(0) : q_ptr(0)
, d_ptr(viewPrivate) , d_ptr(viewPrivate)
...@@ -320,4 +368,34 @@ void QQuickWebEngineViewport::setDevicePixelRatio(qreal devicePixelRatio) ...@@ -320,4 +368,34 @@ void QQuickWebEngineViewport::setDevicePixelRatio(qreal devicePixelRatio)
Q_EMIT devicePixelRatioChanged(); Q_EMIT devicePixelRatioChanged();
} }
void QQuickWebEngineViewExperimental::adoptHandle(QQuickWebEngineViewHandle *viewHandle)
{
if (!viewHandle || !viewHandle->adapter) {
qWarning("Trying to adopt an empty handle, it was either already adopted or was invalidated."
"\nYou must do the adoption synchronously within the createWindow signal handler."
" If the handle hasn't been adopted before returning, it will be invalidated.");
return;
}
Q_Q(QQuickWebEngineView);
Q_D(QQuickWebEngineView);
// This throws away the WebContentsAdapter that has been used until now.
// All its states, particularly the loading URL, are replaced by the adopted WebContentsAdapter.
d->adapter = viewHandle->adapter;
viewHandle->adapter.reset();
d->adapter->initialize(d);
// Emit signals for values that might be different from the previous WebContentsAdapter.
emit q->titleChanged();
emit q->urlChanged();
emit q->iconChanged();
emit q->loadingStateChanged();
emit q->loadProgressChanged();
}
QT_END_NAMESPACE QT_END_NAMESPACE
#include "moc_qquickwebengineview_p.cpp"
#include "moc_qquickwebengineview_p_p.cpp"
...@@ -71,11 +71,27 @@ private: ...@@ -71,11 +71,27 @@ private:
Q_DECLARE_PRIVATE(QQuickWebEngineView) Q_DECLARE_PRIVATE(QQuickWebEngineView)
}; };
class Q_WEBENGINE_PRIVATE_EXPORT QQuickWebEngineViewHandle : public QObject {
Q_OBJECT
public:
QQuickWebEngineViewHandle();
~QQuickWebEngineViewHandle();
private:
QExplicitlySharedDataPointer<WebContentsAdapter> adapter;
friend class QQuickWebEngineViewExperimental;
friend class QQuickWebEngineViewPrivate;
};
class Q_WEBENGINE_PRIVATE_EXPORT QQuickWebEngineViewExperimental : public QObject { class Q_WEBENGINE_PRIVATE_EXPORT QQuickWebEngineViewExperimental : public QObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QQuickWebEngineViewport *viewport READ viewport) Q_PROPERTY(QQuickWebEngineViewport *viewport READ viewport)
public: public:
QQuickWebEngineViewport *viewport() const; QQuickWebEngineViewport *viewport() const;
Q_INVOKABLE void adoptHandle(QQuickWebEngineViewHandle *viewHandle);
Q_SIGNALS:
void createWindow(const QJSValue &newViewHandle, const QString &newViewDisposition);
private: private:
QQuickWebEngineViewExperimental(QQuickWebEngineViewPrivate* viewPrivate); QQuickWebEngineViewExperimental(QQuickWebEngineViewPrivate* viewPrivate);
......
...@@ -68,6 +68,7 @@ public: ...@@ -68,6 +68,7 @@ public:
{ {
QQuickWebEngineViewPrivate *viewPrivate = static_cast<QQuickWebEngineViewPrivate *>(container); QQuickWebEngineViewPrivate *viewPrivate = static_cast<QQuickWebEngineViewPrivate *>(container);
this->setParentItem(viewPrivate->q_func()); this->setParentItem(viewPrivate->q_func());
this->setSize(viewPrivate->q_func()->boundingRect().size());
} }
virtual void initAsPopup(const QRect& rect) Q_DECL_OVERRIDE virtual void initAsPopup(const QRect& rect) Q_DECL_OVERRIDE
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment