免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
Chrome DevTools 遠(yuǎn)程調(diào)試協(xié)議分析及實(shí)戰(zhàn)

Chrome DevTools 可以說是前端開發(fā)最常用的工具,無論是普通頁面、移動(dòng)端 webview、小程序、甚至 node 應(yīng)用,都可以用它來調(diào)試。

Chrome DevTools 提供的功能非常豐富,包含 DOM、debugger、網(wǎng)絡(luò)、性能等許多能力。

為什么 Chrome DevTools 能夠適用這么多場景?如何把 Chrome DevTools 移植到新的應(yīng)用場景?Chrome DevTools 提供的功能我們能不能拆解出模塊單獨(dú)使用?今天我們來嘗試探索這些問題。

Chrome DevTools 組成

Chrome DevTools 包括四個(gè)部分:

  • 調(diào)試器協(xié)議:devtools-protocol[1],基于 json rpc 2.0。

  • 調(diào)試器后端:實(shí)現(xiàn)了調(diào)試協(xié)議的可調(diào)試實(shí)體,例如 chrome、node.js。

  • 調(diào)試器前端:通常指內(nèi)嵌在 chrome 中的調(diào)試面板,通過調(diào)試器協(xié)議和調(diào)試器后端交互,除此之外還有 Puppeteer[2],ndb[3] 等。

  • 消息通道:前后端通信方式,例如 websocket、usb、adb 等,本質(zhì)都是 socket 通信。

Chrome DevTools

我們可以看到,Chrome DevTools 的核心是調(diào)試器協(xié)議。

Chrome DevTools Protocol

協(xié)議按域「Domain」劃分能力,每個(gè)域下有 Method、Event 和 Types。

Method 對應(yīng) socket 通信的請求/響應(yīng)模式,Events 對應(yīng) socket 通信的發(fā)布/訂閱模式,Types 為交互中使用到的實(shí)體。

例如:

# https://chromedevtools.github.io/devtools-protocol/1-3/Log
Log Domain 

Provides access to log entries.

Methods

Log.clear
Log.disable
Log.enable
Log.startViolationsReport
Log.stopViolationsReport

Events

Log.entryAdded

Types

LogEntry
ViolationSetting

一個(gè)調(diào)試器后端,應(yīng)當(dāng)實(shí)現(xiàn)對 Method 的響應(yīng),并在適當(dāng)?shù)臅r(shí)候發(fā)布 Event。

一個(gè)調(diào)試器前端,應(yīng)當(dāng)使用 Method 請求需要的數(shù)據(jù),訂閱需要的 Event。

browser_protocol & js_protocol

協(xié)議分為 browser_protocol[4]js_protocol[5] 兩種。

browser_protocol 是瀏覽器后端使用,js_protocol 是 node 后端使用。除此之外,還有對應(yīng)的 Typescript 類型定義[6]。

js_protocol 只有以下四個(gè)域「Console、Schema 已廢棄」:

  • Debugger
  • Profiler
  • Runtime 「js Runtime」
  • HeapProfiler

能力比 browser_protocol 少很多,這是因?yàn)轫撁嬗邢鄬潭ǖ墓ぷ髂J?,node 應(yīng)用卻千差萬別。

browser_protocol 主要有以下幾個(gè)域:

  • DOM
  • DOMDebugger
  • Emulation 「環(huán)境模擬」
  • Network
  • Page
  • Performance
  • Profiler

涉及了頁面開發(fā)的方方面面。

Chrome DevTools Frontend

devtools-frontend 即調(diào)試器前端,我們平常使用的調(diào)試面板,其源碼可以從 ChromeDevTools/devtools-frontend[7] 獲得。我們先來看一下它是怎么工作的。

項(xiàng)目結(jié)構(gòu)

ChromeDevTools/devtools-frontend[8] 下載源碼后,我們進(jìn)入 front_end 目錄,可以看到如下結(jié)構(gòu):

