Skip to main content

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

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,但是看起来是可行的。

Comments

Subscribe to our blog

Try Qt 6.11 Now!

Download the latest release here: www.qt.io/download

Qt 6.11 is now available, with new features and improvements for application developers and device creators.

We're Hiring

Check out all our open positions here and follow us on Instagram to see what it's like to be #QtPeople.