荧光笔

本文档提供了有关 DevTools 荧光笔的技术文档。

荧光笔是指 DevTools 在内容页面顶部显示的任何内容,用于突出显示元素、一组元素或形状供用户查看。

最明显的荧光笔形式是盒模型荧光笔,其作用是在内容页面上给定元素的顶部显示 4 个盒模型区域,如下面的屏幕截图所示

Box-model highlighter

但是荧光笔的种类很多。特别是,荧光笔是提供有关以下方面详细信息的绝佳方式:

  • css 形状的确切形式,

  • 如何将 css 变换应用于元素,

  • 与给定选择器匹配的所有元素,

使用荧光笔

荧光笔在被调试方运行,而不是在工具箱端运行。这样就可以例如在远程设备上突出显示元素。这意味着您需要通过 远程调试协议 来使用荧光笔。

InspectorFront 提供以下方法

方法 描述
getHighlighterByType(typeName) 根据其类型(作为字符串)实例化一个新的荧光笔。荧光笔的可用类型在 devtools/shared/highlighters.mjs 中定义。在撰写本文时,荧光笔的可用类型为:CssGridHighlighterBoxModelHighlighterCssTransformHighlighterFlexboxHighlighterFontsHighlighterGeometryEditorHighlighterMeasuringToolHighlighterPausedDebuggerOverlayRulersHighlighterSelectorHighlighterShapesHighlighterViewportSizeHighlighterViewportSizeOnResizeHighlighter。这将返回一个 promise,该 promise 解析为 protocol.js actor 的新实例。

荧光笔 API

当通过 InspectorFront.getHighlighterByType(typeName) 获取荧光笔时,服务器端将实例化正确的荧光笔类型,并将其包装到 CustomHighlighterActor 中,这将返回给调用方。这意味着所有类型的荧光笔都共享以下相同的 API

方法 描述
show(NodeActor node[, Object options]) 荧光笔默认情况下是隐藏的。调用此方法会使它们可见。第一个必填参数应该是 NodeActor。NodeActor 是 WalkerActor 返回的内容。很容易为现有的 DOM 节点获取 NodeActor。例如,toolbox.walker.querySelector(toolbox.walker.rootNode, "css selector") 解析为 NodeFront(NodeActor 的客户端版本),它可以用作第一个参数。第二个可选参数取决于正在使用的荧光笔类型。
hide() 隐藏荧光笔。
finalize() 销毁荧光笔。

创建新的荧光笔

在深入了解如何在 DevTools 中添加新的荧光笔类型之前,了解荧光笔如何在页面中显示非常有帮助。

在页面中插入内容

荧光笔本身使用 Web 技术在屏幕上显示所需的信息。例如,盒模型荧光笔使用 SVG 在突出显示的节点上绘制边距、边框、填充和内容区域。

这意味着需要将荧光笔内容插入页面,但要以非侵入性的方式进行。实际上,除非用户进行了更改(例如,使用检查器更改 DOM 或通过样式编辑器更改 CSS 规则),否则 DevTools 永远不会更改页面。因此,简单地将荧光笔的标记附加到内容文档中不是一种选择。

此外,荧光笔不仅需要与 Firefox 桌面版一起使用,还需要在 Firefox OS、Firefox for Android 以及更广泛地运行 Gecko 渲染引擎的任何内容上都能正常工作。因此,将荧光笔的标记附加到浏览器 chrome XUL 结构也不是一种选择。

为此,DevTools 荧光笔利用了一个(仅限 chrome)API

 /**
  * Chrome document anonymous content management.
  * This is a Chrome-only API that allows inserting fixed positioned anonymous
  * content on top of the current page displayed in the document.
  * The supplied content is cloned and inserted into the document's CanvasFrame.
  * Note that this only works for HTML documents.
  */
 partial interface Document {
   /**
    * Deep-clones the provided element and inserts it into the CanvasFrame.
    * Returns an AnonymousContent instance that can be used to manipulate the
    * inserted element.
    */
   [ChromeOnly, NewObject, Throws]
   AnonymousContent insertAnonymousContent(Element aElement);

   /**
    * Removes the element inserted into the CanvasFrame given an AnonymousContent
    * instance.
    */
   [ChromeOnly, Throws]
   void removeAnonymousContent(AnonymousContent aContent);
 };

使用此 API,chrome 权限的 JS 可以将任意 DOM 元素插入内容页面的顶部。

