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