pdfjs内部通信机制
这一块还是比较好理解的,因为发现自己曾经写过类似的代码> < ,当时为了解决桌面端和web页面之间的通信问题,自己手撸了一份简单的通信代码,和这个大差不差,人生就是在不停地重复造轮子。
不管怎么样,都来复习一遍吧。 一段经典的不能再经典的订阅发布代码
- message_handler
核心js message_handler.js:用来处理线程之间的异步通信
- construct 用来接收外部传入的消息处理对象,注册message 监听函数 #onMessage
- actionHandler 用来存放注册的监听函数,key为监听函数的名称 actionName
- on(actionName, handler) 函数用来注册消息
- send(actionName, data, transfers) 发送消息
- sendWithPromise(actionName, data, transfers) 这个对象里面唯一需要理解的就是这个函数啦,其实也不复杂,主要用来处理有回调值的函数。打个简单的比方,A 告诉 B ,你去烧壶水吧,然后B去做了 ,这个时候用send就行了。但是如果B烧完水需要告诉A结果,这个时候就需要用到sendWithPromise啦。这个场景是不是很熟悉 ?里面的关键就是 A 和 B 通过唯一的callbackId来当做彼此通信的标识,具体看源码就一目了然了。
-
web worker
解决性能的利器,可以创建多线程环境
pdf的文档下载就是交给web worker去处理的 -
主线程和pdf线程通过代理对象PDFDocumentProxy 进行交互
api.js 中的 WorkerTransport
messageHandler.on("GetDoc", ({ pdfInfo }) => {
this._numPages = pdfInfo.numPages;
this._htmlForXfa = pdfInfo.htmlForXfa;
delete pdfInfo.htmlForXfa;
loadingTask._capability.resolve(new PDFDocumentProxy(pdfInfo, this));
});
这段代码可以看到,pdf下载完以后返回的是一个代理对象,也就是外部操作document都是通过这个代理对象和真正的document进行交互
class PDFDocumentProxy {
constructor(pdfInfo, transport) {
this._pdfInfo = pdfInfo;
this._transport = transport;
……
}
……
getPage(pageNumber) {
return this._transport.getPage(pageNumber);
}
……
}
可以简单看一下PDFDocumentProxy 内部是如何获取页面的,transport就是构造代理对象的时候传入的messageHandler , getPage方法通过transport (messageHandler)发送了一个getPage的消息
api.js 中的 WorkerTransport
getPage(pageNumber) {
……
const promise = this.messageHandler
.sendWithPromise("GetPage", {
pageIndex,
})
.then(pageInfo => {
if (this.destroyed) {
throw new Error("Transport destroyed");
}
if (pageInfo.refStr) {
this.#pageRefCache.set(pageInfo.refStr, pageNumber);
}
const page = new PDFPageProxy(
pageIndex,
pageInfo,
this,
this._params.pdfBug
);
this.#pageCache.set(pageIndex, page);
return page;
});
this.#pagePromises.set(pageIndex, promise);
return promise;
}
线程接到GetPage的消息后调用pdfManager.getPage方法
worker.js 中的 WorkerMessageHandler
handler.on("GetPage", function (data) {
return pdfManager.getPage(data.pageIndex).then(function (page) {
return Promise.all([
pdfManager.ensure(page, "rotate"),
pdfManager.ensure(page, "ref"),
pdfManager.ensure(page, "userUnit"),
pdfManager.ensure(page, "view"),
]).then(function ([rotate, ref, userUnit, view]) {
return {
rotate,
ref,
refStr: ref?.toString() ?? null,
userUnit,
view,
};
});
});
});
最后是pdfManger的getPage方法,通过真正的ducument获取page
pdf_manager.js 中的 BasePdfManager
getPage(pageIndex) {
return this.pdfDocument.getPage(pageIndex);
}
好啦,一个简单的线程通信例子讲解完毕!
下次我们来看看pdfjs是如何渲染页面的