编译自己的chrome(一)

起步

去官方文档,根据提示下载,安装https://chromium.googlesource.com/chromium/src/+/main/docs/android_build_instructions.md

默认打包

在gn输出文件夹,out/Default/args.gn, args.gn文件必须添加这两行,否则按照官方文档会卡住,因为官方默认把warin作为error然后中断,还有禁止lint这样可以加快编译速度。

1
2
treat_warnings_as_errors = false
disable_android_lint = true

打包完成

经过4-5个小时会生成,/chromium/src/out/Default/apks/ChromePublic.apk, 直接安装即可

第一步修改

编译自己Chrome,主要是安装官方的chrome功能比起桌面版太少。希望可以加extensions功能,还有样式,安卓chrome的标签切换真的反人类。
先改个app名称,下面路径修改app名称MyChromium

1
chrome/android/java/res_chromium_base/values/channel_constants.xml

第二步修改

修改out/Default/args.gn,添加支持enable_extensions,这里会遇到很多gn文件报错,因为开启编译extensions库,但是extensions库里面很多插件不支持安卓系统。先把gn里面一些# assert(!is_android)类似代码删掉,这是gn生成nijia文件成功,编译仍然失败。下一步解决nijia编译错误。

1
2
3
4
5
6
7
8
9
10
11
12
target_os = "android"
target_cpu = "arm64" # See "Figuring out target_cpu" below

is_component_build = false
symbol_level = 1
enable_nacl = false
is_clang = true

treat_warnings_as_errors = false
disable_android_lint = true

enable_extensions = true

第三步修改

SameSite

SameSite 是HTTP响应头 Set-Cookie 的属性之一。它允许您声明该Cookie是否仅限于第一方或者同一站点上下文。

SameSite 接受下面三个值:

  • Lax
    Cookies允许与顶级导航一起发送,并将与第三方网站发起的GET请求一起发送。这是浏览器中的默认值。

  • Strict
    Cookies只会在第一方上下文中发送,不会与第三方网站发起的请求一起发送。

  • None
    Cookie将在所有上下文中发送,即允许跨域发送。

chrome 2020年8月11日 全量一个新特性。https://www.chromium.org/updates/same-site 以前 None 是默认值,但最近的浏览器版本将 Lax 作为默认值,以便对某些类型的跨站请求伪造 (CSRF) 攻击具有相当强的防御能力。

以前跨域设置cookie

Set-Cookie: flavor=choco;

现在跨域设置cookie

Set-Cookie: flavor=choco; SameSite=None; Secure,

1
Secure cookie仅通过HTTPS协议加密发送到服务器。请注意,不安全站点(http:)无法使用 Secure 指令设置cookies。

ts复习

typescript基础类型

Boolean | Number | String | Array | Tuple | Enum | Unknown | Any | Void | Null | Undefined | Never | Object

Interfaces

1
2
3
4
5
6
7
interface Point {
readonly id: number;
readonly name?: string;
}
let p1: Point = { x: 10, y: undefined };
p1.x = 5; // error!
Cannot assign to 'x' because it is a read-only property.

Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Point {
readonly id: number;
readonly name?: string;
constructor(name: string, id: number)
constructor(name: string)
constructor(name?: any, id?: any) {
if (id == null) {
this.id = Math.random();
} else {
this.id = id;
}
this.name = name;
}
}

let a = new Point("world");
let b = new Point("world", Math.random());

泛型 Generics

泛型是编程语言中可以大量减少代码的手段

1
2
3
4
5
6
7
8
9
10
11
12
interface HttpResponseA {
data: {
name: string;
};
code: number;
}
interface HttpResponseB {
data: {
age: number;
};
code: number;
}

优化

1
2
3
4
5
6
interface HttpResponse<T> {
data: T;
code: number;
}
type ResponseA = HttpResponse<{name: string}>;
type ResponseB = HttpResponse<{age: number}>;

类也可以加入泛型

联合类型 Union Types

1
2
type ENV = "production" | "development";
function run(e: ENV) {}

