diff options
66 files changed, 940 insertions, 975 deletions
diff --git a/configure.json b/configure.json index cd8098c56..723730fb7 100644 --- a/configure.json +++ b/configure.json @@ -214,6 +214,7 @@ "webengine-host-compiler": { "label": "host compiler", "test": "hostcompiler", + "host": "true", "type": "compile" }, "webengine-khr": { diff --git a/dist/changes-5.11.0 b/dist/changes-5.11.0 new file mode 100644 index 000000000..cf721de9f --- /dev/null +++ b/dist/changes-5.11.0 @@ -0,0 +1,121 @@ +Qt 5.11 introduces many new features and improvements as well as bugfixes +over the 5.10.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.11 series is binary compatible with the 5.10.x series. +Applications compiled for 5.10 will continue to run with 5.11. + +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 * +**************************************************************************** + +Chromium Snapshot +----------------- + +- Updated the Chromium version to 65.0.3325.151. +- Applied security fixes from Chrome up to version 66.0.3359.139. + * Including: CVE-2018-6085, CVE-2018-6086, CVE-2018-6087, CVE-2018-6088, + CVE-2018-6089, CVE-2018-6090, CVE-2018-6091, CVE-2018-6092, + CVE-2018-6093, CVE-2018-6094, CVE-2018-6096, CVE-2018-6099, + CVE-2018-6100, CVE-2018-6101, CVE-2018-6102, CVE-2018-6103, + CVE-2018-6104, CVE-2018-6105, CVE-2018-6106, CVE-2018-6108, + CVE-2018-6110, CVE-2018-6111, CVE-2018-6115, CVE-2018-6116 and + CVE-2018-6118 + +Behavioral Changes +------------------ + +- [QTBUG-65484] User script metadata parsing now supports regular + expressions in @include and @exclude rules. +- Enabled chrome://quota-internals, chrome://taskscheduler-internals, + and on Linux, chrome://sandbox. +- [QTBUG-62414] Default context menus for Qt Quick and Qt Widgets got + a revamp and are now unified. + +Build System +------------ + +- [QTBUG-66596] QtWebEngine now requires Visual Studio 2017 on Windows. +- Building with -no-feature-draganddrop configure option got fixed. + +Command Line +------------ + +- Process per Site model is now supported via --process-per-site. +- There's now an option to hide internal IP addresses from WebRTC via + --force-webrtc-ip-handling-policy=default_public_interface_only. + +**************************************************************************** +* Libraries * +**************************************************************************** + +Deprecation Notice +------------------ + +- [QTBUG-62640] [Q]WebEngineDownloadItem::type got deprecated. Use newly + introduced [Q]WebEngineDownloadItem::isSavePageDownload instead. + +Qt WebEngineCore +---------------- + +- [QTBUG-62897] Added QWebEngineCookieStore::setCookieFilter() + to allow blocking cookie access. +- Added QWebEngineQuotaRequest, QWebEngineQuotaRequest classes. +- Added QWebEngineUrlRequestJob::initiator() accessor. + +Qt WebEngine (QML) +------------------ + +- Added WebEngineContextMenuRequest::editFlags, + WebEngineContextMenuRequest::mediaFlags properties to allow further tailoring + custom context menus. +- [QTBUG-57505] Added WebEngineSettings::webRTCPublicInterfacesOnly property + to enable hiding private IP addresses from WebRTC services. +- [QTBUG-64056] Added WebEngineSettings::javaScriptCanPaste property + to enable 'execCommand("paste")'. +- Added WebEngineSettings::playbackRequiresUserGesture property + to inhibit playback of media content until the user interacts with the page. +- Added WebEngineSettings::unknownUrlSchemePolicy property + to specify how navigation requests to URLs with unknown schemes should be + handled. +- [QTBUG-51181] Added WebEngineView.geometryChangeRequested signal. +- [QTBUG-47899, QTBUG-50725, QTBUG-50766] WebEngineView::inspectedView, + WebEngineView::devToolsView properties can be used to programmatically set up + a devtools page. +- Added WebEngineView::quotaRequested() signal to handle requests for bigger + file system quotas (navigator.webkitPersistentStorage.requestQuota). +- Added WebEngineView::registerProtocolHandlerRequested() signal to handle + requests from window.navigator.registerProtocolHandler API. + +Qt WebEngineWidgets +------------------- + +- Added QWebEngineContextMenuData::editFlags(), + QWebEngineContextMenuData::mediaFlags() properties to allow further tailoring + custom context menus. +- [QTBUG-57505] Added QWebEngineSettings::WebRTCPublicInterfacesOnly attribute + to enable hiding private IP addresses from WebRTC services. +- [QTBUG-64056] Added QWebEngineSettings::JavaScriptCanPaste attribute + to enable 'execCommand("paste")'. +- Added QWebEngineSettings::PlaybackRequiresUserGesture attribute + to inhibit playback of media content until the user interacts with the page. +- Added QWebEngineSettings::unknownUrlSchemePolicy property + to specify how navigation requests to URLs with unknown schemes should be + handled. +- [QTBUG-47899, QTBUG-50725, QTBUG-50766] QWebEnginePage::setInspectedPage(), + QWebEnginePage::setDevToolsPage() can be set to programmatically set up + a devtools page. +- Added QWebEnginePage::quotaRequested() signal to handle requests for bigger + file system quotas (navigator.webkitPersistentStorage.requestQuota). +- Added QWebEnginePage::registerProtocolHandlerRequested() signal to handle + requests from window.navigator.registerProtocolHandler API. diff --git a/examples/webenginewidgets/contentmanipulation/main.cpp b/examples/webenginewidgets/contentmanipulation/main.cpp index 5645f9b9a..e816079d2 100644 --- a/examples/webenginewidgets/contentmanipulation/main.cpp +++ b/examples/webenginewidgets/contentmanipulation/main.cpp @@ -63,6 +63,7 @@ int main(int argc, char * argv[]) else url = QUrl("http://www.google.com/ncr"); MainWindow *browser = new MainWindow(url); + browser->resize(1024, 768); browser->show(); return app.exec(); } diff --git a/examples/webenginewidgets/cookiebrowser/main.cpp b/examples/webenginewidgets/cookiebrowser/main.cpp index ae208c824..0ae5433ba 100644 --- a/examples/webenginewidgets/cookiebrowser/main.cpp +++ b/examples/webenginewidgets/cookiebrowser/main.cpp @@ -57,6 +57,7 @@ int main(int argc, char *argv[]) QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication app(argc, argv); MainWindow window(QUrl("http://qt.io")); + window.resize(1024, 768); window.show(); return app.exec(); } diff --git a/examples/webenginewidgets/maps/main.cpp b/examples/webenginewidgets/maps/main.cpp index f62518274..cad9c7ea9 100644 --- a/examples/webenginewidgets/maps/main.cpp +++ b/examples/webenginewidgets/maps/main.cpp @@ -57,6 +57,7 @@ int main(int argc, char *argv[]) QApplication app(argc, argv); MainWindow mainWindow; + mainWindow.resize(1024, 768); mainWindow.show(); return app.exec(); diff --git a/examples/webenginewidgets/markdowneditor/resources/index.html b/examples/webenginewidgets/markdowneditor/resources/index.html index 8623a0642..5fa28fa17 100644 --- a/examples/webenginewidgets/markdowneditor/resources/index.html +++ b/examples/webenginewidgets/markdowneditor/resources/index.html @@ -4,7 +4,7 @@ <head> <link rel="stylesheet" type="text/css" href="3rdparty/markdown.css"> <script src="3rdparty/marked.min.js"></script> - <script src="qwebchannel.js"></script> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> </head> <body> <div id="placeholder"></div> diff --git a/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc b/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc index 9a6bd3801..812c8850a 100644 --- a/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc +++ b/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc @@ -1,7 +1,6 @@ <RCC> <qresource prefix="/"> <file>index.html</file> - <file>qwebchannel.js</file> <file>3rdparty/marked.min.js</file> <file>default.md</file> <file>3rdparty/markdown.css</file> diff --git a/examples/webenginewidgets/markdowneditor/resources/qwebchannel.js b/examples/webenginewidgets/markdowneditor/resources/qwebchannel.js deleted file mode 100644 index 8ebfbb1c9..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/qwebchannel.js +++ /dev/null @@ -1,430 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebChannel module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** 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. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -"use strict"; - -var QWebChannelMessageTypes = { - signal: 1, - propertyUpdate: 2, - init: 3, - idle: 4, - debug: 5, - invokeMethod: 6, - connectToSignal: 7, - disconnectFromSignal: 8, - setProperty: 9, - response: 10, -}; - -var QWebChannel = function(transport, initCallback) -{ - if (typeof transport !== "object" || typeof transport.send !== "function") { - console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." + - " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send)); - return; - } - - var channel = this; - this.transport = transport; - - this.send = function(data) - { - if (typeof(data) !== "string") { - data = JSON.stringify(data); - } - channel.transport.send(data); - } - - this.transport.onmessage = function(message) - { - var data = message.data; - if (typeof data === "string") { - data = JSON.parse(data); - } - switch (data.type) { - case QWebChannelMessageTypes.signal: - channel.handleSignal(data); - break; - case QWebChannelMessageTypes.response: - channel.handleResponse(data); - break; - case QWebChannelMessageTypes.propertyUpdate: - channel.handlePropertyUpdate(data); - break; - default: - console.error("invalid message received:", message.data); - break; - } - } - - this.execCallbacks = {}; - this.execId = 0; - this.exec = function(data, callback) - { - if (!callback) { - // if no callback is given, send directly - channel.send(data); - return; - } - if (channel.execId === Number.MAX_VALUE) { - // wrap - channel.execId = Number.MIN_VALUE; - } - if (data.hasOwnProperty("id")) { - console.error("Cannot exec message with property id: " + JSON.stringify(data)); - return; - } - data.id = channel.execId++; - channel.execCallbacks[data.id] = callback; - channel.send(data); - }; - - this.objects = {}; - - this.handleSignal = function(message) - { - var object = channel.objects[message.object]; - if (object) { - object.signalEmitted(message.signal, message.args); - } else { - console.warn("Unhandled signal: " + message.object + "::" + message.signal); - } - } - - this.handleResponse = function(message) - { - if (!message.hasOwnProperty("id")) { - console.error("Invalid response message received: ", JSON.stringify(message)); - return; - } - channel.execCallbacks[message.id](message.data); - delete channel.execCallbacks[message.id]; - } - - this.handlePropertyUpdate = function(message) - { - for (var i in message.data) { - var data = message.data[i]; - var object = channel.objects[data.object]; - if (object) { - object.propertyUpdate(data.signals, data.properties); - } else { - console.warn("Unhandled property update: " + data.object + "::" + data.signal); - } - } - channel.exec({type: QWebChannelMessageTypes.idle}); - } - - this.debug = function(message) - { - channel.send({type: QWebChannelMessageTypes.debug, data: message}); - }; - - channel.exec({type: QWebChannelMessageTypes.init}, function(data) { - for (var objectName in data) { - var object = new QObject(objectName, data[objectName], channel); - } - // now unwrap properties, which might reference other registered objects - for (var objectName in channel.objects) { - channel.objects[objectName].unwrapProperties(); - } - if (initCallback) { - initCallback(channel); - } - channel.exec({type: QWebChannelMessageTypes.idle}); - }); -}; - -function QObject(name, data, webChannel) -{ - this.__id__ = name; - webChannel.objects[name] = this; - - // List of callbacks that get invoked upon signal emission - this.__objectSignals__ = {}; - - // Cache of all properties, updated when a notify signal is emitted - this.__propertyCache__ = {}; - - var object = this; - - // ---------------------------------------------------------------------- - - this.unwrapQObject = function(response) - { - if (response instanceof Array) { - // support list of objects - var ret = new Array(response.length); - for (var i = 0; i < response.length; ++i) { - ret[i] = object.unwrapQObject(response[i]); - } - return ret; - } - if (!response - || !response["__QObject*__"] - || response.id === undefined) { - return response; - } - - var objectId = response.id; - if (webChannel.objects[objectId]) - return webChannel.objects[objectId]; - - if (!response.data) { - console.error("Cannot unwrap unknown QObject " + objectId + " without data."); - return; - } - - var qObject = new QObject( objectId, response.data, webChannel ); - qObject.destroyed.connect(function() { - if (webChannel.objects[objectId] === qObject) { - delete webChannel.objects[objectId]; - // reset the now deleted QObject to an empty {} object - // just assigning {} though would not have the desired effect, but the - // below also ensures all external references will see the empty map - // NOTE: this detour is necessary to workaround QTBUG-40021 - var propertyNames = []; - for (var propertyName in qObject) { - propertyNames.push(propertyName); - } - for (var idx in propertyNames) { - delete qObject[propertyNames[idx]]; - } - } - }); - // here we are already initialized, and thus must directly unwrap the properties - qObject.unwrapProperties(); - return qObject; - } - - this.unwrapProperties = function() - { - for (var propertyIdx in object.__propertyCache__) { - object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]); - } - } - - function addSignal(signalData, isPropertyNotifySignal) - { - var signalName = signalData[0]; - var signalIndex = signalData[1]; - object[signalName] = { - connect: function(callback) { - if (typeof(callback) !== "function") { - console.error("Bad callback given to connect to signal " + signalName); - return; - } - - object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; - object.__objectSignals__[signalIndex].push(callback); - - if (!isPropertyNotifySignal && signalName !== "destroyed") { - // only required for "pure" signals, handled separately for properties in propertyUpdate - // also note that we always get notified about the destroyed signal - webChannel.exec({ - type: QWebChannelMessageTypes.connectToSignal, - object: object.__id__, - signal: signalIndex - }); - } - }, - disconnect: function(callback) { - if (typeof(callback) !== "function") { - console.error("Bad callback given to disconnect from signal " + signalName); - return; - } - object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; - var idx = object.__objectSignals__[signalIndex].indexOf(callback); - if (idx === -1) { - console.error("Cannot find connection of signal " + signalName + " to " + callback.name); - return; - } - object.__objectSignals__[signalIndex].splice(idx, 1); - if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) { - // only required for "pure" signals, handled separately for properties in propertyUpdate - webChannel.exec({ - type: QWebChannelMessageTypes.disconnectFromSignal, - object: object.__id__, - signal: signalIndex - }); - } - } - }; - } - - /** - * Invokes all callbacks for the given signalname. Also works for property notify callbacks. - */ - function invokeSignalCallbacks(signalName, signalArgs) - { - var connections = object.__objectSignals__[signalName]; - if (connections) { - connections.forEach(function(callback) { - callback.apply(callback, signalArgs); - }); - } - } - - this.propertyUpdate = function(signals, propertyMap) - { - // update property cache - for (var propertyIndex in propertyMap) { - var propertyValue = propertyMap[propertyIndex]; - object.__propertyCache__[propertyIndex] = propertyValue; - } - - for (var signalName in signals) { - // Invoke all callbacks, as signalEmitted() does not. This ensures the - // property cache is updated before the callbacks are invoked. - invokeSignalCallbacks(signalName, signals[signalName]); - } - } - - this.signalEmitted = function(signalName, signalArgs) - { - invokeSignalCallbacks(signalName, signalArgs); - } - - function addMethod(methodData) - { - var methodName = methodData[0]; - var methodIdx = methodData[1]; - object[methodName] = function() { - var args = []; - var callback; - for (var i = 0; i < arguments.length; ++i) { - if (typeof arguments[i] === "function") - callback = arguments[i]; - else - args.push(arguments[i]); - } - - webChannel.exec({ - "type": QWebChannelMessageTypes.invokeMethod, - "object": object.__id__, - "method": methodIdx, - "args": args - }, function(response) { - if (response !== undefined) { - var result = object.unwrapQObject(response); - if (callback) { - (callback)(result); - } - } - }); - }; - } - - function bindGetterSetter(propertyInfo) - { - var propertyIndex = propertyInfo[0]; - var propertyName = propertyInfo[1]; - var notifySignalData = propertyInfo[2]; - // initialize property cache with current value - // NOTE: if this is an object, it is not directly unwrapped as it might - // reference other QObject that we do not know yet - object.__propertyCache__[propertyIndex] = propertyInfo[3]; - - if (notifySignalData) { - if (notifySignalData[0] === 1) { - // signal name is optimized away, reconstruct the actual name - notifySignalData[0] = propertyName + "Changed"; - } - addSignal(notifySignalData, true); - } - - Object.defineProperty(object, propertyName, { - configurable: true, - get: function () { - var propertyValue = object.__propertyCache__[propertyIndex]; - if (propertyValue === undefined) { - // This shouldn't happen - console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__); - } - - return propertyValue; - }, - set: function(value) { - if (value === undefined) { - console.warn("Property setter for " + propertyName + " called with undefined value!"); - return; - } - object.__propertyCache__[propertyIndex] = value; - webChannel.exec({ - "type": QWebChannelMessageTypes.setProperty, - "object": object.__id__, - "property": propertyIndex, - "value": value - }); - } - }); - - } - - // ---------------------------------------------------------------------- - - data.methods.forEach(addMethod); - - data.properties.forEach(bindGetterSetter); - - data.signals.forEach(function(signal) { addSignal(signal, false); }); - - for (var name in data.enums) { - object[name] = data.enums[name]; - } -} - -//required for use with nodejs -if (typeof module === 'object') { - module.exports = { - QWebChannel: QWebChannel - }; -} diff --git a/examples/webenginewidgets/simplebrowser/main.cpp b/examples/webenginewidgets/simplebrowser/main.cpp index 9b9bf80b5..96b1eab97 100644 --- a/examples/webenginewidgets/simplebrowser/main.cpp +++ b/examples/webenginewidgets/simplebrowser/main.cpp @@ -50,7 +50,7 @@ #include "browser.h" #include "browserwindow.h" -#include "webview.h" +#include "tabwidget.h" #include <QApplication> #include <QWebEngineSettings> @@ -78,7 +78,7 @@ int main(int argc, char **argv) Browser browser; BrowserWindow *window = browser.createWindow(); - window->currentTab()->setUrl(url); + window->tabWidget()->setUrl(url); return app.exec(); } diff --git a/examples/webenginewidgets/stylesheetbrowser/main.cpp b/examples/webenginewidgets/stylesheetbrowser/main.cpp index 957913f5a..54fce0ce3 100644 --- a/examples/webenginewidgets/stylesheetbrowser/main.cpp +++ b/examples/webenginewidgets/stylesheetbrowser/main.cpp @@ -49,6 +49,7 @@ ****************************************************************************/ #include "mainwindow.h" +#include "stylesheetdialog.h" #include <QApplication> #include <QUrl> @@ -58,6 +59,8 @@ int main(int argc, char *argv[]) QCoreApplication::setOrganizationDomain("www.qt.io"); QCoreApplication::setApplicationName("StyleSheet Browser"); + qRegisterMetaTypeStreamOperators<StyleSheet>("StyleSheet"); + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication a(argc, argv); MainWindow w(QUrl("http://qt.io")); diff --git a/examples/webenginewidgets/stylesheetbrowser/mainwindow.cpp b/examples/webenginewidgets/stylesheetbrowser/mainwindow.cpp index f7e6964cb..c93205b18 100644 --- a/examples/webenginewidgets/stylesheetbrowser/mainwindow.cpp +++ b/examples/webenginewidgets/stylesheetbrowser/mainwindow.cpp @@ -49,9 +49,12 @@ ****************************************************************************/ #include "mainwindow.h" +#include "stylesheetdialog.h" #include "ui_mainwindow.h" -#include "stylesheetdialog.h" +static QMap<QString, QString> defaultStyleSheets = { + {"Upside down", "body { -webkit-transform: rotate(180deg); }"} +}; MainWindow::MainWindow(const QUrl &url) : QMainWindow(), @@ -67,8 +70,16 @@ MainWindow::MainWindow(const QUrl &url) : QSettings settings; settings.beginGroup("styleSheets"); QStringList styleSheets = settings.allKeys(); - for (auto name : qAsConst(styleSheets)) - insertStyleSheet(name, settings.value(name, QString()).toString(), false); + if (styleSheets.empty()) { + // Add back default style sheets if the user cleared them out + loadDefaultStyleSheets(); + } else { + for (auto name : qAsConst(styleSheets)) { + StyleSheet styleSheet = settings.value(name).value<StyleSheet>(); + if (styleSheet.second) + insertStyleSheet(name, styleSheet.first, false); + } + } settings.endGroup(); ui->webEngineView->setUrl(url); @@ -114,6 +125,27 @@ void MainWindow::removeStyleSheet(const QString &name, bool immediately) ui->webEngineView->page()->scripts().remove(script); } +bool MainWindow::hasStyleSheet(const QString &name) +{ + QWebEngineScript script = ui->webEngineView->page()->scripts().findScript(name); + return !script.isNull(); +} + +void MainWindow::loadDefaultStyleSheets() +{ + QSettings settings; + settings.beginGroup("styleSheets"); + + auto it = defaultStyleSheets.constBegin(); + while (it != defaultStyleSheets.constEnd()) { + settings.setValue(it.key(), QVariant::fromValue(qMakePair(it.value(), true))); + insertStyleSheet(it.key(), it.value(), false); + ++it; + } + + settings.endGroup(); +} + void MainWindow::urlEntered() { ui->webEngineView->setUrl(QUrl::fromUserInput(ui->urlBar->text())); diff --git a/examples/webenginewidgets/stylesheetbrowser/mainwindow.h b/examples/webenginewidgets/stylesheetbrowser/mainwindow.h index fb0c8e7be..c39735af8 100644 --- a/examples/webenginewidgets/stylesheetbrowser/mainwindow.h +++ b/examples/webenginewidgets/stylesheetbrowser/mainwindow.h @@ -71,6 +71,8 @@ public: void insertStyleSheet(const QString &name, const QString &source, bool immediately); void removeStyleSheet(const QString &name, bool immediately); + bool hasStyleSheet(const QString &name); + void loadDefaultStyleSheets(); private slots: void urlEntered(); diff --git a/examples/webenginewidgets/stylesheetbrowser/mainwindow.ui b/examples/webenginewidgets/stylesheetbrowser/mainwindow.ui index f9c1af969..bc68c16bb 100644 --- a/examples/webenginewidgets/stylesheetbrowser/mainwindow.ui +++ b/examples/webenginewidgets/stylesheetbrowser/mainwindow.ui @@ -18,11 +18,47 @@ </property> <widget class="QWidget" name="centralWidget"> <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> <item> <widget class="QWidget" name="webContentsWidget" native="true"> <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> <item> <widget class="QWidget" name="urlBarWidget" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> <widget class="QLineEdit" name="urlBar"/> @@ -32,13 +68,13 @@ <property name="text"> <string/> </property> - <property name="shortcut"> - <string>Ctrl+R</string> - </property> <property name="icon"> <iconset resource="stylesheetbrowser.qrc"> <normaloff>:/view-refresh.png</normaloff>:/view-refresh.png</iconset> </property> + <property name="shortcut"> + <string>Ctrl+R</string> + </property> </widget> </item> <item> @@ -52,8 +88,14 @@ </widget> </item> <item> - <widget class="QWebEngineView" name="webEngineView"> - <property name="url"> + <widget class="QWebEngineView" name="webEngineView" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="url" stdset="0"> <url> <string>about:blank</string> </url> @@ -84,6 +126,8 @@ <header location="global">QtWebEngineWidgets/QWebEngineView</header> </customwidget> </customwidgets> - <resources/> + <resources> + <include location="stylesheetbrowser.qrc"/> + </resources> <connections/> </ui> diff --git a/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.cpp b/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.cpp index eb8abe135..7351ab75f 100644 --- a/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.cpp +++ b/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.cpp @@ -59,6 +59,7 @@ StylesheetDialog::StylesheetDialog(QWidget *parent) : ui->setupUi(this); connect(ui->styleSheetList, &QListWidget::currentItemChanged, this, &StylesheetDialog::currentStyleSheetChanged); + connect(ui->styleSheetList, &QListWidget::itemClicked, this, &StylesheetDialog::listItemClicked); connect(ui->fileNameEdit, &QLineEdit::textChanged, this, &StylesheetDialog::fileNameChanged); connect(ui->addButton, &QPushButton::clicked, this, &StylesheetDialog::addButtonClicked); @@ -66,8 +67,12 @@ StylesheetDialog::StylesheetDialog(QWidget *parent) : QSettings settings; settings.beginGroup("styleSheets"); - for (auto name : settings.allKeys()) - new QListWidgetItem(name, ui->styleSheetList); + for (auto name : settings.allKeys()) { + QListWidgetItem *listItem = new QListWidgetItem(name, ui->styleSheetList); + listItem->setFlags(listItem->flags() | Qt::ItemIsUserCheckable); + bool checked = settings.value(name).value<StyleSheet>().second; + listItem->setCheckState(checked ? Qt::Checked : Qt::Unchecked); + } settings.endGroup(); } @@ -92,7 +97,33 @@ void StylesheetDialog::currentStyleSheetChanged(QListWidgetItem *current, QListW QSettings settings; settings.beginGroup("styleSheets"); ui->fileNameEdit->setText(current->text()); - ui->sourceCodeEdit->setPlainText(settings.value(current->text(), QString()).toString()); + const QString source = settings.value(current->text()).value<StyleSheet>().first; + ui->sourceCodeEdit->setPlainText(source); + settings.endGroup(); +} + +void StylesheetDialog::listItemClicked(QListWidgetItem *item) +{ + MainWindow *window = static_cast<MainWindow *>(parent()); + const QString name = item->text(); + bool checkedStateChanged = + (item->checkState() == Qt::Checked && !window->hasStyleSheet(name)) || + (item->checkState() == Qt::Unchecked && window->hasStyleSheet(name)); + if (!checkedStateChanged) + return; + + QSettings settings; + settings.beginGroup("styleSheets"); + const QString source = settings.value(name).value<StyleSheet>().first; + + if (item->checkState() == Qt::Checked) { + settings.setValue(name, QVariant::fromValue(qMakePair(source, true))); + window->insertStyleSheet(name, source, true); + } else { + settings.setValue(name, QVariant::fromValue(qMakePair(source, false))); + window->removeStyleSheet(name, true); + } + settings.endGroup(); } @@ -107,16 +138,21 @@ void StylesheetDialog::fileNameChanged(const QString &text) void StylesheetDialog::addButtonClicked() { - new QListWidgetItem(ui->fileNameEdit->text(), ui->styleSheetList); - - MainWindow *window = static_cast<MainWindow *>(parent()); const QString name = ui->fileNameEdit->text(); const QString source = ui->sourceCodeEdit->toPlainText(); + if (name.isEmpty() || source.isEmpty()) + return; + + QListWidgetItem *listItem = new QListWidgetItem(ui->fileNameEdit->text(), ui->styleSheetList); + listItem->setFlags(listItem->flags() | Qt::ItemIsUserCheckable); + listItem->setCheckState(Qt::Checked); + + MainWindow *window = static_cast<MainWindow *>(parent()); window->insertStyleSheet(name, source, true); QSettings settings; settings.beginGroup("styleSheets"); - settings.setValue(name, source); + settings.setValue(name, QVariant::fromValue(qMakePair(source, true))); settings.endGroup(); ui->addButton->setEnabled(false); diff --git a/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.h b/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.h index f65cfe666..3bf3219ca 100644 --- a/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.h +++ b/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.h @@ -60,6 +60,9 @@ class StylesheetDialog; } QT_END_NAMESPACE +typedef QPair<QString, bool> StyleSheet; // <source, isEnabled> +Q_DECLARE_METATYPE(StyleSheet); + class StylesheetDialog : public QDialog { Q_OBJECT @@ -70,6 +73,7 @@ public: private slots: void currentStyleSheetChanged(QListWidgetItem *current, QListWidgetItem *previous); + void listItemClicked(QListWidgetItem *item); void fileNameChanged(const QString &text); void addButtonClicked(); diff --git a/examples/webenginewidgets/videoplayer/main.cpp b/examples/webenginewidgets/videoplayer/main.cpp index f62518274..cad9c7ea9 100644 --- a/examples/webenginewidgets/videoplayer/main.cpp +++ b/examples/webenginewidgets/videoplayer/main.cpp @@ -57,6 +57,7 @@ int main(int argc, char *argv[]) QApplication app(argc, argv); MainWindow mainWindow; + mainWindow.resize(1024, 768); mainWindow.show(); return app.exec(); diff --git a/src/core/api/qwebenginecookiestore.cpp b/src/core/api/qwebenginecookiestore.cpp index ee82093a5..5fcd46064 100644 --- a/src/core/api/qwebenginecookiestore.cpp +++ b/src/core/api/qwebenginecookiestore.cpp @@ -200,7 +200,7 @@ bool QWebEngineCookieStorePrivate::canAccessCookies(const QUrl &firstPartyUrl, c toGurl(firstPartyUrl), net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); - QWebEngineCookieStore::FilterRequest request = { thirdParty, firstPartyUrl, url }; + QWebEngineCookieStore::FilterRequest request = { firstPartyUrl, url, thirdParty, false, 0}; return filterCallback(request); } @@ -400,6 +400,16 @@ void QWebEngineCookieStore::setCookieFilter(std::function<bool(const FilterReque */ /*! + \variable QWebEngineCookieStore::FilterRequest::_reservedFlag + \internal +*/ + +/*! + \variable QWebEngineCookieStore::FilterRequest::_reservedType + \internal +*/ + +/*! \variable QWebEngineCookieStore::FilterRequest::origin \brief The URL of the script or content accessing a cookie. diff --git a/src/core/api/qwebenginecookiestore.h b/src/core/api/qwebenginecookiestore.h index a62765f77..87d7390a3 100644 --- a/src/core/api/qwebenginecookiestore.h +++ b/src/core/api/qwebenginecookiestore.h @@ -62,9 +62,11 @@ class QWEBENGINE_EXPORT QWebEngineCookieStore : public QObject { public: struct FilterRequest { - bool thirdParty; QUrl firstPartyUrl; QUrl origin; + bool thirdParty; + bool _reservedFlag; + ushort _reservedType; }; virtual ~QWebEngineCookieStore(); diff --git a/src/core/content_browser_client_qt.cpp b/src/core/content_browser_client_qt.cpp index 6748fb956..aa13cce3a 100644 --- a/src/core/content_browser_client_qt.cpp +++ b/src/core/content_browser_client_qt.cpp @@ -53,6 +53,7 @@ #include "content/browser/renderer_host/render_view_host_delegate.h" #include "content/common/url_schemes.h" #include "content/public/browser/browser_main_parts.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_security_policy.h" #include "content/public/browser/client_certificate_delegate.h" #include "content/public/browser/media_observer.h" @@ -67,6 +68,7 @@ #include "content/public/common/main_function_params.h" #include "content/public/common/service_names.mojom.h" #include "content/public/common/url_constants.h" +#include "device/geolocation/public/cpp/location_provider.h" #include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/binding_set.h" #include "printing/features/features.h" @@ -121,6 +123,10 @@ #include "renderer_host/pepper/pepper_host_factory_qt.h" #endif +#if defined(QT_USE_POSITIONING) +#include "location_provider_qt.h" +#endif + #include <QGuiApplication> #include <QLocale> #ifndef QT_NO_OPENGL @@ -723,6 +729,30 @@ bool ContentBrowserClientQt::CanCreateWindow( return (settings && settings->getJavaScriptCanOpenWindowsAutomatically()) || user_gesture; } +std::unique_ptr<device::LocationProvider> ContentBrowserClientQt::OverrideSystemLocationProvider() +{ +#if defined(QT_USE_POSITIONING) + return base::WrapUnique(new LocationProviderQt()); +#else + return nullptr; +#endif +} + +scoped_refptr<net::URLRequestContextGetter> GetSystemRequestContextOnUIThread() +{ + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + return scoped_refptr<net::URLRequestContextGetter>( + BrowserContextAdapter::defaultContext()->browserContext()->GetRequestContext()); +} + +void ContentBrowserClientQt::GetGeolocationRequestContext( + base::OnceCallback<void(scoped_refptr<net::URLRequestContextGetter>)> callback) +{ + content::BrowserThread::PostTaskAndReplyWithResult( + content::BrowserThread::UI, FROM_HERE, + base::BindOnce(&GetSystemRequestContextOnUIThread), std::move(callback)); +} + bool ContentBrowserClientQt::AllowGetCookie(const GURL &url, const GURL &first_party, const net::CookieList & /*cookie_list*/, diff --git a/src/core/content_browser_client_qt.h b/src/core/content_browser_client_qt.h index 042de5c8c..515574147 100644 --- a/src/core/content_browser_client_qt.h +++ b/src/core/content_browser_client_qt.h @@ -168,6 +168,8 @@ public: content::ResourceContext *context, const std::vector<std::pair<int, int> > &render_frames) override; + std::unique_ptr<device::LocationProvider> OverrideSystemLocationProvider() override; + void GetGeolocationRequestContext(base::OnceCallback<void(scoped_refptr<net::URLRequestContextGetter>)> callback) override; #if defined(Q_OS_LINUX) void GetAdditionalMappedFilesForChildProcess(const base::CommandLine& command_line, int child_process_id, content::PosixFileDescriptorInfo* mappings) override; #endif diff --git a/src/core/content_client_qt.cpp b/src/core/content_client_qt.cpp index c1683bfa0..37b601bf8 100644 --- a/src/core/content_client_qt.cpp +++ b/src/core/content_client_qt.cpp @@ -276,6 +276,7 @@ void AddPepperWidevine(std::vector<content::PepperPluginInfo>* plugins) widevine_cdm.mime_types.push_back(widevine_cdm_mime_type); widevine_cdm.permissions = kWidevineCdmPluginPermissions; plugins->push_back(widevine_cdm); + break; } } #endif // defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_PEPPER_CDMS) && diff --git a/src/core/gl_surface_qt.cpp b/src/core/gl_surface_qt.cpp index 8bfbd865c..0d143ee18 100644 --- a/src/core/gl_surface_qt.cpp +++ b/src/core/gl_surface_qt.cpp @@ -72,6 +72,14 @@ #include "ozone/gl_surface_glx_qt.h" #include "ui/gl/gl_glx_api_implementation.h" #include <dlfcn.h> + +#ifndef QT_NO_OPENGL +#include <QOpenGLContext> +QT_BEGIN_NAMESPACE +Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); +QT_END_NAMESPACE +#endif + #endif #include "ozone/gl_surface_egl_qt.h" @@ -195,10 +203,20 @@ bool InitializeStaticGLBindings(GLImplementation implementation) { reinterpret_cast<GLGetProcAddressProc>( base::GetFunctionPointerFromNativeLibrary(library, "glXGetProcAddress")); + +#ifndef QT_NO_OPENGL if (!get_proc_address) { - LOG(ERROR) << "glxGetProcAddress not found."; - base::UnloadNativeLibrary(library); - return false; + // glx handle not loaded , fallback to qpa + if (QOpenGLContext *context = qt_gl_global_share_context()) { + get_proc_address = reinterpret_cast<gl::GLGetProcAddressProc>( + context->getProcAddress("glXGetProcAddress")); + } + } +#endif + if (!get_proc_address) { + LOG(ERROR) << "glxGetProcAddress not found."; + base::UnloadNativeLibrary(library); + return false; } SetGLGetProcAddressProc(get_proc_address); diff --git a/src/core/printing/pdfium_document_wrapper_qt.cpp b/src/core/printing/pdfium_document_wrapper_qt.cpp index ca1e8cd07..a7433b2cc 100644 --- a/src/core/printing/pdfium_document_wrapper_qt.cpp +++ b/src/core/printing/pdfium_document_wrapper_qt.cpp @@ -37,11 +37,8 @@ ** ****************************************************************************/ #include "pdf/features.h" -#if BUILDFLAG(ENABLE_PDF) -#define ENABLE_PDF -#endif -#if defined(ENABLE_PDF) +#if BUILDFLAG(ENABLE_PDF) #include "pdfium_document_wrapper_qt.h" #include <QtCore/qhash.h> @@ -168,4 +165,4 @@ PdfiumDocumentWrapperQt::~PdfiumDocumentWrapperQt() } } -#endif // defined (ENABLE_PDF) +#endif // BUILDFLAG(ENABLE_PDF) diff --git a/src/core/printing/pdfium_document_wrapper_qt.h b/src/core/printing/pdfium_document_wrapper_qt.h index 28c490ae5..7886c51c0 100644 --- a/src/core/printing/pdfium_document_wrapper_qt.h +++ b/src/core/printing/pdfium_document_wrapper_qt.h @@ -40,11 +40,8 @@ #ifndef PDFIUM_DOCUMENT_WRAPPER_QT_H #define PDFIUM_DOCUMENT_WRAPPER_QT_H -#if defined(ENABLE_PDF) #include "qtwebenginecoreglobal.h" -#include <QtCore/qglobal.h> -#include <QtCore/qhash.h> #include <QtGui/qimage.h> namespace QtWebEngineCore { @@ -67,5 +64,4 @@ private: }; } // namespace QtWebEngineCore -#endif // defined (ENABLE_PDF) #endif // PDFIUM_DOCUMENT_WRAPPER_QT_H diff --git a/src/core/quota_request_controller_impl.cpp b/src/core/quota_request_controller_impl.cpp index ee94e1cdd..a18ad761d 100644 --- a/src/core/quota_request_controller_impl.cpp +++ b/src/core/quota_request_controller_impl.cpp @@ -54,6 +54,11 @@ QuotaRequestControllerImpl::QuotaRequestControllerImpl( , m_callback(callback) {} +QuotaRequestControllerImpl::~QuotaRequestControllerImpl() +{ + reject(); +} + void QuotaRequestControllerImpl::accepted() { m_context->dispatchCallbackOnIOThread(m_callback, QuotaPermissionContextQt::QUOTA_PERMISSION_RESPONSE_ALLOW); diff --git a/src/core/quota_request_controller_impl.h b/src/core/quota_request_controller_impl.h index dacdce72f..5814895f3 100644 --- a/src/core/quota_request_controller_impl.h +++ b/src/core/quota_request_controller_impl.h @@ -52,6 +52,8 @@ public: const content::StorageQuotaParams ¶ms, const content::QuotaPermissionContext::PermissionCallback &callback); + ~QuotaRequestControllerImpl(); + protected: void accepted() override; void rejected() override; diff --git a/src/core/register_protocol_handler_request_controller_impl.cpp b/src/core/register_protocol_handler_request_controller_impl.cpp index 1e3a15c93..0f24d8812 100644 --- a/src/core/register_protocol_handler_request_controller_impl.cpp +++ b/src/core/register_protocol_handler_request_controller_impl.cpp @@ -54,6 +54,11 @@ RegisterProtocolHandlerRequestControllerImpl::RegisterProtocolHandlerRequestCont , m_handler(handler) {} +RegisterProtocolHandlerRequestControllerImpl::~RegisterProtocolHandlerRequestControllerImpl() +{ + reject(); +} + ProtocolHandlerRegistry *RegisterProtocolHandlerRequestControllerImpl::protocolHandlerRegistry() { content::WebContents *webContents = web_contents(); diff --git a/src/core/register_protocol_handler_request_controller_impl.h b/src/core/register_protocol_handler_request_controller_impl.h index 5ad64210c..64f229ac4 100644 --- a/src/core/register_protocol_handler_request_controller_impl.h +++ b/src/core/register_protocol_handler_request_controller_impl.h @@ -57,6 +57,8 @@ public: content::WebContents *webContents, ProtocolHandler handler); + ~RegisterProtocolHandlerRequestControllerImpl(); + protected: void accepted() override; void rejected() override; diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp index 66d9b819e..ec3add2f6 100644 --- a/src/core/render_widget_host_view_qt.cpp +++ b/src/core/render_widget_host_view_qt.cpp @@ -327,7 +327,7 @@ RenderWidgetHostViewQt::RenderWidgetHostViewQt(content::RenderWidgetHost *widget , m_adapterClient(0) , m_rendererCompositorFrameSink(0) , m_imeInProgress(false) - , m_receivedEmptyImeText(false) + , m_receivedEmptyImeEvent(false) , m_initPending(false) , m_beginFrameSource(nullptr) , m_needsBeginFrames(false) @@ -1219,22 +1219,20 @@ void RenderWidgetHostViewQt::handleKeyEvent(QKeyEvent *ev) if (IsMouseLocked() && ev->key() == Qt::Key_Escape && ev->type() == QEvent::KeyRelease) UnlockMouse(); - if (m_receivedEmptyImeText) { + if (m_receivedEmptyImeEvent) { // IME composition was not finished with a valid commit string. // We're getting the composition result in a key event. if (ev->key() != 0) { // The key event is not a result of an IME composition. Cancel IME. m_host->ImeCancelComposition(); - m_receivedEmptyImeText = false; + m_receivedEmptyImeEvent = false; } else { if (ev->type() == QEvent::KeyRelease) { - m_receivedEmptyImeText = false; - m_host->ImeSetComposition(toString16(ev->text()), - std::vector<ui::ImeTextSpan>(), - gfx::Range::InvalidRange(), - gfx::Range::InvalidRange().start(), - gfx::Range::InvalidRange().end()); - m_host->ImeFinishComposingText(false); + m_host->ImeCommitText(toString16(ev->text()), + std::vector<ui::ImeTextSpan>(), + gfx::Range::InvalidRange(), + 0); + m_receivedEmptyImeEvent = false; m_imeInProgress = false; } return; @@ -1365,54 +1363,54 @@ void RenderWidgetHostViewQt::handleInputMethodEvent(QInputMethodEvent *ev) } } - auto setCompositionString = [&](const QString &compositionString){ - m_host->ImeSetComposition(toString16(compositionString), - underlines, - replacementRange, - selectionRange.start(), - selectionRange.end()); - }; - - if (!commitString.isEmpty() || replacementLength > 0) { - setCompositionString(commitString); - m_host->ImeFinishComposingText(false); - - // We might get a commit string and a pre-edit string in a single event, which means - // we need to confirm the last composition, and start a new composition. - if (!preeditString.isEmpty()) { - setCompositionString(preeditString); - m_imeInProgress = true; - } else { - m_imeInProgress = false; - } - m_receivedEmptyImeText = commitString.isEmpty(); - } else if (!preeditString.isEmpty()) { - setCompositionString(preeditString); - m_imeInProgress = true; - m_receivedEmptyImeText = false; - } else { - // There are so-far two known cases, when an empty QInputMethodEvent is received. - // First one happens when backspace is used to remove the last character in the pre-edit - // string, thus signaling the end of the composition. - // The second one happens (on Windows) when a Korean char gets composed, but instead of - // the event having a commit string, both strings are empty, and the actual char is received - // as a QKeyEvent after the QInputMethodEvent is processed. - // In lieu of the second case, we can't simply cancel the composition on an empty event, - // and then add the Korean char when QKeyEvent is received, because that leads to text - // flickering in the textarea (or any other element). - // Instead we postpone the processing of the empty QInputMethodEvent by posting it - // to the same focused object, and cancelling the composition on the next event loop tick. - if (!m_receivedEmptyImeText && m_imeInProgress && !hasSelection) { - m_receivedEmptyImeText = true; + // There are so-far two known cases, when an empty QInputMethodEvent is received. + // First one happens when backspace is used to remove the last character in the pre-edit + // string, thus signaling the end of the composition. + // The second one happens (on Windows) when a Korean char gets composed, but instead of + // the event having a commit string, both strings are empty, and the actual char is received + // as a QKeyEvent after the QInputMethodEvent is processed. + // In lieu of the second case, we can't simply cancel the composition on an empty event, + // and then add the Korean char when QKeyEvent is received, because that leads to text + // flickering in the textarea (or any other element). + // Instead we postpone the processing of the empty QInputMethodEvent by posting it + // to the same focused object, and cancelling the composition on the next event loop tick. + if (commitString.isEmpty() && preeditString.isEmpty() && replacementLength == 0) { + if (!m_receivedEmptyImeEvent && m_imeInProgress && !hasSelection) { + m_receivedEmptyImeEvent = true; QInputMethodEvent *eventCopy = new QInputMethodEvent(*ev); QGuiApplication::postEvent(qApp->focusObject(), eventCopy); } else { - m_receivedEmptyImeText = false; + m_receivedEmptyImeEvent = false; if (m_imeInProgress) { m_imeInProgress = false; m_host->ImeCancelComposition(); } } + + return; + } + + m_receivedEmptyImeEvent = false; + + // Finish compostion: insert or erase text. + if (!commitString.isEmpty() || replacementLength > 0) { + m_host->ImeCommitText(toString16(commitString), + underlines, + replacementRange, + 0); + m_imeInProgress = false; + } + + // Update or start new composition. + // Be aware of that, we might get a commit string and a pre-edit string in a single event and + // this means a new composition. + if (!preeditString.isEmpty()) { + m_host->ImeSetComposition(toString16(preeditString), + underlines, + replacementRange, + selectionRange.start(), + selectionRange.end()); + m_imeInProgress = true; } } diff --git a/src/core/render_widget_host_view_qt.h b/src/core/render_widget_host_view_qt.h index db68e5232..2a1485510 100644 --- a/src/core/render_widget_host_view_qt.h +++ b/src/core/render_widget_host_view_qt.h @@ -253,7 +253,7 @@ private: viz::mojom::CompositorFrameSinkClient *m_rendererCompositorFrameSink; bool m_imeInProgress; - bool m_receivedEmptyImeText; + bool m_receivedEmptyImeEvent; QPoint m_previousMousePosition; bool m_initPending; diff --git a/src/core/renderer/user_resource_controller.cpp b/src/core/renderer/user_resource_controller.cpp index ebc88c403..50a3924c6 100644 --- a/src/core/renderer/user_resource_controller.cpp +++ b/src/core/renderer/user_resource_controller.cpp @@ -69,6 +69,11 @@ static content::RenderView * const globalScriptsIndex = 0; // Scripts meant to run after the load event will be run 500ms after DOMContentLoaded if the load event doesn't come within that delay. static const int afterLoadTimeout = 500; +static int validUserScriptSchemes() +{ + return URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS | URLPattern::SCHEME_FILE; +} + static bool regexMatchesURL(const std::string &pat, const GURL &url) { QRegularExpression qre(QtWebEngineCore::toQt(pat)); qre.setPatternOptions(QRegularExpression::CaseInsensitiveOption); @@ -97,8 +102,8 @@ static bool scriptMatchesURL(const UserScriptData &scriptData, const GURL &url) if (!scriptData.urlPatterns.empty()) { matchFound = false; for (auto it = scriptData.urlPatterns.begin(), end = scriptData.urlPatterns.end(); it != end; ++it) { - URLPattern urlPattern(QtWebEngineCore::UserScript::validUserScriptSchemes(), *it); - if (urlPattern.MatchesURL(url)) + URLPattern urlPattern(validUserScriptSchemes()); + if (urlPattern.Parse(*it) == URLPattern::PARSE_SUCCESS && urlPattern.MatchesURL(url)) matchFound = true; } if (!matchFound) diff --git a/src/core/renderer/web_channel_ipc_transport.cpp b/src/core/renderer/web_channel_ipc_transport.cpp index bb544168f..ef00bcef3 100644 --- a/src/core/renderer/web_channel_ipc_transport.cpp +++ b/src/core/renderer/web_channel_ipc_transport.cpp @@ -64,7 +64,7 @@ public: static void Uninstall(blink::WebLocalFrame *frame, uint worldId); private: WebChannelTransport() {} - bool NativeQtSendMessage(gin::Arguments *args); + void NativeQtSendMessage(gin::Arguments *args); // gin::WrappableBase gin::ObjectTemplateBuilder GetObjectTemplateBuilder(v8::Isolate *isolate) override; @@ -118,37 +118,45 @@ void WebChannelTransport::Uninstall(blink::WebLocalFrame *frame, uint worldId) qtObject->Delete(gin::StringToV8(isolate, "webChannelTransport")); } -bool WebChannelTransport::NativeQtSendMessage(gin::Arguments *args) +void WebChannelTransport::NativeQtSendMessage(gin::Arguments *args) { blink::WebLocalFrame *frame = blink::WebLocalFrame::FrameForCurrentContext(); if (!frame || !frame->View()) - return false; + return; content::RenderFrame *renderFrame = content::RenderFrame::FromWebFrame(frame); if (!renderFrame) - return false; + return; + + v8::Local<v8::Value> jsonValue; + if (!args->GetNext(&jsonValue)) { + args->ThrowTypeError("Missing argument"); + return; + } - std::string message; - if (!args->GetNext(&message)) - return false; + if (!jsonValue->IsString()) { + args->ThrowTypeError("Expected string"); + return; + } + v8::Local<v8::String> jsonString = v8::Local<v8::String>::Cast(jsonValue); + + QByteArray json(jsonString->Utf8Length(), 0); + jsonString->WriteUtf8(json.data(), json.size(), + nullptr, + v8::String::REPLACE_INVALID_UTF8); - QByteArray valueData(message.data(), message.size()); QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(valueData, &error); + QJsonDocument doc = QJsonDocument::fromJson(json, &error); if (error.error != QJsonParseError::NoError) { - LOG(WARNING) << "Parsing error: " << qPrintable(error.errorString()); - return false; + args->ThrowTypeError("Invalid JSON"); + return; } int size = 0; const char *rawData = doc.rawData(&size); - if (size == 0) - return false; - renderFrame->Send(new WebChannelIPCTransportHost_SendMessage( renderFrame->GetRoutingID(), std::vector<char>(rawData, rawData + size))); - return true; } gin::ObjectTemplateBuilder WebChannelTransport::GetObjectTemplateBuilder(v8::Isolate *isolate) diff --git a/src/core/renderer_host/web_channel_ipc_transport_host.cpp b/src/core/renderer_host/web_channel_ipc_transport_host.cpp index 6b32093a6..d99dfde97 100644 --- a/src/core/renderer_host/web_channel_ipc_transport_host.cpp +++ b/src/core/renderer_host/web_channel_ipc_transport_host.cpp @@ -49,6 +49,8 @@ #include <QJsonObject> #include <QLoggingCategory> +#include <QtCore/private/qjson_p.h> + namespace QtWebEngineCore { Q_LOGGING_CATEGORY(log, "qt.webengine.webchanneltransport"); @@ -108,10 +110,19 @@ void WebChannelIPCTransportHost::setWorldId(content::RenderFrameHost *frame, bas void WebChannelIPCTransportHost::onWebChannelMessage(const std::vector<char> &message) { - Q_ASSERT(!message.empty()); - QJsonDocument doc = QJsonDocument::fromRawData(message.data(), message.size(), QJsonDocument::BypassValidation); - Q_ASSERT(doc.isObject()); content::RenderFrameHost *frame = web_contents()->GetMainFrame(); + + QJsonDocument doc; + // QJsonDocument::fromRawData does not check the length before it starts + // parsing the QJsonPrivate::Header and QJsonPrivate::Base structures. + if (message.size() >= sizeof(QJsonPrivate::Header) + sizeof(QJsonPrivate::Base)) + doc = QJsonDocument::fromRawData(message.data(), message.size()); + + if (!doc.isObject()) { + qCCritical(log).nospace() << "received invalid webchannel message from " << frame; + return; + } + qCDebug(log).nospace() << "received webchannel message from " << frame << ": " << doc; Q_EMIT messageReceived(doc.object(), this); } diff --git a/src/core/request_controller.h b/src/core/request_controller.h index a15c601d7..ffcf9edac 100644 --- a/src/core/request_controller.h +++ b/src/core/request_controller.h @@ -70,10 +70,7 @@ public: } } - virtual ~RequestController() - { - reject(); - } + virtual ~RequestController() {} protected: virtual void accepted() = 0; diff --git a/src/core/user_script.cpp b/src/core/user_script.cpp index 9b9d66d55..bdd6524ca 100644 --- a/src/core/user_script.cpp +++ b/src/core/user_script.cpp @@ -38,7 +38,6 @@ ****************************************************************************/ #include "common/user_script_data.h" -#include "extensions/common/url_pattern.h" #include "user_script.h" #include "type_conversion.h" @@ -66,11 +65,6 @@ bool GetDeclarationValue(const base::StringPiece& line, namespace QtWebEngineCore { -int UserScript::validUserScriptSchemes() -{ - return URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS | URLPattern::SCHEME_FILE; -} - ASSERT_ENUMS_MATCH(UserScript::AfterLoad, UserScriptData::AfterLoad) ASSERT_ENUMS_MATCH(UserScript::DocumentLoadFinished, UserScriptData::DocumentLoadFinished) ASSERT_ENUMS_MATCH(UserScript::DocumentElementCreation, UserScriptData::DocumentElementCreation) @@ -222,8 +216,6 @@ void UserScript::parseMetadataHeader() // support @noframes rule, we have to change the current default behavior. // static const base::StringPiece kNoFramesDeclaration("// @noframes"); - static URLPattern urlPatternParser(validUserScriptSchemes()); - while (line_start < script_text.length()) { line_end = script_text.find('\n', line_start); @@ -260,8 +252,7 @@ void UserScript::parseMetadataHeader() } scriptData->excludeGlobs.push_back(value); } else if (GetDeclarationValue(line, kMatchDeclaration, &value)) { - if (URLPattern::PARSE_SUCCESS == urlPatternParser.Parse(value)) - scriptData->urlPatterns.push_back(value); + scriptData->urlPatterns.push_back(value); } else if (GetDeclarationValue(line, kRunAtDeclaration, &value)) { if (value == kRunAtDocumentStartValue) scriptData->injectionPoint = DocumentElementCreation; diff --git a/src/core/user_script.h b/src/core/user_script.h index e44efd3e9..93cde9aa6 100644 --- a/src/core/user_script.h +++ b/src/core/user_script.h @@ -85,8 +85,6 @@ public: bool operator==(const UserScript &) const; - static int validUserScriptSchemes(); - private: void initData(); UserScriptData &data() const; diff --git a/src/core/web_contents_adapter_client.h b/src/core/web_contents_adapter_client.h index 4c5133772..0469867f2 100644 --- a/src/core/web_contents_adapter_client.h +++ b/src/core/web_contents_adapter_client.h @@ -449,6 +449,7 @@ public: virtual void updateContentsSize(const QSizeF &size) = 0; virtual void startDragging(const content::DropData &dropData, Qt::DropActions allowedActions, const QPixmap &pixmap, const QPoint &offset) = 0; + virtual bool supportsDragging() const = 0; virtual bool isEnabled() const = 0; virtual const QObject *holdingQObject() const = 0; virtual void setToolTip(const QString& toolTipText) = 0; diff --git a/src/core/web_contents_delegate_qt.cpp b/src/core/web_contents_delegate_qt.cpp index 2de8fd64a..fe1f6fee0 100644 --- a/src/core/web_contents_delegate_qt.cpp +++ b/src/core/web_contents_delegate_qt.cpp @@ -44,6 +44,7 @@ #include "web_contents_delegate_qt.h" #include "browser_context_adapter.h" +#include "browser_context_qt.h" #include "color_chooser_qt.h" #include "color_chooser_controller.h" #include "favicon_manager.h" @@ -115,19 +116,25 @@ WebContentsDelegateQt::~WebContentsDelegateQt() content::WebContents *WebContentsDelegateQt::OpenURLFromTab(content::WebContents *source, const content::OpenURLParams ¶ms) { content::WebContents *target = source; + content::SiteInstance *target_site_instance = params.source_site_instance.get(); + content::Referrer referrer = params.referrer; if (params.disposition != WindowOpenDisposition::CURRENT_TAB) { QSharedPointer<WebContentsAdapter> targetAdapter = createWindow(0, params.disposition, gfx::Rect(), params.user_gesture); if (targetAdapter) { + if (targetAdapter->browserContext() != source->GetBrowserContext()) { + target_site_instance = nullptr; + referrer = content::Referrer(); + } if (!targetAdapter->isInitialized()) - targetAdapter->initialize(params.source_site_instance.get()); + targetAdapter->initialize(target_site_instance); target = targetAdapter->webContents(); } } Q_ASSERT(target); content::NavigationController::LoadURLParams load_url_params(params.url); - load_url_params.source_site_instance = params.source_site_instance; - load_url_params.referrer = params.referrer; + load_url_params.source_site_instance = target_site_instance; + load_url_params.referrer = referrer; load_url_params.frame_tree_node_id = params.frame_tree_node_id; load_url_params.redirect_chain = params.redirect_chain; load_url_params.transition_type = params.transition; @@ -519,6 +526,17 @@ void WebContentsDelegateQt::ActivateContents(content::WebContents* contents) contents->Focus(); } +void WebContentsDelegateQt::RenderViewHostChanged(content::RenderViewHost *old_host, content::RenderViewHost *new_host) +{ + Q_ASSERT(new_host); + + // The old RVH can be nullptr if it was shut down. + if (!old_host) + return; + + new_host->UpdateWebkitPreferences(old_host->GetWebkitPreferences()); +} + void WebContentsDelegateQt::RequestToLockMouse(content::WebContents *web_contents, bool user_gesture, bool last_unlocked_by_target) { Q_UNUSED(user_gesture); diff --git a/src/core/web_contents_delegate_qt.h b/src/core/web_contents_delegate_qt.h index 2ef87ccd8..43badf60e 100644 --- a/src/core/web_contents_delegate_qt.h +++ b/src/core/web_contents_delegate_qt.h @@ -139,6 +139,7 @@ public: void WasShown() override; void DidFirstVisuallyNonEmptyPaint() override; void ActivateContents(content::WebContents* contents) override; + void RenderViewHostChanged(content::RenderViewHost *old_host, content::RenderViewHost *new_host) override; void didFailLoad(const QUrl &url, int errorCode, const QString &errorDescription); void overrideWebPreferences(content::WebContents *, content::WebPreferences*); diff --git a/src/core/web_contents_view_qt.cpp b/src/core/web_contents_view_qt.cpp index a7895d61c..6b68a9569 100644 --- a/src/core/web_contents_view_qt.cpp +++ b/src/core/web_contents_view_qt.cpp @@ -232,6 +232,12 @@ void WebContentsViewQt::StartDragging(const content::DropData &drop_data, #if QT_CONFIG(draganddrop) Q_UNUSED(event_info); + if (!m_client->supportsDragging()) { + if (source_rwh) + source_rwh->DragSourceSystemDragEnded(); + return; + } + QPixmap pixmap; QPoint hotspot; pixmap = QPixmap::fromImage(toQImage(image.GetRepresentation(m_client->dpiScale()))); diff --git a/src/webengine/api/qquickwebengineview.cpp b/src/webengine/api/qquickwebengineview.cpp index fde7b40dc..babfbd014 100644 --- a/src/webengine/api/qquickwebengineview.cpp +++ b/src/webengine/api/qquickwebengineview.cpp @@ -1012,6 +1012,13 @@ void QQuickWebEngineViewPrivate::startDragging(const content::DropData &dropData #endif // QT_CONFIG(draganddrop) } +bool QQuickWebEngineViewPrivate::supportsDragging() const +{ + // QTBUG-57516 + // Fixme: This is just a band-aid workaround. + return QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::MultipleWindows); +} + bool QQuickWebEngineViewPrivate::isEnabled() const { const Q_Q(QQuickWebEngineView); diff --git a/src/webengine/api/qquickwebengineview_p_p.h b/src/webengine/api/qquickwebengineview_p_p.h index 73607aa59..1723da7ea 100644 --- a/src/webengine/api/qquickwebengineview_p_p.h +++ b/src/webengine/api/qquickwebengineview_p_p.h @@ -144,6 +144,7 @@ public: void updateContentsSize(const QSizeF &size) override; void startDragging(const content::DropData &dropData, Qt::DropActions allowedActions, const QPixmap &pixmap, const QPoint &offset) override; + bool supportsDragging() const override; bool isEnabled() const override; void setToolTip(const QString &toolTipText) override; const QObject *holdingQObject() const override; diff --git a/src/webenginewidgets/api/qwebenginepage.cpp b/src/webenginewidgets/api/qwebenginepage.cpp index 8911c63c8..ae47ee39f 100644 --- a/src/webenginewidgets/api/qwebenginepage.cpp +++ b/src/webenginewidgets/api/qwebenginepage.cpp @@ -47,7 +47,6 @@ #include "favicon_manager.h" #include "file_picker_controller.h" #include "javascript_dialog_controller.h" -#include "printing/pdfium_document_wrapper_qt.h" #include "qwebenginefullscreenrequest.h" #include "qwebenginehistory.h" #include "qwebenginehistory_p.h" @@ -87,6 +86,10 @@ #include <QTimer> #include <QUrl> +#if defined(ENABLE_PRINTING) && defined(ENABLE_PDF) +#include "printing/pdfium_document_wrapper_qt.h" +#endif + QT_BEGIN_NAMESPACE using namespace QtWebEngineCore; @@ -1673,6 +1676,11 @@ void QWebEnginePagePrivate::startDragging(const content::DropData &dropData, #endif // QT_CONFIG(draganddrop) } +bool QWebEnginePagePrivate::supportsDragging() const +{ + return true; +} + bool QWebEnginePagePrivate::isEnabled() const { const Q_Q(QWebEnginePage); diff --git a/src/webenginewidgets/api/qwebenginepage_p.h b/src/webenginewidgets/api/qwebenginepage_p.h index dc7d02b73..fde877255 100644 --- a/src/webenginewidgets/api/qwebenginepage_p.h +++ b/src/webenginewidgets/api/qwebenginepage_p.h @@ -142,6 +142,7 @@ public: void updateContentsSize(const QSizeF &size) override; void startDragging(const content::DropData &dropData, Qt::DropActions allowedActions, const QPixmap &pixmap, const QPoint &offset) override; + bool supportsDragging() const override; bool isEnabled() const override; void setToolTip(const QString &toolTipText) override; const QObject *holdingQObject() const override; diff --git a/src/webenginewidgets/api/qwebengineview.cpp b/src/webenginewidgets/api/qwebengineview.cpp index 80c60e1a8..f03679d17 100644 --- a/src/webenginewidgets/api/qwebengineview.cpp +++ b/src/webenginewidgets/api/qwebengineview.cpp @@ -259,7 +259,8 @@ void QWebEngineView::findText(const QString &subString, QWebEnginePage::FindFlag */ QSize QWebEngineView::sizeHint() const { - return QSize(800, 600); + // TODO: Remove this override for Qt 6 + return QWidget::sizeHint(); } QWebEngineSettings *QWebEngineView::settings() const 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 16ea216f2..98482ae78 100644 --- a/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp +++ b/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp @@ -198,6 +198,9 @@ void RenderWidgetHostViewQtDelegateWidget::initAsChild(WebContentsAdapterClient* disconnect(parentWidget(), &QObject::destroyed, this, &RenderWidgetHostViewQtDelegateWidget::removeParentBeforeParentDelete); pagePrivate->view->layout()->addWidget(this); + if (QWidget *focusProxy = pagePrivate->view->focusProxy()) + if (focusProxy != this) + pagePrivate->view->layout()->removeWidget(focusProxy); pagePrivate->view->setFocusProxy(this); show(); } else @@ -250,6 +253,7 @@ void RenderWidgetHostViewQtDelegateWidget::setKeyboardFocus() QGuiApplication::sync(); m_rootItem->forceActiveFocus(); + setFocus(); } bool RenderWidgetHostViewQtDelegateWidget::hasKeyboardFocus() diff --git a/tests/auto/quick/qmltests/data/script-with-bad-match-metadata.js b/tests/auto/quick/qmltests/data/script-with-bad-match-metadata.js new file mode 100644 index 000000000..c9a811e5c --- /dev/null +++ b/tests/auto/quick/qmltests/data/script-with-bad-match-metadata.js @@ -0,0 +1,9 @@ +// ==UserScript== +// @name Test bad match script +// @homepageURL http://www.qt.io/ +// @description Test script with metadata block with an invalid match directive +// @match some:junk +// @run-at document-end +// ==/UserScript== + +document.title = "New title for some:junk"; diff --git a/tests/auto/quick/qmltests/data/tst_userScripts.qml b/tests/auto/quick/qmltests/data/tst_userScripts.qml index d7c7d5983..f4fcc30ab 100644 --- a/tests/auto/quick/qmltests/data/tst_userScripts.qml +++ b/tests/auto/quick/qmltests/data/tst_userScripts.qml @@ -54,6 +54,11 @@ Item { sourceUrl: Qt.resolvedUrl("script-with-metadata.js") } + WebEngineScript { + id: scriptWithBadMatchMetadata + sourceUrl: Qt.resolvedUrl("script-with-bad-match-metadata.js") + } + TestWebEngineView { id: webEngineView width: 400 @@ -191,6 +196,18 @@ Item { tryCompare(webEngineView, "title", "Test page with huge link area and iframe"); } + function test_dontInjectBadUrlPatternsEverywhere() { + compare(scriptWithBadMatchMetadata.name, "Test bad match script"); + compare(scriptWithBadMatchMetadata.injectionPoint, WebEngineScript.DocumentReady); + + webEngineView.userScripts = [ scriptWithBadMatchMetadata ]; + + // @match some:junk + webEngineView.url = Qt.resolvedUrl("test2.html"); + webEngineView.waitForLoadSucceeded(); + tryCompare(webEngineView, "title", "Test page with huge link area"); + } + function test_profileWideScript() { webEngineView.profile.userScripts = [ changeDocumentTitleScript ]; diff --git a/tests/auto/widgets/origins/resources/dedicatedWorker.html b/tests/auto/widgets/origins/resources/dedicatedWorker.html new file mode 100644 index 000000000..cb4f14e73 --- /dev/null +++ b/tests/auto/widgets/origins/resources/dedicatedWorker.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> + <head> + <title>dedicatedWorker</title> + <script> + var done = false; + var result; + var error; + try { + let worker = new Worker("dedicatedWorker.js"); + worker.onmessage = (e) => { done = true; result = e.data; }; + worker.postMessage(41); + } catch (e) { + done = true; error = e.message; + } + </script> + </head> + <body></body> +</html> diff --git a/tests/auto/widgets/origins/resources/dedicatedWorker.js b/tests/auto/widgets/origins/resources/dedicatedWorker.js new file mode 100644 index 000000000..2631939d7 --- /dev/null +++ b/tests/auto/widgets/origins/resources/dedicatedWorker.js @@ -0,0 +1 @@ +onmessage = (e) => { postMessage(e.data + 1); }; diff --git a/tests/auto/widgets/origins/resources/serviceWorker.html b/tests/auto/widgets/origins/resources/serviceWorker.html new file mode 100644 index 000000000..b2bdc8c60 --- /dev/null +++ b/tests/auto/widgets/origins/resources/serviceWorker.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> + <head> + <title>serviceWorker</title> + <script> + var done = false; + var error; + navigator.serviceWorker.register("serviceWorker.js") + .then((r) => { done = true; }) + .catch((e) => { done = true; error = e.message; }); + </script> + </head> + <body></body> +</html> diff --git a/tests/auto/widgets/origins/resources/serviceWorker.js b/tests/auto/widgets/origins/resources/serviceWorker.js new file mode 100644 index 000000000..40a8c178f --- /dev/null +++ b/tests/auto/widgets/origins/resources/serviceWorker.js @@ -0,0 +1 @@ +/* empty */ diff --git a/tests/auto/widgets/origins/resources/sharedWorker.html b/tests/auto/widgets/origins/resources/sharedWorker.html new file mode 100644 index 000000000..8b5a0a794 --- /dev/null +++ b/tests/auto/widgets/origins/resources/sharedWorker.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> + <head> + <title>sharedWorker</title> + <script> + var done; + var result; + var error; + try { + let worker = new SharedWorker("sharedWorker.js"); + worker.port.onmessage = (e) => { done = true; result = e.data; }; + worker.port.postMessage(41); + } catch (e) { + done = true; error = e.message; + } + </script> + </head> + <body></body> +</html> diff --git a/tests/auto/widgets/origins/resources/sharedWorker.js b/tests/auto/widgets/origins/resources/sharedWorker.js new file mode 100644 index 000000000..60ef93a5f --- /dev/null +++ b/tests/auto/widgets/origins/resources/sharedWorker.js @@ -0,0 +1,6 @@ +onconnect = function(e) { + let port = e.ports[0]; + port.onmessage = function(e) { + port.postMessage(e.data + 1); + }; +}; diff --git a/tests/auto/widgets/origins/tst_origins.cpp b/tests/auto/widgets/origins/tst_origins.cpp index 5c798ddc2..61d54e6de 100644 --- a/tests/auto/widgets/origins/tst_origins.cpp +++ b/tests/auto/widgets/origins/tst_origins.cpp @@ -73,6 +73,9 @@ private Q_SLOTS: void subdirWithoutAccess(); void mixedSchemes(); void webSocket(); + void dedicatedWorker(); + void sharedWorker(); + void serviceWorker(); private: bool load(const QUrl &url) @@ -259,9 +262,74 @@ void tst_Origins::webSocket() QVERIFY(load(QSL("qrc:/resources/websocket.html"))); QTRY_VERIFY(eval(QSL("err")) == QVariant(expected)); + QVERIFY(load(QSL("tst:/resources/websocket.html"))); + QTRY_VERIFY(eval(QSL("err")) == QVariant(expected)); +} + +// Create a (Dedicated)Worker. Since dedicated workers can only be accessed from +// one page, there is not much need for security restrictions. +void tst_Origins::dedicatedWorker() +{ + QVERIFY(load(QSL("file:" THIS_DIR "resources/dedicatedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QCOMPARE(eval(QSL("result")), QVariant(42)); + + QVERIFY(load(QSL("qrc:/resources/dedicatedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QCOMPARE(eval(QSL("result")), QVariant(42)); + // FIXME(juvaldma): QTBUG-62536 - // QVERIFY(load(QSL("tst:/resources/websocket.html"))); - // QTRY_VERIFY(eval(QSL("err")) == QVariant(expected)); + QVERIFY(load(QSL("tst:/resources/dedicatedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("Access to dedicated workers is denied to origin 'tst://'"))); +} + +// Create a SharedWorker. Shared workers can be accessed from multiple pages, +// and therefore the same-origin policy applies. +void tst_Origins::sharedWorker() +{ + { + ScopedAttribute sa(m_page.settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, false); + QVERIFY(load(QSL("file:" THIS_DIR "resources/sharedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("cannot be accessed from origin 'null'"))); + } + + { + ScopedAttribute sa(m_page.settings(), QWebEngineSettings::LocalContentCanAccessFileUrls, true); + QVERIFY(load(QSL("file:" THIS_DIR "resources/sharedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QCOMPARE(eval(QSL("result")), QVariant(42)); + } + + QVERIFY(load(QSL("qrc:/resources/sharedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QCOMPARE(eval(QSL("result")), QVariant(42)); + + QVERIFY(load(QSL("tst:/resources/sharedWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QCOMPARE(eval(QSL("result")), QVariant(42)); +} + +// Service workers don't work. +void tst_Origins::serviceWorker() +{ + QVERIFY(load(QSL("file:" THIS_DIR "resources/serviceWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("The URL protocol of the current origin ('file://') is not supported."))); + + QVERIFY(load(QSL("qrc:/resources/serviceWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("The URL protocol of the current origin ('qrc://') is not supported."))); + + QVERIFY(load(QSL("tst:/resources/serviceWorker.html"))); + QTRY_VERIFY(eval(QSL("done")).toBool()); + QVERIFY(eval(QSL("error")).toString() + .contains(QSL("Only secure origins are allowed"))); } QTEST_MAIN(tst_Origins) diff --git a/tests/auto/widgets/origins/tst_origins.qrc b/tests/auto/widgets/origins/tst_origins.qrc index 47be3bd0d..fbbbef139 100644 --- a/tests/auto/widgets/origins/tst_origins.qrc +++ b/tests/auto/widgets/origins/tst_origins.qrc @@ -1,9 +1,15 @@ <!DOCTYPE RCC> <RCC version="1.0"> <qresource> + <file>resources/dedicatedWorker.html</file> + <file>resources/dedicatedWorker.js</file> <file>resources/mixed_frame.html</file> <file>resources/mixed_qrc.html</file> <file>resources/mixed_tst.html</file> + <file>resources/serviceWorker.html</file> + <file>resources/serviceWorker.js</file> + <file>resources/sharedWorker.html</file> + <file>resources/sharedWorker.js</file> <file>resources/subdir/frame2.html</file> <file>resources/subdir/index.html</file> <file>resources/subdir_frame1.html</file> diff --git a/tests/auto/widgets/qwebenginedownloads/tst_qwebenginedownloads.cpp b/tests/auto/widgets/qwebenginedownloads/tst_qwebenginedownloads.cpp index 4848038df..f932d50c3 100644 --- a/tests/auto/widgets/qwebenginedownloads/tst_qwebenginedownloads.cpp +++ b/tests/auto/widgets/qwebenginedownloads/tst_qwebenginedownloads.cpp @@ -112,6 +112,7 @@ void tst_QWebEngineDownloads::initTestCase() m_page = new QWebEnginePage(m_profile); m_view = new QWebEngineView; m_view->setPage(m_page); + m_view->resize(640, 480); m_view->show(); } diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp index d62ace045..8d0d5c43c 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp @@ -217,6 +217,7 @@ private Q_SLOTS: void registerProtocolHandler(); void dataURLFragment(); void devTools(); + void openLinkInDifferentProfile(); private: static QPoint elementCenter(QWebEnginePage *page, const QString &id); @@ -3312,6 +3313,7 @@ protected: void tst_QWebEnginePage::evaluateWillCauseRepaint() { WebView view; + view.resize(640, 480); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); @@ -4052,6 +4054,7 @@ void tst_QWebEnginePage::mouseButtonTranslation() <div style=\"height:600px;\" onmousedown=\"saveLastEvent(event)\">\ </div>\ </body></html>")); + view.resize(640, 480); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QTRY_VERIFY(spy.count() == 1); @@ -4076,6 +4079,7 @@ void tst_QWebEnginePage::mouseMovementProperties() QWebEngineView view; ConsolePage page; view.setPage(&page); + view.resize(640, 480); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); @@ -4370,6 +4374,34 @@ void tst_QWebEnginePage::devTools() QCOMPARE(devToolsPage.inspectedPage(), nullptr); } +void tst_QWebEnginePage::openLinkInDifferentProfile() +{ + class Page : public QWebEnginePage { + public: + QWebEnginePage *targetPage = nullptr; + Page(QWebEngineProfile *profile) : QWebEnginePage(profile) {} + private: + QWebEnginePage *createWindow(WebWindowType) override { return targetPage; } + }; + QWebEngineProfile profile1, profile2; + Page page1(&profile1), page2(&profile2); + QWebEngineView view; + view.resize(500, 500); + view.setPage(&page1); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + QSignalSpy spy1(&page1, &QWebEnginePage::loadFinished), spy2(&page2, &QWebEnginePage::loadFinished); + page1.setHtml("<html><body>" + "<a id='link' href='data:,hello'>link</a>" + "</body></html>"); + QTRY_COMPARE(spy1.count(), 1); + QVERIFY(spy1.takeFirst().value(0).toBool()); + page1.targetPage = &page2; + QTest::mouseClick(view.focusProxy(), Qt::MiddleButton, 0, elementCenter(&page1, "link")); + QTRY_COMPARE(spy2.count(), 1); + QVERIFY(spy2.takeFirst().value(0).toBool()); +} + static QByteArrayList params = {QByteArrayLiteral("--use-fake-device-for-media-stream")}; W_QTEST_MAIN(tst_QWebEnginePage, params) diff --git a/tests/auto/widgets/qwebenginescript/resources/webChannelWithBadString.html b/tests/auto/widgets/qwebenginescript/resources/webChannelWithBadString.html new file mode 100644 index 000000000..ca2bc0f27 --- /dev/null +++ b/tests/auto/widgets/qwebenginescript/resources/webChannelWithBadString.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> + <head> + <title>webChannelWithBadString</title> + </head> + <body> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> + <script type="text/javascript"> + new QWebChannel(qt.webChannelTransport, (channel) => { + channel.objects.host.text = String.fromCharCode(0xD800); + }); + </script> + </body> +</html> diff --git a/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp b/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp index cb45e524e..23d31a478 100644 --- a/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp +++ b/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.cpp @@ -42,6 +42,7 @@ private Q_SLOTS: void webChannelResettingAndUnsetting(); void webChannelWithExistingQtObject(); void navigation(); + void webChannelWithBadString(); }; void tst_QWebEngineScript::domEditing() @@ -263,9 +264,8 @@ static QString readFile(const QString &path) static QWebEngineScript webChannelScript() { - QString sourceCode = readFile(QStringLiteral(":/qwebchannel.js")); - if (sourceCode.isEmpty()) - return {}; + QString sourceCode = readFile(QStringLiteral(":/qtwebchannel/qwebchannel.js")); + Q_ASSERT(!sourceCode.isEmpty()); QWebEngineScript script; script.setSourceCode(sourceCode); @@ -470,6 +470,22 @@ void tst_QWebEngineScript::navigation() QCOMPARE(testObject.text(), url3); } +// Try to set TestObject::text to an invalid UTF-16 string. +// +// See QTBUG-61969. +void tst_QWebEngineScript::webChannelWithBadString() +{ + QWebEnginePage page; + TestObject host; + QSignalSpy hostSpy(&host, &TestObject::textChanged); + QWebChannel channel; + channel.registerObject(QStringLiteral("host"), &host); + page.setWebChannel(&channel); + page.setUrl(QStringLiteral("qrc:/resources/webChannelWithBadString.html")); + QVERIFY(hostSpy.wait(20000)); + QCOMPARE(host.text(), QString(QChar(QChar::ReplacementCharacter))); +} + QTEST_MAIN(tst_QWebEngineScript) #include "tst_qwebenginescript.moc" diff --git a/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.qrc b/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.qrc index 9960a37ba..ada06119a 100644 --- a/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.qrc +++ b/tests/auto/widgets/qwebenginescript/tst_qwebenginescript.qrc @@ -4,5 +4,6 @@ <file>resources/test_iframe_outer.html</file> <file>resources/test_iframe_inner.html</file> <file>resources/test_window_open.html</file> + <file>resources/webChannelWithBadString.html</file> </qresource> </RCC> diff --git a/tests/auto/widgets/qwebenginesettings/tst_qwebenginesettings.cpp b/tests/auto/widgets/qwebenginesettings/tst_qwebenginesettings.cpp index 845520628..150b3c554 100644 --- a/tests/auto/widgets/qwebenginesettings/tst_qwebenginesettings.cpp +++ b/tests/auto/widgets/qwebenginesettings/tst_qwebenginesettings.cpp @@ -37,6 +37,7 @@ private Q_SLOTS: void defaultFontFamily(); void javascriptClipboard_data(); void javascriptClipboard(); + void setInAcceptNavigationRequest(); }; void tst_QWebEngineSettings::resetAttributes() @@ -162,6 +163,40 @@ void tst_QWebEngineSettings::javascriptClipboard() (pasteResult ? QString("AnotherText") : QString("OriginalText"))); } +class NavigationRequestOverride : public QWebEnginePage +{ +protected: + virtual bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) + { + Q_UNUSED(type); + + if (isMainFrame && url.scheme().startsWith("data")) + settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true); + + return true; + } +}; + +void tst_QWebEngineSettings::setInAcceptNavigationRequest() +{ + NavigationRequestOverride page; + QSignalSpy loadFinishedSpy(&page, SIGNAL(loadFinished(bool))); + QWebEngineSettings::globalSettings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false); + QVERIFY(!page.settings()->testAttribute(QWebEngineSettings::JavascriptEnabled)); + + page.load(QUrl("about:blank")); + QVERIFY(loadFinishedSpy.wait()); + QVERIFY(!page.settings()->testAttribute(QWebEngineSettings::JavascriptEnabled)); + + page.setHtml("<html><body>" + "<script>document.write('PASS')</script>" + "<noscript>FAIL</noscript>" + "</body></html>"); + QVERIFY(loadFinishedSpy.wait()); + QVERIFY(page.settings()->testAttribute(QWebEngineSettings::JavascriptEnabled)); + QCOMPARE(toPlainTextSync(&page), QStringLiteral("PASS")); +} + QTEST_MAIN(tst_QWebEngineSettings) #include "tst_qwebenginesettings.moc" diff --git a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp index 207836bef..248d906ef 100644 --- a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp +++ b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp @@ -153,6 +153,7 @@ private Q_SLOTS: void focusOnNavigation_data(); void focusOnNavigation(); void focusInternalRenderWidgetHostViewQuickItem(); + void doNotBreakLayout(); void changeLocale(); void inputMethodsTextFormat_data(); @@ -173,6 +174,7 @@ private Q_SLOTS: void imeCompositionQueryEvent_data(); void imeCompositionQueryEvent(); void newlineInTextarea(); + void imeJSInputEvents(); void mouseLeave(); @@ -344,6 +346,7 @@ void tst_QWebEngineView::crashTests() void tst_QWebEngineView::microFocusCoordinates() { QWebEngineView webView; + webView.resize(640, 480); webView.show(); QVERIFY(QTest::qWaitForWindowExposed(&webView)); @@ -378,6 +381,7 @@ void tst_QWebEngineView::focusInputTypes() bool imeHasHiddenTextCapability = context && context->hasCapability(QPlatformInputContext::HiddenTextCapability); QWebEngineView webView; + webView.resize(640, 480); webView.show(); QVERIFY(QTest::qWaitForWindowExposed(&webView)); @@ -481,6 +485,7 @@ void tst_QWebEngineView::unhandledKeyEventPropagation() { KeyEventRecordingWidget parentWidget; QWebEngineView webView(&parentWidget); + webView.resize(640, 480); parentWidget.show(); QVERIFY(QTest::qWaitForWindowExposed(&webView)); @@ -824,6 +829,7 @@ void tst_QWebEngineView::doNotSendMouseKeyboardEventsWhenDisabled() QFETCH(int, resultEventCount); KeyboardAndMouseEventRecordingWidget parentWidget; + parentWidget.resize(640, 480); QWebEngineView webView(&parentWidget); webView.setEnabled(viewEnabled); parentWidget.setLayout(new QStackedLayout); @@ -1029,6 +1035,31 @@ void tst_QWebEngineView::focusInternalRenderWidgetHostViewQuickItem() QTRY_COMPARE(renderWidgetHostViewQuickItem->hasFocus(), true); } +void tst_QWebEngineView::doNotBreakLayout() +{ + QScopedPointer<QWidget> containerWidget(new QWidget); + + QHBoxLayout *layout = new QHBoxLayout; + layout->addWidget(new QWidget); + layout->addWidget(new QWidget); + layout->addWidget(new QWidget); + layout->addWidget(new QWebEngineView); + + containerWidget->setLayout(layout); + containerWidget->setGeometry(50, 50, 800, 600); + containerWidget->show(); + QVERIFY(QTest::qWaitForWindowExposed(containerWidget.data())); + + QSize previousSize = static_cast<QWidgetItem *>(layout->itemAt(0))->widget()->size(); + for (int i = 1; i < layout->count(); i++) { + QSize actualSize = static_cast<QWidgetItem *>(layout->itemAt(i))->widget()->size(); + // There could be smaller differences on some platforms + QVERIFY(qAbs(previousSize.width() - actualSize.width()) <= 2); + QVERIFY(qAbs(previousSize.height() - actualSize.height()) <= 2); + previousSize = actualSize; + } +} + void tst_QWebEngineView::changeLocale() { QStringList errorLines; @@ -1592,6 +1623,7 @@ void tst_QWebEngineView::softwareInputPanel() { TestInputContext testContext; QWebEngineView view; + view.resize(640, 480); view.show(); QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); @@ -1648,6 +1680,7 @@ void tst_QWebEngineView::softwareInputPanel() void tst_QWebEngineView::inputMethods() { QWebEngineView view; + view.resize(640, 480); view.show(); QSignalSpy selectionChangedSpy(&view, SIGNAL(selectionChanged())); @@ -1744,6 +1777,7 @@ void tst_QWebEngineView::inputMethods() void tst_QWebEngineView::textSelectionInInputField() { QWebEngineView view; + view.resize(640, 480); view.show(); QSignalSpy selectionChangedSpy(&view, SIGNAL(selectionChanged())); @@ -1825,6 +1859,7 @@ void tst_QWebEngineView::textSelectionInInputField() void tst_QWebEngineView::textSelectionOutOfInputField() { QWebEngineView view; + view.resize(640, 480); view.show(); QSignalSpy selectionChangedSpy(&view, SIGNAL(selectionChanged())); @@ -1908,6 +1943,7 @@ void tst_QWebEngineView::textSelectionOutOfInputField() void tst_QWebEngineView::hiddenText() { QWebEngineView view; + view.resize(640, 480); view.show(); QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); @@ -1933,6 +1969,7 @@ void tst_QWebEngineView::hiddenText() void tst_QWebEngineView::emptyInputMethodEvent() { QWebEngineView view; + view.resize(640, 480); view.show(); QSignalSpy selectionChangedSpy(&view, SIGNAL(selectionChanged())); @@ -1979,6 +2016,7 @@ void tst_QWebEngineView::emptyInputMethodEvent() void tst_QWebEngineView::imeComposition() { QWebEngineView view; + view.resize(640, 480); view.show(); QSignalSpy selectionChangedSpy(&view, SIGNAL(selectionChanged())); @@ -2153,6 +2191,7 @@ void tst_QWebEngineView::imeComposition() void tst_QWebEngineView::newlineInTextarea() { QWebEngineView view; + view.resize(640, 480); view.show(); QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); @@ -2249,6 +2288,142 @@ void tst_QWebEngineView::newlineInTextarea() QTRY_COMPARE(view.focusProxy()->inputMethodQuery(Qt::ImSurroundingText).toString(), QString("\n\nthird line")); } +void tst_QWebEngineView::imeJSInputEvents() +{ + QWebEngineView view; + view.resize(640, 480); + view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); + view.show(); + + auto logLines = [&view]() -> QStringList { + return evaluateJavaScriptSync(view.page(), "log.textContent").toString().split("\n").filter(QRegExp(".+")); + }; + + QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); + view.page()->setHtml("<html>" + "<head><script>" + " var input, log;" + " function verboseEvent(ev) {" + " log.textContent += ev + ' ' + ev.type + ' ' + ev.data + '\\n';" + " }" + " function clear(ev) {" + " log.textContent = '';" + " input.textContent = '';" + " }" + " function init() {" + " input = document.getElementById('input');" + " log = document.getElementById('log');" + " events = [ 'textInput', 'beforeinput', 'input', 'compositionstart', 'compositionupdate', 'compositionend' ];" + " for (var e in events)" + " input.addEventListener(events[e], verboseEvent);" + " }" + "</script></head>" + "<body onload='init()'>" + " <div id='input' contenteditable='true' style='border-style: solid;'></div>" + " <pre id='log'></pre>" + "</body></html>"); + QVERIFY(loadFinishedSpy.wait()); + + evaluateJavaScriptSync(view.page(), "document.getElementById('input').focus()"); + QTRY_COMPARE(evaluateJavaScriptSync(view.page(), "document.activeElement.id").toString(), QStringLiteral("input")); + + // 1. Commit text (this is how dead keys work on Linux). + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("", attributes); + event.setCommitString("commit"); + QApplication::sendEvent(view.focusProxy(), &event); + qApp->processEvents(); + } + + // Simply committing text should not trigger any JS composition event. + QTRY_COMPARE(logLines().count(), 3); + QCOMPARE(logLines()[0], "[object InputEvent] beforeinput commit"); + QCOMPARE(logLines()[1], "[object TextEvent] textInput commit"); + QCOMPARE(logLines()[2], "[object InputEvent] input commit"); + + evaluateJavaScriptSync(view.page(), "clear()"); + QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "log.textContent + input.textContent").toString().isEmpty()); + + // 2. Start composition then commit text (this is how dead keys work on macOS). + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("preedit", attributes); + QApplication::sendEvent(view.focusProxy(), &event); + qApp->processEvents(); + } + + QTRY_COMPARE(logLines().count(), 4); + QCOMPARE(logLines()[0], "[object CompositionEvent] compositionstart "); + QCOMPARE(logLines()[1], "[object InputEvent] beforeinput preedit"); + QCOMPARE(logLines()[2], "[object CompositionEvent] compositionupdate preedit"); + QCOMPARE(logLines()[3], "[object InputEvent] input preedit"); + + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("", attributes); + event.setCommitString("commit"); + QApplication::sendEvent(view.focusProxy(), &event); + qApp->processEvents(); + } + + QTRY_COMPARE(logLines().count(), 9); + QCOMPARE(logLines()[4], "[object InputEvent] beforeinput commit"); + QCOMPARE(logLines()[5], "[object CompositionEvent] compositionupdate commit"); + QCOMPARE(logLines()[6], "[object TextEvent] textInput commit"); + QCOMPARE(logLines()[7], "[object InputEvent] input commit"); + QCOMPARE(logLines()[8], "[object CompositionEvent] compositionend commit"); + + evaluateJavaScriptSync(view.page(), "clear()"); + QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "log.textContent + input.textContent").toString().isEmpty()); + + // 3. Start composition then cancel it with an empty IME event. + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("preedit", attributes); + QApplication::sendEvent(view.focusProxy(), &event); + qApp->processEvents(); + } + + QTRY_COMPARE(logLines().count(), 4); + QCOMPARE(logLines()[0], "[object CompositionEvent] compositionstart "); + QCOMPARE(logLines()[1], "[object InputEvent] beforeinput preedit"); + QCOMPARE(logLines()[2], "[object CompositionEvent] compositionupdate preedit"); + QCOMPARE(logLines()[3], "[object InputEvent] input preedit"); + + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("", attributes); + QApplication::sendEvent(view.focusProxy(), &event); + qApp->processEvents(); + } + + QTRY_COMPARE(logLines().count(), 9); + QCOMPARE(logLines()[4], "[object InputEvent] beforeinput "); + QCOMPARE(logLines()[5], "[object CompositionEvent] compositionupdate "); + QCOMPARE(logLines()[6], "[object TextEvent] textInput "); + QCOMPARE(logLines()[7], "[object InputEvent] input null"); + QCOMPARE(logLines()[8], "[object CompositionEvent] compositionend "); + + evaluateJavaScriptSync(view.page(), "clear()"); + QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "log.textContent + input.textContent").toString().isEmpty()); + + // 4. Send empty IME event. + { + QList<QInputMethodEvent::Attribute> attributes; + QInputMethodEvent event("", attributes); + QApplication::sendEvent(view.focusProxy(), &event); + qApp->processEvents(); + } + + // No JS event is expected. + QTest::qWait(100); + QVERIFY(logLines().isEmpty()); + + evaluateJavaScriptSync(view.page(), "clear()"); + QTRY_VERIFY(evaluateJavaScriptSync(view.page(), "log.textContent + input.textContent").toString().isEmpty()); +} + void tst_QWebEngineView::imeCompositionQueryEvent_data() { QTest::addColumn<QString>("receiverObjectName"); @@ -2260,6 +2435,7 @@ void tst_QWebEngineView::imeCompositionQueryEvent_data() void tst_QWebEngineView::imeCompositionQueryEvent() { QWebEngineView view; + view.resize(640, 480); view.settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); view.show(); @@ -2341,6 +2517,7 @@ void tst_QWebEngineView::globalMouseSelection() QApplication::clipboard()->clear(QClipboard::Selection); QWebEngineView view; + view.resize(640, 480); view.show(); QSignalSpy selectionChangedSpy(&view, SIGNAL(selectionChanged())); @@ -2421,6 +2598,7 @@ void tst_QWebEngineView::contextMenu() } view.setContextMenuPolicy(contextMenuPolicy); + view.resize(640, 480); view.show(); QVERIFY(view.findChildren<QMenu *>().isEmpty()); diff --git a/tests/auto/widgets/resources/qwebchannel.js b/tests/auto/widgets/resources/qwebchannel.js deleted file mode 100644 index 1da8f5496..000000000 --- a/tests/auto/widgets/resources/qwebchannel.js +++ /dev/null @@ -1,408 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWebChannel 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$ -** -****************************************************************************/ - -"use strict"; - -var QWebChannelMessageTypes = { - signal: 1, - propertyUpdate: 2, - init: 3, - idle: 4, - debug: 5, - invokeMethod: 6, - connectToSignal: 7, - disconnectFromSignal: 8, - setProperty: 9, - response: 10, -}; - -var QWebChannel = function(transport, initCallback) -{ - if (typeof transport !== "object" || typeof transport.send !== "function") { - console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." + - " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send)); - return; - } - - var channel = this; - this.transport = transport; - - this.send = function(data) - { - if (typeof(data) !== "string") { - data = JSON.stringify(data); - } - channel.transport.send(data); - } - - this.transport.onmessage = function(message) - { - var data = message.data; - if (typeof data === "string") { - data = JSON.parse(data); - } - switch (data.type) { - case QWebChannelMessageTypes.signal: - channel.handleSignal(data); - break; - case QWebChannelMessageTypes.response: - channel.handleResponse(data); - break; - case QWebChannelMessageTypes.propertyUpdate: - channel.handlePropertyUpdate(data); - break; - default: - console.error("invalid message received:", message.data); - break; - } - } - - this.execCallbacks = {}; - this.execId = 0; - this.exec = function(data, callback) - { - if (!callback) { - // if no callback is given, send directly - channel.send(data); - return; - } - if (channel.execId === Number.MAX_VALUE) { - // wrap - channel.execId = Number.MIN_VALUE; - } - if (data.hasOwnProperty("id")) { - console.error("Cannot exec message with property id: " + JSON.stringify(data)); - return; - } - data.id = channel.execId++; - channel.execCallbacks[data.id] = callback; - channel.send(data); - }; - - this.objects = {}; - - this.handleSignal = function(message) - { - var object = channel.objects[message.object]; - if (object) { - object.signalEmitted(message.signal, message.args); - } else { - console.warn("Unhandled signal: " + message.object + "::" + message.signal); - } - } - - this.handleResponse = function(message) - { - if (!message.hasOwnProperty("id")) { - console.error("Invalid response message received: ", JSON.stringify(message)); - return; - } - channel.execCallbacks[message.id](message.data); - delete channel.execCallbacks[message.id]; - } - - this.handlePropertyUpdate = function(message) - { - for (var i in message.data) { - var data = message.data[i]; - var object = channel.objects[data.object]; - if (object) { - object.propertyUpdate(data.signals, data.properties); - } else { - console.warn("Unhandled property update: " + data.object + "::" + data.signal); - } - } - channel.exec({type: QWebChannelMessageTypes.idle}); - } - - this.debug = function(message) - { - channel.send({type: QWebChannelMessageTypes.debug, data: message}); - }; - - channel.exec({type: QWebChannelMessageTypes.init}, function(data) { - for (var objectName in data) { - var object = new QObject(objectName, data[objectName], channel); - } - // now unwrap properties, which might reference other registered objects - for (var objectName in channel.objects) { - channel.objects[objectName].unwrapProperties(); - } - if (initCallback) { - initCallback(channel); - } - channel.exec({type: QWebChannelMessageTypes.idle}); - }); -}; - -function QObject(name, data, webChannel) -{ - this.__id__ = name; - webChannel.objects[name] = this; - - // List of callbacks that get invoked upon signal emission - this.__objectSignals__ = {}; - - // Cache of all properties, updated when a notify signal is emitted - this.__propertyCache__ = {}; - - var object = this; - - // ---------------------------------------------------------------------- - - this.unwrapQObject = function(response) - { - if (response instanceof Array) { - // support list of objects - var ret = new Array(response.length); - for (var i = 0; i < response.length; ++i) { - ret[i] = object.unwrapQObject(response[i]); - } - return ret; - } - if (!response - || !response["__QObject*__"] - || response.id === undefined) { - return response; - } - - var objectId = response.id; - if (webChannel.objects[objectId]) - return webChannel.objects[objectId]; - - if (!response.data) { - console.error("Cannot unwrap unknown QObject " + objectId + " without data."); - return; - } - - var qObject = new QObject( objectId, response.data, webChannel ); - qObject.destroyed.connect(function() { - if (webChannel.objects[objectId] === qObject) { - delete webChannel.objects[objectId]; - // reset the now deleted QObject to an empty {} object - // just assigning {} though would not have the desired effect, but the - // below also ensures all external references will see the empty map - // NOTE: this detour is necessary to workaround QTBUG-40021 - var propertyNames = []; - for (var propertyName in qObject) { - propertyNames.push(propertyName); - } - for (var idx in propertyNames) { - delete qObject[propertyNames[idx]]; - } - } - }); - // here we are already initialized, and thus must directly unwrap the properties - qObject.unwrapProperties(); - return qObject; - } - - this.unwrapProperties = function() - { - for (var propertyIdx in object.__propertyCache__) { - object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]); - } - } - - function addSignal(signalData, isPropertyNotifySignal) - { - var signalName = signalData[0]; - var signalIndex = signalData[1]; - object[signalName] = { - connect: function(callback) { - if (typeof(callback) !== "function") { - console.error("Bad callback given to connect to signal " + signalName); - return; - } - - object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; - object.__objectSignals__[signalIndex].push(callback); - - if (!isPropertyNotifySignal && signalName !== "destroyed") { - // only required for "pure" signals, handled separately for properties in propertyUpdate - // also note that we always get notified about the destroyed signal - webChannel.exec({ - type: QWebChannelMessageTypes.connectToSignal, - object: object.__id__, - signal: signalIndex - }); - } - }, - disconnect: function(callback) { - if (typeof(callback) !== "function") { - console.error("Bad callback given to disconnect from signal " + signalName); - return; - } - object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || []; - var idx = object.__objectSignals__[signalIndex].indexOf(callback); - if (idx === -1) { - console.error("Cannot find connection of signal " + signalName + " to " + callback.name); - return; - } - object.__objectSignals__[signalIndex].splice(idx, 1); - if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) { - // only required for "pure" signals, handled separately for properties in propertyUpdate - webChannel.exec({ - type: QWebChannelMessageTypes.disconnectFromSignal, - object: object.__id__, - signal: signalIndex - }); - } - } - }; - } - - /** - * Invokes all callbacks for the given signalname. Also works for property notify callbacks. - */ - function invokeSignalCallbacks(signalName, signalArgs) - { - var connections = object.__objectSignals__[signalName]; - if (connections) { - connections.forEach(function(callback) { - callback.apply(callback, signalArgs); - }); - } - } - - this.propertyUpdate = function(signals, propertyMap) - { - // update property cache - for (var propertyIndex in propertyMap) { - var propertyValue = propertyMap[propertyIndex]; - object.__propertyCache__[propertyIndex] = propertyValue; - } - - for (var signalName in signals) { - // Invoke all callbacks, as signalEmitted() does not. This ensures the - // property cache is updated before the callbacks are invoked. - invokeSignalCallbacks(signalName, signals[signalName]); - } - } - - this.signalEmitted = function(signalName, signalArgs) - { - invokeSignalCallbacks(signalName, signalArgs); - } - - function addMethod(methodData) - { - var methodName = methodData[0]; - var methodIdx = methodData[1]; - object[methodName] = function() { - var args = []; - var callback; - for (var i = 0; i < arguments.length; ++i) { - if (typeof arguments[i] === "function") - callback = arguments[i]; - else - args.push(arguments[i]); - } - - webChannel.exec({ - "type": QWebChannelMessageTypes.invokeMethod, - "object": object.__id__, - "method": methodIdx, - "args": args - }, function(response) { - if (response !== undefined) { - var result = object.unwrapQObject(response); - if (callback) { - (callback)(result); - } - } - }); - }; - } - - function bindGetterSetter(propertyInfo) - { - var propertyIndex = propertyInfo[0]; - var propertyName = propertyInfo[1]; - var notifySignalData = propertyInfo[2]; - // initialize property cache with current value - // NOTE: if this is an object, it is not directly unwrapped as it might - // reference other QObject that we do not know yet - object.__propertyCache__[propertyIndex] = propertyInfo[3]; - - if (notifySignalData) { - if (notifySignalData[0] === 1) { - // signal name is optimized away, reconstruct the actual name - notifySignalData[0] = propertyName + "Changed"; - } - addSignal(notifySignalData, true); - } - - Object.defineProperty(object, propertyName, { - configurable: true, - get: function () { - var propertyValue = object.__propertyCache__[propertyIndex]; - if (propertyValue === undefined) { - // This shouldn't happen - console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__); - } - - return propertyValue; - }, - set: function(value) { - if (value === undefined) { - console.warn("Property setter for " + propertyName + " called with undefined value!"); - return; - } - object.__propertyCache__[propertyIndex] = value; - webChannel.exec({ - "type": QWebChannelMessageTypes.setProperty, - "object": object.__id__, - "property": propertyIndex, - "value": value - }); - } - }); - - } - - // ---------------------------------------------------------------------- - - data.methods.forEach(addMethod); - - data.properties.forEach(bindGetterSetter); - - data.signals.forEach(function(signal) { addSignal(signal, false); }); - - for (var name in data.enums) { - object[name] = data.enums[name]; - } -} - -//required for use with nodejs -if (typeof module === 'object') { - module.exports = { - QWebChannel: QWebChannel - }; -} diff --git a/tests/auto/widgets/resources/tests.qrc b/tests/auto/widgets/resources/tests.qrc deleted file mode 100644 index 5e9df2873..000000000 --- a/tests/auto/widgets/resources/tests.qrc +++ /dev/null @@ -1,5 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>qwebchannel.js</file> - </qresource> -</RCC> diff --git a/tests/auto/widgets/tests.pri b/tests/auto/widgets/tests.pri index 7bd00834c..5e6699cf8 100644 --- a/tests/auto/widgets/tests.pri +++ b/tests/auto/widgets/tests.pri @@ -11,7 +11,6 @@ TARGET = tst_$$TARGET SOURCES += $${TARGET}.cpp INCLUDEPATH += $$PWD -RESOURCES += ../resources/tests.qrc exists($$_PRO_FILE_PWD_/$${TARGET}.qrc): RESOURCES += $${TARGET}.qrc QT += testlib network webenginewidgets widgets quick quickwidgets |