Skip to content

Commit 17928a5

Browse files
committed
Added documentation for scripting engine
1 parent c08be78 commit 17928a5

4 files changed

Lines changed: 218 additions & 1 deletion

File tree

docs/scripting/Compatibility.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Compatibility 兼容性
1+
Errors and Warnings 错误和警告
22
============================================
33
We try to replicate as much functionality as we can, but sometimes it is just
44
very hard to do. In such cases we will usually silently drop the functionality
@@ -22,6 +22,7 @@ Fatal Errors 致命性错误
2222
代码的总命名空间下,则出现错误之后的代码将不会被执行。
2323

2424
### Security Error 安全冲突
25+
试图调用被禁止的函数或者没有提供正确的Key。
2526

2627
Errors 错误
2728
---------------------------------------------

docs/scripting/Display/Readme.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
Kagerou Display Engine AS3接口实现
2+
===============================================
3+
Kagerou Display Engine 是一套通过模拟 B 站提供的元件API实现的视觉元件双向沟通与操作的API系统。
4+
它建立了一套最底线的 AS3 接口,来供程序使用并接入。Kagerou Display Engine的命名由一套HTML5的
5+
Flash运行空间 Smokescreen 启发。
6+
7+
基础框架 (Basic Framework)
8+
-----------------------------------------------
9+
基础框架由一套底层的AS3库还原而成。继承结构如下:
10+
11+
- DisplayObject["DisplayObject"]
12+
- Sprite["Sprite"]
13+
- CommentButton["Button"]
14+
- CommentBitmap["Bitmap"]
15+
- Shape["Shape"]
16+
- CommentShape
17+
- TextField["TextField"]
18+
- CommentField
19+
20+
依赖关系如下:
21+
22+
- TextField
23+
- TextFormat
24+
- DisplayObject
25+
- Transform
26+
- Matrix
27+
- Filter
28+
- Shape
29+
- Graphics
30+
- IComment
31+
- MotionManager
32+
-ITween
33+
34+
私有成员 (Private Members)
35+
-------------------------------------------------
36+
上述的各个类有可能存在私有类,即只能通过别的对象来进行访问。比如 TextField 的基础存在形式会是
37+
CommentField,而 DisplayObject 则不会自行独立出现,虽然它代表了整个底层系统的视觉元件。
38+
39+
克隆组件 (Cloning Objects)
40+
-----------------------------------------------
41+
我们不鼓励使用自建的 clone() 函数去克隆一个对象,尤其是任何视觉对象,因为所有的视觉对象都有强链接
42+
性,克隆后对副本的更改,会传达到原对象的[箱外实例 (External Instance)](../Instances.md) 导致
43+
出现对象不同步等严重而且难以调试的问题。其中包括如下:
44+
45+
- 任何继承 DisplayObject 的元件
46+
- Graphics 类
47+
- DisplayObject 的 Transform 类
48+
- MotionManager
49+
- ITween (参考 [Tween](../Tween/Readme.md )
50+
51+
对于数据类,克隆可能并没有那么大的影响,但是这些操作也应当尽可能避免,因为不知道何时会引发出影响。
52+
一些疑似安全的类:
53+
54+
- TextFormat
55+
- Filter
56+
- Matrix
57+
58+
根对象和元对象 (Root and Meta Objects)
59+
-----------------------------------------------
60+
为了模拟 AS3/BSE 的根对象我们认为一切元素的 Root 是一个 Sprite(进而是一个 DisplayObject)
61+
但是处于特殊性原因这个 Sprite 的许多功能和属性实际无法获取或设定。在这些情况下,尝试更改这些属性
62+
则会在播放器

docs/scripting/Instances.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
双重存在 Instance Shadowing
2+
=========================================
3+
为了实现沙箱与外界的联通,我们会建立每一个需要的实例在沙箱外的影子示例(Shadow Instance)。在对
4+
沙箱内的实例进行属性改写(Property Modification)和方法调用(Method Invocation)时,该操作
5+
则会在影子实例上重复执行。由于影子实例无法与沙箱内实例主动发起通讯,影子实例的更改不能同步到沙箱内:
6+
7+
Original Instance Shadow Instance
8+
(原始实例) -------通讯------> (影子实例) <====> [DOM]
9+
[沙箱内] [沙箱外]
10+
11+
强绑定与克隆 Strong Binding & Cloning
12+
-----------------------------------------
13+
鉴于这种架构设计,所有实现影子实例的类,都会与影子进行强绑定。也就是说,在沙箱内克隆影子实例产生出的
14+
克隆实例(Cloned Instance)依然会和原本的影子实例绑定。这样以来,就很可能出现不同步导致严重的
15+
BUG。
16+
17+
Original Instance Shadow Instance
18+
(原始实例) -------通讯------> (影子实例)
19+
[沙箱内] | [沙箱外]
20+
|
21+
Cloned Instance |
22+
(克隆实例) --------
23+
[沙箱内]
24+
25+
如果对 Cloned Instance 执行操作(更改属性或调用方法)改变了状态,由于影子实例无法通告原始实例,
26+
则会行程如下的链接状态:
27+
28+
Original Instance Shadow Instance*
29+
(原始实例) -------通讯------> (影子实例*)
30+
[沙箱内] | [沙箱外]
31+
|
32+
Cloned Instance* |
33+
(克隆实例*) --------
34+
[沙箱内]
35+
36+
这时虽然属性进行了更改,在 Original Instance下读取属性并不能读取到正确的属性信息。如果这时再对
37+
原始实例进行操作则会产生出如下状态:
38+
39+
Original Instance# Shadow Instance*#
40+
(原始实例#) -------通讯------> (影子实例*#)
41+
[沙箱内] | [沙箱外]
42+
|
43+
Cloned Instance* |
44+
(克隆实例*) --------
45+
[沙箱内]
46+
47+
这时无论是原始实例还是克隆实例都无法正确的表达在沙箱外的影子实例的真实状况。由于操作并未被记录,所以
48+
试图还原出外部的实际状况將近乎不可能。
49+
50+
保持安全干净的操作 (Safety Precautions)
51+
------------------------------------------
52+
在进行 `clone()` 操作时,请务必确保你克隆的对象没有强绑定。有关具体哪些对象有强绑定哪些没有,请
53+
参考相关的各个子分类。一般来说,Display系统,Player系统和Tween系统产生的元件大都有强绑定。一些
54+
例子比如,`DisplayObject``Sound``Tween``Util.interval/Timer` 定时器等都有强绑定。
55+
56+
保证安全访问的一些好的建议方法:
57+
58+
1. 对于克隆的组件只进行读取操作,或者避免克隆整个对象。大多数情况下使用引用和传递原来的对象就足够了
59+
2. 如果克隆不可避,请尽早销毁克隆前的原始对象,或者不再使用它。不过有关重制属性,可以采取
60+
`a.prop = a.prop`来确保前段显示和 a 对象分离时的原有数值一样。
61+
3. 如果不可销毁对象,则请在架构时确保你知道进行更改会引发不同步问题的可能性,并绕开危险的设计方法。
62+
63+
非同步调用设计原因 (Design Justification)
64+
------------------------------------------
65+
采取上述设计的原因在于沙箱。为了保证安全执行代码,我们采取了沙箱机制,这样的优点在于可以移植到更加
66+
广泛的平台,如NodeWebkit甚至是C/C++系列的软件。你只需引入JS运行时(如V8),然后提供Native的
67+
OOAPI接口,就可以完全自如的执行代码弹幕,同时还可以控制危险操作。
68+
69+
同时我们为了兼容性,选择了忽略更改同步性的策略。这种设计在牺牲了可控性的代价下,提高了效率并且保证了
70+
兼容性。代价是可能会产生赛跑状况(Race Condition),但是由于在worker内是单线程的,所以这些危害
71+
被大大降低了,目前主要只体现在clone下。更新属性和调用方法的流程如下:
72+
73+
更新属性:
74+
75+
1. 代码更新了属性,调用了对象的此属性字段的 setter 函数
76+
2. setter函数更新了对象内部的缓存,同时把属性值打包传递到影子实例上
77+
3. 沙箱中代码继续运行,影子实例获得到属性更改消息后则会执行相应操作
78+
79+
调用方法 :
80+
81+
1. 调用了对象的方法,方法不返回时,直接把参数打包,同时如更改属性那样更改缓存,然后传递参数
82+
2. 调用了对象的方法,有返回时,**先根据本地实例模拟一个返回结果**,递交函数到影子对象。如果影子
83+
对象判定需要更新,则影子对象会派发更新消息,注意这些都是异步的,所以无法确定更新消息会在代码执行的
84+
什么位置生效。
85+
86+

docs/scripting/Readme.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
CCLScripting API 定义 (API Definitions)
2+
===========================
3+
4+
CCLScripting 是一套在最大可能的程度上兼容Bilibili代码弹幕语法和环境的安全的高层接口。
5+
它建造于 OOAPI 底层API之上,并通过 OOAPI 与显示端进行通讯。受益于这种隔离,我们可以在
6+
CCLScripting 中运行相对不受信任的脚本弹幕而不影响整个网站的安全。
7+
8+
CCLScripting API is a set of high-level secured APIs that are modeled after
9+
the Bilibili video player scripting engine. It is build atop the OOAPI
10+
transmission layer and uses it alone to communicate with the player to perform
11+
operations. With this design we are able to run otherwise untrusted JavaScript
12+
code in a controlled environment.
13+
14+
沙箱局限性 (Sandbox Limitations)
15+
----------------------------
16+
17+
沙箱并不是完美的,它在很多方面提供了更多的可控性,其中之一就是可以直接派发 XMLHttpRequest 请求。
18+
由于这种请求依然被同域策略限制,请求只能访问Scripting的载体服务器。为了保证安全,你可以在一个子域
19+
或者另一个根域名下放置脚本代码引擎,避免你的主服务器被请求。不过值得注意的是,代码依然可以访问外部的
20+
开放资源,同时也可以借此把需要的信息向外部传递。当然,可以获得的信息仅限于通过 OOAPI 派发来的信息
21+
而已。
22+
23+
A worker sandbox is not perfect. Scripts running inside still have access to the
24+
potentially dangerous XMLHttpRequest API. To limit scripts' access to your own
25+
domain, you can choose to host the worker files on a separate subdomain or
26+
domain since the Same Origin Policy is still in effect for workers. This,
27+
however, does not prevent the Script from sending out data to a server controlled
28+
by a third party. The Script within the worker context only has as much data as
29+
you feed it through the OOAPI. So it is possible to control data leakage too.
30+
31+
兼容性挑战 (Compatibility Issues)
32+
-----------------------------
33+
34+
由于沙箱是建立在 信息传递(Messaging API) 基础上的,我们只能可靠的进行单向信息汇报。我们无法在
35+
这个API下实现可靠的阻塞式双向信息传递,所以在沙箱内的许多功能將依赖沙箱外界发来的状态报告。
36+
37+
我们举例如下:
38+
39+
Player开放了许多可以用来读取播放器状态的接口,比如 `Player.time`, `Player.state` 等。由于我们
40+
无法在脚本读取这些属性是现去请求外部,所以我们必须使用缓存的数据。即,外部定期会同步状态到沙箱内
41+
而更细的信息则会被沙箱脑补。比如当播放器传来 Play/Timeupdate信息时,`Player`会更新内部的状态信息
42+
而在第二个消息到来前,读取各种状态则会被`Player`脑补。
43+
44+
如果 `Player.state` 在播放中状态,那么在两个 `timeupdate` 之间,读取`Player.time`则会返回上次的
45+
`playtime + 从上次timeupdate 到现在所经过的时差`
46+
47+
同样, 由于无法高效获取外部的刷新率,`enterFrame`事件的广播则会完全由沙箱内部接管。不过这个广播的
48+
派发频率可以由 `$.frameRate` 调整。更加详细的区别会在各个API接口处表示出来。
49+
50+
Due to the CCLScripting Engine using only the messaging API. We cannot reliably
51+
ask for synchronous state information and expect a prompt reply. This means that
52+
we must use cached information at times.
53+
54+
Whenever we resort to using cached information due to a synchronous API, we will
55+
try to compensate the diffence in cache if possible. For example, within the
56+
`Player` api, there are many properties that show information about the player's
57+
state. Most of these fields are cached. As a specific example, `Player.time` is
58+
updated whenver a timeupdate is issued into the sandbox. Between the two issued
59+
timeupdates, requesting for `Player.time` will return you the previous timestamp
60+
plus the difference in time between when you recieved it and the current time.
61+
62+
This may cause the time to slightly decrease as timeupdates are recieved.
63+
64+
Similarly, the broadcast event 'enterFrame' is also completely virtual as it is
65+
not efficient enough to send drawing state into the sandbox at high frequencies.
66+
More details on these compatibility problems are described in the corresponding
67+
API docs.
68+

0 commit comments

Comments
 (0)