关键字

  • typeof,typeof 操作符可以用来获取一个变量或对象的类型。

    1
    2
    let x = {name : 1};
    type X = typeof x; // X = {name : string}
  • keyof, keyof 操作符提取其属性的名称

    1
    2
    type X = { name: string, age: number };
    type Y = keyof X; // Y = "name" | "age"
  • extends + infer, T extends U ? X : Y 跟 JS 中的条件表达式一样,如果extends语句为真,则取X类型 ,反之得到Y类型 。我们这里把X称为条件类型的真分支,Y 称为假分支。infer表示在 extends 条件语句中待推断的类型变量。

    1
    2
    3
    4
    type ReturnType<T> = T extends (...args: any[]) => infer P ? P : any;

    type Func = () => User;
    type Test = ReturnType<Func>; // Test = User

类型推导

  • 1.定义一个输入和输出一样的函数
1
declare const format: <T>(a: T) => T;
  • 2.取出Promise返回的值
1
declare const getPromse: <T>(p: Promise<T>) => T;
  • 3.定义一个函数,入参仅仅Promise<number|string> 返回值也是 对应的类型
1
2
3
4
5
// 一般写法
declare const getPromse: (p: Promise<number|string>) => number|string;

// 正确写法
declare const getPromse: <T extends string | number>(p: Promise<T>) => T;
  • 4.取出函数第一个参数的类型

    1
    2
    3
    type Func = (a: number, ...args: any[]) => void
    type GetFirstArgument<T extends (...args: any[]) => any> = T extends (a: infer P, ...args: any[]) => any ? P : any;
    type FirstArgument = GetFirstArgument<Func>;
  • 5.把一个类型全部属性变只读

    1
    2
    3
    4
    5
    6
    export type ReadonlyObject<T> = { readonly [K in keyof T]: T[K] };
    type A = { name: string };
    let a = { name: "123" } as A;
    a.name = "345";
    let b: ReadonlyObject<A> = a as ReadonlyObject<A>;
    b.name = "123"; //error
  • 6.根据第一参数,限制第二个参数类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function test<T>(value: T, padding: T extends string ? number : never) {}
    test("Hello world", 1);

    interface Obj {
    a: {
    q: number,
    };
    b: {
    w: number,
    }
    }
    function test2<T extends keyof Obj>(value: T, padding: Obj[T]) { }
    test2('a', { q: 1 });
  • 7.义一个不可变的对象,和深度不可变的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Primitive = undefined | null | boolean | string | number | Function;
export type Immutable<T> = T extends Primitive
? T
: T extends Array<infer U>
? ReadonlyArray<U>
: /* T extends Map<infer K, infer V> ? ReadonlyMap<K, V> : // es2015+ only */
ReadonlyObject<T>;


export type DeepImmutable<T> = T extends Primitive
? T
: T extends Array<infer U>
? DeepImmutableArray<U>
: /* T extends Map<infer K, infer V> ? DeepImmutableMap<K, V> : // es2015+ only */
DeepImmutableObject<T>;


type DeepImmutableArray<T> = ReadonlyArray<DeepImmutable<T>>;

type DeepImmutableObject<T> = { readonly [K in keyof T]: DeepImmutable<T[K]> };

export type ReadonlyObject<T> = { readonly [K in keyof T]: T[K] };

/* type DeepImmutableMap<K, V> = ReadonlyMap<DeepImmutable<K>, DeepImmutable<V>> // es2015+ only */

用rust写前端(一)

背景

看了下yew的源码,想学习下。顺便探究下,实现更高性能react,虽然知道,把js换成wasm性能可能不会太大提升,主要是dom操作影响性能。想到的优化是,直接跑在native环境,直接调用servo渲染引擎渲染。

步骤

  • 实现一个过程宏,解决jsx语法解析如
1
react!(<div>asf</div>)

嵌套

1
2
let a = react!(<div>asf</div>);
dbg!(react!(<div>123<div>qwr{ "asdf" }{a}</div></div>));

插入attribute

1
dbg!(react!(<div style={{width:"120px"}}>123<div>qwr{ "asdf" }</div></div>));
  • 实现解析vdom,通过dom渲染
  • 优化

github

用rust写前端(二)

rust 过程宏使用

设计

html虚拟节点的枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
pub enum HtmlVNode {
Element(Box<HtmlElement>), // <div>1</div>
HtmlString(Box<HtmlString>), // "asdf"
Block(Box<HtmlBlock>), // {} 作用域
Empty, // 空
}
pub struct HtmlElement {
name: String,
props: ElementProps,
children: HtmlElementChildren,
}
pub struct HtmlElementChildren(Vec<HtmlVNode>);
1
2
let a = react!(<div>asf</div>);
dbg!(react!(<div>123<div>qwr{ "asdf" }{{a}}</div></div>));

