当前位置: 首页 > news >正文

我有域名和服务器找人建设网站用代码做家乡网站

我有域名和服务器找人建设网站,用代码做家乡网站,厦门建设银行招聘网站,广告软文外链平台基于Chrome扩展的浏览器可信事件与网页离线PDF导出 Chrome扩展是一种可以在浏览器中添加新功能和修改浏览器行为的软件程序#xff0c;我们可以基于Manifest规范的API实现对于浏览器和Web页面在一定程度上的修改#xff0c;例如广告拦截、代理控制等。Chrome DevTools Proto…基于Chrome扩展的浏览器可信事件与网页离线PDF导出 Chrome扩展是一种可以在浏览器中添加新功能和修改浏览器行为的软件程序我们可以基于Manifest规范的API实现对于浏览器和Web页面在一定程度上的修改例如广告拦截、代理控制等。Chrome DevTools Protocol则是Chrome浏览器提供的一套与浏览器进行交互的API我们可以基于DevTools协议控制Chromium内核的浏览器进行各种操作例如操作页面元素、模拟用户交互等。 描述 前段时间我们需要实现一个比较复杂的需求经常做需求的同学都知道很多功能并不是可以按步就班地实现的在某些情况下例如要跨部门甚至无法联系合作的情况下单方面跨系统完成一些事情就可能需要动用不同寻常的方法。当然具体需求的内容不是很方便表达所以在这里我们就替代为其他方面的需求展开文章的叙述虽然实现的目的不一样但是最终想要表达的技术方案是类似的。 因此在这里假设我们的背景变成了另一个故事前段时间语雀进行了商业化对于用户文章的数量和分享都做了一些限制那么此时我们可能希望将现在已经写过的文档内容抽离出来将其放在GitHub或者其他软件中作备份或分享等。那么此时问题来了熟悉富文本的同学都知道我们在语雀上存储的文档都是JSON文件而不是MarkDown等会存在固定的私有格式因此我们可能需要对其先进行一遍解析而调用语雀的OpenAPI所需要的Personal Token是需要超级会员的因此我们可能只能走比较常用的Cookie以及私有格式的解析方案或者自动化操作Puppeteer模拟导出文档也是可行的。 那么有没有更加通用的方案可以参考熟悉富文本的同学还知道由于富文本需要实现DOM与选区MODEL的映射因此生成的DOM结构通常会比较复杂而当我们从文档中复制内容到剪贴板时我们会希望这个结构是更规范化的以便粘贴到其他平台例如飞书、Word等时会有更好的解析。因此我们便可以借助这一点来获取更加通用的方案毕竟通过HTML解析成MarkDown等格式社区有很多完善的方法而不需要我们自行解析了此外由于我们是通过HTML来描述内容对于文档的内容完整性保持的会更好一些自行解析的情况下可能会由于复杂的嵌套内容需要不断完善解析程序。 当然在这里只是平替了一下需求前边我们也提到了背景是假设出来的而由这个背景则延伸出了我们文章要聊的解决方案如果真的是针对于语雀的这个迁移问题在批量处理内容的情况下还是自行解析JSON会更方便一些。那么我们可以继续沿着提取HTML内容的思路处理数据首先我们需要考虑如何获取这个HTML内容最简单的方案就是我们通过读取Node.innerHTML属性来获取DOM结构那么问题来了在语雀当中有大量的ne开头的标签以及大量的ne属性值来表达样式以简单的文本与加粗为例其HTML内容是这样的其实语雀还算比较简单的结构如果是飞书的表达则更加复杂。 !-- 语雀 -- ne-p idu5aec73be data-lake-idu5aec73bene-text idu1e4a00ce123/ne-textne-text iducc026ff4 ne-boldtrue123/ne-textspan classne-viewer-b-filler ne-fillerblockbr/span /ne-p!-- 飞书 --div classblock docx-text-block data-block-typetext data-block-id2 data-record-iddoxcns7E9SHaX2Xft1XweL0Mqwthdiv classtext-block-wrapperdiv classtext-blockdiv classzone-container text-editor non-empty data-zone-id2 data-zone-container* data-slate-editortrue contenteditabletruediv classace-line data-nodetrue dirautospan data-stringtrue class author-0087753711195911211 data-leaftrue123/spanspan data-stringtrue stylefont-weight:bold; class author-0087753711195911211 data-leaftrue123/spanspan data-stringtrue data-entertrue data-leaftrueZeroWidthSpace;/span/div/div/div/div /div可以看出来我们取得这样的HTML解析起来相对成本还是比较高的而如果我们以上述的剪贴板思路也就是富文本通常会对复制的内容作Normalize处理那么我们可以通过剪贴板事件来获取这个规范化的内容然后再进行处理HTML这里的HTML内容就会规范很多那么同样也会便于我们处理数据。在这里实际上通常还会有私有类型的数据这里就是我们选中部分取得的渲染Fragment通常是用来在编辑器内部粘贴处理数据无损化还原使用的如果对于数据格式非常熟悉的话解析这部分内容也是可以的只是并没有比较高的通用性。 !-- 语雀 -- div classlake-content typographyclassicp idu5aec73be classne-p stylemargin: 0; padding: 0; min-height: 24pxspan classne-text123/spanstrongspan classne-text123/span/strong/p /div!-- 飞书 -- div data-page-iddoxcnTYldMboJldT2Mc2wXfervv6vqc data-docx-has-block-datafalsediv classace-line ace-line old-record-id-doxcnsBUassFNud1XwL1vMgth123strong123/strong/div /div那么我们就可以继续沿着这个思路以复制出的的内容为基准解析HTML格式解析内容而实际上说了这么多我们最需要解决的问题是如何自动化提取内容由此就引出了我们今天要聊的Chrome拓展与Chrome DevTools Protocol协议当我们成功解决了内容问题之后接下来将内容格式转换为其他格式社区就有很多成熟的方案了。文中涉及的相关代码都在https://github.com/WindrunnerMax/webpack-simple-environment/tree/master/packages/chrome-debugger中在这里为了方便处理演示DEMO我们的事件触发全部都是DOM0级的事件绑定形式。 JavaScript事件 既然我们的目标是自动操作浏览器执行复制操作那么可供自动化操作的选择有很多例如Selenium、Puppeteer都是可以考虑的方案。在这里我们考虑比较轻量的解决方案不需要安装WebDriver等依赖环境并且可以直接安装在用户本身的浏览器中开箱即用基于这些考虑则使用Chrome扩展来帮我们实现目标是比较好的选择。并且Chrome扩展程序可以帮我们在Web页面中直接注入脚本实现相关功能也会更加方便关于使用扩展程序实现复杂的功能注入可以参考之前的文章在这里就不重复叙述了。 那么接下来我们就需要考虑一下如何触发页面的OnCopy事件试想一下此时我们的目的有两个首先是让编辑器本身提取内容并规范化其次是让转换后的内容写入剪贴板那么实现的方式就很明确了我们只需要主动在页面上触发SelectAll与Copy命令即可那么接下来我们就可以在控制台中测试这两个命令的使用。 document.execCommand(selectAll); const res document.execCommand(copy); console.log(res); // true当我们手动在控制台执行命令的时候可以发现页面上的内容已经被选中并且复制到了剪贴板中那么接下来我们就可以将这两个命令封装到一个函数中然后通过Content Script注入到页面中这样我们就可以在页面上直接调用这个函数就可以了。然而当我们真正借助Chrome扩展实现这个功能的时候会发现页面能够正常全部选中但是剪贴板的内容却是上次的内容也就是本次复制并没有真正执行成功。 这实际上是由于浏览器的安全策略导致的由于浏览器为了加强安全性限制了一些可能会影响用户隐私的API只有在用户的直接操作下才能运行也就是相当于执行Copy命令只有在用户主动激活上下文中才可以正常触发与之类似的就是当我们在Js中主动执行点击事件例如Node.click()时其对于浏览器来说是不可信的在事件触发时会携带isTrusted属性只有用户主动触发的事件才会为true。因此我们在控制台中执行的命令被认为是浏览器的可信命令是用户主动触发的事件而在扩展中执行的不是用户主动触发的事件进而命令执行失败。 那么为什么我们在控制台的命令就可以正常执行呢实际上这是因为我们在执行控制台的命令时会需要点击回车键来执行代码注意这个回车键是我们主动触发的因此浏览器会将我们执行的Js代码认为是可信的所以我们可以正常执行Copy命令。而如果我们在执行代码时将其加入延时例如我们延时5s再执行命令此时我们就可以发现即使是同样的代码同样在控制台执行就无法写入剪贴板document.execCommand(copy)的返回值就是命令是否执行成功在5s的延时下我们得到的返回值就是false我们可以同样在控制台中执行代码来获取命令执行状态在这里也可以不断调整延时的时间来观察执行结果例如将其设置为2s就可以获得true的返回值。 setTimeout(() {document.execCommand(selectAll);const res document.execCommand(copy);console.log(res); // false }, 5000);我们暂且先放开需要用户主动激活的可信事件问题不谈到后边再继续聊这个问题的解决方案。那么我们除了需要测试OnCopy事件之外同样需要测试一下OnPaste的事件不要忘记当我们执行了OnCopy提取内容之后这部分内容实际上还是存在于剪贴板之中的我们还需要将其提取出来。那么在执行下面的代码之后我们可以发现OnPaste和OnCopy的策略还是不一样即使是在用户的主动操作下并且我们此时并没有延时执行但是其结果依然是false并且document绑定的事件也没有触发。 document.onpaste console.log; const res document.execCommand(paste); console.log(res); // false那么会不会是因为我们没有在input或者textarea中执行paste命令的原因我们同样可以测试下这个问题。我们可以通过创建一个input元素然后将其插入到body中然后将焦点移动到这个input元素上然后执行paste命令然而我们仍然无法成功执行命令而且我们执行focus的时候会发现并没有光标的出现 const input document.createElement(input); input.setAttribute(style, position:fixed; top:0; right: 0); document.body.appendChild(input); input.focus(); const res document.execCommand(paste); console.log(res); // false那么是不是还有其他原因会造成这个问题呢在前边我们经过OnCopy部分的测试可以得知在用户主动触发可信事件之后一段时间内的事件都是可信的但是浏览器的安全策略中还有焦点方面的考量。在某些操作中焦点必须要在document上否则操作不会正常执行与之对应的异常就是DOMException: Document is not focused.而此时我们的焦点是在控制台Console面板上的这里同样可能存在不可控的问题。因此我们需要在这2s的执行延时中将焦点转移到document上也就是需要点击body中任意元素当然直接点击input也是可行的然而即使这样我们也没有办法执行paste。 const input document.createElement(input); input.setAttribute(style, position:fixed; top:0; right: 0); document.body.appendChild(input); setTimeout(() {input.focus();const res document.execCommand(paste);console.log(res); // false }, 2000);实际上在经过查阅文档可以知道document.execCommand(paste)在Web Content中实际上已经是被禁用的然而这个命令还是可以执行的我们后边会继续聊到。在现代浏览器中我们还有navigator.clipboard API来操作剪贴板navigator.clipboard.read可以实现有限的剪贴板内容读取调用这个API时会出现明确的调用授权提示主动授权对于用户隐私是没有问题的只是在自动化场景下可能需要多出一步授权操作。 此外我们提到了navigator.clipboard是有限的剪贴板内容读取那么这个有限是指什么呢实际上这个有限是指只能读取特定的类型例如text/plain、text/html、image/png等常见的类型而对于私有类型的数据则是无法读取的例如我们在语雀中复制的text/ne-inode Fragment数据这部分数据是无法通过navigator.clipboard.read来读取的通过执行下面的代码并授权之后可以发现并没有任何输出。 setTimeout(() {navigator.clipboard.read().then(res {for (const item of res) {item.getType(text/ne-inode).then(console.log).catch(() null)}}); }, 2000);我们实际上也可以通过遍历navigator.clipboard的内容来获得剪贴板的内容同样的我们也只能获取text/plain、text/html、image/png等常见的规范MIME-Type类型。而这2s的耗时则是之前提到过的另一个限制我们必须要在执行下面的代码之后将焦点移动到document上否则控制台则会抛出DOMException: Document is not focused.异常同样也不会出现授权弹窗。 setTimeout(() {navigator.clipboard.read().then(res {for (const item of res) {const types item.types;for (const type of types) {item.getType(type).then(data {const reader new FileReader();reader.readAsText(data, utf-8);reader.onload () {console.info(type, reader.result);};});}}}); }, 2000);那么我们可以设想一个问题富文本编辑器中如果只是写数据的时候写入了自定义的MIME-Type类型那么我们在剪贴板中应该如何读取呢。实际上这还是得回归到我们的OnPaste事件上我们借助于navigator.clipboard API是无法读取这部分自定义key值的虽然我们可以将其写入到复制出的HTML的某个节点作为attributes然后再读取这样是可以但是没必要我们可以直接在OnPaste事件中通过clipboardData获取更加完整的相关数据我们可以获取比较完整的类型了这个方法同样也可以用于在浏览器中方便地调试剪贴板的内容。 const input document.createElement(input); input.style.position fixed; input.style.top 100px; input.style.right 10px; input.style.zIndex 999999; input.style.width 200px; input.placeholder Read Clipboard On Paste; input.addEventListener(paste, event {const clipboardData event.clipboardData || window.clipboardData;for (const item of clipboardData.items) {console.log(%c item.type, background-color: #165DFF; color: #fff; padding: 3px 5px;);console.log(item.kind file ? item.getAsFile() : clipboardData.getData(item.type));} }); document.body.appendChild(input);DevToolsProtocol 在前边我们抛出了需要用户主动激活触发的可信事件问题那么在部分我们就需要解决这个问题。首先我们需要解决的问题是如何将代码注入到页面中当然这个问题我们已经说过多次了就是借助于Chrome扩展将脚本注入即可。那么即使我们能够注入脚本执行的代码仍然不是用户主动激活的事件无法突破浏览器的安全限制那么这时候就需要请出我们的Chrome DevTools Protocol协议了。 熟悉E2E的同学都知道DevToolsProtocol协议是Chrome浏览器提供的一套与浏览器进行交互的API无论是Selenium、Puppeteer、Playwright都是基于这个协议来实现的。我们甚至可以基于这个协议主动实现F12的调试面板也就是说当前在F12开发者工具能够实现的功能我们都可以基于这个协议实现而且其API也不仅仅只有调试面板的功能实现并且诸如chrome://inspect等调试程序也可以通过这个协议来完成。 那么在这里就有新的问题了如果我们采用Selenium、Puppeteer等方案就需要用户安装WebDriver或者Node等依赖项不能做到让用户开箱即用那么在这个时候我们就需要将目光转向chrome.debugger了。Chrome.debugger API可以作为Chrome的远程调试协议的另一种传输方式使用chrome.debugger可以连接到一个或多个标签页来监控网络交互、调试JavaScript、修改DOM和CSS等等对我们来说最重要的是这个API是可以在Chrome扩展中调用的这样我们就可以做到开箱即用的应用程序。 那么接下来我们就来处理OnCopy的事件因为chrome.debugger必须要在worker中进行而我们的控制启动的按钮则是定义在Popup中的所以我们就需要进行Popup - Worker的事件通信关于Chrome扩展的通信方案可以在之前的文章中找到也可以在前边提到的仓库中找到在这里就不过多叙述了。那么此时我们就需要在扩展中查询当前活跃的标签页然后需要过滤下当前活跃标签的协议例如chrome://协议的连接我们不会进行处理然后在符合条件的情况下我们将tabId传递下去。 cross.tabs.query({ active: true, currentWindow: true }).then(tabs {const tab tabs[0];const tabId tab tab.id;const tabURL tab tab.url;if (tabURL !URL_MATCH.some(match new RegExp(match).test(tabURL))) {return void 0;}return tabId;})那么接下来我们就需要将协议控制持续挂载到当前活跃的Tab页上当我们将扩展挂载debugger之后会在用户的界面上提示我们的扩展已经开始调试此浏览器这其实也是浏览器的一种安全策略因为debugger的权限实在是太高了给予用户可取消的操作还是非常有必要的。那么当挂载之后我们就可以通过chrome.debugger.sendCommand来发送命令例如我们可以通过Input.dispatchKeyEvent来模拟按键事件在这里我们就需要借助按键的事件来发送selectAll命令实际上发送命令这一环节是可以通过任何按键的发送来实现的只不过为了符合实际操作我们选择了CtrlA的组合键。 chrome.debugger.sendCommand({ tabId }, Input.dispatchKeyEvent, {type: keyDown,modifiers: 4,keyCode: 65,key: a,code: KeyA,windowsVirtualKeyCode: 65,nativeVirtualKeyCode: 65,isSystemKey: true,commands: [selectAll], });需要注意的是经过前边的按键事件发送之后我们此时执行的事件就会是可信的通过DevToolsProtocol的模拟按键事件对于浏览器来说是完全可信的等同于用户主动触发的事件。那么接下来就可以直接通过Eval执行document.execCommand(copy)命令了这里我们可以通过Runtime.evaluate来执行Js代码当执行完毕后我们就需要将debugger卸载出当前活跃的标签页。在我们提供的DEMO中为了对齐之前直接用Js执行的操作我们同样也会延时5s再执行操作此时可以发现我们的代码是可以正常将内容写到剪贴板里的也就是我们成功执行了Copy命令。 chrome.debugger.sendCommand({ tabId }, Runtime.evaluate, {expression: const res document.execCommand(copy); console.log(res);, }) .then(() {chrome.debugger.detach({ tabId }); });那么同样的接下来我们就研究在DevToolsProtocol中的OnPaste事件那么首先我们并不在权限清单中声明clipboardRead权限这是在Chrome扩展程序权限清单中的读剪贴板权限紧接着我们延续之前的代码在debugger中执行document.execCommand(paste)可以发现执行的结果是false这表示即使在可信的条件下执行paste仍然是无法取得结果的。那么如果我们在permissions中声明了clipboardRead会可以发现仍然是false这说明在用户脚本Inject Script下执行document.execCommand(paste)是无法取得效果的。 chrome.debugger.attach({ tabId }, 1.2).then(() chrome.debugger.sendCommand({ tabId }, Input.dispatchKeyEvent, {type: keyDown,// ...})).then(() {return chrome.debugger.sendCommand({ tabId }, Runtime.evaluate, {expression:document.onpaste console.log; const res document.execCommand(paste); console.log(res);,});}).finally(() {chrome.debugger.detach({ tabId });});那么我们继续保持不在清单中声明clipboardRead权限尝试用DevToolsProtocol的方式执行document.execCommand(paste)也就是在模拟按键时将命令发送出去。此时我们可以发现是可以正常触发事件的这里实际上就同样表明了通过DevToolsProtocol协议直接执行事件是完全以用户主动触发的形式来进行的其本身就是可信的事件源。 chrome.debugger.sendCommand({ tabId }, Input.dispatchKeyEvent, {type: keyDown,modifiers: 4,keyCode: 86,key: v,code: KeyV,windowsVirtualKeyCode: 86,nativeVirtualKeyCode: 86,isSystemKey: true,commands: [paste], });紧接着我们简单更改一下先前在用户态执行的Js事件操作将执行的copy命令改为paste命令也就是在Content Script部分执行document.execCommand(paste)此时仍然是会返回false说明我们的命令执行并没有成功。那么别忘了此时我们还没有声明清单中的clipboardRead权限而当我们在清单中声明权限之后再次执行document.execCommand(paste)发现此时的结果是true并且可以正常触发事件。 document.onpaste console.log; case PCBridge.REQUEST.COPY_ALL: {const res document.execCommand(paste);console.log(res);break; }而如果我们更进一步继续保持清单中的clipboardRead权限声明将事件传递到Inject Script中执行可以发现即使是在声明了权限的情况下document.execCommand(paste)返回的结果仍然是false并且无法触发我们绑定的事件这也印证了之前我们说的在Inject Script下执行paste命令是无法正常触发的进而我们可以明确clipboardRead权限是需要我们在Content Script中使用的。而对于navigator.clipboard API即使在权限清单中声明权限的情况下 仍然还需要主动授权。 // Content Script case PCBridge.REQUEST.COPY_ALL: {document.dispatchEvent(new CustomEvent(custom-event));break; }// Inject Script document.onpaste console.log; document.addEventListener(custom-event, () {const res document.execCommand(paste);console.log(res); });网页离线PDF导出 在前段时间刷社区的时候发现有不少用户希望能够将网页保存为PDF文件方便作为快照保存以供离线阅读因此在这里也顺便聊一下相关实现方案而实际上在这里也属于Web页面内容的提取与我们上文聊的剪贴板操作本质上是类似的功能。那么在浏览器中我们当然可以通过Ctrl P将PDF打印出来然而通过打印的方式或者生成图片的方式导出的PDF文件就存在一些问题: 导出的PDF必须指定纸张大小不能随意设定纸张大小例如当想将页面导出为单页PDF的情况下就难以实现。导出PDF时必须要弹出选择对话框不能够静默导出并自动下载这对于想要同时导出多个Tab页的批量场景不够友好。导出的PDF不会自动携带Outline也就是PDF的目录书签大纲需要后续主动使用pdf-lib等工具来生成。导出时必须要全页面打印页面本身可能没有定义media print样式预设希望实现局部打印时会有些困难。如果想在打印PDF前批量自定义样式则需要为每个页面单独注入样式这样的操作显然不适用于批量场景。如果通过类似于HTML2Canvas的方式将页面转换为图片再转换为PDF则会导致图片体积过大且文本不能选中的问题。 那么在这里我们可以借助Chrome DevTools Protocol协议来实现这个功能实际上DevTools Protocol协议中有一个Page.printToPDF方法这也是常用的Node服务端将HTML转换为PDF的常用方法当然借助PDFKit等工具直接绘制生成PDF也是可行的只不过成本很高。Page.printToPDF方法可以将当前页面导出为PDF文件并且可以实现静默导出并自动下载也可以实现自定义纸张大小同时也可以实现Outline的生成这个方法的使用也是非常简单的只需要传递一个PDF的配置对象即可。 那么在调用方法之前我们同样需要查询当前活跃的活动窗口当然直接选择当前Window下的所有窗口也是可行的此时需要注意权限清单中的tabs与activeTab权限的声明同样的在这里我们仍然需要过滤chrome://等协议只处理http://、https://、file://协议的内容。 cross.tabs.query({ active: true, currentWindow: true }).then(tabs {const tab tabs[0];const tabId tab tab.id;const tabURL tab tab.url;if (tabURL !URL_MATCH.some(match new RegExp(match).test(tabURL))) {return void 0;}return tabId;})接下来我们就可以根据TabId挂载debugger前边提到了我们是希望将页面导出为单页PDF的因此我们就需要将页面的高度和宽度取得此时我们可以通过Page.getLayoutMetrics方法来获取页面的布局信息这个方法会返回一个LayoutMetrics对象其中包含了页面的宽度、高度、滚动高度等信息。然而当然我们也可以通过通信的方式将消息传递到Content Script中得到页面的宽高信息在这里我们采用更加简单的方式通过执行Runtime.evaluate的方式获取得到的返回值这样我们可以灵活地取得更多的数据当然也可以灵活地控制页面内容例如在滚动容器不是window的情况下就需要我们注入代码获取宽高以及控制打印范围。 chrome.debugger.attach({ tabId }, 1.3).then(() {return chrome.debugger.sendCommand({ tabId }, Runtime.evaluate, {expression:JSON.stringify({width: document.body.clientWidth, height: document.body.scrollHeight}),});})那么接下来我们就需要根据页面的宽高信息来设置PDF的配置对象在这里需要注意的是我们通过document取得的宽高信息是像素大小而在Page.printToPDF中的paperWidth和paperHeight是以inch为单位的因此我们需要将其转换为inch单位根据CSS规范1px 1/96th of 1 inch我们通常可以认为1px 1/96 inch而不受设备物理像素的影响。此外我们可以指定一些配置当前我们输出的PDF只会包含第一页的内容同时会包含背景颜色、生成文档大纲的配置并且还有Header、Footer等配置选项我们可以根据实际需求来设置输出格式需要注意的是generateDocumentOutline是实验性的配置在比较新的Chrome版本中才被支持。 const value res.result.value as string; const rect TSON.parse{ width: number; height: number }(value); return chrome.debugger.sendCommand({ tabId }, Page.printToPDF, {paperHeight: rect ? rect.height / 96 : undefined,paperWidth: rect ? rect.width / 96 : undefined,pageRanges: 1,printBackground: true,generateDocumentOutline: true, });那么在生成完毕后我们接下来就需要将其下载到设备中触发下载的方法又很多例如可以将数据传递到页面中通过a标签触发下载。在扩展程序中实际上提供了chrome.downloads.download方法这个方法可以直接下载文件到设备中并且虽然传递数据参数名字为url但是实际上并不会受到链接长度/字符数的限制通过传递Base64编码的数据可以实现大量数据下载只要注意在权限清单中声明权限即可。那么在下载完成之后我们同样就可以将debugger分离当前Tab页这样就完成了整个PDF导出的过程。 const base64 res.data as string; chrome.downloads.download({ url: data:application/pdf;base64, base64 });.finally(() {chrome.debugger.detach({ tabId });});每日一题 https://github.com/WindrunnerMax/EveryDay参考 https://chromedevtools.github.io/devtools-protocol/ https://github.com/microsoft/playwright/issues/29417 https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API https://developer.chrome.google.cn/docs/extensions/reference/api/debugger?hlzh-cn https://stackoverflow.com/questions/71005817/how-does-pixels-relate-to-screen-size-in-css https://chromewebstore.google.com/detail/just-one-page-pdf/fgbhbfdgdlojklkbhdoilkdlomoilbpl
http://www.dnsts.com.cn/news/195895.html