# tree -L 1
.
├── accessibility
├── accessibility_test_runner
│   ├── AccessibilityPaneTestRunner.js
│   └── module.json
├── animation
├── application_test_runner
├── axe_core_test_runner
...
├── input
├── inspector.html
├── inspector.js
├── inspector.json
├── network
├── network_test_runner
├── node_app.html
├── node_app.js
├── node_app.json
├── worker_app.html
├── worker_app.js
└── worker_app.json

front_end 目錄下的每一個(gè) json 文件會(huì)有一個(gè)同名的 js 文件,有的還會(huì)有一個(gè)同名的 html 文件。

它們都代表一個(gè)應(yīng)用,如 inspector.json 是其配置文件。如果此應(yīng)用有界面,則帶有 html,可以在瀏覽器中打開 html 運(yùn)行應(yīng)用。

我們可以看到熟悉的應(yīng)用,inspector、node、devtools、ndb 等等。

devtools_app 即我們常用的調(diào)試面板,如圖所示:

devtools

inspector 在 devtools_app 基礎(chǔ)上增加了頁面快照,可以實(shí)時(shí)看到頁面的變化,并且可以在頁面快照上交互,如圖所示:

inspector

以 devtools_app 為例,我們來看配置文件的語義:

// devtools_frontend/front_end/devtools_app.json
{
  'modules' : [
    { 'name''emulation''type''autostart' },
    { 'name''inspector_main''type''autostart' },
    { 'name''mobile_throttling''type''autostart' },
    ...
    { 'name''timeline' },
    { 'name''timeline_model' },
    { 'name''web_audio' },
    { 'name''media' }
  ],
  'extends''shell',
  'has_html'true
}

  • modules 表示此應(yīng)用包含的模塊,每個(gè)模塊都對應(yīng) front_end 目錄下的一個(gè)目錄。
  • extends 表示此應(yīng)用是否繼承自另外一個(gè)應(yīng)用,devtools_app 繼承自 shell 應(yīng)用,我們可以在 front_end 目錄下看到 shell.js、shell.json。
  • has_html 表示此應(yīng)用有 html 界面,即同名的 devtools_app.json。

我們再來看一下模塊,所有的模塊都平級放在 front_end 目錄下,不存在嵌套,每個(gè)模塊都有一個(gè) module.json 文件,表示此模塊的配置。

{
    'extensions': [
        {
            'type''view',
            'location''drawer-view'
        }
    ],
    'dependencies': [
        'elements'
    ],
    'scripts': [],
    'modules': [
        'animation.js',
        'animation-legacy.js',
        'AnimationUI.js'
    ],
    'resources': [
        'animationScreenshotPopover.css',
        'animationTimeline.css'
    ]
}

  • extensions 表示此模塊的自定義屬性。
  • dependencies 表示此模塊依賴的模塊。
  • modules 表示此模塊包括的 js 文件。
  • resources 表示此模塊包括的靜態(tài)資源,主要是 css。

之所以有這些配置,是因?yàn)?,front_end 有自己的一套模塊加載邏輯,和通常的 node 應(yīng)用和前端應(yīng)用都不一樣。

初始化

front_end 各個(gè)應(yīng)用初始化的過程類似,基本如下:

  • 從對應(yīng)的 json 文件中加載配置,并根據(jù)配置加載需要的模塊
