From 1d1094ddcb35e1148deb6e8da470dcf0d314a3f7 Mon Sep 17 00:00:00 2001 From: Kent Hansen Date: Wed, 9 Mar 2011 23:44:59 +0100 Subject: Initialize repository --- README | 34 +++++ examples/README | 3 + examples/console.js | 3 + examples/document.js | 5 + examples/import-envjsnatives.js | 6 + examples/prototype-addclass.js | 8 ++ examples/window.js | 3 + examples/xmlhttprequest.js | 9 ++ src/plugin/plugin.cpp | 44 +++++++ src/plugin/plugin.pro | 8 ++ src/shared/envjsnatives.cpp | 275 ++++++++++++++++++++++++++++++++++++++++ src/src.pro | 2 + 12 files changed, 400 insertions(+) create mode 100644 README create mode 100644 examples/README create mode 100644 examples/console.js create mode 100644 examples/document.js create mode 100644 examples/import-envjsnatives.js create mode 100644 examples/prototype-addclass.js create mode 100644 examples/window.js create mode 100644 examples/xmlhttprequest.js create mode 100644 src/plugin/plugin.cpp create mode 100644 src/plugin/plugin.pro create mode 100644 src/shared/envjsnatives.cpp create mode 100644 src/src.pro diff --git a/README b/README new file mode 100644 index 0000000..2a7e60a --- /dev/null +++ b/README @@ -0,0 +1,34 @@ +Envjs implementation for QtScript. + +Envjs (http://www.envjs.com) is a simulated browser environment written in JavaScript. + +The code in this repository only implements the Envjs parts that need platform support. +You'll also need a file called env.js (not included in this repository), which is the +main Envjs implementation. To create it: +1. Go to http://www.envjs.com/release/envjs-1.2 and download the full release. +2. Unpack. +3. Run ant from the base Envjs directory. +4. Presto, dist/env.js is ready for evaluation. + +To use Envjs with QtScript, you'll need to +1. Evaluate env.js. +2. Call the function initializeEnvjsNatives() (in src/shared/envjsnatives.cpp). + +To achieve that, there are two major options: + +1. Fully embed Envjs in the application: Pass the contents of env.js to +QScriptEngine::evaluate(), and then call initializeEnvjsNatives(). You'll need +to add src/shared/envjsnatives.cpp to your application sources. + +2. Build the envjsnatives plugin (src/plugin) and put it in e.g. $QTDIR/plugins/script. +Then QScriptEngine::importExtension("envjs") can be used to load the native implementation. +env.js still needs to be evaluated separately before the plugin is loaded, otherwise +the plugin will throw an error. + +API-specific notes: +- In order for asynchronous XMLHttpRequest requests to work, you need to be running an +event loop. + +Tested with Envjs version 1.2. + +See also examples/README. diff --git a/examples/README b/examples/README new file mode 100644 index 0000000..c56027c --- /dev/null +++ b/examples/README @@ -0,0 +1,3 @@ +To run the document.js example with $QTDIR/examples/script/qscript: +- Make sure envjsnatives plugin is installed (in e.g. $QTDIR/plugins/script) +- Run qscript path/to/env.js import-envjsnatives.js document.js diff --git a/examples/console.js b/examples/console.js new file mode 100644 index 0000000..b170afe --- /dev/null +++ b/examples/console.js @@ -0,0 +1,3 @@ +console.log("Log output"); +console.warn("Warn output"); +console.error("Error output"); diff --git a/examples/document.js b/examples/document.js new file mode 100644 index 0000000..b0c2496 --- /dev/null +++ b/examples/document.js @@ -0,0 +1,5 @@ +console.log("document:", document); +document.write("

Hello, JavaScript in QtScript!

"); +document.close(); +console.log("p tag's innerHTML:", document.getElementsByTagName("p").item(0).innerHTML); +console.log("document.innerHTML:", document.innerHTML); diff --git a/examples/import-envjsnatives.js b/examples/import-envjsnatives.js new file mode 100644 index 0000000..f6bff3c --- /dev/null +++ b/examples/import-envjsnatives.js @@ -0,0 +1,6 @@ +// This is not an actual example, it's just a helper +// when using e.g. $QTDIR/examples/script/qscript to +// run examples. +// Evaluate this file after evaluating env.js +// to establish the full Envjs/QtScript environment. +qt.script.importExtension("envjsnatives"); diff --git a/examples/prototype-addclass.js b/examples/prototype-addclass.js new file mode 100644 index 0000000..a7ac196 --- /dev/null +++ b/examples/prototype-addclass.js @@ -0,0 +1,8 @@ +// Assumes prototype.js has been evaluated. + +document.close(); + +var my_div = document.createElement('div'); +my_div.addClassName('prototyped').hide(); +document.body.appendChild(my_div); +console.log("document.innerHTML:", document.innerHTML); diff --git a/examples/window.js b/examples/window.js new file mode 100644 index 0000000..e7c1e1b --- /dev/null +++ b/examples/window.js @@ -0,0 +1,3 @@ +console.log("window:", window); +console.log("window === this?", window === this); +console.log("window.navigator.userAgent:", window.navigator.userAgent); diff --git a/examples/xmlhttprequest.js b/examples/xmlhttprequest.js new file mode 100644 index 0000000..3afddc9 --- /dev/null +++ b/examples/xmlhttprequest.js @@ -0,0 +1,9 @@ +req = new XMLHttpRequest(); +req.onreadystatechange = function() { + print("onreadystatechange, state ==", req.readyState); + if (req.readyState == 4) { + console.log(req.responseText); + } +}; +req.open("GET", "http://labs.qt.nokia.com", false); +req.send(null); diff --git a/src/plugin/plugin.cpp b/src/plugin/plugin.cpp new file mode 100644 index 0000000..687cad8 --- /dev/null +++ b/src/plugin/plugin.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file may be used under the terms of the GNU General Public +** License version 2.0 or 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of +** this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +****************************************************************************/ + +#include +#include + +extern void initializeEnvjsNatives(QScriptEngine *); + +class EnvjsPlugin : public QScriptExtensionPlugin +{ +public: + QStringList keys() const; + void initialize(const QString &key, QScriptEngine *); +}; + +QStringList EnvjsPlugin::keys() const +{ + return QStringList() << "envjsnatives"; +} + +void EnvjsPlugin::initialize(const QString &/* key */, QScriptEngine *eng) +{ + initializeEnvjsNatives(eng); +} + +Q_EXPORT_PLUGIN2(envjsplugin, EnvjsPlugin) diff --git a/src/plugin/plugin.pro b/src/plugin/plugin.pro new file mode 100644 index 0000000..4a3f4b2 --- /dev/null +++ b/src/plugin/plugin.pro @@ -0,0 +1,8 @@ +TEMPLATE = lib +TARGET = envjsnatives +QT = core script network +CONFIG += plugin +INCLUDEPATH += . + +SOURCES += plugin.cpp +SOURCES += ../shared/envjsnatives.cpp diff --git a/src/shared/envjsnatives.cpp b/src/shared/envjsnatives.cpp new file mode 100644 index 0000000..6508f20 --- /dev/null +++ b/src/shared/envjsnatives.cpp @@ -0,0 +1,275 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file may be used under the terms of the GNU General Public +** License version 2.0 or 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of +** this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +****************************************************************************/ + +#include +#include +#include + +// Exported API. +void initializeEnvjsNatives(QScriptEngine *); + +#define ENVJS_FUNCTIONS(V) \ + V(lineSource) \ + V(eval) \ + V(loadInlineScript) \ + V(loadLocalScript) \ + V(sync) \ + V(spawn) \ + V(sleep) \ + V(loadFrame) \ + V(unloadFrame) \ + V(proxy) \ + V(getcwd) \ + V(runAsync) \ + V(writeToFile) \ + V(writeToTempFile) \ + V(readFromFile) \ + V(deleteFile) \ + V(connection) + +static QScriptValue Envjs_lineSource(QScriptContext *, QScriptEngine *) +{ + // Supposed to return the source text of the line causing the error. + //QScriptValue e = ctx->argument(0); + return "(line ?)"; +} + +static QScriptValue Envjs_eval(QScriptContext *ctx, QScriptEngine *eng) +{ + // Don't know what this is. this-object? Variable scope? + // QScriptValue scope = ctx->argument(0); + + QScriptValue source = ctx->argument(1); + QScriptValue sourceName = ctx->argument(0); + return eng->evaluate(source.toString(), sourceName.toString()); +} + +static QScriptValue Envjs_loadInlineScript(QScriptContext *ctx, QScriptEngine *) +{ + return ctx->throwError("loadInlineScript() not implemented"); +} + +static QScriptValue Envjs_loadLocalScript(QScriptContext *ctx, QScriptEngine *) +{ + return ctx->throwError("loadLocalScript() not implemented"); +} + +static QScriptValue Envjs_sync(QScriptContext *ctx, QScriptEngine *) +{ + return ctx->argument(0); +} + +static QScriptValue Envjs_spawn(QScriptContext *ctx, QScriptEngine *) +{ + QScriptValue fun = ctx->argument(0); + return fun.call(); +} + +class SleepyThread : public QThread +{ +public: + static void msleep(unsigned long msecs) + { QThread::msleep(msecs); } +}; + +static QScriptValue Envjs_sleep(QScriptContext *ctx, QScriptEngine *eng) +{ + uint msecs = ctx->argument(0).toUInt32(); + static_cast(QThread::currentThread())->msleep(msecs); + return eng->undefinedValue(); +} + +static QScriptValue Envjs_loadFrame(QScriptContext *ctx, QScriptEngine *) +{ + return ctx->throwError("loadFrame() not implemented"); +} + +static QScriptValue Envjs_unloadFrame(QScriptContext *ctx, QScriptEngine *) +{ + return ctx->throwError("unloadFrame() not implemented"); +} + +static QScriptValue Envjs_proxy(QScriptContext *ctx, QScriptEngine *) +{ + return ctx->throwError("proxy() not implemented"); +} + +static QScriptValue Envjs_getcwd(QScriptContext *, QScriptEngine *) +{ + return QDir::currentPath(); +} + +class QtScriptFunctionInvoker : public QObject +{ + Q_OBJECT +public: + QtScriptFunctionInvoker(const QScriptValue &fn, QObject *parent = 0) + : QObject(parent), fun(fn) + {} + + void invokeLater() + { + QMetaObject::invokeMethod(this, "invoke", Qt::QueuedConnection); + } + +private slots: + void invoke() + { + fun.call(); + deleteLater(); + } + +private: + QScriptValue fun; +}; + +static QScriptValue Envjs_runAsync(QScriptContext *ctx, QScriptEngine *eng) +{ + QScriptValue fun = ctx->argument(0); + QtScriptFunctionInvoker *invoker = new QtScriptFunctionInvoker(fun, eng); + invoker->invokeLater(); + return eng->undefinedValue(); +} + +static QScriptValue Envjs_writeToFile(QScriptContext *ctx, QScriptEngine *eng) +{ + QScriptValue text = ctx->argument(0); + QScriptValue url = ctx->argument(1); + QString path = QUrl(url.toString()).toLocalFile(); + QFile file(path); + file.open(QIODevice::WriteOnly); + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + stream << text.toString(); + return eng->undefinedValue(); +} + +static QScriptValue Envjs_writeToTempFile(QScriptContext *ctx, QScriptEngine *) +{ +// QScriptValue text = ctx->argument(0); +// QScriptValue suffix = ctx->argument(1); +// QString fileName = QString::fromLatin1("envjs-tmp%0").arg(suffix.toString())); +// ### Create file and write text. File should be deleted when engine is. + return ctx->throwError("writeToTempFile() not implemented"); +} + +static QScriptValue Envjs_readFromFile(QScriptContext *ctx, QScriptEngine *) +{ + QScriptValue url = ctx->argument(0); + QString path = QUrl(url.toString()).toLocalFile(); + QFile file(path); + file.open(QIODevice::ReadOnly); + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + return stream.readAll(); +} + +static QScriptValue Envjs_deleteFile(QScriptContext *ctx, QScriptEngine *eng) +{ + QScriptValue url = ctx->argument(0); + QString path = QUrl(url.toString()).toLocalFile(); + QFile file(path); + file.remove(); + return eng->undefinedValue(); +} + +static QScriptValue Envjs_connection(QScriptContext *ctx, QScriptEngine *eng) +{ + // TODO: if xhr.async is true, all this stuff should be done in a + // delayed call or different thread, then the response handler can + // be called (in this thread). + + QScriptValue xhr = ctx->argument(0); + QScriptValue responseHandler = ctx->argument(1); + QScriptValue data = ctx->argument(2); + QUrl url = QUrl(xhr.property("url").toString()); + QNetworkAccessManager nam; // TODO: get a manager from somewhere... + QNetworkRequest req(url); + + QScriptValueIterator it(xhr.property("headers")); + while (it.hasNext()) { + it.next(); + req.setRawHeader(it.name().toUtf8(), it.value().toString().toUtf8()); + } + + QString method = xhr.property("method").toString(); + QNetworkReply *rep = 0; + if (method == QLatin1String("GET")) + rep = nam.get(req); + else if (method == QLatin1String("PUT")) + rep = nam.put(req, data.toString().toUtf8()); + else if (method == QLatin1String("POST")) + rep = nam.put(req, data.toString().toUtf8()); + else if (method == QLatin1String("HEAD")) + rep = nam.head(req); + else if (method == QLatin1String("DELETE")) + rep = nam.deleteResource(req); + else + return ctx->throwError(QString::fromLatin1("request method %0 not implemented").arg(method)); + // TODO: connect to reply's download/uploadProgress(), + // implement HEADERS_RECEIVED and LOADING states + + QEventLoop loop; + QObject::connect(rep, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + + QScriptValue responseHeaders = xhr.property("responseHeaders"); + QList rawHeaderPairs = rep->rawHeaderPairs(); + for (int i = 0; i < rawHeaderPairs.size(); ++i) { + const QNetworkReply::RawHeaderPair &rhp = rawHeaderPairs.at(i); + responseHeaders.setProperty(QString::fromUtf8(rhp.first), QString::fromUtf8(rhp.second)); + } + + xhr.setProperty("readyState", 4); + QVariant statusCode = rep->attribute(QNetworkRequest::HttpStatusCodeAttribute); + if (statusCode.isValid()) + xhr.setProperty("status", statusCode.toInt()); + QVariant reasonPhrase = rep->attribute(QNetworkRequest::HttpReasonPhraseAttribute); + if (reasonPhrase.isValid()) + xhr.setProperty("statusText", reasonPhrase.toString()); + + QByteArray responseData = rep->readAll(); + xhr.setProperty("responseText", QString::fromUtf8(responseData)); + + if (responseHandler.isFunction()) + responseHandler.call(); + + return eng->undefinedValue(); +} + +void initializeEnvjsNatives(QScriptEngine *eng) +{ + QScriptValue envjs = eng->globalObject().property("Envjs"); + if (!envjs.isObject()) { + eng->currentContext()->throwError("env.js must be evaluated before natives can be initialized"); + return; + } + + envjs.setProperty("platform", "QtScript"); + + envjs.setProperty("log", eng->globalObject().property("print")); + +#define SET_ENVJS_FUNCTION(f) envjs.setProperty(#f, eng->newFunction(Envjs_##f)); + ENVJS_FUNCTIONS(SET_ENVJS_FUNCTION) +#undef SET_ENVJS_FUNCTION +} + +#include "envjsnatives.moc" diff --git a/src/src.pro b/src/src.pro new file mode 100644 index 0000000..e67a15e --- /dev/null +++ b/src/src.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS = plugin -- cgit v1.2.3