diff --git a/src/filesystem.cpp b/src/filesystem.cpp index 0b4f932e..61c635fb 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -38,7 +38,7 @@ // File // public: File::File(QFile *openfile, QTextCodec *codec, QObject *parent) : - REPLCompletable(parent), + QObject(parent), m_file(openfile), m_fileStream(0) { @@ -213,23 +213,11 @@ void File::close() deleteLater(); } -void File::initCompletions() -{ - // Add completion for the Dynamic Properties of the 'file' object - // functions - addCompletion("read"); - addCompletion("write"); - addCompletion("readLine"); - addCompletion("writeLine"); - addCompletion("flush"); - addCompletion("close"); -} - // FileSystem // public: FileSystem::FileSystem(QObject *parent) - : REPLCompletable(parent) + : QObject(parent) { } // public slots: @@ -487,39 +475,3 @@ bool FileSystem::_remove(const QString &path) const bool FileSystem::_copy(const QString &source, const QString &destination) const { return QFile(source).copy(destination); } - -void FileSystem::initCompletions() -{ - // Add completion for the Dynamic Properties of the 'fs' object - // properties - addCompletion("separator"); - addCompletion("workingDirectory"); - // functions - addCompletion("list"); - addCompletion("absolute"); - addCompletion("readLink"); - addCompletion("exists"); - addCompletion("isDirectory"); - addCompletion("isFile"); - addCompletion("isAbsolute"); - addCompletion("isExecutable"); - addCompletion("isReadable"); - addCompletion("isWritable"); - addCompletion("isLink"); - addCompletion("changeWorkingDirectory"); - addCompletion("makeDirectory"); - addCompletion("makeTree"); - addCompletion("removeDirectory"); - addCompletion("removeTree"); - addCompletion("copyTree"); - addCompletion("open"); - addCompletion("read"); - addCompletion("write"); - addCompletion("size"); - addCompletion("remove"); - addCompletion("copy"); - addCompletion("move"); - addCompletion("touch"); - addCompletion("join"); - addCompletion("split"); -} diff --git a/src/filesystem.h b/src/filesystem.h index aa4ded10..7d5c22e6 100644 --- a/src/filesystem.h +++ b/src/filesystem.h @@ -36,9 +36,7 @@ #include #include -#include "replcompletable.h" - -class File : public REPLCompletable +class File : public QObject { Q_OBJECT @@ -66,16 +64,13 @@ public slots: void flush(); void close(); -private: - virtual void initCompletions(); - private: QFile *m_file; QTextStream *m_fileStream; }; -class FileSystem : public REPLCompletable +class FileSystem : public QObject { Q_OBJECT Q_PROPERTY(QString workingDirectory READ workingDirectory) @@ -139,9 +134,6 @@ public slots: bool isReadable(const QString &path) const; bool isWritable(const QString &path) const; bool isLink(const QString &path) const; - -private: - virtual void initCompletions(); }; #endif // FILESYSTEM_H diff --git a/src/phantom.cpp b/src/phantom.cpp index 21854ead..307dcbc4 100644 --- a/src/phantom.cpp +++ b/src/phantom.cpp @@ -54,7 +54,7 @@ static Phantom *phantomInstance = NULL; // private: Phantom::Phantom(QObject *parent) - : REPLCompletable(parent) + : QObject(parent) , m_terminated(false) , m_returnValue(0) , m_filesystem(0) @@ -471,24 +471,3 @@ void Phantom::doExit(int code) m_page = 0; QApplication::instance()->exit(code); } - -void Phantom::initCompletions() -{ - // Add completion for the Dynamic Properties of the 'phantom' object - // properties - addCompletion("args"); - addCompletion("defaultPageSettings"); - addCompletion("libraryPath"); - addCompletion("outputEncoding"); - addCompletion("scriptName"); - addCompletion("version"); - addCompletion("cookiesEnabled"); - addCompletion("cookies"); - // functions - addCompletion("exit"); - addCompletion("debugExit"); - addCompletion("injectJs"); - addCompletion("addCookie"); - addCompletion("deleteCookie"); - addCompletion("clearCookies"); -} diff --git a/src/phantom.h b/src/phantom.h index e1e88965..49bed7ff 100644 --- a/src/phantom.h +++ b/src/phantom.h @@ -37,7 +37,6 @@ #include "filesystem.h" #include "encoding.h" #include "config.h" -#include "replcompletable.h" #include "system.h" #include "childprocess.h" @@ -45,7 +44,7 @@ class WebPage; class CustomPage; class WebServer; -class Phantom: public REPLCompletable +class Phantom : public QObject { Q_OBJECT Q_PROPERTY(QStringList args READ args) @@ -181,7 +180,6 @@ private slots: private: void doExit(int code); - virtual void initCompletions(); Encoding m_scriptFileEnc; WebPage *m_page; diff --git a/src/phantomjs.pro b/src/phantomjs.pro index 92e1e7b4..e6f5de87 100644 --- a/src/phantomjs.pro +++ b/src/phantomjs.pro @@ -26,8 +26,7 @@ HEADERS += csconverter.h \ encoding.h \ config.h \ childprocess.h \ - repl.h \ - replcompletable.h + repl.h SOURCES += phantom.cpp \ callback.cpp \ @@ -45,8 +44,7 @@ SOURCES += phantom.cpp \ encoding.cpp \ config.cpp \ childprocess.cpp \ - repl.cpp \ - replcompletable.cpp + repl.cpp OTHER_FILES += \ bootstrap.js \ diff --git a/src/repl.cpp b/src/repl.cpp index e2321698..5ac69da2 100644 --- a/src/repl.cpp +++ b/src/repl.cpp @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include "consts.h" #include "terminal.h" @@ -78,6 +80,52 @@ REPL *REPL::getInstance(QWebFrame *webframe, Phantom *parent) return singleton; } +QString REPL::_getClassName(QObject *obj) const +{ + const QMetaObject *meta = obj->metaObject(); + + return QString::fromLatin1(meta->className()); +} + +QStringList REPL::_enumerateCompletions(QObject *obj) const +{ + const QMetaObject *meta = obj->metaObject(); + QMap completions; + + // List up slots, signals, and invokable methods + const int methodOffset = meta->methodOffset(); + const int methodCount = meta->methodCount(); + for (int i = methodOffset; i < methodCount; i++) { + const QString name = QString::fromLatin1(meta->method(i).signature()); + // Ignore methods starting with underscores + if (name.startsWith('_')) { + continue; + } + // Keep only up to, but not including, first paren + const int cutoff = name.indexOf('('); + completions.insert((0 < cutoff ? name.left(cutoff) : name), true); + } + + // List up properties + const int propertyOffset = meta->propertyOffset(); + const int propertyCount = meta->propertyCount(); + for (int i = propertyOffset; i < propertyCount; i++) { + const QMetaProperty prop = meta->property(i); + // Ignore non-scriptable properties + if (!prop.isScriptable()) { + continue; + } + const QString name = QString::fromLatin1(prop.name()); + // Ignore properties starting with underscores + if (name.startsWith('_')) { + continue; + } + completions.insert(name, true); + } + + return completions.uniqueKeys(); +} + // private: REPL::REPL(QWebFrame *webframe, Phantom *parent) : QObject(parent), @@ -101,6 +149,9 @@ REPL::REPL(QWebFrame *webframe, Phantom *parent) // Inject REPL utility functions m_webframe->evaluateJavaScript(Utils::readResourceFileUtf8(":/repl.js"), QString("phantomjs://repl.js")); + // Add self to JavaScript world + m_webframe->addToJavaScriptWindowObject("_repl", this); + // Start the REPL's loop QTimer::singleShot(0, this, SLOT(startLoop())); } diff --git a/src/repl.h b/src/repl.h index dfd1582b..dbea5957 100644 --- a/src/repl.h +++ b/src/repl.h @@ -58,6 +58,9 @@ public: static bool instanceExists(); static REPL *getInstance(QWebFrame *webframe = NULL, Phantom *parent = NULL); + Q_INVOKABLE QString _getClassName(QObject *obj) const; + Q_INVOKABLE QStringList _enumerateCompletions(QObject *obj) const; + private: REPL(QWebFrame *webframe, Phantom *parent); static void offerCompletion(const char *buf, linenoiseCompletions *lc); diff --git a/src/repl.js b/src/repl.js index aca03996..ee92b06f 100644 --- a/src/repl.js +++ b/src/repl.js @@ -31,6 +31,13 @@ var REPL = REPL || {}; +(function () { + +/** + * Cache to hold completions + */ +var _cache = {}; + /** * Return the Completions of the Object, applying the prefix * @@ -40,16 +47,45 @@ var REPL = REPL || {}; REPL._getCompletions = function (obj, prefix) { var completions = []; - // If the given object is "(REPL)Completable", just return it's completions - if (obj._isCompletable && obj._isCompletable() === true) { - completions = obj._getCompletions(prefix || ""); + if (typeof prefix !== "string") { + prefix = ""; } else { - // It's a JS Native Object: build the list of completions manually - for (k in obj) { - if (obj.hasOwnProperty(k) && k.indexOf(prefix || "") === 0) { - completions.push(k); + prefix = prefix.trim(); + } + + try { + // Try to get `QObject` inherited class's name (throws exception if not + // inherited from `QObject`) + var className = _repl._getClassName(obj); + + // Initialize completions for this class as needed + if (null == _cache[className]) { + _cache[className] = _repl._enumerateCompletions(obj); + } + + var key = className; + if ("" !== prefix) { + key = "-" + prefix; + if (null == _cache[key]) { + // Filter out completions + var regexp = new RegExp("^" + prefix); + _cache[key] = _cache[className].filter(function (elm) { + return regexp.test(elm); + }); } } + completions = _cache[key]; + } catch (e) { + try { + Object.keys(obj).forEach(function (k) { + if (obj.hasOwnProperty(k) && k.indexOf(prefix) === 0) { + completions.push(k); + } + }); + completions.sort(); + } catch (e) { + // Ignore... + } } return completions; @@ -67,14 +103,12 @@ REPL._expResStringifyReplacer = function (k, v) { mock = {}, funcToStr = "[Function]"; - // If the result of the last evaluated expression is a REPLCompletable object + // If the result of the last evaluated expression is an object if (k === "" //< only first level of recursive calls - && REPL._lastEval - && REPL._lastEval._isCompletable - && REPL._lastEval._isCompletable() === true) { + && REPL._lastEval) { - // Get all the completions for the REPLCompletable object we are going to pretty-print - iarr = REPL._lastEval._getCompletions(""); + // Get all the completions for the object we are going to pretty-print + iarr = REPL._getCompletions(REPL._lastEval); for (i in iarr) { if (typeof(v[iarr[i]]) !== "undefined") { // add a reference to this "real" property into the mock object @@ -96,3 +130,5 @@ REPL._expResStringifyReplacer = function (k, v) { return v; }; + +})(); diff --git a/src/replcompletable.cpp b/src/replcompletable.cpp deleted file mode 100644 index 0216e437..00000000 --- a/src/replcompletable.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "replcompletable.h" - -// public: -REPLCompletable::REPLCompletable(QObject *parent) - : QObject(parent), - mCompletionsInitialised(false) -{ } - -REPLCompletable::~REPLCompletable() -{ } - -bool REPLCompletable::_isCompletable() -{ - return true; -} - -QStringList REPLCompletable::_getCompletions(const QString &prefix) -{ - // First time this method is invoked, initialise the completions (lazy init) - if (!mCompletionsInitialised) { - initCompletions(); - mCompletionsInitialised = true; - } - - // If no prefix provided? - if (prefix.isEmpty()) { - // Return all the possible completions - return REPLCompletable::getCompletionsIndex()->values( - this->metaObject()->className()); - } - - // make a key to store the (new) completions list - QString cacheKey = QString("%1-%2").arg(this->metaObject()->className()).arg(prefix); - // If a list of completion withi this key is not already in the cache - if (!getCompletionsCache()->contains(cacheKey)) { - // Loop over the completions and pick the one that match the given prefix - QStringList allCompletions = REPLCompletable::getCompletionsIndex()->values( - this->metaObject()->className()); - QStringList *matchingPrefixCompletions = new QStringList(); - - QStringList::iterator i; - for (i = allCompletions.begin(); i != allCompletions.end(); ++i) { - if (((QString) *i).startsWith(prefix)) { - matchingPrefixCompletions->append((QString) *i); - } - } - - // Store the result in the cache - getCompletionsCache()->insert(cacheKey, matchingPrefixCompletions); - } - - return *(getCompletionsCache()->object(cacheKey)); -} - -// protected: -void REPLCompletable::addCompletion(const char *completion) -{ - addCompletion(QString(completion)); -} - -void REPLCompletable::addCompletion(QString completion) -{ - // Accept a completion only if it's unique per MetaObject ClassName - if (!REPLCompletable::getCompletionsIndex()->contains(this->metaObject()->className(), completion)) { - REPLCompletable::getCompletionsIndex()->insert(this->metaObject()->className(), completion); - } -} - -// private: -QMultiHash *REPLCompletable::getCompletionsIndex() -{ - static QMultiHash *compIndex = NULL; - if (!compIndex) { - compIndex = new QMultiHash(); - } - return compIndex; -} - -QCache *REPLCompletable::getCompletionsCache() -{ - static QCache *compCache = NULL; - if (!compCache) { - compCache = new QCache(); - } - return compCache; -} diff --git a/src/replcompletable.h b/src/replcompletable.h deleted file mode 100644 index ecd8f313..00000000 --- a/src/replcompletable.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef REPLCOMPLETABLE_H -#define REPLCOMPLETABLE_H - -#include -#include -#include - -/** - * This subclass of QObject is used by the REPL to better control - * what is "shown" of a QObject exposed in the Javascript Runtime. - * - * By default the JS environment will see all the slots and the Q_INVOKABLE - * of a "exposed" QObject. But also some extra QObject specific methods - * that, in our case, we prefer not to list during REPL autocompletion - * listing or expression result prettyfication. - */ -class REPLCompletable : public QObject -{ - Q_OBJECT - -public: - REPLCompletable(QObject *parent = 0); - virtual ~REPLCompletable(); - - Q_INVOKABLE bool _isCompletable(); - Q_INVOKABLE QStringList _getCompletions(const QString &prefixToComplete); - -protected: - /** - * Used by sublcasses to register a possible "completion". - * - * @param completion Array of characters representing a function/property - * that will be listed as possible completion - */ - void addCompletion(const char *completion); - /** - * Used by sublcasses to register a possible "completion". - * - * @param completion String representing a function/property - * that will be listed as possible completion - */ - void addCompletion(QString completion); - -private: - /** - * This is where subclasses should use REPLCompletable#addCompletion(...) - * to declare/register their completions for the REPL. - * This ensures that ONLY if a REPL is actually requested by the user, - * we bother registering the completion strings. - */ - virtual void initCompletions() = 0; - - static QMultiHash *getCompletionsIndex(); - static QCache *getCompletionsCache(); - -private: - bool mCompletionsInitialised; -}; - -#endif // REPLCOMPLETABLE_H diff --git a/src/system.cpp b/src/system.cpp index 263d8e6d..738542dd 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -38,7 +38,7 @@ #include "../env.h" System::System(QObject *parent) : - REPLCompletable(parent) + QObject(parent) , m_stdout((File *)NULL) , m_stderr((File *)NULL) , m_stdin((File *)NULL) @@ -201,16 +201,3 @@ QObject *System::_stdin() { return m_stdin; } - -void System::initCompletions() -{ - addCompletion("pid"); - addCompletion("args"); - addCompletion("env"); - addCompletion("platform"); - addCompletion("os"); - addCompletion("isSSLSupported"); - addCompletion("stdin"); - addCompletion("stdout"); - addCompletion("stderr"); -} diff --git a/src/system.h b/src/system.h index 46f78d83..677ac752 100644 --- a/src/system.h +++ b/src/system.h @@ -37,11 +37,10 @@ #include #include "filesystem.h" -#include "replcompletable.h" // This class implements the CommonJS System/1.0 spec. // See: http://wiki.commonjs.org/wiki/System/1.0 -class System : public REPLCompletable +class System : public QObject { Q_OBJECT Q_PROPERTY(qint64 pid READ pid) @@ -81,7 +80,6 @@ private: QStringList m_args; QVariant m_env; QMap m_os; - virtual void initCompletions(); File *m_stdout; File *m_stderr; File *m_stdin; diff --git a/src/webpage.cpp b/src/webpage.cpp index 7dd3f4d2..33a61272 100644 --- a/src/webpage.cpp +++ b/src/webpage.cpp @@ -295,7 +295,7 @@ private: WebPage::WebPage(QObject *parent, const QUrl &baseUrl) - : REPLCompletable(parent) + : QObject(parent) , m_navigationLocked(false) , m_mousePos(QPoint(0, 0)) , m_ownsPages(true) @@ -1451,59 +1451,4 @@ void WebPage::setupFrame(QWebFrame *frame) injectCallbacksObjIntoFrame(frame == NULL ? m_mainFrame : frame, m_callbacks); } -void WebPage::initCompletions() -{ - // Add completion for the Dynamic Properties of the 'webpage' object - // properties - addCompletion("clipRect"); - addCompletion("content"); - addCompletion("libraryPath"); - addCompletion("settings"); - addCompletion("viewportSize"); - addCompletion("ownsPages"); - addCompletion("windowName"); - addCompletion("pages"); - addCompletion("pagesWindowName"); - addCompletion("frameName"); - addCompletion("framesName"); - addCompletion("framesCount"); - addCompletion("cookies"); - // functions - addCompletion("evaluate"); - addCompletion("includeJs"); - addCompletion("injectJs"); - addCompletion("open"); - addCompletion("release"); - addCompletion("render"); - addCompletion("renderBase64"); - addCompletion("sendEvent"); - addCompletion("uploadFile"); - addCompletion("getPage"); - addCompletion("switchToFrame"); - addCompletion("switchToMainFrame"); - addCompletion("switchToParentFrame"); - addCompletion("switchToFocusedFrame"); - addCompletion("addCookie"); - addCompletion("deleteCookie"); - addCompletion("clearCookies"); - addCompletion("setContent"); - // callbacks - addCompletion("onAlert"); - addCompletion("onCallback"); - addCompletion("onPrompt"); - addCompletion("onConfirm"); - addCompletion("onFilePicker"); - addCompletion("onConsoleMessage"); - addCompletion("onInitialized"); - addCompletion("onLoadStarted"); - addCompletion("onLoadFinished"); - addCompletion("onResourceRequested"); - addCompletion("onResourceReceived"); - addCompletion("onUrlChanged"); - addCompletion("onNavigationRequested"); - addCompletion("onError"); - addCompletion("onPageCreated"); - addCompletion("onClosing"); -} - #include "webpage.moc" diff --git a/src/webpage.h b/src/webpage.h index 927408e6..657bc061 100644 --- a/src/webpage.h +++ b/src/webpage.h @@ -36,8 +36,6 @@ #include #include -#include "replcompletable.h" - class Config; class CustomPage; class WebpageCallbacks; @@ -45,7 +43,7 @@ class NetworkAccessManager; class QWebInspector; class Phantom; -class WebPage: public REPLCompletable, public QWebFrame::PrintCallback +class WebPage : public QObject, public QWebFrame::PrintCallback { Q_OBJECT Q_PROPERTY(QString title READ title) @@ -492,8 +490,6 @@ private: bool javaScriptConfirm(const QString &msg); bool javaScriptPrompt(const QString &msg, const QString &defaultValue, QString *result); - virtual void initCompletions(); - private: CustomPage *m_customWebPage; NetworkAccessManager *m_networkAccessManager; diff --git a/src/webserver.cpp b/src/webserver.cpp index d9f10818..9c603543 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -93,7 +93,7 @@ static void *callback(mg_event event, } WebServer::WebServer(QObject *parent) - : REPLCompletable(parent) + : QObject(parent) , m_ctx(0) { setObjectName("WebServer"); @@ -268,23 +268,11 @@ bool WebServer::handleRequest(mg_event event, mg_connection *conn, const mg_requ return true; } -void WebServer::initCompletions() -{ - // Add completion for the Dynamic Properties of the 'webpage' object - // properties - addCompletion("clipRect"); - // functions - addCompletion("listen"); - addCompletion("close"); - // callbacks - addCompletion("onNewRequest"); -} - //BEGIN WebServerResponse WebServerResponse::WebServerResponse(mg_connection* conn, QSemaphore* close) - : REPLCompletable() + : QObject() , m_conn(conn) , m_statusCode(200) , m_headersSent(false) @@ -472,15 +460,4 @@ void WebServerResponse::setHeaders(const QVariantMap &headers) m_headers = headers; } -void WebServerResponse::initCompletions() -{ - // Add completion for the Dynamic Properties of the 'webpage' object - // properties - addCompletion("statusCode"); - addCompletion("headers"); - // functions - addCompletion("writeHead"); - addCompletion("write"); -} - //END WebServerResponse diff --git a/src/webserver.h b/src/webserver.h index 9a4a9f92..1cbb5642 100644 --- a/src/webserver.h +++ b/src/webserver.h @@ -36,7 +36,6 @@ #include #include "mongoose.h" -#include "replcompletable.h" class Config; @@ -47,7 +46,7 @@ class WebServerResponse; * * see also: modules/webserver.js */ -class WebServer : public REPLCompletable +class WebServer : public QObject { Q_OBJECT Q_PROPERTY(QString port READ port) @@ -84,9 +83,6 @@ signals: public: bool handleRequest(mg_event event, mg_connection *conn, const mg_request_info *request); -private: - virtual void initCompletions(); - private: mg_context *m_ctx; QString m_port; @@ -99,7 +95,7 @@ private: /** * Outgoing HTTP response to client. */ -class WebServerResponse : public REPLCompletable +class WebServerResponse : public QObject { Q_OBJECT @@ -146,9 +142,6 @@ public slots: /// set all headers void setHeaders(const QVariantMap &headers); -private: - virtual void initCompletions(); - private: mg_connection *m_conn; int m_statusCode;