Back to Blog home

在QtScript中伪造一个网页浏览器

Published on 星期日 三月 06, 2011 by Liang Qi in Internet Qt Qt Script qt-labs-chinese | Comments

原文链接:Kent Hansen - Faking a web browser environment in QtScript

下面这个问题最近在qt-interest邮件列表中经常被问到:我如何才能在QtScript中调试运行[一些任意的]JavaScript代码(通常是包含在一个网页中的)呢?这是一个有趣的题目,我忍不住在这里回答一下。

短答案:您不能。

长答案:这取决于这段脚本在做什么——更精确地,这个脚本使用了哪些JavaScript API。试着使用QScriptEngine::evaluate()来调试运行这段脚本,并且看看是否会得到一个ReferenceError;如果没有,那么你就完成了!但是JavaScript代码通常会被期望运行在一个完整的JavaScript环境中。这个环境是ECMA-262标准环境的一个超集,而QtScript在这一点上完成地很好。JavaScript脚本会期待DOM API的存在。脚本还会期待window对象和document对象的存在。脚本也许会检查window.navigator.userAgent来试图决定一些常用属性的呈现(尽管这一点非常无用)。诸如此类。如果缺少了其中的一部分,那么在一个通常的QScriptEngine中调试运行JavaScript代码就会得到一个参考错误,错误信息类似与:"Can't find variable: window"。该做些什么呢?

伪造它

我们可以试图伪造一个环境。这个思路就是只实现所需的JavaScript API,然后我们想运行的这些脚本就可以运行了。这其实也就是QtScript的Context2D实例所做的。另外HTML5 Canvas API中的绝大部分,就是实现了DOM API(包括基本的事件处理——在这里特别感谢Zack写出了这么酷的实例)的一个子集,因此网上绝大多数脚本就都可以在不修改的情况下运行了。

这个伪造的/部分的环境既可以通过QtScript代码实现,也可以通过本地对象(QObject和/或函数指针)实现;这取决于您的使用案例。但通常情况下,过程如下:

  1. 创建一个QtScript环境(QScriptEngine)。
  2. 添加“伪造的”对象和属性来满足JavaScript代码的需要。
  3. 调试运行这段JavaScript代码。
  4. 如果您得到任何参考错误,请重复这个过程。

一个实例

为了演示上述方案,让我们来考虑一个真实的使用案例。es5conform是一个ECMA-262一致性套件;它可以检查一个ECMA-262实现与标准的一致性如何。如果您下载这个测试套件,您将会注意到所提供的测试运行器是一个网页(runtests.html)。让我们使它运行在QtScript中。这是完全可能的,因为这些测试本身只是JavaScript中ECMA-262的一部分。并且测试结果可以体现有关QtScript一致性水平的有价值信息。

这里是runtests.html的一些基础部分:

<script type="text/javascript" src="SimpleTestHarness/sth.js"></script>
<script>
var ES5Harness = activeSth;
</script>
...
<script type="text/javascript" src="TestCases/chapter11/11.4/11.4.1/11.4.1-3-3.js"></script>
<script type="text/javascript" src="TestCases/chapter11/11.4/11.4.1/11.4.1-4.a-1.js"></script>
<script type="text/javascript" src="TestCases/chapter11/11.4/11.4.1/11.4.1-4.a-10.js"></script>
...
<script>
ES5Harness.startTesting();
</script>

打开SimpleTestHarness/sth.js并且研究一下,那么很显然它是一个用于注册并且运行测试的小库。测试本身被存储在不同的.js文件中(在TestCases/下)。所以工作计划就是使得es5conform可以在QtScript中运行,具体如下:

  1. 调试运行SimpleTestHarness/sth.js
  2. 把值activeSth赋给全局变量ES5Harness
  3. 调试运行TestCases/下的一个或者多个.js文件。
  4. 调用ES5Harness.startTesting()函数。

使用QtScript Shell

上面的步骤可以通过QtScript Shell应用程序(Qt中的examples/script/qscript)实现,它只是一个基本的QScriptEngine::evaluate()的单机封装,您可以把脚本传给它。试一下第一步:

qscript SimpleTestHarness/sth.js
    ReferenceError: Can't find variable: window
<anonymous>()@SimpleTestHarness/sth.js:295

结果就是这段脚本使用了全局的window变量来获得这个全局对象的参考。我们创建一个包含如下内容的pre.js文件:

window = this;

当上述语句在全局代码中被调试运行时,this将会是这个全局对象的一个参考。我们再次运行qscript,但是把pre.js放在sth.js之前:

qscript pre.js SimpleTestHarness/sth.js
    ReferenceError: Can't find variable: document
<anonymous>()@SimpleTestHarness/sth.js:259

好的,一个新错误。有问题的脚本代码行是

    this.resultsDiv = document.createElement("div");

让我们添加一个伪造的document对象到pre.js中:

document = {
    createElement: function(tagName) {
        return { nodeName: tagName };
    }
};

在经过几次迭代之后,sth.js将会被成功调试运行。现在创建一个post.js文件:

ES5Harness = activeSth;

run.js

ES5Harness.startTesting();

最后,我们可以运行一个实际的测试:

qscript pre.js SimpleTestHarness/sth.js post.js TestCases/chapter11/11.4/11.4.1/11.4.1-0-1.js run.js

没有错误,但也没有输出!

输出处理/后处理

看一看sth.js中有关测试结果报告的代码,输出是使用如下定义的println()函数生成的:

sth.prototype.println = function (s) {
    this.innerHTML += s;
    this.innerHTML += "<BR/>";
}

再一次的,网页导向的。我们可以使用QtScript Shell内置的print()函数来替换它(例如在post.js中),或者在测试结束之后把ES5Harness对象的innerHTML属性输出出来。第三个选项是把innerHTML定义为一个getter/setter属性(像一个Qt/C++属性一样),所以只要这个属性被赋值的时候,setter函数就会被调用了。

一个更加灵活的方案是完全地忽略HTML输出并且自己处理测试结果的内部呈现;在这种方式下,我们可以获得对输出格式的完全控制。看一看sth.prototype.report()函数,我们知道了如何访问每一个测试记录的描述和结果属性。举个例子,把它们放在一起,es5conform就可以很容易地被集成到Qt的自动测试系统中(我们所完成的Qt和Mozilla以及V8的测试套件一部分,是用类似方式工作的)。

现在我们可以把es5conform封装到一个使用QScriptEngine::evaluate()以及其它相关函数的Qt应用程序中,这样就可以做任何事情了,这样也就移除了对QtScript Shell应用程序的依赖。如果您需要或者想用本地代码实现任意API,这么做也是必需的。

结论

在QtScript中调试运行任意的JavaScript代码的原则很简单:发现脚本是如何工作的,并且实现(“伪造”)所需的JavaScript API。这个技术也适用于把JavaScript导入到QML中;创建带有ID以及与JavaScript可访问属性匹配的属性的元素。关于后面情况的一个实例,请看看QTBUG-8576(很棒的缺陷报告!)中所附的qmlfbench.tar.gz

下一步,看看类似于prototype.js(使得在QtScript/QML中使用Class.create()以及其它成为可能)的“伪造的”环境。这还需要一些DOM API,但是看起来是可行的。

Subscribe to Our Blog

Stay up to date with the latest marketing, sales and service tips and news.