NavigatorView.js 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039
  1. /*
  2. * Copyright (C) 2012 Google Inc. All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions are
  6. * met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above
  12. * copyright notice, this list of conditions and the following disclaimer
  13. * in the documentation and/or other materials provided with the
  14. * distribution.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS
  17. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  18. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  19. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC.
  20. * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  21. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  22. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  23. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  24. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  26. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. */
  28. /**
  29. * @extends {WebInspector.View}
  30. * @constructor
  31. */
  32. WebInspector.NavigatorView = function()
  33. {
  34. WebInspector.View.call(this);
  35. this.registerRequiredCSS("navigatorView.css");
  36. this._treeSearchBoxElement = document.createElement("div");
  37. this._treeSearchBoxElement.className = "navigator-tree-search-box";
  38. this.element.appendChild(this._treeSearchBoxElement);
  39. var scriptsTreeElement = document.createElement("ol");
  40. this._scriptsTree = new WebInspector.NavigatorTreeOutline(this._treeSearchBoxElement, scriptsTreeElement);
  41. var scriptsOutlineElement = document.createElement("div");
  42. scriptsOutlineElement.addStyleClass("outline-disclosure");
  43. scriptsOutlineElement.addStyleClass("navigator");
  44. scriptsOutlineElement.appendChild(scriptsTreeElement);
  45. this.element.addStyleClass("fill");
  46. this.element.addStyleClass("navigator-container");
  47. this.element.appendChild(scriptsOutlineElement);
  48. this.setDefaultFocusedElement(this._scriptsTree.element);
  49. /** @type {Object.<string, WebInspector.NavigatorUISourceCodeTreeNode>} */
  50. this._uiSourceCodeNodes = {};
  51. this._rootNode = new WebInspector.NavigatorRootTreeNode(this);
  52. this._rootNode.populate();
  53. WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._inspectedURLChanged, this);
  54. }
  55. WebInspector.NavigatorView.Events = {
  56. ItemSelected: "ItemSelected",
  57. FileRenamed: "FileRenamed"
  58. }
  59. WebInspector.NavigatorView.iconClassForType = function(type)
  60. {
  61. if (type === WebInspector.NavigatorTreeOutline.Types.Domain)
  62. return "navigator-domain-tree-item";
  63. if (type === WebInspector.NavigatorTreeOutline.Types.FileSystem)
  64. return "navigator-folder-tree-item";
  65. return "navigator-folder-tree-item";
  66. }
  67. WebInspector.NavigatorView.prototype = {
  68. /**
  69. * @param {WebInspector.UISourceCode} uiSourceCode
  70. */
  71. addUISourceCode: function(uiSourceCode)
  72. {
  73. var node = this._getOrCreateUISourceCodeParentNode(uiSourceCode);
  74. var uiSourceCodeNode = new WebInspector.NavigatorUISourceCodeTreeNode(this, uiSourceCode);
  75. this._uiSourceCodeNodes[uiSourceCode.uri()] = uiSourceCodeNode;
  76. node.appendChild(uiSourceCodeNode);
  77. if (uiSourceCode.url === WebInspector.inspectedPageURL)
  78. this.revealUISourceCode(uiSourceCode);
  79. },
  80. /**
  81. * @param {WebInspector.Event} event
  82. */
  83. _inspectedURLChanged: function(event)
  84. {
  85. var nodes = Object.values(this._uiSourceCodeNodes);
  86. for (var i = 0; i < nodes.length; ++i) {
  87. var uiSourceCode = nodes[i].uiSourceCode();
  88. if (uiSourceCode.url === WebInspector.inspectedPageURL)
  89. this.revealUISourceCode(uiSourceCode);
  90. }
  91. },
  92. /**
  93. * @param {WebInspector.Project} project
  94. * @return {WebInspector.NavigatorTreeNode}
  95. */
  96. _getProjectNode: function(project)
  97. {
  98. if (!project.displayName())
  99. return this._rootNode;
  100. return this._rootNode.child(project.id());
  101. },
  102. /**
  103. * @param {WebInspector.Project} project
  104. * @return {WebInspector.NavigatorFolderTreeNode}
  105. */
  106. _createProjectNode: function(project)
  107. {
  108. var type = project.type() === WebInspector.projectTypes.FileSystem ? WebInspector.NavigatorTreeOutline.Types.FileSystem : WebInspector.NavigatorTreeOutline.Types.Domain;
  109. var projectNode = new WebInspector.NavigatorFolderTreeNode(this, project.id(), type, project.displayName());
  110. this._rootNode.appendChild(projectNode);
  111. return projectNode;
  112. },
  113. /**
  114. * @param {WebInspector.Project} project
  115. * @return {WebInspector.NavigatorTreeNode}
  116. */
  117. _getOrCreateProjectNode: function(project)
  118. {
  119. return this._getProjectNode(project) || this._createProjectNode(project);
  120. },
  121. /**
  122. * @param {WebInspector.NavigatorTreeNode} parentNode
  123. * @param {string} name
  124. * @return {WebInspector.NavigatorFolderTreeNode}
  125. */
  126. _getFolderNode: function(parentNode, name)
  127. {
  128. return parentNode.child(name);
  129. },
  130. /**
  131. * @param {WebInspector.NavigatorTreeNode} parentNode
  132. * @param {string} name
  133. * @return {WebInspector.NavigatorFolderTreeNode}
  134. */
  135. _createFolderNode: function(parentNode, name)
  136. {
  137. var folderNode = new WebInspector.NavigatorFolderTreeNode(this, name, WebInspector.NavigatorTreeOutline.Types.Folder, name);
  138. parentNode.appendChild(folderNode);
  139. return folderNode;
  140. },
  141. /**
  142. * @param {WebInspector.NavigatorTreeNode} parentNode
  143. * @param {string} name
  144. * @return {WebInspector.NavigatorFolderTreeNode}
  145. */
  146. _getOrCreateFolderNode: function(parentNode, name)
  147. {
  148. return this._getFolderNode(parentNode, name) || this._createFolderNode(parentNode, name);
  149. },
  150. /**
  151. * @param {WebInspector.UISourceCode} uiSourceCode
  152. * @return {WebInspector.NavigatorTreeNode}
  153. */
  154. _getUISourceCodeParentNode: function(uiSourceCode)
  155. {
  156. var projectNode = this._getProjectNode(uiSourceCode.project());
  157. if (!projectNode)
  158. return null;
  159. var path = uiSourceCode.path();
  160. var parentNode = projectNode;
  161. for (var i = 0; i < path.length - 1; ++i) {
  162. parentNode = this._getFolderNode(parentNode, path[i]);
  163. if (!parentNode)
  164. return null;
  165. }
  166. return parentNode;
  167. },
  168. /**
  169. * @param {WebInspector.UISourceCode} uiSourceCode
  170. * @return {WebInspector.NavigatorTreeNode}
  171. */
  172. _getOrCreateUISourceCodeParentNode: function(uiSourceCode)
  173. {
  174. var projectNode = this._getOrCreateProjectNode(uiSourceCode.project());
  175. if (!projectNode)
  176. return null;
  177. var path = uiSourceCode.path();
  178. var parentNode = projectNode;
  179. for (var i = 0; i < path.length - 1; ++i) {
  180. parentNode = this._getOrCreateFolderNode(parentNode, path[i]);
  181. if (!parentNode)
  182. return null;
  183. }
  184. return parentNode;
  185. },
  186. /**
  187. * @param {WebInspector.UISourceCode} uiSourceCode
  188. * @param {boolean=} select
  189. */
  190. revealUISourceCode: function(uiSourceCode, select)
  191. {
  192. var node = this._uiSourceCodeNodes[uiSourceCode.uri()];
  193. if (!node)
  194. return null;
  195. if (this._scriptsTree.selectedTreeElement)
  196. this._scriptsTree.selectedTreeElement.deselect();
  197. this._lastSelectedUISourceCode = uiSourceCode;
  198. node.reveal(select);
  199. },
  200. /**
  201. * @param {WebInspector.UISourceCode} uiSourceCode
  202. * @param {boolean} focusSource
  203. */
  204. _scriptSelected: function(uiSourceCode, focusSource)
  205. {
  206. this._lastSelectedUISourceCode = uiSourceCode;
  207. var data = { uiSourceCode: uiSourceCode, focusSource: focusSource};
  208. this.dispatchEventToListeners(WebInspector.NavigatorView.Events.ItemSelected, data);
  209. },
  210. /**
  211. * @param {WebInspector.UISourceCode} uiSourceCode
  212. */
  213. removeUISourceCode: function(uiSourceCode)
  214. {
  215. var parentNode = this._getUISourceCodeParentNode(uiSourceCode);
  216. if (!parentNode)
  217. return;
  218. var node = this._uiSourceCodeNodes[uiSourceCode.uri()];
  219. if (!node)
  220. return;
  221. delete this._uiSourceCodeNodes[uiSourceCode.uri()]
  222. parentNode.removeChild(node);
  223. node = parentNode;
  224. while (node) {
  225. parentNode = node.parent;
  226. if (!parentNode || !node.isEmpty())
  227. break;
  228. parentNode.removeChild(node);
  229. node = parentNode;
  230. }
  231. },
  232. _fileRenamed: function(uiSourceCode, newTitle)
  233. {
  234. var data = { uiSourceCode: uiSourceCode, name: newTitle };
  235. this.dispatchEventToListeners(WebInspector.NavigatorView.Events.FileRenamed, data);
  236. },
  237. /**
  238. * @param {WebInspector.UISourceCode} uiSourceCode
  239. * @param {function(boolean)=} callback
  240. */
  241. rename: function(uiSourceCode, callback)
  242. {
  243. var node = this._uiSourceCodeNodes[uiSourceCode.uri()];
  244. if (!node)
  245. return null;
  246. node.rename(callback);
  247. },
  248. reset: function()
  249. {
  250. for (var uri in this._uiSourceCodeNodes)
  251. this._uiSourceCodeNodes[uri].dispose();
  252. this._scriptsTree.stopSearch();
  253. this._scriptsTree.removeChildren();
  254. this._uiSourceCodeNodes = {};
  255. this._rootNode.reset();
  256. },
  257. handleContextMenu: function(event, uiSourceCode)
  258. {
  259. var contextMenu = new WebInspector.ContextMenu(event);
  260. contextMenu.appendApplicableItems(uiSourceCode);
  261. contextMenu.show();
  262. },
  263. __proto__: WebInspector.View.prototype
  264. }
  265. /**
  266. * @constructor
  267. * @extends {TreeOutline}
  268. * @param {Element} treeSearchBoxElement
  269. * @param {Element} element
  270. */
  271. WebInspector.NavigatorTreeOutline = function(treeSearchBoxElement, element)
  272. {
  273. TreeOutline.call(this, element);
  274. this.element = element;
  275. this._treeSearchBoxElement = treeSearchBoxElement;
  276. this.comparator = WebInspector.NavigatorTreeOutline._treeElementsCompare;
  277. this.searchable = true;
  278. this.searchInputElement = document.createElement("input");
  279. }
  280. WebInspector.NavigatorTreeOutline.Types = {
  281. Root: "Root",
  282. Domain: "Domain",
  283. Folder: "Folder",
  284. UISourceCode: "UISourceCode",
  285. FileSystem: "FileSystem"
  286. }
  287. WebInspector.NavigatorTreeOutline._treeElementsCompare = function compare(treeElement1, treeElement2)
  288. {
  289. // Insert in the alphabetical order, first domains, then folders, then scripts.
  290. function typeWeight(treeElement)
  291. {
  292. var type = treeElement.type();
  293. if (type === WebInspector.NavigatorTreeOutline.Types.Domain) {
  294. if (treeElement.titleText === WebInspector.inspectedPageDomain)
  295. return 1;
  296. return 2;
  297. }
  298. if (type === WebInspector.NavigatorTreeOutline.Types.FileSystem)
  299. return 3;
  300. if (type === WebInspector.NavigatorTreeOutline.Types.Folder)
  301. return 4;
  302. return 5;
  303. }
  304. var typeWeight1 = typeWeight(treeElement1);
  305. var typeWeight2 = typeWeight(treeElement2);
  306. var result;
  307. if (typeWeight1 > typeWeight2)
  308. result = 1;
  309. else if (typeWeight1 < typeWeight2)
  310. result = -1;
  311. else {
  312. var title1 = treeElement1.titleText;
  313. var title2 = treeElement2.titleText;
  314. result = title1.compareTo(title2);
  315. }
  316. return result;
  317. }
  318. WebInspector.NavigatorTreeOutline.prototype = {
  319. /**
  320. * @return {Array.<WebInspector.UISourceCode>}
  321. */
  322. scriptTreeElements: function()
  323. {
  324. var result = [];
  325. if (this.children.length) {
  326. for (var treeElement = this.children[0]; treeElement; treeElement = treeElement.traverseNextTreeElement(false, this, true)) {
  327. if (treeElement instanceof WebInspector.NavigatorSourceTreeElement)
  328. result.push(treeElement.uiSourceCode);
  329. }
  330. }
  331. return result;
  332. },
  333. searchStarted: function()
  334. {
  335. this._treeSearchBoxElement.appendChild(this.searchInputElement);
  336. this._treeSearchBoxElement.addStyleClass("visible");
  337. },
  338. searchFinished: function()
  339. {
  340. this._treeSearchBoxElement.removeChild(this.searchInputElement);
  341. this._treeSearchBoxElement.removeStyleClass("visible");
  342. },
  343. __proto__: TreeOutline.prototype
  344. }
  345. /**
  346. * @constructor
  347. * @extends {TreeElement}
  348. * @param {string} type
  349. * @param {string} title
  350. * @param {Array.<string>} iconClasses
  351. * @param {boolean} hasChildren
  352. * @param {boolean=} noIcon
  353. */
  354. WebInspector.BaseNavigatorTreeElement = function(type, title, iconClasses, hasChildren, noIcon)
  355. {
  356. this._type = type;
  357. TreeElement.call(this, "", null, hasChildren);
  358. this._titleText = title;
  359. this._iconClasses = iconClasses;
  360. this._noIcon = noIcon;
  361. }
  362. WebInspector.BaseNavigatorTreeElement.prototype = {
  363. onattach: function()
  364. {
  365. this.listItemElement.removeChildren();
  366. if (this._iconClasses) {
  367. for (var i = 0; i < this._iconClasses.length; ++i)
  368. this.listItemElement.addStyleClass(this._iconClasses[i]);
  369. }
  370. var selectionElement = document.createElement("div");
  371. selectionElement.className = "selection";
  372. this.listItemElement.appendChild(selectionElement);
  373. if (!this._noIcon) {
  374. this.imageElement = document.createElement("img");
  375. this.imageElement.className = "icon";
  376. this.listItemElement.appendChild(this.imageElement);
  377. }
  378. this.titleElement = document.createElement("div");
  379. this.titleElement.className = "base-navigator-tree-element-title";
  380. this._titleTextNode = document.createTextNode("");
  381. this._titleTextNode.textContent = this._titleText;
  382. this.titleElement.appendChild(this._titleTextNode);
  383. this.listItemElement.appendChild(this.titleElement);
  384. },
  385. onreveal: function()
  386. {
  387. if (this.listItemElement)
  388. this.listItemElement.scrollIntoViewIfNeeded(true);
  389. },
  390. /**
  391. * @return {string}
  392. */
  393. get titleText()
  394. {
  395. return this._titleText;
  396. },
  397. set titleText(titleText)
  398. {
  399. if (this._titleText === titleText)
  400. return;
  401. this._titleText = titleText || "";
  402. if (this.titleElement)
  403. this.titleElement.textContent = this._titleText;
  404. },
  405. /**
  406. * @param {string} searchText
  407. */
  408. matchesSearchText: function(searchText)
  409. {
  410. return this.titleText.match(new RegExp("^" + searchText.escapeForRegExp(), "i"));
  411. },
  412. /**
  413. * @return {string}
  414. */
  415. type: function()
  416. {
  417. return this._type;
  418. },
  419. __proto__: TreeElement.prototype
  420. }
  421. /**
  422. * @constructor
  423. * @extends {WebInspector.BaseNavigatorTreeElement}
  424. * @param {string} type
  425. * @param {string} title
  426. */
  427. WebInspector.NavigatorFolderTreeElement = function(type, title)
  428. {
  429. var iconClass = WebInspector.NavigatorView.iconClassForType(type);
  430. WebInspector.BaseNavigatorTreeElement.call(this, type, title, [iconClass], true);
  431. }
  432. WebInspector.NavigatorFolderTreeElement.prototype = {
  433. onpopulate: function()
  434. {
  435. this._node.populate();
  436. },
  437. onattach: function()
  438. {
  439. WebInspector.BaseNavigatorTreeElement.prototype.onattach.call(this);
  440. this.collapse();
  441. },
  442. __proto__: WebInspector.BaseNavigatorTreeElement.prototype
  443. }
  444. /**
  445. * @constructor
  446. * @extends {WebInspector.BaseNavigatorTreeElement}
  447. * @param {WebInspector.NavigatorView} navigatorView
  448. * @param {WebInspector.UISourceCode} uiSourceCode
  449. * @param {string} title
  450. */
  451. WebInspector.NavigatorSourceTreeElement = function(navigatorView, uiSourceCode, title)
  452. {
  453. WebInspector.BaseNavigatorTreeElement.call(this, WebInspector.NavigatorTreeOutline.Types.UISourceCode, title, ["navigator-" + uiSourceCode.contentType().name() + "-tree-item"], false);
  454. this._navigatorView = navigatorView;
  455. this._uiSourceCode = uiSourceCode;
  456. this.tooltip = uiSourceCode.originURL();
  457. }
  458. WebInspector.NavigatorSourceTreeElement.prototype = {
  459. /**
  460. * @return {WebInspector.UISourceCode}
  461. */
  462. get uiSourceCode()
  463. {
  464. return this._uiSourceCode;
  465. },
  466. onattach: function()
  467. {
  468. WebInspector.BaseNavigatorTreeElement.prototype.onattach.call(this);
  469. this.listItemElement.draggable = true;
  470. this.listItemElement.addEventListener("click", this._onclick.bind(this), false);
  471. this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false);
  472. this.listItemElement.addEventListener("mousedown", this._onmousedown.bind(this), false);
  473. this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false);
  474. },
  475. _onmousedown: function(event)
  476. {
  477. if (event.which === 1) // Warm-up data for drag'n'drop
  478. this._uiSourceCode.requestContent(callback.bind(this));
  479. /**
  480. * @param {?string} content
  481. * @param {boolean} contentEncoded
  482. * @param {string} mimeType
  483. */
  484. function callback(content, contentEncoded, mimeType)
  485. {
  486. this._warmedUpContent = content;
  487. }
  488. },
  489. _ondragstart: function(event)
  490. {
  491. event.dataTransfer.setData("text/plain", this._warmedUpContent);
  492. event.dataTransfer.effectAllowed = "copy";
  493. return true;
  494. },
  495. onspace: function()
  496. {
  497. this._navigatorView._scriptSelected(this.uiSourceCode, true);
  498. return true;
  499. },
  500. /**
  501. * @param {Event} event
  502. */
  503. _onclick: function(event)
  504. {
  505. this._navigatorView._scriptSelected(this.uiSourceCode, false);
  506. },
  507. /**
  508. * @param {Event} event
  509. */
  510. ondblclick: function(event)
  511. {
  512. var middleClick = event.button === 1;
  513. this._navigatorView._scriptSelected(this.uiSourceCode, !middleClick);
  514. },
  515. onenter: function()
  516. {
  517. this._navigatorView._scriptSelected(this.uiSourceCode, true);
  518. return true;
  519. },
  520. /**
  521. * @param {Event} event
  522. */
  523. _handleContextMenuEvent: function(event)
  524. {
  525. this._navigatorView.handleContextMenu(event, this._uiSourceCode);
  526. },
  527. __proto__: WebInspector.BaseNavigatorTreeElement.prototype
  528. }
  529. /**
  530. * @constructor
  531. * @param {string} id
  532. */
  533. WebInspector.NavigatorTreeNode = function(id)
  534. {
  535. this.id = id;
  536. this._children = {};
  537. }
  538. WebInspector.NavigatorTreeNode.prototype = {
  539. /**
  540. * @return {TreeElement}
  541. */
  542. treeElement: function() { },
  543. dispose: function() { },
  544. /**
  545. * @return {boolean}
  546. */
  547. isRoot: function()
  548. {
  549. return false;
  550. },
  551. /**
  552. * @return {boolean}
  553. */
  554. hasChildren: function()
  555. {
  556. return true;
  557. },
  558. populate: function()
  559. {
  560. if (this.isPopulated())
  561. return;
  562. if (this.parent)
  563. this.parent.populate();
  564. this._populated = true;
  565. this.wasPopulated();
  566. },
  567. wasPopulated: function()
  568. {
  569. for (var id in this._children)
  570. this.treeElement().appendChild(this._children[id].treeElement());
  571. },
  572. didAddChild: function(node)
  573. {
  574. if (this.isPopulated())
  575. this.treeElement().appendChild(node.treeElement());
  576. },
  577. willRemoveChild: function(node)
  578. {
  579. if (this.isPopulated())
  580. this.treeElement().removeChild(node.treeElement());
  581. },
  582. isPopulated: function()
  583. {
  584. return this._populated;
  585. },
  586. isEmpty: function()
  587. {
  588. return this.children().length === 0;
  589. },
  590. child: function(id)
  591. {
  592. return this._children[id];
  593. },
  594. children: function()
  595. {
  596. return Object.values(this._children);
  597. },
  598. appendChild: function(node)
  599. {
  600. this._children[node.id] = node;
  601. node.parent = this;
  602. this.didAddChild(node);
  603. },
  604. removeChild: function(node)
  605. {
  606. this.willRemoveChild(node);
  607. delete this._children[node.id];
  608. delete node.parent;
  609. node.dispose();
  610. },
  611. reset: function()
  612. {
  613. this._children = {};
  614. }
  615. }
  616. /**
  617. * @constructor
  618. * @extends {WebInspector.NavigatorTreeNode}
  619. * @param {WebInspector.NavigatorView} navigatorView
  620. */
  621. WebInspector.NavigatorRootTreeNode = function(navigatorView)
  622. {
  623. WebInspector.NavigatorTreeNode.call(this, "");
  624. this._navigatorView = navigatorView;
  625. }
  626. WebInspector.NavigatorRootTreeNode.prototype = {
  627. /**
  628. * @return {boolean}
  629. */
  630. isRoot: function()
  631. {
  632. return true;
  633. },
  634. /**
  635. * @return {TreeOutline}
  636. */
  637. treeElement: function()
  638. {
  639. return this._navigatorView._scriptsTree;
  640. },
  641. wasPopulated: function()
  642. {
  643. for (var id in this._children)
  644. this.treeElement().appendChild(this._children[id].treeElement());
  645. },
  646. didAddChild: function(node)
  647. {
  648. if (this.isPopulated())
  649. this.treeElement().appendChild(node.treeElement());
  650. },
  651. willRemoveChild: function(node)
  652. {
  653. if (this.isPopulated())
  654. this.treeElement().removeChild(node.treeElement());
  655. },
  656. __proto__: WebInspector.NavigatorTreeNode.prototype
  657. }
  658. /**
  659. * @constructor
  660. * @extends {WebInspector.NavigatorTreeNode}
  661. * @param {WebInspector.NavigatorView} navigatorView
  662. * @param {WebInspector.UISourceCode} uiSourceCode
  663. */
  664. WebInspector.NavigatorUISourceCodeTreeNode = function(navigatorView, uiSourceCode)
  665. {
  666. WebInspector.NavigatorTreeNode.call(this, uiSourceCode.name());
  667. this._navigatorView = navigatorView;
  668. this._uiSourceCode = uiSourceCode;
  669. }
  670. WebInspector.NavigatorUISourceCodeTreeNode.prototype = {
  671. /**
  672. * @return {WebInspector.UISourceCode}
  673. */
  674. uiSourceCode: function()
  675. {
  676. return this._uiSourceCode;
  677. },
  678. /**
  679. * @return {TreeElement}
  680. */
  681. treeElement: function()
  682. {
  683. if (this._treeElement)
  684. return this._treeElement;
  685. this._treeElement = new WebInspector.NavigatorSourceTreeElement(this._navigatorView, this._uiSourceCode, "");
  686. this.updateTitle();
  687. this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._titleChanged, this);
  688. this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
  689. this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
  690. this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.FormattedChanged, this._formattedChanged, this);
  691. return this._treeElement;
  692. },
  693. /**
  694. * @param {boolean=} ignoreIsDirty
  695. */
  696. updateTitle: function(ignoreIsDirty)
  697. {
  698. if (!this._treeElement)
  699. return;
  700. var titleText = this._uiSourceCode.name().trimEnd(100);
  701. if (!titleText)
  702. titleText = WebInspector.UIString("(program)");
  703. if (!ignoreIsDirty && this._uiSourceCode.isDirty())
  704. titleText = "*" + titleText;
  705. this._treeElement.titleText = titleText;
  706. },
  707. /**
  708. * @return {boolean}
  709. */
  710. hasChildren: function()
  711. {
  712. return false;
  713. },
  714. dispose: function()
  715. {
  716. if (!this._treeElement)
  717. return;
  718. this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._titleChanged, this);
  719. this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
  720. this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
  721. this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.FormattedChanged, this._formattedChanged, this);
  722. },
  723. _titleChanged: function(event)
  724. {
  725. this.updateTitle();
  726. },
  727. _workingCopyChanged: function(event)
  728. {
  729. this.updateTitle();
  730. },
  731. _workingCopyCommitted: function(event)
  732. {
  733. this.updateTitle();
  734. },
  735. _formattedChanged: function(event)
  736. {
  737. this.updateTitle();
  738. },
  739. /**
  740. * @param {boolean=} select
  741. */
  742. reveal: function(select)
  743. {
  744. this.parent.populate();
  745. this.parent.treeElement().expand();
  746. this._treeElement.reveal();
  747. if (select)
  748. this._treeElement.select();
  749. },
  750. /**
  751. * @param {function(boolean)=} callback
  752. */
  753. rename: function(callback)
  754. {
  755. if (!this._treeElement)
  756. return;
  757. // Tree outline should be marked as edited as well as the tree element to prevent search from starting.
  758. var treeOutlineElement = this._treeElement.treeOutline.element;
  759. WebInspector.markBeingEdited(treeOutlineElement, true);
  760. function commitHandler(element, newTitle, oldTitle)
  761. {
  762. if (newTitle && newTitle !== oldTitle)
  763. this._navigatorView._fileRenamed(this._uiSourceCode, newTitle);
  764. afterEditing.call(this, true);
  765. }
  766. function cancelHandler()
  767. {
  768. afterEditing.call(this, false);
  769. }
  770. /**
  771. * @param {boolean} committed
  772. */
  773. function afterEditing(committed)
  774. {
  775. WebInspector.markBeingEdited(treeOutlineElement, false);
  776. this.updateTitle();
  777. if (callback)
  778. callback(committed);
  779. }
  780. var editingConfig = new WebInspector.EditingConfig(commitHandler.bind(this), cancelHandler.bind(this));
  781. this.updateTitle(true);
  782. WebInspector.startEditing(this._treeElement.titleElement, editingConfig);
  783. window.getSelection().setBaseAndExtent(this._treeElement.titleElement, 0, this._treeElement.titleElement, 1);
  784. },
  785. __proto__: WebInspector.NavigatorTreeNode.prototype
  786. }
  787. /**
  788. * @constructor
  789. * @extends {WebInspector.NavigatorTreeNode}
  790. * @param {WebInspector.NavigatorView} navigatorView
  791. * @param {string} id
  792. * @param {string} type
  793. * @param {string} title
  794. */
  795. WebInspector.NavigatorFolderTreeNode = function(navigatorView, id, type, title)
  796. {
  797. WebInspector.NavigatorTreeNode.call(this, id);
  798. this._navigatorView = navigatorView;
  799. this._type = type;
  800. this._title = title;
  801. }
  802. WebInspector.NavigatorFolderTreeNode.prototype = {
  803. /**
  804. * @return {TreeElement}
  805. */
  806. treeElement: function()
  807. {
  808. if (this._treeElement)
  809. return this._treeElement;
  810. this._treeElement = this._createTreeElement(this._title, this);
  811. return this._treeElement;
  812. },
  813. /**
  814. * @return {TreeElement}
  815. */
  816. _createTreeElement: function(title, node)
  817. {
  818. var treeElement = new WebInspector.NavigatorFolderTreeElement(this._type, title);
  819. treeElement._node = node;
  820. return treeElement;
  821. },
  822. wasPopulated: function()
  823. {
  824. if (!this._treeElement || this._treeElement._node !== this)
  825. return;
  826. this._addChildrenRecursive();
  827. },
  828. _addChildrenRecursive: function()
  829. {
  830. for (var id in this._children) {
  831. var child = this._children[id];
  832. this.didAddChild(child);
  833. if (child instanceof WebInspector.NavigatorFolderTreeNode)
  834. child._addChildrenRecursive();
  835. }
  836. },
  837. _shouldMerge: function(node)
  838. {
  839. return this._type !== WebInspector.NavigatorTreeOutline.Types.Domain && node instanceof WebInspector.NavigatorFolderTreeNode;
  840. },
  841. didAddChild: function(node)
  842. {
  843. function titleForNode(node)
  844. {
  845. return node._title;
  846. }
  847. if (!this._treeElement)
  848. return;
  849. var children = this.children();
  850. if (children.length === 1 && this._shouldMerge(node)) {
  851. node._isMerged = true;
  852. this._treeElement.titleText = this._treeElement.titleText + "/" + node._title;
  853. node._treeElement = this._treeElement;
  854. this._treeElement._node = node;
  855. return;
  856. }
  857. var oldNode;
  858. if (children.length === 2)
  859. oldNode = children[0] !== node ? children[0] : children[1];
  860. if (oldNode && oldNode._isMerged) {
  861. delete oldNode._isMerged;
  862. var mergedToNodes = [];
  863. mergedToNodes.push(this);
  864. var treeNode = this;
  865. while (treeNode._isMerged) {
  866. treeNode = treeNode.parent;
  867. mergedToNodes.push(treeNode);
  868. }
  869. mergedToNodes.reverse();
  870. var titleText = mergedToNodes.map(titleForNode).join("/");
  871. var nodes = [];
  872. treeNode = oldNode;
  873. do {
  874. nodes.push(treeNode);
  875. children = treeNode.children();
  876. treeNode = children.length === 1 ? children[0] : null;
  877. } while (treeNode && treeNode._isMerged);
  878. if (!this.isPopulated()) {
  879. this._treeElement.titleText = titleText;
  880. this._treeElement._node = this;
  881. for (var i = 0; i < nodes.length; ++i) {
  882. delete nodes[i]._treeElement;
  883. delete nodes[i]._isMerged;
  884. }
  885. return;
  886. }
  887. var oldTreeElement = this._treeElement;
  888. var treeElement = this._createTreeElement(titleText, this);
  889. for (var i = 0; i < mergedToNodes.length; ++i)
  890. mergedToNodes[i]._treeElement = treeElement;
  891. oldTreeElement.parent.appendChild(treeElement);
  892. oldTreeElement._node = nodes[nodes.length - 1];
  893. oldTreeElement.titleText = nodes.map(titleForNode).join("/");
  894. oldTreeElement.parent.removeChild(oldTreeElement);
  895. this._treeElement.appendChild(oldTreeElement);
  896. if (oldTreeElement.expanded)
  897. treeElement.expand();
  898. }
  899. if (this.isPopulated())
  900. this._treeElement.appendChild(node.treeElement());
  901. },
  902. willRemoveChild: function(node)
  903. {
  904. if (node._isMerged || !this.isPopulated())
  905. return;
  906. this._treeElement.removeChild(node._treeElement);
  907. },
  908. __proto__: WebInspector.NavigatorTreeNode.prototype
  909. }