Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / sources / RevisionHistoryView.js
blobb2b8e6e56fe0bf4a741c339c1903dad10c26eabc
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  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
31 /**
32  * @constructor
33  * @extends {WebInspector.VBox}
34  */
35 WebInspector.RevisionHistoryView = function()
37     WebInspector.VBox.call(this);
38     this.registerRequiredCSS("sources/revisionHistory.css");
39     this.element.classList.add("revision-history-drawer");
40     this._uiSourceCodeItems = new Map();
42     this._treeOutline = new TreeOutline();
43     this._treeOutline.element.classList.add("outline-disclosure");
44     this.element.appendChild(this._treeOutline.element);
46     /**
47      * @param {!WebInspector.UISourceCode} uiSourceCode
48      * @this {WebInspector.RevisionHistoryView}
49      */
50     function populateRevisions(uiSourceCode)
51     {
52         if (uiSourceCode.history.length)
53             this._createUISourceCodeItem(uiSourceCode);
54     }
56     WebInspector.workspace.uiSourceCodes().forEach(populateRevisions.bind(this));
57     WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeContentCommitted, this._revisionAdded, this);
58     WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
59     WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.ProjectRemoved, this._projectRemoved, this);
62 /**
63  * @param {!WebInspector.UISourceCode} uiSourceCode
64  */
65 WebInspector.RevisionHistoryView.showHistory = function(uiSourceCode)
67     if (!WebInspector.RevisionHistoryView._view)
68         WebInspector.RevisionHistoryView._view = new WebInspector.RevisionHistoryView();
69     var view = WebInspector.RevisionHistoryView._view;
70     WebInspector.inspectorView.showCloseableViewInDrawer("history", WebInspector.UIString("History"), view);
71     view._revealUISourceCode(uiSourceCode);
74 WebInspector.RevisionHistoryView.prototype = {
75     /**
76      * @param {!WebInspector.UISourceCode} uiSourceCode
77      */
78     _createUISourceCodeItem: function(uiSourceCode)
79     {
80         var uiSourceCodeItem = new TreeElement(uiSourceCode.displayName(), true);
81         uiSourceCodeItem.selectable = false;
83         // Insert in sorted order
84         var rootElement = this._treeOutline.rootElement();
85         for (var i = 0; i < rootElement.childCount(); ++i) {
86             if (rootElement.childAt(i).title.localeCompare(uiSourceCode.displayName()) > 0) {
87                 rootElement.insertChild(uiSourceCodeItem, i);
88                 break;
89             }
90         }
91         if (i === rootElement.childCount())
92             rootElement.appendChild(uiSourceCodeItem);
94         this._uiSourceCodeItems.set(uiSourceCode, uiSourceCodeItem);
96         var revisionCount = uiSourceCode.history.length;
97         for (var i = revisionCount - 1; i >= 0; --i) {
98             var revision = uiSourceCode.history[i];
99             var historyItem = new WebInspector.RevisionHistoryTreeElement(revision, uiSourceCode.history[i - 1], i !== revisionCount - 1);
100             uiSourceCodeItem.appendChild(historyItem);
101         }
103         var linkItem = new TreeElement();
104         linkItem.selectable = false;
105         uiSourceCodeItem.appendChild(linkItem);
107         var revertToOriginal = linkItem.listItemElement.createChild("span", "revision-history-link revision-history-link-row");
108         revertToOriginal.textContent = WebInspector.UIString("apply original content");
109         revertToOriginal.addEventListener("click", this._revertToOriginal.bind(this, uiSourceCode));
111         var clearHistoryElement = uiSourceCodeItem.listItemElement.createChild("span", "revision-history-link");
112         clearHistoryElement.textContent = WebInspector.UIString("revert");
113         clearHistoryElement.addEventListener("click", this._clearHistory.bind(this, uiSourceCode));
114         return uiSourceCodeItem;
115     },
117     /**
118      * @param {!WebInspector.UISourceCode} uiSourceCode
119      */
120     _revertToOriginal: function(uiSourceCode)
121     {
122         uiSourceCode.revertToOriginal();
123     },
125     /**
126      * @param {!WebInspector.UISourceCode} uiSourceCode
127      */
128     _clearHistory: function(uiSourceCode)
129     {
130         uiSourceCode.revertAndClearHistory(this._removeUISourceCode.bind(this));
131     },
133     _revisionAdded: function(event)
134     {
135         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data.uiSourceCode);
136         var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode);
137         if (!uiSourceCodeItem) {
138             uiSourceCodeItem = this._createUISourceCodeItem(uiSourceCode);
139             return;
140         }
142         var historyLength = uiSourceCode.history.length;
143         var historyItem = new WebInspector.RevisionHistoryTreeElement(uiSourceCode.history[historyLength - 1], uiSourceCode.history[historyLength - 2], false);
144         if (uiSourceCodeItem.firstChild())
145             uiSourceCodeItem.firstChild().allowRevert();
146         uiSourceCodeItem.insertChild(historyItem, 0);
147     },
149     /**
150      * @param {!WebInspector.UISourceCode} uiSourceCode
151      */
152     _revealUISourceCode: function(uiSourceCode)
153     {
154         var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode);
155         if (uiSourceCodeItem) {
156             uiSourceCodeItem.reveal();
157             uiSourceCodeItem.expand();
158         }
159     },
161     _uiSourceCodeRemoved: function(event)
162     {
163         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
164         this._removeUISourceCode(uiSourceCode);
165     },
167     /**
168      * @param {!WebInspector.UISourceCode} uiSourceCode
169      */
170     _removeUISourceCode: function(uiSourceCode)
171     {
172         var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode);
173         if (!uiSourceCodeItem)
174             return;
175         this._treeOutline.removeChild(uiSourceCodeItem);
176         this._uiSourceCodeItems.remove(uiSourceCode);
177     },
179     _projectRemoved: function(event)
180     {
181         var project = event.data;
182         project.uiSourceCodes().forEach(this._removeUISourceCode.bind(this));
183     },
185     __proto__: WebInspector.VBox.prototype
189  * @constructor
190  * @extends {TreeElement}
191  * @param {!WebInspector.Revision} revision
192  * @param {!WebInspector.Revision} baseRevision
193  * @param {boolean} allowRevert
194  */
195 WebInspector.RevisionHistoryTreeElement = function(revision, baseRevision, allowRevert)
197     TreeElement.call(this, revision.timestamp.toLocaleTimeString(), true);
198     this.selectable = false;
200     this._revision = revision;
201     this._baseRevision = baseRevision;
203     this._revertElement = createElement("span");
204     this._revertElement.className = "revision-history-link";
205     this._revertElement.textContent = WebInspector.UIString("apply revision content");
206     this._revertElement.addEventListener("click", this._revision.revertToThis.bind(this._revision), false);
207     if (!allowRevert)
208         this._revertElement.classList.add("hidden");
211 WebInspector.RevisionHistoryTreeElement.prototype = {
212     onattach: function()
213     {
214         this.listItemElement.classList.add("revision-history-revision");
215     },
217     onpopulate: function()
218     {
219         this.listItemElement.appendChild(this._revertElement);
221         this.childrenListElement.classList.add("source-code");
222         if (this._baseRevision)
223             this._baseRevision.requestContent(step1.bind(this));
224         else
225             this._revision.uiSourceCode.requestOriginalContent(step1.bind(this));
227         /**
228          * @param {?string} baseContent
229          * @this {WebInspector.RevisionHistoryTreeElement}
230          */
231         function step1(baseContent)
232         {
233             this._revision.requestContent(step2.bind(this, baseContent));
234         }
236         /**
237          * @param {?string} baseContent
238          * @param {?string} newContent
239          * @this {WebInspector.RevisionHistoryTreeElement}
240          */
241         function step2(baseContent, newContent)
242         {
243             var baseLines = baseContent.split("\n");
244             var newLines = newContent.split("\n");
245             var opcodes = WebInspector.Diff.lineDiff(baseLines, newLines);
246             var lastWasSeparator = false;
248             var baseLineNumber = 0;
249             var newLineNumber = 0;
250             for (var idx = 0; idx < opcodes.length; idx++) {
251                 var code = opcodes[idx][0];
252                 var rowCount = opcodes[idx][1].length;
253                 if (code === WebInspector.Diff.Operation.Equal) {
254                     baseLineNumber += rowCount;
255                     newLineNumber += rowCount;
256                     if (!lastWasSeparator)
257                         this._createLine(null, null, "    \u2026", "separator");
258                     lastWasSeparator = true;
259                 } else if (code === WebInspector.Diff.Operation.Delete) {
260                     lastWasSeparator = false;
261                     for (var i = 0; i < rowCount; ++i)
262                         this._createLine(baseLineNumber + i, null, baseLines[baseLineNumber + i], "removed");
263                     baseLineNumber += rowCount;
264                 } else if (code === WebInspector.Diff.Operation.Insert) {
265                     lastWasSeparator = false;
266                     for (var i = 0; i < rowCount; ++i)
267                         this._createLine(null, newLineNumber + i, newLines[newLineNumber + i], "added");
268                     newLineNumber += rowCount;
269                 }
270             }
271         }
272     },
274     oncollapse: function()
275     {
276         this._revertElement.remove();
277     },
279     /**
280      * @param {?number} baseLineNumber
281      * @param {?number} newLineNumber
282      * @param {string} lineContent
283      * @param {string} changeType
284      */
285     _createLine: function(baseLineNumber, newLineNumber, lineContent, changeType)
286     {
287         var child = new TreeElement();
288         child.selectable = false;
289         this.appendChild(child);
291         function appendLineNumber(lineNumber)
292         {
293             var numberString = lineNumber !== null ? numberToStringWithSpacesPadding(lineNumber + 1, 4) : spacesPadding(4);
294             var lineNumberSpan = createElement("span");
295             lineNumberSpan.classList.add("webkit-line-number");
296             lineNumberSpan.textContent = numberString;
297             child.listItemElement.appendChild(lineNumberSpan);
298         }
300         appendLineNumber(baseLineNumber);
301         appendLineNumber(newLineNumber);
303         var contentSpan = createElement("span");
304         contentSpan.textContent = lineContent;
305         child.listItemElement.appendChild(contentSpan);
306         child.listItemElement.classList.add("revision-history-line");
307         contentSpan.classList.add("revision-history-line-" + changeType);
308     },
310     allowRevert: function()
311     {
312         this._revertElement.classList.remove("hidden");
313     },
315     __proto__: TreeElement.prototype