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