=>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Node(
VNode {
name: "div",
children: [
Text(
"123",
),
Node(
VNode {
name: "div",
children: [
Text(
"qwr",
),
Text(
"asdf",
),
Node(
VNode {
name: "div",
children: [
Text(
"asf",
),
],
},
),
],
},
),
],
},
)

swc-compiler初次使用

swc-compiler是一个用rust实现的js/ts编译器,类似babel 或者 typescript编译器

这个是deno中用的ts编辑成js工具,研究了下,它号称是babel编译速度是18倍。下面是一个项目babel改swc编译

使用前

使用后

总结

swc-compiler 性能有优化,但是没有官方说的那么明显。

deno wsl下从源码安装

deno从源码安装。官方安装文档。推荐使用linux或wsl环境。

fetch FAIL之类的错误,墙的问题。v8的依赖拉不下来。不过可以使用代理。

在用户目录创建一个.boto文件。然后用export NO_AUTH_BOTO_CONFIG=”~/.boto”,脚本会读取这个NO_AUTH_BOTO_CONFIG的环境变量的配置。

[Boto]
proxy=127.0.0.1
proxy_port=8123

注意:这里8123端口的代理要用http代理。不能用sock5。所以,要用polipo,这是一个命令行代理工具。安装一下。

sccache安装

这个又依赖openssl,又要安装sudo apt-get install pkg-config libssl-dev

这样前置操作搞完后,应该可以。

还有一个坑是,vscode-remote 开发使用的时候。项目下的文件夹不能执行 mv copy 操作。会阻止构建。关闭vscode就行。

hypergl error分类

写程序的经常遇到很多错误,异常。rust社区把错误分成3类,失败,预期的错误,恐慌,我觉得很好。在hypergl中我也这么分类。

失败

比如网络请求,当请求失败会抛出一个错误,这里分类成一个失败。失败是允许的,并且允许重试,不会阻止程序继续运行。

预期的错误

这个是我们在写程序中提前预料的错误。如,检查程序,函数输入,如果用户输入错误。提示用户更改。

这也是告诉用户不用慌,一切都在意料之中,按提示改就好了。

未知的错误(恐慌)

这个在其他语言中叫异常,或者恐慌。就是程序中出现致命错误,而且,还是意料之外的。一般会直接挂掉程序。这时可以要用户提issue了

hypergl新的开始——wasm-math库

hypergl 的核心部分,打算用rust重写。如math,模型加载和渲染部分。

wasm-math(还在开发中)。测试下,矩阵栈的计算大概有10倍多的性能提升。wasm2.0加入SIMD后,性能还会更高。

以后还会使用opengl重写渲染部分,最后编译到webgl。这样更有利于跨平台。js只是作为脚本语言写逻辑。

WebAssembly未来

这段时间再次研究了下wasm,这次应该不是狼来了。

什么是WASM

WebAssembly是一种新的编码方式,可以在现代的网络浏览器中运行 - 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如C / C ++等语言提供一个编译目标,以便它们可以在Web上运行。它也被设计为可以与JavaScript共存,允许两者一起工作。

这是MDN的解释,它原本的目标是在web中运行的,和js一样。不过后来js也有了node,拥有跨平台的能力。但是,就算js能写native,js的弱点是不能避免的。比如,性能问题,多线程,就算是在浏览器环境,它的问题也很大,webgl这么多年都没发展起来,它的性能问题也是原因之一(强行让js背下锅)。

因此,这一切为啥不用wasm,在浏览器环境,js优势还是很明显的。在wasm2.0还未实现前。如果wasm2.0出来,wasm和js都是浏览器的一等公民了,甚至比js地位更高。

1.比如直接操作dom,调用浏览器的api。现在都是绑定到js,还要转换成js值类型,虽然wasm快,但是这么一来性能优势没那么明显了。

2.wasm2.0支持SIMD,这个在矩阵运算等方面。有2-4倍的提升。

3.wasm2.0支持多线程。

什么是WASI

上面说了,wasm在浏览器未来对比js的优势。还没说native。现在,一些大佬已经看中了WebAssembly的优势(性能高,安全,可移植性),如rust团队,开发WebAssembly的native接口,未来wasm可以运行在native。WASI将覆盖与POSIX相同的接口,包括文件,网络,时钟和随机数,Opengl等内容。与POSIX不用的是,WebAssembly是沙箱。这意味着代码无法直接与操作系统通信。这一点类似浏览器。