// devtools-frontend/front_end/RuntimeInstantiator.js
export async function startApplication(appName{
  console.timeStamp('Root.Runtime.startApplication');
  const allDescriptorsByName = {};
  for (let i = 0; i < Root.allDescriptors.length; ++i) {
    const d = Root.allDescriptors[i];
    allDescriptorsByName[d['name']] = d;
  }
  if (!Root.applicationDescriptor) {
    // 加載應(yīng)用配置 <appName>.json
    let data = await RootModule.Runtime.loadResourcePromise(appName + '.json');
    Root.applicationDescriptor = JSON.parse(data);
    let descriptor = Root.applicationDescriptor;
    while (descriptor.extends) {
      // 加載父級配置直到?jīng)]有父級
      data = await RootModule.Runtime.loadResourcePromise(descriptor.extends + '.json');
      descriptor = JSON.parse(data);
      Root.applicationDescriptor.modules = descriptor.modules.concat(Root.applicationDescriptor.modules);
    }
  }
  const configuration = Root.applicationDescriptor.modules;
  const moduleJSONPromises = [];
  const coreModuleNames = [];
  for (let i = 0; i < configuration.length; ++i) {
    const descriptor = configuration[i];
    const name = descriptor['name'];
    const moduleJSON = allDescriptorsByName[name];
    // 根據(jù)每個(gè)模塊的 module.json 加載模塊
    if (moduleJSON) { 
      moduleJSONPromises.push(Promise.resolve(moduleJSON));
    } else {
      moduleJSONPromises.push(
          RootModule.Runtime.loadResourcePromise(name + '/module.json').then(JSON.parse.bind(JSON)));
    }
  }
    // ...
}
  • 實(shí)例化模塊

雖然 js 代碼都是通過 import 來引用依賴,但是 front_end 并非使用 import 來加載模塊,而是自己寫了一個(gè)模塊加載邏輯,先請求模塊文件,然后在根據(jù)依賴關(guān)系把代碼 eval。

// devtools-frontend/front_end/root/Runtime.js
function evaluateScript(sourceURL, scriptSource{
    loadedScripts[sourceURL] = true;
    if (!scriptSource) {
      // Do not reject, as this is normal in the hosted mode.
      console.error('Empty response arrived for script \'' + sourceURL + '\'');
      return;
    }
    self.eval(scriptSource + '\n//# sourceURL=' + sourceURL);
}
  • 模塊加載完成后,才是真正的初始化

作為調(diào)試器前端,socket 通信是不可或缺的,初始化的主要工作就是對調(diào)試器后端建立 socket 連接,準(zhǔn)備好調(diào)試協(xié)議。

對于頁面應(yīng)用來說,還需要初始化 UI,front_end 未使用任何渲染框架,全部都是原生 DOM 操作。

// devtools-frontend/front_end/main/MainImpl.js
new MainImpl(); // 初始化SDK(協(xié)議),初始化socket連接,初始化通信

應(yīng)用

遠(yuǎn)程調(diào)試

我們可以用 front_end 來實(shí)現(xiàn)遠(yuǎn)程調(diào)試頁面,例如:用戶在自己的 PC、APP 上操作頁面,開發(fā)人員在另外一臺電腦上觀察頁面、網(wǎng)絡(luò)、控制臺里發(fā)生的變化,甚至通過協(xié)議控制頁面。

開啟調(diào)試端口

不同后端打開調(diào)試端口的方式不同,以 chrome 為例:

chrome 和內(nèi)嵌的調(diào)試面板使用 Embedder channel 通信,這個(gè)消息通道不能被用來做遠(yuǎn)程調(diào)試,遠(yuǎn)程調(diào)試我們需要使用 websocket channel。

使用 websocket channel 我們還需要打開 chrome 的遠(yuǎn)程調(diào)試端口,以命令行參數(shù) remote-debugging-port 打開 chrome。

[path]/chrome.exe --remote-debugging-port=9222

或者使用腳本 devtools-frontend/scripts/hosted_mode/launch_chrome.js。

調(diào)試端口打開后,chrome 會(huì)啟動(dòng)一個(gè)內(nèi)置的 http 服務(wù),我們可以從中獲取 chrome 的基本信息,其中最重要的是各個(gè) tab 頁的 websocket 通信地址。

chrome 提供的 http 接口如下,訪問方式全部為 GET:

  • /json/protocol 獲取當(dāng)前 chrome 支持的協(xié)議,協(xié)議為 json 格式。

  • /json/list  獲取可調(diào)試的目標(biāo)列表,一般每個(gè) tab 就是一個(gè)可調(diào)試目標(biāo),可調(diào)試目標(biāo)的 webSocketDebuggerUrl 屬性就是我們需要的 websocket 通信地址。例如:

[{
   'description''',
   'devtoolsFrontendUrl''/devtools/inspector.html?ws=localhost:9222/devtools/page/8ED9DABCE2A6BD36952657AEBAA0DE02',
   'faviconUrl''https://github.githubassets.com/favicon.ico',
   'id''8ED9DABCE2A6BD36952657AEBAA0DE02',
   'title''GitHub - Unitech/pm2: Node.js Production Process Manager with a built-in Load Balancer.',
   'type''page',
   'url''https://github.com/Unitech/pm2',
   'webSocketDebuggerUrl''ws://localhost:9222/devtools/page/8ED9DABCE2A6BD36952657AEBAA0DE02'
}]

  • /json/new  創(chuàng)建新的 tab 頁

  • /json/activate/:id 根據(jù) id 激活 tab 頁

  • /json/close/:id 根據(jù) id 關(guān)閉 tab 頁

  • /json/version 獲取瀏覽器/協(xié)議/v8/webkit 版本,例如:

{
   'Browser''Chrome/80.0.3987.149',
   'Protocol-Version''1.3',
   'User-Agent''Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
   'V8-Version''8.0.426.27',
   'WebKit-Version''537.36 (@5f4eb224680e5d7dca88504586e9fd951840cac6)',
   'webSocketDebuggerUrl''ws://localhost:9222/devtools/browser/ad007235-aa36-4465-beb1-70864067ea49'
}

注意:這些接口都不能跨域,可以通過服務(wù)器訪問,或者直接在瀏覽器中打開,但是不能使用 ajax 訪問。

連接

獲取到 webSocketDebuggerUrl 后,我們就可以用此連接來調(diào)試頁面。front_end 下的 devtool、inspector 等應(yīng)用均可使用。

觀察 初始化 socket 鏈接的代碼可以得知,我們需要把 webSocketDebuggerUrl 以 url 參數(shù)的形式傳給應(yīng)用,參數(shù)名為 ws。

// devtools-frontend/front_end/sdk/Connections.js
export function _createMainConnection(websocketConnectionLost{
  const wsParam = Root.Runtime.queryParam('ws');
  const wssParam = Root.Runtime.queryParam('wss');
  if (wsParam || wssParam) {
    const ws = wsParam ? `ws://${wsParam}` : `wss://${wssParam}`;
    return new WebSocketConnection(ws, websocketConnectionLost); 
  }
  if (Host.InspectorFrontendHost.InspectorFrontendHostInstance.isHostedMode()) {
    return new StubConnection();
  }
  return new MainConnection();
}

我們在 front_end 目錄下啟動(dòng)靜態(tài)服務(wù)器。

serve -p 8002

然后訪問 http://localhost:8002/inspector?ws=localhost:9222/devtools/page/8ED9DABCE2A6BD36952657AEBAA0DE02

我們可以看到頁面上的一切變化都會(huì)出現(xiàn)在 inspector 的界面中。

跨域

如果前端和后端都在同一網(wǎng)段,我們使用以上方式就可以進(jìn)行調(diào)試了,但是如果前后端在不同的內(nèi)網(wǎng)內(nèi),我們?nèi)绾螌?shí)現(xiàn)遠(yuǎn)程調(diào)試?

只要我們有一臺放在公網(wǎng)的服務(wù)器就可以調(diào)試。

前端和后端都在各自的內(nèi)網(wǎng)內(nèi),因此相互之間肯定無法直接訪問。但是它們都可以訪問公網(wǎng)的服務(wù)器,并且,websocket 是可以跨域的。

因此我們可以通過兩次轉(zhuǎn)發(fā),讓不同內(nèi)網(wǎng)的前端和后端交互,具體步驟如下:

  • 創(chuàng)建一個(gè)轉(zhuǎn)發(fā)用的 websocket 服務(wù),放在公網(wǎng)。

  • 我們在被調(diào)試的頁面中增加一個(gè)自定義的 launcher.js,對公網(wǎng)的 websocket 服務(wù)建立連接,把頁面的基本信息傳遞給服務(wù)器,同時(shí)通過 json/list 接口找出自身的 webSocketDebuggerUrl 建立連接。

注意:因?yàn)?json/list 是 http 接口,無法跨域,這一步必須手動(dòng)獲取,然后把 webSocketDebuggerUrl 放在 url 參數(shù)上傳給 launcher.js

