inspector.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008
  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. require("devtools/shared/fronts/styles");
  6. require("devtools/shared/fronts/highlighters");
  7. require("devtools/shared/fronts/layout");
  8. const { SimpleStringFront } = require("devtools/shared/fronts/string");
  9. const {
  10. Front,
  11. FrontClassWithSpec,
  12. custom,
  13. preEvent,
  14. types
  15. } = require("devtools/shared/protocol.js");
  16. const {
  17. inspectorSpec,
  18. nodeSpec,
  19. nodeListSpec,
  20. walkerSpec
  21. } = require("devtools/shared/specs/inspector");
  22. const promise = require("promise");
  23. const defer = require("devtools/shared/defer");
  24. const { Task } = require("devtools/shared/task");
  25. const { Class } = require("sdk/core/heritage");
  26. const events = require("sdk/event/core");
  27. const object = require("sdk/util/object");
  28. const nodeConstants = require("devtools/shared/dom-node-constants.js");
  29. loader.lazyRequireGetter(this, "CommandUtils",
  30. "devtools/client/shared/developer-toolbar", true);
  31. const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
  32. /**
  33. * Convenience API for building a list of attribute modifications
  34. * for the `modifyAttributes` request.
  35. */
  36. const AttributeModificationList = Class({
  37. initialize: function (node) {
  38. this.node = node;
  39. this.modifications = [];
  40. },
  41. apply: function () {
  42. let ret = this.node.modifyAttributes(this.modifications);
  43. return ret;
  44. },
  45. destroy: function () {
  46. this.node = null;
  47. this.modification = null;
  48. },
  49. setAttributeNS: function (ns, name, value) {
  50. this.modifications.push({
  51. attributeNamespace: ns,
  52. attributeName: name,
  53. newValue: value
  54. });
  55. },
  56. setAttribute: function (name, value) {
  57. this.setAttributeNS(undefined, name, value);
  58. },
  59. removeAttributeNS: function (ns, name) {
  60. this.setAttributeNS(ns, name, undefined);
  61. },
  62. removeAttribute: function (name) {
  63. this.setAttributeNS(undefined, name, undefined);
  64. }
  65. });
  66. /**
  67. * Client side of the node actor.
  68. *
  69. * Node fronts are strored in a tree that mirrors the DOM tree on the
  70. * server, but with a few key differences:
  71. * - Not all children will be necessary loaded for each node.
  72. * - The order of children isn't guaranteed to be the same as the DOM.
  73. * Children are stored in a doubly-linked list, to make addition/removal
  74. * and traversal quick.
  75. *
  76. * Due to the order/incompleteness of the child list, it is safe to use
  77. * the parent node from clients, but the `children` request should be used
  78. * to traverse children.
  79. */
  80. const NodeFront = FrontClassWithSpec(nodeSpec, {
  81. initialize: function (conn, form, detail, ctx) {
  82. // The parent node
  83. this._parent = null;
  84. // The first child of this node.
  85. this._child = null;
  86. // The next sibling of this node.
  87. this._next = null;
  88. // The previous sibling of this node.
  89. this._prev = null;
  90. Front.prototype.initialize.call(this, conn, form, detail, ctx);
  91. },
  92. /**
  93. * Destroy a node front. The node must have been removed from the
  94. * ownership tree before this is called, unless the whole walker front
  95. * is being destroyed.
  96. */
  97. destroy: function () {
  98. Front.prototype.destroy.call(this);
  99. },
  100. // Update the object given a form representation off the wire.
  101. form: function (form, detail, ctx) {
  102. if (detail === "actorid") {
  103. this.actorID = form;
  104. return;
  105. }
  106. // backward-compatibility: shortValue indicates we are connected to old server
  107. if (form.shortValue) {
  108. // If the value is not complete, set nodeValue to null, it will be fetched
  109. // when calling getNodeValue()
  110. form.nodeValue = form.incompleteValue ? null : form.shortValue;
  111. }
  112. // Shallow copy of the form. We could just store a reference, but
  113. // eventually we'll want to update some of the data.
  114. this._form = object.merge(form);
  115. this._form.attrs = this._form.attrs ? this._form.attrs.slice() : [];
  116. if (form.parent) {
  117. // Get the owner actor for this actor (the walker), and find the
  118. // parent node of this actor from it, creating a standin node if
  119. // necessary.
  120. let parentNodeFront = ctx.marshallPool().ensureParentFront(form.parent);
  121. this.reparent(parentNodeFront);
  122. }
  123. if (form.inlineTextChild) {
  124. this.inlineTextChild =
  125. types.getType("domnode").read(form.inlineTextChild, ctx);
  126. } else {
  127. this.inlineTextChild = undefined;
  128. }
  129. },
  130. /**
  131. * Returns the parent NodeFront for this NodeFront.
  132. */
  133. parentNode: function () {
  134. return this._parent;
  135. },
  136. /**
  137. * Process a mutation entry as returned from the walker's `getMutations`
  138. * request. Only tries to handle changes of the node's contents
  139. * themselves (character data and attribute changes), the walker itself
  140. * will keep the ownership tree up to date.
  141. */
  142. updateMutation: function (change) {
  143. if (change.type === "attributes") {
  144. // We'll need to lazily reparse the attributes after this change.
  145. this._attrMap = undefined;
  146. // Update any already-existing attributes.
  147. let found = false;
  148. for (let i = 0; i < this.attributes.length; i++) {
  149. let attr = this.attributes[i];
  150. if (attr.name == change.attributeName &&
  151. attr.namespace == change.attributeNamespace) {
  152. if (change.newValue !== null) {
  153. attr.value = change.newValue;
  154. } else {
  155. this.attributes.splice(i, 1);
  156. }
  157. found = true;
  158. break;
  159. }
  160. }
  161. // This is a new attribute. The null check is because of Bug 1192270,
  162. // in the case of a newly added then removed attribute
  163. if (!found && change.newValue !== null) {
  164. this.attributes.push({
  165. name: change.attributeName,
  166. namespace: change.attributeNamespace,
  167. value: change.newValue
  168. });
  169. }
  170. } else if (change.type === "characterData") {
  171. this._form.nodeValue = change.newValue;
  172. } else if (change.type === "pseudoClassLock") {
  173. this._form.pseudoClassLocks = change.pseudoClassLocks;
  174. } else if (change.type === "events") {
  175. this._form.hasEventListeners = change.hasEventListeners;
  176. }
  177. },
  178. // Some accessors to make NodeFront feel more like an nsIDOMNode
  179. get id() {
  180. return this.getAttribute("id");
  181. },
  182. get nodeType() {
  183. return this._form.nodeType;
  184. },
  185. get namespaceURI() {
  186. return this._form.namespaceURI;
  187. },
  188. get nodeName() {
  189. return this._form.nodeName;
  190. },
  191. get displayName() {
  192. let {displayName, nodeName} = this._form;
  193. // Keep `nodeName.toLowerCase()` for backward compatibility
  194. return displayName || nodeName.toLowerCase();
  195. },
  196. get doctypeString() {
  197. return "<!DOCTYPE " + this._form.name +
  198. (this._form.publicId ? " PUBLIC \"" + this._form.publicId + "\"" : "") +
  199. (this._form.systemId ? " \"" + this._form.systemId + "\"" : "") +
  200. ">";
  201. },
  202. get baseURI() {
  203. return this._form.baseURI;
  204. },
  205. get className() {
  206. return this.getAttribute("class") || "";
  207. },
  208. get hasChildren() {
  209. return this._form.numChildren > 0;
  210. },
  211. get numChildren() {
  212. return this._form.numChildren;
  213. },
  214. get hasEventListeners() {
  215. return this._form.hasEventListeners;
  216. },
  217. get isBeforePseudoElement() {
  218. return this._form.isBeforePseudoElement;
  219. },
  220. get isAfterPseudoElement() {
  221. return this._form.isAfterPseudoElement;
  222. },
  223. get isPseudoElement() {
  224. return this.isBeforePseudoElement || this.isAfterPseudoElement;
  225. },
  226. get isAnonymous() {
  227. return this._form.isAnonymous;
  228. },
  229. get isInHTMLDocument() {
  230. return this._form.isInHTMLDocument;
  231. },
  232. get tagName() {
  233. return this.nodeType === nodeConstants.ELEMENT_NODE ? this.nodeName : null;
  234. },
  235. get isDocumentElement() {
  236. return !!this._form.isDocumentElement;
  237. },
  238. // doctype properties
  239. get name() {
  240. return this._form.name;
  241. },
  242. get publicId() {
  243. return this._form.publicId;
  244. },
  245. get systemId() {
  246. return this._form.systemId;
  247. },
  248. getAttribute: function (name) {
  249. let attr = this._getAttribute(name);
  250. return attr ? attr.value : null;
  251. },
  252. hasAttribute: function (name) {
  253. this._cacheAttributes();
  254. return (name in this._attrMap);
  255. },
  256. get hidden() {
  257. let cls = this.getAttribute("class");
  258. return cls && cls.indexOf(HIDDEN_CLASS) > -1;
  259. },
  260. get attributes() {
  261. return this._form.attrs;
  262. },
  263. get pseudoClassLocks() {
  264. return this._form.pseudoClassLocks || [];
  265. },
  266. hasPseudoClassLock: function (pseudo) {
  267. return this.pseudoClassLocks.some(locked => locked === pseudo);
  268. },
  269. get isDisplayed() {
  270. // The NodeActor's form contains the isDisplayed information as a boolean
  271. // starting from FF32. Before that, the property is missing
  272. return "isDisplayed" in this._form ? this._form.isDisplayed : true;
  273. },
  274. get isTreeDisplayed() {
  275. let parent = this;
  276. while (parent) {
  277. if (!parent.isDisplayed) {
  278. return false;
  279. }
  280. parent = parent.parentNode();
  281. }
  282. return true;
  283. },
  284. getNodeValue: custom(function () {
  285. // backward-compatibility: if nodevalue is null and shortValue is defined, the actual
  286. // value of the node needs to be fetched on the server.
  287. if (this._form.nodeValue === null && this._form.shortValue) {
  288. return this._getNodeValue();
  289. }
  290. let str = this._form.nodeValue || "";
  291. return promise.resolve(new SimpleStringFront(str));
  292. }, {
  293. impl: "_getNodeValue"
  294. }),
  295. // Accessors for custom form properties.
  296. getFormProperty: function (name) {
  297. return this._form.props ? this._form.props[name] : null;
  298. },
  299. hasFormProperty: function (name) {
  300. return this._form.props ? (name in this._form.props) : null;
  301. },
  302. get formProperties() {
  303. return this._form.props;
  304. },
  305. /**
  306. * Return a new AttributeModificationList for this node.
  307. */
  308. startModifyingAttributes: function () {
  309. return AttributeModificationList(this);
  310. },
  311. _cacheAttributes: function () {
  312. if (typeof this._attrMap != "undefined") {
  313. return;
  314. }
  315. this._attrMap = {};
  316. for (let attr of this.attributes) {
  317. this._attrMap[attr.name] = attr;
  318. }
  319. },
  320. _getAttribute: function (name) {
  321. this._cacheAttributes();
  322. return this._attrMap[name] || undefined;
  323. },
  324. /**
  325. * Set this node's parent. Note that the children saved in
  326. * this tree are unordered and incomplete, so shouldn't be used
  327. * instead of a `children` request.
  328. */
  329. reparent: function (parent) {
  330. if (this._parent === parent) {
  331. return;
  332. }
  333. if (this._parent && this._parent._child === this) {
  334. this._parent._child = this._next;
  335. }
  336. if (this._prev) {
  337. this._prev._next = this._next;
  338. }
  339. if (this._next) {
  340. this._next._prev = this._prev;
  341. }
  342. this._next = null;
  343. this._prev = null;
  344. this._parent = parent;
  345. if (!parent) {
  346. // Subtree is disconnected, we're done
  347. return;
  348. }
  349. this._next = parent._child;
  350. if (this._next) {
  351. this._next._prev = this;
  352. }
  353. parent._child = this;
  354. },
  355. /**
  356. * Return all the known children of this node.
  357. */
  358. treeChildren: function () {
  359. let ret = [];
  360. for (let child = this._child; child != null; child = child._next) {
  361. ret.push(child);
  362. }
  363. return ret;
  364. },
  365. /**
  366. * Do we use a local target?
  367. * Useful to know if a rawNode is available or not.
  368. *
  369. * This will, one day, be removed. External code should
  370. * not need to know if the target is remote or not.
  371. */
  372. isLocalToBeDeprecated: function () {
  373. return !!this.conn._transport._serverConnection;
  374. },
  375. /**
  376. * Get an nsIDOMNode for the given node front. This only works locally,
  377. * and is only intended as a stopgap during the transition to the remote
  378. * protocol. If you depend on this you're likely to break soon.
  379. */
  380. rawNode: function (rawNode) {
  381. if (!this.isLocalToBeDeprecated()) {
  382. console.warn("Tried to use rawNode on a remote connection.");
  383. return null;
  384. }
  385. const { DebuggerServer } = require("devtools/server/main");
  386. let actor = DebuggerServer._searchAllConnectionsForActor(this.actorID);
  387. if (!actor) {
  388. // Can happen if we try to get the raw node for an already-expired
  389. // actor.
  390. return null;
  391. }
  392. return actor.rawNode;
  393. }
  394. });
  395. exports.NodeFront = NodeFront;
  396. /**
  397. * Client side of a node list as returned by querySelectorAll()
  398. */
  399. const NodeListFront = FrontClassWithSpec(nodeListSpec, {
  400. initialize: function (client, form) {
  401. Front.prototype.initialize.call(this, client, form);
  402. },
  403. destroy: function () {
  404. Front.prototype.destroy.call(this);
  405. },
  406. marshallPool: function () {
  407. return this.parent();
  408. },
  409. // Update the object given a form representation off the wire.
  410. form: function (json) {
  411. this.length = json.length;
  412. },
  413. item: custom(function (index) {
  414. return this._item(index).then(response => {
  415. return response.node;
  416. });
  417. }, {
  418. impl: "_item"
  419. }),
  420. items: custom(function (start, end) {
  421. return this._items(start, end).then(response => {
  422. return response.nodes;
  423. });
  424. }, {
  425. impl: "_items"
  426. })
  427. });
  428. exports.NodeListFront = NodeListFront;
  429. /**
  430. * Client side of the DOM walker.
  431. */
  432. const WalkerFront = FrontClassWithSpec(walkerSpec, {
  433. // Set to true if cleanup should be requested after every mutation list.
  434. autoCleanup: true,
  435. /**
  436. * This is kept for backward-compatibility reasons with older remote target.
  437. * Targets previous to bug 916443
  438. */
  439. pick: custom(function () {
  440. return this._pick().then(response => {
  441. return response.node;
  442. });
  443. }, {impl: "_pick"}),
  444. initialize: function (client, form) {
  445. this._createRootNodePromise();
  446. Front.prototype.initialize.call(this, client, form);
  447. this._orphaned = new Set();
  448. this._retainedOrphans = new Set();
  449. },
  450. destroy: function () {
  451. Front.prototype.destroy.call(this);
  452. },
  453. // Update the object given a form representation off the wire.
  454. form: function (json) {
  455. this.actorID = json.actor;
  456. this.rootNode = types.getType("domnode").read(json.root, this);
  457. this._rootNodeDeferred.resolve(this.rootNode);
  458. // FF42+ the actor starts exposing traits
  459. this.traits = json.traits || {};
  460. },
  461. /**
  462. * Clients can use walker.rootNode to get the current root node of the
  463. * walker, but during a reload the root node might be null. This
  464. * method returns a promise that will resolve to the root node when it is
  465. * set.
  466. */
  467. getRootNode: function () {
  468. return this._rootNodeDeferred.promise;
  469. },
  470. /**
  471. * Create the root node promise, triggering the "new-root" notification
  472. * on resolution.
  473. */
  474. _createRootNodePromise: function () {
  475. this._rootNodeDeferred = defer();
  476. this._rootNodeDeferred.promise.then(() => {
  477. events.emit(this, "new-root");
  478. });
  479. },
  480. /**
  481. * When reading an actor form off the wire, we want to hook it up to its
  482. * parent front. The protocol guarantees that the parent will be seen
  483. * by the client in either a previous or the current request.
  484. * So if we've already seen this parent return it, otherwise create
  485. * a bare-bones stand-in node. The stand-in node will be updated
  486. * with a real form by the end of the deserialization.
  487. */
  488. ensureParentFront: function (id) {
  489. let front = this.get(id);
  490. if (front) {
  491. return front;
  492. }
  493. return types.getType("domnode").read({ actor: id }, this, "standin");
  494. },
  495. /**
  496. * See the documentation for WalkerActor.prototype.retainNode for
  497. * information on retained nodes.
  498. *
  499. * From the client's perspective, `retainNode` can fail if the node in
  500. * question is removed from the ownership tree before the `retainNode`
  501. * request reaches the server. This can only happen if the client has
  502. * asked the server to release nodes but hasn't gotten a response
  503. * yet: Either a `releaseNode` request or a `getMutations` with `cleanup`
  504. * set is outstanding.
  505. *
  506. * If either of those requests is outstanding AND releases the retained
  507. * node, this request will fail with noSuchActor, but the ownership tree
  508. * will stay in a consistent state.
  509. *
  510. * Because the protocol guarantees that requests will be processed and
  511. * responses received in the order they were sent, we get the right
  512. * semantics by setting our local retained flag on the node only AFTER
  513. * a SUCCESSFUL retainNode call.
  514. */
  515. retainNode: custom(function (node) {
  516. return this._retainNode(node).then(() => {
  517. node.retained = true;
  518. });
  519. }, {
  520. impl: "_retainNode",
  521. }),
  522. unretainNode: custom(function (node) {
  523. return this._unretainNode(node).then(() => {
  524. node.retained = false;
  525. if (this._retainedOrphans.has(node)) {
  526. this._retainedOrphans.delete(node);
  527. this._releaseFront(node);
  528. }
  529. });
  530. }, {
  531. impl: "_unretainNode"
  532. }),
  533. releaseNode: custom(function (node, options = {}) {
  534. // NodeFront.destroy will destroy children in the ownership tree too,
  535. // mimicking what the server will do here.
  536. let actorID = node.actorID;
  537. this._releaseFront(node, !!options.force);
  538. return this._releaseNode({ actorID: actorID });
  539. }, {
  540. impl: "_releaseNode"
  541. }),
  542. findInspectingNode: custom(function () {
  543. return this._findInspectingNode().then(response => {
  544. return response.node;
  545. });
  546. }, {
  547. impl: "_findInspectingNode"
  548. }),
  549. querySelector: custom(function (queryNode, selector) {
  550. return this._querySelector(queryNode, selector).then(response => {
  551. return response.node;
  552. });
  553. }, {
  554. impl: "_querySelector"
  555. }),
  556. getNodeActorFromObjectActor: custom(function (objectActorID) {
  557. return this._getNodeActorFromObjectActor(objectActorID).then(response => {
  558. return response ? response.node : null;
  559. });
  560. }, {
  561. impl: "_getNodeActorFromObjectActor"
  562. }),
  563. getStyleSheetOwnerNode: custom(function (styleSheetActorID) {
  564. return this._getStyleSheetOwnerNode(styleSheetActorID).then(response => {
  565. return response ? response.node : null;
  566. });
  567. }, {
  568. impl: "_getStyleSheetOwnerNode"
  569. }),
  570. getNodeFromActor: custom(function (actorID, path) {
  571. return this._getNodeFromActor(actorID, path).then(response => {
  572. return response ? response.node : null;
  573. });
  574. }, {
  575. impl: "_getNodeFromActor"
  576. }),
  577. /*
  578. * Incrementally search the document for a given string.
  579. * For modern servers, results will be searched with using the WalkerActor
  580. * `search` function (includes tag names, attributes, and text contents).
  581. * Only 1 result is sent back, and calling the method again with the same
  582. * query will send the next result. When there are no more results to be sent
  583. * back, null is sent.
  584. * @param {String} query
  585. * @param {Object} options
  586. * - "reverse": search backwards
  587. * - "selectorOnly": treat input as a selector string (don't search text
  588. * tags, attributes, etc)
  589. */
  590. search: custom(Task.async(function* (query, options = { }) {
  591. let nodeList;
  592. let searchType;
  593. let searchData = this.searchData = this.searchData || { };
  594. let selectorOnly = !!options.selectorOnly;
  595. // Backwards compat. Use selector only search if the new
  596. // search functionality isn't implemented, or if the caller (tests)
  597. // want it.
  598. if (selectorOnly || !this.traits.textSearch) {
  599. searchType = "selector";
  600. if (this.traits.multiFrameQuerySelectorAll) {
  601. nodeList = yield this.multiFrameQuerySelectorAll(query);
  602. } else {
  603. nodeList = yield this.querySelectorAll(this.rootNode, query);
  604. }
  605. } else {
  606. searchType = "search";
  607. let result = yield this._search(query, options);
  608. nodeList = result.list;
  609. }
  610. // If this is a new search, start at the beginning.
  611. if (searchData.query !== query ||
  612. searchData.selectorOnly !== selectorOnly) {
  613. searchData.selectorOnly = selectorOnly;
  614. searchData.query = query;
  615. searchData.index = -1;
  616. }
  617. if (!nodeList.length) {
  618. return null;
  619. }
  620. // Move search result cursor and cycle if necessary.
  621. searchData.index = options.reverse ? searchData.index - 1 :
  622. searchData.index + 1;
  623. if (searchData.index >= nodeList.length) {
  624. searchData.index = 0;
  625. }
  626. if (searchData.index < 0) {
  627. searchData.index = nodeList.length - 1;
  628. }
  629. // Send back the single node, along with any relevant search data
  630. let node = yield nodeList.item(searchData.index);
  631. return {
  632. type: searchType,
  633. node: node,
  634. resultsLength: nodeList.length,
  635. resultsIndex: searchData.index,
  636. };
  637. }), {
  638. impl: "_search"
  639. }),
  640. _releaseFront: function (node, force) {
  641. if (node.retained && !force) {
  642. node.reparent(null);
  643. this._retainedOrphans.add(node);
  644. return;
  645. }
  646. if (node.retained) {
  647. // Forcing a removal.
  648. this._retainedOrphans.delete(node);
  649. }
  650. // Release any children
  651. for (let child of node.treeChildren()) {
  652. this._releaseFront(child, force);
  653. }
  654. // All children will have been removed from the node by this point.
  655. node.reparent(null);
  656. node.destroy();
  657. },
  658. /**
  659. * Get any unprocessed mutation records and process them.
  660. */
  661. getMutations: custom(function (options = {}) {
  662. return this._getMutations(options).then(mutations => {
  663. let emitMutations = [];
  664. for (let change of mutations) {
  665. // The target is only an actorID, get the associated front.
  666. let targetID;
  667. let targetFront;
  668. if (change.type === "newRoot") {
  669. // We may receive a new root without receiving any documentUnload
  670. // beforehand. Like when opening tools in middle of a document load.
  671. if (this.rootNode) {
  672. this._createRootNodePromise();
  673. }
  674. this.rootNode = types.getType("domnode").read(change.target, this);
  675. this._rootNodeDeferred.resolve(this.rootNode);
  676. targetID = this.rootNode.actorID;
  677. targetFront = this.rootNode;
  678. } else {
  679. targetID = change.target;
  680. targetFront = this.get(targetID);
  681. }
  682. if (!targetFront) {
  683. console.trace("Got a mutation for an unexpected actor: " + targetID +
  684. ", please file a bug on bugzilla.mozilla.org!");
  685. continue;
  686. }
  687. let emittedMutation = object.merge(change, { target: targetFront });
  688. if (change.type === "childList" ||
  689. change.type === "nativeAnonymousChildList") {
  690. // Update the ownership tree according to the mutation record.
  691. let addedFronts = [];
  692. let removedFronts = [];
  693. for (let removed of change.removed) {
  694. let removedFront = this.get(removed);
  695. if (!removedFront) {
  696. console.error("Got a removal of an actor we didn't know about: " +
  697. removed);
  698. continue;
  699. }
  700. // Remove from the ownership tree
  701. removedFront.reparent(null);
  702. // This node is orphaned unless we get it in the 'added' list
  703. // eventually.
  704. this._orphaned.add(removedFront);
  705. removedFronts.push(removedFront);
  706. }
  707. for (let added of change.added) {
  708. let addedFront = this.get(added);
  709. if (!addedFront) {
  710. console.error("Got an addition of an actor we didn't know " +
  711. "about: " + added);
  712. continue;
  713. }
  714. addedFront.reparent(targetFront);
  715. // The actor is reconnected to the ownership tree, unorphan
  716. // it.
  717. this._orphaned.delete(addedFront);
  718. addedFronts.push(addedFront);
  719. }
  720. // Before passing to users, replace the added and removed actor
  721. // ids with front in the mutation record.
  722. emittedMutation.added = addedFronts;
  723. emittedMutation.removed = removedFronts;
  724. // If this is coming from a DOM mutation, the actor's numChildren
  725. // was passed in. Otherwise, it is simulated from a frame load or
  726. // unload, so don't change the front's form.
  727. if ("numChildren" in change) {
  728. targetFront._form.numChildren = change.numChildren;
  729. }
  730. } else if (change.type === "frameLoad") {
  731. // Nothing we need to do here, except verify that we don't have any
  732. // document children, because we should have gotten a documentUnload
  733. // first.
  734. for (let child of targetFront.treeChildren()) {
  735. if (child.nodeType === nodeConstants.DOCUMENT_NODE) {
  736. console.trace("Got an unexpected frameLoad in the inspector, " +
  737. "please file a bug on bugzilla.mozilla.org!");
  738. }
  739. }
  740. } else if (change.type === "documentUnload") {
  741. if (targetFront === this.rootNode) {
  742. this._createRootNodePromise();
  743. }
  744. // We try to give fronts instead of actorIDs, but these fronts need
  745. // to be destroyed now.
  746. emittedMutation.target = targetFront.actorID;
  747. emittedMutation.targetParent = targetFront.parentNode();
  748. // Release the document node and all of its children, even retained.
  749. this._releaseFront(targetFront, true);
  750. } else if (change.type === "unretained") {
  751. // Retained orphans were force-released without the intervention of
  752. // client (probably a navigated frame).
  753. for (let released of change.nodes) {
  754. let releasedFront = this.get(released);
  755. this._retainedOrphans.delete(released);
  756. this._releaseFront(releasedFront, true);
  757. }
  758. } else {
  759. targetFront.updateMutation(change);
  760. }
  761. // Update the inlineTextChild property of the target for a selected list of
  762. // mutation types.
  763. if (change.type === "inlineTextChild" ||
  764. change.type === "childList" ||
  765. change.type === "nativeAnonymousChildList") {
  766. if (change.inlineTextChild) {
  767. targetFront.inlineTextChild =
  768. types.getType("domnode").read(change.inlineTextChild, this);
  769. } else {
  770. targetFront.inlineTextChild = undefined;
  771. }
  772. }
  773. emitMutations.push(emittedMutation);
  774. }
  775. if (options.cleanup) {
  776. for (let node of this._orphaned) {
  777. // This will move retained nodes to this._retainedOrphans.
  778. this._releaseFront(node);
  779. }
  780. this._orphaned = new Set();
  781. }
  782. events.emit(this, "mutations", emitMutations);
  783. });
  784. }, {
  785. impl: "_getMutations"
  786. }),
  787. /**
  788. * Handle the `new-mutations` notification by fetching the
  789. * available mutation records.
  790. */
  791. onMutations: preEvent("new-mutations", function () {
  792. // Fetch and process the mutations.
  793. this.getMutations({cleanup: this.autoCleanup}).catch(() => {});
  794. }),
  795. isLocal: function () {
  796. return !!this.conn._transport._serverConnection;
  797. },
  798. // XXX hack during transition to remote inspector: get a proper NodeFront
  799. // for a given local node. Only works locally.
  800. frontForRawNode: function (rawNode) {
  801. if (!this.isLocal()) {
  802. console.warn("Tried to use frontForRawNode on a remote connection.");
  803. return null;
  804. }
  805. const { DebuggerServer } = require("devtools/server/main");
  806. let walkerActor = DebuggerServer._searchAllConnectionsForActor(this.actorID);
  807. if (!walkerActor) {
  808. throw Error("Could not find client side for actor " + this.actorID);
  809. }
  810. let nodeActor = walkerActor._ref(rawNode);
  811. // Pass the node through a read/write pair to create the client side actor.
  812. let nodeType = types.getType("domnode");
  813. let returnNode = nodeType.read(
  814. nodeType.write(nodeActor, walkerActor), this);
  815. let top = returnNode;
  816. let extras = walkerActor.parents(nodeActor, {sameTypeRootTreeItem: true});
  817. for (let extraActor of extras) {
  818. top = nodeType.read(nodeType.write(extraActor, walkerActor), this);
  819. }
  820. if (top !== this.rootNode) {
  821. // Imported an already-orphaned node.
  822. this._orphaned.add(top);
  823. walkerActor._orphaned
  824. .add(DebuggerServer._searchAllConnectionsForActor(top.actorID));
  825. }
  826. return returnNode;
  827. },
  828. removeNode: custom(Task.async(function* (node) {
  829. let previousSibling = yield this.previousSibling(node);
  830. let nextSibling = yield this._removeNode(node);
  831. return {
  832. previousSibling: previousSibling,
  833. nextSibling: nextSibling,
  834. };
  835. }), {
  836. impl: "_removeNode"
  837. }),
  838. });
  839. exports.WalkerFront = WalkerFront;
  840. /**
  841. * Client side of the inspector actor, which is used to create
  842. * inspector-related actors, including the walker.
  843. */
  844. var InspectorFront = FrontClassWithSpec(inspectorSpec, {
  845. initialize: function (client, tabForm) {
  846. Front.prototype.initialize.call(this, client);
  847. this.actorID = tabForm.inspectorActor;
  848. // XXX: This is the first actor type in its hierarchy to use the protocol
  849. // library, so we're going to self-own on the client side for now.
  850. this.manage(this);
  851. },
  852. destroy: function () {
  853. delete this.walker;
  854. Front.prototype.destroy.call(this);
  855. },
  856. getWalker: custom(function (options = {}) {
  857. return this._getWalker(options).then(walker => {
  858. this.walker = walker;
  859. return walker;
  860. });
  861. }, {
  862. impl: "_getWalker"
  863. }),
  864. getPageStyle: custom(function () {
  865. return this._getPageStyle().then(pageStyle => {
  866. // We need a walker to understand node references from the
  867. // node style.
  868. if (this.walker) {
  869. return pageStyle;
  870. }
  871. return this.getWalker().then(() => {
  872. return pageStyle;
  873. });
  874. });
  875. }, {
  876. impl: "_getPageStyle"
  877. }),
  878. pickColorFromPage: custom(Task.async(function* (toolbox, options) {
  879. if (toolbox) {
  880. // If the eyedropper was already started using the gcli command, hide it so we don't
  881. // end up with 2 instances of the eyedropper on the page.
  882. let {target} = toolbox;
  883. let requisition = yield CommandUtils.createRequisition(target, {
  884. environment: CommandUtils.createEnvironment({target})
  885. });
  886. yield requisition.updateExec("eyedropper --hide");
  887. }
  888. yield this._pickColorFromPage(options);
  889. }), {
  890. impl: "_pickColorFromPage"
  891. })
  892. });
  893. exports.InspectorFront = InspectorFront;