diff --git a/examples/quick/quicknanobrowser/quickwindow.cpp b/examples/quick/quicknanobrowser/quickwindow.cpp index 3b89aa2058204b8586180793e227f1837e9b526a..df93cd218de9fa76bf7550d0313bcbf5a721c96a 100644 --- a/examples/quick/quicknanobrowser/quickwindow.cpp +++ b/examples/quick/quicknanobrowser/quickwindow.cpp @@ -54,7 +54,6 @@ class Utils : public QObject { public: Utils(QObject* parent = 0) : QObject(parent) { } Q_INVOKABLE static QUrl fromUserInput(const QString& userInput) { return urlFromUserInput(userInput); } - Q_INVOKABLE static QUrl initialUrl() { return startupUrl(); } }; #include "quickwindow.moc" @@ -63,4 +62,5 @@ ApplicationEngine::ApplicationEngine() { rootContext()->setContextProperty("utils", new Utils(this)); load(QUrl("qrc:/quickwindow.qml")); + QMetaObject::invokeMethod(rootObjects().first(), "load", Q_ARG(QVariant, startupUrl())); } diff --git a/examples/quick/quicknanobrowser/quickwindow.qml b/examples/quick/quicknanobrowser/quickwindow.qml index 3d3dd9470a56a8f0267501959a18c7e7b6b12513..7ef33c14e888f335b41df17def90743a210b6e74 100644 --- a/examples/quick/quicknanobrowser/quickwindow.qml +++ b/examples/quick/quicknanobrowser/quickwindow.qml @@ -40,26 +40,46 @@ import QtQuick 2.0 import QtWebEngine 1.0 +import QtWebEngine.experimental 1.0 import QtQuick.Controls 1.0 import QtQuick.Controls.Styles 1.0 import QtQuick.Layouts 1.0 ApplicationWindow { id: browserWindow + function load(url) { tabs.currentView.url = url } + function adoptHandle(viewHandle) { tabs.currentView.adoptHandle(viewHandle) } + height: 600 width: 800 visible: true - title: webEngineView.title + title: tabs.currentView && tabs.currentView.title - // Focus and select text in URL bar Action { id: focus - shortcut: "Ctrl+L" // How to have Cmd + L on Mac ? + shortcut: "Ctrl+L" onTriggered: { addressBar.forceActiveFocus(); 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 { id: navigationBar @@ -68,19 +88,19 @@ ApplicationWindow { ToolButton { id: backButton iconSource: "icons/go-previous.png" - onClicked: webEngineView.goBack() - enabled: webEngineView.canGoBack + onClicked: tabs.currentView.goBack() + enabled: tabs.currentView && tabs.currentView.canGoBack } ToolButton { id: forwardButton iconSource: "icons/go-next.png" - onClicked: webEngineView.goForward() - enabled: webEngineView.canGoForward + onClicked: tabs.currentView.goForward() + enabled: tabs.currentView && tabs.currentView.canGoForward } ToolButton { id: reloadButton - iconSource: webEngineView.loading ? "icons/process-stop.png" : "icons/view-refresh.png" - onClicked: webEngineView.reload() + iconSource: tabs.currentView && tabs.currentView.loading ? "icons/process-stop.png" : "icons/view-refresh.png" + onClicked: tabs.currentView.reload() } TextField { id: addressBar @@ -90,6 +110,7 @@ ApplicationWindow { z: 2 id: faviconImage width: 16; height: 16 + source: tabs.currentView && tabs.currentView.icon } style: TextFieldStyle { padding { @@ -98,7 +119,8 @@ ApplicationWindow { } focus: true Layout.fillWidth: true - onAccepted: webEngineView.url = utils.fromUserInput(text) + text: tabs.currentView && tabs.currentView.url + onAccepted: tabs.currentView.url = utils.fromUserInput(text) } } ProgressBar { @@ -117,16 +139,46 @@ ApplicationWindow { z: -2; minimumValue: 0 maximumValue: 100 + value: tabs.currentView && tabs.currentView.loadProgress } } - WebEngineView { - id: webEngineView - focus: true + + TabView { + 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 - url: utils.initialUrl() + Component.onCompleted: createEmptyTab() + + Component { + id: tabComponent + WebEngineView { + function adoptHandle(viewHandle) { experimental.adoptHandle(viewHandle) } + + focus: true - onUrlChanged: addressBar.text = url - onIconChanged: faviconImage.source = icon - onLoadProgressChanged: progressBar.value = loadProgress + experimental { + onCreateWindow: { + 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) + } + } + } + } + } } } diff --git a/src/webengine/api/qquickwebengineview.cpp b/src/webengine/api/qquickwebengineview.cpp index e9375148ea6f2010c0ce28e60343f706f43d6b99..d159ed8fc62bd2ef3e92791acfeb8c406a794b69 100644 --- a/src/webengine/api/qquickwebengineview.cpp +++ b/src/webengine/api/qquickwebengineview.cpp @@ -47,6 +47,7 @@ #include <QScreen> #include <QUrl> +#include <QQmlEngine> QT_BEGIN_NAMESPACE @@ -158,9 +159,48 @@ void QQuickWebEngineViewPrivate::focusContainer() void QQuickWebEngineViewPrivate::adoptNewWindow(WebContentsAdapter *newWebContents, WindowOpenDisposition disposition, const QRect &) { - Q_UNUSED(newWebContents); - Q_UNUSED(disposition); - Q_UNREACHABLE(); + Q_Q(QQuickWebEngineView); + QQmlEngine *engine = QtQml::qmlEngine(q); + // 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() @@ -285,6 +325,14 @@ void QQuickWebEngineView::geometryChanged(const QRectF &newGeometry, const QRect } } +QQuickWebEngineViewHandle::QQuickWebEngineViewHandle() +{ +} + +QQuickWebEngineViewHandle::~QQuickWebEngineViewHandle() +{ +} + QQuickWebEngineViewExperimental::QQuickWebEngineViewExperimental(QQuickWebEngineViewPrivate *viewPrivate) : q_ptr(0) , d_ptr(viewPrivate) @@ -320,4 +368,34 @@ void QQuickWebEngineViewport::setDevicePixelRatio(qreal devicePixelRatio) 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 + +#include "moc_qquickwebengineview_p.cpp" +#include "moc_qquickwebengineview_p_p.cpp" diff --git a/src/webengine/api/qquickwebengineview_p_p.h b/src/webengine/api/qquickwebengineview_p_p.h index b356b88813d244848d4975c493e6031160f597df..0af1dcb450c7f7c3bdf671c67cb7feea2a3df924 100644 --- a/src/webengine/api/qquickwebengineview_p_p.h +++ b/src/webengine/api/qquickwebengineview_p_p.h @@ -71,11 +71,27 @@ private: 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 { Q_OBJECT Q_PROPERTY(QQuickWebEngineViewport *viewport READ viewport) public: QQuickWebEngineViewport *viewport() const; + Q_INVOKABLE void adoptHandle(QQuickWebEngineViewHandle *viewHandle); + +Q_SIGNALS: + void createWindow(const QJSValue &newViewHandle, const QString &newViewDisposition); private: QQuickWebEngineViewExperimental(QQuickWebEngineViewPrivate* viewPrivate); diff --git a/src/webengine/render_widget_host_view_qt_delegate_quick.h b/src/webengine/render_widget_host_view_qt_delegate_quick.h index e8e073c24c51e0bc548e881f1df79cc81f36950f..b31a9c873e8b179b0597505d201688b85ab299fb 100644 --- a/src/webengine/render_widget_host_view_qt_delegate_quick.h +++ b/src/webengine/render_widget_host_view_qt_delegate_quick.h @@ -68,6 +68,7 @@ public: { QQuickWebEngineViewPrivate *viewPrivate = static_cast<QQuickWebEngineViewPrivate *>(container); this->setParentItem(viewPrivate->q_func()); + this->setSize(viewPrivate->q_func()->boundingRect().size()); } virtual void initAsPopup(const QRect& rect) Q_DECL_OVERRIDE