Skip to content

Commit 4ce52ae

Browse files
zomboripatricia-gallardo
authored andcommitted
Stabilize load signals emitting
Make the WebContentsDelegateQt::EmitLoadStarted() and the WebContentsDelegateQt::EmitLoadFinished() independent from the WebContentsDelegateQt::LoadProgressChanged() by removing m_lastLoadProgress. Adapt the WebContentsDelegateQt::LoadProgressChanged() to send signal only if load is in progress. Add a new test based on the bugreport. Fix qmltests::WebEngineViewSource::test_viewSourceURL() flaky tests. Fixes: QTBUG-65223 Fixes: QTBUG-87089 Change-Id: I90af4d2e85105dba801beb8102991eb4ef14c6a3 Reviewed-by: Allan Sandfeld Jensen <[email protected]>
1 parent 9275646 commit 4ce52ae

File tree

11 files changed

+229
-50
lines changed

11 files changed

+229
-50
lines changed

src/core/web_contents_delegate_qt.cpp

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ WebContentsDelegateQt::WebContentsDelegateQt(content::WebContents *webContents,
106106
: m_viewClient(adapterClient)
107107
, m_faviconManager(new FaviconManager(webContents, adapterClient))
108108
, m_findTextHelper(new FindTextHelper(webContents, adapterClient))
109-
, m_lastLoadProgress(-1)
110109
, m_loadingState(determineLoadingState(webContents))
111110
, m_didStartLoadingSeen(m_loadingState == LoadingState::Loading)
112111
, m_frameFocusedObserver(adapterClient)
@@ -259,14 +258,14 @@ void WebContentsDelegateQt::CloseContents(content::WebContents *source)
259258

260259
void WebContentsDelegateQt::LoadProgressChanged(double progress)
261260
{
262-
if (!m_loadingErrorFrameList.isEmpty())
263-
return;
264-
if (m_lastLoadProgress < 0) // suppress signals that aren't between loadStarted and loadFinished
261+
QUrl current_url(m_viewClient->webContentsAdapter()->getNavigationEntryOriginalUrl(m_viewClient->webContentsAdapter()->currentNavigationEntryIndex()));
262+
int p = qMin(qRound(progress * 100), 100);
263+
264+
if (!m_loadingErrorFrameList.isEmpty() || !m_loadProgressMap.contains(current_url) || m_loadProgressMap[current_url] == 100 || p == 100)
265265
return;
266266

267-
int p = qMin(qRound(progress * 100), 100);
268-
if (p > m_lastLoadProgress) { // ensure strict monotonic increase
269-
m_lastLoadProgress = p;
267+
if (p > m_loadProgressMap[current_url]) { // ensure strict monotonic increase
268+
m_loadProgressMap[current_url] = p;
270269
m_viewClient->loadProgressChanged(p);
271270
}
272271
}
@@ -341,12 +340,32 @@ void WebContentsDelegateQt::RenderViewHostChanged(content::RenderViewHost *, con
341340

342341
void WebContentsDelegateQt::EmitLoadStarted(const QUrl &url, bool isErrorPage)
343342
{
344-
if (m_lastLoadProgress >= 0 && m_lastLoadProgress < 100) // already running
345-
return;
346343
m_viewClient->loadStarted(url, isErrorPage);
347344
m_viewClient->updateNavigationActions();
345+
346+
if ((url.hasFragment() || m_lastLoadedUrl.hasFragment())
347+
&& url.adjusted(QUrl::RemoveFragment) == m_lastLoadedUrl.adjusted(QUrl::RemoveFragment)
348+
&& !m_isNavigationCommitted) {
349+
m_loadProgressMap.insert(url, 100);
350+
m_lastLoadedUrl = url;
351+
m_viewClient->loadProgressChanged(100);
352+
return;
353+
}
354+
355+
if (!m_loadProgressMap.isEmpty()) {
356+
QMap<QUrl, int>::iterator it = m_loadProgressMap.begin();
357+
while (it != m_loadProgressMap.end()) {
358+
if (it.value() == 100) {
359+
it = m_loadProgressMap.erase(it);
360+
continue;
361+
}
362+
++it;
363+
}
364+
}
365+
366+
m_lastLoadedUrl = url;
367+
m_loadProgressMap.insert(url, 0);
348368
m_viewClient->loadProgressChanged(0);
349-
m_lastLoadProgress = 0;
350369
}
351370

352371
void WebContentsDelegateQt::DidStartNavigation(content::NavigationHandle *navigation_handle)
@@ -364,11 +383,15 @@ void WebContentsDelegateQt::DidStartNavigation(content::NavigationHandle *naviga
364383

365384
void WebContentsDelegateQt::EmitLoadFinished(bool success, const QUrl &url, bool isErrorPage, int errorCode, const QString &errorDescription)
366385
{
367-
if (m_lastLoadProgress < 0) // not currently running
386+
// When error page enabled we don't need to send the error page load finished signal
387+
if (m_loadProgressMap[url] == 100)
368388
return;
369-
if (m_lastLoadProgress < 100)
370-
m_viewClient->loadProgressChanged(100);
371-
m_lastLoadProgress = -1;
389+
390+
m_lastLoadedUrl = url;
391+
m_loadProgressMap[url] = 100;
392+
m_isNavigationCommitted = false;
393+
m_viewClient->loadProgressChanged(100);
394+
372395
m_viewClient->loadFinished(success, url, isErrorPage, errorCode, errorDescription);
373396
m_viewClient->updateNavigationActions();
374397
}
@@ -393,8 +416,10 @@ void WebContentsDelegateQt::DidFinishNavigation(content::NavigationHandle *navig
393416
profileAdapter->visitedLinksManager()->addUrl(url);
394417
}
395418

419+
m_isNavigationCommitted = true;
396420
EmitLoadCommitted();
397421
}
422+
398423
// Success is reported by DidFinishLoad, but DidFailLoad is now dead code and needs to be handled below
399424
if (navigation_handle->GetNetErrorCode() == net::OK)
400425
return;

src/core/web_contents_delegate_qt.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,6 @@ class WebContentsDelegateQt : public content::WebContentsDelegate
222222
SavePageInfo m_savePageInfo;
223223
QSharedPointer<FilePickerController> m_filePickerController;
224224
QUrl m_initialTargetUrl;
225-
int m_lastLoadProgress;
226225
LoadingState m_loadingState;
227226
bool m_didStartLoadingSeen;
228227
FrameFocusedObserver m_frameFocusedObserver;
@@ -234,6 +233,9 @@ class WebContentsDelegateQt : public content::WebContentsDelegate
234233
int m_desktopStreamCount = 0;
235234
mutable bool m_pendingUrlUpdate = false;
236235

236+
QMap<QUrl, int> m_loadProgressMap;
237+
QUrl m_lastLoadedUrl;
238+
bool m_isNavigationCommitted = false;
237239
base::WeakPtrFactory<WebContentsDelegateQt> m_weakPtrFactory { this };
238240
};
239241

tests/auto/quick/qmltests/BLACKLIST

Lines changed: 0 additions & 2 deletions
This file was deleted.

tests/auto/quick/qmltests/data/tst_loadUrl.qml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,8 @@ TestWebEngineView {
301301
// In-page navigation.
302302
webEngineView.url = Qt.resolvedUrl("test4.html#content");
303303
// In-page navigation doesn't trigger load succeeded, wait for load progress instead.
304+
tryCompare(loadRequestArray, "length", 3);
304305
tryCompare(webEngineView, "loadProgress", 100);
305-
compare(loadRequestArray.length, 3);
306306
compare(loadRequestArray[2].status, WebEngineView.LoadStartedStatus);
307307

308308
// Load after in-page navigation.

tests/auto/quick/qmltests/data/tst_viewSource.qml

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -94,36 +94,6 @@ TestWebEngineView {
9494
compare(webEngineView.url, "view-source:" + Qt.resolvedUrl("test1.html"));
9595
}
9696

97-
function test_viewSourceURL_data() {
98-
var testLocalUrl = "view-source:" + Qt.resolvedUrl("test1.html");
99-
var testLocalUrlWithoutScheme = "view-source:" + Qt.resolvedUrl("test1.html").substring(7);
100-
101-
return [
102-
{ tag: "view-source:", userInputUrl: "view-source:", loadSucceed: true, url: "view-source:", title: "view-source:" },
103-
{ tag: "view-source:about:blank", userInputUrl: "view-source:about:blank", loadSucceed: true, url: "view-source:about:blank", title: "view-source:about:blank" },
104-
{ tag: testLocalUrl, userInputUrl: testLocalUrl, loadSucceed: true, url: testLocalUrl, title: "test1.html" },
105-
{ tag: testLocalUrlWithoutScheme, userInputUrl: testLocalUrlWithoutScheme, loadSucceed: true, url: testLocalUrl, title: "test1.html" },
106-
{ tag: "view-source:http://non.existent", userInputUrl: "view-source:http://non.existent", loadSucceed: false, url: "http://non.existent/", title: "non.existent" },
107-
{ tag: "view-source:non.existent", userInputUrl: "view-source:non.existent", loadSucceed: false, url: "http://non.existent/", title: "non.existent" },
108-
];
109-
}
110-
111-
function test_viewSourceURL(row) {
112-
WebEngine.settings.errorPageEnabled = true
113-
webEngineView.url = row.userInputUrl;
114-
115-
if (row.loadSucceed) {
116-
tryCompare(webEngineView, "loadStatus", WebEngineView.LoadSucceededStatus);
117-
} else {
118-
tryCompare(webEngineView, "loadStatus", WebEngineView.LoadFailedStatus, 15000);
119-
}
120-
tryVerify(function() { return titleChangedSpy.count == 1; });
121-
122-
compare(webEngineView.url, row.url);
123-
tryCompare(webEngineView, "title", row.title);
124-
verify(!webEngineView.action(WebEngineView.ViewSource).enabled);
125-
}
126-
12797
function test_viewSourceCredentials() {
12898
var url = "http://user:[email protected]/basic-auth/user/passwd";
12999

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/****************************************************************************
2+
**
3+
** Copyright (C) 2020 The Qt Company Ltd.
4+
** Contact: https://www.qt.io/licensing/
5+
**
6+
** This file is part of the QtWebEngine module of the Qt Toolkit.
7+
**
8+
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9+
** Commercial License Usage
10+
** Licensees holding valid commercial Qt licenses may use this file in
11+
** accordance with the commercial license agreement provided with the
12+
** Software or, alternatively, in accordance with the terms contained in
13+
** a written agreement between you and The Qt Company. For licensing terms
14+
** and conditions see https://www.qt.io/terms-conditions. For further
15+
** information use the contact form at https://www.qt.io/contact-us.
16+
**
17+
** GNU General Public License Usage
18+
** Alternatively, this file may be used under the terms of the GNU
19+
** General Public License version 3 as published by the Free Software
20+
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21+
** included in the packaging of this file. Please review the following
22+
** information to ensure the GNU General Public License requirements will
23+
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24+
**
25+
** $QT_END_LICENSE$
26+
**
27+
****************************************************************************/
28+
29+
import QtQuick 2.0
30+
import QtTest 1.0
31+
import QtWebEngine 1.4
32+
import QtWebEngine.testsupport 1.0
33+
import "../../qmltests/data" 1.0
34+
35+
TestWebEngineView {
36+
id: webEngineView
37+
width: 200
38+
height: 400
39+
40+
property var viewRequest: null
41+
property var loadRequestArray: []
42+
43+
testSupport: WebEngineTestSupport {
44+
errorPage.onLoadingChanged: {
45+
loadRequestArray.push({
46+
"status": loadRequest.status,
47+
})
48+
}
49+
}
50+
51+
onLoadingChanged: {
52+
loadRequestArray.push({
53+
"status": loadRequest.status,
54+
});
55+
}
56+
57+
SignalSpy {
58+
id: newViewRequestedSpy
59+
target: webEngineView
60+
signalName: "newViewRequested"
61+
}
62+
63+
SignalSpy {
64+
id: titleChangedSpy
65+
target: webEngineView
66+
signalName: "titleChanged"
67+
}
68+
69+
onNewViewRequested: {
70+
viewRequest = {
71+
"destination": request.destination,
72+
"userInitiated": request.userInitiated
73+
};
74+
75+
request.openIn(webEngineView);
76+
}
77+
78+
TestCase {
79+
id: test
80+
name: "WebEngineViewSource"
81+
82+
function init() {
83+
webEngineView.loadStatus = null;
84+
webEngineView.url = Qt.resolvedUrl("test1.html");
85+
tryCompare(webEngineView, "loadStatus", WebEngineView.LoadSucceededStatus);
86+
webEngineView.loadStatus = null;
87+
88+
newViewRequestedSpy.clear();
89+
titleChangedSpy.clear();
90+
viewRequest = null;
91+
}
92+
93+
function test_viewSourceURL_data() {
94+
var testLocalUrl = "view-source:" + Qt.resolvedUrl("test1.html");
95+
var testLocalUrlWithoutScheme = "view-source:" + Qt.resolvedUrl("test1.html").substring(7);
96+
97+
return [
98+
{ tag: "view-source:", userInputUrl: "view-source:", loadSucceed: true, url: "view-source:", title: "view-source:" },
99+
{ tag: "view-source:about:blank", userInputUrl: "view-source:about:blank", loadSucceed: true, url: "view-source:about:blank", title: "view-source:about:blank" },
100+
{ tag: testLocalUrl, userInputUrl: testLocalUrl, loadSucceed: true, url: testLocalUrl, title: "test1.html" },
101+
{ tag: testLocalUrlWithoutScheme, userInputUrl: testLocalUrlWithoutScheme, loadSucceed: true, url: testLocalUrl, title: "test1.html" },
102+
{ tag: "view-source:http://non.existent", userInputUrl: "view-source:http://non.existent", loadSucceed: false, url: "http://non.existent/", title: "non.existent" },
103+
{ tag: "view-source:non.existent", userInputUrl: "view-source:non.existent", loadSucceed: false, url: "http://non.existent/", title: "non.existent" },
104+
];
105+
}
106+
107+
function test_viewSourceURL(row) {
108+
loadRequestArray = [];
109+
WebEngine.settings.errorPageEnabled = true
110+
webEngineView.url = row.userInputUrl;
111+
112+
if (row.loadSucceed) {
113+
tryVerify(function() { return loadRequestArray.length >= 2 });
114+
compare(loadRequestArray[1].status, WebEngineView.LoadSucceededStatus);
115+
} else {
116+
tryVerify(function() { return loadRequestArray.length >= 2 });
117+
compare(loadRequestArray[1].status, WebEngineView.LoadFailedStatus);
118+
tryVerify(function() { return loadRequestArray.length == 4 });
119+
compare(loadRequestArray[3].status, WebEngineView.LoadSucceededStatus);
120+
}
121+
tryVerify(function() { return titleChangedSpy.count == 1; });
122+
123+
compare(webEngineView.url, row.url);
124+
tryCompare(webEngineView, "title", row.title);
125+
if (row.loadSucceed) {
126+
verify(!webEngineView.action(WebEngineView.ViewSource).enabled);
127+
} else {
128+
verify(webEngineView.action(WebEngineView.ViewSource).enabled);
129+
}
130+
}
131+
}
132+
}
133+

tests/auto/quick/qmltests2/qmltests2.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ OTHER_FILES += \
3333
$$PWD/data/tst_linkHovered.qml \
3434
$$PWD/data/tst_loadFail.qml \
3535
$$PWD/data/tst_mouseClick.qml \
36+
$$PWD/data/tst_viewSource.qml \
3637
$$PWD/data/icons/favicon.png \
3738
$$PWD/data/icons/gray128.png \
3839
$$PWD/data/icons/gray16.png \

tests/auto/shared/httpserver.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ bool HttpServer::start()
5454
{
5555
m_error = false;
5656
m_expectingError = false;
57+
m_ignoreNewConnection = false;
5758

5859
if (!m_tcpServer->listen()) {
5960
qCWarning(gHttpServerLog).noquote() << m_tcpServer->errorString();
@@ -84,6 +85,9 @@ QUrl HttpServer::url(http://github.com/const%20QString%20&path) const
8485

8586
void HttpServer::handleNewConnection()
8687
{
88+
if (m_ignoreNewConnection)
89+
return;
90+
8791
auto rr = new HttpReqRep(m_tcpServer->nextPendingConnection(), this);
8892
connect(rr, &HttpReqRep::requestReceived, [this, rr]() {
8993
Q_EMIT newRequest(rr);
@@ -122,5 +126,9 @@ void HttpServer::handleNewConnection()
122126
<< error;
123127
m_error = true;
124128
});
125-
connect(rr, &HttpReqRep::closed, rr, &QObject::deleteLater);
129+
130+
if (!m_tcpServer->isListening()) {
131+
m_ignoreNewConnection = true;
132+
connect(rr, &HttpReqRep::closed, rr, &QObject::deleteLater);
133+
}
126134
}

tests/auto/shared/httpserver.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ private Q_SLOTS:
9090
QUrl m_url;
9191
QStringList m_dirs;
9292
bool m_error = false;
93+
bool m_ignoreNewConnection = false;
9394
bool m_expectingError = false;
9495
};
9596

tests/auto/widgets/loadsignals/tst_loadsignals.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ private Q_SLOTS:
5252
void secondLoadForError_WhenErrorPageEnabled();
5353
void loadAfterInPageNavigation_qtbug66869();
5454
void fileDownloadDoesNotTriggerLoadSignals_qtbug66661();
55+
void numberOfStartedAndFinishedSignalsIsSame();
5556

5657
private:
5758
QWebEngineProfile profile;
@@ -243,5 +244,44 @@ void tst_LoadSignals::fileDownloadDoesNotTriggerLoadSignals_qtbug66661()
243244
QCOMPARE(loadFinishedSpy.size(), 1);
244245
}
245246

247+
void tst_LoadSignals::numberOfStartedAndFinishedSignalsIsSame() {
248+
249+
HttpServer server;
250+
server.setResourceDirs({ TESTS_SOURCE_DIR "/qwebengineprofile/resources" });
251+
connect(&server, &HttpServer::newRequest, [] (HttpReqRep *) {
252+
QTest::qWait(250); // just add delay to trigger some progress for every sub resource
253+
});
254+
QVERIFY(server.start());
255+
256+
view.load(server.url("/hedgehog.png"));
257+
QTRY_COMPARE(loadFinishedSpy.size(), 1);
258+
QVERIFY(loadFinishedSpy[0][0].toBool());
259+
260+
loadStartedSpy.clear();
261+
loadFinishedSpy.clear();
262+
loadProgressSpy.clear();
263+
264+
view.page()->setHtml("<html><body>"
265+
"<img src=\"" + server.url("/hedgehog.png").toEncoded() + "\">"
266+
"<form method='GET' name='hiddenform' action='qrc:///resources/page1.html' />"
267+
"<script language='javascript'>document.forms[0].submit();</script>"
268+
"</body></html>");
269+
270+
QTRY_COMPARE(loadStartedSpy.size(), 2);
271+
QTRY_COMPARE(loadFinishedSpy.size(), 2);
272+
273+
QTRY_VERIFY(!loadFinishedSpy[0][0].toBool());
274+
QTRY_VERIFY(loadFinishedSpy[1][0].toBool());
275+
276+
view.page()->setHtml("<html><body>"
277+
"<form method='GET' name='hiddenform' action='qrc:///resources/page1.html' />"
278+
"<script language='javascript'>document.forms[0].submit();</script>"
279+
"</body></html>");
280+
QTRY_COMPARE(loadStartedSpy.size(), 4);
281+
QTRY_COMPARE(loadFinishedSpy.size(), 4);
282+
QVERIFY(loadFinishedSpy[2][0].toBool());
283+
QVERIFY(loadFinishedSpy[3][0].toBool());
284+
}
285+
246286
QTEST_MAIN(tst_LoadSignals)
247287
#include "tst_loadsignals.moc"

0 commit comments

Comments
 (0)