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