具体说来,有几个不同的代码修改层次。
最底层上layer树硬件合成加速部分的修改,在这一层你可以做到的是让某个图层显示往上或者往下一点,同时页面的JS代码根本不知道这一点;但是要注意,页面交互所依赖的HitTest作用在这一层,一个最顶上的RenderLayer对象最先hitTest测试。所以你要确保不会影响原来的hitTest逻辑,思路就是维护两套索引:用于显示的,和用于model的。
接下来可以修改RenderObject层。实际上,这一层正是layout的核心。你需要了解不同RenderObject的OOP层次结构。譬如文本对应RenderText,图像对应RenderImage。不同之处在于,当文本重新布局时,RenderText所拥有的InlineTextBox可能删除重建;而DOM元素的display属性变化时,render对象的局部render子树的结构也可能发生变化。这一层做修改时,除非你深刻了解WebKit的layout原理,否则可能需要使用DRT(DumpRenderTree)这样的工具来查看render子树的结构。render树的某个子树属性修改完后,可以setNeedsLayout,下一次layout回调时就会触发啊重现layout。注意:触发重新layout并不会重新应用解析CSS字符串数据。
实际上,当前Web规范只是针对DOM树的,也就是说,JS只能通过设置DOM元素的style来改变外观显示。最近有一个未成型的CSSOM规范,但是仅仅针对CSS computedStyle,还没有达到能够以JS任意操纵修改render树的级别,事实上我希望这能够做到。譬如CSS selector都能够借助LLVM编译为机器代码,性能上应该没有什么问题。
接下来则是RenderObject关联的RenderStyle,实际上就是CSS computedStyle。由于它们是从DOM CSS层的字符串解析得到的数据,同时又是依赖于RenderObject的,修改这一层可能需要特殊的注意。
然后就到了DOM层了,在浏览器内核里直接修改DOM看似没有必要,理论上来讲,应该可以通过注入JavaScript代码(userscript类的插件)来做到,问题是某些事件回调、特殊的状态值从W3C所定义的JS/DOM API不一定能获取到,或者能够获取到但比较麻烦,所以在内核中直接修改DOM Element对象的style属性的做法还是很有必要的。记住这里设置的都是字符串类型的CSS取值,实际上设置的值还需要进一步的解析应用。好处就是你不需要担心如何一致的应用setNeedsStyleRecacle这样的状态位。缺点就是,性能上可能不是最优的。
浏览器内核也有类似Linux内核的某种上下文概念。在Linux内核驱动中,你不可以在中断上下文中调用可引起睡眠的函数,——那样会导致死锁从而触发内核panic。浏览器内核也有类似的概念,你需要理解的是当前代码运行在哪个线程,一般来说,A线程不能修改B线程拥有的状态。几个基本的线程:主UI/事件回调、JS、网络IO等等。有的时候不能直接发起嵌套函数调用,而是需要先设置状态,然后等待下一次layout回调时应用。
最后就是直接从应用外层的WebView接口注入JS的方法了。实际上这一层也能做到许多事情,比方说,你不能通过DOM API来直接修改某个文本对象的显示(当然可以修改整个文本所在的包含块,但没办法直接修改某个字符),但是可以间接做到:通过首先包装一个Range对象,然后再将此Range对象放到一个新的div元素里。这么做最大的问题是改变了DOM树的结构,不过还是可以接受的。