Home | History | Annotate | Download | only in sources
      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 /**
     30  * @constructor
     31  * @extends {WebInspector.VBox}
     32  */
     33 WebInspector.NavigatorView = function()
     34 {
     35     WebInspector.VBox.call(this);
     36     this.registerRequiredCSS("navigatorView.css");
     37 
     38     this.element.classList.add("navigator-container");
     39     var scriptsOutlineElement = this.element.createChild("div", "outline-disclosure navigator");
     40     var scriptsTreeElement = scriptsOutlineElement.createChild("ol");
     41     this._scriptsTree = new WebInspector.NavigatorTreeOutline(scriptsTreeElement);
     42 
     43     this.setDefaultFocusedElement(this._scriptsTree.element);
     44 
     45     /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.NavigatorUISourceCodeTreeNode>} */
     46     this._uiSourceCodeNodes = new Map();
     47     /** @type {!Map.<!WebInspector.NavigatorTreeNode, !StringMap.<!WebInspector.NavigatorFolderTreeNode>>} */
     48     this._subfolderNodes = new Map();
     49 
     50     this._rootNode = new WebInspector.NavigatorRootTreeNode(this);
     51     this._rootNode.populate();
     52 
     53     this.element.addEventListener("contextmenu", this.handleContextMenu.bind(this), false);
     54 }
     55 
     56 WebInspector.NavigatorView.Events = {
     57     ItemSelected: "ItemSelected",
     58     ItemRenamed: "ItemRenamed",
     59 }
     60 
     61 /**
     62  * @param {string} type
     63  * @return {string}
     64  */
     65 WebInspector.NavigatorView.iconClassForType = function(type)
     66 {
     67     if (type === WebInspector.NavigatorTreeOutline.Types.Domain)
     68         return "navigator-domain-tree-item";
     69     if (type === WebInspector.NavigatorTreeOutline.Types.FileSystem)
     70         return "navigator-folder-tree-item";
     71     return "navigator-folder-tree-item";
     72 }
     73 
     74 WebInspector.NavigatorView.prototype = {
     75     setWorkspace: function(workspace)
     76     {
     77         this._workspace = workspace;
     78         this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this);
     79         this._workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
     80         this._workspace.addEventListener(WebInspector.Workspace.Events.ProjectRemoved, this._projectRemoved.bind(this), this);
     81     },
     82 
     83     wasShown: function()
     84     {
     85         if (this._loaded)
     86             return;
     87         this._loaded = true;
     88         this._workspace.uiSourceCodes().forEach(this._addUISourceCode.bind(this));
     89     },
     90 
     91     /**
     92      * @param {!WebInspector.UISourceCode} uiSourceCode
     93      * @return {boolean}
     94      */
     95     accept: function(uiSourceCode)
     96     {
     97         return !uiSourceCode.project().isServiceProject();
     98     },
     99 
    100     /**
    101      * @param {!WebInspector.UISourceCode} uiSourceCode
    102      */
    103     _addUISourceCode: function(uiSourceCode)
    104     {
    105         if (!this.accept(uiSourceCode))
    106             return;
    107         var projectNode = this._projectNode(uiSourceCode.project());
    108         var folderNode = this._folderNode(projectNode, uiSourceCode.parentPath());
    109         var uiSourceCodeNode = new WebInspector.NavigatorUISourceCodeTreeNode(this, uiSourceCode);
    110         this._uiSourceCodeNodes.set(uiSourceCode, uiSourceCodeNode);
    111         folderNode.appendChild(uiSourceCodeNode);
    112     },
    113 
    114     /**
    115      * @param {!WebInspector.Event} event
    116      */
    117     _uiSourceCodeAdded: function(event)
    118     {
    119         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
    120         this._addUISourceCode(uiSourceCode);
    121     },
    122 
    123     /**
    124      * @param {!WebInspector.Event} event
    125      */
    126     _uiSourceCodeRemoved: function(event)
    127     {
    128         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
    129         this._removeUISourceCode(uiSourceCode);
    130     },
    131 
    132     /**
    133      * @param {!WebInspector.Event} event
    134      */
    135     _projectRemoved: function(event)
    136     {
    137         var project = /** @type {!WebInspector.Project} */ (event.data);
    138         project.removeEventListener(WebInspector.Project.Events.DisplayNameUpdated, this._updateProjectNodeTitle, this);
    139         var uiSourceCodes = project.uiSourceCodes();
    140         for (var i = 0; i < uiSourceCodes.length; ++i)
    141             this._removeUISourceCode(uiSourceCodes[i]);
    142     },
    143 
    144     /**
    145      * @param {!WebInspector.Project} project
    146      * @return {!WebInspector.NavigatorTreeNode}
    147      */
    148     _projectNode: function(project)
    149     {
    150         if (!project.displayName())
    151             return this._rootNode;
    152 
    153         var projectNode = this._rootNode.child(project.id());
    154         if (!projectNode) {
    155             projectNode = this._createProjectNode(project);
    156             this._rootNode.appendChild(projectNode);
    157         }
    158         return projectNode;
    159     },
    160 
    161     /**
    162      * @param {!WebInspector.Project} project
    163      * @return {!WebInspector.NavigatorTreeNode}
    164      */
    165     _createProjectNode: function(project)
    166     {
    167         var type = project.type() === WebInspector.projectTypes.FileSystem ? WebInspector.NavigatorTreeOutline.Types.FileSystem : WebInspector.NavigatorTreeOutline.Types.Domain;
    168         var projectNode = new WebInspector.NavigatorFolderTreeNode(this, project, project.id(), type, "", project.displayName());
    169         project.addEventListener(WebInspector.Project.Events.DisplayNameUpdated, this._updateProjectNodeTitle, this);
    170         return projectNode;
    171     },
    172 
    173     /**
    174      * @param {!WebInspector.Event} event
    175      */
    176     _updateProjectNodeTitle: function(event)
    177     {
    178         var project = /** @type {!WebInspector.Project} */(event.target);
    179         var projectNode = this._rootNode.child(project.id());
    180         if (!projectNode)
    181             return;
    182         projectNode.treeElement().titleText = project.displayName();
    183     },
    184 
    185     /**
    186      * @param {!WebInspector.NavigatorTreeNode} projectNode
    187      * @param {string} folderPath
    188      * @return {!WebInspector.NavigatorTreeNode}
    189      */
    190     _folderNode: function(projectNode, folderPath)
    191     {
    192         if (!folderPath)
    193             return projectNode;
    194 
    195         var subfolderNodes = this._subfolderNodes.get(projectNode);
    196         if (!subfolderNodes) {
    197             subfolderNodes = /** @type {!StringMap.<!WebInspector.NavigatorFolderTreeNode>} */ (new StringMap());
    198             this._subfolderNodes.set(projectNode, subfolderNodes);
    199         }
    200 
    201         var folderNode = subfolderNodes.get(folderPath);
    202         if (folderNode)
    203             return folderNode;
    204 
    205         var parentNode = projectNode;
    206         var index = folderPath.lastIndexOf("/");
    207         if (index !== -1)
    208             parentNode = this._folderNode(projectNode, folderPath.substring(0, index));
    209 
    210         var name = folderPath.substring(index + 1);
    211         folderNode = new WebInspector.NavigatorFolderTreeNode(this, null, name, WebInspector.NavigatorTreeOutline.Types.Folder, folderPath, name);
    212         subfolderNodes.set(folderPath, folderNode);
    213         parentNode.appendChild(folderNode);
    214         return folderNode;
    215     },
    216 
    217     /**
    218      * @param {!WebInspector.UISourceCode} uiSourceCode
    219      * @param {boolean=} select
    220      */
    221     revealUISourceCode: function(uiSourceCode, select)
    222     {
    223         var node = this._uiSourceCodeNodes.get(uiSourceCode);
    224         if (!node)
    225             return;
    226         if (this._scriptsTree.selectedTreeElement)
    227             this._scriptsTree.selectedTreeElement.deselect();
    228         this._lastSelectedUISourceCode = uiSourceCode;
    229         node.reveal(select);
    230     },
    231 
    232     /**
    233      * @param {!WebInspector.UISourceCode} uiSourceCode
    234      * @param {boolean} focusSource
    235      */
    236     _sourceSelected: function(uiSourceCode, focusSource)
    237     {
    238         this._lastSelectedUISourceCode = uiSourceCode;
    239         var data = { uiSourceCode: uiSourceCode, focusSource: focusSource};
    240         this.dispatchEventToListeners(WebInspector.NavigatorView.Events.ItemSelected, data);
    241     },
    242 
    243     /**
    244      * @param {!WebInspector.UISourceCode} uiSourceCode
    245      */
    246     sourceDeleted: function(uiSourceCode)
    247     {
    248     },
    249 
    250     /**
    251      * @param {!WebInspector.UISourceCode} uiSourceCode
    252      */
    253     _removeUISourceCode: function(uiSourceCode)
    254     {
    255         var node = this._uiSourceCodeNodes.get(uiSourceCode);
    256         if (!node)
    257             return;
    258 
    259         var projectNode = this._projectNode(uiSourceCode.project());
    260         var subfolderNodes = this._subfolderNodes.get(projectNode);
    261         var parentNode = node.parent;
    262         this._uiSourceCodeNodes.remove(uiSourceCode);
    263         parentNode.removeChild(node);
    264         node = parentNode;
    265 
    266         while (node) {
    267             parentNode = node.parent;
    268             if (!parentNode || !node.isEmpty())
    269                 break;
    270             if (subfolderNodes)
    271                 subfolderNodes.remove(node._folderPath);
    272             parentNode.removeChild(node);
    273             node = parentNode;
    274         }
    275     },
    276 
    277     /**
    278      * @param {!WebInspector.UISourceCode} uiSourceCode
    279      */
    280     _updateIcon: function(uiSourceCode)
    281     {
    282         var node = this._uiSourceCodeNodes.get(uiSourceCode);
    283         node.updateIcon();
    284     },
    285 
    286     reset: function()
    287     {
    288         var nodes = this._uiSourceCodeNodes.values();
    289         for (var i = 0; i < nodes.length; ++i)
    290             nodes[i].dispose();
    291 
    292         this._scriptsTree.removeChildren();
    293         this._uiSourceCodeNodes.clear();
    294         this._subfolderNodes.clear();
    295         this._rootNode.reset();
    296     },
    297 
    298     /**
    299      * @param {!Event} event
    300      */
    301     handleContextMenu: function(event)
    302     {
    303         var contextMenu = new WebInspector.ContextMenu(event);
    304         this._appendAddFolderItem(contextMenu);
    305         contextMenu.show();
    306     },
    307 
    308     /**
    309      * @param {!WebInspector.ContextMenu} contextMenu
    310      */
    311     _appendAddFolderItem: function(contextMenu)
    312     {
    313         function addFolder()
    314         {
    315             WebInspector.isolatedFileSystemManager.addFileSystem();
    316         }
    317 
    318         var addFolderLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add folder to workspace" : "Add Folder to Workspace");
    319         contextMenu.appendItem(addFolderLabel, addFolder);
    320     },
    321 
    322     /**
    323      * @param {!WebInspector.Project} project
    324      * @param {string} path
    325      */
    326     _handleContextMenuRefresh: function(project, path)
    327     {
    328         project.refresh(path);
    329     },
    330 
    331     /**
    332      * @param {!WebInspector.Project} project
    333      * @param {string} path
    334      * @param {!WebInspector.UISourceCode=} uiSourceCode
    335      */
    336     _handleContextMenuCreate: function(project, path, uiSourceCode)
    337     {
    338         this.create(project, path, uiSourceCode);
    339     },
    340 
    341     /**
    342      * @param {!WebInspector.UISourceCode} uiSourceCode
    343      */
    344     _handleContextMenuRename: function(uiSourceCode)
    345     {
    346         this.rename(uiSourceCode, false);
    347     },
    348 
    349     /**
    350      * @param {!WebInspector.Project} project
    351      * @param {string} path
    352      */
    353     _handleContextMenuExclude: function(project, path)
    354     {
    355         var shouldExclude = window.confirm(WebInspector.UIString("Are you sure you want to exclude this folder?"));
    356         if (shouldExclude) {
    357             WebInspector.startBatchUpdate();
    358             project.excludeFolder(path);
    359             WebInspector.endBatchUpdate();
    360         }
    361     },
    362 
    363     /**
    364      * @param {!WebInspector.UISourceCode} uiSourceCode
    365      */
    366     _handleContextMenuDelete: function(uiSourceCode)
    367     {
    368         var shouldDelete = window.confirm(WebInspector.UIString("Are you sure you want to delete this file?"));
    369         if (shouldDelete)
    370             uiSourceCode.project().deleteFile(uiSourceCode.path());
    371     },
    372 
    373     /**
    374      * @param {!Event} event
    375      * @param {!WebInspector.UISourceCode} uiSourceCode
    376      */
    377     handleFileContextMenu: function(event, uiSourceCode)
    378     {
    379         var contextMenu = new WebInspector.ContextMenu(event);
    380         contextMenu.appendApplicableItems(uiSourceCode);
    381         contextMenu.appendSeparator();
    382 
    383         var project = uiSourceCode.project();
    384         if (project.type() === WebInspector.projectTypes.FileSystem) {
    385             var path = uiSourceCode.parentPath();
    386             contextMenu.appendItem(WebInspector.UIString("Rename\u2026"), this._handleContextMenuRename.bind(this, uiSourceCode));
    387             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Make a copy\u2026" : "Make a Copy\u2026"), this._handleContextMenuCreate.bind(this, project, path, uiSourceCode));
    388             contextMenu.appendItem(WebInspector.UIString("Delete"), this._handleContextMenuDelete.bind(this, uiSourceCode));
    389             contextMenu.appendSeparator();
    390         }
    391 
    392         this._appendAddFolderItem(contextMenu);
    393         contextMenu.show();
    394     },
    395 
    396     /**
    397      * @param {!Event} event
    398      * @param {!WebInspector.NavigatorFolderTreeNode} node
    399      */
    400     handleFolderContextMenu: function(event, node)
    401     {
    402         var contextMenu = new WebInspector.ContextMenu(event);
    403         var path = "/";
    404         var projectNode = node;
    405         while (projectNode.parent !== this._rootNode) {
    406             path = "/" + projectNode.id + path;
    407             projectNode = projectNode.parent;
    408         }
    409 
    410         var project = projectNode._project;
    411 
    412         if (project.type() === WebInspector.projectTypes.FileSystem) {
    413             contextMenu.appendItem(WebInspector.UIString("Refresh"), this._handleContextMenuRefresh.bind(this, project, path));
    414             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "New file" : "New File"), this._handleContextMenuCreate.bind(this, project, path));
    415             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Exclude folder" : "Exclude Folder"), this._handleContextMenuExclude.bind(this, project, path));
    416         }
    417         contextMenu.appendSeparator();
    418         this._appendAddFolderItem(contextMenu);
    419 
    420         function removeFolder()
    421         {
    422             var shouldRemove = window.confirm(WebInspector.UIString("Are you sure you want to remove this folder?"));
    423             if (shouldRemove)
    424                 project.remove();
    425         }
    426 
    427         if (project.type() === WebInspector.projectTypes.FileSystem && node === projectNode) {
    428             var removeFolderLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove folder from workspace" : "Remove Folder from Workspace");
    429             contextMenu.appendItem(removeFolderLabel, removeFolder);
    430         }
    431 
    432         contextMenu.show();
    433     },
    434 
    435     /**
    436      * @param {!WebInspector.UISourceCode} uiSourceCode
    437      * @param {boolean} deleteIfCanceled
    438      */
    439     rename: function(uiSourceCode, deleteIfCanceled)
    440     {
    441         var node = this._uiSourceCodeNodes.get(uiSourceCode);
    442         console.assert(node);
    443         node.rename(callback.bind(this));
    444 
    445         /**
    446          * @this {WebInspector.NavigatorView}
    447          * @param {boolean} committed
    448          */
    449         function callback(committed)
    450         {
    451             if (!committed) {
    452                 if (deleteIfCanceled)
    453                     uiSourceCode.remove();
    454                 return;
    455             }
    456 
    457             this.dispatchEventToListeners(WebInspector.NavigatorView.Events.ItemRenamed, uiSourceCode);
    458             this._updateIcon(uiSourceCode);
    459             this._sourceSelected(uiSourceCode, true)
    460         }
    461     },
    462 
    463     /**
    464      * @param {!WebInspector.Project} project
    465      * @param {string} path
    466      * @param {!WebInspector.UISourceCode=} uiSourceCodeToCopy
    467      */
    468     create: function(project, path, uiSourceCodeToCopy)
    469     {
    470         var filePath;
    471         var uiSourceCode;
    472 
    473         /**
    474          * @this {WebInspector.NavigatorView}
    475          * @param {?string} content
    476          */
    477         function contentLoaded(content)
    478         {
    479             createFile.call(this, content || "");
    480         }
    481 
    482         if (uiSourceCodeToCopy)
    483             uiSourceCodeToCopy.requestContent(contentLoaded.bind(this));
    484         else
    485             createFile.call(this);
    486 
    487         /**
    488          * @this {WebInspector.NavigatorView}
    489          * @param {string=} content
    490          */
    491         function createFile(content)
    492         {
    493             project.createFile(path, null, content || "", fileCreated.bind(this));
    494         }
    495 
    496         /**
    497          * @this {WebInspector.NavigatorView}
    498          * @param {?string} path
    499          */
    500         function fileCreated(path)
    501         {
    502             if (!path)
    503                 return;
    504             filePath = path;
    505             uiSourceCode = project.uiSourceCode(filePath);
    506             if (!uiSourceCode) {
    507                 console.assert(uiSourceCode)
    508                 return;
    509             }
    510             this._sourceSelected(uiSourceCode, false);
    511             this.revealUISourceCode(uiSourceCode, true);
    512             this.rename(uiSourceCode, true);
    513         }
    514     },
    515 
    516     __proto__: WebInspector.VBox.prototype
    517 }
    518 
    519 /**
    520  * @constructor
    521  * @extends {WebInspector.NavigatorView}
    522  */
    523 WebInspector.SourcesNavigatorView = function()
    524 {
    525     WebInspector.NavigatorView.call(this);
    526     WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.InspectedURLChanged, this._inspectedURLChanged, this);
    527 }
    528 
    529 WebInspector.SourcesNavigatorView.prototype = {
    530     /**
    531      * @override
    532      * @param {!WebInspector.UISourceCode} uiSourceCode
    533      * @return {boolean}
    534      */
    535     accept: function(uiSourceCode)
    536     {
    537         if (!WebInspector.NavigatorView.prototype.accept(uiSourceCode))
    538             return false;
    539         return uiSourceCode.project().type() !== WebInspector.projectTypes.ContentScripts && uiSourceCode.project().type() !== WebInspector.projectTypes.Snippets;
    540 
    541     },
    542 
    543     /**
    544      * @param {!WebInspector.Event} event
    545      */
    546     _inspectedURLChanged: function(event)
    547     {
    548        var nodes = this._uiSourceCodeNodes.values();
    549        for (var i = 0; i < nodes.length; ++i) {
    550            var uiSourceCode = nodes[i].uiSourceCode();
    551            var inspectedPageURL = WebInspector.targetManager.inspectedPageURL();
    552            if (inspectedPageURL && uiSourceCode.url === inspectedPageURL)
    553               this.revealUISourceCode(uiSourceCode, true);
    554        }
    555     },
    556 
    557     /**
    558      * @param {!WebInspector.UISourceCode} uiSourceCode
    559      */
    560     _addUISourceCode: function(uiSourceCode)
    561     {
    562         WebInspector.NavigatorView.prototype._addUISourceCode.call(this, uiSourceCode);
    563         var inspectedPageURL = WebInspector.targetManager.inspectedPageURL();
    564         if (inspectedPageURL && uiSourceCode.url === inspectedPageURL)
    565             this.revealUISourceCode(uiSourceCode, true);
    566      },
    567 
    568     __proto__: WebInspector.NavigatorView.prototype
    569 }
    570 
    571 /**
    572  * @constructor
    573  * @extends {WebInspector.NavigatorView}
    574  */
    575 WebInspector.ContentScriptsNavigatorView = function()
    576 {
    577     WebInspector.NavigatorView.call(this);
    578 }
    579 
    580 WebInspector.ContentScriptsNavigatorView.prototype = {
    581     /**
    582      * @override
    583      * @param {!WebInspector.UISourceCode} uiSourceCode
    584      * @return {boolean}
    585      */
    586     accept: function(uiSourceCode)
    587     {
    588         if (!WebInspector.NavigatorView.prototype.accept(uiSourceCode))
    589             return false;
    590         return uiSourceCode.project().type() === WebInspector.projectTypes.ContentScripts;
    591     },
    592 
    593     __proto__: WebInspector.NavigatorView.prototype
    594 }
    595 
    596 /**
    597  * @constructor
    598  * @extends {TreeOutline}
    599  * @param {!Element} element
    600  */
    601 WebInspector.NavigatorTreeOutline = function(element)
    602 {
    603     TreeOutline.call(this, element);
    604     this.element = element;
    605 
    606     this.comparator = WebInspector.NavigatorTreeOutline._treeElementsCompare;
    607 }
    608 
    609 WebInspector.NavigatorTreeOutline.Types = {
    610     Root: "Root",
    611     Domain: "Domain",
    612     Folder: "Folder",
    613     UISourceCode: "UISourceCode",
    614     FileSystem: "FileSystem"
    615 }
    616 
    617 /**
    618  * @param {!TreeElement} treeElement1
    619  * @param {!TreeElement} treeElement2
    620  * @return {number}
    621  */
    622 WebInspector.NavigatorTreeOutline._treeElementsCompare = function compare(treeElement1, treeElement2)
    623 {
    624     // Insert in the alphabetical order, first domains, then folders, then scripts.
    625     function typeWeight(treeElement)
    626     {
    627         var type = treeElement.type();
    628         if (type === WebInspector.NavigatorTreeOutline.Types.Domain) {
    629             if (treeElement.titleText === WebInspector.targetManager.inspectedPageDomain())
    630                 return 1;
    631             return 2;
    632         }
    633         if (type === WebInspector.NavigatorTreeOutline.Types.FileSystem)
    634             return 3;
    635         if (type === WebInspector.NavigatorTreeOutline.Types.Folder)
    636             return 4;
    637         return 5;
    638     }
    639 
    640     var typeWeight1 = typeWeight(treeElement1);
    641     var typeWeight2 = typeWeight(treeElement2);
    642 
    643     var result;
    644     if (typeWeight1 > typeWeight2)
    645         result = 1;
    646     else if (typeWeight1 < typeWeight2)
    647         result = -1;
    648     else {
    649         var title1 = treeElement1.titleText;
    650         var title2 = treeElement2.titleText;
    651         result = title1.compareTo(title2);
    652     }
    653     return result;
    654 }
    655 
    656 WebInspector.NavigatorTreeOutline.prototype = {
    657    /**
    658     * @return {!Array.<!WebInspector.UISourceCode>}
    659     */
    660    scriptTreeElements: function()
    661    {
    662        var result = [];
    663        if (this.children.length) {
    664            for (var treeElement = this.children[0]; treeElement; treeElement = treeElement.traverseNextTreeElement(false, this, true)) {
    665                if (treeElement instanceof WebInspector.NavigatorSourceTreeElement)
    666                    result.push(treeElement.uiSourceCode);
    667            }
    668        }
    669        return result;
    670    },
    671 
    672     __proto__: TreeOutline.prototype
    673 }
    674 
    675 /**
    676  * @constructor
    677  * @extends {TreeElement}
    678  * @param {string} type
    679  * @param {string} title
    680  * @param {!Array.<string>} iconClasses
    681  * @param {boolean} hasChildren
    682  * @param {boolean=} noIcon
    683  */
    684 WebInspector.BaseNavigatorTreeElement = function(type, title, iconClasses, hasChildren, noIcon)
    685 {
    686     this._type = type;
    687     TreeElement.call(this, "", null, hasChildren);
    688     this._titleText = title;
    689     this._iconClasses = iconClasses;
    690     this._noIcon = noIcon;
    691 }
    692 
    693 WebInspector.BaseNavigatorTreeElement.prototype = {
    694     onattach: function()
    695     {
    696         this.listItemElement.removeChildren();
    697         if (this._iconClasses) {
    698             for (var i = 0; i < this._iconClasses.length; ++i)
    699                 this.listItemElement.classList.add(this._iconClasses[i]);
    700         }
    701 
    702         this.listItemElement.createChild("div", "selection");
    703 
    704         if (!this._noIcon)
    705             this.imageElement = this.listItemElement.createChild("img", "icon");
    706 
    707         this.titleElement = this.listItemElement.createChild("div", "base-navigator-tree-element-title");
    708         this.titleElement.textContent = this._titleText;
    709     },
    710 
    711     /**
    712      * @param {!Array.<string>} iconClasses
    713      */
    714     updateIconClasses: function(iconClasses)
    715     {
    716         for (var i = 0; i < this._iconClasses.length; ++i)
    717             this.listItemElement.classList.remove(this._iconClasses[i]);
    718         this._iconClasses = iconClasses;
    719         for (var i = 0; i < this._iconClasses.length; ++i)
    720             this.listItemElement.classList.add(this._iconClasses[i]);
    721     },
    722 
    723     onreveal: function()
    724     {
    725         if (this.listItemElement)
    726             this.listItemElement.scrollIntoViewIfNeeded(true);
    727     },
    728 
    729     /**
    730      * @return {string}
    731      */
    732     get titleText()
    733     {
    734         return this._titleText;
    735     },
    736 
    737     set titleText(titleText)
    738     {
    739         if (this._titleText === titleText)
    740             return;
    741         this._titleText = titleText || "";
    742         if (this.titleElement) {
    743             this.titleElement.textContent = this._titleText;
    744             this.titleElement.title = this._titleText;
    745         }
    746     },
    747 
    748     /**
    749      * @return {string}
    750      */
    751     type: function()
    752     {
    753         return this._type;
    754     },
    755 
    756     __proto__: TreeElement.prototype
    757 }
    758 
    759 /**
    760  * @constructor
    761  * @extends {WebInspector.BaseNavigatorTreeElement}
    762  * @param {!WebInspector.NavigatorView} navigatorView
    763  * @param {string} type
    764  * @param {string} title
    765  */
    766 WebInspector.NavigatorFolderTreeElement = function(navigatorView, type, title)
    767 {
    768     var iconClass = WebInspector.NavigatorView.iconClassForType(type);
    769     WebInspector.BaseNavigatorTreeElement.call(this, type, title, [iconClass], true);
    770     this._navigatorView = navigatorView;
    771 }
    772 
    773 WebInspector.NavigatorFolderTreeElement.prototype = {
    774     onpopulate: function()
    775     {
    776         this._node.populate();
    777     },
    778 
    779     onattach: function()
    780     {
    781         WebInspector.BaseNavigatorTreeElement.prototype.onattach.call(this);
    782         this.collapse();
    783         this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false);
    784     },
    785 
    786     /**
    787      * @param {!WebInspector.NavigatorFolderTreeNode} node
    788      */
    789     setNode: function(node)
    790     {
    791         this._node = node;
    792         var paths = [];
    793         while (node && !node.isRoot()) {
    794             paths.push(node._title);
    795             node = node.parent;
    796         }
    797         paths.reverse();
    798         this.tooltip = paths.join("/");
    799     },
    800 
    801     /**
    802      * @param {!Event} event
    803      */
    804     _handleContextMenuEvent: function(event)
    805     {
    806         if (!this._node)
    807             return;
    808         this.select();
    809         this._navigatorView.handleFolderContextMenu(event, this._node);
    810     },
    811 
    812     __proto__: WebInspector.BaseNavigatorTreeElement.prototype
    813 }
    814 
    815 /**
    816  * @constructor
    817  * @extends {WebInspector.BaseNavigatorTreeElement}
    818  * @param {!WebInspector.NavigatorView} navigatorView
    819  * @param {!WebInspector.UISourceCode} uiSourceCode
    820  * @param {string} title
    821  */
    822 WebInspector.NavigatorSourceTreeElement = function(navigatorView, uiSourceCode, title)
    823 {
    824     this._navigatorView = navigatorView;
    825     this._uiSourceCode = uiSourceCode;
    826     WebInspector.BaseNavigatorTreeElement.call(this, WebInspector.NavigatorTreeOutline.Types.UISourceCode, title, this._calculateIconClasses(), false);
    827     this.tooltip = uiSourceCode.originURL();
    828 }
    829 
    830 WebInspector.NavigatorSourceTreeElement.prototype = {
    831     /**
    832      * @return {!WebInspector.UISourceCode}
    833      */
    834     get uiSourceCode()
    835     {
    836         return this._uiSourceCode;
    837     },
    838 
    839     /**
    840      * @return {!Array.<string>}
    841      */
    842     _calculateIconClasses: function()
    843     {
    844         return ["navigator-" + this._uiSourceCode.contentType().name() + "-tree-item"];
    845     },
    846 
    847     updateIcon: function()
    848     {
    849         this.updateIconClasses(this._calculateIconClasses());
    850     },
    851 
    852     onattach: function()
    853     {
    854         WebInspector.BaseNavigatorTreeElement.prototype.onattach.call(this);
    855         this.listItemElement.draggable = true;
    856         this.listItemElement.addEventListener("click", this._onclick.bind(this), false);
    857         this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false);
    858         this.listItemElement.addEventListener("mousedown", this._onmousedown.bind(this), false);
    859         this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false);
    860     },
    861 
    862     _onmousedown: function(event)
    863     {
    864         if (event.which === 1) // Warm-up data for drag'n'drop
    865             this._uiSourceCode.requestContent(callback.bind(this));
    866         /**
    867          * @param {?string} content
    868          * @this {WebInspector.NavigatorSourceTreeElement}
    869          */
    870         function callback(content)
    871         {
    872             this._warmedUpContent = content;
    873         }
    874     },
    875 
    876     _shouldRenameOnMouseDown: function()
    877     {
    878         if (!this._uiSourceCode.canRename())
    879             return false;
    880         var isSelected = this === this.treeOutline.selectedTreeElement;
    881         var isFocused = this.treeOutline.childrenListElement.isSelfOrAncestor(document.activeElement);
    882         return isSelected && isFocused && !WebInspector.isBeingEdited(this.treeOutline.element);
    883     },
    884 
    885     selectOnMouseDown: function(event)
    886     {
    887         if (event.which !== 1 || !this._shouldRenameOnMouseDown()) {
    888             TreeElement.prototype.selectOnMouseDown.call(this, event);
    889             return;
    890         }
    891         setTimeout(rename.bind(this), 300);
    892 
    893         /**
    894          * @this {WebInspector.NavigatorSourceTreeElement}
    895          */
    896         function rename()
    897         {
    898             if (this._shouldRenameOnMouseDown())
    899                 this._navigatorView.rename(this.uiSourceCode, false);
    900         }
    901     },
    902 
    903     _ondragstart: function(event)
    904     {
    905         event.dataTransfer.setData("text/plain", this._warmedUpContent);
    906         event.dataTransfer.effectAllowed = "copy";
    907         return true;
    908     },
    909 
    910     /**
    911      * @return {boolean}
    912      */
    913     onspace: function()
    914     {
    915         this._navigatorView._sourceSelected(this.uiSourceCode, true);
    916         return true;
    917     },
    918 
    919     /**
    920      * @param {!Event} event
    921      */
    922     _onclick: function(event)
    923     {
    924         this._navigatorView._sourceSelected(this.uiSourceCode, false);
    925     },
    926 
    927     /**
    928      * @override
    929      * @return {boolean}
    930      */
    931     ondblclick: function(event)
    932     {
    933         var middleClick = event.button === 1;
    934         this._navigatorView._sourceSelected(this.uiSourceCode, !middleClick);
    935         return false;
    936     },
    937 
    938     /**
    939      * @override
    940      * @return {boolean}
    941      */
    942     onenter: function()
    943     {
    944         this._navigatorView._sourceSelected(this.uiSourceCode, true);
    945         return true;
    946     },
    947 
    948     /**
    949      * @override
    950      * @return {boolean}
    951      */
    952     ondelete: function()
    953     {
    954         this._navigatorView.sourceDeleted(this.uiSourceCode);
    955         return true;
    956     },
    957 
    958     /**
    959      * @param {!Event} event
    960      */
    961     _handleContextMenuEvent: function(event)
    962     {
    963         this.select();
    964         this._navigatorView.handleFileContextMenu(event, this._uiSourceCode);
    965     },
    966 
    967     __proto__: WebInspector.BaseNavigatorTreeElement.prototype
    968 }
    969 
    970 /**
    971  * @constructor
    972  * @param {string} id
    973  */
    974 WebInspector.NavigatorTreeNode = function(id)
    975 {
    976     this.id = id;
    977     /** @type {!StringMap.<!WebInspector.NavigatorTreeNode>} */
    978     this._children = new StringMap();
    979 }
    980 
    981 WebInspector.NavigatorTreeNode.prototype = {
    982     /**
    983      * @return {!TreeElement}
    984      */
    985     treeElement: function() { throw "Not implemented"; },
    986 
    987     dispose: function() { },
    988 
    989     /**
    990      * @return {boolean}
    991      */
    992     isRoot: function()
    993     {
    994         return false;
    995     },
    996 
    997     /**
    998      * @return {boolean}
    999      */
   1000     hasChildren: function()
   1001     {
   1002         return true;
   1003     },
   1004 
   1005     populate: function()
   1006     {
   1007         if (this.isPopulated())
   1008             return;
   1009         if (this.parent)
   1010             this.parent.populate();
   1011         this._populated = true;
   1012         this.wasPopulated();
   1013     },
   1014 
   1015     wasPopulated: function()
   1016     {
   1017         var children = this.children();
   1018         for (var i = 0; i < children.length; ++i)
   1019             this.treeElement().appendChild(children[i].treeElement());
   1020     },
   1021 
   1022     /**
   1023      * @param {!WebInspector.NavigatorTreeNode} node
   1024      */
   1025     didAddChild: function(node)
   1026     {
   1027         if (this.isPopulated())
   1028             this.treeElement().appendChild(node.treeElement());
   1029     },
   1030 
   1031     /**
   1032      * @param {!WebInspector.NavigatorTreeNode} node
   1033      */
   1034     willRemoveChild: function(node)
   1035     {
   1036         if (this.isPopulated())
   1037             this.treeElement().removeChild(node.treeElement());
   1038     },
   1039 
   1040     /**
   1041      * @return {boolean}
   1042      */
   1043     isPopulated: function()
   1044     {
   1045         return this._populated;
   1046     },
   1047 
   1048     /**
   1049      * @return {boolean}
   1050      */
   1051     isEmpty: function()
   1052     {
   1053         return !this._children.size;
   1054     },
   1055 
   1056     /**
   1057      * @param {string} id
   1058      * @return {?WebInspector.NavigatorTreeNode}
   1059      */
   1060     child: function(id)
   1061     {
   1062         return this._children.get(id) || null;
   1063     },
   1064 
   1065     /**
   1066      * @return {!Array.<!WebInspector.NavigatorTreeNode>}
   1067      */
   1068     children: function()
   1069     {
   1070         return this._children.values();
   1071     },
   1072 
   1073     /**
   1074      * @param {!WebInspector.NavigatorTreeNode} node
   1075      */
   1076     appendChild: function(node)
   1077     {
   1078         this._children.set(node.id, node);
   1079         node.parent = this;
   1080         this.didAddChild(node);
   1081     },
   1082 
   1083     /**
   1084      * @param {!WebInspector.NavigatorTreeNode} node
   1085      */
   1086     removeChild: function(node)
   1087     {
   1088         this.willRemoveChild(node);
   1089         this._children.remove(node.id);
   1090         delete node.parent;
   1091         node.dispose();
   1092     },
   1093 
   1094     reset: function()
   1095     {
   1096         this._children.clear();
   1097     }
   1098 }
   1099 
   1100 /**
   1101  * @constructor
   1102  * @extends {WebInspector.NavigatorTreeNode}
   1103  * @param {!WebInspector.NavigatorView} navigatorView
   1104  */
   1105 WebInspector.NavigatorRootTreeNode = function(navigatorView)
   1106 {
   1107     WebInspector.NavigatorTreeNode.call(this, "");
   1108     this._navigatorView = navigatorView;
   1109 }
   1110 
   1111 WebInspector.NavigatorRootTreeNode.prototype = {
   1112     /**
   1113      * @return {boolean}
   1114      */
   1115     isRoot: function()
   1116     {
   1117         return true;
   1118     },
   1119 
   1120     /**
   1121      * @return {!TreeOutline}
   1122      */
   1123     treeElement: function()
   1124     {
   1125         return this._navigatorView._scriptsTree;
   1126     },
   1127 
   1128     __proto__: WebInspector.NavigatorTreeNode.prototype
   1129 }
   1130 
   1131 /**
   1132  * @constructor
   1133  * @extends {WebInspector.NavigatorTreeNode}
   1134  * @param {!WebInspector.NavigatorView} navigatorView
   1135  * @param {!WebInspector.UISourceCode} uiSourceCode
   1136  */
   1137 WebInspector.NavigatorUISourceCodeTreeNode = function(navigatorView, uiSourceCode)
   1138 {
   1139     WebInspector.NavigatorTreeNode.call(this, uiSourceCode.name());
   1140     this._navigatorView = navigatorView;
   1141     this._uiSourceCode = uiSourceCode;
   1142     this._treeElement = null;
   1143 }
   1144 
   1145 WebInspector.NavigatorUISourceCodeTreeNode.prototype = {
   1146     /**
   1147      * @return {!WebInspector.UISourceCode}
   1148      */
   1149     uiSourceCode: function()
   1150     {
   1151         return this._uiSourceCode;
   1152     },
   1153 
   1154     updateIcon: function()
   1155     {
   1156         if (this._treeElement)
   1157             this._treeElement.updateIcon();
   1158     },
   1159 
   1160     /**
   1161      * @return {!TreeElement}
   1162      */
   1163     treeElement: function()
   1164     {
   1165         if (this._treeElement)
   1166             return this._treeElement;
   1167 
   1168         this._treeElement = new WebInspector.NavigatorSourceTreeElement(this._navigatorView, this._uiSourceCode, "");
   1169         this.updateTitle();
   1170 
   1171         this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._titleChanged, this);
   1172         this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
   1173         this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
   1174 
   1175         return this._treeElement;
   1176     },
   1177 
   1178     /**
   1179      * @param {boolean=} ignoreIsDirty
   1180      */
   1181     updateTitle: function(ignoreIsDirty)
   1182     {
   1183         if (!this._treeElement)
   1184             return;
   1185 
   1186         var titleText = this._uiSourceCode.displayName();
   1187         if (!ignoreIsDirty && (this._uiSourceCode.isDirty() || this._uiSourceCode.hasUnsavedCommittedChanges()))
   1188             titleText = "*" + titleText;
   1189         this._treeElement.titleText = titleText;
   1190     },
   1191 
   1192     /**
   1193      * @return {boolean}
   1194      */
   1195     hasChildren: function()
   1196     {
   1197         return false;
   1198     },
   1199 
   1200     dispose: function()
   1201     {
   1202         if (!this._treeElement)
   1203             return;
   1204         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._titleChanged, this);
   1205         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
   1206         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
   1207     },
   1208 
   1209     _titleChanged: function(event)
   1210     {
   1211         this.updateTitle();
   1212     },
   1213 
   1214     _workingCopyChanged: function(event)
   1215     {
   1216         this.updateTitle();
   1217     },
   1218 
   1219     _workingCopyCommitted: function(event)
   1220     {
   1221         this.updateTitle();
   1222     },
   1223 
   1224     /**
   1225      * @param {boolean=} select
   1226      */
   1227     reveal: function(select)
   1228     {
   1229         this.parent.populate();
   1230         this.parent.treeElement().expand();
   1231         this._treeElement.reveal();
   1232         if (select)
   1233             this._treeElement.select(true);
   1234     },
   1235 
   1236     /**
   1237      * @param {function(boolean)=} callback
   1238      */
   1239     rename: function(callback)
   1240     {
   1241         if (!this._treeElement)
   1242             return;
   1243 
   1244         // Tree outline should be marked as edited as well as the tree element to prevent search from starting.
   1245         var treeOutlineElement = this._treeElement.treeOutline.element;
   1246         WebInspector.markBeingEdited(treeOutlineElement, true);
   1247 
   1248         /**
   1249          * @param {!Element} element
   1250          * @param {string} newTitle
   1251          * @param {string} oldTitle
   1252          * @this {WebInspector.NavigatorUISourceCodeTreeNode}
   1253          */
   1254         function commitHandler(element, newTitle, oldTitle)
   1255         {
   1256             if (newTitle !== oldTitle) {
   1257                 this._treeElement.titleText = newTitle;
   1258                 this._uiSourceCode.rename(newTitle, renameCallback.bind(this));
   1259                 return;
   1260             }
   1261             afterEditing.call(this, true);
   1262         }
   1263 
   1264         /**
   1265          * @param {boolean} success
   1266          * @this {WebInspector.NavigatorUISourceCodeTreeNode}
   1267          */
   1268         function renameCallback(success)
   1269         {
   1270             if (!success) {
   1271                 WebInspector.markBeingEdited(treeOutlineElement, false);
   1272                 this.updateTitle();
   1273                 this.rename(callback);
   1274                 return;
   1275             }
   1276             afterEditing.call(this, true);
   1277         }
   1278 
   1279         /**
   1280          * @this {WebInspector.NavigatorUISourceCodeTreeNode}
   1281          */
   1282         function cancelHandler()
   1283         {
   1284             afterEditing.call(this, false);
   1285         }
   1286 
   1287         /**
   1288          * @param {boolean} committed
   1289          * @this {WebInspector.NavigatorUISourceCodeTreeNode}
   1290          */
   1291         function afterEditing(committed)
   1292         {
   1293             WebInspector.markBeingEdited(treeOutlineElement, false);
   1294             this.updateTitle();
   1295             this._treeElement.treeOutline.childrenListElement.focus();
   1296             if (callback)
   1297                 callback(committed);
   1298         }
   1299 
   1300         var editingConfig = new WebInspector.InplaceEditor.Config(commitHandler.bind(this), cancelHandler.bind(this));
   1301         this.updateTitle(true);
   1302         WebInspector.InplaceEditor.startEditing(this._treeElement.titleElement, editingConfig);
   1303         window.getSelection().setBaseAndExtent(this._treeElement.titleElement, 0, this._treeElement.titleElement, 1);
   1304     },
   1305 
   1306     __proto__: WebInspector.NavigatorTreeNode.prototype
   1307 }
   1308 
   1309 /**
   1310  * @constructor
   1311  * @extends {WebInspector.NavigatorTreeNode}
   1312  * @param {!WebInspector.NavigatorView} navigatorView
   1313  * @param {?WebInspector.Project} project
   1314  * @param {string} id
   1315  * @param {string} type
   1316  * @param {string} folderPath
   1317  * @param {string} title
   1318  */
   1319 WebInspector.NavigatorFolderTreeNode = function(navigatorView, project, id, type, folderPath, title)
   1320 {
   1321     WebInspector.NavigatorTreeNode.call(this, id);
   1322     this._navigatorView = navigatorView;
   1323     this._project = project;
   1324     this._type = type;
   1325     this._folderPath = folderPath;
   1326     this._title = title;
   1327 }
   1328 
   1329 WebInspector.NavigatorFolderTreeNode.prototype = {
   1330     /**
   1331      * @return {!TreeElement}
   1332      */
   1333     treeElement: function()
   1334     {
   1335         if (this._treeElement)
   1336             return this._treeElement;
   1337         this._treeElement = this._createTreeElement(this._title, this);
   1338         return this._treeElement;
   1339     },
   1340 
   1341     /**
   1342      * @return {!TreeElement}
   1343      */
   1344     _createTreeElement: function(title, node)
   1345     {
   1346         var treeElement = new WebInspector.NavigatorFolderTreeElement(this._navigatorView, this._type, title);
   1347         treeElement.setNode(node);
   1348         return treeElement;
   1349     },
   1350 
   1351     wasPopulated: function()
   1352     {
   1353         if (!this._treeElement || this._treeElement._node !== this)
   1354             return;
   1355         this._addChildrenRecursive();
   1356     },
   1357 
   1358     _addChildrenRecursive: function()
   1359     {
   1360         var children = this.children();
   1361         for (var i = 0; i < children.length; ++i) {
   1362             var child = children[i];
   1363             this.didAddChild(child);
   1364             if (child instanceof WebInspector.NavigatorFolderTreeNode)
   1365                 child._addChildrenRecursive();
   1366         }
   1367     },
   1368 
   1369     _shouldMerge: function(node)
   1370     {
   1371         return this._type !== WebInspector.NavigatorTreeOutline.Types.Domain && node instanceof WebInspector.NavigatorFolderTreeNode;
   1372     },
   1373 
   1374     didAddChild: function(node)
   1375     {
   1376         function titleForNode(node)
   1377         {
   1378             return node._title;
   1379         }
   1380 
   1381         if (!this._treeElement)
   1382             return;
   1383 
   1384         var children = this.children();
   1385 
   1386         if (children.length === 1 && this._shouldMerge(node)) {
   1387             node._isMerged = true;
   1388             this._treeElement.titleText = this._treeElement.titleText + "/" + node._title;
   1389             node._treeElement = this._treeElement;
   1390             this._treeElement.setNode(node);
   1391             return;
   1392         }
   1393 
   1394         var oldNode;
   1395         if (children.length === 2)
   1396             oldNode = children[0] !== node ? children[0] : children[1];
   1397         if (oldNode && oldNode._isMerged) {
   1398             delete oldNode._isMerged;
   1399             var mergedToNodes = [];
   1400             mergedToNodes.push(this);
   1401             var treeNode = this;
   1402             while (treeNode._isMerged) {
   1403                 treeNode = treeNode.parent;
   1404                 mergedToNodes.push(treeNode);
   1405             }
   1406             mergedToNodes.reverse();
   1407             var titleText = mergedToNodes.map(titleForNode).join("/");
   1408 
   1409             var nodes = [];
   1410             treeNode = oldNode;
   1411             do {
   1412                 nodes.push(treeNode);
   1413                 children = treeNode.children();
   1414                 treeNode = children.length === 1 ? children[0] : null;
   1415             } while (treeNode && treeNode._isMerged);
   1416 
   1417             if (!this.isPopulated()) {
   1418                 this._treeElement.titleText = titleText;
   1419                 this._treeElement.setNode(this);
   1420                 for (var i = 0; i < nodes.length; ++i) {
   1421                     delete nodes[i]._treeElement;
   1422                     delete nodes[i]._isMerged;
   1423                 }
   1424                 return;
   1425             }
   1426             var oldTreeElement = this._treeElement;
   1427             var treeElement = this._createTreeElement(titleText, this);
   1428             for (var i = 0; i < mergedToNodes.length; ++i)
   1429                 mergedToNodes[i]._treeElement = treeElement;
   1430             oldTreeElement.parent.appendChild(treeElement);
   1431 
   1432             oldTreeElement.setNode(nodes[nodes.length - 1]);
   1433             oldTreeElement.titleText = nodes.map(titleForNode).join("/");
   1434             oldTreeElement.parent.removeChild(oldTreeElement);
   1435             this._treeElement.appendChild(oldTreeElement);
   1436             if (oldTreeElement.expanded)
   1437                 treeElement.expand();
   1438         }
   1439         if (this.isPopulated())
   1440             this._treeElement.appendChild(node.treeElement());
   1441     },
   1442 
   1443     willRemoveChild: function(node)
   1444     {
   1445         if (node._isMerged || !this.isPopulated())
   1446             return;
   1447         this._treeElement.removeChild(node._treeElement);
   1448     },
   1449 
   1450     __proto__: WebInspector.NavigatorTreeNode.prototype
   1451 }
   1452