Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Source/WebCore/loader/DocumentThreadableLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,12 @@ void DocumentThreadableLoader::notifyFinished(CachedResource& resource, const Ne
ASSERT(m_client);
ASSERT_UNUSED(resource, &resource == m_resource);

#if PLATFORM(QT)
LocalFrame* localFrame = document().frame();
if (localFrame)
localFrame->loader().client().dispatchDidFinishResourceLoad(resource);
#endif

if (m_resource->errorOccurred())
didFail(m_resource->resourceLoaderIdentifier(), m_resource->resourceError());
else
Expand Down Expand Up @@ -595,6 +601,13 @@ void DocumentThreadableLoader::loadRequest(ResourceRequest&& request, SecurityCh

auto cachedResource = m_document->protectedCachedResourceLoader()->requestRawResource(WTFMove(newRequest));
m_resource = cachedResource.value_or(nullptr);

#if PLATFORM(QT)
LocalFrame* localFrame = document().frame();
if (localFrame && m_resource.get())
localFrame->loader().client().dispatchDidStartResourceLoad(*m_resource.get());
#endif

if (CachedResourceHandle resource = m_resource)
resource->addClient(*this);
else
Expand Down
12 changes: 12 additions & 0 deletions Source/WebCore/loader/ImageLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,12 @@ void ImageLoader::notifyFinished(CachedResource& resource, const NetworkLoadMetr
{
LOG_WITH_STREAM(LazyLoading, stream << "ImageLoader " << this << " notifyFinished - hasPendingLoadEvent " << m_hasPendingLoadEvent);

#if PLATFORM(QT)
LocalFrame* localFrame = element().document().frame();
if (localFrame)
localFrame->loader().client().dispatchDidFinishResourceLoad(resource);
#endif

ASSERT(m_failedLoadURL.isEmpty());
ASSERT_UNUSED(resource, &resource == m_image.get());

Expand Down Expand Up @@ -690,6 +696,12 @@ void ImageLoader::dispatchPendingBeforeLoadEvent()
return;
if (!element().document().hasLivingRenderTree())
return;
#if PLATFORM(QT)
LocalFrame* localFrame = element().document().frame();
if (localFrame)
localFrame->loader().client().dispatchDidStartResourceLoad(*m_image.get());
#endif

m_hasPendingBeforeLoadEvent = false;
if (!element().isConnected())
return;
Expand Down
8 changes: 8 additions & 0 deletions Source/WebCore/loader/LocalFrameLoaderClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
#include <wtf/WeakRef.h>
#include <wtf/text/WTFString.h>

#if PLATFORM(QT)
#include "CachedResource.h"
#endif

#if ENABLE(APPLICATION_MANIFEST)
#include "ApplicationManifest.h"
#endif
Expand Down Expand Up @@ -160,6 +164,10 @@ class WEBCORE_EXPORT LocalFrameLoaderClient : public FrameLoaderClient {
virtual void dispatchDidFinishLoading(DocumentLoader*, IsMainResourceLoad, ResourceLoaderIdentifier) = 0;
virtual void dispatchDidFailLoading(DocumentLoader*, IsMainResourceLoad, ResourceLoaderIdentifier, const ResourceError&) = 0;
virtual bool dispatchDidLoadResourceFromMemoryCache(DocumentLoader*, const ResourceRequest&, const ResourceResponse&, int length) = 0;
#if PLATFORM(QT)
virtual void dispatchDidStartResourceLoad(const CachedResource&) { }
virtual void dispatchDidFinishResourceLoad(const CachedResource&) { }
#endif

virtual void dispatchDidDispatchOnloadEvents() = 0;
virtual void dispatchDidReceiveServerRedirectForProvisionalLoad() = 0;
Expand Down
2 changes: 2 additions & 0 deletions Source/WebKitLegacy/PlatformQt.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ set(QtWebKit_PUBLIC_FRAMEWORK_HEADERS
qt/Api/qwebhistory.h
qt/Api/qwebhistoryinterface.h
qt/Api/qwebkitglobal.h
qt/Api/qwebresourcetypes.h
qt/Api/qwebkitplatformplugin.h
qt/Api/qwebpluginfactory.h
qt/Api/qwebscriptworld.h
Expand All @@ -238,6 +239,7 @@ ecm_generate_headers(
QWebHistoryInterface
QWebKitPlatformPlugin,QWebHapticFeedbackPlayer,QWebFullScreenVideoHandler,QWebNotificationData,QWebNotificationPresenter,QWebSelectData,QWebSelectMethod,QWebSpellChecker,QWebTouchModifier
QWebPluginFactory
QWebResourceTypes
QWebSecurityOrigin
QWebSettings
COMMON_HEADER
Expand Down
44 changes: 44 additions & 0 deletions Source/WebKitLegacy/qt/Api/qwebresourcetypes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
Copyright (C) 2025 Michael Nutt <[email protected]>

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.

You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/

#ifndef QWEBRESOURCETYPES_H
#define QWEBRESOURCETYPES_H

#include <QtWebKit/qwebkitglobal.h>
#include <QtCore/QString>
#include <QtCore/QUrl>

// These types are duplicated from WebCore to provide a clean public API
// They have the same layout and field names so Qt signals/slots can pass them directly

struct QWEBKIT_EXPORT QtResourceTimingInfo {
qint64 domainLookupMs = -1;
qint64 connectMs = -1;
qint64 sslMs = -1;
qint64 requestMs = -1;
qint64 responseMs = -1;
qint64 totalMs = 0;
};

struct QWEBKIT_EXPORT QtResourceRequestInfo {
QString initiatorType;
QString resourceType;
};

#endif // QWEBRESOURCETYPES_H
115 changes: 115 additions & 0 deletions Source/WebKitLegacy/qt/WebCoreSupport/FrameLoaderClientQt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ void FrameLoaderClientQt::setFrame(QWebFrameAdapter* webFrame, LocalFrame* frame

connect(this, SIGNAL(titleChanged(QString)),
m_webFrame->handle(), SIGNAL(titleChanged(QString)));

connect(this, SIGNAL(resourceLoadStarted(const QUrl&, const QtResourceRequestInfo&, bool)),
m_webFrame->pageAdapter->handle(), SIGNAL(resourceLoadStarted(const QUrl&, const QtResourceRequestInfo&, bool)));
connect(this, SIGNAL(resourceLoadFinished(const QUrl, qint64, const QtResourceTimingInfo&, bool)),
m_webFrame->pageAdapter->handle(), SIGNAL(resourceLoadFinished(const QUrl&, qint64, const QtResourceTimingInfo&, bool)));
}

bool FrameLoaderClientQt::hasWebView() const
Expand Down Expand Up @@ -1392,6 +1397,116 @@ RefPtr<HistoryItem> FrameLoaderClientQt::createHistoryItemTree(bool clipAtTarget
return coreMainFrame->loader().history().createItemTree(*m_frame, clipAtTarget, itemID);
}


void FrameLoaderClientQt::dispatchDidStartResourceLoad(const WebCore::CachedResource& resource)
{
QUrl qurl(resource.url());

bool isCoalesced = resource.numberOfClients() == 0;

QtResourceRequestInfo requestInfo;
requestInfo.resourceType = resourceTypeToString(resource.type());
requestInfo.initiatorType = resource.initiatorType().string();

Q_EMIT resourceLoadStarted(qurl, requestInfo, isCoalesced);
}

void FrameLoaderClientQt::dispatchDidFinishResourceLoad(const WebCore::CachedResource& resource)
{
QUrl qurl(resource.url());
qint64 size = resource.encodedSize();
bool success = (resource.status() != WebCore::CachedResource::Status::LoadError &&
resource.status() != WebCore::CachedResource::Status::DecodeError);

// Create timing info from resource
QtResourceTimingInfo timing;
if (auto networkMetrics = const_cast<WebCore::CachedResource&>(resource).takeNetworkLoadMetrics()) {
timing = extractTimingInfo(*networkMetrics);
} else {
timing.totalMs = 0; // Fallback
}

Q_EMIT resourceLoadFinished(qurl, size, timing, success);
}

QString FrameLoaderClientQt::resourceTypeToString(WebCore::CachedResource::Type type) const
{
switch (type) {
case WebCore::CachedResource::Type::MainResource:
return QStringLiteral("document");
case WebCore::CachedResource::Type::ImageResource:
return QStringLiteral("image");
case WebCore::CachedResource::Type::CSSStyleSheet:
return QStringLiteral("stylesheet");
case WebCore::CachedResource::Type::Script:
return QStringLiteral("script");
case WebCore::CachedResource::Type::FontResource:
return QStringLiteral("font");
case WebCore::CachedResource::Type::SVGFontResource:
return QStringLiteral("svg-font");
case WebCore::CachedResource::Type::MediaResource:
return QStringLiteral("media");
case WebCore::CachedResource::Type::RawResource:
return QStringLiteral("xhr");
case WebCore::CachedResource::Type::Icon:
return QStringLiteral("icon");
case WebCore::CachedResource::Type::Beacon:
return QStringLiteral("beacon");
case WebCore::CachedResource::Type::Ping:
return QStringLiteral("ping");
#if ENABLE(XSLT)
case WebCore::CachedResource::Type::XSLStyleSheet:
return QStringLiteral("xsl");
#endif
case WebCore::CachedResource::Type::LinkPrefetch:
return QStringLiteral("prefetch");
#if ENABLE(VIDEO)
case WebCore::CachedResource::Type::TextTrackResource:
return QStringLiteral("track");
#endif
#if ENABLE(APPLICATION_MANIFEST)
case WebCore::CachedResource::Type::ApplicationManifest:
return QStringLiteral("manifest");
#endif
case WebCore::CachedResource::Type::SVGDocumentResource:
return QStringLiteral("svg");
}
return QStringLiteral("unknown");
}

QtResourceTimingInfo FrameLoaderClientQt::extractTimingInfo(const WebCore::NetworkLoadMetrics& metrics) const
{
QtResourceTimingInfo info;

// Extract detailed timing breakdown from NetworkLoadMetrics (W3C Resource Timing API)
if (metrics.domainLookupStart.secondsSinceEpoch().value() > 0 && metrics.domainLookupEnd.secondsSinceEpoch().value() > 0) {
info.domainLookupMs = (metrics.domainLookupEnd - metrics.domainLookupStart).millisecondsAs<qint64>();
}

if (metrics.connectStart.secondsSinceEpoch().value() > 0 && metrics.connectEnd.secondsSinceEpoch().value() > 0) {
info.connectMs = (metrics.connectEnd - metrics.connectStart).millisecondsAs<qint64>();
}

if (metrics.secureConnectionStart.secondsSinceEpoch().value() > 0 && metrics.connectEnd.secondsSinceEpoch().value() > 0) {
info.sslMs = (metrics.connectEnd - metrics.secureConnectionStart).millisecondsAs<qint64>();
}

if (metrics.requestStart.secondsSinceEpoch().value() > 0 && metrics.responseStart.secondsSinceEpoch().value() > 0) {
info.requestMs = (metrics.responseStart - metrics.requestStart).millisecondsAs<qint64>();
}

if (metrics.responseStart.secondsSinceEpoch().value() > 0 && metrics.responseEnd.secondsSinceEpoch().value() > 0) {
info.responseMs = (metrics.responseEnd - metrics.responseStart).millisecondsAs<qint64>();
}

// Calculate total timing
if (metrics.fetchStart.secondsSinceEpoch().value() > 0 && metrics.responseEnd.secondsSinceEpoch().value() > 0) {
info.totalMs = (metrics.responseEnd - metrics.fetchStart).millisecondsAs<qint64>();
}

return info;
}

}

#include "moc_FrameLoaderClientQt.cpp"
11 changes: 11 additions & 0 deletions Source/WebKitLegacy/qt/WebCoreSupport/FrameLoaderClientQt.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@
#ifndef FrameLoaderClientQt_h
#define FrameLoaderClientQt_h

#include <WebCore/CachedResource.h>
#include <WebCore/FormState.h>
#include <WebCore/LocalFrameLoaderClient.h>
#include <WebCore/NetworkLoadMetrics.h>
#include <WebCore/ProcessSwapDisposition.h>
#include <WebCore/ResourceResponse.h>
#include <WebCore/ResourceError.h>

#include <QObject>
#include "qwebresourcetypes.h"
#include <QUrl>
#include <wtf/URL.h>

Expand Down Expand Up @@ -68,6 +71,8 @@ class FrameLoaderClientQt final : public QObject, public LocalFrameLoaderClient
Q_SIGNALS:
void titleChanged(const QString& title);
void unsupportedContent(QNetworkReply*);
void resourceLoadStarted(const QUrl& url, const QtResourceRequestInfo& requestInfo, bool fromCache);
void resourceLoadFinished(const QUrl& url, qint64 size, const QtResourceTimingInfo& timing, bool success);

public:
FrameLoaderClientQt(WebCore::FrameLoader&);
Expand Down Expand Up @@ -97,6 +102,8 @@ class FrameLoaderClientQt final : public QObject, public LocalFrameLoaderClient
void dispatchDidFinishLoading(WebCore::DocumentLoader*, WebCore::IsMainResourceLoad, WebCore::ResourceLoaderIdentifier) override;
void dispatchDidFailLoading(WebCore::DocumentLoader*, WebCore::IsMainResourceLoad, WebCore::ResourceLoaderIdentifier, const WebCore::ResourceError&) override;
bool dispatchDidLoadResourceFromMemoryCache(WebCore::DocumentLoader*, const WebCore::ResourceRequest&, const WebCore::ResourceResponse&, int) override;
void dispatchDidStartResourceLoad(const WebCore::CachedResource&) override;
void dispatchDidFinishResourceLoad(const WebCore::CachedResource&) override;

void dispatchDidDispatchOnloadEvents() override;
void dispatchDidReceiveServerRedirectForProvisionalLoad() override;
Expand Down Expand Up @@ -246,6 +253,10 @@ private Q_SLOTS:
// QTFIXME: consider introducing some sort of flags for storing state
bool m_isDisplayingErrorPage;
bool m_shouldSuppressLoadStarted;

// Helper methods for resource tracking
QString resourceTypeToString(WebCore::CachedResource::Type type) const;
QtResourceTimingInfo extractTimingInfo(const WebCore::NetworkLoadMetrics& metrics) const;
};

}
Expand Down
25 changes: 25 additions & 0 deletions Source/WebKitLegacy/qt/WidgetApi/qwebpage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3439,6 +3439,31 @@ bool QWebPage::recentlyAudible() const
\sa loadStarted(), ErrorPageExtension
*/

/*!
\fn void QWebPage::resourceLoadStarted(const QUrl& url, const QtResourceRequestInfo& requestInfo, bool fromCache)

This signal is emitted when a resource (image, stylesheet, script, etc.) begins loading.
\a url contains the URL of the resource being loaded.
\a requestInfo contains detailed information about the resource request including HTTP method,
headers, priority, and other request metadata.
\a fromCache indicates whether the resource is being loaded from memory cache.

\sa resourceLoadFinished(), QtResourceRequestInfo
*/

/*!
\fn void QWebPage::resourceLoadFinished(const QUrl& url, qint64 size, const QtResourceTimingInfo& timing, bool success)

This signal is emitted when a resource finishes loading (successfully or with error).
\a url contains the URL of the resource that finished loading.
\a size contains the actual size in bytes of the loaded resource.
\a timing contains detailed timing information about the resource load including
DNS lookup, connection, request/response times, and cache status.
\a success indicates whether the resource loaded successfully.

\sa resourceLoadStarted(), QtResourceTimingInfo
*/

/*!
\fn void QWebPage::linkHovered(const QString &link, const QString &title, const QString &textContent)

Expand Down
4 changes: 4 additions & 0 deletions Source/WebKitLegacy/qt/WidgetApi/qwebpage.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include <QtWebKit/qwebkitglobal.h>
#include <QtWebKit/qwebfullscreenrequest.h>
#include <QtWebKit/qwebresourcetypes.h>
#include <QtWebKit/qwebsettings.h>

#include <QtCore/qobject.h>
Expand Down Expand Up @@ -67,6 +68,7 @@ namespace WebCore {
class FrameLoadRequest;
}


class QWEBKITWIDGETS_EXPORT QWebPage : public QObject {
Q_OBJECT

Expand Down Expand Up @@ -431,6 +433,8 @@ class QWEBKITWIDGETS_EXPORT QWebPage : public QObject {
void loadStarted();
void loadProgress(int progress);
void loadFinished(bool ok);
void resourceLoadStarted(const QUrl& url, const QtResourceRequestInfo& requestInfo, bool fromCache);
void resourceLoadFinished(const QUrl& url, qint64 size, const QtResourceTimingInfo& timing, bool success);

void linkHovered(const QString &link, const QString &title, const QString &textContent);
void statusBarMessage(const QString& text);
Expand Down
Loading