Chrome 开发者工具(DevTools)是一个强大的网页开发和调试工具,内置于 Google Chrome 浏览器中。它提供了多种功能,帮助开发者调试和优化网页。以下是一个详细的 Chrome 开发者工具教程,涵盖了主要功能和使用方法。
Chrome 开发者工具综述
打开 Chrome 开发者工具
有几种方法可以打开 Chrome 开发者工具:
- 快捷键:
- Windows/Linux: Ctrl + Shift + I 或 F12
- macOS: Cmd + Option + I 或 F12
- 右键菜单:
- 右键点击网页,然后选择“检查”(Inspect)。
- Chrome 菜单:
- 点击浏览器右上角的三点菜单,选择“更多工具”(More tools),然后选择“开发者工具”(Developer tools)。
DevTools 面板概览
打开 DevTools 后,你会看到一个包含多个面板的界面。每个面板都有特定的功能。
以下是主要面板的概览:
- Elements(元素):
- 查看和编辑 HTML 和 CSS。
- 检查和修改网页的 DOM 结构和样式。
- 实时编辑 HTML 和 CSS,查看效果。
- Console(控制台):
- 执行 JavaScript 代码,输出日志信息。
- 查看错误和警告信息。
- 交互式调试和测试代码片段。
- Sources(源代码):
- 查看、编辑和调试 JavaScript 代码。
- 设置断点,逐行调试代码。
- 查看和管理网络请求和响应。
- Network(网络):
- 监控所有网络请求,查看请求和响应的详细信息。
- 分析加载时间和性能瓶颈。
- 查看和测试不同的网络条件。
- Performance(性能):
- 记录和分析网页的性能指标。
- 查看页面加载时间、帧率和内存使用情况。
- 优化渲染和资源加载。
- Memory(内存):
- 分析内存使用情况,查找内存泄漏。
- 查看和比较内存快照。
- Application(应用):
- 查看和管理浏览器存储,如 LocalStorage、SessionStorage、IndexedDB 和 Cookies。
- 查看和调试 Service Workers 和 Web Manifest。
- Security(安全):
- 查看网页的安全信息。
- 检查 SSL/TLS 证书的有效性。
Chrome 开发者工具模块详解
Elements 面板
查看和编辑 HTML 和 CSS:
- 选择元素:点击页面上的元素或在 Elements 面板中选择元素,查看其 HTML 代码和应用的 CSS 样式。
- 编辑 HTML:双击元素标签或右键选择“Edit as HTML”,直接修改 HTML 代码。
- 编辑 CSS:在右侧的 Styles 面板中,实时修改 CSS 属性值,查看效果。
示例:
<!DOCTYPE html> <html> <head> <style> .example {color: red;} </style> </head> <body> <div class="example">Hello, World!</div> </body> </html>
- 打开 Elements 面板,选择 <div class=”example”> 元素。
- 在 Styles 面板中,将 color: red; 修改为 color: blue;,页面上文字颜色会立即变化。
Console 面板
执行 JavaScript 代码:
- 在控制台中输入 JavaScript 代码并按回车,立即执行代码。
- 使用 log() 输出调试信息。
示例:
console.log('Hello, World!');
查看错误和警告:
- 控制台会显示 JavaScript 运行时错误和警告信息。
- 点击错误信息,可以跳转到 Sources 面板查看详细的错误位置和堆栈信息。
Sources 面板
查看和编辑 JavaScript 代码:
- 在 Sources 面板中导航到网页加载的所有脚本文件。
- 点击文件名查看和编辑 JavaScript 代码。
设置断点和调试:
- 在代码行号上点击,添加断点。
- 使用顶部的调试控制按钮(继续、逐行调试、步入、步出)进行代码调试。
示例:
function sayHello() { const message = 'Hello, World!'; console.log(message); } sayHello();
- 打开 Sources 面板,找到包含 sayHello 函数的文件。
- 在 log(message); 行设置断点。
- 刷新页面,代码执行到断点处会暂停,可以逐行调试。
Network 面板
监控网络请求:
- 打开 Network 面板,刷新页面,查看所有网络请求。
- 点击单个请求,查看请求头、响应头、载荷和预览。
分析性能瓶颈:
- 查看各资源的加载时间、大小和 HTTP 状态。
- 使用“过滤器”筛选特定类型的资源,如文档、脚本、样式、图片等。
模拟网络条件:
- 在 Network 面板顶部,可以选择不同的网络速度,如“慢 3G”或“快速 3G”,模拟不同的网络条件,查看页面加载性能。
Performance 面板
记录和分析性能:
- 点击 Performance 面板中的“录制”按钮,开始记录页面性能。
- 执行某些操作后,点击“停止”按钮,查看性能分析结果。
查看性能指标:
- 查看页面加载时间、脚本执行时间、渲染时间等。
- 使用时间轴查看各阶段的性能情况,识别性能瓶颈。
Memory 面板
分析内存使用:
- 点击 Memory 面板中的“捕获快照”按钮,生成内存快照。
查看和分析内存使用情况,找出内存泄漏和不必要的内存占用。
比较内存快照:
- 生成多个内存快照,比较它们之间的差异,分析内存增长情况。
Application 面板
查看和管理存储:
- 在 Application 面板中,查看 LocalStorage、SessionStorage、IndexedDB 和 Cookies 的内容。
- 添加、修改或删除存储项。
调试 Service Workers:
- 查看注册的 Service Workers,调试离线功能。
- 清除缓存和存储,重新加载 Service Workers。
Security 面板
检查网页安全信息:
- 查看网页的 SSL/TLS 证书信息。
- 检查是否存在混合内容(HTTP 和 HTTPS 资源混合使用)问题。
Chrome 开发者工具深入
Chrome DevTools 是辅助开发者进行 Web 开发的重要调试工具,DevTools 是 Chromium 的一部分,可以作为独立项目被 Electron 等容器集成。DevTools 主要分为四部分:
- Frontend:调试器前端,默认由 Chromium 内核层集成
- Backend:调试器后端,Chromium、V8 或 js
- Protocol:调试协议
- Message Channels:消息通道,包括:Embedder Channel、WebSocket Channel、Chrome Extensions Channel、USB/ADB Channel
Chrome DevTools Frontend 是一个 Web 应用程序,通过 WebSocket 与 Blink 的 C++ 后端通信。
关于 Chrome 开发者工具的详细使用可以看官方文档,本文不做赘述。Chrome DevTools Extensions 基于 Javascript、CSS、HTML 技术构建可以让用户根据业务需要为 DevTools 增强扩展功能项。当 Chrome DevTools 不能满足我们需求的时候,我们可以写一个 Chrome DevTools Extension,类似于 vue-devtools。
目前主流小程序平台针对小程序特有的技术特征,基于 Chrome DevTools Extensions 进行扩展及改造,赋能小程序开发的各种调试能力。
Chrome Extension
官方文档及示例如下:
- Chrome Extensions: https://developer.chrome.com/extensions
- Sample Extensions: https://developer.chrome.com/extensions/samples
因为 Chrome DevTools Extension 属于 Chrome Extension 中的一种特殊的拓展程序。在了解 Chrome DevTools Extension 之前,我们先简单的了解一下 Chrome Extension(Chrome 拓展)的内容。
我们经常说的 Chrome“插件”,其实不是真正意义上的 Chrome Plug-in,一般是指 Chrome Extension(简称“拓展”)。
- 扩展(Extension),指的是通过调用 Chrome 提供的 Chrome API 来扩展浏览器功能的一种组件,工作在浏览器层面,使用 HTML+Javascript 语言开发。
- 插件(Plug-in),指的是通过调用 Webkit 内核 NPAPI/PPAPI 来扩展内核功能的一种组件,工作在内核层面,理论上可以用任何一种生成本地二进制程序的语言开发,比如 C/C++、Delphi 等。比如 Flash player 插件,就属于这种类型。一般在网页中用 <object> 或者 <embed> 标签声明的部分,就要靠插件来渲染。
Chrome 拓展一般包含如下几个组件:
- Manifest
- Background Script
- UI Elements
- Content Script
- Options Page
- DevTools
Chrome Extension 架构图:
Chrome 拓展的 JS 主要可以分为这 5 类:injected script、content-script、popup js、background js 和 devtools js,
JS 种类 | 可访问的 API | DOM 访问情况 | JS 访问情况 | 直接跨域 |
injected script | 和普通 JS 无任何差别,不能访问任何扩展 API | 可以访问 | 可以访问 | 不可以 |
content script | 只能访问 extension、runtime 等部分 API | 可以访问 | 不可以 | 不可以 |
popup js | 可访问绝大部分 API,除了 devtools 系列 | 不可直接访问 | 不可以 | 可以 |
background js | 可访问绝大部分 API,除了 devtools 系列 | 不可直接访问 | 不可以 | 可以 |
devtools js | 只能访问 devtools、extension、runtime 等部分 API | 可以 | 可以 | 不可以 |
Chrome DevTools Extensions
官方文档及示例如下:
- 官方文档:https://developer.chrome.com/extensions/devtools
- 中文文档:https://crxdoc-zh.appspot.com/extensions/devtools
- awesome-chrome-devtools: https://github.com/ChromeDevTools/awesome-chrome-devtools
Sample DevTools Extensions:https://developer.chrome.com/devtools/docs/sample-extensions
Chrome DevTools 扩展程序的结构与 Chrome 其他任何的扩展程序一样:它可以具有背景页面(background),内容脚本(content-scripts)和其他选项,此外每个 Chrome DevTools 扩展都有一个 Chrome DevTools 页面,该页面可以访问 DevTools API。Chrome DevTools 扩展程序可以为 Chrome DevTools 添加新功能,可以添加新的 UI 面板和侧边栏,与检查的页面进行交互,获取有关网络请求的信息等。
Chrome DevTools Extension 架构图:
DevTools page 在 manifest.json 中注册,必须为一个 html 页面,对用户不可见,可调用以下 Chrome DevTools Extension 特有的 API:
- devtools.inspectedWindow:获取被审查窗口的相关信息
- devtools.network:获取有关网络请求的信息
- devtools.panels:面板相关
Background Page 是常驻后台运行的 JS 脚本,拥有对 Extensions API 的全部调用权限,可以和 DevTools Page 进行通信; Inspected Window 指当前 DevTools 检测的 Web 页,可被 Background Page 注入内容脚本。
Chrome DevTools Protocol
- 官方文档:https://chromedevtools.github.io/devtools-protocol
- getting-started-with-cdp:https://github.com/aslushnikov/getting-started-with-cdp
Chrome DevTools Protocol 允许第三方对基于 Chrome 的 Web 应用程序进行调试、分析等,基于 WebSocket,利用 WebSocket 建立连接 DevTools 和浏览器内核的快速数据通道。基于 WebSocket 建立连接 DevTools 和浏览器内核的快速数据通道,DevTools Frontend 中的源代码(Connections.js):
/** * @param {function()} websocketConnectionLost * @return {!ProtocolModule.InspectorBackend.Connection} */ 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(); }
该协议把操作划分为不同的域(domain),比如 DOM、Debugger、Network、Console 和 Timeline 等,可以理解为 DevTools 中的不同功能模块。
每个域(domain)定义了它所支持的 command 和它所产生的 event。每个 command 包含 request 和 response 两部分,request 部分指定所要进行的操作以及操作说要的参数,response 部分表明操作状态,成功或失败。command 和 event 中可能涉及到非基本数据类型,在 domain 中被归为 Type,比如:’frameId’: <FrameId>,其中 FrameId 为非基本数据类型。
基于 Chrome Debugging Protocol 的 Client 端有常见几种:
基于 HTML5 标准的 WebSocket 或 Node ws 库
// 建立连接 var ws = new WebSocket('ws://localhost:9222/devtools/page/A12A4B08-E5AF-4A84-A86A-A1C86E731D7F"'); // 调用 Command ws.onmessage = function(event) { console.log(event.data); // 获取数据:{"method":"Page.loadEventFired","params":{"timestamp":1402317772.874949}} }; ws.send('{"id":1,"method":"Page.navigate","params":{"url":"http://www.github.com"}}');
基于 Node chrome-remote-interface 库
const CDP = require("chrome-remote-interface"); const target = { port: '', ws: '' }; const client = await CDP({ port: target.port, target: target.ws, local: true }); client.on('event', (message) => { console.log(message); }); const { Runtime, Page } = client; Runtime.enable(); Page.enable(); const data = await Runtime.evaluate({ expression: 'document.documentElement.outerHTML' }); console.log(data.result.value);
Electron集成DevTools
setDevToolsWebContents
Electron官方文档 contents.setDevToolsWebContents(devToolsWebContents),在renderer层建立两个WebView,将Simulator和DevTools建立联系。
<webview id="simulator" src="https://zhaomenghuan.js.org"></webview> <webview id="devtools"></webview> const simulatorView = document.getElementById('simulator'); const devtoolsView = document.getElementById('devtools'); simulatorView.addEventListener('dom-ready', () => { const simulatorContents = simulatorView.getWebContents(); const devtoolsContents = devtoolsView.getWebContents(); simulatorContents.setDevToolsWebContents(devtoolsContents); simulatorContents.openDevTools(); });
这种方法devtoolsWebView加载的地址是:
chrome-devtools://devtools/bundled/inspector.html?remoteBase=https://chrome-devtools-frontend.appspot.com/serve_file/@7accc8730b0f99b5e7c0702ea89d1fa7c17bfe33/&can_dock=&toolbarColor=rgba(223,223,223,1)&textColor=rgba(0,0,0,1)&experiments=true
在Electron2.x版本中上述的方法生效,通过setDevToolsWebContents方法,我们将两个WebView建立了联系,可以实现之间的交互,但是在Electron6.x版本上测试发现居然不生效。通过github issues找到一种新的解决办法,使用BrowserView代替WebView尝试了一下:
// main process let mainWindow; let devtoolsView; // WorkbenchWindow mainWindow = new BrowserWindow({ width, height, useContentSize: false, titleBarStyle: 'hidden', webPreferences: { webSecurity: false, nodeIntegration: true, webviewTag: true } }); mainWindow.maximize(); mainWindow.loadURL(winURL); // DevtoolsView devtoolsView = new BrowserView(); mainWindow.setBrowserView(devtoolsView); devtoolsView.setBounds({ x: 330, y: 101, width: width - 330, height: height - 101 }); ipcMain.on('initialized', (event, message) => { const container = webContents.getAllWebContents().find((item) => { return item.getURL().includes(message); }); if (container) { container.setDevToolsWebContents(devtoolsView.webContents); container.debugger.attach(); container.openDevTools(); } }); // renderer process const simulatorView = document.getElementById('simulator'); simulatorView.addEventListener('dom-ready', () => { ipcRenderer.send('initialized', simulatorView.src); });
再回头看了看官方v8.0.2最新文档警告提示不要使用WebView标签,建议使用iframe和BrowserView替代。
Electron的webview标签基于Chromium webview,后者正在经历巨大的架构变化。这将影响webview的稳定性,包括呈现、导航和事件路由。我们目前建议不使用webview标签,并考虑其他替代方案,如iframe、Electron的BrowserView或完全避免嵌入内容的体系结构。
这里的BrowserView相对WebView、BrowserWindow有什么区别呢?Electron中WebView是DOM层级结构的一部分,BrowserView位于操作系统窗口层次结构。BrowserView与Chrome浏览器标签页类似,可以作为一个子窗口,它的位置是相对于父窗口。另外相对WebView而言,Chrome浏览器标签页的错误修复很快,BrowserView比WebView更容易解决一些错误,且BrowserView比WebView运行更快。
重要结论:通过Electron setDevToolsWebContents方法,可以使用任何WebContents在其中显示devtools,包括BrowserWindow,BrowserView和webview标签,这种方式适合IDE进行本地Web化模拟调试,模拟器与调试器建立联系。
Remote Debugging
基本原理
什么是远程调试?远程调试可以让您从自己的开发计算机上检查Android设备上运行的页面,当然开发本地的页面也可以通过远程调试的方式实现。
远程调试的交互流程:
Electron支持在app模块的ready事件触发之前使用app.commandLine.appendSwitch添加Chrome命令行参数:
// 主进程 main.js const {app} = require('electron'); // 远程调试 const port = await getPort(); process.env.EMP_REMOTE_DEBUGGING_PORT = port; app.commandLine.appendSwitch('remote-debugging-port', `${port}`); app.commandLine.appendSwitch('remote-debugging-address', 'http://127.0.0.1'); app.on('ready', () => { // ... })
可以通过 /json 或 /json/list获取所有可用的WebSocket目标地址。
返回一个数组,里面是所有可以远程调试的页面,其中包含以下字段信息:
- description:页面信息描述
- devtoolsFrontendUrl:调试URL地址
- id:页面ID
- webSocketDebuggerUrl:WebViewDebugServer的WebSocket地址
获取远程WebViewDevToolsFrontend地址:
function getTargetWebViewDevtoolsFrontendUrl(url){ return fetch(`http://127.0.0.1:9222/json`) .then(res => res.json()) .then(res => { const target = res.find( child => child.type === 'webview' && child.url === url ); return target.devtoolsFrontendUrl; }); }
然后加上remote-debugging-address和remote-debugging-port就是DevToolsFrontend完整的地址,然后可以使用WebView、BrowserView展示出来,如下:
http://127.0.0.1:9222/devtools/inspector.html?ws=127.0.0.1:9222/devtools/page/eac1573a-c713-4421-ad64-3e07fb20f034
这个地址相当于两部分组成,一个是Inspector(调试器),由Chromium默认提供集成,也可以进行修改ChromeDevToolsFrontend源代码进行自定义集成,ws是Chromium内核层为目标WebView生成的唯一的websocketDebuggerUrl。目前Electron限制,WebView集成DevToolsFrontend前端页面不能加载自定义拓展。
真机调试
真机调试整体流程如下:
Android平台真机调试
对于AndroidWebView调试,打开Chrome://inspect可以显示每一个连接上的设备,以及它们打开了的浏览器标签和启用调试的WebViews,如下图:
基于ADB调试AndroidWebView的原理如下:
$ adb shell cat /proc/net/unix | grep --text _devtools_remote 0000000000000000: 00000002 00000000 00010000 0001 01 28940245 @stetho_org.js.emp.engine.sample:emp_devtools_remote 0000000000000000: 00000002 00000000 00010000 0001 01 28942398 @webview_devtools_remote_14277 $ adb forward tcp:9223 localabstract:webview_devtools_remote_14277
webview_devtools_remote后面带的_14277这种是对应的pid,可以使用adb forward命令将本地9223端口映射到远程设备的unix domain socket(webview_devtools_remote_8208),这样就可以在本地访问到AndroidWebViewDebugServer。
其中devtoolsFrontendUrl是Chrome云服务器提供,根据AndroidWebView集成的Chromium不同版本加载不同的前端地址,4cd8c034a5b41cc6da41e00e42d9aadfaa34932b和WebKitVersion对应。
iOS平台真机调试
iOS设备可以连接数据线借助Safari或者按安装SafariTechnologyPreview 远程调试。如果想使用Chrome调试,可以使用RemoteDebugiOSWebKitAdapter进行代理。
关于iOS平台调试权限的问题,开发阶段,用开发者账号build出来的app可以很方便的调试,但是苹果应用商店中的app使用Distribution签名,无法直接打开webview的远程调试。因此我们通过替换ipa包签名方式改成开发者的签名实现远程调试。
定制Inspector
Electron支持ChromeDevTools扩展程序,可增强开发工具调试流行web框架的能力。
Electron主进程通过API管理DevToolsExtension:
- addDevToolsExtension(path)
- removeDevToolsExtension(name)
- getDevToolsExtensions()
经过测试发现 setDevToolsWebContents 模式下 DevTools 面板可以加载自定义 DevToolsExtenson,但是远程调试模式下自定义 DevToolsExtenson 无法加载。目前主流小程序平台调试器的自定义面板如 Wxml、SwanElement、AppData、Storage 等都是通过 ChromeDevToolsExtension 实现,都是改造devtools-frontend 项目。
可以通过 preload 或者注入 JS Script 可以实现对 DevTools 前端界面进行定制:
// 隐藏默认面板 const tabbedPane = window.UI.inspectorView._tabbedLocation._tabbedPane; tabbedPane.closeTab('elements'); tabbedPane.closeTab('timeline'); tabbedPane.closeTab('resources'); // 新增 Connection const capabilitiesForPageFrameTarget = () => { return window.SDK.Target.Capability.Browser | window.SDK.Target.Capability.DOM | window.SDK.Target.Capability.DeviceEmulation | window.SDK.Target.Capability.Emulation | window.SDK.Target.Capability.Input | window.SDK.Target.Capability.JS | window.SDK.Target.Capability.Log | window.SDK.Target.Capability.Network | window.SDK.Target.Capability.ScreenCapture | window.SDK.Target.Capability.Security | window.SDK.Target.Capability.Target | window.SDK.Target.Capability.Tracing | window.SDK.Target.Capability.Inspector; }; const createPageFrameConnection = (params: any) => { const onDisconnect = (message: any) => { console.log('', '-onDisconnect-' + message); }; let wsConnection = new window.SDK.WebSocketConnection(url, onDisconnect, { onMessage: params.onMessage, onDisconnect }); wsConnection.isPageFrame = true; return wsConnection; }; window.SDK.targetManager.createTarget('page-frame', 'Page', capabilitiesForPageFrameTarget(), createPageFrameConnection, null);
参考链接: