diff options
78 files changed, 1891 insertions, 350 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..3399f428c --- /dev/null +++ b/.clang-format @@ -0,0 +1,105 @@ +# Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com> +# +# You may use this file under the terms of the 3-clause BSD license. +# See the file LICENSE from this package for details. + +# This is the clang-format configuration style to be used by Qt, +# based on the rules from https://wiki.qt.io/Qt_Coding_Style and +# https://wiki.qt.io/Coding_Conventions + +--- +# Webkit style was loosely based on the Qt style +BasedOnStyle: WebKit + +Standard: Cpp11 + +# Column width is limited to 100 in accordance with Qt Coding Style. +# https://wiki.qt.io/Qt_Coding_Style +# Note that this may be changed at some point in the future. +ColumnLimit: 100 +# How much weight do extra characters after the line length limit have. +# PenaltyExcessCharacter: 4 + +# Disable reflow of qdoc comments: indentation rules are different. +# Translation comments are also excluded. +CommentPragmas: "^!|^:" + +# We want a space between the type and the star for pointer types. +PointerBindsToType: false + +# We use template< without space. +SpaceAfterTemplateKeyword: false + +# We want to break before the operators, but not before a '='. +BreakBeforeBinaryOperators: NonAssignment + +# Braces are usually attached, but not after functions or class declarations. +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: true + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + +# When constructor initializers do not fit on one line, put them each on a new line. +ConstructorInitializerAllOnOneLineOrOnePerLine: true +# Indent initializers by 4 spaces +ConstructorInitializerIndentWidth: 4 + +# Indent width for line continuations. +ContinuationIndentWidth: 8 + +# No indentation for namespaces. +NamespaceIndentation: None + +# Allow indentation for preprocessing directives (if/ifdef/endif). https://reviews.llvm.org/rL312125 +IndentPPDirectives: AfterHash + +# Horizontally align arguments after an open bracket. +# The coding style does not specify the following, but this is what gives +# results closest to the existing code. +AlignAfterOpenBracket: true +AlwaysBreakTemplateDeclarations: true + +# Ideally we should also allow less short function in a single line, but +# clang-format does not handle that. +AllowShortFunctionsOnASingleLine: Inline + +# The coding style specifies some include order categories, but also tells to +# separate categories with an empty line. It does not specify the order within +# the categories. Since the SortInclude feature of clang-format does not +# re-order includes separated by empty lines, the feature is not used. +SortIncludes: false + +# macros for which the opening brace stays attached. +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ] + +# Break constructor initializers before the colon and after the commas. +BreakConstructorInitializers: BeforeColon + +################################################################################ +# QtWebEngine specific changes to Qt style + +# We are using BeforeComma consistently so don't change it +BreakConstructorInitializers: BeforeComma + +# Chromium macros +MacroBlockBegin: "^\ +IPC_BEGIN_MESSAGE_MAP|\ +IPC_BEGIN_MESSAGE_MAP_WITH_PARAM|\ +IPC_STRUCT_BEGIN|\ +IPC_STRUCT_BEGIN_WITH_PARENT|\ +IPC_STRUCT_TRAITS_BEGIN|\ +PPAPI_BEGIN_MESSAGE_MAP$" +MacroBlockEnd: "^\ +IPC_END_MESSAGE_MAP|\ +IPC_STRUCT_END|\ +IPC_STRUCT_TRAITS_END|\ +PPAPI_END_MESSAGE_MAP$" diff --git a/dist/changes-5.15.0 b/dist/changes-5.15.0 new file mode 100644 index 000000000..630a70852 --- /dev/null +++ b/dist/changes-5.15.0 @@ -0,0 +1,91 @@ +Qt 5.15 introduces many new features and improvements as well as bugfixes +over the 5.14.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.15 series is binary compatible with the 5.14.x series. +Applications compiled for 5.14 will continue to run with 5.15. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + + +**************************************************************************** +* General * +**************************************************************************** + +Behavior Changes +---------------- + + - XSS Auditing has been removed, and the XSSAuditingEnabled setting no + longer has any effect. + - [QTBUG-79864] The viz display compositor is now used by default on all + platforms, but can be disabled with --disable-viz-display-compositor. + - The network layer integration has been rewritten to use Chromium's network + service, and now runs in a separate sandboxed process by default. + - [QTBUG-83656] CTRL+mouse wheel page zoom fixed, and now works by default. + + +Chromium Snapshot +----------------- + + - Updated the Chromium version to 80.0.3987.163 + - Applied security fixes from Chrome up to version 81.0.4044.138 + + +General +------- + + - Fixed hardware accelerated video decoding on Windows and macOS. + - Updated where to look for Google Chrome's CDM plugin. + - [QTBUG-82390] Disabled picture-in-picture to avoid non-functional button + showing up. + - Command-line specified PAC files can now be loaded from QRC. + - [QTBUG-82012] Placeholder for missing PPAPI plugins added. + + +Libraries +--------- + + - Added a renderProcessPid() getter to QWebEnginePage and WebEngineView + which allows reading the process ID of the underlying render process. + - [QTBUG-83338] Avoid decoding HTML in default JavaScript message handlers. + + +**************************************************************************** +* Qt PDF * +**************************************************************************** + +General +------- + + - The qt-labs/qtpdf module was using an out-of-date version of pdfium. + Development will now continue in the qtwebengine repository in order to + reuse Qt WebEngine's pdfium build system integration. + - QtPdf is still in Tech Preview. + - It's now possible to build QtPDF for iOS (even though Qt WebEngine does not). + - Added a Qt Quick API (import QtQuick.Pdf): + * Added an image plugin: Image { source: "my.pdf"; currentFrame: 2 } is + enough to view the third page in a PDF file. + * High-level API for full-featured viewers: PdfScrollablePageView shows + one page at a time; PdfMultiPageView allows flicking vertically from + page to page. These are implemented in QML and packaged with the module. + - Low-level QtQuick API: + * PdfDocument provides API for the document and its metadata. + * PdfSearchModel can find a text string and provides information about + the locations where it is found on each page, which can be listed in + a ListView and visualized by using a Repeater to instantiate + QtQuick.Shapes for the bounding boxes. + * PdfSelection allows selecting text and copying to the clipboard; + visualization of the selected region is done via QtQuick.Shapes. + * PdfLinkModel provides information about the links in the document; + visual feedback and clicking can be handled with QtQuick.Shapes + and TapHandler, respectively. + * PdfNavigationStack is a model for implementing forward/back navigation. + diff --git a/examples/pdf/pdfviewer/viewer.qml b/examples/pdf/pdfviewer/viewer.qml index f4c913154..38d62740a 100644 --- a/examples/pdf/pdfviewer/viewer.qml +++ b/examples/pdf/pdfviewer/viewer.qml @@ -226,11 +226,11 @@ ApplicationWindow { Drawer { id: searchDrawer edge: Qt.LeftEdge - modal: false +// modal: false +// dim: false // commented out as workaround for QTBUG-83859 width: 300 y: root.header.height height: view.height - dim: false clip: true ListView { id: searchResultsList diff --git a/src/3rdparty b/src/3rdparty -Subproject 757b9f459d3770644ad83d2faf26a7539c65023 +Subproject 623647821aa7c7565ed5153a27c5a1bb088efbe diff --git a/src/buildtools/config/linux.pri b/src/buildtools/config/linux.pri index c02af3909..455a2e3c6 100644 --- a/src/buildtools/config/linux.pri +++ b/src/buildtools/config/linux.pri @@ -192,15 +192,11 @@ host_build { } else { gn_args += use_alsa=false } - qtConfig(build-qtwebengine-core):qtConfig(webengine-system-xkbcommon) { - gn_args += use_xkbcommon=true - } else { - gn_args += use_xkbcommon=false - } !packagesExist(libpci): gn_args += use_libpci=false qtConfig(build-qtwebengine-core):qtConfig(webengine-ozone-x11) { gn_args += ozone_platform_x11=true + gn_args += use_xkbcommon=true packagesExist(xscrnsaver): gn_args += use_xscrnsaver=true qtConfig(webengine-webrtc): gn_args += rtc_use_x11=true } diff --git a/src/buildtools/configure.json b/src/buildtools/configure.json index 27bd3ebf8..f02ab8070 100644 --- a/src/buildtools/configure.json +++ b/src/buildtools/configure.json @@ -32,12 +32,6 @@ { "type": "pkgConfig", "args": "libdrm" } ] }, - "webengine-xkbcommon": { - "label": "xkbcommon", - "sources": [ - { "type": "pkgConfig", "args": "xkbcommon" } - ] - }, "webengine-xcomposite": { "label": "xcomposite", "sources": [ @@ -296,13 +290,6 @@ ] } }, - "webengine-xkbcommon": { - "label": "system xkbcommon", - "type": "compile", - "test": { - "include": "xkbcommon/xkbcommon.h" - } - }, "webengine-ninja": { "label": "system ninja", "type": "detectNinja", @@ -457,11 +444,6 @@ "condition": "libs.webengine-libdrm", "output": [ "privateFeature" ] }, - "webengine-system-xkbcommon": { - "label": "xkbcommon", - "condition": "libs.webengine-xkbcommon && tests.webengine-xkbcommon", - "output": [ "privateFeature" ] - }, "webengine-system-xcomposite": { "label": "xcomposite", "condition": "libs.webengine-xcomposite", @@ -756,8 +738,7 @@ "webengine-system-png", "webengine-system-jpeg", "webengine-system-harfbuzz", - "webengine-system-freetype", - "webengine-system-xkbcommon" + "webengine-system-freetype" ] } ] diff --git a/src/core/accessibility_activation_observer.cpp b/src/core/accessibility_activation_observer.cpp index 75ad90c54..833190844 100644 --- a/src/core/accessibility_activation_observer.cpp +++ b/src/core/accessibility_activation_observer.cpp @@ -39,7 +39,7 @@ #include "accessibility_activation_observer.h" -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) #include "content/browser/accessibility/browser_accessibility_state_impl.h" @@ -86,4 +86,4 @@ void AccessibilityActivationObserver::accessibilityActiveChanged(bool active) } // namespace QtWebEngineCore -#endif // QT_NO_ACCESSIBILITY +#endif // QT_CONFIG(accessibility) diff --git a/src/core/accessibility_activation_observer.h b/src/core/accessibility_activation_observer.h index e42c83eb5..23fd2101e 100644 --- a/src/core/accessibility_activation_observer.h +++ b/src/core/accessibility_activation_observer.h @@ -40,9 +40,10 @@ #ifndef ACCESSIBILITY_ACTIVATION_OBSERVER_H #define ACCESSIBILITY_ACTIVATION_OBSERVER_H -#ifndef QT_NO_ACCESSIBILITY #include <QtGui/qaccessible.h> +#if QT_CONFIG(accessibility) + namespace QtWebEngineCore { class RenderWidgetHostViewQt; @@ -58,6 +59,6 @@ public: } // namespace QtWebEngineCore -#endif // QT_NO_ACCESSIBILITY +#endif // QT_CONFIG(accessibility) #endif // ACCESSIBILITY_ACTIVATION_OBSERVER_H diff --git a/src/core/accessibility_tree_formatter_qt.cpp b/src/core/accessibility_tree_formatter_qt.cpp index 334759abb..081856b37 100644 --- a/src/core/accessibility_tree_formatter_qt.cpp +++ b/src/core/accessibility_tree_formatter_qt.cpp @@ -52,7 +52,7 @@ namespace content { -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) class AccessibilityTreeFormatterQt : public AccessibilityTreeFormatterBrowser { public: explicit AccessibilityTreeFormatterQt(); @@ -204,12 +204,12 @@ const std::string AccessibilityTreeFormatterQt::GetDenyNodeString() return "@QT-DENY-NODE:"; } -#endif // QT_NO_ACCESSIBILITY +#endif // QT_CONFIG(accessibility) // static std::unique_ptr<AccessibilityTreeFormatter> AccessibilityTreeFormatter::Create() { -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) return std::unique_ptr<AccessibilityTreeFormatter>(new AccessibilityTreeFormatterQt()); #else return nullptr; diff --git a/src/core/api/qtwebenginecoreglobal.cpp b/src/core/api/qtwebenginecoreglobal.cpp index 9e2a4a5b1..ce4362741 100644 --- a/src/core/api/qtwebenginecoreglobal.cpp +++ b/src/core/api/qtwebenginecoreglobal.cpp @@ -40,7 +40,7 @@ #include "qtwebenginecoreglobal_p.h" #include <QGuiApplication> -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) # include <QOpenGLContext> #ifdef Q_OS_MACOS #include <sys/types.h> @@ -49,14 +49,14 @@ #endif #include <QThread> -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) QT_BEGIN_NAMESPACE Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); QT_END_NAMESPACE #endif -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) #ifdef Q_OS_MACOS static bool needsOfflineRendererWorkaround() { @@ -75,7 +75,7 @@ static bool needsOfflineRendererWorkaround() #endif namespace QtWebEngineCore { -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) static QOpenGLContext *shareContext; static void deleteShareContext() @@ -94,7 +94,7 @@ static void deleteShareContext() Q_WEBENGINECORE_PRIVATE_EXPORT void initialize() { -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) #ifdef Q_OS_WIN32 qputenv("QT_D3DCREATE_MULTITHREADED", "1"); #endif @@ -134,6 +134,6 @@ Q_WEBENGINECORE_PRIVATE_EXPORT void initialize() // Classes like QOpenGLWidget check for the attribute app->setAttribute(Qt::AA_ShareOpenGLContexts); -#endif // QT_NO_OPENGL +#endif // QT_CONFIG(opengl) } } // namespace QtWebEngineCore diff --git a/src/core/browser_accessibility_manager_qt.cpp b/src/core/browser_accessibility_manager_qt.cpp index 8e3ee5940..5968bfd30 100644 --- a/src/core/browser_accessibility_manager_qt.cpp +++ b/src/core/browser_accessibility_manager_qt.cpp @@ -51,24 +51,24 @@ BrowserAccessibilityManager* BrowserAccessibilityManager::Create( BrowserAccessibilityDelegate* delegate, BrowserAccessibilityFactory* factory) { -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) return new BrowserAccessibilityManagerQt(nullptr, initialTree, delegate, factory); #else delete factory; return nullptr; -#endif // QT_NO_ACCESSIBILITY +#endif // QT_CONFIG(accessibility) } BrowserAccessibility *BrowserAccessibility::Create() { -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) return new BrowserAccessibilityQt(); #else return nullptr; -#endif // QT_NO_ACCESSIBILITY +#endif // QT_CONFIG(accessibility) } -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) BrowserAccessibilityManagerQt::BrowserAccessibilityManagerQt( QObject *parentObject, const ui::AXTreeUpdate &initialTree, BrowserAccessibilityDelegate* delegate, BrowserAccessibilityFactory* factory) @@ -164,6 +164,6 @@ void BrowserAccessibilityManagerQt::FireGeneratedEvent(ui::AXEventGenerator::Eve } } -#endif // QT_NO_ACCESSIBILITY +#endif // QT_CONFIG(accessibility) } diff --git a/src/core/browser_accessibility_manager_qt.h b/src/core/browser_accessibility_manager_qt.h index 16e2d1fe7..6d6086811 100644 --- a/src/core/browser_accessibility_manager_qt.h +++ b/src/core/browser_accessibility_manager_qt.h @@ -41,8 +41,11 @@ #define BROWSER_ACCESSIBILITY_MANAGER_QT_H #include "content/browser/accessibility/browser_accessibility_manager.h" -#ifndef QT_NO_ACCESSIBILITY + #include <QtCore/qobject.h> +#include <QtGui/qtgui-config.h> + +#if QT_CONFIG(accessibility) QT_BEGIN_NAMESPACE class QAccessibleInterface; @@ -74,5 +77,5 @@ private: } -#endif // QT_NO_ACCESSIBILITY +#endif // QT_CONFIG(accessibility) #endif diff --git a/src/core/browser_accessibility_qt.cpp b/src/core/browser_accessibility_qt.cpp index 6104fb1f8..de78eb85d 100644 --- a/src/core/browser_accessibility_qt.cpp +++ b/src/core/browser_accessibility_qt.cpp @@ -43,7 +43,7 @@ #include "browser_accessibility_qt.h" -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) #include "ui/accessibility/ax_enums.mojom.h" @@ -996,4 +996,4 @@ void BrowserAccessibilityQt::modelChange(QAccessibleTableModelChangeEvent *) } // namespace content -#endif // QT_NO_ACCESSIBILITY +#endif // QT_CONFIG(accessibility) diff --git a/src/core/browser_accessibility_qt.h b/src/core/browser_accessibility_qt.h index 4acac6aa7..19c7a1e54 100644 --- a/src/core/browser_accessibility_qt.h +++ b/src/core/browser_accessibility_qt.h @@ -43,7 +43,7 @@ #include <QtGui/qaccessible.h> #include "content/browser/accessibility/browser_accessibility.h" -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) namespace content { @@ -151,5 +151,5 @@ QAccessibleInterface *toQAccessibleInterface(BrowserAccessibility *acc); } // namespace content -#endif // QT_NO_ACCESSIBILITY +#endif // QT_CONFIG(accessibility) #endif diff --git a/src/core/compositor/delegated_frame_node.cpp b/src/core/compositor/delegated_frame_node.cpp index 4d74937d9..fcaae27bb 100644 --- a/src/core/compositor/delegated_frame_node.cpp +++ b/src/core/compositor/delegated_frame_node.cpp @@ -68,7 +68,7 @@ #include "components/viz/service/display/bsp_tree.h" #include "components/viz/service/display_embedder/server_shared_bitmap_manager.h" -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) # include <QOpenGLContext> # include <QOpenGLFunctions> # include <QSGFlatColorMaterial> @@ -79,7 +79,7 @@ #include <QSGImageNode> #include <QSGRectangleNode> -#if !defined(QT_NO_EGL) +#if QT_CONFIG(egl) #include <EGL/egl.h> #include <EGL/eglext.h> #endif @@ -108,14 +108,14 @@ #define GL_LINE_LOOP 0x0002 #endif -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) QT_BEGIN_NAMESPACE Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); QT_END_NAMESPACE #endif namespace QtWebEngineCore { -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) class MailboxTexture : public QSGTexture, protected QOpenGLFunctions { public: MailboxTexture(const CompositorResource *resource, bool hasAlphaChannel, int target = -1); @@ -141,7 +141,7 @@ private: #endif friend class DelegatedFrameNode; }; -#endif // QT_NO_OPENGL +#endif // QT_CONFIG(opengl) class RectClipNode : public QSGClipNode { @@ -167,7 +167,7 @@ public: QSGNode *) = 0; virtual void setupSolidColorNode(const QRect &, const QColor &, QSGNode *) = 0; -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) virtual void setupDebugBorderNode(QSGGeometry *, QSGFlatColorMaterial *, QSGNode *) = 0; virtual void setupYUVVideoNode(QSGTexture *, QSGTexture *, QSGTexture *, QSGTexture *, const QRectF &, const QRectF &, const QSizeF &, const QSizeF &, @@ -177,7 +177,7 @@ public: virtual void setupStreamVideoNode(MailboxTexture *, const QRectF &, const QMatrix4x4 &, QSGNode *) = 0; #endif // GL_OES_EGL_image_external -#endif // QT_NO_OPENGL +#endif // QT_CONFIG(opengl) protected: QVector<QSGNode*> *m_sceneGraphNodes; }; @@ -234,7 +234,7 @@ public: if (rectangleNode->color() != color) rectangleNode->setColor(color); } -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) void setupDebugBorderNode(QSGGeometry *geometry, QSGFlatColorMaterial *material, QSGNode *) override { @@ -259,7 +259,7 @@ public: Q_UNREACHABLE(); } #endif // GL_OES_EGL_image_external -#endif // QT_NO_OPENGL +#endif // QT_CONFIG(opengl) private: QVector<QSGNode*>::iterator m_nodeIterator; @@ -314,7 +314,7 @@ public: m_sceneGraphNodes->append(rectangleNode); } -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) void setupDebugBorderNode(QSGGeometry *geometry, QSGFlatColorMaterial *material, QSGNode *layerChain) override { @@ -363,7 +363,7 @@ public: m_sceneGraphNodes->append(svideoNode); } #endif // GL_OES_EGL_image_external -#endif // QT_NO_OPENGL +#endif // QT_CONFIG(opengl) private: RenderWidgetHostViewQtDelegate *m_apiDelegate; @@ -421,7 +421,7 @@ static QSGNode *buildLayerChain(QSGNode *chainParent, const viz::SharedQuadState return layerChain; } -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) MailboxTexture::MailboxTexture(const CompositorResource *resource, bool hasAlphaChannel, int target) : m_textureId(resource->texture_id) , m_fence(resource->texture_fence) @@ -476,7 +476,7 @@ void MailboxTexture::bind() } #endif } -#endif // !QT_NO_OPENGL +#endif // QT_CONFIG(opengl) RectClipNode::RectClipNode(const QRectF &rect) : m_geometry(QSGGeometry::defaultAttributes_Point2D(), 4) @@ -488,12 +488,12 @@ RectClipNode::RectClipNode(const QRectF &rect) } DelegatedFrameNode::DelegatedFrameNode() -#if defined(USE_OZONE) && !defined(QT_NO_OPENGL) +#if defined(USE_OZONE) && QT_CONFIG(opengl) : m_contextShared(true) #endif { setFlag(UsePreprocess); -#if defined(USE_OZONE) && !defined(QT_NO_OPENGL) +#if defined(USE_OZONE) && QT_CONFIG(opengl) QOpenGLContext *currentContext = QOpenGLContext::currentContext() ; QOpenGLContext *sharedContext = qt_gl_global_share_context(); if (currentContext && sharedContext && !QOpenGLContext::areSharing(currentContext, sharedContext)) { @@ -569,14 +569,14 @@ static bool areRenderPassStructuresEqual(const viz::CompositorFrame *frameData, const viz::DrawQuad *prevQuad = *prevIt; if (quad->material != prevQuad->material) return false; -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) if (quad->material == viz::DrawQuad::Material::kYuvVideoContent) return false; #ifdef GL_OES_EGL_image_external if (quad->material == viz::DrawQuad::Material::kStreamVideoContent) return false; #endif // GL_OES_EGL_image_external -#endif // QT_NO_OPENGL +#endif // QT_CONFIG(opengl) if (!areSharedQuadStatesEqual(quad->shared_quad_state, prevQuad->shared_quad_state)) return false; if (quad->shared_quad_state->is_clipped && quad->visible_rect != prevQuad->visible_rect) { @@ -680,7 +680,7 @@ void DelegatedFrameNode::commit(const viz::CompositorFrame &pendingFrame, rpLayer->setSize(toQt(pass->output_rect.size())); rpLayer->setFormat(pass->has_transparent_background ? GL_RGBA : GL_RGB); rpLayer->setHasMipmaps(pass->generate_mipmap); - rpLayer->setMirrorVertical(true); + rpLayer->setMirrorVertical(false); scissorRect = pass->output_rect; } else { renderPassParent = this; @@ -880,7 +880,7 @@ void DelegatedFrameNode::handleQuad( Q_UNUSED(scquad->force_anti_aliasing_off); nodeHandler->setupSolidColorNode(toQt(quad->rect), toQt(scquad->color), currentLayerChain); break; -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) } case viz::DrawQuad::Material::kDebugBorder: { const viz::DebugBorderDrawQuad *dbquad = viz::DebugBorderDrawQuad::MaterialCast(quad); @@ -913,7 +913,7 @@ void DelegatedFrameNode::handleQuad( toQt(quad->rect), toQt(tquad->tex_coord_rect), QSGImageNode::NoTransform, currentLayerChain); break; -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) } case viz::DrawQuad::Material::kYuvVideoContent: { const viz::YUVVideoDrawQuad *vquad = viz::YUVVideoDrawQuad::MaterialCast(quad); @@ -952,7 +952,7 @@ void DelegatedFrameNode::handleQuad( nodeHandler->setupStreamVideoNode(texture, toQt(squad->rect), qMatrix, currentLayerChain); break; #endif // GL_OES_EGL_image_external -#endif // QT_NO_OPENGL +#endif // QT_CONFIG(opengl) } case viz::DrawQuad::Material::kSurfaceContent: Q_UNREACHABLE(); @@ -1050,7 +1050,7 @@ QSharedPointer<QSGTexture> DelegatedFrameNode::createBitmapTexture(const Composi QSharedPointer<MailboxTexture> DelegatedFrameNode::createMailboxTexture(const CompositorResource *resource, bool hasAlphaChannel, int target) { -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) return QSharedPointer<MailboxTexture>::create(resource, hasAlphaChannel, target); #else Q_UNREACHABLE(); @@ -1059,7 +1059,7 @@ QSharedPointer<MailboxTexture> DelegatedFrameNode::createMailboxTexture(const Co void DelegatedFrameNode::copyMailboxTextures() { -#if !defined(QT_NO_OPENGL) && defined(USE_OZONE) +#if QT_CONFIG(opengl) && defined(USE_OZONE) // Workaround when context is not shared QTBUG-48969 // Make slow copy between two contexts. if (!m_contextShared) { diff --git a/src/core/content_browser_client_qt.cpp b/src/core/content_browser_client_qt.cpp index cac392182..05957c26f 100644 --- a/src/core/content_browser_client_qt.cpp +++ b/src/core/content_browser_client_qt.cpp @@ -312,7 +312,7 @@ private: void ShareGroupQtQuick::AboutToAddFirstContext() { -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) // This currently has to be setup by ::main in all applications using QQuickWebEngineView with delegated rendering. QOpenGLContext *shareContext = qt_gl_global_share_context(); if (!shareContext) { diff --git a/src/core/core_module.pro b/src/core/core_module.pro index d7e2ab8da..5007012ac 100644 --- a/src/core/core_module.pro +++ b/src/core/core_module.pro @@ -29,12 +29,11 @@ write_file($$RSP_OBJECT_FILE, RSP_O_CONTENT) RSP_ARCHIVE_FILE = $$OUT_PWD/$$getConfigDir()/$${TARGET}_a.rsp for(archive, NINJA_ARCHIVES): RSP_A_CONTENT += $$archive write_file($$RSP_ARCHIVE_FILE, RSP_A_CONTENT) -macos:LIBS_PRIVATE += -Wl,-filelist,$$shell_quote($$RSP_OBJECT_FILE) -linux:QMAKE_LFLAGS += @$${RSP_OBJECT_FILE} -# QTBUG-58710 add main rsp file on windows -win32:QMAKE_LFLAGS += @$${RSP_OBJECT_FILE} -linux:QMAKE_LFLAGS += -Wl,--start-group @$${RSP_ARCHIVE_FILE} -Wl,--end-group -else: LIBS_PRIVATE += $$NINJA_ARCHIVES + +macos:LIBS_PRIVATE += -Wl,-filelist,$$shell_quote($${RSP_OBJECT_FILE}) @$${RSP_ARCHIVE_FILE} +linux:QMAKE_LFLAGS += @$${RSP_OBJECT_FILE} -Wl,--start-group @$${RSP_ARCHIVE_FILE} -Wl,--end-group +win32:QMAKE_LFLAGS += @$${RSP_OBJECT_FILE} @$${RSP_ARCHIVE_FILE} + LIBS_PRIVATE += $$NINJA_LIB_DIRS $$NINJA_LIBS # GN's LFLAGS doesn't always work across all the Linux configurations we support. # The Windows and macOS ones from GN does provide a few useful flags however diff --git a/src/core/net/cookie_monster_delegate_qt.cpp b/src/core/net/cookie_monster_delegate_qt.cpp index cf114406b..d3157f760 100644 --- a/src/core/net/cookie_monster_delegate_qt.cpp +++ b/src/core/net/cookie_monster_delegate_qt.cpp @@ -223,20 +223,11 @@ void CookieMonsterDelegateQt::OnCookieChanged(const net::CookieChangeInfo &chang m_client->d_func()->onCookieChanged(toQt(change.cookie), change.cause != net::CookieChangeCause::INSERTED); } -void CookieMonsterDelegateQt::GetAllCookiesCallbackOnUIThread(qint64 callbackId, const std::vector<net::CanonicalCookie> &cookies) -{ - DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - QByteArray rawCookies; - for (auto &&cookie : cookies) - rawCookies += toQt(cookie).toRawForm() % QByteArrayLiteral("\n"); - - GetAllCookiesResultOnUIThread(callbackId, rawCookies); -} - -void CookieMonsterDelegateQt::GetAllCookiesResultOnUIThread(qint64 callbackId, const QByteArray &cookies) +void CookieMonsterDelegateQt::GetAllCookiesCallbackOnUIThread(qint64 callbackId, const net::CookieList &cookies) { + QByteArray rawCookies = QByteArray::fromStdString(net::CanonicalCookie::BuildCookieLine(cookies)); if (m_client) - m_client->d_func()->onGetAllCallbackResult(callbackId, cookies); + m_client->d_func()->onGetAllCallbackResult(callbackId, rawCookies); } void CookieMonsterDelegateQt::SetCookieCallbackOnUIThread(qint64 callbackId, net::CanonicalCookie::CookieInclusionStatus status) @@ -250,4 +241,5 @@ void CookieMonsterDelegateQt::DeleteCookiesCallbackOnUIThread(qint64 callbackId, if (m_client) m_client->d_func()->onDeleteCallbackResult(callbackId, numCookies); } -} + +} // namespace QtWebEngineCore diff --git a/src/core/net/cookie_monster_delegate_qt.h b/src/core/net/cookie_monster_delegate_qt.h index 6caaeea94..bcbbe4c52 100644 --- a/src/core/net/cookie_monster_delegate_qt.h +++ b/src/core/net/cookie_monster_delegate_qt.h @@ -115,8 +115,7 @@ public: void OnCookieChanged(const net::CookieChangeInfo &change); private: - void GetAllCookiesCallbackOnUIThread(qint64 callbackId, const std::vector<net::CanonicalCookie> &cookies); - void GetAllCookiesResultOnUIThread(qint64 callbackId, const QByteArray &cookies); + void GetAllCookiesCallbackOnUIThread(qint64 callbackId, const net::CookieList &cookies); void SetCookieCallbackOnUIThread(qint64 callbackId, net::CanonicalCookie::CookieInclusionStatus status); void DeleteCookiesCallbackOnUIThread(qint64 callbackId, uint numCookies); }; diff --git a/src/core/net/system_network_context_manager.cpp b/src/core/net/system_network_context_manager.cpp index 43b2b2557..29cc82abf 100644 --- a/src/core/net/system_network_context_manager.cpp +++ b/src/core/net/system_network_context_manager.cpp @@ -166,7 +166,8 @@ private: network::mojom::NetworkContext *SystemNetworkContextManager::GetContext() { - if (!network_service_network_context_ || network_service_network_context_.encountered_error()) { + if (!network_service_network_context_ || + !network_service_network_context_.is_connected()) { // This should call into OnNetworkServiceCreated(), which will re-create // the network service, if needed. There's a chance that it won't be // invoked, if the NetworkContext has encountered an error but the @@ -182,14 +183,14 @@ network::mojom::NetworkContext *SystemNetworkContextManager::GetContext() network::mojom::URLLoaderFactory *SystemNetworkContextManager::GetURLLoaderFactory() { // Create the URLLoaderFactory as needed. - if (url_loader_factory_ && !url_loader_factory_.encountered_error()) { + if (url_loader_factory_ && url_loader_factory_.is_connected()) { return url_loader_factory_.get(); } network::mojom::URLLoaderFactoryParamsPtr params = network::mojom::URLLoaderFactoryParams::New(); params->process_id = network::mojom::kBrowserProcessId; params->is_corb_enabled = false; - GetContext()->CreateURLLoaderFactory(mojo::MakeRequest(&url_loader_factory_), std::move(params)); + GetContext()->CreateURLLoaderFactory(url_loader_factory_.BindNewPipeAndPassReceiver(), std::move(params)); return url_loader_factory_.get(); } @@ -252,7 +253,10 @@ void SystemNetworkContextManager::OnNetworkServiceCreated(network::mojom::Networ // The system NetworkContext must be created first, since it sets // |primary_network_context| to true. - network_service->CreateNetworkContext(MakeRequest(&network_service_network_context_), CreateNetworkContextParams()); + network_service_network_context_.reset(); + network_service->CreateNetworkContext( + network_service_network_context_.BindNewPipeAndPassReceiver(), + CreateNetworkContextParams()); // Configure the stub resolver. This must be done after the system // NetworkContext is created, but before anything has the chance to use it. diff --git a/src/core/net/system_network_context_manager.h b/src/core/net/system_network_context_manager.h index e429453a2..5094008f2 100644 --- a/src/core/net/system_network_context_manager.h +++ b/src/core/net/system_network_context_manager.h @@ -167,12 +167,12 @@ private: // NetworkContext using the network service, if the network service is // enabled. nullptr, otherwise. - network::mojom::NetworkContextPtr network_service_network_context_; + mojo::Remote<network::mojom::NetworkContext> network_service_network_context_; // URLLoaderFactory backed by the NetworkContext returned by GetContext(), so // consumers don't all need to create their own factory. scoped_refptr<URLLoaderFactoryForSystem> shared_url_loader_factory_; - network::mojom::URLLoaderFactoryPtr url_loader_factory_; + mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory_; ProxyConfigMonitor proxy_config_monitor_; diff --git a/src/core/ozone/gl_context_qt.cpp b/src/core/ozone/gl_context_qt.cpp index e9337874a..10347bdc7 100644 --- a/src/core/ozone/gl_context_qt.cpp +++ b/src/core/ozone/gl_context_qt.cpp @@ -59,7 +59,7 @@ namespace { inline void *resourceForContext(const QByteArray &resource) { -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) QOpenGLContext *shareContext = qt_gl_global_share_context(); if (!shareContext) { qFatal("QWebEngine: OpenGL resource sharing is not set up in QtQuick. Please make sure to call QtWebEngine::initialize() in your main() function."); @@ -153,7 +153,7 @@ void* GLContextHelper::getNativeDisplay() QFunctionPointer GLContextHelper::getGlXGetProcAddress() { QFunctionPointer get_proc_address = nullptr; -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) if (QOpenGLContext *context = qt_gl_global_share_context()) { get_proc_address = context->getProcAddress("glXGetProcAddress"); } @@ -164,7 +164,7 @@ QFunctionPointer GLContextHelper::getGlXGetProcAddress() QFunctionPointer GLContextHelper::getEglGetProcAddress() { QFunctionPointer get_proc_address = nullptr; -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) if (QOpenGLContext *context = qt_gl_global_share_context()) { get_proc_address = context->getProcAddress("eglGetProcAddress"); } diff --git a/src/core/ozone/gl_ozone_egl_qt.cpp b/src/core/ozone/gl_ozone_egl_qt.cpp index 2fa86d79b..c692920cf 100644 --- a/src/core/ozone/gl_ozone_egl_qt.cpp +++ b/src/core/ozone/gl_ozone_egl_qt.cpp @@ -55,9 +55,9 @@ #include <EGL/egl.h> #include <dlfcn.h> -#include <QtGui/qtgui-config.h> // for QT_NO_OPENGL +#include <QtGui/qtgui-config.h> -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) #include <QOpenGLContext> QT_BEGIN_NAMESPACE Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); @@ -88,7 +88,7 @@ bool GLOzoneEGLQt::LoadGLES2Bindings(gl::GLImplementation /*implementation*/) reinterpret_cast<gl::GLGetProcAddressProc>( base::GetFunctionPointerFromNativeLibrary(eglgles2Library, "eglGetProcAddress")); -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) if (!get_proc_address) { // QTBUG-63341 most likely libgles2 not linked with libegl -> fallback to qpa if (QOpenGLContext *context = qt_gl_global_share_context()) { diff --git a/src/core/permission_manager_qt.cpp b/src/core/permission_manager_qt.cpp index 16a7b25bf..2f9543769 100644 --- a/src/core/permission_manager_qt.cpp +++ b/src/core/permission_manager_qt.cpp @@ -112,8 +112,13 @@ PermissionManagerQt::~PermissionManagerQt() { } -void PermissionManagerQt::permissionRequestReply(const QUrl &origin, ProfileAdapter::PermissionType type, bool reply) +void PermissionManagerQt::permissionRequestReply(const QUrl &url, ProfileAdapter::PermissionType type, bool reply) { + // Normalize the QUrl to GURL origin form. + const GURL gorigin = toGurl(url).GetOrigin(); + const QUrl origin = gorigin.is_empty() ? url : toQt(gorigin); + if (origin.isEmpty()) + return; QPair<QUrl, ProfileAdapter::PermissionType> key(origin, type); m_permissions[key] = reply; blink::mojom::PermissionStatus status = reply ? blink::mojom::PermissionStatus::GRANTED : blink::mojom::PermissionStatus::DENIED; diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp index 5887b356e..c4d6200b7 100644 --- a/src/core/render_widget_host_view_qt.cpp +++ b/src/core/render_widget_host_view_qt.cpp @@ -286,6 +286,7 @@ RenderWidgetHostViewQt::RenderWidgetHostViewQt(content::RenderWidgetHost *widget , m_adapterClient(0) , m_imeInProgress(false) , m_receivedEmptyImeEvent(false) + , m_isMouseLocked(false) , m_imState(0) , m_anchorPositionWithinSelection(-1) , m_cursorPositionWithinSelection(-1) @@ -425,14 +426,14 @@ gfx::NativeViewAccessible RenderWidgetHostViewQt::GetNativeViewAccessible() content::BrowserAccessibilityManager* RenderWidgetHostViewQt::CreateBrowserAccessibilityManager(content::BrowserAccessibilityDelegate* delegate, bool for_root_frame) { Q_UNUSED(for_root_frame); // FIXME -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) return new content::BrowserAccessibilityManagerQt( m_adapterClient->accessibilityParentObject(), content::BrowserAccessibilityManagerQt::GetEmptyDocument(), delegate); #else return 0; -#endif // QT_NO_ACCESSIBILITY +#endif // QT_CONFIG(accessibility) } // Set focus to the associated View component. @@ -448,6 +449,11 @@ bool RenderWidgetHostViewQt::HasFocus() return m_delegate->hasKeyboardFocus(); } +bool RenderWidgetHostViewQt::IsMouseLocked() +{ + return m_isMouseLocked; +} + bool RenderWidgetHostViewQt::IsSurfaceAvailableForCopy() { if (m_enableViz) @@ -520,6 +526,7 @@ bool RenderWidgetHostViewQt::LockMouse(bool) { m_previousMousePosition = QCursor::pos(); m_delegate->lockMouse(); + m_isMouseLocked = true; qApp->setOverrideCursor(Qt::BlankCursor); return true; } @@ -528,6 +535,7 @@ void RenderWidgetHostViewQt::UnlockMouse() { m_delegate->unlockMouse(); qApp->restoreOverrideCursor(); + m_isMouseLocked = false; host()->LostMouseLock(); } @@ -1530,7 +1538,21 @@ void RenderWidgetHostViewQt::WheelEventAck(const blink::WebMouseWheelEvent &even m_mouseWheelPhaseHandler.AddPhaseIfNeededAndScheduleEndEvent(webEvent, false); host()->ForwardWheelEvent(webEvent); } - // TODO: We could forward unhandled wheelevents to our parent. +} + +void RenderWidgetHostViewQt::GestureEventAck(const blink::WebGestureEvent &event, content::InputEventAckState ack_result) +{ + // Forward unhandled scroll events back as wheel events + if (event.GetType() != blink::WebInputEvent::kGestureScrollUpdate) + return; + switch (ack_result) { + case content::INPUT_EVENT_ACK_STATE_NOT_CONSUMED: + case content::INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS: + WebEventFactory::sendUnhandledWheelEvent(event, delegate()); + break; + default: + break; + } } content::MouseWheelPhaseHandler *RenderWidgetHostViewQt::GetMouseWheelPhaseHandler() @@ -1770,6 +1792,8 @@ void RenderWidgetHostViewQt::handleFocusEvent(QFocusEvent *ev) else if (ev->reason() == Qt::BacktabFocusReason) viewHost->SetInitialFocus(true); ev->accept(); + + m_adapterClient->webContentsAdapter()->handlePendingMouseLockPermission(); } else if (ev->lostFocus()) { host()->SetActive(false); host()->LostFocus(); diff --git a/src/core/render_widget_host_view_qt.h b/src/core/render_widget_host_view_qt.h index 41ce50b34..453b90888 100644 --- a/src/core/render_widget_host_view_qt.h +++ b/src/core/render_widget_host_view_qt.h @@ -132,6 +132,7 @@ public: gfx::NativeViewAccessible GetNativeViewAccessible() override; void Focus() override; bool HasFocus() override; + bool IsMouseLocked() override; bool IsSurfaceAvailableForCopy() override; void CopyFromSurface(const gfx::Rect &src_rect, const gfx::Size &output_size, @@ -155,6 +156,7 @@ public: void DidCreateNewRendererCompositorFrameSink(viz::mojom::CompositorFrameSinkClient* renderer_compositor_frame_sink) override; void SubmitCompositorFrame(const viz::LocalSurfaceId&, viz::CompositorFrame, base::Optional<viz::HitTestRegionList>) override; void WheelEventAck(const blink::WebMouseWheelEvent &event, content::InputEventAckState ack_result) override; + void GestureEventAck(const blink::WebGestureEvent &event, content::InputEventAckState ack_result) override; content::MouseWheelPhaseHandler *GetMouseWheelPhaseHandler() override; viz::ScopedSurfaceIdAllocator DidUpdateVisualProperties(const cc::RenderFrameMetadata &metadata) override; void OnDidUpdateVisualPropertiesComplete(const cc::RenderFrameMetadata &metadata); @@ -288,6 +290,7 @@ private: bool m_imeInProgress; bool m_receivedEmptyImeEvent; QPoint m_previousMousePosition; + bool m_isMouseLocked; gfx::Vector2dF m_lastScrollOffset; gfx::SizeF m_lastContentsSize; diff --git a/src/core/render_widget_host_view_qt_delegate.h b/src/core/render_widget_host_view_qt_delegate.h index 4ee790ce9..46f1802a6 100644 --- a/src/core/render_widget_host_view_qt_delegate.h +++ b/src/core/render_widget_host_view_qt_delegate.h @@ -58,12 +58,13 @@ QT_BEGIN_NAMESPACE class QEvent; +class QInputMethodEvent; class QSGLayer; class QSGNode; class QSGRectangleNode; class QSGTexture; class QVariant; -class QInputMethodEvent; +class QWheelEvent; class QSGImageNode; @@ -111,6 +112,7 @@ public: virtual void setInputMethodHints(Qt::InputMethodHints hints) = 0; virtual void setClearColor(const QColor &color) = 0; virtual bool copySurface(const QRect &, const QSize &, QImage &) = 0; + virtual void unhandledWheelEvent(QWheelEvent *) {} }; } // namespace QtWebEngineCore diff --git a/src/core/web_contents_adapter.cpp b/src/core/web_contents_adapter.cpp index 2d559bb38..0f2f21f83 100644 --- a/src/core/web_contents_adapter.cpp +++ b/src/core/web_contents_adapter.cpp @@ -1024,7 +1024,7 @@ QWebEngineUrlRequestInterceptor* WebContentsAdapter::requestInterceptor() const return m_requestInterceptor; } -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) QAccessibleInterface *WebContentsAdapter::browserAccessible() { CHECK_INITIALIZED(nullptr); @@ -1040,7 +1040,7 @@ QAccessibleInterface *WebContentsAdapter::browserAccessible() return content::toQAccessibleInterface(acc); } -#endif // QT_NO_ACCESSIBILITY +#endif // QT_CONFIG(accessibility) void WebContentsAdapter::runJavaScript(const QString &javaScript, quint32 worldId) { @@ -1372,20 +1372,40 @@ void WebContentsAdapter::runFeatureRequestCallback(const QUrl &securityOrigin, P m_profileAdapter->permissionRequestReply(securityOrigin, feature, allowed); } -void WebContentsAdapter::grantMouseLockPermission(bool granted) +void WebContentsAdapter::grantMouseLockPermission(const QUrl &securityOrigin, bool granted) { CHECK_INITIALIZED(); + if (securityOrigin != toQt(m_webContents->GetLastCommittedURL().GetOrigin())) + return; if (granted) { - if (RenderWidgetHostViewQt *rwhv = static_cast<RenderWidgetHostViewQt *>(m_webContents->GetRenderWidgetHostView())) + if (RenderWidgetHostViewQt *rwhv = static_cast<RenderWidgetHostViewQt *>(m_webContents->GetRenderWidgetHostView())) { rwhv->Focus(); - else + if (!rwhv->HasFocus()) { + // We tried to activate our RWHVQtDelegate, but we failed. This probably means that + // the permission was granted from a modal dialog and the windowing system is not ready + // to set focus on the originating view. Since pointer lock strongly requires it, we just + // wait until the next FocusIn event. + m_pendingMouseLockPermissions.insert(securityOrigin, granted); + return; + } + } else granted = false; } m_webContents->GotResponseToLockMouseRequest(granted); } +void WebContentsAdapter::handlePendingMouseLockPermission() +{ + CHECK_INITIALIZED(); + auto it = m_pendingMouseLockPermissions.find(toQt(m_webContents->GetLastCommittedURL().GetOrigin())); + if (it != m_pendingMouseLockPermissions.end()) { + m_webContents->GotResponseToLockMouseRequest(it.value()); + m_pendingMouseLockPermissions.erase(it); + } +} + void WebContentsAdapter::setBackgroundColor(const QColor &color) { CHECK_INITIALIZED(); diff --git a/src/core/web_contents_adapter.h b/src/core/web_contents_adapter.h index cc041ed55..66808ce5e 100644 --- a/src/core/web_contents_adapter.h +++ b/src/core/web_contents_adapter.h @@ -194,7 +194,8 @@ public: void devToolsFrontendDestroyed(DevToolsFrontendQt *frontend); void grantMediaAccessPermission(const QUrl &securityOrigin, WebContentsAdapterClient::MediaRequestFlags flags); - void grantMouseLockPermission(bool granted); + void grantMouseLockPermission(const QUrl &securityOrigin, bool granted); + void handlePendingMouseLockPermission(); void runFeatureRequestCallback(const QUrl &securityOrigin, ProfileAdapter::PermissionType feature, bool allowed); void setBackgroundColor(const QColor &color); @@ -268,6 +269,7 @@ private: #endif WebContentsAdapterClient *m_adapterClient; quint64 m_nextRequestId; + QMap<QUrl, bool> m_pendingMouseLockPermissions; std::unique_ptr<content::DropData> m_currentDropData; uint m_currentDropAction; bool m_updateDragActionCalled; diff --git a/src/core/web_contents_adapter_client.h b/src/core/web_contents_adapter_client.h index 250801f51..04df99f0e 100644 --- a/src/core/web_contents_adapter_client.h +++ b/src/core/web_contents_adapter_client.h @@ -469,7 +469,10 @@ public: virtual void loadFinished(bool success, const QUrl &url, bool isErrorPage = false, int errorCode = 0, const QString &errorDescription = QString()) = 0; virtual void focusContainer() = 0; virtual void unhandledKeyEvent(QKeyEvent *event) = 0; - virtual void adoptNewWindow(QSharedPointer<WebContentsAdapter> newWebContents, WindowOpenDisposition disposition, bool userGesture, const QRect & initialGeometry, const QUrl &targetUrl) = 0; + virtual QSharedPointer<WebContentsAdapter> + adoptNewWindow(QSharedPointer<WebContentsAdapter> newWebContents, + WindowOpenDisposition disposition, bool userGesture, + const QRect &initialGeometry, const QUrl &targetUrl) = 0; virtual bool isBeingAdopted() = 0; virtual void close() = 0; virtual void windowCloseRejected() = 0; diff --git a/src/core/web_contents_delegate_qt.cpp b/src/core/web_contents_delegate_qt.cpp index fada874a3..bf0254e82 100644 --- a/src/core/web_contents_delegate_qt.cpp +++ b/src/core/web_contents_delegate_qt.cpp @@ -655,7 +655,7 @@ void WebContentsDelegateQt::RequestToLockMouse(content::WebContents *web_content if (last_unlocked_by_target) web_contents->GotResponseToLockMouseRequest(true); else - m_viewClient->runMouseLockPermissionRequest(toQt(web_contents->GetVisibleURL())); + m_viewClient->runMouseLockPermissionRequest(toQt(web_contents->GetLastCommittedURL().GetOrigin())); } void WebContentsDelegateQt::overrideWebPreferences(content::WebContents *webContents, content::WebPreferences *webPreferences) @@ -663,14 +663,17 @@ void WebContentsDelegateQt::overrideWebPreferences(content::WebContents *webCont m_viewClient->webEngineSettings()->overrideWebPreferences(webContents, webPreferences); } -QWeakPointer<WebContentsAdapter> WebContentsDelegateQt::createWindow(std::unique_ptr<content::WebContents> new_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, bool user_gesture) +QSharedPointer<WebContentsAdapter> +WebContentsDelegateQt::createWindow(std::unique_ptr<content::WebContents> new_contents, + WindowOpenDisposition disposition, const gfx::Rect &initial_pos, + bool user_gesture) { QSharedPointer<WebContentsAdapter> newAdapter = QSharedPointer<WebContentsAdapter>::create(std::move(new_contents)); - m_viewClient->adoptNewWindow(newAdapter, static_cast<WebContentsAdapterClient::WindowOpenDisposition>(disposition), user_gesture, toQt(initial_pos), m_initialTargetUrl); - - // If the client didn't reference the adapter, it will be deleted now, and the weak pointer zeroed. - return newAdapter; + return m_viewClient->adoptNewWindow( + std::move(newAdapter), + static_cast<WebContentsAdapterClient::WindowOpenDisposition>(disposition), user_gesture, + toQt(initial_pos), m_initialTargetUrl); } void WebContentsDelegateQt::allowCertificateError(const QSharedPointer<CertificateErrorController> &errorController) @@ -797,6 +800,15 @@ bool WebContentsDelegateQt::TakeFocus(content::WebContents *source, bool reverse return m_viewClient->passOnFocus(reverse); } +void WebContentsDelegateQt::ContentsZoomChange(bool zoom_in) +{ + WebContentsAdapter *adapter = webContentsAdapter(); + if (zoom_in) + adapter->setZoomFactor(adapter->currentZoomFactor() + 0.1f); + else + adapter->setZoomFactor(adapter->currentZoomFactor() - 0.1f); +} + FaviconManager *WebContentsDelegateQt::faviconManager() { return m_faviconManager.data(); diff --git a/src/core/web_contents_delegate_qt.h b/src/core/web_contents_delegate_qt.h index f32b02caf..bfef9a1df 100644 --- a/src/core/web_contents_delegate_qt.h +++ b/src/core/web_contents_delegate_qt.h @@ -148,6 +148,7 @@ public: void RegisterProtocolHandler(content::WebContents* web_contents, const std::string& protocol, const GURL& url, bool user_gesture) override; void UnregisterProtocolHandler(content::WebContents* web_contents, const std::string& protocol, const GURL& url, bool user_gesture) override; bool TakeFocus(content::WebContents *source, bool reverse) override; + void ContentsZoomChange(bool zoom_in) override; // WebContentsObserver overrides void RenderFrameCreated(content::RenderFrameHost *render_frame_host) override; @@ -200,7 +201,10 @@ public: base::WeakPtr<WebContentsDelegateQt> AsWeakPtr() { return m_weakPtrFactory.GetWeakPtr(); } private: - QWeakPointer<WebContentsAdapter> createWindow(std::unique_ptr<content::WebContents> new_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, bool user_gesture); + QSharedPointer<WebContentsAdapter> + createWindow(std::unique_ptr<content::WebContents> new_contents, + WindowOpenDisposition disposition, const gfx::Rect &initial_pos, + bool user_gesture); void EmitLoadStarted(const QUrl &url, bool isErrorPage = false); void EmitLoadFinished(bool success, const QUrl &url, bool isErrorPage = false, int errorCode = 0, const QString &errorDescription = QString()); void EmitLoadCommitted(); diff --git a/src/core/web_engine_context.cpp b/src/core/web_engine_context.cpp index 17742e1d7..a28e469a3 100644 --- a/src/core/web_engine_context.cpp +++ b/src/core/web_engine_context.cpp @@ -106,7 +106,7 @@ #include "base/mac/foundation_util.h" #endif -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) #include "accessibility_activation_observer.h" #endif #include "api/qwebengineurlscheme.h" @@ -125,7 +125,7 @@ #include <QGuiApplication> #include <QMutex> #include <QOffscreenSurface> -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) # include <QOpenGLContext> #endif #include <QQuickWindow> @@ -138,7 +138,7 @@ using namespace QtWebEngineCore; -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) QT_BEGIN_NAMESPACE Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); QT_END_NAMESPACE @@ -146,7 +146,7 @@ QT_END_NAMESPACE namespace { -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) bool usingANGLE() { #if defined(Q_OS_WIN) @@ -179,7 +179,7 @@ bool usingDefaultSGBackend() return device.isEmpty(); } -#endif //QT_NO_OPENGL +#endif // QT_CONFIG(opengl) #if QT_CONFIG(webengine_pepper_plugins) void dummyGetPluginCallback(const std::vector<content::WebPluginInfo>&) { @@ -208,7 +208,7 @@ bool usingSoftwareDynamicGL() { if (QCoreApplication::testAttribute(Qt::AA_UseSoftwareOpenGL)) return true; -#if defined(Q_OS_WIN) && !defined(QT_NO_OPENGL) +#if defined(Q_OS_WIN) && QT_CONFIG(opengl) HMODULE handle = static_cast<HMODULE>(QOpenGLContext::openGLModuleHandle()); wchar_t path[MAX_PATH]; DWORD size = GetModuleFileName(handle, path, MAX_PATH); @@ -261,6 +261,52 @@ static void cleanupVizProcess() vizCompositorThreadRunner->CleanupForShutdown(base::BindOnce(&completeVizCleanup)); } +static QStringList parseEnvCommandLine(const QString &cmdLine) +{ + QString arg; + QStringList arguments; + enum { Parse, Quoted, Unquoted } state = Parse; + for (const QChar c : cmdLine) { + switch (state) { + case Parse: + if (c == '"') { + state = Quoted; + } else if (c != ' ' ) { + arg += c; + state = Unquoted; + } + // skips spaces + break; + case Quoted: + if (c == '"') { + DCHECK(!arg.isEmpty()); + state = Unquoted; + } else { + // includes spaces + arg += c; + } + break; + case Unquoted: + if (c == '"') { + // skips quotes + state = Quoted; + } else if (c == ' ') { + arguments.append(arg); + arg.clear(); + state = Parse; + } else { + arg += c; + } + break; + } + } + // last arg + if (!arg.isEmpty()) { + arguments.append(arg); + } + return arguments; +} + scoped_refptr<QtWebEngineCore::WebEngineContext> WebEngineContext::m_handle; bool WebEngineContext::m_destroyed = false; @@ -559,7 +605,7 @@ WebEngineContext::WebEngineContext() parsedCommandLine->AppendSwitch(switches::kDisableES3GLContext); #endif bool threadedGpu = false; -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) threadedGpu = QOpenGLContext::supportsThreadedOpenGL(); #if defined(Q_OS_MACOS) // QtBase disabled it when building on 10.14+, unfortunately we still need it @@ -595,6 +641,9 @@ WebEngineContext::WebEngineContext() appendToFeatureList(disableFeatures, network::features::kDnsOverHttpsUpgrade.name); + // When enabled, event.movement is calculated in blink instead of in browser. + appendToFeatureList(disableFeatures, features::kConsolidatedMovementXY.name); + // Explicitly tell Chromium about default-on features we do not support appendToFeatureList(disableFeatures, features::kBackgroundFetch.name); appendToFeatureList(disableFeatures, features::kSmsReceiver.name); @@ -643,7 +692,7 @@ WebEngineContext::WebEngineContext() GLContextHelper::initialize(); const char *glType = 0; -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) const bool tryGL = (usingDefaultSGBackend() && !usingSoftwareDynamicGL() && QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL)) @@ -709,7 +758,7 @@ WebEngineContext::WebEngineContext() qWarning("WebEngineContext used before QtWebEngine::initialize() or OpenGL context creation failed."); } } -#endif +#endif // QT_CONFIG(opengl) if (glType) { parsedCommandLine->AppendSwitchASCII(switches::kUseGL, glType); @@ -796,7 +845,7 @@ WebEngineContext::WebEngineContext() m_printJobManager.reset(new printing::PrintJobManager()); #endif -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) m_accessibilityActivationObserver.reset(new AccessibilityActivationObserver()); #endif @@ -829,7 +878,7 @@ base::CommandLine* WebEngineContext::commandLine() { QStringList appArgs = QCoreApplication::arguments(); if (qEnvironmentVariableIsSet(kChromiumFlagsEnv)) { appArgs = appArgs.mid(0, 1); // Take application name and drop the rest - appArgs.append(QString::fromLocal8Bit(qgetenv(kChromiumFlagsEnv)).split(' ')); + appArgs.append(parseEnvCommandLine(QString::fromLocal8Bit(qgetenv(kChromiumFlagsEnv)))); } #ifdef Q_OS_WIN appArgs.removeAll(QStringLiteral("--enable-webgl-software-rendering")); diff --git a/src/core/web_engine_context.h b/src/core/web_engine_context.h index ac0536596..6cbd5c8e5 100644 --- a/src/core/web_engine_context.h +++ b/src/core/web_engine_context.h @@ -46,6 +46,7 @@ #include "base/memory/ref_counted.h" #include "base/values.h" +#include <QtGui/qtgui-config.h> #include <QVector> namespace base { @@ -142,7 +143,7 @@ private: std::unique_ptr<ProfileAdapter> m_defaultProfileAdapter; std::unique_ptr<DevToolsServerQt> m_devtoolsServer; QVector<ProfileAdapter*> m_profileAdapters; -#ifndef QT_NO_ACCESSIBILITY +#if QT_CONFIG(accessibility) std::unique_ptr<AccessibilityActivationObserver> m_accessibilityActivationObserver; #endif diff --git a/src/core/web_event_factory.cpp b/src/core/web_event_factory.cpp index f37cce6c7..e1db69b16 100644 --- a/src/core/web_event_factory.cpp +++ b/src/core/web_event_factory.cpp @@ -71,6 +71,8 @@ #include "ui/events/keycodes/dom/keycode_converter.h" #include "ui/events/keycodes/keyboard_code_conversion.h" +#include "render_widget_host_view_qt_delegate.h" + #include <QtGui/private/qtgui-config_p.h> #include <QCoreApplication> @@ -84,6 +86,8 @@ #endif #include <QWheelEvent> +namespace QtWebEngineCore { + using namespace blink; enum class KeyboardDriver { Unknown, Windows, Cocoa, Xkb, Evdev }; @@ -174,8 +178,13 @@ static QString qtTextForKeyEvent(const QKeyEvent *ev, int qtKey, Qt::KeyboardMod { QString text = ev->text(); - if ((qtModifiers & Qt::ControlModifier) && - (keyboardDriver() == KeyboardDriver::Xkb || keyboardDriver() == KeyboardDriver::Windows)) { + if (keyboardDriver() == KeyboardDriver::Xkb && (qtModifiers & Qt::ControlModifier)) { + text.clear(); + } + + // Keep text for Ctrl+Alt key combinations on Windows. It is an alternative for AltGr. + if (keyboardDriver() == KeyboardDriver::Windows + && (qtModifiers & Qt::ControlModifier) && !(qtModifiers & Qt::AltModifier)) { text.clear(); } @@ -1306,6 +1315,42 @@ static inline WebInputEvent::Modifiers modifiersForEvent(const QInputEvent* even return (WebInputEvent::Modifiers)result; } +static inline Qt::KeyboardModifiers keyboardModifiersForModifier(unsigned int modifier) +{ + Qt::KeyboardModifiers modifiers = {}; + if (modifier & WebInputEvent::kControlKey) + modifiers |= Qt::ControlModifier; + if (modifier & WebInputEvent::kMetaKey) + modifiers |= Qt::MetaModifier; + if (modifier & WebInputEvent::kShiftKey) + modifiers |= Qt::ShiftModifier; + if (modifier & WebInputEvent::kAltKey) + modifiers |= Qt::AltModifier; + if (modifier & WebInputEvent::kIsKeyPad) + modifiers |= Qt::KeypadModifier; + + if (keyboardDriver() == KeyboardDriver::Cocoa && !qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + bool controlModifier = modifiers.testFlag(Qt::ControlModifier); + bool metaModifier = modifiers.testFlag(Qt::MetaModifier); + modifiers.setFlag(Qt::ControlModifier, metaModifier); + modifiers.setFlag(Qt::MetaModifier, controlModifier); + } + + return modifiers; +} + +static inline Qt::MouseButtons mouseButtonsForModifier(unsigned int modifier) +{ + Qt::MouseButtons buttons = {}; + if (modifier & WebInputEvent::kLeftButtonDown) + buttons |= Qt::LeftButton; + if (modifier & WebInputEvent::kRightButtonDown) + buttons |= Qt::RightButton; + if (modifier & WebInputEvent::kMiddleButtonDown) + buttons |= Qt::MiddleButton; + return buttons; +} + static WebInputEvent::Type webEventTypeForEvent(const QEvent* event) { switch (event->type()) { @@ -1471,6 +1516,14 @@ static void setBlinkWheelEventDelta(blink::WebMouseWheelEvent &webEvent) webEvent.delta_y = webEvent.wheel_ticks_y * wheelScrollLines * cDefaultQtScrollStep; } +static QPoint getWheelEventDelta(const blink::WebGestureEvent &webEvent) +{ + static const float cDefaultQtScrollStep = 20.f; + static const int wheelScrollLines = QGuiApplication::styleHints()->wheelScrollLines(); + return QPoint(webEvent.data.scroll_update.delta_x * QWheelEvent::DefaultDeltasPerStep / (wheelScrollLines * cDefaultQtScrollStep), + webEvent.data.scroll_update.delta_y * QWheelEvent::DefaultDeltasPerStep / (wheelScrollLines * cDefaultQtScrollStep)); +} + blink::WebMouseWheelEvent::Phase toBlinkPhase(QWheelEvent *ev) { switch (ev->phase()) { @@ -1552,6 +1605,26 @@ bool WebEventFactory::coalesceWebWheelEvent(blink::WebMouseWheelEvent &webEvent, return true; } +static QPointF toQt(blink::WebFloatPoint p) +{ + return QPointF(p.x, p.y); +} + +void WebEventFactory::sendUnhandledWheelEvent(const blink::WebGestureEvent &event, + RenderWidgetHostViewQtDelegate *delegate) +{ + Q_ASSERT(event.GetType() == blink::WebInputEvent::kGestureScrollUpdate); + + QWheelEvent ev(toQt(event.PositionInWidget()), + toQt(event.PositionInScreen()), + QPoint(event.data.scroll_update.delta_x, event.data.scroll_update.delta_y), + getWheelEventDelta(event), + mouseButtonsForModifier(event.GetModifiers()), + keyboardModifiersForModifier(event.GetModifiers()), + Qt::NoScrollPhase, false); + delegate->unhandledWheelEvent(&ev); +} + content::NativeWebKeyboardEvent WebEventFactory::toWebKeyboardEvent(QKeyEvent *ev) { content::NativeWebKeyboardEvent webKitEvent(reinterpret_cast<gfx::NativeEvent>(ev)); @@ -1686,3 +1759,5 @@ bool WebEventFactory::getEditCommand(QKeyEvent *event, std::string *editCommand) return false; } + +} // namespace QtWebEngineCore diff --git a/src/core/web_event_factory.h b/src/core/web_event_factory.h index 526202cfb..390502a9d 100644 --- a/src/core/web_event_factory.h +++ b/src/core/web_event_factory.h @@ -63,6 +63,10 @@ class QNativeGestureEvent; #endif QT_END_NAMESPACE +namespace QtWebEngineCore { + +class RenderWidgetHostViewQtDelegate; + class WebEventFactory { public: @@ -77,9 +81,11 @@ public: #endif static blink::WebMouseWheelEvent toWebWheelEvent(QWheelEvent *); static bool coalesceWebWheelEvent(blink::WebMouseWheelEvent &, QWheelEvent *); + static void sendUnhandledWheelEvent(const blink::WebGestureEvent &, RenderWidgetHostViewQtDelegate *); static content::NativeWebKeyboardEvent toWebKeyboardEvent(QKeyEvent*); static bool getEditCommand(QKeyEvent *event, std::string *editCommand); }; +} // namespace QtWebEngineCore #endif // WEB_EVENT_FACTORY_H diff --git a/src/pdf/api/qpdfdocument.h b/src/pdf/api/qpdfdocument.h index f80a7832b..54ca687fa 100644 --- a/src/pdf/api/qpdfdocument.h +++ b/src/pdf/api/qpdfdocument.h @@ -114,6 +114,7 @@ public: QImage render(int page, QSize imageSize, QPdfDocumentRenderOptions options = QPdfDocumentRenderOptions()); Q_INVOKABLE QPdfSelection getSelection(int page, QPointF start, QPointF end); + Q_INVOKABLE QPdfSelection getSelectionAtIndex(int page, int startIndex, int maxLength); Q_INVOKABLE QPdfSelection getAllText(int page); Q_SIGNALS: @@ -127,6 +128,7 @@ private: friend class QPdfLinkModelPrivate; friend class QPdfSearchModel; friend class QPdfSearchModelPrivate; + friend class QQuickPdfSelection; Q_PRIVATE_SLOT(d, void _q_tryLoadingWithSizeFromContentHeader()) Q_PRIVATE_SLOT(d, void _q_copyFromSequentialSourceDevice()) diff --git a/src/pdf/api/qpdfdocument_p.h b/src/pdf/api/qpdfdocument_p.h index b69b6f19e..9a737766b 100644 --- a/src/pdf/api/qpdfdocument_p.h +++ b/src/pdf/api/qpdfdocument_p.h @@ -66,7 +66,7 @@ public: QPdfMutexLocker(); }; -class QPdfDocumentPrivate: public FPDF_FILEACCESS, public FX_FILEAVAIL, public FX_DOWNLOADHINTS +class Q_PDF_PRIVATE_EXPORT QPdfDocumentPrivate: public FPDF_FILEACCESS, public FX_FILEAVAIL, public FX_DOWNLOADHINTS { public: QPdfDocumentPrivate(); @@ -106,6 +106,15 @@ public: static void fpdf_AddSegment(struct _FX_DOWNLOADHINTS* pThis, size_t offset, size_t size); void updateLastError(); QString getText(FPDF_TEXTPAGE textPage, int startIndex, int count); + QPointF getCharPosition(FPDF_TEXTPAGE textPage, double pageHeight, int charIndex); + QRectF getCharBox(FPDF_TEXTPAGE textPage, double pageHeight, int charIndex); + + struct TextPosition { + QPointF position; + qreal height = 0; + int charIndex = -1; + }; + TextPosition hitTest(int page, QPointF position); }; QT_END_NAMESPACE diff --git a/src/pdf/api/qpdflinkmodel_p_p.h b/src/pdf/api/qpdflinkmodel_p_p.h index 3e44f1651..0454d6755 100644 --- a/src/pdf/api/qpdflinkmodel_p_p.h +++ b/src/pdf/api/qpdflinkmodel_p_p.h @@ -74,7 +74,7 @@ public: // destination inside PDF int page = -1; // -1 means look at the url instead QPointF location; - qreal zoom = 1; + qreal zoom = 0; // 0 means no specified zoom: don't change when clicking // web destination QUrl url; diff --git a/src/pdf/api/qpdfselection.h b/src/pdf/api/qpdfselection.h index 5a6a1cddc..9d91d46c7 100644 --- a/src/pdf/api/qpdfselection.h +++ b/src/pdf/api/qpdfselection.h @@ -53,7 +53,10 @@ class Q_PDF_EXPORT QPdfSelection Q_GADGET Q_PROPERTY(bool valid READ isValid) Q_PROPERTY(QVector<QPolygonF> bounds READ bounds) + Q_PROPERTY(QRectF boundingRectangle READ boundingRectangle) Q_PROPERTY(QString text READ text) + Q_PROPERTY(int startIndex READ startIndex) + Q_PROPERTY(int endIndex READ endIndex) public: ~QPdfSelection(); @@ -65,13 +68,16 @@ public: bool isValid() const; QVector<QPolygonF> bounds() const; QString text() const; + QRectF boundingRectangle() const; + int startIndex() const; + int endIndex() const; #if QT_CONFIG(clipboard) void copyToClipboard(QClipboard::Mode mode = QClipboard::Clipboard) const; #endif private: QPdfSelection(); - QPdfSelection(const QString &text, QVector<QPolygonF> bounds); + QPdfSelection(const QString &text, QVector<QPolygonF> bounds, QRectF boundingRect, int startIndex, int endIndex); QPdfSelection(QPdfSelectionPrivate *d); friend class QPdfDocument; friend class QQuickPdfSelection; diff --git a/src/pdf/api/qpdfselection_p.h b/src/pdf/api/qpdfselection_p.h index 37145f7f9..0577e5a31 100644 --- a/src/pdf/api/qpdfselection_p.h +++ b/src/pdf/api/qpdfselection_p.h @@ -46,12 +46,18 @@ class QPdfSelectionPrivate : public QSharedData { public: QPdfSelectionPrivate() = default; - QPdfSelectionPrivate(const QString &text, QVector<QPolygonF> bounds) + QPdfSelectionPrivate(const QString &text, QVector<QPolygonF> bounds, QRectF boundingRect, int startIndex, int endIndex) : text(text), - bounds(bounds) { } + bounds(bounds), + boundingRect(boundingRect), + startIndex(startIndex), + endIndex(endIndex) { } QString text; QVector<QPolygonF> bounds; + QRectF boundingRect; + int startIndex; + int endIndex; }; QT_END_NAMESPACE diff --git a/src/pdf/api/qtpdfglobal.h b/src/pdf/api/qtpdfglobal.h index 223ec4bcb..8b4b0c206 100644 --- a/src/pdf/api/qtpdfglobal.h +++ b/src/pdf/api/qtpdfglobal.h @@ -53,6 +53,8 @@ QT_BEGIN_NAMESPACE # endif #endif +#define Q_PDF_PRIVATE_EXPORT Q_PDF_EXPORT + QT_END_NAMESPACE #endif // QTPDFGLOBAL_H diff --git a/src/pdf/qpdfdocument.cpp b/src/pdf/qpdfdocument.cpp index 89b27da8b..e4ec363ce 100644 --- a/src/pdf/qpdfdocument.cpp +++ b/src/pdf/qpdfdocument.cpp @@ -54,7 +54,7 @@ QT_BEGIN_NAMESPACE // The library is not thread-safe at all, it has a lot of global variables. Q_GLOBAL_STATIC_WITH_ARGS(QMutex, pdfMutex, (QMutex::Recursive)); static int libraryRefCount; -static const double CharacterHitTolerance = 6.0; +static const double CharacterHitTolerance = 16.0; Q_LOGGING_CATEGORY(qLcDoc, "qt.pdf.document") QPdfMutexLocker::QPdfMutexLocker() @@ -402,6 +402,50 @@ QString QPdfDocumentPrivate::getText(FPDF_TEXTPAGE textPage, int startIndex, int return QString::fromUtf16(buf.constData(), len - 1); } +QPointF QPdfDocumentPrivate::getCharPosition(FPDF_TEXTPAGE textPage, double pageHeight, int charIndex) +{ + double x, y; + int count = FPDFText_CountChars(textPage); + bool ok = FPDFText_GetCharOrigin(textPage, qMin(count - 1, charIndex), &x, &y); + if (!ok) + return QPointF(); + return QPointF(x, pageHeight - y); +} + +QRectF QPdfDocumentPrivate::getCharBox(FPDF_TEXTPAGE textPage, double pageHeight, int charIndex) +{ + double l, t, r, b; + bool ok = FPDFText_GetCharBox(textPage, charIndex, &l, &r, &b, &t); + if (!ok) + return QRectF(); + return QRectF(l, pageHeight - t, r - l, t - b); +} + +QPdfDocumentPrivate::TextPosition QPdfDocumentPrivate::hitTest(int page, QPointF position) +{ + const QPdfMutexLocker lock; + FPDF_PAGE pdfPage = FPDF_LoadPage(doc, page); + double pageHeight = FPDF_GetPageHeight(pdfPage); + FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage); + int hitIndex = FPDFText_GetCharIndexAtPos(textPage, position.x(), pageHeight - position.y(), + CharacterHitTolerance, CharacterHitTolerance); + if (hitIndex >= 0) { + QPointF charPos = getCharPosition(textPage, pageHeight, hitIndex); + if (!charPos.isNull()) { + QRectF charBox = getCharBox(textPage, pageHeight, hitIndex); + // If the given position is past the end of the line, i.e. if the right edge of the found character's + // bounding box is closer to it than the left edge is, we say that we "hit" the next character index after + if (qAbs(charBox.right() - position.x()) < qAbs(charPos.x() - position.x())) { + charPos.setX(charBox.right()); + ++hitIndex; + } + qCDebug(qLcDoc) << "on page" << page << "@" << position << "got char position" << charPos << "index" << hitIndex; + return { charPos, charBox.height(), hitIndex }; + } + } + return {}; +} + /*! \class QPdfDocument \since 5.10 @@ -748,29 +792,80 @@ QPdfSelection QPdfDocument::getSelection(int page, QPointF start, QPointF end) if (startIndex >= 0 && endIndex != startIndex) { if (startIndex > endIndex) qSwap(startIndex, endIndex); - int count = endIndex - startIndex + 1; + + // If the given end position is past the end of the line, i.e. if the right edge of the last character's + // bounding box is closer to it than the left edge is, then extend the char range by one + QRectF endCharBox = d->getCharBox(textPage, pageHeight, endIndex); + if (qAbs(endCharBox.right() - end.x()) < qAbs(endCharBox.x() - end.x())) + ++endIndex; + + int count = endIndex - startIndex; QString text = d->getText(textPage, startIndex, count); QVector<QPolygonF> bounds; + QRectF hull; int rectCount = FPDFText_CountRects(textPage, startIndex, endIndex - startIndex); for (int i = 0; i < rectCount; ++i) { double l, r, b, t; FPDFText_GetRect(textPage, i, &l, &t, &r, &b); - QPolygonF poly; - poly << QPointF(l, pageHeight - t); - poly << QPointF(r, pageHeight - t); - poly << QPointF(r, pageHeight - b); - poly << QPointF(l, pageHeight - b); - poly << QPointF(l, pageHeight - t); - bounds << poly; + QRectF rect(l, pageHeight - t, r - l, t - b); + if (hull.isNull()) + hull = rect; + else + hull = hull.united(rect); + bounds << QPolygonF(rect); } qCDebug(qLcDoc) << page << start << "->" << end << "found" << startIndex << "->" << endIndex << text; - return QPdfSelection(text, bounds); + return QPdfSelection(text, bounds, hull, startIndex, endIndex); } qCDebug(qLcDoc) << page << start << "->" << end << "nothing found"; return QPdfSelection(); } +/*! + Returns information about the text on the given \a page that can be found + beginning at the given \a startIndex with at most \l maxLength characters. +*/ +QPdfSelection QPdfDocument::getSelectionAtIndex(int page, int startIndex, int maxLength) +{ + + if (page < 0 || startIndex < 0 || maxLength < 0) + return {}; + const QPdfMutexLocker lock; + FPDF_PAGE pdfPage = FPDF_LoadPage(d->doc, page); + double pageHeight = FPDF_GetPageHeight(pdfPage); + FPDF_TEXTPAGE textPage = FPDFText_LoadPage(pdfPage); + int pageCount = FPDFText_CountChars(textPage); + if (startIndex >= pageCount) + return QPdfSelection(); + QVector<QPolygonF> bounds; + QRectF hull; + int rectCount = 0; + QString text; + if (maxLength > 0) { + text = d->getText(textPage, startIndex, maxLength); + rectCount = FPDFText_CountRects(textPage, startIndex, text.length()); + for (int i = 0; i < rectCount; ++i) { + double l, r, b, t; + FPDFText_GetRect(textPage, i, &l, &t, &r, &b); + QRectF rect(l, pageHeight - t, r - l, t - b); + if (hull.isNull()) + hull = rect; + else + hull = hull.united(rect); + bounds << QPolygonF(rect); + } + } + if (bounds.isEmpty()) + hull = QRectF(d->getCharPosition(textPage, pageHeight, startIndex), QSizeF()); + qCDebug(qLcDoc) << "on page" << page << "at index" << startIndex << "maxLength" << maxLength + << "got" << text.length() << "chars," << rectCount << "rects within" << hull; + return QPdfSelection(text, bounds, hull, startIndex, startIndex + text.length()); +} + +/*! + Returns all the text and its bounds on the given \a page. +*/ QPdfSelection QPdfDocument::getAllText(int page) { const QPdfMutexLocker lock; @@ -782,20 +877,20 @@ QPdfSelection QPdfDocument::getAllText(int page) return QPdfSelection(); QString text = d->getText(textPage, 0, count); QVector<QPolygonF> bounds; + QRectF hull; int rectCount = FPDFText_CountRects(textPage, 0, count); for (int i = 0; i < rectCount; ++i) { double l, r, b, t; FPDFText_GetRect(textPage, i, &l, &t, &r, &b); - QPolygonF poly; - poly << QPointF(l, pageHeight - t); - poly << QPointF(r, pageHeight - t); - poly << QPointF(r, pageHeight - b); - poly << QPointF(l, pageHeight - b); - poly << QPointF(l, pageHeight - t); - bounds << poly; + QRectF rect(l, pageHeight - t, r - l, t - b); + if (hull.isNull()) + hull = rect; + else + hull = hull.united(rect); + bounds << QPolygonF(rect); } - qCDebug(qLcDoc) << "on page" << page << "got" << count << "chars" << rectCount << "rects"; - return QPdfSelection(text, bounds); + qCDebug(qLcDoc) << "on page" << page << "got" << count << "chars," << rectCount << "rects within" << hull; + return QPdfSelection(text, bounds, hull, 0, count); } QT_END_NAMESPACE diff --git a/src/pdf/qpdfselection.cpp b/src/pdf/qpdfselection.cpp index e334f0fb6..5f0ee3b20 100644 --- a/src/pdf/qpdfselection.cpp +++ b/src/pdf/qpdfselection.cpp @@ -67,8 +67,8 @@ QPdfSelection::QPdfSelection() \a text string, and which take up space on the page within the polygon regions given in \a bounds. */ -QPdfSelection::QPdfSelection(const QString &text, QVector<QPolygonF> bounds) - : d(new QPdfSelectionPrivate(text, bounds)) +QPdfSelection::QPdfSelection(const QString &text, QVector<QPolygonF> bounds, QRectF boundingRect, int startIndex, int endIndex) + : d(new QPdfSelectionPrivate(text, bounds, boundingRect, startIndex, endIndex)) { } @@ -134,6 +134,36 @@ QString QPdfSelection::text() const return d->text; } +/*! + \property rect QPdfSelection::boundingRectangle + + This property holds the overall bounding rectangle (convex hull) around \l bounds. +*/ +QRectF QPdfSelection::boundingRectangle() const +{ + return d->boundingRect; +} + +/*! + \property int QPdfSelection::startIndex + + This property holds the index at the beginning of \l text within the full text on the page. +*/ +int QPdfSelection::startIndex() const +{ + return d->startIndex; +} + +/*! + \property int QPdfSelection::endIndex + + This property holds the index at the end of \l text within the full text on the page. +*/ +int QPdfSelection::endIndex() const +{ + return d->endIndex; +} + #if QT_CONFIG(clipboard) /*! Copies \l text to the \l {QGuiApplication::clipboard()}{system clipboard}. diff --git a/src/pdf/quick/plugin.cpp b/src/pdf/quick/plugin.cpp index 670fe0bf9..b082fcb4a 100644 --- a/src/pdf/quick/plugin.cpp +++ b/src/pdf/quick/plugin.cpp @@ -43,6 +43,7 @@ #include "qquickpdfnavigationstack_p.h" #include "qquickpdfsearchmodel_p.h" #include "qquickpdfselection_p.h" +#include "qquicktableviewextra_p.h" QT_BEGIN_NAMESPACE @@ -89,6 +90,7 @@ public: qmlRegisterType<QQuickPdfNavigationStack>(uri, 5, 15, "PdfNavigationStack"); qmlRegisterType<QQuickPdfSearchModel>(uri, 5, 15, "PdfSearchModel"); qmlRegisterType<QQuickPdfSelection>(uri, 5, 15, "PdfSelection"); + qmlRegisterType<QQuickTableViewExtra>(uri, 5, 15, "TableViewExtra"); qmlRegisterType(QUrl("qrc:/qt-project.org/qtpdf/qml/PdfPageView.qml"), uri, 5, 15, "PdfPageView"); qmlRegisterType(QUrl("qrc:/qt-project.org/qtpdf/qml/PdfMultiPageView.qml"), uri, 5, 15, "PdfMultiPageView"); diff --git a/src/pdf/quick/qml/PdfMultiPageView.qml b/src/pdf/quick/qml/PdfMultiPageView.qml index 70bb5454f..71485c214 100644 --- a/src/pdf/quick/qml/PdfMultiPageView.qml +++ b/src/pdf/quick/qml/PdfMultiPageView.qml @@ -48,15 +48,15 @@ Item { property string selectedText function selectAll() { - var currentItem = tableView.itemAtPos(0, tableView.contentY + root.height / 2) - if (currentItem !== null) + var currentItem = tableHelper.itemAtCell(tableHelper.cellAtPos(root.width / 2, root.height / 2)) + if (currentItem) currentItem.selection.selectAll() } function copySelectionToClipboard() { - var currentItem = tableView.itemAtPos(0, tableView.contentY + root.height / 2) + var currentItem = tableHelper.itemAtCell(tableHelper.cellAtPos(root.width / 2, root.height / 2)) if (debug) console.log("currentItem", currentItem, "sel", currentItem.selection.text) - if (currentItem !== null) + if (currentItem) currentItem.selection.copyToClipboard() } @@ -69,14 +69,19 @@ Item { function goToPage(page) { if (page === navigationStack.currentPage) return - goToLocation(page, Qt.point(0, 0), 0) + goToLocation(page, Qt.point(-1, -1), 0) } function goToLocation(page, location, zoom) { - if (zoom > 0) + if (zoom > 0) { + navigationStack.jumping = true // don't call navigationStack.update() because we will push() instead root.renderScale = zoom - navigationStack.push(page, location, zoom) - searchModel.currentPage = page + tableView.forceLayout() // but do ensure that the table layout is correct before we try to jump + navigationStack.jumping = false + } + navigationStack.push(page, location, zoom) // actually jump } + property vector2d jumpLocationMargin: Qt.vector2d(10, 10) // px from top-left corner + property int currentPageRenderingStatus: Image.Null // page scaling property real renderScale: 1 @@ -115,29 +120,27 @@ Item { id: tableView anchors.fill: parent anchors.leftMargin: 2 - model: root.document === undefined ? 0 : root.document.pageCount + model: modelInUse && root.document !== undefined ? root.document.pageCount : 0 + // workaround to make TableView do scheduleRebuildTable(RebuildOption::All) in cases when forceLayout() doesn't + property bool modelInUse: true + function rebuild() { + modelInUse = false + modelInUse = true + } + // end workaround rowSpacing: 6 property real rotationNorm: Math.round((360 + (root.pageRotation % 360)) % 360) property bool rot90: rotationNorm == 90 || rotationNorm == 270 onRot90Changed: forceLayout() property size firstPagePointSize: document === undefined ? Qt.size(0, 0) : document.pagePointSize(0) - contentWidth: document === undefined ? 0 : (rot90 ? document.maxPageHeight : document.maxPageWidth) * root.renderScale + vscroll.width + 2 - // workaround for missing function (see https://codereview.qt-project.org/c/qt/qtdeclarative/+/248464) - function itemAtPos(x, y, includeSpacing) { - // we don't care about x (assume col 0), and assume includeSpacing is true - var ret = null - for (var i = 0; i < contentItem.children.length; ++i) { - var child = contentItem.children[i]; - if (root.debug) - console.log(child, "@y", child.y) - if (child.y < y && (!ret || child.y > ret.y)) - ret = child - } - if (root.debug && ret !== null) - console.log("given y", y, "found", ret, "@", ret.y) - return ret // the delegate with the largest y that is less than the given y - } + property real pageHolderWidth: Math.max(root.width, document === undefined ? 0 : + (rot90 ? document.maxPageHeight : document.maxPageWidth) * root.renderScale) + contentWidth: document === undefined ? 0 : pageHolderWidth + vscroll.width + 2 rowHeightProvider: function(row) { return (rot90 ? document.pagePointSize(row).width : document.pagePointSize(row).height) * root.renderScale } + TableViewExtra { + id: tableHelper + tableView: tableView + } delegate: Rectangle { id: pageHolder color: root.debug ? "beige" : "transparent" @@ -147,11 +150,8 @@ Item { rotation: -90; text: pageHolder.width.toFixed(1) + "x" + pageHolder.height.toFixed(1) + "\n" + image.width.toFixed(1) + "x" + image.height.toFixed(1) } - implicitWidth: Math.max(root.width, (tableView.rot90 ? document.maxPageHeight : document.maxPageWidth) * root.renderScale) + implicitWidth: tableView.pageHolderWidth implicitHeight: tableView.rot90 ? image.width : image.height - onImplicitWidthChanged: tableView.forceLayout() - objectName: "page " + index - property int delegateIndex: row // expose the context property for JS outside of the delegate property alias selection: selection Rectangle { id: paper @@ -177,6 +177,10 @@ Item { paper.scale = 1 searchHighlights.update() } + onStatusChanged: { + if (index === navigationStack.currentPage) + root.currentPageRenderingStatus = status + } } Shape { anchors.fill: parent @@ -276,9 +280,17 @@ Item { target: null } TapHandler { - id: tapHandler + id: mouseClickHandler acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus } + TapHandler { + id: touchTapHandler + acceptedDevices: PointerDevice.TouchScreen + onTapped: { + selection.clear() + selection.forceActiveFocus() + } + } Repeater { model: PdfLinkModel { id: linkModel @@ -290,6 +302,7 @@ Item { y: rect.y * paper.pageScale width: rect.width * paper.pageScale height: rect.height * paper.pageScale + visible: image.status === Image.Ready ShapePath { strokeWidth: style.linkUnderscoreStrokeWidth strokeColor: style.linkUnderscoreColor @@ -320,17 +333,18 @@ Item { } } } - } - PdfSelection { - id: selection - document: root.document - page: image.currentFrame - fromPoint: Qt.point(textSelectionDrag.centroid.pressPosition.x / paper.pageScale, - textSelectionDrag.centroid.pressPosition.y / paper.pageScale) - toPoint: Qt.point(textSelectionDrag.centroid.position.x / paper.pageScale, - textSelectionDrag.centroid.position.y / paper.pageScale) - hold: !textSelectionDrag.active && !tapHandler.pressed - onTextChanged: root.selectedText = text + PdfSelection { + id: selection + anchors.fill: parent + document: root.document + page: image.currentFrame + renderScale: image.renderScale + fromPoint: textSelectionDrag.centroid.pressPosition + toPoint: textSelectionDrag.centroid.position + hold: !textSelectionDrag.active && !mouseClickHandler.pressed + onTextChanged: root.selectedText = text + focus: true + } } } ScrollBar.vertical: ScrollBar { @@ -338,42 +352,83 @@ Item { property bool moved: false onPositionChanged: moved = true onActiveChanged: { - var currentItem = tableView.itemAtPos(0, tableView.contentY + root.height / 2) - var currentPage = currentItem.delegateIndex - var currentLocation = Qt.point((tableView.contentX - currentItem.x + root.width / 2) / root.renderScale, - (tableView.contentY - currentItem.y + root.height / 2) / root.renderScale) + var cell = tableHelper.cellAtPos(root.width / 2, root.height / 2) + var currentItem = tableHelper.itemAtCell(cell) + var currentLocation = Qt.point(0, 0) + if (currentItem) { // maybe the delegate wasn't loaded yet + currentLocation = Qt.point((tableView.contentX - currentItem.x + jumpLocationMargin.x) / root.renderScale, + (tableView.contentY - currentItem.y + jumpLocationMargin.y) / root.renderScale) + } if (active) { moved = false - navigationStack.push(currentPage, currentLocation, root.renderScale) + // emitJumped false to avoid interrupting a pinch if TableView thinks it should scroll at the same time + navigationStack.push(cell.y, currentLocation, root.renderScale, false) } else if (moved) { - navigationStack.update(currentPage, currentLocation, root.renderScale) + navigationStack.update(cell.y, currentLocation, root.renderScale) } } } ScrollBar.horizontal: ScrollBar { } } onRenderScaleChanged: { - tableView.forceLayout() - var currentItem = tableView.itemAtPos(tableView.contentX + root.width / 2, tableView.contentY + root.height / 2) - if (currentItem !== undefined) - navigationStack.update(currentItem.delegateIndex, Qt.point(currentItem.x / renderScale, currentItem.y / renderScale), renderScale) + // if navigationStack.jumped changes the scale, don't turn around and update the stack again; + // and don't force layout either, because positionViewAtCell() will do that + if (navigationStack.jumping) + return + // make TableView rebuild from scratch, because otherwise it doesn't know the delegates are changing size + tableView.rebuild() + var cell = tableHelper.cellAtPos(root.width / 2, root.height / 2) + var currentItem = tableHelper.itemAtCell(cell) + if (currentItem) { + var currentLocation = Qt.point((tableView.contentX - currentItem.x + jumpLocationMargin.x) / root.renderScale, + (tableView.contentY - currentItem.y + jumpLocationMargin.y) / root.renderScale) + navigationStack.update(cell.y, currentLocation, renderScale) + } } PdfNavigationStack { id: navigationStack + property bool jumping: false + property int previousPage: 0 onJumped: { + jumping = true root.renderScale = zoom - tableView.contentX = Math.max(0, location.x - root.width / 2) * root.renderScale - tableView.contentY = tableView.originY + root.document.heightSumBeforePage(page, tableView.rowSpacing / root.renderScale) * root.renderScale - if (root.debug) { - console.log("going to page", page, - "@y", root.document.heightSumBeforePage(page, tableView.rowSpacing / root.renderScale) * root.renderScale, - "ended up @", tableView.contentY, "originY is", tableView.originY) + if (location.y < 0) { + // invalid to indicate that a specific location was not needed, + // so attempt to position the new page just as the current page is + var currentYOffset = 0 + var previousPageDelegate = tableHelper.itemAtCell(0, previousPage) + if (previousPageDelegate) + currentYOffset = tableView.contentY - previousPageDelegate.y + tableHelper.positionViewAtRow(page, Qt.AlignTop, currentYOffset) + if (root.debug) { + console.log("going from page", previousPage, "to", page, "offset", currentYOffset, + "ended up @", tableView.contentX.toFixed(1) + ", " + tableView.contentY.toFixed(1)) + } + } else { + // jump to a page and position the given location relative to the top-left corner of the viewport + var pageSize = root.document.pagePointSize(page) + pageSize.width *= root.renderScale + pageSize.height *= root.renderScale + var xOffsetLimit = Math.max(0, pageSize.width - root.width) / 2 + var offset = Qt.point(Math.max(-xOffsetLimit, Math.min(xOffsetLimit, + location.x * root.renderScale - jumpLocationMargin.x)), + Math.max(0, location.y * root.renderScale - jumpLocationMargin.y)) + tableHelper.positionViewAtCell(0, page, Qt.AlignLeft | Qt.AlignTop, offset) + if (root.debug) { + console.log("going to zoom", zoom, "loc", location, "on page", page, + "ended up @", tableView.contentX.toFixed(1) + ", " + tableView.contentY.toFixed(1)) + } } + jumping = false + previousPage = page } + onCurrentPageChanged: searchModel.currentPage = currentPage } PdfSearchModel { id: searchModel document: root.document === undefined ? null : root.document - onCurrentPageChanged: if (currentPage != navigationStack.currentPage) root.goToPage(currentPage) + // TODO maybe avoid jumping if the result is already fully visible in the viewport + onCurrentResultBoundingRectChanged: root.goToLocation(currentPage, + Qt.point(currentResultBoundingRect.x, currentResultBoundingRect.y), 0) } } diff --git a/src/pdf/quick/qml/PdfScrollablePageView.qml b/src/pdf/quick/qml/PdfScrollablePageView.qml index 6076e57df..51d9e530d 100644 --- a/src/pdf/quick/qml/PdfScrollablePageView.qml +++ b/src/pdf/quick/qml/PdfScrollablePageView.qml @@ -133,32 +133,29 @@ Flickable { navigationStack.update(navigationStack.currentPage, currentLocation, root.renderScale) } - PdfSelection { - id: selection - document: root.document - page: navigationStack.currentPage - fromPoint: Qt.point(textSelectionDrag.centroid.pressPosition.x / image.pageScale, - textSelectionDrag.centroid.pressPosition.y / image.pageScale) - toPoint: Qt.point(textSelectionDrag.centroid.position.x / image.pageScale, - textSelectionDrag.centroid.position.y / image.pageScale) - hold: !textSelectionDrag.active && !tapHandler.pressed - } - PdfSearchModel { id: searchModel document: root.document === undefined ? null : root.document - onCurrentPageChanged: root.goToPage(currentPage) + // TODO maybe avoid jumping if the result is already fully visible in the viewport + onCurrentResultBoundingRectChanged: root.goToLocation(currentPage, + Qt.point(currentResultBoundingRect.x, currentResultBoundingRect.y), 0) } PdfNavigationStack { id: navigationStack onJumped: { root.renderScale = zoom - root.contentX = Math.max(0, location.x * root.renderScale - root.width / 2) - root.contentY = Math.max(0, location.y * root.renderScale - root.height / 2) - if (root.debug) + var dx = Math.max(0, location.x * root.renderScale - root.width / 2) - root.contentX + var dy = Math.max(0, location.y * root.renderScale - root.height / 2) - root.contentY + // don't jump if location is in the viewport already, i.e. if the "error" between desired and actual contentX/Y is small + if (Math.abs(dx) > root.width / 3) + root.contentX += dx + if (Math.abs(dy) > root.height / 3) + root.contentY += dy + if (root.debug) { console.log("going to zoom", zoom, "loc", location, "on page", page, "ended up @", root.contentX + ", " + root.contentY) + } } onCurrentPageChanged: searchModel.currentPage = currentPage } @@ -246,9 +243,29 @@ Flickable { target: null } TapHandler { - id: tapHandler + id: mouseClickHandler acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus } + TapHandler { + id: touchTapHandler + acceptedDevices: PointerDevice.TouchScreen + onTapped: { + selection.clear() + selection.focus = true + } + } + } + + PdfSelection { + id: selection + anchors.fill: parent + document: root.document + page: navigationStack.currentPage + renderScale: image.pageScale + fromPoint: textSelectionDrag.centroid.pressPosition + toPoint: textSelectionDrag.centroid.position + hold: !textSelectionDrag.active && !mouseClickHandler.pressed + focus: true } PinchHandler { diff --git a/src/pdf/quick/qquickpdfdocument.cpp b/src/pdf/quick/qquickpdfdocument.cpp index 3d5f0fa10..ab5910523 100644 --- a/src/pdf/quick/qquickpdfdocument.cpp +++ b/src/pdf/quick/qquickpdfdocument.cpp @@ -38,7 +38,6 @@ #include <QQuickItem> #include <QQmlEngine> #include <QStandardPaths> -#include <private/qguiapplication_p.h> QT_BEGIN_NAMESPACE diff --git a/src/pdf/quick/qquickpdflinkmodel.cpp b/src/pdf/quick/qquickpdflinkmodel.cpp index f2ff3fd22..4f3958337 100644 --- a/src/pdf/quick/qquickpdflinkmodel.cpp +++ b/src/pdf/quick/qquickpdflinkmodel.cpp @@ -38,7 +38,6 @@ #include <QQuickItem> #include <QQmlEngine> #include <QStandardPaths> -#include <private/qguiapplication_p.h> QT_BEGIN_NAMESPACE diff --git a/src/pdf/quick/qquickpdfnavigationstack.cpp b/src/pdf/quick/qquickpdfnavigationstack.cpp index 7ba317557..044023ef6 100644 --- a/src/pdf/quick/qquickpdfnavigationstack.cpp +++ b/src/pdf/quick/qquickpdfnavigationstack.cpp @@ -90,6 +90,8 @@ void QQuickPdfNavigationStack::forward() if (forwardAvailableWas != forwardAvailable()) emit forwardAvailableChanged(); m_changing = false; + qCDebug(qLcNav) << "forward: index" << m_currentHistoryIndex << "page" << currentPage() + << "@" << currentLocation() << "zoom" << currentZoom(); } /*! @@ -120,6 +122,8 @@ void QQuickPdfNavigationStack::back() if (!forwardAvailableWas) emit forwardAvailableChanged(); m_changing = false; + qCDebug(qLcNav) << "back: index" << m_currentHistoryIndex << "page" << currentPage() + << "@" << currentLocation() << "zoom" << currentZoom(); } /*! @@ -163,13 +167,14 @@ qreal QQuickPdfNavigationStack::currentZoom() const \qmlmethod void PdfNavigationStack::push(int page, point location, qreal zoom) Adds the given destination, consisting of \a page, \a location and \a zoom, - to the history of visited locations. + to the history of visited locations. If \a emitJumped is \c false, the + \l jumped() signal will not be emitted. If forwardAvailable is \c true, calling this function represents a branch in the timeline which causes the "future" to be lost, and therefore forwardAvailable will change to \c false. */ -void QQuickPdfNavigationStack::push(int page, QPointF location, qreal zoom) +void QQuickPdfNavigationStack::push(int page, QPointF location, qreal zoom, bool emitJumped) { if (page == currentPage() && location == currentLocation() && zoom == currentZoom()) return; @@ -192,7 +197,8 @@ void QQuickPdfNavigationStack::push(int page, QPointF location, qreal zoom) emit backAvailableChanged(); if (forwardAvailableWas) emit forwardAvailableChanged(); - emit jumped(page, location, zoom); + if (emitJumped) + emit jumped(page, location, zoom); qCDebug(qLcNav) << "push: index" << m_currentHistoryIndex << "page" << page << "@" << location << "zoom" << zoom << "-> history" << [this]() { diff --git a/src/pdf/quick/qquickpdfnavigationstack_p.h b/src/pdf/quick/qquickpdfnavigationstack_p.h index 8d7102fb1..0d88d62fd 100644 --- a/src/pdf/quick/qquickpdfnavigationstack_p.h +++ b/src/pdf/quick/qquickpdfnavigationstack_p.h @@ -67,7 +67,7 @@ class QQuickPdfNavigationStack : public QObject public: explicit QQuickPdfNavigationStack(QObject *parent = nullptr); - Q_INVOKABLE void push(int page, QPointF location, qreal zoom); + Q_INVOKABLE void push(int page, QPointF location, qreal zoom, bool emitJumped = true); Q_INVOKABLE void update(int page, QPointF location, qreal zoom); Q_INVOKABLE void forward(); Q_INVOKABLE void back(); diff --git a/src/pdf/quick/qquickpdfsearchmodel.cpp b/src/pdf/quick/qquickpdfsearchmodel.cpp index a4b457841..1f62fbad0 100644 --- a/src/pdf/quick/qquickpdfsearchmodel.cpp +++ b/src/pdf/quick/qquickpdfsearchmodel.cpp @@ -116,6 +116,29 @@ QVector<QPolygonF> QQuickPdfSearchModel::currentResultBoundingPolygons() const return ret; } +/*! + \qmlproperty point PdfSearchModel::currentResultBoundingRect + + The bounding box containing all \l currentResultBoundingPolygons. + + When this property changes, a scrollable view should automatically scroll + itself in such a way as to ensure that this region is visible; for example, + it could try to position the upper-left corner near the upper-left of its + own viewport, subject to the constraints of the scrollable area. +*/ +QRectF QQuickPdfSearchModel::currentResultBoundingRect() const +{ + QRectF ret; + const auto &results = const_cast<QQuickPdfSearchModel *>(this)->resultsOnPage(m_currentPage); + if (m_currentResult < 0 || m_currentResult >= results.count()) + return ret; + auto rects = results[m_currentResult].rectangles(); + ret = rects.takeFirst(); + for (auto rect : rects) + ret = ret.united(rect); + return ret; +} + void QQuickPdfSearchModel::onResultsChanged() { emit currentPageBoundingPolygonsChanged(); @@ -266,6 +289,7 @@ void QQuickPdfSearchModel::setCurrentResult(int currentResult) m_currentResult = currentResult; emit currentResultChanged(); emit currentResultBoundingPolygonsChanged(); + emit currentResultBoundingRectChanged(); } /*! diff --git a/src/pdf/quick/qquickpdfsearchmodel_p.h b/src/pdf/quick/qquickpdfsearchmodel_p.h index 3e05f80e3..66fc583d9 100644 --- a/src/pdf/quick/qquickpdfsearchmodel_p.h +++ b/src/pdf/quick/qquickpdfsearchmodel_p.h @@ -64,6 +64,7 @@ class QQuickPdfSearchModel : public QPdfSearchModel Q_PROPERTY(int currentResult READ currentResult WRITE setCurrentResult NOTIFY currentResultChanged) Q_PROPERTY(QVector<QPolygonF> currentPageBoundingPolygons READ currentPageBoundingPolygons NOTIFY currentPageBoundingPolygonsChanged) Q_PROPERTY(QVector<QPolygonF> currentResultBoundingPolygons READ currentResultBoundingPolygons NOTIFY currentResultBoundingPolygonsChanged) + Q_PROPERTY(QRectF currentResultBoundingRect READ currentResultBoundingRect NOTIFY currentResultBoundingRectChanged) public: explicit QQuickPdfSearchModel(QObject *parent = nullptr); @@ -81,6 +82,7 @@ public: QVector<QPolygonF> currentPageBoundingPolygons() const; QVector<QPolygonF> currentResultBoundingPolygons() const; + QRectF currentResultBoundingRect() const; signals: void documentChanged(); @@ -88,6 +90,7 @@ signals: void currentResultChanged(); void currentPageBoundingPolygonsChanged(); void currentResultBoundingPolygonsChanged(); + void currentResultBoundingRectChanged(); private: void updateResults(); diff --git a/src/pdf/quick/qquickpdfselection.cpp b/src/pdf/quick/qquickpdfselection.cpp index 5371e85e5..23fbb80b9 100644 --- a/src/pdf/quick/qquickpdfselection.cpp +++ b/src/pdf/quick/qquickpdfselection.cpp @@ -37,13 +37,20 @@ #include "qquickpdfselection_p.h" #include "qquickpdfdocument_p.h" #include <QClipboard> +#include <QGuiApplication> +#include <QLoggingCategory> #include <QQuickItem> #include <QQmlEngine> +#include <QRegularExpression> #include <QStandardPaths> -#include <private/qguiapplication_p.h> +#include <QtPdf/private/qpdfdocument_p.h> + +Q_LOGGING_CATEGORY(qLcIm, "qt.pdf.im") QT_BEGIN_NAMESPACE +static const QRegularExpression WordDelimiter("\\s"); + /*! \qmltype PdfSelection \instantiates QQuickPdfSelection @@ -54,14 +61,29 @@ QT_BEGIN_NAMESPACE PdfSelection provides the text string and its geometry within a bounding box from one point to another. + + To modify the selection using the mouse, bind \l fromPoint and \l toPoint + to the suitable properties of an input handler so that they will be set to + the positions where the drag gesture begins and ends, respectively; and + bind the \l hold property so that it will be set to \c true during the drag + gesture and \c false when the gesture ends. + + PdfSelection also directly handles Input Method queries so that text + selection handles can be used on platforms such as iOS. For this purpose, + it must have keyboard focus. */ /*! Constructs a SearchModel. */ -QQuickPdfSelection::QQuickPdfSelection(QObject *parent) - : QObject(parent) +QQuickPdfSelection::QQuickPdfSelection(QQuickItem *parent) + : QQuickItem(parent) { +#if QT_CONFIG(im) + setFlags(ItemIsFocusScope | ItemAcceptsInputMethod); + // workaround to get Copy instead of Paste on the popover menu (QTBUG-83811) + setProperty("qt_im_readonly", QVariant(true)); +#endif } QQuickPdfDocument *QQuickPdfSelection::document() const @@ -124,6 +146,24 @@ QVector<QPolygonF> QQuickPdfSelection::geometry() const return m_geometry; } +void QQuickPdfSelection::clear() +{ + m_hitPoint = QPointF(); + m_fromPoint = QPointF(); + m_toPoint = QPointF(); + m_heightAtAnchor = 0; + m_heightAtCursor = 0; + m_fromCharIndex = -1; + m_toCharIndex = -1; + m_text.clear(); + m_geometry.clear(); + emit fromPointChanged(); + emit toPointChanged(); + emit textChanged(); + emit selectedAreaChanged(); + QGuiApplication::inputMethod()->update(Qt::ImQueryInput); +} + void QQuickPdfSelection::selectAll() { QPdfSelection sel = m_document->m_doc.getAllText(m_page); @@ -136,10 +176,172 @@ void QQuickPdfSelection::selectAll() if (sel.bounds() != m_geometry) { m_geometry = sel.bounds(); - emit geometryChanged(); + emit selectedAreaChanged(); + } +#if QT_CONFIG(im) + m_fromCharIndex = sel.startIndex(); + m_toCharIndex = sel.endIndex(); + if (sel.bounds().isEmpty()) { + m_fromPoint = QPointF(); + m_toPoint = QPointF(); + } else { + m_fromPoint = sel.bounds().first().boundingRect().topLeft() * m_renderScale; + m_toPoint = sel.bounds().last().boundingRect().bottomRight() * m_renderScale - QPointF(0, m_heightAtCursor); + } + + QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle | Qt::ImAnchorRectangle); +#endif +} + +#if QT_CONFIG(im) +void QQuickPdfSelection::keyReleaseEvent(QKeyEvent *ev) +{ + qCDebug(qLcIm) << "release" << ev; + const auto &allText = pageText(); + if (ev == QKeySequence::MoveToPreviousWord) { + // iOS sends MoveToPreviousWord first to get to the beginning of the word, + // and then SelectNextWord to select the whole word. + int i = allText.lastIndexOf(WordDelimiter, m_fromCharIndex - allText.length()); + if (i < 0) + i = 0; + else + i += 1; // don't select the space before the word + auto sel = m_document->m_doc.getSelectionAtIndex(m_page, i, m_text.length() + m_fromCharIndex - i); + update(sel); + QGuiApplication::inputMethod()->update(Qt::ImAnchorRectangle); + } else if (ev == QKeySequence::SelectNextWord) { + int i = allText.indexOf(WordDelimiter, m_toCharIndex); + if (i < 0) + i = allText.length(); // go to the end of m_textAfter + auto sel = m_document->m_doc.getSelectionAtIndex(m_page, m_fromCharIndex, m_text.length() + i - m_toCharIndex); + update(sel); + QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle); + } else if (ev == QKeySequence::Copy) { + copyToClipboard(); + } +} + +void QQuickPdfSelection::inputMethodEvent(QInputMethodEvent *event) +{ + for (auto attr : event->attributes()) { + switch (attr.type) { + case QInputMethodEvent::Cursor: + qCDebug(qLcIm) << "QInputMethodEvent::Cursor: moved to" << attr.start << "len" << attr.length; + break; + case QInputMethodEvent::Selection: { + auto sel = m_document->m_doc.getSelectionAtIndex(m_page, attr.start, attr.length); + update(sel); + qCDebug(qLcIm) << "QInputMethodEvent::Selection: from" << attr.start << "len" << attr.length + << "result:" << m_fromCharIndex << "->" << m_toCharIndex << sel.boundingRectangle(); + // the iOS plugin decided that it wanted to change the selection, but still has to be told to move the handles (!?) + QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle | Qt::ImAnchorRectangle); + break; + } + case QInputMethodEvent::Language: + case QInputMethodEvent::Ruby: + case QInputMethodEvent::TextFormat: + break; + } } } +QVariant QQuickPdfSelection::inputMethodQuery(Qt::InputMethodQuery query, const QVariant &argument) const +{ + if (!argument.isNull()) { + qCDebug(qLcIm) << "IM query" << query << "with arg" << argument; + if (query == Qt::ImCursorPosition) { + // If it didn't move since last time, return the same result. + if (m_hitPoint == argument.toPointF()) + return inputMethodQuery(query); + m_hitPoint = argument.toPointF(); + auto tp = m_document->m_doc.d->hitTest(m_page, m_hitPoint / m_renderScale); + qCDebug(qLcIm) << "ImCursorPosition hit testing in px" << m_hitPoint << "pt" << (m_hitPoint / m_renderScale) + << "got char index" << tp.charIndex << "@" << tp.position << "pt," << tp.position * m_renderScale << "px"; + if (tp.charIndex >= 0) { + m_toCharIndex = tp.charIndex; + m_toPoint = tp.position * m_renderScale - QPointF(0, m_heightAtCursor); + m_heightAtCursor = tp.height * m_renderScale; + if (qFuzzyIsNull(m_heightAtAnchor)) + m_heightAtAnchor = m_heightAtCursor; + } + } + } + return inputMethodQuery(query); +} + +QVariant QQuickPdfSelection::inputMethodQuery(Qt::InputMethodQuery query) const +{ + QVariant ret; + switch (query) { + case Qt::ImEnabled: + ret = true; + break; + case Qt::ImHints: + ret = QVariant(Qt::ImhMultiLine | Qt::ImhNoPredictiveText); + break; + case Qt::ImInputItemClipRectangle: + ret = boundingRect(); + break; + case Qt::ImAnchorPosition: + ret = m_fromCharIndex; + break; + case Qt::ImAbsolutePosition: + ret = m_toCharIndex; + break; + case Qt::ImCursorPosition: + ret = m_toCharIndex; + break; + case Qt::ImAnchorRectangle: + ret = QRectF(m_fromPoint, QSizeF(1, m_heightAtAnchor)); + break; + case Qt::ImCursorRectangle: + ret = QRectF(m_toPoint, QSizeF(1, m_heightAtCursor)); + break; + case Qt::ImSurroundingText: + ret = QVariant(pageText()); + break; + case Qt::ImTextBeforeCursor: + ret = QVariant(pageText().mid(0, m_toCharIndex)); + break; + case Qt::ImTextAfterCursor: + ret = QVariant(pageText().mid(m_toCharIndex)); + break; + case Qt::ImCurrentSelection: + ret = QVariant(m_text); + break; + case Qt::ImEnterKeyType: + break; + case Qt::ImFont: { + QFont font = QGuiApplication::font(); + font.setPointSizeF(m_heightAtCursor); + ret = font; + break; + } + case Qt::ImMaximumTextLength: + break; + case Qt::ImPreferredLanguage: + break; + case Qt::ImPlatformData: + break; + case Qt::ImQueryInput: + case Qt::ImQueryAll: + qWarning() << "unexpected composite query"; + break; + } + qCDebug(qLcIm) << "IM query" << query << "returns" << ret; + return ret; +} +#endif // QT_CONFIG(im) + +const QString &QQuickPdfSelection::pageText() const +{ + if (m_pageTextDirty) { + m_pageText = m_document->m_doc.getAllText(m_page).text(); + m_pageTextDirty = false; + } + return m_pageText; +} + void QQuickPdfSelection::resetPoints() { bool wasHolding = m_hold; @@ -167,18 +369,42 @@ void QQuickPdfSelection::setPage(int page) return; m_page = page; + m_pageTextDirty = true; emit pageChanged(); resetPoints(); } /*! + \qmlproperty real PdfSelection::renderScale + \brief The ratio from points to pixels at which the page is rendered. + + This is used to scale \l fromPoint and \l toPoint to find ranges of + selected characters in the document, because positions within the document + are always given in points. +*/ +qreal QQuickPdfSelection::renderScale() const +{ + return m_renderScale; +} + +void QQuickPdfSelection::setRenderScale(qreal scale) +{ + if (qFuzzyCompare(scale, m_renderScale)) + return; + + m_renderScale = scale; + emit renderScaleChanged(); + updateResults(); +} + +/*! \qmlproperty point PdfSelection::fromPoint - The beginning location, in \l {https://en.wikipedia.org/wiki/Point_(typography)}{points} - from the upper-left corner of the page, from which to find selected text. - This can be bound to a scaled version of the \c centroid.pressPosition - of a \l DragHandler to begin selecting text from the position where the user - presses the mouse button and begins dragging, for example. + The beginning location, in pixels from the upper-left corner of the page, + from which to find selected text. This can be bound to the + \c centroid.pressPosition of a \l DragHandler to begin selecting text from + the position where the user presses the mouse button and begins dragging, + for example. */ QPointF QQuickPdfSelection::fromPoint() const { @@ -198,11 +424,10 @@ void QQuickPdfSelection::setFromPoint(QPointF fromPoint) /*! \qmlproperty point PdfSelection::toPoint - The ending location, in \l {https://en.wikipedia.org/wiki/Point_(typography)}{points} - from the upper-left corner of the page, from which to find selected text. - This can be bound to a scaled version of the \c centroid.position - of a \l DragHandler to end selection of text at the position where the user - is currently dragging the mouse, for example. + The ending location, in pixels from the upper-left corner of the page, + from which to find selected text. This can be bound to the + \c centroid.position of a \l DragHandler to end selection of text at the + position where the user is currently dragging the mouse, for example. */ QPointF QQuickPdfSelection::toPoint() const { @@ -267,7 +492,13 @@ void QQuickPdfSelection::updateResults() { if (!m_document) return; - QPdfSelection sel = m_document->document().getSelection(m_page, m_fromPoint, m_toPoint); + QPdfSelection sel = m_document->document().getSelection(m_page, + m_fromPoint / m_renderScale, m_toPoint / m_renderScale); + update(sel, true); +} + +void QQuickPdfSelection::update(const QPdfSelection &sel, bool textAndGeometryOnly) +{ if (sel.text() != m_text) { m_text = sel.text(); if (QGuiApplication::clipboard()->supportsSelection()) @@ -277,7 +508,33 @@ void QQuickPdfSelection::updateResults() if (sel.bounds() != m_geometry) { m_geometry = sel.bounds(); - emit geometryChanged(); + emit selectedAreaChanged(); + } + + if (textAndGeometryOnly) + return; + + m_fromCharIndex = sel.startIndex(); + m_toCharIndex = sel.endIndex(); + if (sel.bounds().isEmpty()) { + m_fromPoint = sel.boundingRectangle().topLeft() * m_renderScale; + m_toPoint = m_fromPoint; + } else { + Qt::InputMethodQueries toUpdate = {}; + QRectF firstLineBounds = sel.bounds().first().boundingRect(); + m_fromPoint = firstLineBounds.topLeft() * m_renderScale; + if (!qFuzzyCompare(m_heightAtAnchor, firstLineBounds.height())) { + m_heightAtAnchor = firstLineBounds.height() * m_renderScale; + toUpdate.setFlag(Qt::ImAnchorRectangle); + } + QRectF lastLineBounds = sel.bounds().last().boundingRect(); + if (!qFuzzyCompare(m_heightAtCursor, lastLineBounds.height())) { + m_heightAtCursor = lastLineBounds.height() * m_renderScale; + toUpdate.setFlag(Qt::ImCursorRectangle); + } + m_toPoint = lastLineBounds.topRight() * m_renderScale; + if (toUpdate) + QGuiApplication::inputMethod()->update(toUpdate); } } diff --git a/src/pdf/quick/qquickpdfselection_p.h b/src/pdf/quick/qquickpdfselection_p.h index d231c0d11..ee7e1f85f 100644 --- a/src/pdf/quick/qquickpdfselection_p.h +++ b/src/pdf/quick/qquickpdfselection_p.h @@ -52,30 +52,35 @@ #include <QPolygonF> #include <QVariant> #include <QtQml/qqml.h> +#include <QtQuick/qquickitem.h> #include "qquickpdfdocument_p.h" QT_BEGIN_NAMESPACE +class QPdfSelection; -class QQuickPdfSelection : public QObject +class QQuickPdfSelection : public QQuickItem { Q_OBJECT Q_PROPERTY(QQuickPdfDocument *document READ document WRITE setDocument NOTIFY documentChanged) Q_PROPERTY(int page READ page WRITE setPage NOTIFY pageChanged) + Q_PROPERTY(qreal renderScale READ renderScale WRITE setRenderScale NOTIFY renderScaleChanged) Q_PROPERTY(QPointF fromPoint READ fromPoint WRITE setFromPoint NOTIFY fromPointChanged) Q_PROPERTY(QPointF toPoint READ toPoint WRITE setToPoint NOTIFY toPointChanged) Q_PROPERTY(bool hold READ hold WRITE setHold NOTIFY holdChanged) Q_PROPERTY(QString text READ text NOTIFY textChanged) - Q_PROPERTY(QVector<QPolygonF> geometry READ geometry NOTIFY geometryChanged) + Q_PROPERTY(QVector<QPolygonF> geometry READ geometry NOTIFY selectedAreaChanged) public: - explicit QQuickPdfSelection(QObject *parent = nullptr); + explicit QQuickPdfSelection(QQuickItem *parent = nullptr); QQuickPdfDocument *document() const; void setDocument(QQuickPdfDocument * document); int page() const; void setPage(int page); + qreal renderScale() const; + void setRenderScale(qreal scale); QPointF fromPoint() const; void setFromPoint(QPointF fromPoint); QPointF toPoint() const; @@ -86,6 +91,7 @@ public: QString text() const; QVector<QPolygonF> geometry() const; + Q_INVOKABLE void clear(); Q_INVOKABLE void selectAll(); #if QT_CONFIG(clipboard) Q_INVOKABLE void copyToClipboard() const; @@ -94,24 +100,43 @@ public: signals: void documentChanged(); void pageChanged(); + void renderScaleChanged(); void fromPointChanged(); void toPointChanged(); void holdChanged(); void textChanged(); - void geometryChanged(); + void selectedAreaChanged(); + +protected: +#if QT_CONFIG(im) + void keyReleaseEvent(QKeyEvent *ev) override; + void inputMethodEvent(QInputMethodEvent *event) override; + Q_INVOKABLE QVariant inputMethodQuery(Qt::InputMethodQuery query, const QVariant &argument) const; + QVariant inputMethodQuery(Qt::InputMethodQuery query) const override; +#endif private: void resetPoints(); void updateResults(); + void update(const QPdfSelection &sel, bool textAndGeometryOnly = false); + const QString &pageText() const; private: QQuickPdfDocument *m_document = nullptr; + mutable QPointF m_hitPoint; QPointF m_fromPoint; - QPointF m_toPoint; - QString m_text; + mutable QPointF m_toPoint; + qreal m_renderScale = 1; + mutable qreal m_heightAtAnchor = 0; + mutable qreal m_heightAtCursor = 0; + QString m_text; // selected text + mutable QString m_pageText; // all text on the page QVector<QPolygonF> m_geometry; int m_page = 0; + int m_fromCharIndex = -1; // same as anchor position + mutable int m_toCharIndex = -1; // same as cursor position bool m_hold = false; + mutable bool m_pageTextDirty = true; Q_DISABLE_COPY(QQuickPdfSelection) }; @@ -119,5 +144,5 @@ private: QT_END_NAMESPACE QML_DECLARE_TYPE(QQuickPdfSelection) -\ + #endif // QQUICKPDFSELECTION_P_H diff --git a/src/pdf/quick/qquicktableviewextra.cpp b/src/pdf/quick/qquicktableviewextra.cpp new file mode 100644 index 000000000..2b59d6c6e --- /dev/null +++ b/src/pdf/quick/qquicktableviewextra.cpp @@ -0,0 +1,193 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF 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 "qquicktableviewextra_p.h" +#include <QtQml> +#include <QQmlContext> + +Q_LOGGING_CATEGORY(qLcTVE, "qt.pdf.tableextra") + +QT_BEGIN_NAMESPACE + +/*! + \internal + \qmltype TableViewExtra + \instantiates QQuickTableViewExtra + \inqmlmodule QtQuick.Pdf + \ingroup pdf + \brief A helper class with missing TableView functions + \since 5.15 + + TableViewExtra provides equivalents for some functions that will be added + to TableView in Qt 6. +*/ + +QQuickTableViewExtra::QQuickTableViewExtra(QObject *parent) : QObject(parent) +{ +} + +QPoint QQuickTableViewExtra::cellAtPos(qreal x, qreal y) const +{ + QPointF position(x, y); +#if QT_VERSION >= QT_VERSION_CHECK(6,0,0) + return m_tableView->cellAtPos(position); +#else + if (!m_tableView->boundingRect().contains(position)) + return QPoint(-1, -1); + + const QQuickItem *contentItem = m_tableView->contentItem(); + + for (const QQuickItem *child : contentItem->childItems()) { + const QPointF posInChild = m_tableView->mapToItem(child, position); + if (child->boundingRect().contains(posInChild)) { + const auto context = qmlContext(child); + const int column = context->contextProperty("column").toInt(); + const int row = context->contextProperty("row").toInt(); + return QPoint(column, row); + } + } + + return QPoint(-1, -1); +#endif +} + +QQuickItem *QQuickTableViewExtra::itemAtCell(const QPoint &cell) const +{ +#if QT_VERSION >= QT_VERSION_CHECK(6,0,0) + return m_tableView->itemAtCell(cell); +#else + const QQuickItem *contentItem = m_tableView->contentItem(); + + for (QQuickItem *child : contentItem->childItems()) { + const auto context = qmlContext(child); + const int column = context->contextProperty("column").toInt(); + const int row = context->contextProperty("row").toInt(); + if (QPoint(column, row) == cell) + return child; + } + + return nullptr; +#endif +} + +void QQuickTableViewExtra::positionViewAtCell(const QPoint &cell, Qt::Alignment alignment, const QPointF &offset) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6,0,0) + m_tableView->positionViewAtCell(cell, alignment, offset); +#else + // Note: this fallback implementation assumes all cells to be of the same size! + + if (cell.x() < 0 || cell.x() > m_tableView->columns() - 1) + return; + if (cell.y() < 0 || cell.y() > m_tableView->rows() - 1) + return; + + Qt::Alignment verticalAlignment = alignment & (Qt::AlignTop | Qt::AlignVCenter | Qt::AlignBottom); + Qt::Alignment horizontalAlignment = alignment & (Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight); + + const QQuickItem *contentItem = m_tableView->contentItem(); + const QQuickItem *randomChild = contentItem->childItems().first(); + const qreal cellWidth = randomChild->width(); + const qreal cellHeight = randomChild->height(); + + if (!verticalAlignment && !horizontalAlignment) { + qmlWarning(this) << "No valid alignment specified"; + return; + } + + if (horizontalAlignment) { + qreal newPosX = 0; + const qreal columnPosLeft = int(cell.x() * (cellWidth + m_tableView->columnSpacing())); + m_tableView->setContentX(0); + m_tableView->forceLayout(); + m_tableView->setContentX(columnPosLeft); + m_tableView->forceLayout(); + + switch (horizontalAlignment) { + case Qt::AlignLeft: + newPosX = m_tableView->contentX() + offset.x(); + break; + case Qt::AlignHCenter: + newPosX = m_tableView->contentX() + - m_tableView->width() / 2 + + (cellWidth / 2) + + offset.x(); + break; + case Qt::AlignRight: + newPosX = m_tableView->contentX() + - m_tableView->width() + + cellWidth + + offset.x(); + break; + } + + m_tableView->setContentX(newPosX); + m_tableView->forceLayout(); + } + + if (verticalAlignment) { + qreal newPosY = 0; + const qreal rowPosTop = int(cell.y() * (cellHeight + m_tableView->rowSpacing())); + m_tableView->setContentY(0); + m_tableView->forceLayout(); + m_tableView->setContentY(rowPosTop); + m_tableView->forceLayout(); + + switch (verticalAlignment) { + case Qt::AlignTop: + newPosY = m_tableView->contentY() + offset.y(); + break; + case Qt::AlignVCenter: + newPosY = m_tableView->contentY() + - m_tableView->height() / 2 + + (cellHeight / 2) + + offset.y(); + break; + case Qt::AlignBottom: + newPosY = m_tableView->contentY() + - m_tableView->height() + + cellHeight + + offset.y(); + break; + } + + m_tableView->setContentY(newPosY); + m_tableView->forceLayout(); + } +#endif +} + +QT_END_NAMESPACE diff --git a/src/pdf/quick/qquicktableviewextra_p.h b/src/pdf/quick/qquicktableviewextra_p.h new file mode 100644 index 000000000..11b4955a1 --- /dev/null +++ b/src/pdf/quick/qquicktableviewextra_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtPDF 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 QQUICKTABLEVIEWEXTRA_P_H +#define QQUICKTABLEVIEWEXTRA_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 <QPointF> +#include <QPolygonF> +#include <QVariant> +#include <QtQml/qqml.h> +#include <QtQuick/qquickitem.h> +#include <QtQuick/private/qquicktableview_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickTableViewExtra : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickTableView *tableView READ tableView WRITE setTableView) + +public: + QQuickTableViewExtra(QObject *parent = nullptr); + + QQuickTableView * tableView() const { return m_tableView; } + void setTableView(QQuickTableView * tableView) { m_tableView = tableView; } + + Q_INVOKABLE QPoint cellAtPos(qreal x, qreal y) const; + Q_INVOKABLE QQuickItem *itemAtCell(int column, int row) const { + return itemAtCell(QPoint(column, row)); + } + Q_INVOKABLE QQuickItem *itemAtCell(const QPoint &cell) const; + Q_INVOKABLE void positionViewAtCell(int column, int row, Qt::Alignment alignment, const QPointF &offset = QPointF()) { + positionViewAtCell(QPoint(column, row), alignment, offset); + } + Q_INVOKABLE void positionViewAtCell(const QPoint &cell, Qt::Alignment alignment, const QPointF &offset); + Q_INVOKABLE void positionViewAtRow(int row, Qt::Alignment alignment, qreal offset = 0) { + positionViewAtCell(QPoint(0, row), alignment & Qt::AlignVertical_Mask, QPointF(0, offset)); + } + +private: + QQuickTableView *m_tableView = nullptr; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickTableViewExtra) + +#endif // QQUICKTABLEVIEWEXTRA_P_H diff --git a/src/pdf/quick/quick.pro b/src/pdf/quick/quick.pro index b62b80346..bd6bc8827 100644 --- a/src/pdf/quick/quick.pro +++ b/src/pdf/quick/quick.pro @@ -3,6 +3,10 @@ TARGET = pdfplugin TARGETPATH = QtQuick/Pdf IMPORT_VERSION = 1.0 +# qpdfdocument_p.h includes pdfium headers which we must find in order to use private API +CHROMIUM_SRC_DIR = $$QTWEBENGINE_ROOT/$$getChromiumSrcDir() +INCLUDEPATH += $$CHROMIUM_SRC_DIR + #QMAKE_DOCS = $$PWD/doc/qtquickpdf.qdocconf PDF_QML_FILES = \ @@ -21,6 +25,7 @@ SOURCES += \ qquickpdfnavigationstack.cpp \ qquickpdfsearchmodel.cpp \ qquickpdfselection.cpp \ + qquicktableviewextra.cpp \ HEADERS += \ qquickpdfdocument_p.h \ @@ -28,7 +33,8 @@ HEADERS += \ qquickpdfnavigationstack_p.h \ qquickpdfsearchmodel_p.h \ qquickpdfselection_p.h \ + qquicktableviewextra_p.h \ -QT += pdf quick-private gui gui-private core core-private qml qml-private +QT += pdf pdf-private gui core qml quick quick-private load(qml_plugin) diff --git a/src/webengine/api/qquickwebengineview.cpp b/src/webengine/api/qquickwebengineview.cpp index afb0fbd25..744891a61 100644 --- a/src/webengine/api/qquickwebengineview.cpp +++ b/src/webengine/api/qquickwebengineview.cpp @@ -129,6 +129,7 @@ QQuickWebEngineViewPrivate::QQuickWebEngineViewPrivate() , devicePixelRatio(QGuiApplication::primaryScreen()->devicePixelRatio()) , m_webChannel(0) , m_webChannelWorld(0) + , m_defaultAudioMuted(false) , m_isBeingAdopted(false) , m_backgroundColor(Qt::white) , m_zoomFactor(1.0) @@ -547,12 +548,13 @@ void QQuickWebEngineViewPrivate::unhandledKeyEvent(QKeyEvent *event) QCoreApplication::sendEvent(q->parentItem(), event); } -void QQuickWebEngineViewPrivate::adoptNewWindow(QSharedPointer<WebContentsAdapter> newWebContents, WindowOpenDisposition disposition, bool userGesture, const QRect &, const QUrl &targetUrl) +QSharedPointer<WebContentsAdapter> +QQuickWebEngineViewPrivate::adoptNewWindow(QSharedPointer<WebContentsAdapter> newWebContents, + WindowOpenDisposition disposition, bool userGesture, + const QRect &, const QUrl &targetUrl) { Q_Q(QQuickWebEngineView); QQuickWebEngineNewViewRequest request; - // This increases the ref-count of newWebContents and will tell Chromium - // to start loading it and possibly return it to its parent page window.open(). request.m_adapter = newWebContents; request.m_isUserInitiated = userGesture; request.m_requestedUrl = targetUrl; @@ -575,6 +577,8 @@ void QQuickWebEngineViewPrivate::adoptNewWindow(QSharedPointer<WebContentsAdapte } Q_EMIT q->newViewRequested(&request); + + return newWebContents; } bool QQuickWebEngineViewPrivate::isBeingAdopted() @@ -669,11 +673,8 @@ void QQuickWebEngineViewPrivate::runMediaAccessPermissionRequest(const QUrl &sec void QQuickWebEngineViewPrivate::runMouseLockPermissionRequest(const QUrl &securityOrigin) { - - Q_UNUSED(securityOrigin); - // TODO: Add mouse lock support - adapter->grantMouseLockPermission(false); + adapter->grantMouseLockPermission(securityOrigin, false); } void QQuickWebEngineViewPrivate::runQuotaRequest(QWebEngineQuotaRequest request) @@ -906,6 +907,9 @@ void QQuickWebEngineViewPrivate::initializationFinished() adapter->setWebChannel(m_webChannel, m_webChannelWorld); #endif + if (m_defaultAudioMuted != adapter->isAudioMuted()) + adapter->setAudioMuted(m_defaultAudioMuted); + if (devToolsView && devToolsView->d_ptr->adapter) adapter->openDevToolsFrontend(devToolsView->d_ptr->adapter); @@ -1426,15 +1430,18 @@ void QQuickWebEngineView::setBackgroundColor(const QColor &color) bool QQuickWebEngineView::isAudioMuted() const { const Q_D(QQuickWebEngineView); - return d->adapter->isAudioMuted(); + if (d->adapter->isInitialized()) + return d->adapter->isAudioMuted(); + return d->m_defaultAudioMuted; } void QQuickWebEngineView::setAudioMuted(bool muted) { Q_D(QQuickWebEngineView); - bool wasAudioMuted = d->adapter->isAudioMuted(); + bool wasAudioMuted = isAudioMuted(); + d->m_defaultAudioMuted = muted; d->adapter->setAudioMuted(muted); - if (wasAudioMuted != d->adapter->isAudioMuted()) + if (wasAudioMuted != isAudioMuted()) Q_EMIT audioMutedChanged(muted); } diff --git a/src/webengine/api/qquickwebengineview_p_p.h b/src/webengine/api/qquickwebengineview_p_p.h index 5c884e36e..12a991ffa 100644 --- a/src/webengine/api/qquickwebengineview_p_p.h +++ b/src/webengine/api/qquickwebengineview_p_p.h @@ -121,7 +121,10 @@ public: void loadFinished(bool success, const QUrl &url, bool isErrorPage = false, int errorCode = 0, const QString &errorDescription = QString()) override; void focusContainer() override; void unhandledKeyEvent(QKeyEvent *event) override; - void adoptNewWindow(QSharedPointer<QtWebEngineCore::WebContentsAdapter> newWebContents, WindowOpenDisposition disposition, bool userGesture, const QRect &, const QUrl &targetUrl) override; + QSharedPointer<QtWebEngineCore::WebContentsAdapter> + adoptNewWindow(QSharedPointer<QtWebEngineCore::WebContentsAdapter> newWebContents, + WindowOpenDisposition disposition, bool userGesture, const QRect &, + const QUrl &targetUrl) override; bool isBeingAdopted() override; void close() override; void windowCloseRejected() override; @@ -214,6 +217,7 @@ public: QPointer<QQuickWebEngineView> inspectedView; QPointer<QQuickWebEngineView> devToolsView; uint m_webChannelWorld; + bool m_defaultAudioMuted; bool m_isBeingAdopted; mutable QQuickWebEngineAction *actions[QQuickWebEngineView::WebActionCount]; QtWebEngineCore::RenderWidgetHostViewQtDelegateQuick *widget = nullptr; diff --git a/src/webengine/doc/src/external-resources.qdoc b/src/webengine/doc/src/external-resources.qdoc index acf63fb04..7878ed9f8 100644 --- a/src/webengine/doc/src/external-resources.qdoc +++ b/src/webengine/doc/src/external-resources.qdoc @@ -71,8 +71,8 @@ */ /*! - \externalpage http://www.widevine.com/wv_drm.html - \title Widevine DRM + \externalpage http://www.widevine.com + \title Widevine CDM */ /*! @@ -86,8 +86,8 @@ */ /*! - \externalpage https://shaka-player-demo.appspot.com/demo/ - \title Shaka Player + \externalpage https://bitmovin.com/demos/drm + \title Bitmovin Player */ /*! diff --git a/src/webengine/doc/src/qtwebengine-features.qdoc b/src/webengine/doc/src/qtwebengine-features.qdoc index 954992de1..431367765 100644 --- a/src/webengine/doc/src/qtwebengine-features.qdoc +++ b/src/webengine/doc/src/qtwebengine-features.qdoc @@ -181,8 +181,30 @@ \section1 HTML5 DRM - \QWE supports viewing DRM protected videos if the \l{Widevine DRM} - plugin has been installed. + \QWE supports viewing DRM protected videos if the \l{Widevine CDM} plugin has been installed. + CDM plugin is a replacement of Flash based plugins for displaying DRM-protected content. + It comes only in a binary format, so it can hide DRM decryption implementation details. + It can be obtained from a third party or from a Google Chrome installation. + + \QWE on startup looks for the \l{Widevine CDM} plugin in well know locations, like + default Google Chrome installation directory or Linux distro specific paths. However, plugin + location can be also passed with \c {QTWEBENGINE_CHROMIUM_FLAGS} using \c {widevine-path}. + + On Windows: + \code + set QTWEBENGINE_CHROMIUM_FLAGS=--widevine-path="C:/some path/widevinecdm.dll" + \endcode + + On Linux: + \code + export QTWEBENGINE_CHROMIUM_FLAGS=--widevine-path="/some path/libwidevinecdm.so" + \endcode + + On macOS: + \code + export QTWEBENGINE_CHROMIUM_FLAGS=--widevine-path="/some path/libwidevinecdm.dylib" + \endcode + The video format most commonly used by DRM services, H.264, requires proprietary audio and video codecs. For more information about enabling the @@ -190,7 +212,7 @@ This feature can be tested by playing a video in \l{WebEngine Widgets Simple Browser Example}{Simple Browser} or \l{WebEngine Quick Nano Browser}{Nano Browser} - from \l{castLabs}, \l{Swank Motion Pictures, Inc.}, or \l{Shaka Player}. + from \l{castLabs}, \l{Swank Motion Pictures, Inc.}, or \l{Bitmovin Player}. Support for this feature was added in Qt 5.7.0. diff --git a/src/webengine/render_widget_host_view_qt_delegate_quick.cpp b/src/webengine/render_widget_host_view_qt_delegate_quick.cpp index cb41c8706..30f50d7c1 100644 --- a/src/webengine/render_widget_host_view_qt_delegate_quick.cpp +++ b/src/webengine/render_widget_host_view_qt_delegate_quick.cpp @@ -64,7 +64,7 @@ RenderWidgetHostViewQtDelegateQuick::RenderWidgetHostViewQtDelegateQuick(RenderW setFocus(true); setActiveFocusOnTab(true); -#if defined(Q_OS_MACOS) && !defined(QT_NO_OPENGL) +#if defined(Q_OS_MACOS) && QT_CONFIG(opengl) // Check that the default QSurfaceFormat OpenGL profile is compatible with the global OpenGL // shared context profile, otherwise this could lead to a nasty crash. QOpenGLContext *globalSharedContext = QOpenGLContext::globalShareContext(); diff --git a/src/webenginewidgets/api/qtwebenginewidgetsglobal.cpp b/src/webenginewidgets/api/qtwebenginewidgetsglobal.cpp index a39c0e483..5949f3d6e 100644 --- a/src/webenginewidgets/api/qtwebenginewidgetsglobal.cpp +++ b/src/webenginewidgets/api/qtwebenginewidgetsglobal.cpp @@ -49,13 +49,13 @@ namespace QtWebEngineCore QT_BEGIN_NAMESPACE -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); #endif static void initialize() { -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) if (QCoreApplication::instance()) { //On window/ANGLE, calling QtWebEngine::initialize from DllMain will result in a crash. if (!qt_gl_global_share_context()) { @@ -67,7 +67,7 @@ static void initialize() } //QCoreApplication is not yet instantiated, ensuring the call will be deferred qAddPreRoutine(QtWebEngineCore::initialize); -#endif // QT_NO_OPENGL +#endif // QT_CONFIG(opengl) } Q_CONSTRUCTOR_FUNCTION(initialize) diff --git a/src/webenginewidgets/api/qwebenginepage.cpp b/src/webenginewidgets/api/qwebenginepage.cpp index b267c5dd1..dc06e48be 100644 --- a/src/webenginewidgets/api/qwebenginepage.cpp +++ b/src/webenginewidgets/api/qwebenginepage.cpp @@ -348,7 +348,10 @@ void QWebEnginePagePrivate::unhandledKeyEvent(QKeyEvent *event) QGuiApplication::sendEvent(view->parentWidget(), event); } -void QWebEnginePagePrivate::adoptNewWindow(QSharedPointer<WebContentsAdapter> newWebContents, WindowOpenDisposition disposition, bool userGesture, const QRect &initialGeometry, const QUrl &targetUrl) +QSharedPointer<WebContentsAdapter> +QWebEnginePagePrivate::adoptNewWindow(QSharedPointer<WebContentsAdapter> newWebContents, + WindowOpenDisposition disposition, bool userGesture, + const QRect &initialGeometry, const QUrl &targetUrl) { Q_Q(QWebEnginePage); Q_UNUSED(userGesture); @@ -356,27 +359,11 @@ void QWebEnginePagePrivate::adoptNewWindow(QSharedPointer<WebContentsAdapter> ne QWebEnginePage *newPage = q->createWindow(toWindowType(disposition)); if (!newPage) - return; + return nullptr; - if (newPage->d_func() == this) { - // If createWindow returns /this/ we must delay the adoption. - Q_ASSERT(q == newPage); - // WebContents might be null if we just opened a new page for navigation, in that case - // avoid referencing newWebContents so that it is deleted and WebContentsDelegateQt::OpenURLFromTab - // will fall back to navigating current page. - if (newWebContents->webContents()) { - QTimer::singleShot(0, q, [this, newPage, newWebContents, initialGeometry] () { - adoptNewWindowImpl(newPage, newWebContents, initialGeometry); - }); - } - } else { - adoptNewWindowImpl(newPage, newWebContents, initialGeometry); - } -} + if (!newWebContents->webContents()) + return newPage->d_func()->adapter; // Reuse existing adapter -void QWebEnginePagePrivate::adoptNewWindowImpl(QWebEnginePage *newPage, - const QSharedPointer<WebContentsAdapter> &newWebContents, const QRect &initialGeometry) -{ // Mark the new page as being in the process of being adopted, so that a second mouse move event // sent by newWebContents->initialize() gets filtered in RenderWidgetHostViewQt::forwardEvent. // The first mouse move event is being sent by q->createWindow(). This is necessary because @@ -394,6 +381,8 @@ void QWebEnginePagePrivate::adoptNewWindowImpl(QWebEnginePage *newPage, if (!initialGeometry.isEmpty()) emit newPage->geometryChangeRequested(initialGeometry); + + return newWebContents; } bool QWebEnginePagePrivate::isBeingAdopted() @@ -1147,9 +1136,11 @@ bool QWebEnginePage::isAudioMuted() const { void QWebEnginePage::setAudioMuted(bool muted) { Q_D(QWebEnginePage); + bool wasAudioMuted = isAudioMuted(); d->defaultAudioMuted = muted; - if (d->adapter->isInitialized()) - d->adapter->setAudioMuted(muted); + d->adapter->setAudioMuted(muted); + if (wasAudioMuted != isAudioMuted()) + Q_EMIT audioMutedChanged(muted); } /*! @@ -1971,7 +1962,7 @@ void QWebEnginePage::setFeaturePermission(const QUrl &securityOrigin, QWebEngine d->adapter->runFeatureRequestCallback(securityOrigin, ProfileAdapter::GeolocationPermission, true); break; case MouseLock: - d->adapter->grantMouseLockPermission(true); + d->adapter->grantMouseLockPermission(securityOrigin, true); break; case Notifications: d->adapter->runFeatureRequestCallback(securityOrigin, ProfileAdapter::NotificationPermission, true); @@ -1990,7 +1981,7 @@ void QWebEnginePage::setFeaturePermission(const QUrl &securityOrigin, QWebEngine d->adapter->runFeatureRequestCallback(securityOrigin, ProfileAdapter::GeolocationPermission, false); break; case MouseLock: - d->adapter->grantMouseLockPermission(false); + d->adapter->grantMouseLockPermission(securityOrigin, false); break; case Notifications: d->adapter->runFeatureRequestCallback(securityOrigin, ProfileAdapter::NotificationPermission, false); diff --git a/src/webenginewidgets/api/qwebenginepage_p.h b/src/webenginewidgets/api/qwebenginepage_p.h index f37413b8e..b4424ec4b 100644 --- a/src/webenginewidgets/api/qwebenginepage_p.h +++ b/src/webenginewidgets/api/qwebenginepage_p.h @@ -112,10 +112,10 @@ public: void loadFinished(bool success, const QUrl &url, bool isErrorPage = false, int errorCode = 0, const QString &errorDescription = QString()) override; void focusContainer() override; void unhandledKeyEvent(QKeyEvent *event) override; - void adoptNewWindow(QSharedPointer<QtWebEngineCore::WebContentsAdapter> newWebContents, WindowOpenDisposition disposition, bool userGesture, const QRect &initialGeometry, const QUrl &targetUrl) override; - void adoptNewWindowImpl(QWebEnginePage *newPage, - const QSharedPointer<QtWebEngineCore::WebContentsAdapter> &newWebContents, - const QRect &initialGeometry); + QSharedPointer<QtWebEngineCore::WebContentsAdapter> + adoptNewWindow(QSharedPointer<QtWebEngineCore::WebContentsAdapter> newWebContents, + WindowOpenDisposition disposition, bool userGesture, + const QRect &initialGeometry, const QUrl &targetUrl) override; bool isBeingAdopted() override; void close() override; void windowCloseRejected() override; diff --git a/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc b/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc index 7701b9b3d..9d5c41713 100644 --- a/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc +++ b/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc @@ -805,9 +805,10 @@ Sets the permission for the web site identified by \a securityOrigin to use \a feature to \a policy. - \note Call this method on the featurePermissionRequested() signal, as it is - meant to serve pending feature requests only. Setting feature permissions - ahead of a request has no effect. + \note This method is primarily for calling after a featurePermissionRequested() signal has been emitted + to trigger the feature permission response. It can also be called before a request has been emitted, + but will only set a granted permission for passive checks, mainly for Notification APIs that can check + if permission has already been granted before explicitly requesting it. \sa featurePermissionRequested(), featurePermissionRequestCanceled() */ diff --git a/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp b/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp index 2c7cc83d1..08c471763 100644 --- a/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp +++ b/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp @@ -106,7 +106,7 @@ RenderWidgetHostViewQtDelegateWidget::RenderWidgetHostViewQtDelegateWidget(Rende format.setDepthBufferSize(24); format.setStencilBufferSize(8); -#ifndef QT_NO_OPENGL +#if QT_CONFIG(opengl) QOpenGLContext *globalSharedContext = QOpenGLContext::globalShareContext(); if (globalSharedContext) { QSurfaceFormat sharedFormat = globalSharedContext->format(); @@ -473,11 +473,16 @@ bool RenderWidgetHostViewQtDelegateWidget::event(QEvent *event) if (!handled) return QQuickWidget::event(event); - // Most events are accepted by default, but tablet events are not: event->accept(); return true; } +void RenderWidgetHostViewQtDelegateWidget::unhandledWheelEvent(QWheelEvent *ev) +{ + if (QWidget *p = parentWidget()) + qApp->sendEvent(p, ev); +} + void RenderWidgetHostViewQtDelegateWidget::onWindowPosChanged() { m_client->visualPropertiesChanged(); diff --git a/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.h b/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.h index c7783117a..034fdd65c 100644 --- a/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.h +++ b/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.h @@ -91,6 +91,7 @@ public: void setInputMethodHints(Qt::InputMethodHints) override; void setClearColor(const QColor &color) override; bool copySurface(const QRect &, const QSize &, QImage &) override; + void unhandledWheelEvent(QWheelEvent *ev) override; protected: bool event(QEvent *event) override; diff --git a/tests/auto/quick/qmltests/data/tst_audioMuted.qml b/tests/auto/quick/qmltests/data/tst_audioMuted.qml new file mode 100644 index 000000000..c626d07a0 --- /dev/null +++ b/tests/auto/quick/qmltests/data/tst_audioMuted.qml @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtWebEngine 1.4 + +TestWebEngineView { + id: view + width: 400 + height: 400 + + SignalSpy { + id: spy + target: view + signalName: "audioMutedChanged" + } + + TestCase { + id: test + name: "WebEngineViewAudioMuted" + + function test_audioMuted() { + compare(view.audioMuted, false); + view.audioMuted = true; + view.url = "about:blank"; + verify(view.waitForLoadSucceeded()); + compare(view.audioMuted, true); + compare(spy.count, 1); + compare(spy.signalArguments[0][0], true); + view.audioMuted = false; + compare(view.audioMuted, false); + compare(spy.count, 2); + compare(spy.signalArguments[1][0], false); + } + } +} + diff --git a/tests/auto/quick/qmltests/qmltests.pro b/tests/auto/quick/qmltests/qmltests.pro index 0b3ff7c7e..5c57f7ad9 100644 --- a/tests/auto/quick/qmltests/qmltests.pro +++ b/tests/auto/quick/qmltests/qmltests.pro @@ -31,6 +31,7 @@ OTHER_FILES += \ $$PWD/data/titleupdate.js \ $$PWD/data/tst_action.qml \ $$PWD/data/tst_activeFocusOnPress.qml \ + $$PWD/data/tst_audioMuted.qml \ $$PWD/data/tst_contextMenu.qml \ $$PWD/data/tst_desktopBehaviorLoadHtml.qml \ $$PWD/data/tst_download.qml \ diff --git a/tests/auto/widgets/proxy/proxy_server.cpp b/tests/auto/widgets/proxy/proxy_server.cpp index 55f014914..3bf915609 100644 --- a/tests/auto/widgets/proxy/proxy_server.cpp +++ b/tests/auto/widgets/proxy/proxy_server.cpp @@ -42,8 +42,16 @@ void ProxyServer::setCredentials(const QByteArray &user, const QByteArray passwo m_auth.append(QChar(':')); m_auth.append(password); m_auth = m_auth.toBase64(); + m_authenticate = true; } +void ProxyServer::setCookie(const QByteArray &cookie) +{ + m_cookie.append(QByteArrayLiteral("Cookie: ")); + m_cookie.append(cookie); +} + + bool ProxyServer::isListening() { return m_server.isListening(); @@ -75,7 +83,7 @@ void ProxyServer::handleReadReady() if (!m_data.endsWith("\r\n\r\n")) return; - if (!m_data.contains(QByteArrayLiteral("Proxy-Authorization: Basic"))) { + if (m_authenticate && !m_data.contains(QByteArrayLiteral("Proxy-Authorization: Basic"))) { socket->write("HTTP/1.1 407 Proxy Authentication Required\nProxy-Authenticate: " "Basic realm=\"Proxy requires authentication\"\r\n" "content-length: 0\r\n" @@ -83,8 +91,12 @@ void ProxyServer::handleReadReady() return; } - if (m_data.contains(m_auth)) { - emit success(); + if (m_authenticate && m_data.contains(m_auth)) { + emit authenticationSuccess(); + } + + if (m_data.contains(m_cookie)) { + emit cookieMatch(); } m_data.clear(); } diff --git a/tests/auto/widgets/proxy/proxy_server.h b/tests/auto/widgets/proxy/proxy_server.h index cb7c30600..7bc7b100b 100644 --- a/tests/auto/widgets/proxy/proxy_server.h +++ b/tests/auto/widgets/proxy/proxy_server.h @@ -39,6 +39,7 @@ class ProxyServer : public QObject public: explicit ProxyServer(QObject *parent = nullptr); void setCredentials(const QByteArray &user, const QByteArray password); + void setCookie(const QByteArray &cookie); bool isListening(); public slots: @@ -49,11 +50,15 @@ private slots: void handleReadReady(); signals: - void success(); + void authenticationSuccess(); + void cookieMatch(); + private: QByteArray m_data; QTcpServer m_server; QByteArray m_auth; + QByteArray m_cookie; + bool m_authenticate = false; }; #endif // PROXY_SERVER_H diff --git a/tests/auto/widgets/proxy/tst_proxy.cpp b/tests/auto/widgets/proxy/tst_proxy.cpp index 5f5dec016..c3e3c88a4 100644 --- a/tests/auto/widgets/proxy/tst_proxy.cpp +++ b/tests/auto/widgets/proxy/tst_proxy.cpp @@ -32,6 +32,17 @@ #include <QNetworkProxy> #include <QWebEnginePage> #include <QWebEngineView> +#include <QWebEngineUrlRequestInterceptor> + + +struct Interceptor : public QWebEngineUrlRequestInterceptor +{ + Interceptor(const QByteArray cookie):m_cookie(cookie){}; + void interceptRequest(QWebEngineUrlRequestInfo &info) override { + info.setHttpHeader(QByteArray("Cookie"), m_cookie); + }; + QByteArray m_cookie; +}; class tst_Proxy : public QObject { @@ -41,8 +52,10 @@ public: private slots: void proxyAuthentication(); + void forwardCookie(); }; + void tst_Proxy::proxyAuthentication() { QByteArray user(QByteArrayLiteral("test")); @@ -59,11 +72,31 @@ void tst_Proxy::proxyAuthentication() server.run(); QTRY_VERIFY2(server.isListening(), "Could not setup authentication server"); QWebEnginePage page; - QSignalSpy successSpy(&server, &ProxyServer::success); + QSignalSpy successSpy(&server, &ProxyServer::authenticationSuccess); page.load(QUrl("http://www.qt.io")); QTRY_VERIFY2(successSpy.count() > 0, "Could not get authentication token"); } +void tst_Proxy::forwardCookie() +{ + QNetworkProxy proxy; + proxy.setType(QNetworkProxy::HttpProxy); + proxy.setHostName("localhost"); + proxy.setPort(5555); + QNetworkProxy::setApplicationProxy(proxy); + ProxyServer server; + QByteArray cookie("foo=bar; sessionToken=123"); + server.setCookie(cookie); + server.run(); + QTRY_VERIFY2(server.isListening(), "Could not setup proxy server"); + Interceptor interceptor(cookie); + QWebEnginePage page; + page.setUrlRequestInterceptor(&interceptor); + QSignalSpy cookieSpy(&server, &ProxyServer::cookieMatch); + page.load(QUrl("http://www.qt.io")); + QTRY_VERIFY2(cookieSpy.count() > 0, "Could not get cookie"); +} + #include "tst_proxy.moc" QTEST_MAIN(tst_Proxy) diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp index 098656390..92ee791db 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp @@ -197,6 +197,8 @@ private Q_SLOTS: void dataURLFragment(); void devTools(); void openLinkInDifferentProfile(); + void openLinkInNewPage_data(); + void openLinkInNewPage(); void triggerActionWithoutMenu(); void dynamicFrame(); @@ -204,6 +206,7 @@ private Q_SLOTS: void notificationRequest(); void sendNotification(); void contentsSize(); + void notificationPermission(); void setLifecycleState(); void setVisible(); @@ -227,6 +230,7 @@ private Q_SLOTS: void renderProcessCrashed(); void renderProcessPid(); void backgroundColor(); + void audioMuted(); private: static QPoint elementCenter(QWebEnginePage *page, const QString &id); @@ -3378,6 +3382,162 @@ void tst_QWebEnginePage::openLinkInDifferentProfile() QVERIFY(spy2.takeFirst().value(0).toBool()); } +// What does createWindow do? +enum class OpenLinkInNewPageDecision { + // Returns nullptr, + ReturnNull, + // Returns this, + ReturnSelf, + // Returns page != this + ReturnOther, +}; + +// What causes createWindow to be called? +enum class OpenLinkInNewPageCause { + // User clicks on a link with target=_blank. + TargetBlank, + // User clicks with MiddleButton. + MiddleClick, +}; + +// What happens after createWindow? +enum class OpenLinkInNewPageEffect { + // The navigation request disappears into the ether. + Blocked, + // The navigation request becomes a navigation in the original page. + LoadInSelf, + // The navigation request becomes a navigation in a different page. + LoadInOther, +}; + +Q_DECLARE_METATYPE(OpenLinkInNewPageCause) +Q_DECLARE_METATYPE(OpenLinkInNewPageDecision) +Q_DECLARE_METATYPE(OpenLinkInNewPageEffect) + +void tst_QWebEnginePage::openLinkInNewPage_data() +{ + using Decision = OpenLinkInNewPageDecision; + using Cause = OpenLinkInNewPageCause; + using Effect = OpenLinkInNewPageEffect; + + QTest::addColumn<Decision>("decision"); + QTest::addColumn<Cause>("cause"); + QTest::addColumn<Effect>("effect"); + + // Note that the meaning of returning nullptr from createWindow is not + // consistent between the TargetBlank and MiddleClick scenarios. + // + // With TargetBlank, the open-in-new-page disposition comes from the HTML + // target attribute; something the user is probably not aware of. Returning + // nullptr is interpreted as a decision by the app to block an unwanted + // popup. + // + // With MiddleClick, the open-in-new-page disposition comes from the user's + // explicit intent. Returning nullptr is then interpreted as a failure by + // the app to fulfill this intent, which we try to compensate by ignoring + // the disposition and performing the navigation request normally. + + QTest::newRow("BlockPopup") << Decision::ReturnNull << Cause::TargetBlank << Effect::Blocked; + QTest::newRow("IgnoreIntent") << Decision::ReturnNull << Cause::MiddleClick << Effect::LoadInSelf; + QTest::newRow("OverridePopup") << Decision::ReturnSelf << Cause::TargetBlank << Effect::LoadInSelf; + QTest::newRow("OverrideIntent") << Decision::ReturnSelf << Cause::MiddleClick << Effect::LoadInSelf; + QTest::newRow("AcceptPopup") << Decision::ReturnOther << Cause::TargetBlank << Effect::LoadInOther; + QTest::newRow("AcceptIntent") << Decision::ReturnOther << Cause::MiddleClick << Effect::LoadInOther; +} + +void tst_QWebEnginePage::openLinkInNewPage() +{ + using Decision = OpenLinkInNewPageDecision; + using Cause = OpenLinkInNewPageCause; + using Effect = OpenLinkInNewPageEffect; + + class Page : public QWebEnginePage + { + public: + Page *targetPage = nullptr; + QSignalSpy spy{this, &QWebEnginePage::loadFinished}; + Page(QWebEngineProfile *profile) : QWebEnginePage(profile) {} + private: + QWebEnginePage *createWindow(WebWindowType) override { return targetPage; } + }; + + class View : public QWebEngineView + { + public: + View(Page *page) + { + resize(500, 500); + setPage(page); + } + }; + + QFETCH(Decision, decision); + QFETCH(Cause, cause); + QFETCH(Effect, effect); + + QWebEngineProfile profile; + Page page1(&profile); + Page page2(&profile); + View view1(&page1); + View view2(&page2); + + view1.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view1)); + + page1.setHtml("<html><body>" + "<a id='link' href='data:,hello' target='_blank'>link</a>" + "</body></html>"); + QTRY_COMPARE(page1.spy.count(), 1); + QVERIFY(page1.spy.takeFirst().value(0).toBool()); + + switch (decision) { + case Decision::ReturnNull: + page1.targetPage = nullptr; + break; + case Decision::ReturnSelf: + page1.targetPage = &page1; + break; + case Decision::ReturnOther: + page1.targetPage = &page2; + break; + } + + Qt::MouseButton button; + switch (cause) { + case Cause::TargetBlank: + button = Qt::LeftButton; + break; + case Cause::MiddleClick: + button = Qt::MiddleButton; + break; + } + QTest::mouseClick(view1.focusProxy(), button, {}, elementCenter(&page1, "link")); + + switch (effect) { + case Effect::Blocked: + // Nothing to test + break; + case Effect::LoadInSelf: + QTRY_COMPARE(page1.spy.count(), 1); + QVERIFY(page1.spy.takeFirst().value(0).toBool()); + QCOMPARE(page2.spy.count(), 0); + if (decision == Decision::ReturnSelf && cause == Cause::TargetBlank) + // History was discarded due to AddNewContents + QCOMPARE(page1.history()->count(), 1); + else + QCOMPARE(page1.history()->count(), 2); + QCOMPARE(page2.history()->count(), 0); + break; + case Effect::LoadInOther: + QTRY_COMPARE(page2.spy.count(), 1); + QVERIFY(page2.spy.takeFirst().value(0).toBool()); + QCOMPARE(page1.spy.count(), 0); + QCOMPARE(page1.history()->count(), 1); + QCOMPARE(page2.history()->count(), 1); + break; + } +} + void tst_QWebEnginePage::triggerActionWithoutMenu() { // Calling triggerAction should not crash even when for @@ -3455,6 +3615,18 @@ void tst_QWebEnginePage::notificationRequest() QCOMPARE(page.getPermission(), permission); } +void tst_QWebEnginePage::notificationPermission() +{ + QWebEngineProfile otr; + QWebEnginePage page(&otr, nullptr); + QSignalSpy spy(&page, &QWebEnginePage::loadFinished); + page.setHtml(QString("<html><body>Test</body></html>"), QUrl("https://www.example.com")); + QTRY_COMPARE(spy.count(), 1); + QCOMPARE(evaluateJavaScriptSync(&page, QStringLiteral("Notification.permission")), QLatin1String("default")); + page.setFeaturePermission(QUrl("https://www.example.com"), QWebEnginePage::Notifications, QWebEnginePage::PermissionGrantedByUser); + QTRY_COMPARE(evaluateJavaScriptSync(&page, QStringLiteral("Notification.permission")), QLatin1String("granted")); +} + void tst_QWebEnginePage::sendNotification() { NotificationPage page(QWebEnginePage::PermissionGrantedByUser); @@ -4432,6 +4604,24 @@ void tst_QWebEnginePage::backgroundColor() QTRY_COMPARE(view.grab().toImage().pixelColor(center), Qt::green); } +void tst_QWebEnginePage::audioMuted() +{ + QWebEngineProfile profile; + QWebEnginePage page(&profile); + QSignalSpy spy(&page, &QWebEnginePage::audioMutedChanged); + + QCOMPARE(page.isAudioMuted(), false); + page.setAudioMuted(true); + loadSync(&page, QUrl("about:blank")); + QCOMPARE(page.isAudioMuted(), true); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy[0][0], QVariant(true)); + page.setAudioMuted(false); + QCOMPARE(page.isAudioMuted(), false); + QCOMPARE(spy.count(), 2); + QCOMPARE(spy[1][0], QVariant(false)); +} + static QByteArrayList params = {QByteArrayLiteral("--use-fake-device-for-media-stream")}; W_QTEST_MAIN(tst_QWebEnginePage, params) diff --git a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp index 6350c8510..00d4bae5a 100644 --- a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp +++ b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp @@ -222,18 +222,6 @@ private: } }; -static bool loadSync(QWebEnginePage *page, const QUrl &url, bool ok = true) -{ - QSignalSpy spy(page, &QWebEnginePage::loadFinished); - page->load(url); - return (!spy.empty() || spy.wait(20000)) && (spy.front().value(0).toBool() == ok); -} - -static bool loadSync(QWebEngineView *view, const QUrl &url, bool ok = true) -{ - return loadSync(view->page(), url, ok); -} - void tst_QWebEngineProfile::clearDataFromCache() { TestServer server; @@ -258,7 +246,7 @@ void tst_QWebEngineProfile::clearDataFromCache() QTest::qWait(1000); QVERIFY(sizeBeforeClear > totalSize(cacheDir)); - QVERIFY(server.stop()); + (void)server.stop(); } void tst_QWebEngineProfile::disableCache() @@ -283,7 +271,7 @@ void tst_QWebEngineProfile::disableCache() QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); QVERIFY(cacheDir.exists("Cache")); - QVERIFY(server.stop()); + (void)server.stop(); } class RedirectingUrlSchemeHandler : public QWebEngineUrlSchemeHandler @@ -876,7 +864,7 @@ void tst_QWebEngineProfile::changePersistentPath() QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); QVERIFY(dataDir2.exists()); - QVERIFY(server.stop()); + (void)server.stop(); } void tst_QWebEngineProfile::changeHttpUserAgent() @@ -949,7 +937,7 @@ void tst_QWebEngineProfile::changeUseForGlobalCertificateVerification() page.reset(new QWebEnginePage(&profile)); QVERIFY(loadSync(page.get(), server.url("/hedgehog.html"))); // Don't check for error: there can be disconnects during GET hedgehog.png. - server.stop(); + (void)server.stop(); } void tst_QWebEngineProfile::changePersistentCookiesPolicy() @@ -973,7 +961,7 @@ void tst_QWebEngineProfile::changePersistentCookiesPolicy() QVERIFY(loadSync(&page, server.url("/hedgehog.html"))); QVERIFY(dataDir.exists("Cookies")); - QVERIFY(server.stop()); + (void)server.stop(); } class InitiatorSpy : public QWebEngineUrlSchemeHandler diff --git a/tests/auto/widgets/util.h b/tests/auto/widgets/util.h index ca03c5833..cb58f4243 100644 --- a/tests/auto/widgets/util.h +++ b/tests/auto/widgets/util.h @@ -36,6 +36,7 @@ #include <QSignalSpy> #include <QTimer> #include <qwebenginepage.h> +#include <qwebengineview.h> #if !defined(TESTS_SOURCE_DIR) #define TESTS_SOURCE_DIR "" @@ -160,6 +161,18 @@ static inline QUrl baseUrlSync(QWebEnginePage *page) return spy.waitForResult().toUrl(); } +static inline bool loadSync(QWebEnginePage *page, const QUrl &url, bool ok = true) +{ + QSignalSpy spy(page, &QWebEnginePage::loadFinished); + page->load(url); + return (!spy.empty() || spy.wait(20000)) && (spy.front().value(0).toBool() == ok); +} + +static inline bool loadSync(QWebEngineView *view, const QUrl &url, bool ok = true) +{ + return loadSync(view->page(), url, ok); +} + #define W_QSKIP(a, b) QSKIP(a) #define W_QTEST_MAIN(TestObject, params) \ diff --git a/tools/scripts/version_resolver.py b/tools/scripts/version_resolver.py index 20141e4b2..b3181db10 100644 --- a/tools/scripts/version_resolver.py +++ b/tools/scripts/version_resolver.py @@ -38,7 +38,7 @@ import json import urllib2 import git_submodule as GitSubmodule -chromium_version = '80.0.3987.136' +chromium_version = '80.0.3987.163' chromium_branch = '3987' ninja_version = 'v1.8.2' |