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