1# Highlighters
2
3This article provides technical documentation about DevTools highlighters.
4
5By highlighter, we mean anything that DevTools displays on top of the content page, in order to highlight an element, a set of elements or shapes to users.
6
7The most obvious form of highlighter is the box-model highlighter, whose job is to display the 4 box-model regions on top of a given element in the content page, as illustrated in the following screen capture:
8
9![Box-model highlighter](../resources/box-model-highlighter-screenshot.png)
10
11But there can be a wide variety of highlighters. In particular, highlighters are a pretty good way to give detailed information about:
12
13* the exact form of a css shape,
14* how a css transform applied to an element,
15* which are all the elements that match a given selector,
16* ...
17
18## Using highlighters
19
20Highlighters run on the debuggee side, not on the toolbox side. This is so that it's possible to highlight elements on a remote device for instance. This means you need to go through the [Remote Debugging Protocol](protocol.md) to use a highlighter.
21
22The InspectorFront provides the following method:
23
24| Method                           | Description                                                                                                                                                                                                                                                                                                   |
25|----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
26| `getHighlighterByType(typeName)` | Instantiate a new highlighter, given its type (as a String). At the time of writing, the available types of highlighters are: `CssGridHighlighter`, `BoxModelHighlighter`, `CssTransformHighlighter`, `FlexboxHighlighter`, `FontsHighlighter`, `GeometryEditorHighlighter`, `MeasuringToolHighlighter`, `PausedDebuggerOverlay`, `RulersHighlighter`, `SelectorHighlighter` and `ShapesHighlighter`. This returns a promise that resolves to the new instance of [protocol.js](https://wiki.mozilla.org/DevTools/protocol.js) actor. |
27
28### The highlighter API
29
30When getting a highlighter via `InspectorFront.getHighlighterByType(typeName)`, the right type of highlighter will be instantiated on the server-side and will be wrapped into a `CustomHighlighterActor` and that's what will be returned to the caller. This means that all types of highlighters share the same following API:
31
32| Method                                   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
33|------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
34| `show(NodeActor node[, Object options])` | Highlighters are hidden by default. Calling this method is what makes them visible. The first, mandatory, parameter should be a NodeActor. NodeActors are what the WalkerActor return. It's easy to get a NodeActor for an existing DOM node. For example `toolbox.walker.querySelector(toolbox.walker.rootNode, "css selector")` resolves to a NodeFront (the client-side version of the NodeActor) which can be used as the first parameter. The second, optional, parameter depends on the type of highlighter being used. |
35| `hide()`                                 | Hides the highlighter.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
36| `finalize()`                             | Destroys the highlighter.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
37
38## Creating new highlighters
39
40Before digging into how one goes about adding a new type of highlighter to the DevTools, it is worth understanding how are highlighters displayed in the page.
41
42### Inserting content in the page
43
44Highlighters use web technology themselves to display the required information on screen. For instance, the box-model highlighter uses SVG to draw the margin, border, padding and content regions over the highlighted node.
45
46This means the highlighter content needs to be inserted in the page, but in a non-intrusive way. Indeed, the DevTools should never alter the page unless the alteration was done by the user (like changing the DOM using the inspector or a CSS rule via the style-editor for example). So simply appending the highlighter's markup in the content document is not an option.
47
48Furthermore, highlighters not only need to work with Firefox Desktop, but they should work just as well on Firefox OS, Firefox for Android, and more generally anything that runs the Gecko rendering engine. Therefore appending the highlighter's markup to the browser chrome XUL structure isn't an option either.
49
50To this end, DevTools highlighters make use of a (chrome-only) API:
51
52```
53 /**
54  * Chrome document anonymous content management.
55  * This is a Chrome-only API that allows inserting fixed positioned anonymous
56  * content on top of the current page displayed in the document.
57  * The supplied content is cloned and inserted into the document's CanvasFrame.
58  * Note that this only works for HTML documents.
59  */
60 partial interface Document {
61   /**
62    * Deep-clones the provided element and inserts it into the CanvasFrame.
63    * Returns an AnonymousContent instance that can be used to manipulate the
64    * inserted element.
65    */
66   [ChromeOnly, NewObject, Throws]
67   AnonymousContent insertAnonymousContent(Element aElement);
68
69   /**
70    * Removes the element inserted into the CanvasFrame given an AnonymousContent
71    * instance.
72    */
73   [ChromeOnly, Throws]
74   void removeAnonymousContent(AnonymousContent aContent);
75 };
76```
77
78Using this API, it is possible for chrome-privileged JS to insert arbitrary DOM elements on top of the content page.
79
80Technically, the DOM element is inserted into the `CanvasFrame` of the document. The `CanvasFrame` is part of the rendered frame tree and the DOM element is part of the native anonymous elements of the `CanvasFrame`.
81
82Consider the following simple example:
83
84```js
85 let el = document.createElement("div");
86 el.textContent = "My test element";
87 let insertedEl = document.insertAnonymousContent(el);
88```
89
90In this example, the test DIV will be inserted in the page, and will be displayed on top of everything else, in a way that doesn't impact the current layout.
91
92### The AnonymousContent API
93
94In the previous example, the returned `insertedEl` object isn't a DOM node, and it certainly is not `el`. It is a new object, whose type is `AnonymousContent` ([see the WebIDL here](https://searchfox.org/mozilla-central/source/dom/webidl/AnonymousContent.webidl)).
95
96Because of the way content is inserted into the page, it isn't wanted to give consumers a direct reference to the inserted DOM node. This is why `document.insertAnonymousContent(el)` actually **clones** `el` and returns a new object whose API lets consumers make changes to the inserted element in a way that never gives back a reference to the inserted DOM node.
97
98### CanvasFrameAnonymousContentHelper
99
100In order to help with the API described in the previous section, the `CanvasFrameAnonymousContentHelper` class was introduced.
101
102Its goal is to provide a simple way for highlighters to insert their content into the page and modify it dynamically later. One of its goal is also to re-insert the highlighters' content on page navigation. Indeed, the frame tree is destroyed when the page is navigated away from since it represents the document element. One thing to note is that highlighter content insertion is asynchronous and `CanvasFrameAnonymousContentHelper` users must call and wait for its `initialize` method to resolve.
103
104Using this helper is quite simple:
105
106```js
107let helper = new CanvasFrameAnonymousContentHelper(targetActor, this.buildMarkup.bind(this));
108await helper.initialize();
109```
110
111It only requires a `targetActor`, which highlighters get when they are instantiated, and a callback function that will be used to create and insert the content the first time the highlighter is shown, and every time there's a page navigation.
112
113The returned object provides the following API:
114
115| Method                                    | Description                                                |
116|-------------------------------------------|------------------------------------------------------------|
117| `getTextContentForElement(id)`            | Get the textContent of an element given its ID.            |
118| `setTextContentForElement(id, text)`      | Set the textContent of an element given its ID.            |
119| `setAttributeForElement(id, name, value)` | Set an attribute value of an element given its ID.         |
120| `getAttributeForElement(id, name)`        | Get an attribute value of an element given its ID.         |
121| `removeAttributeForElement(id, name)`     | Remove an attribute of an element given its ID.            |
122| `content`                                 | This property returns the wrapped AnonymousContent object. |
123| `destroy()`                               | Destroy the helper instance.                               |
124
125  ### Creating a new highlighter class
126
127A good way to get started is by taking a look at [existing highlighters here](https://searchfox.org/mozilla-central/rev/1a973762afcbc5066f73f1508b0c846872fe3952/devtools/server/actors/highlighters.js#519-530).
128
129Here is some boilerplate code for a new highlighter class:
130
131```js
132 function MyNewHighlighter(targetActor) {
133   this.doc = targetActor.window.document;
134   this.markup = new CanvasFrameAnonymousContentHelper(targetActor, this._buildMarkup.bind(this));
135   this.markup.initialize();
136 }
137
138 MyNewHighlighter.prototype = {
139   destroy: function() {
140     this.doc = null;
141     this.markup.destroy();
142   },
143
144   _buildMarkup: function() {
145     let container = this.markup.anonymousContentDocument.createElement("div");
146     container.innerHTML = '<div id="new-highlighted-" style="display:none;">';
147     return container;
148   },
149
150   show: function(node, options) {
151     this.markup.removeAttributeForElement("new-highlighted-el", "style");
152   },
153
154   hide: function() {
155     this.markup.setAttributeForElement("new-highlighted-el", "style", "display:none;");
156   }
157 };
158```
159
160In most situations, the `container` returned by `_buildMarkup` will be absolutely positioned, and will need to contain elements with IDs, so that these can then later be moved, resized, hidden or shown in `show` and `hide` using the AnonymousContent API.
161
162### The AutoRefreshHighlighter parent class
163
164It is worth mentioning this class as it may be a useful parent class to inherit a new highlighter from in some situations.
165
166If the new highlighter's job is to highlight an element in the DOM, then it most likely should inherit from `AutoRefreshHighlighter`.
167
168The `AutoRefreshHighlighter` class updates itself in a loop, checking if the currently highlighted node's geometry has changed since the last iteration. This is useful to make sure the highlighter **follows** the highlighted node around, in case the layout around it changes, or in case it is an animated node.
169
170Sub classes must implement the following methods:
171
172| Method      | Description                                                                         |
173|-------------|-------------------------------------------------------------------------------------|
174| `_show()`   | Called when the highlighter should be shown.                                        |
175| `_update()` | Called while the highlighter is shown and the geometry of the current node changes. |
176| `_hide()`   | Called when the highlighter should be hidden.                                       |
177
178Sub classes will have access to the following properties:
179
180| Property            | Description                               |
181|---------------------|-------------------------------------------|
182| `this.currentNode`  | The node to be shown.                     |
183| `this.currentQuads` | All of the node's box model region quads. |
184| `this.win`          | The current window                        |
185