root.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. "use strict";
  6. const { Cc, Ci, Cu } = require("chrome");
  7. const Services = require("Services");
  8. const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common");
  9. const { DebuggerServer } = require("devtools/server/main");
  10. loader.lazyGetter(this, "ppmm", () => {
  11. return Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIMessageBroadcaster);
  12. });
  13. /* Root actor for the remote debugging protocol. */
  14. /**
  15. * Create a remote debugging protocol root actor.
  16. *
  17. * @param aConnection
  18. * The DebuggerServerConnection whose root actor we are constructing.
  19. *
  20. * @param aParameters
  21. * The properties of |aParameters| provide backing objects for the root
  22. * actor's requests; if a given property is omitted from |aParameters|, the
  23. * root actor won't implement the corresponding requests or notifications.
  24. * Supported properties:
  25. *
  26. * - tabList: a live list (see below) of tab actors. If present, the
  27. * new root actor supports the 'listTabs' request, providing the live
  28. * list's elements as its tab actors, and sending 'tabListChanged'
  29. * notifications when the live list's contents change. One actor in
  30. * this list must have a true '.selected' property.
  31. *
  32. * - addonList: a live list (see below) of addon actors. If present, the
  33. * new root actor supports the 'listAddons' request, providing the live
  34. * list's elements as its addon actors, and sending 'addonListchanged'
  35. * notifications when the live list's contents change.
  36. *
  37. * - globalActorFactories: an object |A| describing further actors to
  38. * attach to the 'listTabs' reply. This is the type accumulated by
  39. * DebuggerServer.addGlobalActor. For each own property |P| of |A|,
  40. * the root actor adds a property named |P| to the 'listTabs'
  41. * reply whose value is the name of an actor constructed by
  42. * |A[P]|.
  43. *
  44. * - onShutdown: a function to call when the root actor is disconnected.
  45. *
  46. * Instance properties:
  47. *
  48. * - applicationType: the string the root actor will include as the
  49. * "applicationType" property in the greeting packet. By default, this
  50. * is "browser".
  51. *
  52. * Live lists:
  53. *
  54. * A "live list", as used for the |tabList|, is an object that presents a
  55. * list of actors, and also notifies its clients of changes to the list. A
  56. * live list's interface is two properties:
  57. *
  58. * - getList: a method that returns a promise to the contents of the list.
  59. *
  60. * - onListChanged: a handler called, with no arguments, when the set of
  61. * values the iterator would produce has changed since the last
  62. * time 'iterator' was called. This may only be set to null or a
  63. * callable value (one for which the typeof operator returns
  64. * 'function'). (Note that the live list will not call the
  65. * onListChanged handler until the list has been iterated over
  66. * once; if nobody's seen the list in the first place, nobody
  67. * should care if its contents have changed!)
  68. *
  69. * When the list changes, the list implementation should ensure that any
  70. * actors yielded in previous iterations whose referents (tabs) still exist
  71. * get yielded again in subsequent iterations. If the underlying referent
  72. * is the same, the same actor should be presented for it.
  73. *
  74. * The root actor registers an 'onListChanged' handler on the appropriate
  75. * list when it may need to send the client 'tabListChanged' notifications,
  76. * and is careful to remove the handler whenever it does not need to send
  77. * such notifications (including when it is disconnected). This means that
  78. * live list implementations can use the state of the handler property (set
  79. * or null) to install and remove observers and event listeners.
  80. *
  81. * Note that, as the only way for the root actor to see the members of the
  82. * live list is to begin an iteration over the list, the live list need not
  83. * actually produce any actors until they are reached in the course of
  84. * iteration: alliterative lazy live lists.
  85. */
  86. function RootActor(aConnection, aParameters) {
  87. this.conn = aConnection;
  88. this._parameters = aParameters;
  89. this._onTabListChanged = this.onTabListChanged.bind(this);
  90. this._onAddonListChanged = this.onAddonListChanged.bind(this);
  91. this._onWorkerListChanged = this.onWorkerListChanged.bind(this);
  92. this._onServiceWorkerRegistrationListChanged = this.onServiceWorkerRegistrationListChanged.bind(this);
  93. this._onProcessListChanged = this.onProcessListChanged.bind(this);
  94. this._extraActors = {};
  95. this._globalActorPool = new ActorPool(this.conn);
  96. this.conn.addActorPool(this._globalActorPool);
  97. this._chromeActor = null;
  98. }
  99. RootActor.prototype = {
  100. constructor: RootActor,
  101. applicationType: "browser",
  102. traits: {
  103. sources: true,
  104. // Whether the inspector actor allows modifying outer HTML.
  105. editOuterHTML: true,
  106. // Whether the inspector actor allows modifying innerHTML and inserting
  107. // adjacent HTML.
  108. pasteHTML: true,
  109. // Whether the server-side highlighter actor exists and can be used to
  110. // remotely highlight nodes (see server/actors/highlighters.js)
  111. highlightable: true,
  112. // Which custom highlighter does the server-side highlighter actor supports?
  113. // (see server/actors/highlighters.js)
  114. customHighlighters: true,
  115. // Whether the inspector actor implements the getImageDataFromURL
  116. // method that returns data-uris for image URLs. This is used for image
  117. // tooltips for instance
  118. urlToImageDataResolver: true,
  119. networkMonitor: true,
  120. // Whether the storage inspector actor to inspect cookies, etc.
  121. storageInspector: true,
  122. // Whether storage inspector is read only
  123. storageInspectorReadOnly: true,
  124. // Whether conditional breakpoints are supported
  125. conditionalBreakpoints: true,
  126. // Whether the server supports full source actors (breakpoints on
  127. // eval scripts, etc)
  128. debuggerSourceActors: true,
  129. bulk: true,
  130. // Whether the style rule actor implements the modifySelector method
  131. // that modifies the rule's selector
  132. selectorEditable: true,
  133. // Whether the page style actor implements the addNewRule method that
  134. // adds new rules to the page
  135. addNewRule: true,
  136. // Whether the dom node actor implements the getUniqueSelector method
  137. getUniqueSelector: true,
  138. // Whether the dom node actor implements the getCssPath method
  139. getCssPath: true,
  140. // Whether the director scripts are supported
  141. directorScripts: true,
  142. // Whether the debugger server supports
  143. // blackboxing/pretty-printing (not supported in Fever Dream yet)
  144. noBlackBoxing: false,
  145. noPrettyPrinting: false,
  146. // Whether the page style actor implements the getUsedFontFaces method
  147. // that returns the font faces used on a node
  148. getUsedFontFaces: true,
  149. // Trait added in Gecko 38, indicating that all features necessary for
  150. // grabbing allocations from the MemoryActor are available for the performance tool
  151. memoryActorAllocations: true,
  152. // Added in Gecko 40, indicating that the backend isn't stupid about
  153. // sending resumption packets on tab navigation.
  154. noNeedToFakeResumptionOnNavigation: true,
  155. // Added in Firefox 40. Indicates that the backend supports registering custom
  156. // commands through the WebConsoleCommands API.
  157. webConsoleCommands: true,
  158. // Whether root actor exposes tab actors
  159. // if allowChromeProcess is true, you can fetch a ChromeActor instance
  160. // to debug chrome and any non-content ressource via getProcess request
  161. // if allocChromeProcess is defined, but not true, it means that root actor
  162. // no longer expose tab actors, but also that getProcess forbids
  163. // exposing actors for security reasons
  164. get allowChromeProcess() {
  165. return DebuggerServer.allowChromeProcess;
  166. },
  167. // Whether or not `getProfile()` supports specifying a `startTime`
  168. // and `endTime` to filter out samples. Fx40+
  169. profilerDataFilterable: true,
  170. // Whether or not the MemoryActor's heap snapshot abilities are
  171. // fully equipped to handle heap snapshots for the memory tool. Fx44+
  172. heapSnapshots: true,
  173. // Whether or not the timeline actor can emit DOMContentLoaded and Load
  174. // markers, currently in use by the network monitor. Fx45+
  175. documentLoadingMarkers: true
  176. },
  177. /**
  178. * Return a 'hello' packet as specified by the Remote Debugging Protocol.
  179. */
  180. sayHello: function () {
  181. return {
  182. from: this.actorID,
  183. applicationType: this.applicationType,
  184. /* This is not in the spec, but it's used by tests. */
  185. testConnectionPrefix: this.conn.prefix,
  186. traits: this.traits
  187. };
  188. },
  189. forwardingCancelled: function (prefix) {
  190. return {
  191. from: this.actorID,
  192. type: "forwardingCancelled",
  193. prefix,
  194. };
  195. },
  196. /**
  197. * Disconnects the actor from the browser window.
  198. */
  199. disconnect: function () {
  200. /* Tell the live lists we aren't watching any more. */
  201. if (this._parameters.tabList) {
  202. this._parameters.tabList.onListChanged = null;
  203. }
  204. if (this._parameters.addonList) {
  205. this._parameters.addonList.onListChanged = null;
  206. }
  207. if (this._parameters.workerList) {
  208. this._parameters.workerList.onListChanged = null;
  209. }
  210. if (this._parameters.serviceWorkerRegistrationList) {
  211. this._parameters.serviceWorkerRegistrationList.onListChanged = null;
  212. }
  213. if (typeof this._parameters.onShutdown === "function") {
  214. this._parameters.onShutdown();
  215. }
  216. this._extraActors = null;
  217. this.conn = null;
  218. this._tabActorPool = null;
  219. this._globalActorPool = null;
  220. this._parameters = null;
  221. this._chromeActor = null;
  222. },
  223. /* The 'listTabs' request and the 'tabListChanged' notification. */
  224. /**
  225. * Handles the listTabs request. The actors will survive until at least
  226. * the next listTabs request.
  227. */
  228. onListTabs: function () {
  229. let tabList = this._parameters.tabList;
  230. if (!tabList) {
  231. return { from: this.actorID, error: "noTabs",
  232. message: "This root actor has no browser tabs." };
  233. }
  234. /*
  235. * Walk the tab list, accumulating the array of tab actors for the
  236. * reply, and moving all the actors to a new ActorPool. We'll
  237. * replace the old tab actor pool with the one we build here, thus
  238. * retiring any actors that didn't get listed again, and preparing any
  239. * new actors to receive packets.
  240. */
  241. let newActorPool = new ActorPool(this.conn);
  242. let tabActorList = [];
  243. let selected;
  244. return tabList.getList().then((tabActors) => {
  245. for (let tabActor of tabActors) {
  246. if (tabActor.selected) {
  247. selected = tabActorList.length;
  248. }
  249. tabActor.parentID = this.actorID;
  250. newActorPool.addActor(tabActor);
  251. tabActorList.push(tabActor);
  252. }
  253. /* DebuggerServer.addGlobalActor support: create actors. */
  254. if (!this._globalActorPool) {
  255. this._globalActorPool = new ActorPool(this.conn);
  256. this.conn.addActorPool(this._globalActorPool);
  257. }
  258. this._createExtraActors(this._parameters.globalActorFactories, this._globalActorPool);
  259. /*
  260. * Drop the old actorID -> actor map. Actors that still mattered were
  261. * added to the new map; others will go away.
  262. */
  263. if (this._tabActorPool) {
  264. this.conn.removeActorPool(this._tabActorPool);
  265. }
  266. this._tabActorPool = newActorPool;
  267. this.conn.addActorPool(this._tabActorPool);
  268. let reply = {
  269. "from": this.actorID,
  270. "selected": selected || 0,
  271. "tabs": tabActorList.map(actor => actor.form())
  272. };
  273. /* If a root window is accessible, include its URL. */
  274. if (this.url) {
  275. reply.url = this.url;
  276. }
  277. /* DebuggerServer.addGlobalActor support: name actors in 'listTabs' reply. */
  278. this._appendExtraActors(reply);
  279. /*
  280. * Now that we're actually going to report the contents of tabList to
  281. * the client, we're responsible for letting the client know if it
  282. * changes.
  283. */
  284. tabList.onListChanged = this._onTabListChanged;
  285. return reply;
  286. });
  287. },
  288. onGetTab: function (options) {
  289. let tabList = this._parameters.tabList;
  290. if (!tabList) {
  291. return { error: "noTabs",
  292. message: "This root actor has no browser tabs." };
  293. }
  294. if (!this._tabActorPool) {
  295. this._tabActorPool = new ActorPool(this.conn);
  296. this.conn.addActorPool(this._tabActorPool);
  297. }
  298. return tabList.getTab(options)
  299. .then(tabActor => {
  300. tabActor.parentID = this.actorID;
  301. this._tabActorPool.addActor(tabActor);
  302. return { tab: tabActor.form() };
  303. }, error => {
  304. if (error.error) {
  305. // Pipe expected errors as-is to the client
  306. return error;
  307. } else {
  308. return { error: "noTab",
  309. message: "Unexpected error while calling getTab(): " + error };
  310. }
  311. });
  312. },
  313. onTabListChanged: function () {
  314. this.conn.send({ from: this.actorID, type:"tabListChanged" });
  315. /* It's a one-shot notification; no need to watch any more. */
  316. this._parameters.tabList.onListChanged = null;
  317. },
  318. onListAddons: function () {
  319. let addonList = this._parameters.addonList;
  320. if (!addonList) {
  321. return { from: this.actorID, error: "noAddons",
  322. message: "This root actor has no browser addons." };
  323. }
  324. return addonList.getList().then((addonActors) => {
  325. let addonActorPool = new ActorPool(this.conn);
  326. for (let addonActor of addonActors) {
  327. addonActorPool.addActor(addonActor);
  328. }
  329. if (this._addonActorPool) {
  330. this.conn.removeActorPool(this._addonActorPool);
  331. }
  332. this._addonActorPool = addonActorPool;
  333. this.conn.addActorPool(this._addonActorPool);
  334. addonList.onListChanged = this._onAddonListChanged;
  335. return {
  336. "from": this.actorID,
  337. "addons": addonActors.map(addonActor => addonActor.form())
  338. };
  339. });
  340. },
  341. onAddonListChanged: function () {
  342. this.conn.send({ from: this.actorID, type: "addonListChanged" });
  343. this._parameters.addonList.onListChanged = null;
  344. },
  345. onListWorkers: function () {
  346. let workerList = this._parameters.workerList;
  347. if (!workerList) {
  348. return { from: this.actorID, error: "noWorkers",
  349. message: "This root actor has no workers." };
  350. }
  351. return workerList.getList().then(actors => {
  352. let pool = new ActorPool(this.conn);
  353. for (let actor of actors) {
  354. pool.addActor(actor);
  355. }
  356. this.conn.removeActorPool(this._workerActorPool);
  357. this._workerActorPool = pool;
  358. this.conn.addActorPool(this._workerActorPool);
  359. workerList.onListChanged = this._onWorkerListChanged;
  360. return {
  361. "from": this.actorID,
  362. "workers": actors.map(actor => actor.form())
  363. };
  364. });
  365. },
  366. onWorkerListChanged: function () {
  367. this.conn.send({ from: this.actorID, type: "workerListChanged" });
  368. this._parameters.workerList.onListChanged = null;
  369. },
  370. onListServiceWorkerRegistrations: function () {
  371. let registrationList = this._parameters.serviceWorkerRegistrationList;
  372. if (!registrationList) {
  373. return { from: this.actorID, error: "noServiceWorkerRegistrations",
  374. message: "This root actor has no service worker registrations." };
  375. }
  376. return registrationList.getList().then(actors => {
  377. let pool = new ActorPool(this.conn);
  378. for (let actor of actors) {
  379. pool.addActor(actor);
  380. }
  381. this.conn.removeActorPool(this._serviceWorkerRegistrationActorPool);
  382. this._serviceWorkerRegistrationActorPool = pool;
  383. this.conn.addActorPool(this._serviceWorkerRegistrationActorPool);
  384. registrationList.onListChanged = this._onServiceWorkerRegistrationListChanged;
  385. return {
  386. "from": this.actorID,
  387. "registrations": actors.map(actor => actor.form())
  388. };
  389. });
  390. },
  391. onServiceWorkerRegistrationListChanged: function () {
  392. this.conn.send({ from: this.actorID, type: "serviceWorkerRegistrationListChanged" });
  393. this._parameters.serviceWorkerRegistrationList.onListChanged = null;
  394. },
  395. onListProcesses: function () {
  396. let { processList } = this._parameters;
  397. if (!processList) {
  398. return { from: this.actorID, error: "noProcesses",
  399. message: "This root actor has no processes." };
  400. }
  401. processList.onListChanged = this._onProcessListChanged;
  402. return {
  403. processes: processList.getList()
  404. };
  405. },
  406. onProcessListChanged: function () {
  407. this.conn.send({ from: this.actorID, type: "processListChanged" });
  408. this._parameters.processList.onListChanged = null;
  409. },
  410. onGetProcess: function (aRequest) {
  411. if (!DebuggerServer.allowChromeProcess) {
  412. return { error: "forbidden",
  413. message: "You are not allowed to debug chrome." };
  414. }
  415. if (("id" in aRequest) && typeof (aRequest.id) != "number") {
  416. return { error: "wrongParameter",
  417. message: "getProcess requires a valid `id` attribute." };
  418. }
  419. // If the request doesn't contains id parameter or id is 0
  420. // (id == 0, based on onListProcesses implementation)
  421. if ((!("id" in aRequest)) || aRequest.id === 0) {
  422. if (!this._chromeActor) {
  423. // Create a ChromeActor for the parent process
  424. let { ChromeActor } = require("devtools/server/actors/chrome");
  425. this._chromeActor = new ChromeActor(this.conn);
  426. this._globalActorPool.addActor(this._chromeActor);
  427. }
  428. return { form: this._chromeActor.form() };
  429. } else {
  430. let mm = ppmm.getChildAt(aRequest.id);
  431. if (!mm) {
  432. return { error: "noProcess",
  433. message: "There is no process with id '" + aRequest.id + "'." };
  434. }
  435. return DebuggerServer.connectToContent(this.conn, mm)
  436. .then(form => ({ form }));
  437. }
  438. },
  439. /* This is not in the spec, but it's used by tests. */
  440. onEcho: function (aRequest) {
  441. /*
  442. * Request packets are frozen. Copy aRequest, so that
  443. * DebuggerServerConnection.onPacket can attach a 'from' property.
  444. */
  445. return Cu.cloneInto(aRequest, {});
  446. },
  447. onProtocolDescription: function () {
  448. return require("devtools/shared/protocol").dumpProtocolSpec();
  449. },
  450. /* Support for DebuggerServer.addGlobalActor. */
  451. _createExtraActors: createExtraActors,
  452. _appendExtraActors: appendExtraActors,
  453. /**
  454. * Remove the extra actor (added by DebuggerServer.addGlobalActor or
  455. * DebuggerServer.addTabActor) name |aName|.
  456. */
  457. removeActorByName: function (aName) {
  458. if (aName in this._extraActors) {
  459. const actor = this._extraActors[aName];
  460. if (this._globalActorPool.has(actor)) {
  461. this._globalActorPool.removeActor(actor);
  462. }
  463. if (this._tabActorPool) {
  464. // Iterate over TabActor instances to also remove tab actors
  465. // created during listTabs for each document.
  466. this._tabActorPool.forEach(tab => {
  467. tab.removeActorByName(aName);
  468. });
  469. }
  470. delete this._extraActors[aName];
  471. }
  472. }
  473. };
  474. RootActor.prototype.requestTypes = {
  475. "listTabs": RootActor.prototype.onListTabs,
  476. "getTab": RootActor.prototype.onGetTab,
  477. "listAddons": RootActor.prototype.onListAddons,
  478. "listWorkers": RootActor.prototype.onListWorkers,
  479. "listServiceWorkerRegistrations": RootActor.prototype.onListServiceWorkerRegistrations,
  480. "listProcesses": RootActor.prototype.onListProcesses,
  481. "getProcess": RootActor.prototype.onGetProcess,
  482. "echo": RootActor.prototype.onEcho,
  483. "protocolDescription": RootActor.prototype.onProtocolDescription
  484. };
  485. exports.RootActor = RootActor;