手動(dòng)獲取 webSocketDebuggerUrl
  • 把 front_end 頁面 url 的 ws 參數(shù)改為公網(wǎng)的 websocket 服務(wù)。

這樣,我們的 socket 鏈路上有了四個(gè)節(jié)點(diǎn),分別是:

  • front_end(調(diào)試器前端)
  • 公網(wǎng)服務(wù)器(server)
  • laucher.js
  • debugger(調(diào)試器后端)

server 和 laucher 完全作為轉(zhuǎn)發(fā)器,轉(zhuǎn)發(fā)兩邊傳來的信息,即可實(shí)現(xiàn) front_end 到 debugger 的交互。

注意:如果 front_end 請求了 Network.enable, 就不能把 laucher.js 所在的頁面作為調(diào)試頁面,因?yàn)?laucher.js 收到 debugger 傳來的數(shù)據(jù)會(huì)觸發(fā) Network.webSocketFrameReceived 推送,這個(gè)推送本身又會(huì)觸發(fā) Network.webSocketFrameReceived ,造成無限循環(huán)。處理方式有兩種,一是攔截掉 Network.enable 請求,這樣會(huì)取消掉所有的 Network 的推送。二是不把 laucher.js 所在的頁面作為調(diào)試頁面,僅作數(shù)據(jù)中轉(zhuǎn)用。

