[Qtwebengine] new QML window API

Jocelyn Turcotte jocelyn.turcotte at digia.com
Thu Jan 2 17:09:32 CET 2014


Hello,

I've been chatting with Simon about this on IRC and after having put aside most issues that I raised in my "After trying to get it work and try it out, here is the snapshot.." comment at <https://trello.com/c/oGyDWKlQ> we came to the conclusion that the choice between his suggested simple component approach vs. the current API is the power that a signal handler (current API) would give vs. the convenience of not having to instantiate the component manually and have the adoption done behind the scene through the creation QQmlContext (API in this email).

There are still two important bugs in the way to get something reliable:
https://bugreports.qt-project.org/browse/QTBUG-35910
https://bugreports.qt-project.org/browse/QTBUG-35913

Once we have this solved we can try to implement it properly. The current "onCreateWindow" API will stay experimental until 1.0 anyway and we can try out a few things before committing to a public API.

That said, this means that the current experimental new window API will change soon if everything goes right.

Regards,
Jocelyn


On Fri, Dec 27, 2013 at 06:01:46PM +0000, Hausmann Simon wrote:
> Hi,
> 
> I keep on thinking about the new window QML API and how much I'd like it to be
> convenient, i.e. hide the process of stitching the newly created window
> component to engine-given web contents object entirey from the developer. I
> think I have found a solution:
> 
> Generally speaking the webview can be created in two different contexts:
> 
> 1) A new window is created by the user, or for example on start-up. In this
> context there's nothing for the implementation to adopt or do.
> 
> 2) A new window is created in the context of an engine requesting/informing us
> to do so.
> 
> Now if we want to allow the user to specify the environment the QML webview
> lives in as a QML component, then we are presented with an arbitrary QML type.
> It could be simple like this:
> 
> Component {
>     id: viewComponent
> 
>     WebView {
>         newWindowComponent: viewComponent
>     }
> }
> 
> Or it could be deeply nested:
> 
> Component {
>     id: viewComponent
> 
>     SomeOtherType {
>         ...
>     }
> }
> 
> SomeOtherType.qml:
> import QtQuick 2.0
> import QtWebEngine 1.0
> 
> SomeCrazyItem {
>     WebView {
>         newWindowComponent: viewComponent // this works, but it's better to use an intermediate property
>     }
> }
> 
> In these two cases the user supplied component will eventually create one
> WebView. It is also possible that multiple WebViews are created, maybe each
> WebView also has a WebView for the web inspector or some meta-data.  I'd like
> to classify the first two cases as very common and the latter as rare.
> 
> I think the solution for the common case is simply to specify and determine the
> the context of creation, in the literal as well as in the QML sense. When a new
> instance is created with 'viewComponent", the QML context for the new type will
> be a child context of viewComponent's context.
> 
> We can utilize this concept and whenever the WebView itself wants to create a
> new window with a given WebContents, we take the creation context of the view
> component, but inject an intermediate QQmlContext - the NewWindowContext. In
> this context we can put anything we want. We could do it on a QML property
> level, but I actually think it's easier to simply do that in C++.
> 
> So when a WebView is created, in its componentComplete() implementation we can
> fetch the QML context that belongs do this instance and walk up the parent
> context chain until we either find the root context or a NewWindowContext. If
> the latter, then we can extract all the data we need for a successful
> WebContents adoption. That's entirely transparent to the user of the API.
> 
> In the rare case where somebody specifies multiple WebView instances, we need
> an API for the developer to specify which WebView in the component shall be the
> primary one for new window content, for example through a property that
> indicates whether the WebView should adopt web contents or not.
> 
> The resulting API makes the common case intuitive and trivial: The component
> specified must have a WebView _somewhere_ in its object hierarchy and it'll
> automatically be picked up and used for the context of newly created windows.
> The rare case we can make possible by requiring a little bit of help from the
> user.
> 
> In addition to all this we can of course still have informational signals about
> newly created windows, etc. - but they don't come with any contractual
> obligations :)
> 
> I've hacked up a prototype below that demonstrates the concept. Right now it
> uses a dedicated QQmlContext for each new-window created WebView, but using an
> incubator we could also avoid the "overhead" and get rid of the dummy context I
> think.
> 
> Let me know what you think :)
> 
> 
> Simon
> 
> #include <QtQml>
> 
> class NewWindowContext : public QQmlContext
> {
>     Q_OBJECT
> public:
>     NewWindowContext(QQmlContext *parentContext)
>         : QQmlContext(parentContext)
>         , handle(0)
>     {}
> 
>     // Use QExplicitlySharedDataPointer<WebContentsAdapter> in real version :)
>     void *handle;
> };
> 
> class WebView : public QObject, public QQmlParserStatus
> {
>     Q_OBJECT
>     Q_INTERFACES(QQmlParserStatus)
>     Q_PROPERTY(QQmlComponent* newWindowComponent READ newWindowComponent WRITE setNewWindowComponent)
> public:
>     WebView();
> 
>     void setNewWindowComponent(QQmlComponent *comp) { m_newWindowComponent = comp; }
>     QQmlComponent *newWindowComponent() const { return m_newWindowComponent; }
> 
>     Q_INVOKABLE void createNewWindow();
> 
>     virtual void classBegin() {}
>     virtual void componentComplete();
> 
> private:
>     QQmlComponent *m_newWindowComponent;
> };
> 
> WebView::WebView()
>     : m_newWindowComponent(0)
> {
>     qDebug() << "WebView::WebView()";
> }
> 
> void WebView::createNewWindow()
> {
>     Q_ASSERT(m_newWindowComponent);
> 
>     QQmlContext *parentContext = m_newWindowComponent->creationContext();
>     if (!parentContext)
>         parentContext = qmlContext(this);
>     // ### can this happen??
>     if (!parentContext) {
>         QQmlEngine *engine = qmlEngine(this);
>         Q_ASSERT(engine);
>         parentContext = engine->rootContext();
>     }
>     NewWindowContext *newContext = new NewWindowContext(parentContext);
>     newContext->handle = this; // Or anything, really :)
>     m_newWindowComponent->create(newContext);
> }
> 
> void WebView::componentComplete()
> {
>     QQmlContext *context = qmlContext(this);
> 
>     void *handle = 0;
>     while (context) {
>         if (NewWindowContext *newWindowContext = qobject_cast<NewWindowContext*>(context)) {
>             qSwap(newWindowContext->handle, handle);
>             break;
>         }
>         context = context->parentContext();
>     }
>     if (handle)
>         qDebug() << "WebView created from another WebView!" << handle;
>     else
>         qDebug() << "Standalone WebView complete";
> }
> 
> int main(int argc, char **argv)
> {
>     QCoreApplication app(argc, argv);
> 
>     QQmlEngine engine;
> 
>     qmlRegisterType<WebView>("TestWebEngine", 1, 0, "WebView");
> 
>     QQmlComponent doc(&engine);
>     doc.setData(""
>             "import QtQml 2.0\n"
>             "import TestWebEngine 1.0\n"
> 
>             "QtObject {\n"
>             "    id: root\n"
>             "    property string test: 'Hello World'\n"
>             "\n"
>             "    property Component factory: Component {\n"
>             "        id: viewComponent\n"
>             "\n"
>             "        WebView {\n"
>             "            newWindowComponent: viewComponent\n"
>             "        }\n"
>             "    }\n"
>             "\n"
>             "    property QtObject initialWebView: factory.createObject();\n"
>             "}", QUrl());
> 
>     QScopedPointer<QObject> root(doc.create());
>     if (!root) {
>         qDebug() << doc.errors().first().toString();
>         return 1;
>     }
> 
>     QObject *initialView = qvariant_cast<QObject*>(root->property("initialWebView"));
>     QMetaObject::invokeMethod(initialView, "createNewWindow");
> 
>     return 0;
> }
> 
> #include "main.moc"
> _______________________________________________
> QtWebEngine mailing list
> QtWebEngine at qt-project.org
> http://lists.qt-project.org/mailman/listinfo/qtwebengine



More information about the QtWebEngine mailing list