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