From 7d7e0129656959558a563f87ceabfd0d4c399c9b Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 6 May 2014 11:28:09 -0700 Subject: [PATCH 1/8] Add XMLHttpRequest --- .../script-engine/src/XMLHttpRequestClass.cpp | 233 ++++++++++++++++++ .../script-engine/src/XMLHttpRequestClass.h | 128 ++++++++++ 2 files changed, 361 insertions(+) create mode 100644 libraries/script-engine/src/XMLHttpRequestClass.cpp create mode 100644 libraries/script-engine/src/XMLHttpRequestClass.h diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp new file mode 100644 index 0000000000..77b03db999 --- /dev/null +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -0,0 +1,233 @@ +// +// XMLHttpRequestClass.cpp +// libraries/script-engine/src/ +// +// Created by Ryan Huffman on 5/2/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// This class is an implementation of the XMLHttpRequest object for scripting use. It provides a near-complete implementation +// of the class described in the Mozilla docs: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "XMLHttpRequestClass.h" + +XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) : + _engine(engine), + _async(true), + _url(), + _method(""), + _responseType(""), + _manager(this), + _request(), + _reply(NULL), + _sendData(NULL), + _rawResponseData(), + _responseData(""), + _onTimeout(QScriptValue::NullValue), + _onReadyStateChange(QScriptValue::NullValue), + _readyState(XMLHttpRequestClass::UNSENT), + _errorCode(QNetworkReply::NoError), + _timeout(0), + _timer(this), + _numRedirects(0) { + + _timer.setSingleShot(true); +} + +XMLHttpRequestClass::~XMLHttpRequestClass() { + if (_reply) { delete _reply; } + if (_sendData) { delete _sendData; } +} + +QScriptValue XMLHttpRequestClass::constructor(QScriptContext* context, QScriptEngine* engine) { + return engine->newQObject(new XMLHttpRequestClass(engine)); +} + +QScriptValue XMLHttpRequestClass::getStatus() const { + if (_reply) { + return QScriptValue(_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()); + } + return QScriptValue(0); +} + +QString XMLHttpRequestClass::getStatusText() const { + if (_reply) { + return _reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); + } + return ""; +} + +void XMLHttpRequestClass::abort() { + _abortRequest(); +} + +void XMLHttpRequestClass::setRequestHeader(const QString& name, const QString& value) { + _request.setRawHeader(QByteArray(name.toLatin1()), QByteArray(value.toLatin1())); +} + +void XMLHttpRequestClass::requestMetaDataChanged() { + QVariant redirect = _reply->attribute(QNetworkRequest::RedirectionTargetAttribute); + + // If this is a redirect, abort the current request and start a new one + if (redirect.isValid() && _numRedirects < MAXIMUM_REDIRECTS) { + _numRedirects++; + _abortRequest(); + + QUrl newUrl = _url.resolved(redirect.toUrl().toString()); + _request.setUrl(newUrl); + _doSend(); + } +} + +void XMLHttpRequestClass::requestDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { + if (_readyState == OPENED && bytesReceived > 0) { + _setReadyState(HEADERS_RECEIVED); + _setReadyState(LOADING); + } +} + +QScriptValue XMLHttpRequestClass::getAllResponseHeaders() const { + if (_reply) { + QList headerList = _reply->rawHeaderPairs(); + QByteArray headers; + for (int i = 0; i < headerList.size(); i++) { + headers.append(headerList[i].first); + headers.append(": "); + headers.append(headerList[i].second); + headers.append("\n"); + } + return QString(headers.data()); + } + return QScriptValue(""); +} + +QScriptValue XMLHttpRequestClass::getResponseHeader(const QString& name) const { + if (_reply && _reply->hasRawHeader(name.toLatin1())) { + return QScriptValue(QString(_reply->rawHeader(name.toLatin1()))); + } + return QScriptValue::NullValue; +} + +void XMLHttpRequestClass::_setReadyState(ReadyState readyState) { + if (readyState != _readyState) { + _readyState = readyState; + if (_onReadyStateChange.isFunction()) { + _onReadyStateChange.call(QScriptValue::NullValue); + } + } +} + +void XMLHttpRequestClass::open(const QString& method, const QString& url, bool async, const QString& username, + const QString& password) { + if (_readyState == UNSENT) { + _async = async; + _url.setUrl(url); + if (!username.isEmpty()) { + _url.setUserName(username); + } + if (!password.isEmpty()) { + _url.setPassword(password); + } + _request.setUrl(_url); + _method = method; + _setReadyState(OPENED); + } +} + +void XMLHttpRequestClass::send() { + send(QString::Null()); +} + +void XMLHttpRequestClass::send(const QString& data) { + if (_readyState == OPENED && !_reply) { + if (!data.isNull()) { + _sendData = new QBuffer(this); + _sendData->setData(data.toUtf8()); + } + + _doSend(); + + if (!_async) { + QEventLoop loop; + connect(this, SIGNAL(requestComplete()), &loop, SLOT(quit())); + loop.exec(); + } + } +} + +void XMLHttpRequestClass::_doSend() { + _reply = _manager.sendCustomRequest(_request, _method.toLatin1(), _sendData); + + _connectToReply(_reply); + + if (_timeout > 0) { + _timer.start(_timeout); + connect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout())); + } +} + +void XMLHttpRequestClass::requestTimeout() { + if (_onTimeout.isFunction()) { + _onTimeout.call(QScriptValue::NullValue); + } + _abortRequest(); + _errorCode = QNetworkReply::TimeoutError; + _setReadyState(DONE); + emit requestComplete(); +} + +void XMLHttpRequestClass::requestError(QNetworkReply::NetworkError code) { +} + +void XMLHttpRequestClass::requestFinished() { + disconnect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout())); + + _errorCode = _reply->error(); + if (_errorCode == QNetworkReply::NoError) { + _rawResponseData.append(_reply->readAll()); + + if (_responseType == "json") { + _responseData = _engine->evaluate("(" + QString(_rawResponseData.data()) + ")"); + if (_responseData.isError()) { + _engine->clearExceptions(); + _responseData = QScriptValue::NullValue; + } + } else if (_responseType == "arraybuffer") { + _responseData = QScriptValue(_rawResponseData.data()); + } else { + _responseData = QScriptValue(QString(_rawResponseData.data())); + } + } + _setReadyState(DONE); + emit requestComplete(); +} + +void XMLHttpRequestClass::_abortRequest() { + // Disconnect from signals we don't want to receive any longer. + disconnect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout())); + if (_reply) { + _disconnectFromReply(_reply); + _reply->abort(); + delete _reply; + _reply = NULL; + } +} + +void XMLHttpRequestClass::_connectToReply(QNetworkReply* reply) { + connect(reply, SIGNAL(finished()), this, SLOT(requestFinished())); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); + connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(requestDownloadProgress(qint64, qint64))); + connect(reply, SIGNAL(metaDataChanged()), this, SLOT(requestMetaDataChanged())); +} + +void XMLHttpRequestClass::_disconnectFromReply(QNetworkReply* reply) { + disconnect(reply, SIGNAL(finished()), this, SLOT(requestFinished())); + disconnect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); + disconnect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(requestDownloadProgress(qint64, qint64))); + disconnect(reply, SIGNAL(metaDataChanged()), this, SLOT(requestMetaDataChanged())); +} diff --git a/libraries/script-engine/src/XMLHttpRequestClass.h b/libraries/script-engine/src/XMLHttpRequestClass.h new file mode 100644 index 0000000000..b1b33e4cc6 --- /dev/null +++ b/libraries/script-engine/src/XMLHttpRequestClass.h @@ -0,0 +1,128 @@ +// +// XMLHttpRequestClass.h +// libraries/script-engine/src/ +// +// Created by Ryan Huffman on 5/2/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_XMLHttpRequestClass_h +#define hifi_XMLHttpRequestClass_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class XMLHttpRequestClass : public QObject { + Q_OBJECT + Q_PROPERTY(QScriptValue response READ getResponse) + Q_PROPERTY(QScriptValue responseText READ getResponseText) + Q_PROPERTY(QString responseType READ getResponseType WRITE setResponseType) + Q_PROPERTY(QScriptValue status READ getStatus) + Q_PROPERTY(QString statusText READ getStatusText) + Q_PROPERTY(QScriptValue readyState READ getReadyState) + Q_PROPERTY(QScriptValue errorCode READ getError) + Q_PROPERTY(int timeout READ getTimeout WRITE setTimeout) + + Q_PROPERTY(int UNSENT READ getUnsent) + Q_PROPERTY(int OPENED READ getOpened) + Q_PROPERTY(int HEADERS_RECEIVED READ getHeadersReceived) + Q_PROPERTY(int LOADING READ getLoading) + Q_PROPERTY(int DONE READ getDone) + + // Callbacks + Q_PROPERTY(QScriptValue ontimeout WRITE setOnTimeout) + Q_PROPERTY(QScriptValue onreadystatechange WRITE setOnReadyStateChange) +public: + XMLHttpRequestClass(QScriptEngine* engine); + ~XMLHttpRequestClass(); + + static const int MAXIMUM_REDIRECTS = 5; + enum ReadyState { + UNSENT = 0, + OPENED, + HEADERS_RECEIVED, + LOADING, + DONE + }; + + int getUnsent() const { return UNSENT; }; + int getOpened() const { return OPENED; }; + int getHeadersReceived() const { return HEADERS_RECEIVED; }; + int getLoading() const { return LOADING; }; + int getDone() const { return DONE; }; + + static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + static QString escapeJavascriptString(const QString& jsString); + + int getTimeout() const { return _timeout; } + void setTimeout(int timeout) { _timeout = timeout; } + QScriptValue getResponse() const { return _responseData; } + QScriptValue getResponseText() const { return QScriptValue(QString(_rawResponseData.data())); } + QString getResponseType() const { return _responseType; } + void setResponseType(const QString& responseType) { _responseType = responseType; } + QScriptValue getReadyState() const { return QScriptValue(_readyState); } + QScriptValue getError() const { return QScriptValue(_errorCode); } + QScriptValue getStatus() const; + QString getStatusText() const; + + void setOnTimeout(QScriptValue function) { _onTimeout = function; } + void setOnReadyStateChange(QScriptValue function) { _onReadyStateChange = function; } + +public slots: + void abort(); + void setRequestHeader(const QString& name, const QString& value); + void open(const QString& method, const QString& url, bool async = true, const QString& username = "", + const QString& password = ""); + void send(); + void send(const QString& data); + QScriptValue getAllResponseHeaders() const; + QScriptValue getResponseHeader(const QString& name) const; + +signals: + void requestComplete(); + +private: + void _setReadyState(ReadyState readyState); + void _doSend(); + void _connectToReply(QNetworkReply* reply); + void _disconnectFromReply(QNetworkReply* reply); + void _abortRequest(); + + QScriptEngine* _engine; + bool _async; + QUrl _url; + QString _method; + QString _responseType; + QNetworkAccessManager _manager; + QNetworkRequest _request; + QNetworkReply* _reply; + QBuffer* _sendData; + QByteArray _rawResponseData; + QScriptValue _responseData; + QScriptValue _onTimeout; + QScriptValue _onReadyStateChange; + ReadyState _readyState; + QNetworkReply::NetworkError _errorCode; + int _timeout; + QTimer _timer; + int _numRedirects; + +private slots: + void requestFinished(); + void requestError(QNetworkReply::NetworkError code); + void requestMetaDataChanged(); + void requestDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void requestTimeout(); +}; + +#endif // hifi_XMLHttpRequestClass_h From 2dda87fe7f4c5d8d8ed78e0ea73c2794d6b26c97 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 6 May 2014 11:28:35 -0700 Subject: [PATCH 2/8] Add XMLHttpRequest constructor to ScriptEngine --- libraries/script-engine/src/ScriptEngine.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 402f1a2885..41ed07cc23 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -34,6 +34,7 @@ #include "MenuItemProperties.h" #include "LocalVoxels.h" #include "ScriptEngine.h" +#include "XMLHttpRequestClass.h" VoxelsScriptingInterface ScriptEngine::_voxelsScriptingInterface; ParticlesScriptingInterface ScriptEngine::_particlesScriptingInterface; @@ -214,6 +215,9 @@ void ScriptEngine::init() { qScriptRegisterSequenceMetaType >(&_engine); qScriptRegisterSequenceMetaType >(&_engine); + QScriptValue xmlHttpRequestConstructorValue = _engine.newFunction(XMLHttpRequestClass::constructor); + _engine.globalObject().setProperty("XMLHttpRequest", xmlHttpRequestConstructorValue); + QScriptValue printConstructorValue = _engine.newFunction(debugPrint); _engine.globalObject().setProperty("print", printConstructorValue); From 39e6e61a6d2026c3c57da3a425566551a6fca982 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 6 May 2014 11:31:33 -0700 Subject: [PATCH 3/8] Add XMLHttpRequest tests and examples --- examples/Test.js | 68 +++++++++++++++ examples/streetAreaExample.js | 51 ++++++++++++ examples/testXMLHttpRequest.js | 147 +++++++++++++++++++++++++++++++++ 3 files changed, 266 insertions(+) create mode 100644 examples/Test.js create mode 100644 examples/streetAreaExample.js create mode 100644 examples/testXMLHttpRequest.js diff --git a/examples/Test.js b/examples/Test.js new file mode 100644 index 0000000000..c56c7ebdc1 --- /dev/null +++ b/examples/Test.js @@ -0,0 +1,68 @@ +// +// Test.js +// examples +// +// Created by Ryan Huffman on 5/4//14 +// Copyright 2014 High Fidelity, Inc. +// +// This provides very basic unit testing functionality. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +test = function(name, func) { + print("Running test: " + name); + + var unitTest = new UnitTest(name, func); + + try { + unitTest.run(); + print(" Success: " + unitTest.numAssertions + " assertions passed"); + } catch (error) { + print(" Failure: " + error.message); + } +}; + +AssertionException = function(expected, actual, message) { + print("Creating exception"); + this.message = message + "\n: " + actual + " != " + expected; + this.name = 'AssertionException'; +}; + +UnitTest = function(name, func) { + this.numAssertions = 0; + this.func = func; +}; + +UnitTest.prototype.run = function() { + this.func(); +}; + +UnitTest.prototype.assertNotEquals = function(expected, actual, message) { + this.numAssertions++; + if (expected == actual) { + throw new AssertionException(expected, actual, message); + } +}; + +UnitTest.prototype.assertEquals = function(expected, actual, message) { + this.numAssertions++; + if (expected != actual) { + throw new AssertionException(expected, actual, message); + } +}; + +UnitTest.prototype.assertHasProperty = function(property, actual, message) { + this.numAssertions++; + if (actual[property] === undefined) { + throw new AssertionException(property, actual, message); + } +}; + +UnitTest.prototype.assertNull = function(value, message) { + this.numAssertions++; + if (value !== null) { + throw new AssertionException(value, null, message); + } +}; diff --git a/examples/streetAreaExample.js b/examples/streetAreaExample.js new file mode 100644 index 0000000000..5e92753689 --- /dev/null +++ b/examples/streetAreaExample.js @@ -0,0 +1,51 @@ +// +// streetAreaExample.js +// examples +// +// Created by Ryan Huffman on 5/4//14 +// Copyright 2014 High Fidelity, Inc. +// +// This is an example script showing how to load JSON data using XMLHttpRequest. +// +// URL Macro created by Thijs Wenker. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var url = "https://script.google.com/macros/s/AKfycbwIo4lmF-qUwX1Z-9eA_P-g2gse9oFhNcjVyyksGukyDDEFXgU/exec?action=listOwners&domain=alpha.highfidelity.io"; +print("Loading street data from " + url); + +var req = new XMLHttpRequest(); + +// Set response type to "json". This will tell XMLHttpRequest to parse the response data as json, so req.response can be used +// as a regular javascript object +req.responseType = 'json'; + +req.open("GET", url, false); +req.send(); + +if (req.status == 200) { + for (var domain in req.response) { + print("DOMAIN: " + domain); + var locations = req.response[domain]; + var userAreas = []; + for (var i = 0; i < locations.length; i++) { + var loc = locations[i]; + var x1 = loc[1], + y1 = loc[2], + x2 = loc[3], + y2 = loc[4]; + userAreas.push({ + username: loc[0], + area: Math.abs(x2 - x1) * Math.abs(y2 - y1), + }); + } + userAreas.sort(function(a, b) { return a.area > b.area ? -1 : (a.area < b.area ? 1 : 0) }); + for (var i = 0; i < userAreas.length; i++) { + print(userAreas[i].username + ": " + userAreas[i].area + " sq units"); + } + } +} else { + print("Error loading data: " + req.status + " " + req.statusText + ", " + req.errorCode); +} diff --git a/examples/testXMLHttpRequest.js b/examples/testXMLHttpRequest.js new file mode 100644 index 0000000000..6471b2fdeb --- /dev/null +++ b/examples/testXMLHttpRequest.js @@ -0,0 +1,147 @@ +// +// testXMLHttpRequest.js +// examples +// +// Created by Ryan Huffman on 5/4//14 +// Copyright 2014 High Fidelity, Inc. +// +// XMLHttpRequest Tests +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("Test.js"); + +test("Test default request values", function(finished) { + var req = new XMLHttpRequest(); + + this.assertEquals(req.UNSENT, req.readyState, "readyState should be UNSENT"); + this.assertEquals(0, req.status, "status should be `0` by default"); + this.assertEquals("", req.statusText, "statusText should be empty string by default"); + this.assertEquals("", req.getAllResponseHeaders(), "getAllResponseHeaders() should return empty string by default"); + this.assertEquals("", req.response, "response should be empty string by default"); + this.assertEquals("", req.responseText, "responseText should be empty string by default"); + this.assertEquals("", req.responseType, "responseType should be empty string by default"); + this.assertEquals(0, req.timeout, "timeout should be `0` by default"); + this.assertEquals(0, req.errorCode, "there should be no error by default"); +}); + + +test("Test readyStates", function() { + var req = new XMLHttpRequest(); + var state = req.readyState; + + var statesVisited = [true, false, false, false, false] + + req.onreadystatechange = function() { + statesVisited[req.readyState] = true; + }; + + req.open("GET", "https://gist.githubusercontent.com/huffman/33cc618fec183d1bccd0/raw/test.json", false); + req.send(); + for (var i = 0; i <= req.DONE; i++) { + this.assertEquals(true, statesVisited[i], i + " should be set"); + } + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); +}); + +test("Test TEXT request", function() { + var req = new XMLHttpRequest(); + var state = req.readyState; + + req.open("GET", "https://gist.githubusercontent.com/huffman/33cc618fec183d1bccd0/raw/test.json", false); + req.send(); + + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertEquals(200, req.status, "status should be `200`"); + this.assertEquals(0, req.errorCode); + this.assertEquals("OK", req.statusText, "statusText should be `OK`"); + this.assertNotEquals("", req.getAllResponseHeaders(), "headers should no longer be empty string"); + this.assertNull(req.getResponseHeader('invalidheader'), "invalid header should return `null`"); + this.assertEquals("GitHub.com", req.getResponseHeader('Server'), "Server header should be GitHub.com"); + this.assertEquals('{"id": 1}', req.response); + this.assertEquals('{"id": 1}', req.responseText); +}); + +test("Test JSON request", function() { + var req = new XMLHttpRequest(); + var state = req.readyState; + + req.responseType = "json"; + req.open("GET", "https://gist.githubusercontent.com/huffman/33cc618fec183d1bccd0/raw/test.json", false); + req.send(); + + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertEquals(200, req.status, "status should be `200`"); + this.assertEquals(0, req.errorCode); + this.assertEquals("OK", req.statusText, "statusText should be `OK`"); + this.assertNotEquals("", req.getAllResponseHeaders(), "headers should no longer be empty string"); + this.assertNull(req.getResponseHeader('invalidheader'), "invalid header should return `null`"); + this.assertEquals("GitHub.com", req.getResponseHeader('Server'), "Server header should be GitHub.com"); + this.assertHasProperty('id', req.response); + this.assertEquals(1, req.response.id); + this.assertEquals('{"id": 1}', req.responseText); +}); + +test("Test Bad URL", function() { + var req = new XMLHttpRequest(); + var state = req.readyState; + + req.open("POST", "hifi://domain/path", false); + req.send(); + + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertNotEquals(0, req.errorCode); +}); + +test("Test Bad Method Error", function() { + var req = new XMLHttpRequest(); + var state = req.readyState; + + req.open("POST", "https://www.google.com", false); + + req.send("randomdata"); + + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertEquals(405, req.status); + this.assertEquals(202, req.errorCode); + + req.open("POST", "https://www.google.com", false) + req.send(); + + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertEquals(405, req.status); + this.assertEquals(202, req.errorCode); +}); + +test("Test abort", function() { + var req = new XMLHttpRequest(); + var state = req.readyState; + + req.open("POST", "https://www.google.com", true) + req.send(); + req.abort(); + + this.assertEquals(0, req.status); + this.assertEquals(0, req.errorCode); +}); + +test("Test timeout", function() { + var req = new XMLHttpRequest(); + var state = req.readyState; + var timedOut = false; + + req.ontimeout = function() { + timedOut = true; + }; + + req.open("POST", "https://gist.githubusercontent.com/huffman/33cc618fec183d1bccd0/raw/test.json", false) + req.timeout = 1; + req.send(); + + this.assertEquals(true, timedOut, "request should have timed out"); + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertEquals(0, req.status, "status should be `0`"); + this.assertEquals(4, req.errorCode, "4 is the timeout error code for QNetworkReply::NetworkError"); +}); From 5a7b133d2556bae5b9620e67b219c3e692a707a8 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 6 May 2014 11:38:12 -0700 Subject: [PATCH 4/8] Fix dates in XMLHttpRequest js files --- examples/Test.js | 2 +- examples/streetAreaExample.js | 2 +- examples/testXMLHttpRequest.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/Test.js b/examples/Test.js index c56c7ebdc1..056ec3cbbf 100644 --- a/examples/Test.js +++ b/examples/Test.js @@ -2,7 +2,7 @@ // Test.js // examples // -// Created by Ryan Huffman on 5/4//14 +// Created by Ryan Huffman on 5/4/14 // Copyright 2014 High Fidelity, Inc. // // This provides very basic unit testing functionality. diff --git a/examples/streetAreaExample.js b/examples/streetAreaExample.js index 5e92753689..deb10dd65a 100644 --- a/examples/streetAreaExample.js +++ b/examples/streetAreaExample.js @@ -2,7 +2,7 @@ // streetAreaExample.js // examples // -// Created by Ryan Huffman on 5/4//14 +// Created by Ryan Huffman on 5/4/14 // Copyright 2014 High Fidelity, Inc. // // This is an example script showing how to load JSON data using XMLHttpRequest. diff --git a/examples/testXMLHttpRequest.js b/examples/testXMLHttpRequest.js index 6471b2fdeb..421eb458e4 100644 --- a/examples/testXMLHttpRequest.js +++ b/examples/testXMLHttpRequest.js @@ -2,7 +2,7 @@ // testXMLHttpRequest.js // examples // -// Created by Ryan Huffman on 5/4//14 +// Created by Ryan Huffman on 5/4/14 // Copyright 2014 High Fidelity, Inc. // // XMLHttpRequest Tests From f3305a51e28047feaad47b43bac1ea9735af6200 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 6 May 2014 13:09:49 -0700 Subject: [PATCH 5/8] Remove XMLHttpRequestClass::escapeJavascriptString --- libraries/script-engine/src/XMLHttpRequestClass.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/script-engine/src/XMLHttpRequestClass.h b/libraries/script-engine/src/XMLHttpRequestClass.h index b1b33e4cc6..bff88d91a9 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.h +++ b/libraries/script-engine/src/XMLHttpRequestClass.h @@ -62,7 +62,6 @@ public: int getDone() const { return DONE; }; static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); - static QString escapeJavascriptString(const QString& jsString); int getTimeout() const { return _timeout; } void setTimeout(int timeout) { _timeout = timeout; } From 310f184978b4b396d698e72916b21f3187465ea4 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 May 2014 09:53:44 -0700 Subject: [PATCH 6/8] Add getters for onTimeout and onReadyStateChange --- libraries/script-engine/src/XMLHttpRequestClass.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/script-engine/src/XMLHttpRequestClass.h b/libraries/script-engine/src/XMLHttpRequestClass.h index bff88d91a9..e94c67562a 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.h +++ b/libraries/script-engine/src/XMLHttpRequestClass.h @@ -40,8 +40,8 @@ class XMLHttpRequestClass : public QObject { Q_PROPERTY(int DONE READ getDone) // Callbacks - Q_PROPERTY(QScriptValue ontimeout WRITE setOnTimeout) - Q_PROPERTY(QScriptValue onreadystatechange WRITE setOnReadyStateChange) + Q_PROPERTY(QScriptValue ontimeout READ getOnTimeout WRITE setOnTimeout) + Q_PROPERTY(QScriptValue onreadystatechange READ getOnReadyStateChange WRITE setOnReadyStateChange) public: XMLHttpRequestClass(QScriptEngine* engine); ~XMLHttpRequestClass(); @@ -74,7 +74,9 @@ public: QScriptValue getStatus() const; QString getStatusText() const; + QScriptValue getOnTimeout() const { return _onTimeout; } void setOnTimeout(QScriptValue function) { _onTimeout = function; } + QScriptValue getOnReadyStateChange() const { return _onReadyStateChange; } void setOnReadyStateChange(QScriptValue function) { _onReadyStateChange = function; } public slots: From 02676848c605643e46789767a489701e5b8286dd Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 May 2014 09:58:04 -0700 Subject: [PATCH 7/8] Fix order of components in streetAreaExample.js --- examples/streetAreaExample.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/streetAreaExample.js b/examples/streetAreaExample.js index deb10dd65a..b4efd99b70 100644 --- a/examples/streetAreaExample.js +++ b/examples/streetAreaExample.js @@ -33,8 +33,8 @@ if (req.status == 200) { for (var i = 0; i < locations.length; i++) { var loc = locations[i]; var x1 = loc[1], - y1 = loc[2], - x2 = loc[3], + x2 = loc[2], + y1 = loc[3], y2 = loc[4]; userAreas.push({ username: loc[0], From a5c10220e6d62ddcac541893f0209c9be446eb50 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 May 2014 10:16:04 -0700 Subject: [PATCH 8/8] Remove _ prefix from private methods in XMLHttpRequest --- .../script-engine/src/XMLHttpRequestClass.cpp | 34 +++++++++---------- .../script-engine/src/XMLHttpRequestClass.h | 10 +++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 77b03db999..a81f8950fa 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -63,7 +63,7 @@ QString XMLHttpRequestClass::getStatusText() const { } void XMLHttpRequestClass::abort() { - _abortRequest(); + abortRequest(); } void XMLHttpRequestClass::setRequestHeader(const QString& name, const QString& value) { @@ -76,18 +76,18 @@ void XMLHttpRequestClass::requestMetaDataChanged() { // If this is a redirect, abort the current request and start a new one if (redirect.isValid() && _numRedirects < MAXIMUM_REDIRECTS) { _numRedirects++; - _abortRequest(); + abortRequest(); QUrl newUrl = _url.resolved(redirect.toUrl().toString()); _request.setUrl(newUrl); - _doSend(); + doSend(); } } void XMLHttpRequestClass::requestDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { if (_readyState == OPENED && bytesReceived > 0) { - _setReadyState(HEADERS_RECEIVED); - _setReadyState(LOADING); + setReadyState(HEADERS_RECEIVED); + setReadyState(LOADING); } } @@ -113,7 +113,7 @@ QScriptValue XMLHttpRequestClass::getResponseHeader(const QString& name) const { return QScriptValue::NullValue; } -void XMLHttpRequestClass::_setReadyState(ReadyState readyState) { +void XMLHttpRequestClass::setReadyState(ReadyState readyState) { if (readyState != _readyState) { _readyState = readyState; if (_onReadyStateChange.isFunction()) { @@ -135,7 +135,7 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a } _request.setUrl(_url); _method = method; - _setReadyState(OPENED); + setReadyState(OPENED); } } @@ -150,7 +150,7 @@ void XMLHttpRequestClass::send(const QString& data) { _sendData->setData(data.toUtf8()); } - _doSend(); + doSend(); if (!_async) { QEventLoop loop; @@ -160,10 +160,10 @@ void XMLHttpRequestClass::send(const QString& data) { } } -void XMLHttpRequestClass::_doSend() { +void XMLHttpRequestClass::doSend() { _reply = _manager.sendCustomRequest(_request, _method.toLatin1(), _sendData); - _connectToReply(_reply); + connectToReply(_reply); if (_timeout > 0) { _timer.start(_timeout); @@ -175,9 +175,9 @@ void XMLHttpRequestClass::requestTimeout() { if (_onTimeout.isFunction()) { _onTimeout.call(QScriptValue::NullValue); } - _abortRequest(); + abortRequest(); _errorCode = QNetworkReply::TimeoutError; - _setReadyState(DONE); + setReadyState(DONE); emit requestComplete(); } @@ -203,29 +203,29 @@ void XMLHttpRequestClass::requestFinished() { _responseData = QScriptValue(QString(_rawResponseData.data())); } } - _setReadyState(DONE); + setReadyState(DONE); emit requestComplete(); } -void XMLHttpRequestClass::_abortRequest() { +void XMLHttpRequestClass::abortRequest() { // Disconnect from signals we don't want to receive any longer. disconnect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout())); if (_reply) { - _disconnectFromReply(_reply); + disconnectFromReply(_reply); _reply->abort(); delete _reply; _reply = NULL; } } -void XMLHttpRequestClass::_connectToReply(QNetworkReply* reply) { +void XMLHttpRequestClass::connectToReply(QNetworkReply* reply) { connect(reply, SIGNAL(finished()), this, SLOT(requestFinished())); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(requestDownloadProgress(qint64, qint64))); connect(reply, SIGNAL(metaDataChanged()), this, SLOT(requestMetaDataChanged())); } -void XMLHttpRequestClass::_disconnectFromReply(QNetworkReply* reply) { +void XMLHttpRequestClass::disconnectFromReply(QNetworkReply* reply) { disconnect(reply, SIGNAL(finished()), this, SLOT(requestFinished())); disconnect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); disconnect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(requestDownloadProgress(qint64, qint64))); diff --git a/libraries/script-engine/src/XMLHttpRequestClass.h b/libraries/script-engine/src/XMLHttpRequestClass.h index e94c67562a..49a952e638 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.h +++ b/libraries/script-engine/src/XMLHttpRequestClass.h @@ -93,11 +93,11 @@ signals: void requestComplete(); private: - void _setReadyState(ReadyState readyState); - void _doSend(); - void _connectToReply(QNetworkReply* reply); - void _disconnectFromReply(QNetworkReply* reply); - void _abortRequest(); + void setReadyState(ReadyState readyState); + void doSend(); + void connectToReply(QNetworkReply* reply); + void disconnectFromReply(QNetworkReply* reply); + void abortRequest(); QScriptEngine* _engine; bool _async;