[Qt][WK2] Do not apply new viewport properties until after the first visually non...
[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.h"
23
24 #include "QtViewInterface.h"
25 #include "QtWebPageProxy.h"
26 #include "UtilsQt.h"
27 #include "WebPageGroup.h"
28 #include "WebPreferences.h"
29
30 #include "qquickwebpage_p.h"
31 #include "qquickwebview_p.h"
32 #include "qwebpreferences_p.h"
33
34 #include <QtDeclarative/QQuickCanvas>
35 #include <QtWidgets/QFileDialog>
36 #include <QtWidgets/QInputDialog>
37 #include <QtWidgets/QMessageBox>
38 #include <WKOpenPanelResultListener.h>
39
40 QQuickWebViewPrivate::QQuickWebViewPrivate()
41     : q_ptr(0)
42     , postTransitionState(adoptPtr(new PostTransitionState(this)))
43     , transitioningToNewPage(false)
44     , useTraditionalDesktopBehaviour(false)
45 {
46 }
47
48 void QQuickWebViewPrivate::enableMouseEvents()
49 {
50     Q_Q(QQuickWebView);
51     q->setAcceptedMouseButtons(Qt::MouseButtonMask);
52     q->setAcceptHoverEvents(true);
53     pageView->setAcceptedMouseButtons(Qt::MouseButtonMask);
54     pageView->setAcceptHoverEvents(true);
55 }
56
57 void QQuickWebViewPrivate::disableMouseEvents()
58 {
59     Q_Q(QQuickWebView);
60     q->setAcceptedMouseButtons(Qt::NoButton);
61     q->setAcceptHoverEvents(false);
62     pageView->setAcceptedMouseButtons(Qt::NoButton);
63     pageView->setAcceptHoverEvents(false);
64 }
65
66 void QQuickWebViewPrivate::initialize(QQuickWebView* viewport, WKContextRef contextRef, WKPageGroupRef pageGroupRef)
67 {
68     q_ptr = viewport;
69     viewport->setFlags(QQuickItem::ItemClipsChildrenToShape);
70
71     QObject::connect(viewport, SIGNAL(visibleChanged()), viewport, SLOT(_q_onVisibleChanged()));
72     pageView.reset(new QQuickWebPage(viewport));
73     viewInterface.reset(new WebKit::QtViewInterface(viewport, pageView.data()));
74
75     QQuickWebPagePrivate* const pageViewPrivate = pageView.data()->d;
76     setPageProxy(new QtWebPageProxy(viewInterface.data(), 0, this, contextRef, pageGroupRef));
77     pageViewPrivate->setPageProxy(pageProxy.data());
78
79     QWebPreferencesPrivate::get(pageProxy->preferences())->setAttribute(QWebPreferencesPrivate::AcceleratedCompositingEnabled, true);
80     pageProxy->init();
81
82     QObject::connect(pageProxy.data(), SIGNAL(updateNavigationState()), q_ptr, SIGNAL(navigationStateChanged()));
83 }
84
85 void QQuickWebViewPrivate::initializeDesktop(QQuickWebView* viewport)
86 {
87     if (interactionEngine) {
88         QObject::disconnect(interactionEngine.data(), SIGNAL(viewportUpdateRequested()), viewport, SLOT(_q_viewportUpdated()));
89         QObject::disconnect(interactionEngine.data(), SIGNAL(viewportTrajectoryVectorChanged(const QPointF&)), viewport, SLOT(_q_viewportTrajectoryVectorChanged(const QPointF&)));
90     }
91     interactionEngine.reset(0);
92     pageProxy->setViewportInteractionEngine(0);
93     enableMouseEvents();
94 }
95
96 void QQuickWebViewPrivate::initializeTouch(QQuickWebView* viewport)
97 {
98     interactionEngine.reset(new QtViewportInteractionEngine(viewport, pageView.data()));
99     pageProxy->setViewportInteractionEngine(interactionEngine.data());
100     disableMouseEvents();
101     QObject::connect(interactionEngine.data(), SIGNAL(viewportUpdateRequested()), viewport, SLOT(_q_viewportUpdated()));
102     QObject::connect(interactionEngine.data(), SIGNAL(viewportTrajectoryVectorChanged(const QPointF&)), viewport, SLOT(_q_viewportTrajectoryVectorChanged(const QPointF&)));
103     updateViewportSize();
104 }
105
106 void QQuickWebViewPrivate::loadDidCommit()
107 {
108     transitioningToNewPage = true;
109 }
110
111 void QQuickWebViewPrivate::didFinishFirstNonEmptyLayout()
112 {
113     transitioningToNewPage = false;
114
115     if (useTraditionalDesktopBehaviour)
116         return;
117
118     postTransitionState->apply();
119 }
120
121 void QQuickWebViewPrivate::didChangeContentsSize(const QSize& newSize)
122 {
123     if (useTraditionalDesktopBehaviour)
124         return;
125
126     if (isTransitioningToNewPage()) {
127         postTransitionState->contentsSize = newSize;
128         return;
129     }
130
131     pageView->setWidth(newSize.width());
132     pageView->setHeight(newSize.height());
133 }
134
135 void QQuickWebViewPrivate::didChangeViewportProperties(const WebCore::ViewportArguments& args)
136 {
137     if (useTraditionalDesktopBehaviour)
138         return;
139
140     viewportArguments = args;
141
142     if (isTransitioningToNewPage())
143         return;
144
145     interactionEngine->applyConstraints(computeViewportConstraints());
146 }
147
148 void QQuickWebViewPrivate::scrollPositionRequested(const QPoint& pos)
149 {
150     if (!useTraditionalDesktopBehaviour)
151         interactionEngine->pagePositionRequest(pos);
152 }
153
154 void QQuickWebViewPrivate::_q_viewportUpdated()
155 {
156     Q_Q(QQuickWebView);
157     const QRectF visibleRectInPageViewCoordinates = q->mapRectToItem(pageView.data(), q->boundingRect()).intersected(pageView->boundingRect());
158     float scale = pageView->scale();
159     pageProxy->setVisibleContentRectAndScale(visibleRectInPageViewCoordinates, scale);
160 }
161
162 void QQuickWebViewPrivate::_q_viewportTrajectoryVectorChanged(const QPointF& trajectoryVector)
163 {
164     pageProxy->setVisibleContentRectTrajectoryVector(trajectoryVector);
165 }
166
167 void QQuickWebViewPrivate::_q_onVisibleChanged()
168 {
169     WebPageProxy* wkPage = toImpl(pageProxy->pageRef());
170
171     wkPage->viewStateDidChange(WebPageProxy::ViewIsVisible);
172 }
173
174 void QQuickWebViewPrivate::updateViewportSize()
175 {
176     Q_Q(QQuickWebView);
177     QSize viewportSize = q->boundingRect().size().toSize();
178
179     if (viewportSize.isEmpty())
180         return;
181
182     WebPageProxy* wkPage = toImpl(pageProxy->pageRef());
183     // Let the WebProcess know about the new viewport size, so that
184     // it can resize the content accordingly.
185     wkPage->setViewportSize(viewportSize);
186
187     interactionEngine->applyConstraints(computeViewportConstraints());
188     _q_viewportUpdated();
189 }
190
191 QtViewportInteractionEngine::Constraints QQuickWebViewPrivate::computeViewportConstraints()
192 {
193     Q_Q(QQuickWebView);
194
195     QSize availableSize = q->boundingRect().size().toSize();
196
197     Q_ASSERT(!availableSize.isEmpty());
198
199     WebPageProxy* wkPage = toImpl(pageProxy->pageRef());
200     WebPreferences* wkPrefs = wkPage->pageGroup()->preferences();
201
202     // FIXME: Remove later; Hardcode some values for now to make sure the DPI adjustment is being tested.
203     wkPrefs->setDeviceDPI(240);
204     wkPrefs->setDeviceWidth(480);
205     wkPrefs->setDeviceHeight(720);
206
207     int minimumLayoutFallbackWidth = qMax<int>(wkPrefs->layoutFallbackWidth(), availableSize.width());
208
209     WebCore::ViewportAttributes attr = WebCore::computeViewportAttributes(viewportArguments, minimumLayoutFallbackWidth, wkPrefs->deviceWidth(), wkPrefs->deviceHeight(), wkPrefs->deviceDPI(), availableSize);
210     WebCore::restrictMinimumScaleFactorToViewportSize(attr, availableSize);
211     WebCore::restrictScaleFactorToInitialScaleIfNotUserScalable(attr);
212
213     QtViewportInteractionEngine::Constraints newConstraints;
214
215     newConstraints.initialScale = attr.initialScale;
216     newConstraints.minimumScale = attr.minimumScale;
217     newConstraints.maximumScale = attr.maximumScale;
218     newConstraints.devicePixelRatio = attr.devicePixelRatio;
219     newConstraints.isUserScalable = !!attr.userScalable;
220
221     return newConstraints;
222 }
223
224 void QQuickWebViewPrivate::runJavaScriptAlert(const QString& alertText)
225 {
226 #ifndef QT_NO_MESSAGEBOX
227     Q_Q(QQuickWebView);
228     const QString title = QObject::tr("JavaScript Alert - %1").arg(q->url().host());
229     disableMouseEvents();
230     QMessageBox::information(0, title, escapeHtml(alertText), QMessageBox::Ok);
231     enableMouseEvents();
232 #else
233     Q_UNUSED(alertText);
234 #endif
235 }
236
237 bool QQuickWebViewPrivate::runJavaScriptConfirm(const QString& message)
238 {
239     bool result = true;
240 #ifndef QT_NO_MESSAGEBOX
241     Q_Q(QQuickWebView);
242     const QString title = QObject::tr("JavaScript Confirm - %1").arg(q->url().host());
243     disableMouseEvents();
244     result = QMessageBox::Yes == QMessageBox::information(0, title, escapeHtml(message), QMessageBox::Yes, QMessageBox::No);
245     enableMouseEvents();
246 #else
247     Q_UNUSED(message);
248 #endif
249     return result;
250 }
251
252 QString QQuickWebViewPrivate::runJavaScriptPrompt(const QString& message, const QString& defaultValue, bool& ok)
253 {
254 #ifndef QT_NO_INPUTDIALOG
255     Q_Q(QQuickWebView);
256     const QString title = QObject::tr("JavaScript Prompt - %1").arg(q->url().host());
257     disableMouseEvents();
258     QString result = QInputDialog::getText(0, title, escapeHtml(message), QLineEdit::Normal, defaultValue, &ok);
259     enableMouseEvents();
260     return result;
261 #else
262     Q_UNUSED(message);
263     return defaultValue;
264 #endif
265 }
266
267 void QQuickWebViewPrivate::chooseFiles(WKOpenPanelResultListenerRef listenerRef, const QStringList& selectedFileNames, QtViewInterface::FileChooserType type)
268 {
269 #ifndef QT_NO_FILEDIALOG
270     Q_Q(QQuickWebView);
271     openPanelResultListener = listenerRef;
272
273     // Qt does not support multiple files suggestion, so we get just the first suggestion.
274     QString selectedFileName;
275     if (!selectedFileNames.isEmpty())
276         selectedFileName = selectedFileNames.at(0);
277
278     Q_ASSERT(!fileDialog);
279
280     QWindow* window = q->canvas();
281     if (!window)
282         return;
283
284     fileDialog = new QFileDialog(0, QString(), selectedFileName);
285     fileDialog->window()->winId(); // Ensure that the dialog has a window
286     Q_ASSERT(fileDialog->window()->windowHandle());
287     fileDialog->window()->windowHandle()->setTransientParent(window);
288
289     fileDialog->open(q, SLOT(_q_onOpenPanelFilesSelected()));
290
291     q->connect(fileDialog, SIGNAL(finished(int)), SLOT(_q_onOpenPanelFinished(int)));
292 #endif
293 }
294
295 void QQuickWebViewPrivate::_q_onOpenPanelFilesSelected()
296 {
297     const QStringList fileList = fileDialog->selectedFiles();
298     Vector<RefPtr<APIObject> > wkFiles(fileList.size());
299
300     for (unsigned i = 0; i < fileList.size(); ++i)
301         wkFiles[i] = WebURL::create(QUrl::fromLocalFile(fileList.at(i)).toString());
302
303     WKOpenPanelResultListenerChooseFiles(openPanelResultListener, toAPI(ImmutableArray::adopt(wkFiles).leakRef()));
304 }
305
306 void QQuickWebViewPrivate::_q_onOpenPanelFinished(int result)
307 {
308     if (result == QDialog::Rejected)
309         WKOpenPanelResultListenerCancel(openPanelResultListener);
310
311     fileDialog->deleteLater();
312     fileDialog = 0;
313 }
314
315 void QQuickWebViewPrivate::setUseTraditionalDesktopBehaviour(bool enable)
316 {
317     Q_Q(QQuickWebView);
318     if (enable == useTraditionalDesktopBehaviour)
319         return;
320
321     useTraditionalDesktopBehaviour = enable;
322     if (useTraditionalDesktopBehaviour)
323         initializeDesktop(q);
324     else
325         initializeTouch(q);
326 }
327
328 static QtPolicyInterface::PolicyAction toPolicyAction(QQuickWebView::NavigationPolicy policy)
329 {
330     switch (policy) {
331     case QQuickWebView::UsePolicy:
332         return QtPolicyInterface::Use;
333     case QQuickWebView::DownloadPolicy:
334         return QtPolicyInterface::Download;
335     case QQuickWebView::IgnorePolicy:
336         return QtPolicyInterface::Ignore;
337     }
338     ASSERT_NOT_REACHED();
339     return QtPolicyInterface::Ignore;
340 }
341
342 static bool hasMetaMethod(QObject* object, const char* methodName)
343 {
344     int methodIndex = object->metaObject()->indexOfMethod(QMetaObject::normalizedSignature(methodName));
345     return methodIndex >= 0 && methodIndex < object->metaObject()->methodCount();
346 }
347
348 /*!
349     \qmlmethod NavigationPolicy DesktopWebView::navigationPolicyForUrl(url, button, modifiers)
350
351     This method should be implemented by the user of DesktopWebView element.
352
353     It will be called to decide the policy for a navigation: whether the WebView should ignore the navigation,
354     continue it or start a download. The return value must be one of the policies in the NavigationPolicy enumeration.
355 */
356 QtPolicyInterface::PolicyAction QQuickWebViewPrivate::navigationPolicyForURL(const QUrl& url, Qt::MouseButton button, Qt::KeyboardModifiers modifiers)
357 {
358     Q_Q(QQuickWebView);
359     // We need to check this first because invokeMethod() warns if the method doesn't exist for the object.
360     if (!hasMetaMethod(q, "navigationPolicyForUrl(QVariant,QVariant,QVariant)"))
361         return QtPolicyInterface::Use;
362
363     QVariant ret;
364     if (QMetaObject::invokeMethod(q, "navigationPolicyForUrl", Q_RETURN_ARG(QVariant, ret), Q_ARG(QVariant, url), Q_ARG(QVariant, button), Q_ARG(QVariant, QVariant(modifiers))))
365         return toPolicyAction(static_cast<QQuickWebView::NavigationPolicy>(ret.toInt()));
366     return QtPolicyInterface::Use;
367 }
368
369 void QQuickWebViewPrivate::setPageProxy(QtWebPageProxy* pageProxy)
370 {
371     Q_Q(QQuickWebView);
372     this->pageProxy.reset(pageProxy);
373     QObject::connect(pageProxy, SIGNAL(receivedMessageFromNavigatorQtObject(QVariantMap)), q, SIGNAL(messageReceived(QVariantMap)));
374 }
375
376 QQuickWebView::QQuickWebView(QQuickItem* parent)
377     : QQuickItem(parent)
378     , d_ptr(new QQuickWebViewPrivate)
379 {
380     Q_D(QQuickWebView);
381     d->initialize(this);
382     d->initializeTouch(this);
383 }
384
385 QQuickWebView::QQuickWebView(WKContextRef contextRef, WKPageGroupRef pageGroupRef, QQuickItem* parent)
386     : QQuickItem(parent)
387     , d_ptr(new QQuickWebViewPrivate)
388 {
389     Q_D(QQuickWebView);
390     d->initialize(this, contextRef, pageGroupRef);
391     // Used by WebKitTestRunner.
392     d->setUseTraditionalDesktopBehaviour(true);
393 }
394
395 QQuickWebView::~QQuickWebView()
396 {
397 }
398
399 QQuickWebPage* QQuickWebView::page()
400 {
401     Q_D(QQuickWebView);
402     return d->pageView.data();
403 }
404
405 void QQuickWebView::load(const QUrl& url)
406 {
407     Q_D(QQuickWebView);
408     d->pageProxy->load(url);
409 }
410
411 void QQuickWebView::postMessage(const QString& message)
412 {
413     Q_D(QQuickWebView);
414     d->pageProxy->postMessageToNavigatorQtObject(message);
415 }
416
417 void QQuickWebView::goBack()
418 {
419     Q_D(QQuickWebView);
420     d->pageProxy->goBack();
421 }
422
423 void QQuickWebView::goForward()
424 {
425     Q_D(QQuickWebView);
426     d->pageProxy->goForward();
427 }
428
429 void QQuickWebView::stop()
430 {
431     Q_D(QQuickWebView);
432     d->pageProxy->stop();
433 }
434
435 void QQuickWebView::reload()
436 {
437     Q_D(QQuickWebView);
438     d->pageProxy->reload();
439 }
440
441 QUrl QQuickWebView::url() const
442 {
443     Q_D(const QQuickWebView);
444     return d->pageProxy->url();
445 }
446
447 int QQuickWebView::loadProgress() const
448 {
449     Q_D(const QQuickWebView);
450     return d->pageProxy->loadProgress();
451 }
452
453 bool QQuickWebView::canGoBack() const
454 {
455     Q_D(const QQuickWebView);
456     return d->pageProxy->canGoBack();
457 }
458
459 bool QQuickWebView::canGoForward() const
460 {
461     Q_D(const QQuickWebView);
462     return d->pageProxy->canGoForward();
463 }
464
465 bool QQuickWebView::canStop() const
466 {
467     Q_D(const QQuickWebView);
468     return d->pageProxy->canStop();
469 }
470
471 bool QQuickWebView::canReload() const
472 {
473     Q_D(const QQuickWebView);
474     return d->pageProxy->canReload();
475 }
476
477 QString QQuickWebView::title() const
478 {
479     Q_D(const QQuickWebView);
480     return d->pageProxy->title();
481 }
482
483 QWebPreferences* QQuickWebView::preferences() const
484 {
485     Q_D(const QQuickWebView);
486     return d->pageProxy->preferences();
487 }
488
489 void QQuickWebView::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry)
490 {
491     Q_D(QQuickWebView);
492     QQuickItem::geometryChanged(newGeometry, oldGeometry);
493     if (newGeometry.size() != oldGeometry.size()) {
494         if (d->useTraditionalDesktopBehaviour) {
495             d->pageView->setWidth(newGeometry.width());
496             d->pageView->setHeight(newGeometry.height());
497         } else
498             d->updateViewportSize();
499     }
500 }
501
502 void QQuickWebView::touchEvent(QTouchEvent* event)
503 {
504     forceActiveFocus();
505     QQuickItem::touchEvent(event);
506 }
507
508 WKPageRef QQuickWebView::pageRef() const
509 {
510     Q_D(const QQuickWebView);
511     return d->pageProxy->pageRef();
512 }
513
514 #include "moc_qquickwebview.cpp"
515 #include "moc_qquickwebview_p.cpp"