From 9348533bdfac5c185173dd763892b611fa730bf1 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Fri, 17 Oct 2014 14:03:38 +0200 Subject: [PATCH] WIP: Implement Internet Explorer based on QAxWindow. TODO: Url changed notification does not seem to work, wrong URLs are displayed in the minibrowser when navigating. Task-number: QTBUG-41130 Change-Id: I24eec2756811cdb787b0dbc46375dc111f1d44c6 --- src/imports/imports.pro | 2 + src/webview/qwebview_win.cpp | 628 +++++++++++++++++++++++++++++++++++++++++++ src/webview/qwebview_win_p.h | 191 +++++++++++++ src/webview/webview-lib.pri | 11 + 4 files changed, 832 insertions(+) create mode 100644 src/webview/qwebview_win.cpp create mode 100644 src/webview/qwebview_win_p.h diff --git a/src/imports/imports.pro b/src/imports/imports.pro index 4fd268d..ba6b2ec 100644 --- a/src/imports/imports.pro +++ b/src/imports/imports.pro @@ -12,6 +12,8 @@ android|mac|winrt { } else:qtHaveModule(webengine) { QT += webengine webengine-private DEFINES += QT_WEBVIEW_WEBENGINE_BACKEND +} else:win32:!wince* { + QT += webview webview-private } else { error(No WebView backend found!) } diff --git a/src/webview/qwebview_win.cpp b/src/webview/qwebview_win.cpp new file mode 100644 index 0000000..9f93f57 --- /dev/null +++ b/src/webview/qwebview_win.cpp @@ -0,0 +1,628 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtWebView module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwebview_win_p.h" +#include "qwebviewloadrequest_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// exdisp.h to get IWebBrowser2 for direct invocation of ExecWB() +// Turn off precompiled headers to prevent errors when including exdisp.h (MSVC 2013) +#include // IWebBrowser2 for direct invocation of ExecWB(); +#include // MS HTML + +QT_BEGIN_NAMESPACE + +enum { verbose = 0 }; + +Q_LOGGING_CATEGORY(lcWinWebView, "qt.qtwebview") + +static const char clsidBrowser[] = "{8856F961-340A-11D0-A96B-00C04FD705A2}"; + +static const GUID iidWebBrowser = {0xd30c1661, 0xcdaf, 0x11d0,{0x8a, 0x3e, 0x0, 0xc0, 0x4f, 0xc9, 0xe2, 0x6e}}; +static const GUID iiDocHostUiHandler = {0xbd3f23c0, 0xd43e, 0x11cf,{0x89, 0x3b, 0x0, 0xaa, 0x0, 0xbd, 0xce, 0x1a}}; + +// Helpers for sharing the rewriter as property between the adapter and doc handler via QObject::property. +Q_DECLARE_METATYPE(QAbstractUrlRewriterPtr) + +static const char urlRewriterPropertyC[] = "__q_urlRewriter"; + +static inline QAbstractUrlRewriterPtr getUrlWriterProperty(const QObject *o) +{ + const QVariant v = o->property(urlRewriterPropertyC); + return v.isValid() ? qvariant_cast(v) : QAbstractUrlRewriterPtr(); +} + +static inline void setUrlRewriterProperty(QObject *o, const QAbstractUrlRewriterPtr &r) +{ o->setProperty(urlRewriterPropertyC, qVariantFromValue(r)); } + +template +ReturnedInterface *queryComInterface(QAxBase *a, const IID &iid) +{ + void *result = 0; + if (SUCCEEDED(a->queryInterface(QUuid(iid), &result))) + return reinterpret_cast(result); + return 0; +} + +template +ReturnedInterface *queryComInterface(IDispatch *i, const IID &iid) +{ + void *result = 0; + if (SUCCEEDED(i->QueryInterface(iid, &result))) + return reinterpret_cast(result); + return 0; +} + +static LPWSTR comStrDup(const QString &s) // Return wchar_t string allocated using CoTaskMemAlloc() for COM +{ + void *mem = CoTaskMemAlloc((s.size() + 1) * sizeof(wchar_t)); + Q_ASSERT(mem); + LPWSTR result = reinterpret_cast(mem); + s.toWCharArray(result); + result[s.size()] = 0; + return result; +} + +// IWebBrowserAdapter: Maps the IWebBrowser2 interface to QWebViewPrivate, +// takes an object of QAxBase for use with either a QAxWidget or a QAxWindow. +// See http://msdn.microsoft.com/en-us/library/aa752042(v=vs.85).aspx +// IWebBrowser2, DWebBrowserEvents2 + +// URL rewriting can be achieved by +// 1) Implementing IDocHostUIHandler::TranslateUrl() (and aggregating IDocHostUIHandler) +// 2) Catching DWebBrowserEvents2::BeforeNavigate2() and calling IWebBrowser2::Stop() +// followed by IWebBrowser2::Navigate(). +// Neither of those approaches is able to do rewriting of URLs embedded in the page (images, CSS,...). + +IWebBrowserAdapter::IWebBrowserAdapter(QObject *parent) + : QObject(parent) + , m_axObject(0) + , m_state(0) + , m_loadStatus(QWebView::LoadStoppedStatus) + , m_loadProgress(0) +{ +} + +QUrl IWebBrowserAdapter::url() const +{ + return IWebBrowserAdapter::urlFromWebBrowser(axProperty("LocationURL").toString()); +} + +inline void IWebBrowserAdapter::fixWebBrowserUrl(QString *url) +{ + // local files are passed in native separator drive letter notation from IE notifications. + if (url->contains(QLatin1Char('\\'))) + *url = QUrl::fromLocalFile(QDir::cleanPath(*url)).toString(); +} + +inline QUrl IWebBrowserAdapter::urlFromWebBrowser(QString urlS) +{ + IWebBrowserAdapter::fixWebBrowserUrl(&urlS); + return QUrl(urlS); +} + +inline void IWebBrowserAdapter::emitLoadingChanged(const QWebViewLoadRequestPrivate &r) +{ + qCDebug(lcWinWebView) << __FUNCTION__ << r.m_url << r.m_status << r.m_errorString; + emit loadingChanged(r); +} + +QVariant IWebBrowserAdapter::axProperty(const char *name) const +{ + return m_axObject ? m_axObject->qObject()->property(name) : QVariant(); +} + +void IWebBrowserAdapter::axInvoke(const char *function, const QVariant &a1) const +{ + if (m_axObject) + m_axObject->dynamicCall(function, a1); +} + +inline void IWebBrowserAdapter::setUrl(const QUrl &url) +{ + // First URL needs to be rewritten, too; the interface is queried later. + qCDebug(lcWinWebView) << __FUNCTION__ << url; + const QString urlS = url.toString(); + const QAbstractUrlRewriterPtr r = urlRewriter(); + const QVariant v(r.isNull() ? urlS : r->rewriteUrl(urlS)); + axInvoke("Navigate(const QString&)", v); +} + +// Return interface for direct invocation of ExecWB() which does not work via QVariant. +// Invoke Release(). +inline IWebBrowser2 *IWebBrowserAdapter::iwebBrowser2() const +{ + if (m_axObject) + return queryComInterface(m_axObject, iidWebBrowser); + return 0; +} + +inline QAbstractUrlRewriterPtr IWebBrowserAdapter::urlRewriter() const +{ + return m_axObject + ? getUrlWriterProperty(m_axObject->qObject()) + : QAbstractUrlRewriterPtr(); +} + +void IWebBrowserAdapter::setUrlRewriter(const QAbstractUrlRewriterPtr &urlRewriter) +{ + if (m_axObject) + setUrlRewriterProperty(m_axObject->qObject(), urlRewriter); + else + qWarning() << __FUNCTION__ << "requires an QAxBase object."; +} + +// Implement IDocHostUIHandler in order to be able to handle IDocHostUIHandler::TranslateUrl(). +// The documentation claims IDocHostUIHandler needs to delegate to default +// implementation, but it is not clear where to get it from. Apparently via +// IWebBrowser2::get_Document(), but that returns 0 initially. But it seems +// to work happily without. + +class DocHostUiHandlerAggregated : public QAxAggregated, public IDocHostUIHandler +{ + Q_DISABLE_COPY(DocHostUiHandlerAggregated) +public: + explicit DocHostUiHandlerAggregated(QObject *o) : m_baseObject(o) {} + virtual ~DocHostUiHandlerAggregated() {} + + long queryInterface(const QUuid &iid, void **iface) Q_DECL_OVERRIDE // QAxAggregated + { + *iface = 0; + if (iid != iiDocHostUiHandler) + return E_NOINTERFACE; + qCDebug(lcWinWebView) << __FUNCTION__ << "queried"; + *iface = static_cast(this); // static_cast is required! + AddRef(); + return S_OK; + } + + QAXAGG_IUNKNOWN + + // IDocHostUIHandler + IFACEMETHODIMP ShowContextMenu(DWORD, POINT *, IUnknown *, IDispatch *) { return E_NOTIMPL; } + IFACEMETHODIMP GetHostInfo(DOCHOSTUIINFO *) { return E_NOTIMPL; } + IFACEMETHODIMP ShowUI(DWORD, IOleInPlaceActiveObject *, IOleCommandTarget *, + IOleInPlaceFrame *, IOleInPlaceUIWindow *) + { return E_NOTIMPL; } + IFACEMETHODIMP HideUI(void) { return E_NOTIMPL; } + IFACEMETHODIMP UpdateUI(void) { return E_NOTIMPL; } + IFACEMETHODIMP EnableModeless(BOOL) { return E_NOTIMPL; } + IFACEMETHODIMP OnDocWindowActivate(BOOL) { return E_NOTIMPL; } + IFACEMETHODIMP OnFrameWindowActivate(BOOL) { return E_NOTIMPL; } + IFACEMETHODIMP ResizeBorder(LPCRECT, IOleInPlaceUIWindow *, BOOL) { return E_NOTIMPL; } + IFACEMETHODIMP TranslateAccelerator(LPMSG, const GUID *, DWORD) { return E_NOTIMPL; } + IFACEMETHODIMP GetOptionKeyPath(LPOLESTR *, DWORD) { return E_NOTIMPL; } + IFACEMETHODIMP GetDropTarget(IDropTarget *, IDropTarget **) { return E_NOTIMPL; } + IFACEMETHODIMP GetExternal(IDispatch **) { return E_NOTIMPL; } + IFACEMETHODIMP TranslateUrl(DWORD dwTranslate, LPWSTR pchURLIn, LPWSTR *ppchURLOut); + IFACEMETHODIMP FilterDataObject(IDataObject *, IDataObject **) { return E_NOTIMPL; } + +private: + QPointer m_baseObject; +}; + +HRESULT DocHostUiHandlerAggregated::TranslateUrl(DWORD, LPWSTR pchURLIn, LPWSTR *ppchURLOut) +{ + *ppchURLOut = 0; + if (!m_baseObject.isNull()) { + const QAbstractUrlRewriterPtr r = getUrlWriterProperty(m_baseObject); + if (!r.isNull()) { + const QString origUrl = QString::fromWCharArray(pchURLIn); + const QString rewrittenUrl = r->rewriteUrl(origUrl); + qCDebug(lcWinWebView)<< __FUNCTION__ << origUrl << rewrittenUrl; + if (rewrittenUrl != origUrl) + *ppchURLOut = comStrDup(rewrittenUrl); + } + } + return S_OK; +} + +template class DocHostUiHandlerAggregatingMixin : public AxBase { +public: + explicit DocHostUiHandlerAggregatingMixin(Parent *parent = 0) : AxBase(parent) {} + QAxAggregated *createAggregate() Q_DECL_OVERRIDE + { return new DocHostUiHandlerAggregated(this); } +}; + +typedef DocHostUiHandlerAggregatingMixin DocHostUiHandlerAggregatingAxWindow; + + +QAxBase *IWebBrowserAdapter::takeAxObject() +{ + QAxBase *axo = m_axObject; + if (axo) + axo->qObject()->disconnect(this); + + m_state = 0; + m_axObject = Q_NULLPTR; + m_loadStatus = QWebView::LoadStoppedStatus; + m_loadProgress = 0; + + return axo; +} + +void IWebBrowserAdapter::setAxObject(QAxBase* axObject) +{ + if (m_axObject == axObject) + return; + + takeAxObject(); + + m_axObject = axObject; + if (m_axObject) { + if (!m_axObject->setControl(QLatin1String(clsidBrowser))) + qCWarning(lcWinWebView()) << "Unable to set CLSID" << clsidBrowser; + QObject *qObject = m_axObject->qObject(); + connect(qObject, SIGNAL(CommandStateChange(int,bool)), + this, SLOT(onAxBrowserCommandStateChange(int,bool))); + connect(qObject, SIGNAL(exception(int,QString,QString,QString)), + this, SLOT(onAxBrowserExceptionThrown(int,QString,QString,QString))); + connect(qObject, SIGNAL(TitleChange(QString)), + this, SLOT(onAxBrowserTitleChange(QString))); + connect(qObject, SIGNAL(NavigateComplete(QString)), + this, SLOT(onAxBrowserNavigateComplete(QString))); + connect(qObject, SIGNAL(ProgressChange(int,int)), + this, SLOT(onAxBrowserProgressChange(int,int))); + connect(qObject, SIGNAL(BeforeNavigate2(IDispatch*,QVariant&,QVariant&,QVariant&,QVariant&,QVariant&,bool&)), + this, SLOT(onAxBrowserBeforeNavigate2(IDispatch*,QVariant&,QVariant&,QVariant&,QVariant&,QVariant&,bool&))); + connect(qObject, SIGNAL(DocumentComplete(IDispatch*,QVariant&)), + this, SLOT(onAxBrowserDocumentComplete(IDispatch*,QVariant&))); + connect(qObject, SIGNAL(DownloadBegin()), + this, SLOT(onAxBrowserDownloadBegin())); + connect(qObject, SIGNAL(DownloadComplete()), + this, SLOT(onAxBrowserDownloadComplete())); + connect(qObject, SIGNAL(NavigateError(IDispatch*,QVariant&,QVariant&,QVariant&,bool&)), + this, SLOT(onAxBrowserNavigateError(IDispatch*,QVariant&,QVariant&,QVariant&,bool&))); + } +} + +static inline int getZoomPercent(IWebBrowser2 *iw2) +{ + VARIANT zoomP; + return SUCCEEDED(iw2->ExecWB(OLECMDID_OPTICAL_ZOOM, OLECMDEXECOPT_DONTPROMPTUSER, NULL, &zoomP)) + ? zoomP.intVal : 100; +} + +static inline bool setZoomPercent(IWebBrowser2 *iw2, int zoomPercent) +{ + VARIANT zoomV; + zoomV.vt = VT_I4; + zoomV.intVal = zoomPercent; + return SUCCEEDED(iw2->ExecWB(OLECMDID_OPTICAL_ZOOM, OLECMDEXECOPT_DODEFAULT, &zoomV, NULL)); +} + +typedef QPair ZoomRange; +static ZoomRange getZoomRange(IWebBrowser2 *iw2) +{ + ZoomRange result(0.1, 4); + VARIANT zoomRange; + if (SUCCEEDED(iw2->ExecWB(OLECMDID_GETZOOMRANGE, MSOCMDEXECOPT_DONTPROMPTUSER, NULL, &zoomRange))) + result.second = HIWORD(zoomRange.intVal); // Low word is 0, which is useless. + return result; +} + +qreal IWebBrowserAdapter::zoomFactor() +{ + qreal result = 1; + if (IWebBrowser2 *iw2 = iwebBrowser2()) { + result = qreal(getZoomPercent(iw2) / qreal(100.0)); + iw2->Release(); + } + return result; +} + +void IWebBrowserAdapter::setZoomFactor(qreal zf) +{ + IWebBrowser2 *iw2 = iwebBrowser2(); + if (!iw2) + return; + const ZoomRange range = getZoomRange(iw2); + if (zf < range.first || zf > range.second) { + qWarning() << "Zoom value " << zf << "out of range" << range; + return; + } + const int percent = qRound(zf * 100.0); + if (percent != getZoomPercent(iw2)) { + setZoomPercent(iw2, percent); + emit zoomFactorChanged(); + } + iw2->Release(); +} + +bool IWebBrowserAdapter::execWb0(unsigned command, unsigned option) +{ + bool ok = false; + if (IWebBrowser2 *iw2 = iwebBrowser2()) { + ok = SUCCEEDED(iw2->ExecWB(static_cast(command), + static_cast(option), + NULL, NULL)); + iw2->Release(); + } + return ok; +} + +void IWebBrowserAdapter::copy() +{ + execWb0(OLECMDID_COPY, OLECMDEXECOPT_DODEFAULT); +} + +void IWebBrowserAdapter::print() +{ + execWb0(OLECMDID_PRINT2, OLECMDEXECOPT_DODEFAULT); +} + +void IWebBrowserAdapter::onAxBrowserCommandStateChange(int cmd, bool on) +{ + const unsigned oldState = m_state; + switch (cmd) { + case 1: + if (on) + m_state |= CanGoForward; + else + m_state &= ~CanGoForward; + if (oldState != m_state) + emit canGoForwardChanged(canGoForward()); + break; + case 2: + if (on) + m_state |= CanGoBack; + else + m_state &= ~CanGoBack; + if (oldState != m_state) + emit canGoBackChanged(canGoBack()); + break; + } +} + +void IWebBrowserAdapter::onAxBrowserExceptionThrown(int c, const QString &s1, const QString &s2, const QString &s3) +{ + qCWarning(lcWinWebView) << __FUNCTION__ << c << s1 << s2 << s3; +} + +void IWebBrowserAdapter::onAxBrowserProgressChange(int progress, int progressMax) +{ + if (progressMax <= 0) + return; // 0 0 spuriously emitted. + const int percent = progress < 0 ? 100 : qMin((100 * progress) / progressMax, 100); + if (m_loadProgress != percent) { + if (verbose) { + qCDebug(lcWinWebView()) << __FUNCTION__ << percent + << "(progress=" << progress << ",max=" << progressMax << ')'; + } + m_loadProgress = percent; + emit progressChanged(m_loadProgress); + } +} + +void IWebBrowserAdapter::onAxBrowserBeforeNavigate2(IDispatch*, QVariant &urlV, + QVariant &postData, + QVariant & /* headers */, + QVariant &, QVariant &, + bool & /* cancel */) +{ + // ### fixme: This triggers on each load started on a page (images, etc), + // it is not possible to detect user-initiated clicks. Checking on postData + // empirically works, remove if it causes trouble. + if (postData.toInt()) { + const QUrl url(IWebBrowserAdapter::urlFromWebBrowser(urlV.toString())); + m_loadStatus = QWebView::LoadStartedStatus; + qCDebug(lcWinWebView) << __FUNCTION__ << url << "postData=" << postData; + emitLoadingChanged(QWebViewLoadRequestPrivate(url, m_loadStatus, QString())); + } +} + +void IWebBrowserAdapter::onAxBrowserNavigateError(IDispatch *, QVariant &urlV, + QVariant & /* frameName */, + QVariant &statusCodeV, + bool &cancel) +{ + const QUrl url = IWebBrowserAdapter::urlFromWebBrowser(urlV.toString()); + const QString errorMessage = QStringLiteral("Status ") + + QString::number(statusCodeV.toInt()); + qCDebug(lcWinWebView) << __FUNCTION__ << url << errorMessage; + m_loadStatus = QWebView::LoadFailedStatus; + emitLoadingChanged(QWebViewLoadRequestPrivate(url, m_loadStatus, errorMessage)); + cancel = true; +} + +void IWebBrowserAdapter::onAxBrowserNavigateComplete(QString urlS) +{ + if (m_loadStatus != QWebView::LoadFailedStatus) { // NavigateComplete() triggers after NavigateError() + const QUrl url(IWebBrowserAdapter::urlFromWebBrowser(urlS)); + m_loadStatus = QWebView::LoadSucceededStatus; + qCDebug(lcWinWebView) << __FUNCTION__ << url; + emit urlChanged(url); + emitLoadingChanged(QWebViewLoadRequestPrivate(url, m_loadStatus, QString())); + } +} + +void IWebBrowserAdapter::onAxBrowserDocumentComplete(IDispatch *, QVariant &v) +{ + if (verbose) + qCDebug(lcWinWebView) << __FUNCTION__ << v; +} + +void IWebBrowserAdapter::onAxBrowserDownloadBegin() +{ + if (verbose) + qCDebug(lcWinWebView) << __FUNCTION__; +} + +void IWebBrowserAdapter::onAxBrowserDownloadComplete() +{ + if (verbose) + qCDebug(lcWinWebView) << __FUNCTION__; +} + +void IWebBrowserAdapter::onAxBrowserTitleChange(const QString &title) +{ + if (verbose) + qCDebug(lcWinWebView) << __FUNCTION__ << title; + emit titleChanged(title); +} + +// ---------------- QWebViewPrivate + +QWebViewPrivate *QWebViewPrivate::create(QWebView *q) +{ + return new QWinWebViewPrivate(q); +} + +QWinWebViewPrivate::QWinWebViewPrivate(QWebView *q) + : QWebViewPrivate(q) +{ + connect(&m_browserAdapter, &IWebBrowserAdapter::urlChanged, + this, &QWebViewPrivate::urlChanged); + connect(&m_browserAdapter, &IWebBrowserAdapter::loadingChanged, + this, &QWebViewPrivate::loadingChanged); + connect(&m_browserAdapter, &IWebBrowserAdapter::progressChanged, + this, &QWebViewPrivate::loadProgressChanged); + connect(&m_browserAdapter, &IWebBrowserAdapter::titleChanged, + this, &QWebViewPrivate::titleChanged); + qApp->installEventFilter(this); +} + +void QWinWebViewPrivate::setUrl(const QUrl &url) +{ + qCDebug(lcWinWebView()) << __FUNCTION__ << url; + if (m_browserAdapter.axObject()) + m_browserAdapter.setUrl(url); + else + m_cachedUrl = url; +} + +void QWinWebViewPrivate::loadHtml(const QString &html, const QUrl &baseUrl) +{ + Q_UNUSED(html) + Q_UNUSED(baseUrl) + Q_UNIMPLEMENTED(); +} + +void QWinWebViewPrivate::runJavaScriptPrivate(const QString &script, int callbackId) +{ + Q_UNUSED(script) + Q_UNUSED(callbackId) + Q_UNIMPLEMENTED(); +} + +QWindow *QWinWebViewPrivate::axWindow() const +{ + if (const QAxBase *ab = m_browserAdapter.axObject()) + return qobject_cast(ab->qObject()); + return Q_NULLPTR; +} + +QWindow *QWinWebViewPrivate::createAxWindow(QWindow *parentWindow) +{ + if (!m_browserAdapter.axObject()) { + qCDebug(lcWinWebView) << __FUNCTION__ << "on" << parentWindow; + // ### fixme: As long as URL rewriting is not actually used: + // m_browserAdapter.setAxObject(new QAxWindow(parentWindow)) is sufficient. + m_browserAdapter.setAxObject(new DocHostUiHandlerAggregatingAxWindow(parentWindow)); + if (!m_cachedUrl.isEmpty()) { + m_browserAdapter.setUrl(m_cachedUrl.toString()); + m_cachedUrl.clear(); + } + } + return axWindow(); +} + +void QWinWebViewPrivate::setParentView(QObject *view) +{ + if (QWindow *axw = axWindow()) { + if (view) + axw->setParent(axw); + else + delete m_browserAdapter.takeAxObject(); // Prevent orphaned top levels + } else { + createAxWindow(qobject_cast(view)); + } +} + +QObject *QWinWebViewPrivate::parentView() const +{ + if (QWindow *axw = axWindow()) + return axw->parent(); + return Q_NULLPTR; +} + +void QWinWebViewPrivate::setGeometry(const QRect &geometry) +{ + if (QWindow *axw = axWindow()) + axw->setGeometry(geometry); +} + +void QWinWebViewPrivate::setVisibility(QWindow::Visibility visibility) +{ + if (QWindow *axw = axWindow()) + axw->setVisibility(visibility); +} + +void QWinWebViewPrivate::setVisible(bool visible) +{ + if (QWindow *axw = axWindow()) + axw->setVisible(visible); +} + +bool QWinWebViewPrivate::eventFilter(QObject *o, QEvent *e) +{ + if (o->isWindowType() && e->type() == QEvent::MouseButtonPress) { + QWindow *window = static_cast(o); + // The Ax host window steals the keyboard focus (SetFocus) behind + // the back of QGuiApplication. Manually activate the top level + // window when clicking inside it. + if (QWindow *axw = axWindow()) { + if (QGuiApplication::focusWindow() != window && window->isAncestorOf(axw)) + window->requestActivate(); + } + } + return QWebViewPrivate::eventFilter(o, e); +} + +QT_END_NAMESPACE diff --git a/src/webview/qwebview_win_p.h b/src/webview/qwebview_win_p.h new file mode 100644 index 0000000..838a6ea --- /dev/null +++ b/src/webview/qwebview_win_p.h @@ -0,0 +1,191 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtWebView module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWEBVIEW_WIN_P_H +#define QWEBVIEW_WIN_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qwebview_p.h" + +#include +#include +#include + +struct IDispatch; +struct IWebBrowser2; + +QT_BEGIN_NAMESPACE + +class QAxWindow; +class QAxBase; + +class QAbstractUrlRewriter { +public: + virtual QString rewriteUrl(const QString &url) const = 0; + virtual ~QAbstractUrlRewriter() {} +}; + +typedef QSharedPointer QAbstractUrlRewriterPtr; + +class IWebBrowserAdapter : public QObject { + Q_OBJECT + Q_PROPERTY(qreal zoomFactor READ zoomFactor WRITE setZoomFactor NOTIFY zoomFactorChanged) +public: + explicit IWebBrowserAdapter(QObject *parent = 0); + + QUrl url() const; + QString title() const { return axProperty("LocationName").toString(); } + bool canGoBack() const { return m_state & CanGoBack; } + bool canGoForward() const { return m_state & CanGoForward; } + + QAxBase *axObject() const { return m_axObject; } + void setAxObject(QAxBase *axObject); + QAxBase *takeAxObject(); + + qreal zoomFactor(); + void setZoomFactor(qreal zf); + + bool isLoading() const { return m_loadStatus == QWebView::LoadStartedStatus; } + int loadProgress() const { return m_loadProgress; } + + void setUrlRewriter(const QAbstractUrlRewriterPtr &urlRewriter); + QAbstractUrlRewriterPtr urlRewriter() const; + +public Q_SLOTS: + void setUrl(const QUrl &url); + void goBack() const { axInvoke("GoBack()"); } + void goForward() const { axInvoke("GoForward()"); } + void stop() const { axInvoke("Stop()"); } + void copy(); + void print(); + void reload() { axInvoke("Refresh()"); } + +Q_SIGNALS: + void urlChanged(const QUrl &); + void loadingChanged(const QWebViewLoadRequestPrivate &); + void progressChanged(int progress); + void titleChanged(const QString &title); + void canGoBackChanged(bool); // Currently unused. + void canGoForwardChanged(bool); // Currently unused. + void zoomFactorChanged(); + +private Q_SLOTS: + void onAxBrowserCommandStateChange(int cmd, bool on); + void onAxBrowserExceptionThrown(int, const QString &, const QString &, + const QString &); + void onAxBrowserProgressChange(int progress, int progressMax); + void onAxBrowserBeforeNavigate2(IDispatch*, QVariant &, QVariant &, + QVariant &, QVariant &, QVariant &, bool &); + void onAxBrowserNavigateComplete(QString); + void onAxBrowserTitleChange(const QString &); + void onAxBrowserDocumentComplete(IDispatch *, QVariant &); + void onAxBrowserDownloadBegin(); + void onAxBrowserDownloadComplete(); + void onAxBrowserNavigateError(IDispatch *, QVariant &, QVariant &, QVariant &, bool &); + +private: + enum StateFlags { + CanGoBack = 0x1, + CanGoForward = 0x2 + }; + + QVariant axProperty(const char *name) const; + void axInvoke(const char *function, const QVariant &a1 = QVariant()) const; + bool execWb0(unsigned /* OLECMDID */ command, unsigned /* OLECMDEXECOPT */ option); + IWebBrowser2 *iwebBrowser2() const; + IDispatch *iDocument() const; + static void fixWebBrowserUrl(QString *url); + static QUrl urlFromWebBrowser(QString urlS); + void emitLoadingChanged(const QWebViewLoadRequestPrivate &); + + QAxBase *m_axObject; + unsigned m_state; + QWebView::LoadStatus m_loadStatus; + int m_loadProgress; +}; + +class QWinWebViewPrivate : public QWebViewPrivate +{ + Q_OBJECT +public: + QWinWebViewPrivate(QWebView *q); + + QUrl url() const Q_DECL_OVERRIDE { return m_browserAdapter.url(); } + bool canGoBack() const Q_DECL_OVERRIDE { return m_browserAdapter.canGoBack(); } + bool canGoForward() const Q_DECL_OVERRIDE { return m_browserAdapter.canGoForward(); } + QString title() const Q_DECL_OVERRIDE { return m_browserAdapter.title(); } + int loadProgress() const Q_DECL_OVERRIDE { return m_browserAdapter.loadProgress(); } + bool isLoading() const Q_DECL_OVERRIDE { return m_browserAdapter.isLoading(); } + void reload() Q_DECL_OVERRIDE {m_browserAdapter.reload(); } + void loadHtml(const QString &html, const QUrl &baseUrl = QUrl()); + void runJavaScriptPrivate(const QString &script, int callbackId) Q_DECL_OVERRIDE; + + void setParentView(QObject *view) Q_DECL_OVERRIDE; + QObject *parentView() const Q_DECL_OVERRIDE; + + void setGeometry(const QRect &geometry) Q_DECL_OVERRIDE; + void setVisibility(QWindow::Visibility visibility) Q_DECL_OVERRIDE; + void setVisible(bool visible) Q_DECL_OVERRIDE; + + bool eventFilter(QObject *, QEvent *) Q_DECL_OVERRIDE; + +public Q_SLOTS: + void setUrl(const QUrl &url) Q_DECL_OVERRIDE; + void goBack() Q_DECL_OVERRIDE { m_browserAdapter.goBack(); } + void goForward() Q_DECL_OVERRIDE { m_browserAdapter.goForward(); } + void stop() Q_DECL_OVERRIDE { m_browserAdapter.stop(); } + +private: + QWindow *createAxWindow(QWindow *parentWindow); + QWindow *axWindow() const; + + QUrl m_cachedUrl; + IWebBrowserAdapter m_browserAdapter; +}; + +QT_END_NAMESPACE + +#endif // QWEBVIEW_WIN_P_H diff --git a/src/webview/webview-lib.pri b/src/webview/webview-lib.pri index deafc74..08be76d 100644 --- a/src/webview/webview-lib.pri +++ b/src/webview/webview-lib.pri @@ -64,6 +64,17 @@ android { PRIVATE_HEADERS += \ $$COMMON_HEADERS \ qwebview_winrt_p.h + +} else:win32:!wince* { + QT += axcontainer +# Turn off precompiled headers to prevent errors when including exdisp.h (MSVC 2013) + CONFIG -= precompile_header + SOURCES += \ + $$COMMON_SOURCES \ + qwebview_win.cpp + PRIVATE_HEADERS += \ + $$COMMON_HEADERS \ + qwebview_win_p.h } else:qtHaveModule(webengine) { QT += webengine webengine-private DEFINES += QT_WEBVIEW_WEBENGINE_BACKEND -- 1.9.1