Promise<WebAssembly.Instance> WebAssembly.instantiate(module, importObject); importObject这个是声明需要导入函数。在wasi中,声明 需要请求网络,读写文件等权限。这样一个wasi程序执行需要的权限一目了然。类似苹果,安卓的权限管理。这一点其实也和deno很像,看来大佬都想到一块去了。

wasm作为一种抽象的汇编码,可移植性,高性能不用说了。

HTTP / 1.1性能提升

减少DNS查找

每个主机名解析都需要网络往返,对请求施加延迟并在查找过程中阻止请求。使用dns-prefetch。

减少HTTP请求

消除页面上不必要的资源。

使用CDN(Content Delivery Network)

在地理位置上将数据定位在更靠近客户端的位置可以显着减少每个TCP连接的网络延迟并提高吞吐量。

添加Expires标头并配置ETag

应缓存相关资源,以避免在每个页面上重新请求相同的字节。Expires标头可用于指定对象的缓存生存期,允许直接从用户的缓存中检索它并完全消除HTTP请求。ETag和Last-Modified标头提供了有效的缓存重新验证机制 - 实际上是指纹或上次更新的时间戳。

Gzip

在客户端和服务器之间传输时,所有基于文本的资产都应使用Gzip进行压缩。平均而言,Gzip将文件大小减小60-80%,这使其成为更简单的(服务器上的配置标志)和您可以做的高效益优化之一。

避免HTTP重定向

HTTP重定向可能非常昂贵,尤其是当它们将客户端重定向到不同的主机名时,这会导致额外的DNS查找,TCP连接延迟等。

域分片

现代浏览器同域,只允许6个并行连接。

js 错误类型

  • EvalError 本对象代表了一个关于 eval 函数的错误.此异常不再会被JavaScript抛出,但是EvalError对象仍然保持兼容性.
    EvalError 不在当前ECMAScript规范中使用,因此不会被运行时抛出. 但是对象本身仍然与规范的早期版本向后兼容
  • InternalError 对象表示出现在JavaScript引擎内部的错误。 例如:

“InternalError: too much recursion”(内部错误:递归过深)。

  • RangeError 对象标明一个错误,当一个值不在其所允许的范围或者集合中。

  • ReferenceError(引用错误) 对象代表当一个不存在的变量被引用时发生的错误。

    try {

    var a = undefinedVariable;

    } catch (e) {

    console.log(e); // ReferenceError: undefinedVariable is not defined
    console.log(e instanceof ReferenceError); // true

    }

  • SyntaxError 对象代表尝试解析语法上不合法的代码的错误。

    try {

    eval('hoo bar');

    } catch (e) {

    console.log(e);   // SyntaxError: Unexpected identifier
    console.log(e instanceof SyntaxError); // true

    }

  • TypeError(类型错误) 对象用来表示值的类型非预期类型时发生的错误。

    try {

    var a={}
    a.asd()

    } catch (e) {

    console.log(e); // TypeError: a.asd is not a function
    console.log(e instanceof TypeError); // true

    }

  • URIError 对象用来表示以一种错误的方式使用全局URI处理函数而产生的错误。

    try {

    decodeURIComponent('%');

    } catch (e) {

    console.log(e);  // URIError: URI malformed
    console.log(e instanceof URIError); // true

    }

pbr 材质实现

PBR(Physically Based Rendering),基于物理的渲染目的便是为了使用一种更符合物理学规律的方式来模拟光线。这种渲染方式与我们原来的Phong或者Blinn-Phong光照算法相比总体上看起来要更真实一些。

判断一种PBR光照模型是否是基于物理的,必须满足以下三个条件(不用担心,我们很快就会了解它们的):

  • 基于微平面(Microfacet)的表面模型。
  • 能量守恒。
  • 应用基于物理的BRDF。

chrome OffscreenCanvas

最近在,写hypergl例子的时候,发现webgl不能稳定60fps。明明每帧的计算时间约2ms,远远小于16ms。用chrome分析,发现主线程中,很多chrome插件注入页面,都在主线程中执行,浪费了时间,而且主线程还管理页面各种渲染,css加载。导致帧数浮动。上次看chrome 更新日志,发现了这个OffscreenCanvas。使用webworker更新canvas,这个真的有用。

