[Qt] [WK2] Expose onNavigationRequested signal instead of expecting a slot be defined...
[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 /*!
348     \qmlsignal WebView::onNavigationRequested(request)
349
350     This signal is emitted for every navigation request. The request object contains url, button and modifiers properties
351     describing the navigation action, e.g. "a middle click with shift key pressed to 'http://qt-project.org'".
352
353     The navigation will be accepted by default. To change that, one can set the action property to WebView.IgnoreRequest to reject
354     the request or WebView.DownloadRequest to trigger a download instead of navigating to the url.
355
356     The request object cannot be used after the signal handler function ends.
357 */
358
359 void QQuickWebViewPrivate::setPageProxy(QtWebPageProxy* pageProxy)
360 {
361     Q_Q(QQuickWebView);
362     this->pageProxy.reset(pageProxy);
363     QObject::connect(pageProxy, SIGNAL(receivedMessageFromNavigatorQtObject(QVariantMap)), q, SIGNAL(messageReceived(QVariantMap)));
364 }
365
366 QQuickWebViewAttached::QQuickWebViewAttached(QObject* object)
367     : QObject(object)
368     , m_view(0)
369 {
370
371 }
372
373 void QQuickWebViewAttached::setView(QQuickWebView* view)
374 {
375     if (m_view == view)
376         return;
377     m_view = view;
378     emit viewChanged();
379 }
380
381 QQuickWebViewExperimental::QQuickWebViewExperimental(QQuickWebView *webView)
382     : QObject(webView)
383     , q_ptr(webView)
384     , d_ptr(webView->d_ptr.data())
385 {
386 }
387
388 QQuickWebViewExperimental::~QQuickWebViewExperimental()
389 {
390 }
391
392 void QQuickWebViewExperimental::setUseTraditionalDesktopBehaviour(bool enable)
393 {
394     Q_D(QQuickWebView);
395     d->setUseTraditionalDesktopBehaviour(enable);
396 }
397
398 QDeclarativeComponent* QQuickWebViewExperimental::alertDialog() const
399 {
400     Q_D(const QQuickWebView);
401     return d->alertDialog;
402 }
403
404 void QQuickWebViewExperimental::setAlertDialog(QDeclarativeComponent* alertDialog)
405 {
406     Q_D(QQuickWebView);
407     if (d->alertDialog == alertDialog)
408         return;
409     d->alertDialog = alertDialog;
410     emit alertDialogChanged();
411 }
412
413 QDeclarativeComponent* QQuickWebViewExperimental::confirmDialog() const
414 {
415     Q_D(const QQuickWebView);
416     return d->confirmDialog;
417 }
418
419 void QQuickWebViewExperimental::setConfirmDialog(QDeclarativeComponent* confirmDialog)
420 {
421     Q_D(QQuickWebView);
422     if (d->confirmDialog == confirmDialog)
423         return;
424     d->confirmDialog = confirmDialog;
425     emit confirmDialogChanged();
426 }
427
428 QDeclarativeComponent* QQuickWebViewExperimental::promptDialog() const
429 {
430     Q_D(const QQuickWebView);
431     return d->promptDialog;
432 }
433
434 void QQuickWebViewExperimental::setPromptDialog(QDeclarativeComponent* promptDialog)
435 {
436     Q_D(QQuickWebView);
437     if (d->promptDialog == promptDialog)
438         return;
439     d->promptDialog = promptDialog;
440     emit promptDialogChanged();
441 }
442
443 QQuickWebView::QQuickWebView(QQuickItem* parent)
444     : QQuickItem(parent)
445     , d_ptr(new QQuickWebViewPrivate(this))
446     , m_experimental(new QQuickWebViewExperimental(this))
447 {
448     Q_D(QQuickWebView);
449     d->initializeTouch(this);
450 }
451
452 QQuickWebView::QQuickWebView(WKContextRef contextRef, WKPageGroupRef pageGroupRef, QQuickItem* parent)
453     : QQuickItem(parent)
454     , d_ptr(new QQuickWebViewPrivate(this, contextRef, pageGroupRef))
455     , m_experimental(new QQuickWebViewExperimental(this))
456 {
457     Q_D(QQuickWebView);
458     // Used by WebKitTestRunner.
459     d->setUseTraditionalDesktopBehaviour(true);
460 }
461
462 QQuickWebView::~QQuickWebView()
463 {
464 }
465
466 QQuickWebPage* QQuickWebView::page()
467 {
468     Q_D(QQuickWebView);
469     return d->pageView.data();
470 }
471
472 void QQuickWebView::load(const QUrl& url)
473 {
474     Q_D(QQuickWebView);
475     d->pageProxy->load(url);
476 }
477
478 void QQuickWebView::postMessage(const QString& message)
479 {
480     Q_D(QQuickWebView);
481     d->pageProxy->postMessageToNavigatorQtObject(message);
482 }
483
484 void QQuickWebView::goBack()
485 {
486     Q_D(QQuickWebView);
487     d->pageProxy->goBack();
488 }
489
490 void QQuickWebView::goForward()
491 {
492     Q_D(QQuickWebView);
493     d->pageProxy->goForward();
494 }
495
496 void QQuickWebView::stop()
497 {
498     Q_D(QQuickWebView);
499     d->pageProxy->stop();
500 }
501
502 void QQuickWebView::reload()
503 {
504     Q_D(QQuickWebView);
505     d->pageProxy->reload();
506 }
507
508 QUrl QQuickWebView::url() const
509 {
510     Q_D(const QQuickWebView);
511     return d->pageProxy->url();
512 }
513
514 int QQuickWebView::loadProgress() const
515 {
516     Q_D(const QQuickWebView);
517     return d->pageLoadClient->loadProgress();
518 }
519
520 bool QQuickWebView::canGoBack() const
521 {
522     Q_D(const QQuickWebView);
523     return d->pageProxy->canGoBack();
524 }
525
526 bool QQuickWebView::canGoForward() const
527 {
528     Q_D(const QQuickWebView);
529     return d->pageProxy->canGoForward();
530 }
531
532 bool QQuickWebView::canStop() const
533 {
534     Q_D(const QQuickWebView);
535     return d->pageProxy->canStop();
536 }
537
538 bool QQuickWebView::canReload() const
539 {
540     Q_D(const QQuickWebView);
541     return d->pageProxy->canReload();
542 }
543
544 QString QQuickWebView::title() const
545 {
546     Q_D(const QQuickWebView);
547     return d->pageProxy->title();
548 }
549
550 QWebPreferences* QQuickWebView::preferences() const
551 {
552     Q_D(const QQuickWebView);
553     return d->pageProxy->preferences();
554 }
555
556 QQuickWebViewExperimental* QQuickWebView::experimental() const
557 {
558     return m_experimental;
559 }
560
561 QQuickWebViewAttached* QQuickWebView::qmlAttachedProperties(QObject* object)
562 {
563     return new QQuickWebViewAttached(object);
564 }
565
566 void QQuickWebView::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry)
567 {
568     Q_D(QQuickWebView);
569     QQuickItem::geometryChanged(newGeometry, oldGeometry);
570     if (newGeometry.size() != oldGeometry.size()) {
571         if (d->useTraditionalDesktopBehaviour) {
572             d->pageView->setWidth(newGeometry.width());
573             d->pageView->setHeight(newGeometry.height());
574         } else
575             d->updateViewportSize();
576     }
577 }
578
579 void QQuickWebView::focusInEvent(QFocusEvent* event)
580 {
581     Q_D(QQuickWebView);
582     d->pageView->event(event);
583 }
584
585 void QQuickWebView::focusOutEvent(QFocusEvent* event)
586 {
587     Q_D(QQuickWebView);
588     d->pageView->event(event);
589 }
590
591 void QQuickWebView::touchEvent(QTouchEvent* event)
592 {
593     forceActiveFocus();
594     QQuickItem::touchEvent(event);
595 }
596
597 WKPageRef QQuickWebView::pageRef() const
598 {
599     Q_D(const QQuickWebView);
600     return d->pageProxy->pageRef();
601 }
602
603 /*!
604     Loads the specified \a html as the content of the web view.
605
606     External objects such as stylesheets or images referenced in the HTML
607     document are located relative to \a baseUrl.
608
609     \sa load()
610 */
611 void QQuickWebView::loadHtml(const QString& html, const QUrl& baseUrl)
612 {
613     Q_D(QQuickWebView);
614     d->pageProxy->loadHTMLString(html, baseUrl);
615 }
616
617 #include "moc_qquickwebview_p.cpp"