[Qt][WK2] Implement loadHtml API for QQuickWebView
[WebKit.git] / Source / WebKit2 / UIProcess / API / qt / qquickwebview.cpp
1 /*
2  * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies)
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this program; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  *
19  */
20
21 #include "config.h"
22 #include "qquickwebview_p.h"
23
24 #include "QtDialogRunner.h"
25 #include "QtWebPageProxy.h"
26 #include "UtilsQt.h"
27 #include "WebPageGroup.h"
28 #include "WebPreferences.h"
29
30 #include "qquickwebpage_p_p.h"
31 #include "qquickwebview_p_p.h"
32 #include "qwebpreferences_p_p.h"
33
34 #include <QtDeclarative/QQuickCanvas>
35 #include <QtWidgets/QFileDialog>
36 #include <QtWidgets/QInputDialog>
37 #include <WKOpenPanelResultListener.h>
38
39 QQuickWebViewPrivate::QQuickWebViewPrivate()
40     : q_ptr(0)
41     , alertDialog(0)
42     , confirmDialog(0)
43     , promptDialog(0)
44     , postTransitionState(adoptPtr(new PostTransitionState(this)))
45     , transitioningToNewPage(false)
46     , useTraditionalDesktopBehaviour(false)
47 {
48 }
49
50 void QQuickWebViewPrivate::enableMouseEvents()
51 {
52     Q_Q(QQuickWebView);
53     q->setAcceptedMouseButtons(Qt::MouseButtonMask);
54     q->setAcceptHoverEvents(true);
55     pageView->setAcceptedMouseButtons(Qt::MouseButtonMask);
56     pageView->setAcceptHoverEvents(true);
57 }
58
59 void QQuickWebViewPrivate::disableMouseEvents()
60 {
61     Q_Q(QQuickWebView);
62     q->setAcceptedMouseButtons(Qt::NoButton);
63     q->setAcceptHoverEvents(false);
64     pageView->setAcceptedMouseButtons(Qt::NoButton);
65     pageView->setAcceptHoverEvents(false);
66 }
67
68 void QQuickWebViewPrivate::initialize(QQuickWebView* viewport, WKContextRef contextRef, WKPageGroupRef pageGroupRef)
69 {
70     q_ptr = viewport;
71     viewport->setFlags(QQuickItem::ItemClipsChildrenToShape);
72
73     QObject::connect(viewport, SIGNAL(visibleChanged()), viewport, SLOT(_q_onVisibleChanged()));
74     pageView.reset(new QQuickWebPage(viewport));
75
76     QQuickWebPagePrivate* const pageViewPrivate = pageView.data()->d;
77     setPageProxy(new QtWebPageProxy(pageView.data(), q_ptr, 0, this, contextRef, pageGroupRef));
78     pageViewPrivate->setPageProxy(pageProxy.data());
79
80     QWebPreferencesPrivate::get(pageProxy->preferences())->setAttribute(QWebPreferencesPrivate::AcceleratedCompositingEnabled, true);
81     pageProxy->init();
82
83     QObject::connect(pageProxy.data(), SIGNAL(updateNavigationState()), q_ptr, SIGNAL(navigationStateChanged()));
84 }
85
86 void QQuickWebViewPrivate::initializeDesktop(QQuickWebView* viewport)
87 {
88     if (interactionEngine) {
89         QObject::disconnect(interactionEngine.data(), SIGNAL(viewportUpdateRequested()), viewport, SLOT(_q_viewportUpdated()));
90         QObject::disconnect(interactionEngine.data(), SIGNAL(viewportTrajectoryVectorChanged(const QPointF&)), viewport, SLOT(_q_viewportTrajectoryVectorChanged(const QPointF&)));
91     }
92     interactionEngine.reset(0);
93     pageProxy->setViewportInteractionEngine(0);
94     enableMouseEvents();
95 }
96
97 void QQuickWebViewPrivate::initializeTouch(QQuickWebView* viewport)
98 {
99     interactionEngine.reset(new QtViewportInteractionEngine(viewport, pageView.data()));
100     pageProxy->setViewportInteractionEngine(interactionEngine.data());
101     disableMouseEvents();
102     QObject::connect(interactionEngine.data(), SIGNAL(viewportUpdateRequested()), viewport, SLOT(_q_viewportUpdated()));
103     QObject::connect(interactionEngine.data(), SIGNAL(viewportTrajectoryVectorChanged(const QPointF&)), viewport, SLOT(_q_viewportTrajectoryVectorChanged(const QPointF&)));
104     updateViewportSize();
105 }
106
107 void QQuickWebViewPrivate::loadDidCommit()
108 {
109     transitioningToNewPage = true;
110 }
111
112 void QQuickWebViewPrivate::didFinishFirstNonEmptyLayout()
113 {
114     transitioningToNewPage = false;
115
116     if (useTraditionalDesktopBehaviour)
117         return;
118
119     postTransitionState->apply();
120 }
121
122 void QQuickWebViewPrivate::didChangeContentsSize(const QSize& newSize)
123 {
124     if (useTraditionalDesktopBehaviour)
125         return;
126
127     if (isTransitioningToNewPage()) {
128         postTransitionState->contentsSize = newSize;
129         return;
130     }
131
132     pageView->setWidth(newSize.width());
133     pageView->setHeight(newSize.height());
134 }
135
136 void QQuickWebViewPrivate::didChangeViewportProperties(const WebCore::ViewportArguments& args)
137 {
138     if (useTraditionalDesktopBehaviour)
139         return;
140
141     viewportArguments = args;
142
143     if (isTransitioningToNewPage())
144         return;
145
146     interactionEngine->applyConstraints(computeViewportConstraints());
147 }
148
149 void QQuickWebViewPrivate::scrollPositionRequested(const QPoint& pos)
150 {
151     if (!useTraditionalDesktopBehaviour)
152         interactionEngine->pagePositionRequest(pos);
153 }
154
155 void QQuickWebViewPrivate::_q_viewportUpdated()
156 {
157     Q_Q(QQuickWebView);
158     const QRectF visibleRectInPageViewCoordinates = q->mapRectToItem(pageView.data(), q->boundingRect()).intersected(pageView->boundingRect());
159     float scale = pageView->scale();
160     pageProxy->setVisibleContentRectAndScale(visibleRectInPageViewCoordinates, scale);
161 }
162
163 void QQuickWebViewPrivate::_q_viewportTrajectoryVectorChanged(const QPointF& trajectoryVector)
164 {
165     pageProxy->setVisibleContentRectTrajectoryVector(trajectoryVector);
166 }
167
168 void QQuickWebViewPrivate::_q_onVisibleChanged()
169 {
170     WebPageProxy* wkPage = toImpl(pageProxy->pageRef());
171
172     wkPage->viewStateDidChange(WebPageProxy::ViewIsVisible);
173 }
174
175 void QQuickWebViewPrivate::updateViewportSize()
176 {
177     Q_Q(QQuickWebView);
178     QSize viewportSize = q->boundingRect().size().toSize();
179
180     if (viewportSize.isEmpty())
181         return;
182
183     WebPageProxy* wkPage = toImpl(pageProxy->pageRef());
184     // Let the WebProcess know about the new viewport size, so that
185     // it can resize the content accordingly.
186     wkPage->setViewportSize(viewportSize);
187
188     interactionEngine->applyConstraints(computeViewportConstraints());
189     _q_viewportUpdated();
190 }
191
192 QtViewportInteractionEngine::Constraints QQuickWebViewPrivate::computeViewportConstraints()
193 {
194     Q_Q(QQuickWebView);
195
196     QSize availableSize = q->boundingRect().size().toSize();
197
198     Q_ASSERT(!availableSize.isEmpty());
199
200     WebPageProxy* wkPage = toImpl(pageProxy->pageRef());
201     WebPreferences* wkPrefs = wkPage->pageGroup()->preferences();
202
203     // FIXME: Remove later; Hardcode some values for now to make sure the DPI adjustment is being tested.
204     wkPrefs->setDeviceDPI(240);
205     wkPrefs->setDeviceWidth(480);
206     wkPrefs->setDeviceHeight(720);
207
208     int minimumLayoutFallbackWidth = qMax<int>(wkPrefs->layoutFallbackWidth(), availableSize.width());
209
210     WebCore::ViewportAttributes attr = WebCore::computeViewportAttributes(viewportArguments, minimumLayoutFallbackWidth, wkPrefs->deviceWidth(), wkPrefs->deviceHeight(), wkPrefs->deviceDPI(), availableSize);
211     WebCore::restrictMinimumScaleFactorToViewportSize(attr, availableSize);
212     WebCore::restrictScaleFactorToInitialScaleIfNotUserScalable(attr);
213
214     QtViewportInteractionEngine::Constraints newConstraints;
215
216     newConstraints.initialScale = attr.initialScale;
217     newConstraints.minimumScale = attr.minimumScale;
218     newConstraints.maximumScale = attr.maximumScale;
219     newConstraints.devicePixelRatio = attr.devicePixelRatio;
220     newConstraints.isUserScalable = !!attr.userScalable;
221
222     return newConstraints;
223 }
224
225 void QQuickWebViewPrivate::runJavaScriptAlert(const QString& alertText)
226 {
227     if (!alertDialog)
228         return;
229
230     Q_Q(QQuickWebView);
231     QtDialogRunner dialogRunner;
232     if (!dialogRunner.initForAlert(alertDialog, q, alertText))
233         return;
234     setViewInAttachedProperties(dialogRunner.dialog());
235
236     disableMouseEvents();
237     dialogRunner.exec();
238     enableMouseEvents();
239 }
240
241 bool QQuickWebViewPrivate::runJavaScriptConfirm(const QString& message)
242 {
243     if (!confirmDialog)
244         return true;
245
246     Q_Q(QQuickWebView);
247     QtDialogRunner dialogRunner;
248     if (!dialogRunner.initForConfirm(confirmDialog, q, message))
249         return true;
250     setViewInAttachedProperties(dialogRunner.dialog());
251
252     disableMouseEvents();
253     dialogRunner.exec();
254     enableMouseEvents();
255
256     return dialogRunner.wasAccepted();
257 }
258
259 QString QQuickWebViewPrivate::runJavaScriptPrompt(const QString& message, const QString& defaultValue, bool& ok)
260 {
261     if (!promptDialog) {
262         ok = true;
263         return defaultValue;
264     }
265
266     Q_Q(QQuickWebView);
267     QtDialogRunner dialogRunner;
268     if (!dialogRunner.initForPrompt(promptDialog, q, message, defaultValue)) {
269         ok = true;
270         return defaultValue;
271     }
272     setViewInAttachedProperties(dialogRunner.dialog());
273
274     disableMouseEvents();
275     dialogRunner.exec();
276     enableMouseEvents();
277
278     ok = dialogRunner.wasAccepted();
279     return dialogRunner.result();
280 }
281
282 void QQuickWebViewPrivate::chooseFiles(WKOpenPanelResultListenerRef listenerRef, const QStringList& selectedFileNames, QtWebPageProxy::FileChooserType type)
283 {
284 #ifndef QT_NO_FILEDIALOG
285     Q_Q(QQuickWebView);
286     openPanelResultListener = listenerRef;
287
288     // Qt does not support multiple files suggestion, so we get just the first suggestion.
289     QString selectedFileName;
290     if (!selectedFileNames.isEmpty())
291         selectedFileName = selectedFileNames.at(0);
292
293     Q_ASSERT(!fileDialog);
294
295     QWindow* window = q->canvas();
296     if (!window)
297         return;
298
299     fileDialog = new QFileDialog(0, QString(), selectedFileName);
300     fileDialog->window()->winId(); // Ensure that the dialog has a window
301     Q_ASSERT(fileDialog->window()->windowHandle());
302     fileDialog->window()->windowHandle()->setTransientParent(window);
303
304     fileDialog->open(q, SLOT(_q_onOpenPanelFilesSelected()));
305
306     q->connect(fileDialog, SIGNAL(finished(int)), SLOT(_q_onOpenPanelFinished(int)));
307 #endif
308 }
309
310 void QQuickWebViewPrivate::_q_onOpenPanelFilesSelected()
311 {
312     const QStringList fileList = fileDialog->selectedFiles();
313     Vector<RefPtr<APIObject> > wkFiles(fileList.size());
314
315     for (unsigned i = 0; i < fileList.size(); ++i)
316         wkFiles[i] = WebURL::create(QUrl::fromLocalFile(fileList.at(i)).toString());
317
318     WKOpenPanelResultListenerChooseFiles(openPanelResultListener, toAPI(ImmutableArray::adopt(wkFiles).leakRef()));
319 }
320
321 void QQuickWebViewPrivate::_q_onOpenPanelFinished(int result)
322 {
323     if (result == QDialog::Rejected)
324         WKOpenPanelResultListenerCancel(openPanelResultListener);
325
326     fileDialog->deleteLater();
327     fileDialog = 0;
328 }
329
330 void QQuickWebViewPrivate::setUseTraditionalDesktopBehaviour(bool enable)
331 {
332     Q_Q(QQuickWebView);
333     if (enable == useTraditionalDesktopBehaviour)
334         return;
335
336     useTraditionalDesktopBehaviour = enable;
337     if (useTraditionalDesktopBehaviour)
338         initializeDesktop(q);
339     else
340         initializeTouch(q);
341 }
342
343 void QQuickWebViewPrivate::setViewInAttachedProperties(QObject* object)
344 {
345     Q_Q(QQuickWebView);
346     QQuickWebViewAttached* attached = static_cast<QQuickWebViewAttached*>(qmlAttachedPropertiesObject<QQuickWebView>(object));
347     attached->setView(q);
348 }
349
350 static QtPolicyInterface::PolicyAction toPolicyAction(QQuickWebView::NavigationPolicy policy)
351 {
352     switch (policy) {
353     case QQuickWebView::UsePolicy:
354         return QtPolicyInterface::Use;
355     case QQuickWebView::DownloadPolicy:
356         return QtPolicyInterface::Download;
357     case QQuickWebView::IgnorePolicy:
358         return QtPolicyInterface::Ignore;
359     }
360     ASSERT_NOT_REACHED();
361     return QtPolicyInterface::Ignore;
362 }
363
364 static bool hasMetaMethod(QObject* object, const char* methodName)
365 {
366     int methodIndex = object->metaObject()->indexOfMethod(QMetaObject::normalizedSignature(methodName));
367     return methodIndex >= 0 && methodIndex < object->metaObject()->methodCount();
368 }
369
370 /*!
371     \qmlmethod NavigationPolicy DesktopWebView::navigationPolicyForUrl(url, button, modifiers)
372
373     This method should be implemented by the user of DesktopWebView element.
374
375     It will be called to decide the policy for a navigation: whether the WebView should ignore the navigation,
376     continue it or start a download. The return value must be one of the policies in the NavigationPolicy enumeration.
377 */
378 QtPolicyInterface::PolicyAction QQuickWebViewPrivate::navigationPolicyForURL(const QUrl& url, Qt::MouseButton button, Qt::KeyboardModifiers modifiers)
379 {
380     Q_Q(QQuickWebView);
381     // We need to check this first because invokeMethod() warns if the method doesn't exist for the object.
382     if (!hasMetaMethod(q, "navigationPolicyForUrl(QVariant,QVariant,QVariant)"))
383         return QtPolicyInterface::Use;
384
385     QVariant ret;
386     if (QMetaObject::invokeMethod(q, "navigationPolicyForUrl", Q_RETURN_ARG(QVariant, ret), Q_ARG(QVariant, url), Q_ARG(QVariant, button), Q_ARG(QVariant, QVariant(modifiers))))
387         return toPolicyAction(static_cast<QQuickWebView::NavigationPolicy>(ret.toInt()));
388     return QtPolicyInterface::Use;
389 }
390
391 void QQuickWebViewPrivate::setPageProxy(QtWebPageProxy* pageProxy)
392 {
393     Q_Q(QQuickWebView);
394     this->pageProxy.reset(pageProxy);
395     QObject::connect(pageProxy, SIGNAL(receivedMessageFromNavigatorQtObject(QVariantMap)), q, SIGNAL(messageReceived(QVariantMap)));
396 }
397
398 QQuickWebViewAttached::QQuickWebViewAttached(QObject* object)
399     : QObject(object)
400     , m_view(0)
401 {
402
403 }
404
405 void QQuickWebViewAttached::setView(QQuickWebView* view)
406 {
407     if (m_view == view)
408         return;
409     m_view = view;
410     emit viewChanged();
411 }
412
413 QQuickWebViewExperimental::QQuickWebViewExperimental(QQuickWebView *webView)
414     : QObject(webView)
415     , q_ptr(webView)
416     , d_ptr(webView->d_ptr.data())
417 {
418 }
419
420 QQuickWebViewExperimental::~QQuickWebViewExperimental()
421 {
422 }
423
424 void QQuickWebViewExperimental::setUseTraditionalDesktopBehaviour(bool enable)
425 {
426     Q_D(QQuickWebView);
427     d->setUseTraditionalDesktopBehaviour(enable);
428 }
429
430 QDeclarativeComponent* QQuickWebViewExperimental::alertDialog() const
431 {
432     Q_D(const QQuickWebView);
433     return d->alertDialog;
434 }
435
436 void QQuickWebViewExperimental::setAlertDialog(QDeclarativeComponent* alertDialog)
437 {
438     Q_D(QQuickWebView);
439     if (d->alertDialog == alertDialog)
440         return;
441     d->alertDialog = alertDialog;
442     emit alertDialogChanged();
443 }
444
445 QDeclarativeComponent* QQuickWebViewExperimental::confirmDialog() const
446 {
447     Q_D(const QQuickWebView);
448     return d->confirmDialog;
449 }
450
451 void QQuickWebViewExperimental::setConfirmDialog(QDeclarativeComponent* confirmDialog)
452 {
453     Q_D(QQuickWebView);
454     if (d->confirmDialog == confirmDialog)
455         return;
456     d->confirmDialog = confirmDialog;
457     emit confirmDialogChanged();
458 }
459
460 QDeclarativeComponent* QQuickWebViewExperimental::promptDialog() const
461 {
462     Q_D(const QQuickWebView);
463     return d->promptDialog;
464 }
465
466 void QQuickWebViewExperimental::setPromptDialog(QDeclarativeComponent* promptDialog)
467 {
468     Q_D(QQuickWebView);
469     if (d->promptDialog == promptDialog)
470         return;
471     d->promptDialog = promptDialog;
472     emit promptDialogChanged();
473 }
474
475 QQuickWebView::QQuickWebView(QQuickItem* parent)
476     : QQuickItem(parent)
477     , d_ptr(new QQuickWebViewPrivate)
478     , m_experimental(new QQuickWebViewExperimental(this))
479 {
480     Q_D(QQuickWebView);
481     d->initialize(this);
482     d->initializeTouch(this);
483 }
484
485 QQuickWebView::QQuickWebView(WKContextRef contextRef, WKPageGroupRef pageGroupRef, QQuickItem* parent)
486     : QQuickItem(parent)
487     , d_ptr(new QQuickWebViewPrivate)
488     , m_experimental(new QQuickWebViewExperimental(this))
489 {
490     Q_D(QQuickWebView);
491     d->initialize(this, contextRef, pageGroupRef);
492     // Used by WebKitTestRunner.
493     d->setUseTraditionalDesktopBehaviour(true);
494 }
495
496 QQuickWebView::~QQuickWebView()
497 {
498 }
499
500 QQuickWebPage* QQuickWebView::page()
501 {
502     Q_D(QQuickWebView);
503     return d->pageView.data();
504 }
505
506 void QQuickWebView::load(const QUrl& url)
507 {
508     Q_D(QQuickWebView);
509     d->pageProxy->load(url);
510 }
511
512 void QQuickWebView::postMessage(const QString& message)
513 {
514     Q_D(QQuickWebView);
515     d->pageProxy->postMessageToNavigatorQtObject(message);
516 }
517
518 void QQuickWebView::goBack()
519 {
520     Q_D(QQuickWebView);
521     d->pageProxy->goBack();
522 }
523
524 void QQuickWebView::goForward()
525 {
526     Q_D(QQuickWebView);
527     d->pageProxy->goForward();
528 }
529
530 void QQuickWebView::stop()
531 {
532     Q_D(QQuickWebView);
533     d->pageProxy->stop();
534 }
535
536 void QQuickWebView::reload()
537 {
538     Q_D(QQuickWebView);
539     d->pageProxy->reload();
540 }
541
542 QUrl QQuickWebView::url() const
543 {
544     Q_D(const QQuickWebView);
545     return d->pageProxy->url();
546 }
547
548 int QQuickWebView::loadProgress() const
549 {
550     Q_D(const QQuickWebView);
551     return d->pageProxy->loadProgress();
552 }
553
554 bool QQuickWebView::canGoBack() const
555 {
556     Q_D(const QQuickWebView);
557     return d->pageProxy->canGoBack();
558 }
559
560 bool QQuickWebView::canGoForward() const
561 {
562     Q_D(const QQuickWebView);
563     return d->pageProxy->canGoForward();
564 }
565
566 bool QQuickWebView::canStop() const
567 {
568     Q_D(const QQuickWebView);
569     return d->pageProxy->canStop();
570 }
571
572 bool QQuickWebView::canReload() const
573 {
574     Q_D(const QQuickWebView);
575     return d->pageProxy->canReload();
576 }
577
578 QString QQuickWebView::title() const
579 {
580     Q_D(const QQuickWebView);
581     return d->pageProxy->title();
582 }
583
584 QWebPreferences* QQuickWebView::preferences() const
585 {
586     Q_D(const QQuickWebView);
587     return d->pageProxy->preferences();
588 }
589
590 QQuickWebViewExperimental* QQuickWebView::experimental() const
591 {
592     return m_experimental;
593 }
594
595 QQuickWebViewAttached* QQuickWebView::qmlAttachedProperties(QObject* object)
596 {
597     return new QQuickWebViewAttached(object);
598 }
599
600 void QQuickWebView::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry)
601 {
602     Q_D(QQuickWebView);
603     QQuickItem::geometryChanged(newGeometry, oldGeometry);
604     if (newGeometry.size() != oldGeometry.size()) {
605         if (d->useTraditionalDesktopBehaviour) {
606             d->pageView->setWidth(newGeometry.width());
607             d->pageView->setHeight(newGeometry.height());
608         } else
609             d->updateViewportSize();
610     }
611 }
612
613 void QQuickWebView::touchEvent(QTouchEvent* event)
614 {
615     forceActiveFocus();
616     QQuickItem::touchEvent(event);
617 }
618
619 WKPageRef QQuickWebView::pageRef() const
620 {
621     Q_D(const QQuickWebView);
622     return d->pageProxy->pageRef();
623 }
624
625 /*!
626     Loads the specified \a html as the content of the web view.
627
628     External objects such as stylesheets or images referenced in the HTML
629     document are located relative to \a baseUrl.
630
631     \sa load()
632 */
633 void QQuickWebView::loadHtml(const QString& html, const QUrl& baseUrl)
634 {
635     Q_D(QQuickWebView);
636     d->pageProxy->loadHTMLString(html, baseUrl);
637 }
638
639 #include "moc_qquickwebview_p.cpp"