/*
* Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this program; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#include "qwkpage.h"
#include "qwkpage_p.h"
#include "qwkpreferences_p.h"
#include "ChunkedUpdateDrawingAreaProxy.h"
#include "ClientImpl.h"
#include "qgraphicswkview.h"
#include "qwkcontext.h"
#include "qwkcontext_p.h"
#include "qwkhistory.h"
#include "qwkhistory_p.h"
#include "FindIndicator.h"
#include "LocalizedStrings.h"
#include "NativeWebKeyboardEvent.h"
#include "NativeWebMouseEvent.h"
#include "NotImplemented.h"
#include "TiledDrawingAreaProxy.h"
#include "WebContext.h"
#include "WebContextMenuProxyQt.h"
#include "WebEventFactoryQt.h"
#include "WebPopupMenuProxyQt.h"
#include "WKStringQt.h"
#include "WKURLQt.h"
#include "ViewportArguments.h"
#include <QAction>
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
#include <QStyle>
#include <QTouchEvent>
#include <QtDebug>
#include <WebCore/Cursor.h>
#include <WebCore/FloatRect.h>
#include <WebCore/NotImplemented.h>
#include <WebKit2/WKFrame.h>
#include <WebKit2/WKPageGroup.h>
#include <WebKit2/WKRetainPtr.h>
using namespace WebKit;
using namespace WebCore;
static WebCore::ContextMenuAction contextMenuActionForWebAction(QWKPage::WebAction action)
{
switch (action) {
case QWKPage::OpenLink:
return WebCore::ContextMenuItemTagOpenLink;
case QWKPage::OpenLinkInNewWindow:
return WebCore::ContextMenuItemTagOpenLinkInNewWindow;
case QWKPage::CopyLinkToClipboard:
return WebCore::ContextMenuItemTagCopyLinkToClipboard;
case QWKPage::OpenImageInNewWindow:
return WebCore::ContextMenuItemTagOpenImageInNewWindow;
case QWKPage::Cut:
return WebCore::ContextMenuItemTagCut;
case QWKPage::Copy:
return WebCore::ContextMenuItemTagCopy;
case QWKPage::Paste:
return WebCore::ContextMenuItemTagPaste;
case QWKPage::SelectAll:
return WebCore::ContextMenuItemTagSelectAll;
default:
ASSERT(false);
break;
}
return WebCore::ContextMenuItemTagNoAction;
}
QWKPagePrivate::QWKPagePrivate(QWKPage* qq, QWKContext* c)
: q(qq)
, view(0)
, context(c)
, preferences(0)
, createNewPageFn(0)
, backingStoreType(QGraphicsWKView::Simple)
, isConnectedToEngine(true)
{
memset(actions, 0, sizeof(actions));
page = context->d->context->createWebPage(this, 0);
history = QWKHistoryPrivate::createHistory(page->backForwardList());
}
QWKPagePrivate::~QWKPagePrivate()
{
page->close();
delete history;
}
void QWKPagePrivate::init(QGraphicsItem* view, QGraphicsWKView::BackingStoreType backingStoreType)
{
this->view = view;
this->backingStoreType = backingStoreType;
page->initializeWebPage();
}
void QWKPagePrivate::setCursor(const WebCore::Cursor& cursor)
{
#ifndef QT_NO_CURSOR
emit q->cursorChanged(*cursor.platformCursor());
#endif
}
void QWKPagePrivate::setViewportArguments(const ViewportArguments& args)
{
viewportArguments = args;
emit q->viewportChangeRequested();
}
PassOwnPtr<DrawingAreaProxy> QWKPagePrivate::createDrawingAreaProxy()
{
// FIXME: We should avoid this cast by decoupling the view from the page.
QGraphicsWKView* wkView = static_cast<QGraphicsWKView*>(view);
#if ENABLE(TILED_BACKING_STORE)
if (backingStoreType == QGraphicsWKView::Tiled)
return TiledDrawingAreaProxy::create(wkView, page.get());
#endif
return ChunkedUpdateDrawingAreaProxy::create(wkView, page.get());
}
void QWKPagePrivate::setViewNeedsDisplay(const WebCore::IntRect& rect)
{
view->update(QRect(rect));
}
void QWKPagePrivate::displayView()
{
// FIXME: Implement.
}
void QWKPagePrivate::scrollView(const WebCore::IntRect& scrollRect, const WebCore::IntSize& scrollOffset)
{
// FIXME: Implement.
}
WebCore::IntSize QWKPagePrivate::viewSize()
{
// FIXME: Implement.
return WebCore::IntSize();
}
bool QWKPagePrivate::isViewWindowActive()
{
return view && view->isActive();
}
bool QWKPagePrivate::isViewFocused()
{
return view && view->hasFocus();
}
bool QWKPagePrivate::isViewVisible()
{
return view && view->isVisible();
}
bool QWKPagePrivate::isViewInWindow()
{
// FIXME: Implement.
return true;
}
void QWKPagePrivate::enterAcceleratedCompositingMode(const LayerTreeContext&)
{
// FIXME: Implement.
}
void QWKPagePrivate::exitAcceleratedCompositingMode()
{
// FIXME: Implement.
}
void QWKPagePrivate::pageDidRequestScroll(const IntPoint& point)
{
emit q->scrollRequested(point.x(), point.y());
}
void QWKPagePrivate::didChangeContentsSize(const IntSize& newSize)
{
emit q->contentsSizeChanged(QSize(newSize));
}
void QWKPagePrivate::toolTipChanged(const String&, const String& newTooltip)
{
emit q->toolTipChanged(QString(newTooltip));
}
void QWKPagePrivate::registerEditCommand(PassRefPtr<WebEditCommandProxy>, WebPageProxy::UndoOrRedo)
{
}
void QWKPagePrivate::clearAllEditCommands()
{
}
bool QWKPagePrivate::canUndoRedo(WebPageProxy::UndoOrRedo)
{
return false;
}
void QWKPagePrivate::executeUndoRedo(WebPageProxy::UndoOrRedo)
{
}
FloatRect QWKPagePrivate::convertToDeviceSpace(const FloatRect& rect)
{
return rect;
}
IntRect QWKPagePrivate::windowToScreen(const IntRect& rect)
{
return rect;
}
FloatRect QWKPagePrivate::convertToUserSpace(const FloatRect& rect)
{
return rect;
}
void QWKPagePrivate::selectionChanged(bool, bool, bool, bool)
{
}
void QWKPagePrivate::doneWithKeyEvent(const NativeWebKeyboardEvent&, bool)
{
}
PassRefPtr<WebPopupMenuProxy> QWKPagePrivate::createPopupMenuProxy(WebPageProxy*)
{
return WebPopupMenuProxyQt::create();
}
PassRefPtr<WebContextMenuProxy> QWKPagePrivate::createContextMenuProxy(WebPageProxy*)
{
return WebContextMenuProxyQt::create(q);
}
void QWKPagePrivate::setFindIndicator(PassRefPtr<FindIndicator>, bool fadeOut)
{
}
void QWKPagePrivate::didCommitLoadForMainFrame(bool useCustomRepresentation)
{
}
void QWKPagePrivate::didFinishLoadingDataForCustomRepresentation(const String& suggestedFilename, const CoreIPC::DataReference&)
{
}
void QWKPagePrivate::flashBackingStoreUpdates(const Vector<IntRect>&)
{
notImplemented();
}
void QWKPagePrivate::paint(QPainter* painter, QRect area)
{
if (page->isValid() && page->drawingArea())
page->drawingArea()->paint(IntRect(area), painter);
else
painter->fillRect(area, Qt::white);
}
void QWKPagePrivate::keyPressEvent(QKeyEvent* ev)
{
page->handleKeyboardEvent(NativeWebKeyboardEvent(ev));
}
void QWKPagePrivate::keyReleaseEvent(QKeyEvent* ev)
{
page->handleKeyboardEvent(NativeWebKeyboardEvent(ev));
}
void QWKPagePrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* ev)
{
// For some reason mouse press results in mouse hover (which is
// converted to mouse move for WebKit). We ignore these hover
// events by comparing lastPos with newPos.
// NOTE: lastPos from the event always comes empty, so we work
// around that here.
static QPointF lastPos = QPointF();
if (lastPos == ev->pos())
return;
lastPos = ev->pos();
page->handleMouseEvent(NativeWebMouseEvent(ev, 0));
}
void QWKPagePrivate::mousePressEvent(QGraphicsSceneMouseEvent* ev)
{
if (tripleClickTimer.isActive() && (ev->pos() - tripleClick).manhattanLength() < QApplication::startDragDistance()) {
page->handleMouseEvent(NativeWebMouseEvent(ev, 3));
return;
}
page->handleMouseEvent(NativeWebMouseEvent(ev, 1));
}
void QWKPagePrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* ev)
{
page->handleMouseEvent(NativeWebMouseEvent(ev, 0));
}
void QWKPagePrivate::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* ev)
{
page->handleMouseEvent(NativeWebMouseEvent(ev, 2));
tripleClickTimer.start(QApplication::doubleClickInterval(), q);
tripleClick = ev->pos().toPoint();
}
void QWKPagePrivate::wheelEvent(QGraphicsSceneWheelEvent* ev)
{
WebWheelEvent wheelEvent = WebEventFactory::createWebWheelEvent(ev);
page->handleWheelEvent(wheelEvent);
}
void QWKPagePrivate::updateAction(QWKPage::WebAction action)
{
#ifdef QT_NO_ACTION
Q_UNUSED(action)
#else
QAction* a = actions[action];
if (!a)
return;
RefPtr<WebKit::WebFrameProxy> mainFrame = page->mainFrame();
if (!mainFrame)
return;
bool enabled = a->isEnabled();
bool checked = a->isChecked();
switch (action) {
case QWKPage::Back:
enabled = page->canGoBack();
break;
case QWKPage::Forward:
enabled = page->canGoForward();
break;
case QWKPage::Stop:
enabled = !(WebFrameProxy::LoadStateFinished == mainFrame->loadState());
break;
case QWKPage::Reload:
enabled = (WebFrameProxy::LoadStateFinished == mainFrame->loadState());
break;
default:
break;
}
a->setEnabled(enabled);
if (a->isCheckable())
a->setChecked(checked);
#endif // QT_NO_ACTION
}
void QWKPagePrivate::updateNavigationActions()
{
updateAction(QWKPage::Back);
updateAction(QWKPage::Forward);
updateAction(QWKPage::Stop);
updateAction(QWKPage::Reload);
}
#ifndef QT_NO_ACTION
void QWKPagePrivate::_q_webActionTriggered(bool checked)
{
QAction* a = qobject_cast<QAction*>(q->sender());
if (!a)
return;
QWKPage::WebAction action = static_cast<QWKPage::WebAction>(a->data().toInt());
q->triggerAction(action, checked);
}
#endif // QT_NO_ACTION
void QWKPagePrivate::touchEvent(QTouchEvent* event)
{
#if ENABLE(TOUCH_EVENTS)
WebTouchEvent touchEvent = WebEventFactory::createWebTouchEvent(event);
page->handleTouchEvent(touchEvent);
#else
event->ignore();
#endif
}
void QWKPagePrivate::didRelaunchProcess()
{
QGraphicsWKView* wkView = static_cast<QGraphicsWKView*>(view);
if (wkView)
q->setViewportSize(wkView->size().toSize());
isConnectedToEngine = true;
emit q->engineConnectionChanged(true);
}
void QWKPagePrivate::processDidCrash()
{
isConnectedToEngine = false;
emit q->engineConnectionChanged(false);
}
QWKPage::QWKPage(QWKContext* context)
: d(new QWKPagePrivate(this, context))
{
WKPageLoaderClient loadClient = {
0, /* version */
this, /* clientInfo */
qt_wk_didStartProvisionalLoadForFrame,
qt_wk_didReceiveServerRedirectForProvisionalLoadForFrame,
qt_wk_didFailProvisionalLoadWithErrorForFrame,
qt_wk_didCommitLoadForFrame,
qt_wk_didFinishDocumentLoadForFrame,
qt_wk_didFinishLoadForFrame,
qt_wk_didFailLoadWithErrorForFrame,
0, /* didSameDocumentNavigationForFrame */
qt_wk_didReceiveTitleForFrame,
qt_wk_didFirstLayoutForFrame,
qt_wk_didFirstVisuallyNonEmptyLayoutForFrame,
qt_wk_didRemoveFrameFromHierarchy,
0, /* didDisplayInsecureContentForFrame */
0, /* didRunInsecureContentForFrame */
0, /* canAuthenticateAgainstProtectionSpaceInFrame */
0, /* didReceiveAuthenticationChallengeInFrame */
qt_wk_didStartProgress,
qt_wk_didChangeProgress,
qt_wk_didFinishProgress,
qt_wk_didBecomeUnresponsive,
qt_wk_didBecomeResponsive,
0, /* processDidCrash */
0, /* didChangeBackForwardList */
0 /* shouldGoToBackForwardListItem */
};
WKPageSetPageLoaderClient(pageRef(), &loadClient);
WKPageUIClient uiClient = {
0, /* version */
this, /* clientInfo */
qt_wk_createNewPage,
qt_wk_showPage,
qt_wk_close,
qt_wk_takeFocus,
0, /* focus */
0, /* unfocus */
qt_wk_runJavaScriptAlert,
0, /* runJavaScriptConfirm */
0, /* runJavaScriptPrompt */
qt_wk_setStatusText,
0, /* mouseDidMoveOverElement */
0, /* missingPluginButtonClicked */
0, /* didNotHandleKeyEvent */
0, /* toolbarsAreVisible */
0, /* setToolbarsAreVisible */
0, /* menuBarIsVisible */
0, /* setMenuBarIsVisible */
0, /* statusBarIsVisible */
0, /* setStatusBarIsVisible */
0, /* isResizable */
0, /* setIsResizable */
0, /* getWindowFrame */
0, /* setWindowFrame */
0, /* runBeforeUnloadConfirmPanel */
0, /* didDraw */
0, /* pageDidScroll */
0, /* exceededDatabaseQuota */
0, /* runOpenPanel */
0, /* decidePolicyForGeolocationPermissionRequest */
0, /* headerHeight */
0, /* footerHeight */
0, /* drawHeader */
0, /* drawFooter */
0, /* printFrame */
0, /* runModal */
0, /* didCompleteRubberBandForMainFrame */
0 /* saveDataToFileInDownloadsFolder */
};
WKPageSetPageUIClient(pageRef(), &uiClient);
}
QWKPage::~QWKPage()
{
delete d;
}
QWKPage::ViewportAttributes::ViewportAttributes()
: d(0)
, m_initialScaleFactor(-1.0)
, m_minimumScaleFactor(-1.0)
, m_maximumScaleFactor(-1.0)
, m_devicePixelRatio(-1.0)
, m_isUserScalable(true)
, m_isValid(false)
{
}
QWKPage::ViewportAttributes::ViewportAttributes(const QWKPage::ViewportAttributes& other)
: d(other.d)
, m_initialScaleFactor(other.m_initialScaleFactor)
, m_minimumScaleFactor(other.m_minimumScaleFactor)
, m_maximumScaleFactor(other.m_maximumScaleFactor)
, m_devicePixelRatio(other.m_devicePixelRatio)
, m_isUserScalable(other.m_isUserScalable)
, m_isValid(other.m_isValid)
, m_size(other.m_size)
{
}
QWKPage::ViewportAttributes::~ViewportAttributes()
{
}
QWKPage::ViewportAttributes& QWKPage::ViewportAttributes::operator=(const QWKPage::ViewportAttributes& other)
{
if (this != &other) {
d = other.d;
m_initialScaleFactor = other.m_initialScaleFactor;
m_minimumScaleFactor = other.m_minimumScaleFactor;
m_maximumScaleFactor = other.m_maximumScaleFactor;
m_devicePixelRatio = other.m_devicePixelRatio;
m_isUserScalable = other.m_isUserScalable;
m_isValid = other.m_isValid;
m_size = other.m_size;
}
return *this;
}
QWKPage::ViewportAttributes QWKPage::viewportAttributesForSize(const QSize& availableSize) const
{
static int desktopWidth = 980;
static int deviceDPI = 160;
ViewportAttributes result;
if (availableSize.isEmpty())
return result; // Returns an invalid instance.
// FIXME: Add a way to get these data via the platform plugin and fall back
// to the size of the view.
int deviceWidth = 480;
int deviceHeight = 864;
WebCore::ViewportAttributes conf = WebCore::computeViewportAttributes(d->viewportArguments, desktopWidth, deviceWidth, deviceHeight, deviceDPI, availableSize);
result.m_isValid = true;
result.m_size = conf.layoutSize;
result.m_initialScaleFactor = conf.initialScale;
result.m_minimumScaleFactor = conf.minimumScale;
result.m_maximumScaleFactor = conf.maximumScale;
result.m_devicePixelRatio = conf.devicePixelRatio;
result.m_isUserScalable = static_cast<bool>(conf.userScalable);
return result;
}
void QWKPage::setActualVisibleContentsRect(const QRect& rect) const
{
#if ENABLE(TILED_BACKING_STORE)
d->page->setActualVisibleContentRect(rect);
#endif
}
void QWKPage::timerEvent(QTimerEvent* ev)
{
int timerId = ev->timerId();
if (timerId == d->tripleClickTimer.timerId())
d->tripleClickTimer.stop();
else
QObject::timerEvent(ev);
}
WKPageRef QWKPage::pageRef() const
{
return toAPI(d->page.get());
}
QWKContext* QWKPage::context() const
{
return d->context;
}
QWKPreferences* QWKPage::preferences() const
{
if (!d->preferences) {
WKPageGroupRef pageGroupRef = WKPageGetPageGroup(pageRef());
d->preferences = QWKPreferencesPrivate::createPreferences(pageGroupRef);
}
return d->preferences;
}
void QWKPage::setCreateNewPageFunction(CreateNewPageFn function)
{
d->createNewPageFn = function;
}
void QWKPage::setCustomUserAgent(const QString& userAgent)
{
WKRetainPtr<WKStringRef> wkUserAgent(WKStringCreateWithQString(userAgent));
WKPageSetCustomUserAgent(pageRef(), wkUserAgent.get());
}
QString QWKPage::customUserAgent() const
{
return WKStringCopyQString(WKPageCopyCustomUserAgent(pageRef()));
}
void QWKPage::load(const QUrl& url)
{
WKRetainPtr<WKURLRef> wkurl(WKURLCreateWithQUrl(url));
WKPageLoadURL(pageRef(), wkurl.get());
}
void QWKPage::setUrl(const QUrl& url)
{
load(url);
}
QUrl QWKPage::url() const
{
WKRetainPtr<WKFrameRef> frame = WKPageGetMainFrame(pageRef());
if (!frame)
return QUrl();
return WKURLCopyQUrl(WKFrameCopyURL(frame.get()));
}
QString QWKPage::title() const
{
return WKStringCopyQString(WKPageCopyTitle(pageRef()));
}
void QWKPage::setViewportSize(const QSize& size)
{
if (d->page->drawingArea())
d->page->drawingArea()->setSize(IntSize(size), IntSize());
}
qreal QWKPage::textZoomFactor() const
{
return WKPageGetTextZoomFactor(pageRef());
}
void QWKPage::setTextZoomFactor(qreal zoomFactor)
{
WKPageSetTextZoomFactor(pageRef(), zoomFactor);
}
qreal QWKPage::pageZoomFactor() const
{
return WKPageGetPageZoomFactor(pageRef());
}
void QWKPage::setPageZoomFactor(qreal zoomFactor)
{
WKPageSetPageZoomFactor(pageRef(), zoomFactor);
}
void QWKPage::setPageAndTextZoomFactors(qreal pageZoomFactor, qreal textZoomFactor)
{
WKPageSetPageAndTextZoomFactors(pageRef(), pageZoomFactor, textZoomFactor);
}
QWKHistory* QWKPage::history() const
{
return d->history;
}
void QWKPage::setResizesToContentsUsingLayoutSize(const QSize& targetLayoutSize)
{
#if ENABLE(TILED_BACKING_STORE)
d->page->setResizesToContentsUsingLayoutSize(targetLayoutSize);
#endif
}
#ifndef QT_NO_ACTION
void QWKPage::triggerAction(WebAction webAction, bool)
{
switch (webAction) {
case Back:
d->page->goBack();
return;
case Forward:
d->page->goForward();
return;
case Stop:
d->page->stopLoading();
return;
case Reload:
d->page->reload(/* reloadFromOrigin */ true);
return;
default:
break;
}
QAction* qtAction = action(webAction);
WebKit::WebContextMenuItemData menuItemData(ActionType, contextMenuActionForWebAction(webAction), qtAction->text(), qtAction->isEnabled(), qtAction->isChecked());
d->page->contextMenuItemSelected(menuItemData);
}
#endif // QT_NO_ACTION
#ifndef QT_NO_ACTION
QAction* QWKPage::action(WebAction action) const
{
if (action == QWKPage::NoWebAction || action >= WebActionCount)
return 0;
if (d->actions[action])
return d->actions[action];
QString text;
QIcon icon;
QStyle* style = qobject_cast<QApplication*>(QCoreApplication::instance())->style();
bool checkable = false;
switch (action) {
case OpenLink:
text = contextMenuItemTagOpenLink();
break;
case OpenLinkInNewWindow:
text = contextMenuItemTagOpenLinkInNewWindow();
break;
case CopyLinkToClipboard:
text = contextMenuItemTagCopyLinkToClipboard();
break;
case OpenImageInNewWindow:
text = contextMenuItemTagOpenImageInNewWindow();
break;
case Back:
text = contextMenuItemTagGoBack();
icon = style->standardIcon(QStyle::SP_ArrowBack);
break;
case Forward:
text = contextMenuItemTagGoForward();
icon = style->standardIcon(QStyle::SP_ArrowForward);
break;
case Stop:
text = contextMenuItemTagStop();
icon = style->standardIcon(QStyle::SP_BrowserStop);
break;
case Reload:
text = contextMenuItemTagReload();
icon = style->standardIcon(QStyle::SP_BrowserReload);
break;
case Cut:
text = contextMenuItemTagCut();
break;
case Copy:
text = contextMenuItemTagCopy();
break;
case Paste:
text = contextMenuItemTagPaste();
break;
case SelectAll:
text = contextMenuItemTagSelectAll();
break;
default:
return 0;
break;
}
if (text.isEmpty())
return 0;
QAction* a = new QAction(d->q);
a->setText(text);
a->setData(action);
a->setCheckable(checkable);
a->setIcon(icon);
connect(a, SIGNAL(triggered(bool)), this, SLOT(_q_webActionTriggered(bool)));
d->actions[action] = a;
d->updateAction(action);
return a;
}
#endif // QT_NO_ACTION
void QWKPage::findZoomableAreaForPoint(const QPoint& point)
{
d->page->findZoomableAreaForPoint(point);
}
void QWKPagePrivate::didFindZoomableArea(const IntRect& area)
{
emit q->zoomableAreaFound(QRect(area));
}
bool QWKPage::isConnectedToEngine() const
{
return d->isConnectedToEngine;
}
#include "moc_qwkpage.cpp"