相关文章:

  • 全球排行前50网站开发语言做网站会提供源代码
  • 树状结构的网站使用wordpress做图站
  • 做网店好还是网站怎么做卖橘子的网站
  • 恶意刷网站嗨学网官网
  • 建网站空间都有什么平台wordpress主题等
  • 哪几个网站做acm题目温州网站优化指导
  • hao123网站模板深圳买房最新政策
  • 专门做电子书的网站有哪些最新消息新闻
  • 科技公司网站设建设电影网站广告哪里找
  • asp网站咋做国外代理网站
  • 一些可以做翻译的网站专做蓝领招聘网站有哪些
  • 大连企业网站模板建站中国域名注册局官网
  • 做设计不进设计公司网站安徽省住房城乡建设厅网站官网
  • 网站游戏下载九宫格导航网站
  • 网站建设有几个文件夹公司网站二维码生成器
  • 外贸英文网站开发网站主页面设计模板
  • 宁波网站建设制作公司排名定制虚拟偶像汉化破解版
  • 电子政务门户网站建设项目招标采购深圳网站开发antnw
  • 国外黄冈网站推广软件有哪些网站可以用中国二字做抬头吗
  • 低价网站建设顺德百度关键词规划师入口
  • 东莞贸易网站建设复杂的手游app要多少钱
  • 三门峡城乡建设局网站安阳知名网络公司首选
  • 网站建设的阶段免费申请激活码
  • 番禺高端网站建设公司哪家好网站建设公司的服务器
  • 电子商务网站建设与网页设计wordpress商业网站
  • 太原网站制作策划合肥网站建合肥网站建设找蓝领商务
  • 金桥网站建设wordpress怎么加404
  • 做音乐网站要求wordpress添加内链按钮
  • 济南做网站优化公司apicloud安装wordpress
  • 网站开发详细报价中国建设企业协会网站首页