从技术上讲,DOM 元素被插入到文档的 CanvasFrame 中。 CanvasFrame 是渲染帧树的一部分,DOM 元素是 CanvasFrame 的本机匿名元素的一部分。

考虑以下简单示例

 let el = document.createElement("div");
 el.textContent = "My test element";
 let insertedEl = document.insertAnonymousContent(el);

在此示例中,测试 DIV 将插入页面,并以不会影响当前布局的方式显示在其他所有内容的顶部。

AnonymousContent API

在前面的示例中,返回的 insertedEl 对象不是 DOM 节点,当然也不是 el。它是一个新对象,其类型为 AnonymousContent此处查看 WebIDL)。

由于内容插入页面的方式,因此不希望为使用者提供对插入的 DOM 节点的直接引用。这就是为什么 document.insertAnonymousContent(el) 实际上克隆 el 并返回一个新对象,其 API 允许使用者以永远不会返回对插入的 DOM 节点的引用的方式更改插入的元素。

CanvasFrameAnonymousContentHelper

为了帮助使用上一节中描述的 API,引入了 CanvasFrameAnonymousContentHelper 类。

其目标是为荧光笔提供一种简单的方法,将它们的内容插入页面,并在以后动态修改它们。其目标之一也是在页面导航时重新插入荧光笔的内容。实际上,当页面导航离开时,帧树会被销毁,因为它表示文档元素。需要注意的是,荧光笔内容插入是异步的,CanvasFrameAnonymousContentHelper 用户必须调用并等待其 initialize 方法解析。

使用此助手非常简单

let helper = new CanvasFrameAnonymousContentHelper(targetActor, this.buildMarkup.bind(this));
await helper.initialize();

它只需要一个 targetActor(荧光笔在实例化时会获取),以及一个回调函数,该函数将在第一次显示荧光笔时以及每次页面导航时用于创建和插入内容。

返回的对象提供以下 API

方法 描述
getTextContentForElement(id) 获取给定 ID 的元素的 textContent。
setTextContentForElement(id, text) 设置给定 ID 的元素的 textContent。
setAttributeForElement(id, name, value) 设置给定 ID 的元素的属性值。
getAttributeForElement(id, name) 获取给定 ID 的元素的属性值。
removeAttributeForElement(id, name) 删除给定 ID 的元素的属性。
content 此属性返回包装的 AnonymousContent 对象。
destroy() 销毁助手实例。

创建新的荧光笔类

一个好的入门方法是查看 此处现有的荧光笔

以下是新荧光笔类的某些样板代码

 function MyNewHighlighter(targetActor) {
   this.doc = targetActor.window.document;
   this.markup = new CanvasFrameAnonymousContentHelper(targetActor, this._buildMarkup.bind(this));
   this.markup.initialize();
 }

 MyNewHighlighter.prototype = {
   destroy: function() {
     this.doc = null;
     this.markup.destroy();
   },

   _buildMarkup: function() {
     let container = this.markup.anonymousContentDocument.createElement("div");
     container.innerHTML = '<div id="new-highlighted-" style="display:none;">';
     return container;
   },

   show: function(node, options) {
     this.markup.removeAttributeForElement("new-highlighted-el", "style");
   },

   hide: function() {
     this.markup.setAttributeForElement("new-highlighted-el", "style", "display:none;");
   }
 };

在大多数情况下,_buildMarkup 返回的 container 将被绝对定位,并且需要包含具有 ID 的元素,以便稍后可以在 showhide 中使用 AnonymousContent API 移动、调整大小、隐藏或显示这些元素。

AutoRefreshHighlighter 父类

值得一提的是这个类,因为它在某些情况下可能是一个有用的继承父类。

如果新荧光笔的作用是在 DOM 中突出显示元素,那么它很可能应该继承自 AutoRefreshHighlighter

AutoRefreshHighlighter 类在循环中更新自身,检查当前突出显示节点的几何形状自上次迭代以来是否已更改。这对于确保荧光笔跟随突出显示的节点很有用,以防其周围的布局发生更改,或者以防它是动画节点。

子类必须实现以下方法

方法 描述
_show() 当应显示荧光笔时调用。
_update() 在显示荧光笔并且当前节点的几何形状发生更改时调用。
_hide() 当应隐藏荧光笔时调用。

子类将可以访问以下属性

属性 描述
this.currentNode 要显示的节点。
this.currentQuads 节点的所有盒模型区域四边形。
this.win 当前窗口