From 17d71b413f24dcbc232f672383e6461e08cd4e4f Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Thu, 19 Nov 2015 05:39:07 +0100 Subject: [PATCH] Implement Tomahawk.ajax native implementation on behalf of NativeScriptJobs --- data/js/tomahawk.js | 106 +++++++++--------- src/libtomahawk/resolvers/JSAccount.cpp | 16 +++ src/libtomahawk/resolvers/JSAccount.h | 2 + .../resolvers/JSResolverHelper.cpp | 37 ++++-- src/libtomahawk/resolvers/JSResolverHelper.h | 21 ++-- src/libtomahawk/resolvers/ScriptAccount.h | 3 +- 6 files changed, 108 insertions(+), 77 deletions(-) diff --git a/data/js/tomahawk.js b/data/js/tomahawk.js index 34cbefa90..7149b5dae 100644 --- a/data/js/tomahawk.js +++ b/data/js/tomahawk.js @@ -430,6 +430,23 @@ Tomahawk.nativeAsyncRequestDone = function (reqId, xhr) { delete Tomahawk.asyncRequestCallbacks[reqId]; }; + + +/** + * This method is externalized from Tomahawk.asyncRequest, so that other clients + * (like tomahawk-android) can inject their own logic that determines whether or not to do a request + * natively. + * + * @returns boolean indicating whether or not to do a request with the given parameters natively + */ +var shouldDoNativeRequest = function (options) { + var extraHeaders = options.headers; + return (extraHeaders && (extraHeaders.hasOwnProperty("Referer") + || extraHeaders.hasOwnProperty("referer") + || extraHeaders.hasOwnProperty("User-Agent"))); +}; + + /** * Possible options: * - method: The HTTP request method (default: GET) @@ -439,59 +456,45 @@ Tomahawk.nativeAsyncRequestDone = function (reqId, xhr) { * - data: body data included in POST requests * - needCookieHeader: boolean indicating whether or not the request needs to be able to get the * "Set-Cookie" response header + * - headers: headers set on the request */ -Tomahawk.asyncRequest = function (url, callback, extraHeaders, options) { - // unpack options - var opt = options || {}; - var method = opt.method || 'GET'; +var doRequest = function(options) { + if (shouldDoNativeRequest(options)) { + return Tomahawk.NativeScriptJobManager.invoke('httpRequest', options).then(function(xhr) { + xhr.responseHeaders = xhr.responseHeaders || {}; + xhr.getAllResponseHeaders = function() { + return this.responseHeaders; + }; + xhr.getResponseHeader = function (header) { + return this.responseHeaders[header]; + }; - if (shouldDoNativeRequest(url, callback, extraHeaders, options)) { - // Assign a request Id to the callback so we can use it when we are - // returning from the native call. - var reqId = Tomahawk.asyncRequestIdCounter; - Tomahawk.asyncRequestIdCounter++; - Tomahawk.asyncRequestCallbacks[reqId] = { - callback: callback, - errorHandler: opt.errorHandler - }; - Tomahawk.nativeAsyncRequest(reqId, url, extraHeaders, options); + return xhr; + }); } else { - var xmlHttpRequest = new XMLHttpRequest(); - xmlHttpRequest.open(method, url, true, opt.username, opt.password); - if (extraHeaders) { - for (var headerName in extraHeaders) { - xmlHttpRequest.setRequestHeader(headerName, extraHeaders[headerName]); - } - } - xmlHttpRequest.onreadystatechange = function () { - if (xmlHttpRequest.readyState == 4 - && httpSuccessStatuses.indexOf(xmlHttpRequest.status) != -1) { - callback.call(window, xmlHttpRequest); - } else if (xmlHttpRequest.readyState === 4) { - Tomahawk.log("Failed to do " + method + " request: to: " + url); - Tomahawk.log("Status Code was: " + xmlHttpRequest.status); - if (opt.hasOwnProperty('errorHandler')) { - opt.errorHandler.call(window, xmlHttpRequest); + return new RSVP.Promise(function(resolve, reject) { + var xmlHttpRequest = new XMLHttpRequest(); + xmlHttpRequest.open(options.method, options.url, true, options.username, options.password); + if (options.headers) { + for (var headerName in options.headers) { + xmlHttpRequest.setRequestHeader(headerName, options.headers[headerName]); } } - }; - xmlHttpRequest.send(opt.data || null); + xmlHttpRequest.onreadystatechange = function () { + if (xmlHttpRequest.readyState == 4 + && httpSuccessStatuses.indexOf(xmlHttpRequest.status) != -1) { + resolve(xmlHttpRequest); + } else if (xmlHttpRequest.readyState === 4) { + Tomahawk.log("Failed to do " + options.method + " request: to: " + options.url); + Tomahawk.log("Status Code was: " + xmlHttpRequest.status); + reject(xmlHttpRequest); + } + }; + xmlHttpRequest.send(options.data || null); + }); } }; -/** - * This method is externalized from Tomahawk.asyncRequest, so that other clients - * (like tomahawk-android) can inject their own logic that determines whether or not to do a request - * natively. - * - * @returns boolean indicating whether or not to do a request with the given parameters natively - */ -var shouldDoNativeRequest = function (url, callback, extraHeaders, options) { - return (extraHeaders && (extraHeaders.hasOwnProperty("Referer") - || extraHeaders.hasOwnProperty("referer") - || extraHeaders.hasOwnProperty("User-Agent"))); -}; - Tomahawk.ajax = function (url, settings) { if (typeof url === "object") { settings = url; @@ -549,10 +552,7 @@ Tomahawk.ajax = function (url, settings) { } } - return new RSVP.Promise(function (resolve, reject) { - settings.errorHandler = reject; - Tomahawk.asyncRequest(settings.url, resolve, settings.headers, settings); - }).then(function (xhr) { + return doRequest(settings).then(function (xhr) { if (settings.rawResponse) { return xhr; } @@ -876,12 +876,17 @@ Tomahawk.PluginManager = { } }; + +var encodeParamsToNativeFunctions = function(param) { + return param; +}; + Tomahawk.NativeScriptJobManager = { idCounter: 0, deferreds: {}, invoke: function (methodName, params) { var requestId = this.idCounter++; - Tomahawk.invokeNativeScriptJob(requestId, methodName, JSON.stringify(params)); + Tomahawk.invokeNativeScriptJob(requestId, methodName, encodeParamsToNativeFunctions(params)); this.deferreds[requestId] = RSVP.defer(); return this.deferreds[requestId].promise; }, @@ -891,6 +896,7 @@ Tomahawk.NativeScriptJobManager = { Tomahawk.log("Deferred object with the given requestId is not present!"); } deferred.resolve(result); + delete this.deferreds[requestId]; } }; diff --git a/src/libtomahawk/resolvers/JSAccount.cpp b/src/libtomahawk/resolvers/JSAccount.cpp index 772f0cf9e..5f666c568 100644 --- a/src/libtomahawk/resolvers/JSAccount.cpp +++ b/src/libtomahawk/resolvers/JSAccount.cpp @@ -170,6 +170,22 @@ JSAccount::syncInvoke( const scriptobject_ptr& scriptObject, const QString& meth return evaluateJavaScriptWithResult( eval ); } +void +JSAccount::reportNativeScriptJobResult( int resultId, const QVariantMap& result ) +{ + QString eval = QString( + "Tomahawk.NativeScriptJobManager.reportNativeScriptJobResult(" + "%1," // requestId + "%2" // results + ");" + ).arg( resultId ) + .arg( serializeQVariantMap( result ) ); + + // Remove when new scripting api turned out to work reliably + tDebug( LOGVERBOSE ) << Q_FUNC_INFO << eval; + + evaluateJavaScript( eval ); +} QVariant JSAccount::evaluateJavaScriptInternal( const QString& scriptSource ) diff --git a/src/libtomahawk/resolvers/JSAccount.h b/src/libtomahawk/resolvers/JSAccount.h index 9bff73b38..6965de07d 100644 --- a/src/libtomahawk/resolvers/JSAccount.h +++ b/src/libtomahawk/resolvers/JSAccount.h @@ -71,6 +71,8 @@ public: static QString serializeQVariantMap(const QVariantMap& map); + void reportNativeScriptJobResult( int resultId, const QVariantMap& result ) override; + private: /** * Wrap the pure evaluateJavaScript call in here, while the threadings guards are in public methods diff --git a/src/libtomahawk/resolvers/JSResolverHelper.cpp b/src/libtomahawk/resolvers/JSResolverHelper.cpp index 8bc7bbd89..773ae0a69 100644 --- a/src/libtomahawk/resolvers/JSResolverHelper.cpp +++ b/src/libtomahawk/resolvers/JSResolverHelper.cpp @@ -47,6 +47,8 @@ #include #include #include +#include + #include #include #include @@ -627,12 +629,24 @@ JSResolverHelper::nativeRetrieveMetadata( int metadataId, const QString& url, } } +void +JSResolverHelper::invokeNativeScriptJob( int requestId, const QString& methodName, const QVariantMap& params ) +{ + if ( methodName == "httpRequest" ) { + nativeAsyncRequest( requestId, params ); + } else { + // TODO: make promise reject instead + Q_ASSERT_X(false, "invokeNativeScriptJob", "NativeScriptJob methodName was not found"); + } +} + void -JSResolverHelper::nativeAsyncRequest( const int requestId, const QString& url, - const QVariantMap& headers, - const QVariantMap& options ) +JSResolverHelper::nativeAsyncRequest( const int requestId, const QVariantMap& options ) { + QString url = options[ "url" ].toString(); + QVariantMap headers = options[ "headers" ].toMap(); + QNetworkRequest req( url ); foreach ( const QString& key, headers.keys() ) { @@ -688,17 +702,16 @@ JSResolverHelper::nativeAsyncRequestDone( int requestId, NetworkReply* reply ) map["status"] = reply->reply()->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt(); map["statusText"] = QString("%1 %2").arg( map["status"].toString() ) .arg( reply->reply()->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString() ); - if (reply->reply()->hasRawHeader( "Content-Type" )) - map["contentType"] = reply->reply()->rawHeader( "Content-Type" ); - bool ok = false; - QString json = QString::fromUtf8( TomahawkUtils::toJson( map, &ok ) ); - Q_ASSERT( ok ); - QString javascript = QString( "Tomahawk.nativeAsyncRequestDone( %1, %2 );" ) - .arg( QString::number( requestId ) ) - .arg( json ); - m_resolver->d_func()->scriptAccount->evaluateJavaScript( javascript ); + QVariantMap responseHeaders; + foreach( const QNetworkReply::RawHeaderPair& pair, reply->reply()->rawHeaderPairs() ) + { + responseHeaders[ pair.first ] = pair.second; + } + map["responseHeaders"] = responseHeaders; + + m_resolver->d_func()->scriptAccount->reportNativeScriptJobResult( requestId, map ); } diff --git a/src/libtomahawk/resolvers/JSResolverHelper.h b/src/libtomahawk/resolvers/JSResolverHelper.h index 57b963e39..8790cb49b 100644 --- a/src/libtomahawk/resolvers/JSResolverHelper.h +++ b/src/libtomahawk/resolvers/JSResolverHelper.h @@ -86,20 +86,9 @@ public: int sizehint, const QVariantMap& options ); - /** - * Native handler for asynchronous HTTP requests. - * - * This handler shall only be used if we cannot achieve the request with - * XMLHttpRequest as that would be more efficient. - * Use cases are: - * * Referer header: Stripped on MacOS and the specification says it - * should be stripped - * - * INTERNAL USE ONLY! - */ - Q_INVOKABLE void nativeAsyncRequest( int requestId, const QString& url, - const QVariantMap& headers, - const QVariantMap& options ); + Q_INVOKABLE void invokeNativeScriptJob( int requestId, + const QString& methodName, + const QVariantMap& params ); /** * Lucene++ indices for JS resolvers @@ -145,6 +134,10 @@ private: bool indexDataFromVariant( const QVariantMap& map, struct Tomahawk::IndexData& indexData ); QVariantList searchInFuzzyIndex( const Tomahawk::query_ptr& query ); + // native script jobs + void nativeAsyncRequest( int requestId, const QVariantMap& options ); + + QVariantMap m_resolverConfig; JSResolver* m_resolver; QString m_scriptPath; diff --git a/src/libtomahawk/resolvers/ScriptAccount.h b/src/libtomahawk/resolvers/ScriptAccount.h index 1c9890d9e..c773fe0fe 100644 --- a/src/libtomahawk/resolvers/ScriptAccount.h +++ b/src/libtomahawk/resolvers/ScriptAccount.h @@ -65,13 +65,14 @@ public: ScriptJob* invoke( const scriptobject_ptr& scriptObject, const QString& methodName, const QVariantMap& arguments ); virtual QVariant syncInvoke( const scriptobject_ptr& scriptObject, const QString& methodName, const QVariantMap& arguments ) = 0; - virtual void startJob( ScriptJob* scriptJob ) = 0; void reportScriptJobResult( const QVariantMap& result ); void registerScriptPlugin( const QString& type, const QString& objectId ); void unregisterScriptPlugin( const QString& type, const QString& objectId ); + virtual void reportNativeScriptJobResult( int resultId, const QVariantMap& result ) = 0; + virtual void scriptPluginFactory( const QString& type, const scriptobject_ptr& object ); QList< Tomahawk::result_ptr > parseResultVariantList( const QVariantList& reslist );