summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2017-07-24 15:47:48 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2017-10-25 11:02:41 +0000
commit684cfe05641ba9f3abc7e128d9dab5b331ef0689 (patch)
treeebaa3721e883360c5313a2ba5f1a9232d880ec6e
parentd96f8495b59878f50223a81a734ce0983539d8cf (diff)
Support streaming QIODevices in custom URL scheme handlers
We didn't handle the case where the QIODevice does not have all the data available all the time. Change-Id: I6aea8ed48ba9ed297efb907b8f6e5c5fc2a18abd Reviewed-by: Michal Klocek <michal.klocek@qt.io>
-rw-r--r--src/core/url_request_custom_job.cpp62
-rw-r--r--src/core/url_request_custom_job.h4
-rw-r--r--src/core/url_request_custom_job_delegate.cpp11
-rw-r--r--src/core/url_request_custom_job_delegate.h3
-rw-r--r--src/core/url_request_custom_job_proxy.cpp7
-rw-r--r--src/core/url_request_custom_job_proxy.h6
-rw-r--r--tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp85
7 files changed, 173 insertions, 5 deletions
diff --git a/src/core/url_request_custom_job.cpp b/src/core/url_request_custom_job.cpp
index 47c9b3b4c..b49135f79 100644
--- a/src/core/url_request_custom_job.cpp
+++ b/src/core/url_request_custom_job.cpp
@@ -56,6 +56,9 @@ URLRequestCustomJob::URLRequestCustomJob(URLRequest *request,
, m_proxy(new URLRequestCustomJobProxy(this, scheme, adapter))
, m_device(nullptr)
, m_error(0)
+ , m_pendingReadSize(0)
+ , m_pendingReadPos(0)
+ , m_pendingReadBuffer(nullptr)
{
}
@@ -83,6 +86,12 @@ void URLRequestCustomJob::Kill()
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (m_device && m_device->isOpen())
m_device->close();
+ if (m_pendingReadBuffer) {
+ m_pendingReadBuffer->Release();
+ m_pendingReadBuffer = nullptr;
+ m_pendingReadSize = 0;
+ m_pendingReadPos = 0;
+ }
m_device = nullptr;
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
base::Bind(&URLRequestCustomJobProxy::release,
@@ -127,13 +136,64 @@ int URLRequestCustomJob::ReadRawData(IOBuffer *buf, int bufSize)
if (m_error)
return m_error;
qint64 rv = m_device ? m_device->read(buf->data(), bufSize) : -1;
- if (rv >= 0) {
+ if (rv > 0) {
return static_cast<int>(rv);
+ } else if (rv == 0) {
+ // Returning zero is interpreted as EOF by Chromium, so only
+ // return zero if we are the end of the file.
+ if (m_device->atEnd())
+ return 0;
+ // Otherwise return IO_PENDING and call ReadRawDataComplete when we have data
+ // for them.
+ buf->AddRef();
+ m_pendingReadPos = 0;
+ m_pendingReadSize = bufSize;
+ m_pendingReadBuffer = buf;
+ return ERR_IO_PENDING;
} else {
// QIODevice::read might have called fail on us.
if (m_error)
return m_error;
+ if (m_device && m_device->atEnd())
+ return 0;
return ERR_FAILED;
}
}
+
+void URLRequestCustomJob::notifyReadyRead()
+{
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+ if (!m_device)
+ return;
+ if (!m_pendingReadSize)
+ return;
+ Q_ASSERT(m_pendingReadBuffer);
+ if (!m_pendingReadBuffer)
+ return;
+
+ qint64 rv = m_device->read(m_pendingReadBuffer->data() + m_pendingReadPos, m_pendingReadSize - m_pendingReadPos);
+ if (rv == 0)
+ return;
+ if (rv < 0) {
+ if (m_error)
+ rv = m_error;
+ else if (m_device->atEnd())
+ rv = 0;
+ else
+ rv = ERR_FAILED;
+ } else {
+ m_pendingReadPos += rv;
+ if (m_pendingReadPos < m_pendingReadSize && !m_device->atEnd())
+ return;
+ rv = m_pendingReadPos;
+ }
+ // killJob may be called from ReadRawDataComplete
+ net::IOBuffer *buf = m_pendingReadBuffer;
+ m_pendingReadBuffer = nullptr;
+ m_pendingReadSize = 0;
+ m_pendingReadPos = 0;
+ ReadRawDataComplete(rv);
+ buf->Release();
+}
+
} // namespace
diff --git a/src/core/url_request_custom_job.h b/src/core/url_request_custom_job.h
index 68a834d48..021cf3204 100644
--- a/src/core/url_request_custom_job.h
+++ b/src/core/url_request_custom_job.h
@@ -70,12 +70,16 @@ protected:
virtual ~URLRequestCustomJob();
private:
+ void notifyReadyRead();
scoped_refptr<URLRequestCustomJobProxy> m_proxy;
std::string m_mimeType;
std::string m_charset;
GURL m_redirect;
QIODevice *m_device;
int m_error;
+ int m_pendingReadSize;
+ int m_pendingReadPos;
+ net::IOBuffer *m_pendingReadBuffer;
friend class URLRequestCustomJobProxy;
diff --git a/src/core/url_request_custom_job_delegate.cpp b/src/core/url_request_custom_job_delegate.cpp
index 14de9a812..6b82cebb5 100644
--- a/src/core/url_request_custom_job_delegate.cpp
+++ b/src/core/url_request_custom_job_delegate.cpp
@@ -73,16 +73,23 @@ QByteArray URLRequestCustomJobDelegate::method() const
void URLRequestCustomJobDelegate::reply(const QByteArray &contentType, QIODevice *device)
{
+ if (device)
+ QObject::connect(device, &QIODevice::readyRead, this, &URLRequestCustomJobDelegate::slotReadyRead);
content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
base::Bind(&URLRequestCustomJobProxy::reply,
m_proxy,contentType.toStdString(),device));
}
+void URLRequestCustomJobDelegate::slotReadyRead()
+{
+ content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
+ base::Bind(&URLRequestCustomJobProxy::readyRead, m_proxy));
+}
+
void URLRequestCustomJobDelegate::abort()
{
content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
- base::Bind(&URLRequestCustomJobProxy::abort,
- m_proxy));
+ base::Bind(&URLRequestCustomJobProxy::abort, m_proxy));
}
void URLRequestCustomJobDelegate::redirect(const QUrl &url)
diff --git a/src/core/url_request_custom_job_delegate.h b/src/core/url_request_custom_job_delegate.h
index eb99f3576..3f5e6d591 100644
--- a/src/core/url_request_custom_job_delegate.h
+++ b/src/core/url_request_custom_job_delegate.h
@@ -74,6 +74,9 @@ public:
void abort();
void fail(Error);
+private Q_SLOTS:
+ void slotReadyRead();
+
private:
URLRequestCustomJobDelegate(URLRequestCustomJobProxy *proxy,
const QUrl &url,
diff --git a/src/core/url_request_custom_job_proxy.cpp b/src/core/url_request_custom_job_proxy.cpp
index d53602c85..832d3d11e 100644
--- a/src/core/url_request_custom_job_proxy.cpp
+++ b/src/core/url_request_custom_job_proxy.cpp
@@ -144,6 +144,13 @@ void URLRequestCustomJobProxy::fail(int error)
// else we fail on the next read, or the read that might already be in progress
}
+void URLRequestCustomJobProxy::readyRead()
+{
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+ if (m_job)
+ m_job->notifyReadyRead();
+}
+
void URLRequestCustomJobProxy::initialize(GURL url, std::string method)
{
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
diff --git a/src/core/url_request_custom_job_proxy.h b/src/core/url_request_custom_job_proxy.h
index df7171f5e..3ea30a4e1 100644
--- a/src/core/url_request_custom_job_proxy.h
+++ b/src/core/url_request_custom_job_proxy.h
@@ -63,6 +63,7 @@ public:
QWeakPointer<const BrowserContextAdapter> adapter);
~URLRequestCustomJobProxy();
+ // Called from URLRequestCustomJobDelegate via post:
//void setReplyCharset(const std::string &);
void reply(std::string mimeType, QIODevice *device);
void redirect(GURL url);
@@ -70,12 +71,13 @@ public:
void fail(int error);
void release();
void initialize(GURL url, std::string method);
+ void readyRead();
- //IO thread owned
+ // IO thread owned:
URLRequestCustomJob *m_job;
bool m_started;
- //UI thread owned
+ // UI thread owned:
std::string m_scheme;
URLRequestCustomJobDelegate *m_delegate;
QWeakPointer<const BrowserContextAdapter> m_adapter;
diff --git a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp
index 093bc2e43..400105152 100644
--- a/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp
+++ b/tests/auto/widgets/qwebengineprofile/tst_qwebengineprofile.cpp
@@ -49,6 +49,7 @@ private Q_SLOTS:
void urlSchemeHandlers();
void urlSchemeHandlerFailRequest();
void urlSchemeHandlerFailOnRead();
+ void urlSchemeHandlerStreaming();
void customUserAgent();
void httpAcceptLanguage();
void downloadItem();
@@ -178,6 +179,74 @@ public:
}
};
+class StreamingIODevice : public QIODevice {
+ Q_OBJECT
+public:
+ StreamingIODevice(QObject *parent) : QIODevice(parent), m_bytesRead(0), m_bytesAvailable(0)
+ {
+ setOpenMode(QIODevice::ReadOnly);
+ m_timer.start(100, this);
+ }
+ bool isSequential() const override { return true; }
+ qint64 bytesAvailable() const override
+ { return m_bytesAvailable; }
+ bool atEnd() const override
+ {
+ return (m_data.size() >= 1000 && m_bytesRead >= 1000);
+ }
+protected:
+ void timerEvent(QTimerEvent *) override
+ {
+ QMutexLocker lock(&m_mutex);
+ m_bytesAvailable += 200;
+ m_data.append(200, 'c');
+ emit readyRead();
+ if (m_data.size() >= 1000) {
+ m_timer.stop();
+ emit readChannelFinished();
+ }
+ }
+
+ qint64 readData(char *data, qint64 maxlen) override
+ {
+ QMutexLocker lock(&m_mutex);
+ qint64 len = qMin(qint64(m_bytesAvailable), maxlen);
+ if (len) {
+ memcpy(data, m_data.constData() + m_bytesRead, len);
+ m_bytesAvailable -= len;
+ m_bytesRead += len;
+ } else if (m_data.size() > 0)
+ return -1;
+
+ return len;
+ }
+ qint64 writeData(const char *, qint64) override
+ {
+ return 0;
+ }
+
+private:
+ QMutex m_mutex;
+ QByteArray m_data;
+ QBasicTimer m_timer;
+ int m_bytesRead;
+ int m_bytesAvailable;
+};
+
+class StreamingUrlSchemeHandler : public QWebEngineUrlSchemeHandler
+{
+public:
+ StreamingUrlSchemeHandler(QObject *parent = nullptr)
+ : QWebEngineUrlSchemeHandler(parent)
+ {
+ }
+
+ void requestStarted(QWebEngineUrlRequestJob *job)
+ {
+ job->reply("text/plain;charset=utf-8", new StreamingIODevice(job));
+ }
+};
+
static bool loadSync(QWebEngineView *view, const QUrl &url, int timeout = 5000)
{
// Ripped off QTRY_VERIFY.
@@ -310,6 +379,22 @@ void tst_QWebEngineProfile::urlSchemeHandlerFailOnRead()
QCOMPARE(toPlainTextSync(view.page()), QString());
}
+void tst_QWebEngineProfile::urlSchemeHandlerStreaming()
+{
+ StreamingUrlSchemeHandler handler;
+ QWebEngineProfile profile;
+ profile.installUrlSchemeHandler("stream", &handler);
+ QWebEngineView view;
+ QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool)));
+ view.setPage(new QWebEnginePage(&profile, &view));
+ view.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+ view.load(QUrl(QStringLiteral("stream://whatever")));
+ QVERIFY(loadFinishedSpy.wait());
+ QByteArray result;
+ result.append(1000, 'c');
+ QCOMPARE(toPlainTextSync(view.page()), QString::fromLatin1(result));
+}
+
void tst_QWebEngineProfile::customUserAgent()
{
QString defaultUserAgent = QWebEngineProfile::defaultProfile()->httpUserAgent();