highlighters.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. const { Ci } = require("chrome");
  6. const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
  7. const EventEmitter = require("devtools/shared/event-emitter");
  8. const events = require("sdk/event/core");
  9. const protocol = require("devtools/shared/protocol");
  10. const Services = require("Services");
  11. const { isWindowIncluded } = require("devtools/shared/layout/utils");
  12. const { highlighterSpec, customHighlighterSpec } = require("devtools/shared/specs/highlighters");
  13. const { isXUL } = require("./highlighters/utils/markup");
  14. const { SimpleOutlineHighlighter } = require("./highlighters/simple-outline");
  15. const HIGHLIGHTER_PICKED_TIMER = 1000;
  16. const IS_OSX = Services.appinfo.OS === "Darwin";
  17. /**
  18. * The registration mechanism for highlighters provide a quick way to
  19. * have modular highlighters, instead of a hard coded list.
  20. * It allow us to split highlighers in sub modules, and add them dynamically
  21. * using add-on (useful for 3rd party developers, or prototyping)
  22. *
  23. * Note that currently, highlighters added using add-ons, can only work on
  24. * Firefox desktop, or Fennec if the same add-on is installed in both.
  25. */
  26. const highlighterTypes = new Map();
  27. /**
  28. * Returns `true` if a highlighter for the given `typeName` is registered,
  29. * `false` otherwise.
  30. */
  31. const isTypeRegistered = (typeName) => highlighterTypes.has(typeName);
  32. exports.isTypeRegistered = isTypeRegistered;
  33. /**
  34. * Registers a given constructor as highlighter, for the `typeName` given.
  35. * If no `typeName` is provided, is looking for a `typeName` property in
  36. * the prototype's constructor.
  37. */
  38. const register = (constructor, typeName = constructor.prototype.typeName) => {
  39. if (!typeName) {
  40. throw Error("No type's name found, or provided.");
  41. }
  42. if (highlighterTypes.has(typeName)) {
  43. throw Error(`${typeName} is already registered.`);
  44. }
  45. highlighterTypes.set(typeName, constructor);
  46. };
  47. exports.register = register;
  48. /**
  49. * The Highlighter is the server-side entry points for any tool that wishes to
  50. * highlight elements in some way in the content document.
  51. *
  52. * A little bit of vocabulary:
  53. * - <something>HighlighterActor classes are the actors that can be used from
  54. * the client. They do very little else than instantiate a given
  55. * <something>Highlighter and use it to highlight elements.
  56. * - <something>Highlighter classes aren't actors, they're just JS classes that
  57. * know how to create and attach the actual highlighter elements on top of the
  58. * content
  59. *
  60. * The most used highlighter actor is the HighlighterActor which can be
  61. * conveniently retrieved via the InspectorActor's 'getHighlighter' method.
  62. * The InspectorActor will always return the same instance of
  63. * HighlighterActor if asked several times and this instance is used in the
  64. * toolbox to highlighter elements's box-model from the markup-view,
  65. * box model view, console, debugger, ... as well as select elements with the
  66. * pointer (pick).
  67. *
  68. * Other types of highlighter actors exist and can be accessed via the
  69. * InspectorActor's 'getHighlighterByType' method.
  70. */
  71. /**
  72. * The HighlighterActor class
  73. */
  74. var HighlighterActor = exports.HighlighterActor = protocol.ActorClassWithSpec(highlighterSpec, {
  75. initialize: function (inspector, autohide) {
  76. protocol.Actor.prototype.initialize.call(this, null);
  77. this._autohide = autohide;
  78. this._inspector = inspector;
  79. this._walker = this._inspector.walker;
  80. this._tabActor = this._inspector.tabActor;
  81. this._highlighterEnv = new HighlighterEnvironment();
  82. this._highlighterEnv.initFromTabActor(this._tabActor);
  83. this._highlighterReady = this._highlighterReady.bind(this);
  84. this._highlighterHidden = this._highlighterHidden.bind(this);
  85. this._onNavigate = this._onNavigate.bind(this);
  86. let doc = this._tabActor.window.document;
  87. // Only try to create the highlighter when the document is loaded,
  88. // otherwise, wait for the navigate event to fire.
  89. if (doc.documentElement && doc.readyState != "uninitialized") {
  90. this._createHighlighter();
  91. }
  92. // Listen to navigation events to switch from the BoxModelHighlighter to the
  93. // SimpleOutlineHighlighter, and back, if the top level window changes.
  94. events.on(this._tabActor, "navigate", this._onNavigate);
  95. },
  96. get conn() {
  97. return this._inspector && this._inspector.conn;
  98. },
  99. form: function () {
  100. return {
  101. actor: this.actorID,
  102. traits: {
  103. autoHideOnDestroy: true
  104. }
  105. };
  106. },
  107. _createHighlighter: function () {
  108. this._isPreviousWindowXUL = isXUL(this._tabActor.window);
  109. if (!this._isPreviousWindowXUL) {
  110. this._highlighter = new BoxModelHighlighter(this._highlighterEnv,
  111. this._inspector);
  112. this._highlighter.on("ready", this._highlighterReady);
  113. this._highlighter.on("hide", this._highlighterHidden);
  114. } else {
  115. this._highlighter = new SimpleOutlineHighlighter(this._highlighterEnv);
  116. }
  117. },
  118. _destroyHighlighter: function () {
  119. if (this._highlighter) {
  120. if (!this._isPreviousWindowXUL) {
  121. this._highlighter.off("ready", this._highlighterReady);
  122. this._highlighter.off("hide", this._highlighterHidden);
  123. }
  124. this._highlighter.destroy();
  125. this._highlighter = null;
  126. }
  127. },
  128. _onNavigate: function ({isTopLevel}) {
  129. // Skip navigation events for non top-level windows, or if the document
  130. // doesn't exist anymore.
  131. if (!isTopLevel || !this._tabActor.window.document.documentElement) {
  132. return;
  133. }
  134. // Only rebuild the highlighter if the window type changed.
  135. if (isXUL(this._tabActor.window) !== this._isPreviousWindowXUL) {
  136. this._destroyHighlighter();
  137. this._createHighlighter();
  138. }
  139. },
  140. destroy: function () {
  141. protocol.Actor.prototype.destroy.call(this);
  142. this.hideBoxModel();
  143. this._destroyHighlighter();
  144. events.off(this._tabActor, "navigate", this._onNavigate);
  145. this._highlighterEnv.destroy();
  146. this._highlighterEnv = null;
  147. this._autohide = null;
  148. this._inspector = null;
  149. this._walker = null;
  150. this._tabActor = null;
  151. },
  152. /**
  153. * Display the box model highlighting on a given NodeActor.
  154. * There is only one instance of the box model highlighter, so calling this
  155. * method several times won't display several highlighters, it will just move
  156. * the highlighter instance to these nodes.
  157. *
  158. * @param NodeActor The node to be highlighted
  159. * @param Options See the request part for existing options. Note that not
  160. * all options may be supported by all types of highlighters.
  161. */
  162. showBoxModel: function (node, options = {}) {
  163. if (!node || !this._highlighter.show(node.rawNode, options)) {
  164. this._highlighter.hide();
  165. }
  166. },
  167. /**
  168. * Hide the box model highlighting if it was shown before
  169. */
  170. hideBoxModel: function () {
  171. if (this._highlighter) {
  172. this._highlighter.hide();
  173. }
  174. },
  175. /**
  176. * Returns `true` if the event was dispatched from a window included in
  177. * the current highlighter environment; or if the highlighter environment has
  178. * chrome privileges
  179. *
  180. * The method is specifically useful on B2G, where we do not want that events
  181. * from app or main process are processed if we're inspecting the content.
  182. *
  183. * @param {Event} event
  184. * The event to allow
  185. * @return {Boolean}
  186. */
  187. _isEventAllowed: function ({view}) {
  188. let { window } = this._highlighterEnv;
  189. return window instanceof Ci.nsIDOMChromeWindow ||
  190. isWindowIncluded(window, view);
  191. },
  192. /**
  193. * Pick a node on click, and highlight hovered nodes in the process.
  194. *
  195. * This method doesn't respond anything interesting, however, it starts
  196. * mousemove, and click listeners on the content document to fire
  197. * events and let connected clients know when nodes are hovered over or
  198. * clicked.
  199. *
  200. * Once a node is picked, events will cease, and listeners will be removed.
  201. */
  202. _isPicking: false,
  203. _hoveredNode: null,
  204. _currentNode: null,
  205. pick: function () {
  206. if (this._isPicking) {
  207. return null;
  208. }
  209. this._isPicking = true;
  210. this._preventContentEvent = event => {
  211. event.stopPropagation();
  212. event.preventDefault();
  213. };
  214. this._onPick = event => {
  215. this._preventContentEvent(event);
  216. if (!this._isEventAllowed(event)) {
  217. return;
  218. }
  219. // If shift is pressed, this is only a preview click, send the event to
  220. // the client, but don't stop picking.
  221. if (event.shiftKey) {
  222. events.emit(this._walker, "picker-node-previewed", this._findAndAttachElement(event));
  223. return;
  224. }
  225. this._stopPickerListeners();
  226. this._isPicking = false;
  227. if (this._autohide) {
  228. this._tabActor.window.setTimeout(() => {
  229. this._highlighter.hide();
  230. }, HIGHLIGHTER_PICKED_TIMER);
  231. }
  232. if (!this._currentNode) {
  233. this._currentNode = this._findAndAttachElement(event);
  234. }
  235. events.emit(this._walker, "picker-node-picked", this._currentNode);
  236. };
  237. this._onHovered = event => {
  238. this._preventContentEvent(event);
  239. if (!this._isEventAllowed(event)) {
  240. return;
  241. }
  242. this._currentNode = this._findAndAttachElement(event);
  243. if (this._hoveredNode !== this._currentNode.node) {
  244. this._highlighter.show(this._currentNode.node.rawNode);
  245. events.emit(this._walker, "picker-node-hovered", this._currentNode);
  246. this._hoveredNode = this._currentNode.node;
  247. }
  248. };
  249. this._onKey = event => {
  250. if (!this._currentNode || !this._isPicking) {
  251. return;
  252. }
  253. this._preventContentEvent(event);
  254. if (!this._isEventAllowed(event)) {
  255. return;
  256. }
  257. let currentNode = this._currentNode.node.rawNode;
  258. /**
  259. * KEY: Action/scope
  260. * LEFT_KEY: wider or parent
  261. * RIGHT_KEY: narrower or child
  262. * ENTER/CARRIAGE_RETURN: Picks currentNode
  263. * ESC/CTRL+SHIFT+C: Cancels picker, picks currentNode
  264. */
  265. switch (event.keyCode) {
  266. // Wider.
  267. case Ci.nsIDOMKeyEvent.DOM_VK_LEFT:
  268. if (!currentNode.parentElement) {
  269. return;
  270. }
  271. currentNode = currentNode.parentElement;
  272. break;
  273. // Narrower.
  274. case Ci.nsIDOMKeyEvent.DOM_VK_RIGHT:
  275. if (!currentNode.children.length) {
  276. return;
  277. }
  278. // Set firstElementChild by default
  279. let child = currentNode.firstElementChild;
  280. // If currentNode is parent of hoveredNode, then
  281. // previously selected childNode is set
  282. let hoveredNode = this._hoveredNode.rawNode;
  283. for (let sibling of currentNode.children) {
  284. if (sibling.contains(hoveredNode) || sibling === hoveredNode) {
  285. child = sibling;
  286. }
  287. }
  288. currentNode = child;
  289. break;
  290. // Select the element.
  291. case Ci.nsIDOMKeyEvent.DOM_VK_RETURN:
  292. this._onPick(event);
  293. return;
  294. // Cancel pick mode.
  295. case Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE:
  296. this.cancelPick();
  297. events.emit(this._walker, "picker-node-canceled");
  298. return;
  299. case Ci.nsIDOMKeyEvent.DOM_VK_C:
  300. if ((IS_OSX && event.metaKey && event.altKey) ||
  301. (!IS_OSX && event.ctrlKey && event.shiftKey)) {
  302. this.cancelPick();
  303. events.emit(this._walker, "picker-node-canceled");
  304. return;
  305. }
  306. default: return;
  307. }
  308. // Store currently attached element
  309. this._currentNode = this._walker.attachElement(currentNode);
  310. this._highlighter.show(this._currentNode.node.rawNode);
  311. events.emit(this._walker, "picker-node-hovered", this._currentNode);
  312. };
  313. this._startPickerListeners();
  314. return null;
  315. },
  316. /**
  317. * This pick method also focuses the highlighter's target window.
  318. */
  319. pickAndFocus: function() {
  320. // Go ahead and pass on the results to help future-proof this method.
  321. let pickResults = this.pick();
  322. this._highlighterEnv.window.focus();
  323. return pickResults;
  324. },
  325. _findAndAttachElement: function (event) {
  326. // originalTarget allows access to the "real" element before any retargeting
  327. // is applied, such as in the case of XBL anonymous elements. See also
  328. // https://developer.mozilla.org/docs/XBL/XBL_1.0_Reference/Anonymous_Content#Event_Flow_and_Targeting
  329. let node = event.originalTarget || event.target;
  330. return this._walker.attachElement(node);
  331. },
  332. _startPickerListeners: function () {
  333. let target = this._highlighterEnv.pageListenerTarget;
  334. target.addEventListener("mousemove", this._onHovered, true);
  335. target.addEventListener("click", this._onPick, true);
  336. target.addEventListener("mousedown", this._preventContentEvent, true);
  337. target.addEventListener("mouseup", this._preventContentEvent, true);
  338. target.addEventListener("dblclick", this._preventContentEvent, true);
  339. target.addEventListener("keydown", this._onKey, true);
  340. target.addEventListener("keyup", this._preventContentEvent, true);
  341. },
  342. _stopPickerListeners: function () {
  343. let target = this._highlighterEnv.pageListenerTarget;
  344. target.removeEventListener("mousemove", this._onHovered, true);
  345. target.removeEventListener("click", this._onPick, true);
  346. target.removeEventListener("mousedown", this._preventContentEvent, true);
  347. target.removeEventListener("mouseup", this._preventContentEvent, true);
  348. target.removeEventListener("dblclick", this._preventContentEvent, true);
  349. target.removeEventListener("keydown", this._onKey, true);
  350. target.removeEventListener("keyup", this._preventContentEvent, true);
  351. },
  352. _highlighterReady: function () {
  353. events.emit(this._inspector.walker, "highlighter-ready");
  354. },
  355. _highlighterHidden: function () {
  356. events.emit(this._inspector.walker, "highlighter-hide");
  357. },
  358. cancelPick: function () {
  359. if (this._isPicking) {
  360. this._highlighter.hide();
  361. this._stopPickerListeners();
  362. this._isPicking = false;
  363. this._hoveredNode = null;
  364. }
  365. }
  366. });
  367. /**
  368. * A generic highlighter actor class that instantiate a highlighter given its
  369. * type name and allows to show/hide it.
  370. */
  371. var CustomHighlighterActor = exports.CustomHighlighterActor = protocol.ActorClassWithSpec(customHighlighterSpec, {
  372. /**
  373. * Create a highlighter instance given its typename
  374. * The typename must be one of HIGHLIGHTER_CLASSES and the class must
  375. * implement constructor(tabActor), show(node), hide(), destroy()
  376. */
  377. initialize: function (inspector, typeName) {
  378. protocol.Actor.prototype.initialize.call(this, null);
  379. this._inspector = inspector;
  380. let constructor = highlighterTypes.get(typeName);
  381. if (!constructor) {
  382. let list = [...highlighterTypes.keys()];
  383. throw new Error(`${typeName} isn't a valid highlighter class (${list})`);
  384. }
  385. // The assumption is that all custom highlighters need the canvasframe
  386. // container to append their elements, so if this is a XUL window, bail out.
  387. if (!isXUL(this._inspector.tabActor.window)) {
  388. this._highlighterEnv = new HighlighterEnvironment();
  389. this._highlighterEnv.initFromTabActor(inspector.tabActor);
  390. this._highlighter = new constructor(this._highlighterEnv);
  391. } else {
  392. throw new Error("Custom " + typeName +
  393. "highlighter cannot be created in a XUL window");
  394. }
  395. },
  396. get conn() {
  397. return this._inspector && this._inspector.conn;
  398. },
  399. destroy: function () {
  400. protocol.Actor.prototype.destroy.call(this);
  401. this.finalize();
  402. this._inspector = null;
  403. },
  404. release: function () {},
  405. /**
  406. * Show the highlighter.
  407. * This calls through to the highlighter instance's |show(node, options)|
  408. * method.
  409. *
  410. * Most custom highlighters are made to highlight DOM nodes, hence the first
  411. * NodeActor argument (NodeActor as in
  412. * devtools/server/actor/inspector).
  413. * Note however that some highlighters use this argument merely as a context
  414. * node: the RectHighlighter for instance uses it to calculate the absolute
  415. * position of the provided rect. The SelectHighlighter uses it as a base node
  416. * to run the provided CSS selector on.
  417. *
  418. * @param {NodeActor} The node to be highlighted
  419. * @param {Object} Options for the custom highlighter
  420. * @return {Boolean} True, if the highlighter has been successfully shown
  421. * (FF41+)
  422. */
  423. show: function (node, options) {
  424. if (!node || !this._highlighter) {
  425. return false;
  426. }
  427. return this._highlighter.show(node.rawNode, options);
  428. },
  429. /**
  430. * Hide the highlighter if it was shown before
  431. */
  432. hide: function () {
  433. if (this._highlighter) {
  434. this._highlighter.hide();
  435. }
  436. },
  437. /**
  438. * Kill this actor. This method is called automatically just before the actor
  439. * is destroyed.
  440. */
  441. finalize: function () {
  442. if (this._highlighter) {
  443. this._highlighter.destroy();
  444. this._highlighter = null;
  445. }
  446. if (this._highlighterEnv) {
  447. this._highlighterEnv.destroy();
  448. this._highlighterEnv = null;
  449. }
  450. }
  451. });
  452. /**
  453. * The HighlighterEnvironment is an object that holds all the required data for
  454. * highlighters to work: the window, docShell, event listener target, ...
  455. * It also emits "will-navigate" and "navigate" events, similarly to the
  456. * TabActor.
  457. *
  458. * It can be initialized either from a TabActor (which is the most frequent way
  459. * of using it, since highlighters are usually initialized by the
  460. * HighlighterActor or CustomHighlighterActor, which have a tabActor reference).
  461. * It can also be initialized just with a window object (which is useful for
  462. * when a highlighter is used outside of the debugger server context, for
  463. * instance from a gcli command).
  464. */
  465. function HighlighterEnvironment() {
  466. this.relayTabActorNavigate = this.relayTabActorNavigate.bind(this);
  467. this.relayTabActorWillNavigate = this.relayTabActorWillNavigate.bind(this);
  468. EventEmitter.decorate(this);
  469. }
  470. exports.HighlighterEnvironment = HighlighterEnvironment;
  471. HighlighterEnvironment.prototype = {
  472. initFromTabActor: function (tabActor) {
  473. this._tabActor = tabActor;
  474. events.on(this._tabActor, "navigate", this.relayTabActorNavigate);
  475. events.on(this._tabActor, "will-navigate", this.relayTabActorWillNavigate);
  476. },
  477. initFromWindow: function (win) {
  478. this._win = win;
  479. // We need a progress listener to know when the window will navigate/has
  480. // navigated.
  481. let self = this;
  482. this.listener = {
  483. QueryInterface: XPCOMUtils.generateQI([
  484. Ci.nsIWebProgressListener,
  485. Ci.nsISupportsWeakReference,
  486. Ci.nsISupports
  487. ]),
  488. onStateChange: function (progress, request, flag) {
  489. let isStart = flag & Ci.nsIWebProgressListener.STATE_START;
  490. let isStop = flag & Ci.nsIWebProgressListener.STATE_STOP;
  491. let isWindow = flag & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
  492. let isDocument = flag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
  493. if (progress.DOMWindow !== win) {
  494. return;
  495. }
  496. if (isDocument && isStart) {
  497. // One of the earliest events that tells us a new URI is being loaded
  498. // in this window.
  499. self.emit("will-navigate", {
  500. window: win,
  501. isTopLevel: true
  502. });
  503. }
  504. if (isWindow && isStop) {
  505. self.emit("navigate", {
  506. window: win,
  507. isTopLevel: true
  508. });
  509. }
  510. }
  511. };
  512. this.webProgress.addProgressListener(this.listener,
  513. Ci.nsIWebProgress.NOTIFY_STATUS |
  514. Ci.nsIWebProgress.NOTIFY_STATE_WINDOW |
  515. Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
  516. },
  517. get isInitialized() {
  518. return this._win || this._tabActor;
  519. },
  520. get isXUL() {
  521. return isXUL(this.window);
  522. },
  523. get window() {
  524. if (!this.isInitialized) {
  525. throw new Error("Initialize HighlighterEnvironment with a tabActor " +
  526. "or window first");
  527. }
  528. return this._tabActor ? this._tabActor.window : this._win;
  529. },
  530. get document() {
  531. return this.window.document;
  532. },
  533. get docShell() {
  534. return this.window.QueryInterface(Ci.nsIInterfaceRequestor)
  535. .getInterface(Ci.nsIWebNavigation)
  536. .QueryInterface(Ci.nsIDocShell);
  537. },
  538. get webProgress() {
  539. return this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
  540. .getInterface(Ci.nsIWebProgress);
  541. },
  542. /**
  543. * Get the right target for listening to events on the page.
  544. * - If the environment was initialized from a TabActor *and* if we're in the
  545. * Browser Toolbox (to inspect firefox desktop): the tabActor is the
  546. * RootActor, in which case, the window property can be used to listen to
  547. * events.
  548. * - With firefox desktop, that tabActor is a BrowserTabActor, and with B2G,
  549. * a ContentActor (which overrides BrowserTabActor). In both cases we use
  550. * the chromeEventHandler which gives us a target we can use to listen to
  551. * events, even from nested iframes.
  552. * - If the environment was initialized from a window, we also use the
  553. * chromeEventHandler.
  554. */
  555. get pageListenerTarget() {
  556. if (this._tabActor && this._tabActor.isRootActor) {
  557. return this.window;
  558. }
  559. return this.docShell.chromeEventHandler;
  560. },
  561. relayTabActorNavigate: function (data) {
  562. this.emit("navigate", data);
  563. },
  564. relayTabActorWillNavigate: function (data) {
  565. this.emit("will-navigate", data);
  566. },
  567. destroy: function () {
  568. if (this._tabActor) {
  569. events.off(this._tabActor, "navigate", this.relayTabActorNavigate);
  570. events.off(this._tabActor, "will-navigate", this.relayTabActorWillNavigate);
  571. }
  572. // In case the environment was initialized from a window, we need to remove
  573. // the progress listener.
  574. if (this._win) {
  575. try {
  576. this.webProgress.removeProgressListener(this.listener);
  577. } catch (e) {
  578. // Which may fail in case the window was already destroyed.
  579. }
  580. }
  581. this._tabActor = null;
  582. this._win = null;
  583. }
  584. };
  585. const { BoxModelHighlighter } = require("./highlighters/box-model");
  586. register(BoxModelHighlighter);
  587. exports.BoxModelHighlighter = BoxModelHighlighter;
  588. const { CssGridHighlighter } = require("./highlighters/css-grid");
  589. register(CssGridHighlighter);
  590. exports.CssGridHighlighter = CssGridHighlighter;
  591. const { CssTransformHighlighter } = require("./highlighters/css-transform");
  592. register(CssTransformHighlighter);
  593. exports.CssTransformHighlighter = CssTransformHighlighter;
  594. const { SelectorHighlighter } = require("./highlighters/selector");
  595. register(SelectorHighlighter);
  596. exports.SelectorHighlighter = SelectorHighlighter;
  597. const { RectHighlighter } = require("./highlighters/rect");
  598. register(RectHighlighter);
  599. exports.RectHighlighter = RectHighlighter;
  600. const { GeometryEditorHighlighter } = require("./highlighters/geometry-editor");
  601. register(GeometryEditorHighlighter);
  602. exports.GeometryEditorHighlighter = GeometryEditorHighlighter;
  603. const { RulersHighlighter } = require("./highlighters/rulers");
  604. register(RulersHighlighter);
  605. exports.RulersHighlighter = RulersHighlighter;
  606. const { MeasuringToolHighlighter } = require("./highlighters/measuring-tool");
  607. register(MeasuringToolHighlighter);
  608. exports.MeasuringToolHighlighter = MeasuringToolHighlighter;
  609. const { EyeDropper } = require("./highlighters/eye-dropper");
  610. register(EyeDropper);
  611. exports.EyeDropper = EyeDropper;