使用难点

  • 因为在webworker不能访问dom,window等。需要用postMessage通信才能解决。不过我觉得还是值得的。因为,webgl项目,只会用到一个canvas dom对象。不过事件需要通过postMessage传递。

  • webworker中Image不能用。用fetch。这样加载图片。

    fetch(url).then(b => b.blob()).then(blob => {
             return createImageBitmap(blob); // ImageBitmap
    });

opengl 逆矩阵的意义

比如把点(1, 1)变到点(2, 2),可以有两种做法:

①坐标系不动,点动,把(1, 1)点挪到(2, 2);

②点不动,坐标系动,让x轴的度量(单位向量)变成原来的1/2,让y轴的度量(单位向量)变成原先的1/2,这样点还是那个点,点的坐标就变成(2, 2)了(用通俗的话说,先把点拿开,把空间挤压成1/2,再放回原位。)。

这是应该困惑了,向量还是原样,但是,坐标变了,其实,这是指在第二个坐标系下,点坐标为(2,2)。

向量这个东西客观存在,但是要把它表示出来,就要把它放在一个坐标系中去度量它,然后把度量的结果(向量在各个坐标轴上的投影值)按一定顺序列在一起,就成了我们平时所见的向量表示形式。你选择的坐标系不同,得出来的向量的表示就不同。向量还是那个向量,选择的坐标系不同,其表示方式就不同。

假设第一个坐标系矩阵为m1.第二个坐标系矩阵为m2。m1为单位矩阵。

let m1 = new Mat4();
let m2 = new Mat4();
m2.setScale(0.5, 0.5, 0.5);
let p1 = new Vec4(1, 1, 0, 1);
let p2 = new Vec4(2, 2, 0, 1);
expect(m1.mulVec4(p1)).toEqual(m2.mulVec4(p2));
expect(p2).toEqual(m2.invert().mulVec4(p1)); // p1在乘以m2坐标系逆矩阵,得出在m2坐标系下的坐标

使用逆矩阵,可以求出一个点在另一个坐标系的位置。

一个使用js/typescirpt隐藏问题。

下面这段代码,看似没什么问题。但是会报错,如下Uncaught TypeError: Cannot read property 'a' of undefined,这里会执行两次speak函数。第一次执行的时候,在Animal的构造函数,this.speak(),执行的却是Snake中的speak()。但是abc这是还没有初始化。

class Animal {
    constructor(public name: string) {
        this.speak();
    }
    speak() {

    }
}
class Snake extends Animal {
    abc={ 
        a:123
    }
    constructor(name: string) {
        super(name);
        this.speak();
    }
    speak() {
        console.log(this.abc.a);
    }
}
let sam = new Snake("Sammy the Python");

WebGLRenderingContext.getContextAttributes()

WebGL渲染,设置模板缓冲发现没有正确显示。即使gl.enable(gl.STENCIL_TEST)了。还有设置。

var gl = canvas.getContext("webgl",{stencil:true});

getContext可以有两个参数。contextAttributes如下。

alpha: 指示Canvas是否含有透明通道,若设置为false不透明,如果Canvas下叠加了其他元素时,可以在绘制时提升一些性能
antialias: 绘制时是否开启抗锯齿功能
depth: 是否开启深度缓冲功能
failIfMajorPerformanceCaveat: true表示当系统性能较低时,将不允许创建context。也就是是getContext()返回null。
powerPreference: 向用户代理提示,指示GPU的哪种配置适合WebGL上下文。
      "default" | "high-performance" | "low-power"
premultipliedAlpha: 这个功能做图形渲染的应该很熟悉,将alpha通道预先乘入rgb通道内,以提高合成性能,一两句话说不清,具体自己谷歌一下吧。
preserveDrawingBuffer: 是否保留缓冲区数据,如果你需要读取像素,或者复用绘制到主屏幕上的图像(实现脏矩形局部刷新),需要开启这个,否则浏览器在发生新的绘制操作的时候,有可能清空以前的数据。
stencil: 是否开启模板缓冲功能

默认的参数

{ 
  alpha: true, 
  antialias: true, 
  depth: true, 
  failIfMajorPerformanceCaveat: false, 
  powerPreference: "default",
  premultipliedAlpha: true, 
  preserveDrawingBuffer: false, 
  stencil: false 
}