遠(yuǎn)程調(diào)試

websocket 服務(wù)代碼示例:

// server.js
var WebSocketServer = require('websocket').server;
var http = require('http');
var server = http.createServer(function(request, response{
    response.writeHead(404);
    response.end();
});
server.listen(3232function({
    console.log((new Date()) + ' Server is listening on port 3232');
});
wsServer = new WebSocketServer({
    httpServer: server
});
var frontendConnection;
var debugConnection;

wsServer.on('request'async function(request{
    var requestedProtocols = request.requestedProtocols;
    if(requestedProtocols.indexOf('frontend') != -1){  // 處理來自調(diào)試器前端的請求
        frontendConnection = request.accept('frontend', request.origin);
        frontendConnection.on('message'function(message{
            if (message.type === 'utf8') {
                // 把調(diào)試器前端的請求直接轉(zhuǎn)發(fā)給被調(diào)試頁面
                if(debugConnection){
                    debugConnection.sendUTF(message.utf8Data)
                }else{
                    frontendConnection.sendUTF(JSON.stringify({msg:'調(diào)試器后端未準(zhǔn)備好,先打開被調(diào)試的頁面'}))
                }  
            }
        })
        frontendConnection.on('close'function(reasonCode, description{
            console.log('frontendConnection disconnected.');
        });
    }
    if(requestedProtocols.indexOf('remote-debug') != -1){ // 處理來自被調(diào)試頁面的請求
        debugConnection = request.accept('remote-debug', request.origin);
        debugConnection.on('message'function(message{
            if (message.type === 'utf8') {
                var feed = JSON.parse(message.utf8Data);
                if(feed.type == 'remote_debug_page'){   // 確認(rèn)連接
                    debugConnection.sendUTF(JSON.stringify({'type':'start_debug'}));
                }else if(feed.type == 'start_debug_ready'){
                    // 被調(diào)試頁面已連接好
                } else{
                    // 把被調(diào)試頁面的數(shù)據(jù)全部轉(zhuǎn)發(fā)給調(diào)試器前端
                    if(frontendConnection){
                        frontendConnection.sendUTF(message.utf8Data)
                    }else{
                        console.log('無法轉(zhuǎn)發(fā)給frontend,沒有建立連接')
                    }
                }                
            }
        });
        debugConnection.on('close'function(reasonCode, description{
            console.log((new Date()) + ' Peer remote' + debugConnection.remoteAddress + ' disconnected.');
        });
    }
});

laucher.js 代碼示例:


var host = 'localhost:3232'
var ws = new WebSocket(`ws://${host}`,'remote-debug');       
var search = location.search.slice(1);
var urlParams = {};
search.split('&').forEach(s=>{
    var pair = s.split('=');
    if(pair.length == 2){
        urlParams[pair[0]] = pair[1]
    }
})
ws.onopen = function({
    ws.send(JSON.stringify({type:'remote_debug_page',url:location.href}))
};
ws.onmessage = function (evt)  
    var feed = JSON.parse(received_msg);
    if(feed.type == 'start_debug') {
        // 連接到 webSocketDebuggerUrl
        var debugWS = new WebSocket(`ws://${urlParams.ws}`);  
        debugWS.onopen = function({  
            ws.send(JSON.stringify({type:'start_debug_ready'})); // 確認(rèn)可以開始調(diào)試
            ws.onmessage = function (evt// 轉(zhuǎn)發(fā)到 debugger
                debugWS.send(evt.data);
            }
            ws.onclose = function (evt{
                debugWS.close()
            }
        }
        debugWS.onmessage = function (evt)  
            ws.send(evt.data); // 轉(zhuǎn)發(fā)到 server
        }
        debugWS.onclose = function(
            ws.send(JSON.stringify({type:'remote_page_lost',url:location.href}))
        };
    }
};
ws.onclose = function(
    console.log('連接已關(guān)閉...'); 
};

回放

使用 inspector 時(shí)我們可以發(fā)現(xiàn),只要開啟了 Page.enable 和 Network.enable,就可以一直接收到調(diào)試器后端推送的頁面快照和網(wǎng)絡(luò)請求數(shù)據(jù)。

我們可以略微改造一下 server.js 的代碼,把所有收到的推送數(shù)據(jù)打時(shí)間戳后保存到一個(gè)文件,持久化存儲(chǔ)起來。

if (message.type === 'utf8') {
    var feed = JSON.parse(message.utf8Data);
    if(feed.type == 'remote_debug_page'){  
        debugConnection.sendUTF(JSON.stringify({'type':'start_debug'}));
    }else if(feed.type == 'start_debug_ready'){
        writeStream = fs.createWriteStream(saveFilePath,{flags:'as',encoding'utf8'});
    } else{
        // 全部轉(zhuǎn)發(fā)給 frontendConnection
        if(frontendConnection){
            frontendConnection.sendUTF(message.utf8Data)
        }else{
            console.log('無法轉(zhuǎn)發(fā)給frontend,沒有建立連接')
        }
        // 保存數(shù)據(jù)到文件
        if(feed.method)writeStream.write(message.utf8Data+'\n'
    }                
}

然后我們給 websocket 服務(wù)增加一個(gè)協(xié)議類型,和 inspector 建立連接后,讀取文件中保存的數(shù)據(jù),按照時(shí)間戳上的時(shí)間間隔推送數(shù)據(jù)。

這樣就實(shí)現(xiàn)了回放功能,把之前調(diào)試時(shí)的現(xiàn)場重現(xiàn)一遍。

if(requestedProtocols.indexOf('feedback') != -1){
    feedbackConnection = request.accept('feedback', request.origin);
    feedbackConnection.on('message'function(message{
        // 忽略來的消息
    })
    const fileStream = fs.createReadStream(saveFilePath);
    const rl = readline.createInterface({
        input: fileStream,
        crlfDelayInfinity
    });
    for await (const line of rl) {  // 逐行讀取數(shù)據(jù)
        feedbackConnection.sendUTF(line)
        rl.pause();
        setTimeout(_=>{rl.resume()},1000)
    }
    feedbackConnection.on('close'function(reasonCode, description{
        console.log('feedbackConnection disconnected.');
    });
}

甚至可以更進(jìn)一步,創(chuàng)建一個(gè) websocket 服務(wù)作為調(diào)試器前端,模擬 inspector 發(fā)送請求的邏輯并保存推送數(shù)據(jù)到文件,這樣就實(shí)現(xiàn)了一個(gè)錄制服務(wù)器,可以隨時(shí)錄制調(diào)試現(xiàn)場,然后在需要的時(shí)候播放,因?yàn)橛涗浟藭r(shí)間戳,pause、seek、resume、stop 都可以實(shí)現(xiàn)。

devtools-frontend 的調(diào)用方式

一般來說,我們習(xí)慣用 require/import 的方式調(diào)用模塊,devtools-frontend 雖然也是個(gè) npm 包 ,chrome-devtools-frontend[9],但是卻不方便用 require/import 的方式直接引用。

主要是因?yàn)橹八龅?front_end 應(yīng)用有自己的一套模塊加載邏輯,應(yīng)用的 js、json 配置文件必須在同一個(gè)目錄下,模塊也必須在同一個(gè)目錄下,否則就會(huì)出現(xiàn)路徑錯(cuò)誤。

如果僅使用 front_end 的某個(gè)模塊,還可以用 require/import 來引用。

如果想創(chuàng)建一個(gè)新的應(yīng)用,最好是把整個(gè) front_end 復(fù)制過來修改。

Chrome DevTools Extensions

如果想在 chrome 內(nèi)嵌的調(diào)試面板中增加自定義的能力,可以用 chrome 插件的方式實(shí)現(xiàn),例如vue-devtools[10]。

參考資料

ChromeDevTools/awesome-chrome-devtools[11]

ChromeDevTools/devtools-protocol[12]

參考資料

[1]

devtools-protocol: https://github.com/chromedevtools/devtools-protocol

[2]

Puppeteer: https://github.com/GoogleChrome/puppeteer/

[3]

ndb: https://github.com/GoogleChromeLabs/ndb

[4]

browser_protocol: https://github.com/ChromeDevTools/devtools-protocol/blob/master/json/browser_protocol.json

[5]

js_protocol: https://github.com/ChromeDevTools/devtools-protocol/blob/master/json/js_protocol.json

[6]

Typescript 類型定義: https://github.com/ChromeDevTools/devtools-protocol/tree/master/types

[7]

ChromeDevTools/devtools-frontend: https://github.com/ChromeDevTools/devtools-frontend

[8]

ChromeDevTools/devtools-frontend: https://github.com/ChromeDevTools/devtools-frontend

[9]

chrome-devtools-frontend: https://www.npmjs.com/package/chrome-devtools-frontend

[10]

vue-devtools: https://github.com/vuejs/vue-devtools

[11]

ChromeDevTools/awesome-chrome-devtools: https://github.com/ChromeDevTools/awesome-chrome-devtools

[12]

ChromeDevTools/devtools-protocol: https://github.com/chromedevtools/devtools-protocol

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報(bào)
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
方便快捷的調(diào)試 Node.js 程序
Visual Studio Code 前端調(diào)試不完全指南 | 咀嚼之味
史詩級更新,VSCODE 可無縫調(diào)試瀏覽器了!
【干貨】Chrome插件(擴(kuò)展)開發(fā)全攻略
從零開始編寫一個(gè)chrome插件
好用的谷歌瀏覽器插件
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服