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