tizen beta release
[profile/ivi/webkit-efl.git] / debian / libwebkit-engine / usr / share / ewebkit-0 / webinspector / SourceFrame.js
1 /*
2  * Copyright (C) 2011 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  */
30
31 /**
32  * @extends {WebInspector.View}
33  * @constructor
34  */
35 WebInspector.SourceFrame = function(url)
36 {
37     WebInspector.View.call(this);
38     this.element.addStyleClass("script-view");
39
40     this._url = url;
41
42     this._textModel = new WebInspector.TextEditorModel();
43
44     var textViewerDelegate = new WebInspector.TextViewerDelegateForSourceFrame(this);
45     this._textViewer = new WebInspector.TextViewer(this._textModel, WebInspector.platform(), this._url, textViewerDelegate);
46
47     this._editButton = new WebInspector.StatusBarButton(WebInspector.UIString("Edit"), "edit-source-status-bar-item");
48     this._editButton.addEventListener("click", this._editButtonClicked.bind(this), this);
49
50     this._currentSearchResultIndex = -1;
51     this._searchResults = [];
52
53     this._messages = [];
54     this._rowMessages = {};
55     this._messageBubbles = {};
56 }
57
58 WebInspector.SourceFrame.Events = {
59     Loaded: "loaded"
60 }
61
62 WebInspector.SourceFrame.createSearchRegex = function(query)
63 {
64     var regex;
65
66     // First try creating regex if user knows the / / hint.
67     try {
68         if (/^\/.*\/$/.test(query))
69             regex = new RegExp(query.substring(1, query.length - 1));
70     } catch (e) {
71         // Silent catch.
72     }
73
74     // Otherwise just do case-insensitive search.
75     if (!regex)
76         regex = createPlainTextSearchRegex(query, "i");
77
78     return regex;
79 }
80
81 WebInspector.SourceFrame.prototype = {
82     wasShown: function()
83     {
84         this._ensureContentLoaded();
85         this._textViewer.show(this.element);
86         if (this._wasHiddenWhileEditing)
87             this.setReadOnly(false);
88     },
89
90     willHide: function()
91     {
92         WebInspector.View.prototype.willHide.call(this);
93         if (this.loaded)
94             this._textViewer.freeCachedElements();
95
96         this._clearLineHighlight();
97         if (!this._textViewer.readOnly)
98             this._wasHiddenWhileEditing = true;
99         this.setReadOnly(true);
100     },
101
102     focus: function()
103     {
104         this._textViewer.focus();
105     },
106
107     get statusBarItems()
108     {
109         return [this._editButton.element];
110     },
111
112     get loaded()
113     {
114         return this._loaded;
115     },
116
117     hasContent: function()
118     {
119         return true;
120     },
121
122     get textViewer()
123     {
124         return this._textViewer;
125     },
126
127     _ensureContentLoaded: function()
128     {
129         if (!this._contentRequested) {
130             this._contentRequested = true;
131             this.requestContent(this.setContent.bind(this));
132         }
133     },
134
135     requestContent: function(callback)
136     {
137     },
138
139     /**
140      * @param {TextDiff} diffData
141      */
142     markDiff: function(diffData)
143     {
144         if (this._diffLines && this.loaded)
145             this._removeDiffDecorations();
146
147         this._diffLines = diffData;
148         if (this.loaded)
149             this._updateDiffDecorations();
150     },
151
152     addMessage: function(msg)
153     {
154         this._messages.push(msg);
155         if (this.loaded)
156             this.addMessageToSource(msg.line - 1, msg);
157     },
158
159     clearMessages: function()
160     {
161         for (var line in this._messageBubbles) {
162             var bubble = this._messageBubbles[line];
163             bubble.parentNode.removeChild(bubble);
164         }
165
166         this._messages = [];
167         this._rowMessages = {};
168         this._messageBubbles = {};
169
170         this._textViewer.doResize();
171     },
172
173     get textModel()
174     {
175         return this._textModel;
176     },
177
178     canHighlightLine: function(line)
179     {
180         return true;
181     },
182
183     highlightLine: function(line)
184     {
185         if (this.loaded)
186             this._textViewer.highlightLine(line);
187         else
188             this._lineToHighlight = line;
189     },
190
191     _clearLineHighlight: function()
192     {
193         if (this.loaded)
194             this._textViewer.clearLineHighlight();
195         else
196             delete this._lineToHighlight;
197     },
198
199     _saveViewerState: function()
200     {
201         this._viewerState = {
202             textModelContent: this._textModel.text,
203             messages: this._messages,
204             diffLines: this._diffLines,
205         };
206     },
207
208     _restoreViewerState: function()
209     {
210         if (!this._viewerState)
211             return;
212         this._textModel.setText(null, this._viewerState.textModelContent);
213
214         this._messages = this._viewerState.messages;
215         this._diffLines = this._viewerState.diffLines;
216         this._setTextViewerDecorations();
217
218         delete this._viewerState;
219     },
220
221     beforeTextChanged: function()
222     {
223         if (!this._viewerState)
224             this._saveViewerState();
225
226         WebInspector.searchController.cancelSearch();
227         this.clearMessages();
228     },
229
230     afterTextChanged: function(oldRange, newRange)
231     {
232     },
233
234     setContent: function(mimeType, content)
235     {
236         this._textViewer.mimeType = mimeType;
237
238         this._loaded = true;
239         this._textModel.setText(null, content);
240
241         this._textViewer.beginUpdates();
242
243         this._setTextViewerDecorations();
244
245         if (typeof this._lineToHighlight === "number") {
246             this.highlightLine(this._lineToHighlight);
247             delete this._lineToHighlight;
248         }
249
250         if (this._delayedFindSearchMatches) {
251             this._delayedFindSearchMatches();
252             delete this._delayedFindSearchMatches;
253         }
254
255         this.dispatchEventToListeners(WebInspector.SourceFrame.Events.Loaded);
256
257         this._textViewer.endUpdates();
258
259         if (!this.canEditSource())
260             this._editButton.disabled = true;
261     },
262
263     _setTextViewerDecorations: function()
264     {
265         this._rowMessages = {};
266         this._messageBubbles = {};
267
268         this._textViewer.beginUpdates();
269
270         this._addExistingMessagesToSource();
271         this._updateDiffDecorations();
272
273         this._textViewer.doResize();
274
275         this._textViewer.endUpdates();
276     },
277
278     performSearch: function(query, callback)
279     {
280         // Call searchCanceled since it will reset everything we need before doing a new search.
281         this.searchCanceled();
282
283         function doFindSearchMatches(query)
284         {
285             this._currentSearchResultIndex = -1;
286             this._searchResults = [];
287
288             var regex = WebInspector.SourceFrame.createSearchRegex(query);
289             this._searchResults = this._collectRegexMatches(regex);
290
291             callback(this, this._searchResults.length);
292         }
293
294         if (this.loaded)
295             doFindSearchMatches.call(this, query);
296         else
297             this._delayedFindSearchMatches = doFindSearchMatches.bind(this, query);
298
299         this._ensureContentLoaded();
300     },
301
302     searchCanceled: function()
303     {
304         delete this._delayedFindSearchMatches;
305         if (!this.loaded)
306             return;
307
308         this._currentSearchResultIndex = -1;
309         this._searchResults = [];
310         this._textViewer.markAndRevealRange(null);
311     },
312
313     hasSearchResults: function()
314     {
315         return this._searchResults.length > 0;
316     },
317
318     jumpToFirstSearchResult: function()
319     {
320         this.jumpToSearchResult(0);
321     },
322
323     jumpToLastSearchResult: function()
324     {
325         this.jumpToSearchResult(this._searchResults.length - 1);
326     },
327
328     jumpToNextSearchResult: function()
329     {
330         this.jumpToSearchResult(this._currentSearchResultIndex + 1);
331     },
332
333     jumpToPreviousSearchResult: function()
334     {
335         this.jumpToSearchResult(this._currentSearchResultIndex - 1);
336     },
337
338     showingFirstSearchResult: function()
339     {
340         return this._searchResults.length &&  this._currentSearchResultIndex === 0;
341     },
342
343     showingLastSearchResult: function()
344     {
345         return this._searchResults.length && this._currentSearchResultIndex === (this._searchResults.length - 1);
346     },
347
348     get currentSearchResultIndex()
349     {
350         return this._currentSearchResultIndex;
351     },
352
353     jumpToSearchResult: function(index)
354     {
355         if (!this.loaded || !this._searchResults.length)
356             return;
357         this._currentSearchResultIndex = (index + this._searchResults.length) % this._searchResults.length;
358         this._textViewer.markAndRevealRange(this._searchResults[this._currentSearchResultIndex]);
359     },
360
361     _collectRegexMatches: function(regexObject)
362     {
363         var ranges = [];
364         for (var i = 0; i < this._textModel.linesCount; ++i) {
365             var line = this._textModel.line(i);
366             var offset = 0;
367             do {
368                 var match = regexObject.exec(line);
369                 if (match) {
370                     if (match[0].length)
371                         ranges.push(new WebInspector.TextRange(i, offset + match.index, i, offset + match.index + match[0].length));
372                     offset += match.index + 1;
373                     line = line.substring(match.index + 1);
374                 }
375             } while (match && line);
376         }
377         return ranges;
378     },
379
380     _updateDiffDecorations: function()
381     {
382         if (!this._diffLines)
383             return;
384
385         function addDecorations(textViewer, lines, className)
386         {
387             for (var i = 0; i < lines.length; ++i)
388                 textViewer.addDecoration(lines[i], className);
389         }
390         addDecorations(this._textViewer, this._diffLines.added, "webkit-added-line");
391         addDecorations(this._textViewer, this._diffLines.removed, "webkit-removed-line");
392         addDecorations(this._textViewer, this._diffLines.changed, "webkit-changed-line");
393     },
394
395     _removeDiffDecorations: function()
396     {
397         function removeDecorations(textViewer, lines, className)
398         {
399             for (var i = 0; i < lines.length; ++i)
400                 textViewer.removeDecoration(lines[i], className);
401         }
402         removeDecorations(this._textViewer, this._diffLines.added, "webkit-added-line");
403         removeDecorations(this._textViewer, this._diffLines.removed, "webkit-removed-line");
404         removeDecorations(this._textViewer, this._diffLines.changed, "webkit-changed-line");
405     },
406
407     _addExistingMessagesToSource: function()
408     {
409         var length = this._messages.length;
410         for (var i = 0; i < length; ++i)
411             this.addMessageToSource(this._messages[i].line - 1, this._messages[i]);
412     },
413
414     addMessageToSource: function(lineNumber, msg)
415     {
416         if (lineNumber >= this._textModel.linesCount)
417             lineNumber = this._textModel.linesCount - 1;
418         if (lineNumber < 0)
419             lineNumber = 0;
420
421         var messageBubbleElement = this._messageBubbles[lineNumber];
422         if (!messageBubbleElement || messageBubbleElement.nodeType !== Node.ELEMENT_NODE || !messageBubbleElement.hasStyleClass("webkit-html-message-bubble")) {
423             messageBubbleElement = document.createElement("div");
424             messageBubbleElement.className = "webkit-html-message-bubble";
425             this._messageBubbles[lineNumber] = messageBubbleElement;
426             this._textViewer.addDecoration(lineNumber, messageBubbleElement);
427         }
428
429         var rowMessages = this._rowMessages[lineNumber];
430         if (!rowMessages) {
431             rowMessages = [];
432             this._rowMessages[lineNumber] = rowMessages;
433         }
434
435         for (var i = 0; i < rowMessages.length; ++i) {
436             if (rowMessages[i].consoleMessage.isEqual(msg)) {
437                 rowMessages[i].repeatCount = msg.totalRepeatCount;
438                 this._updateMessageRepeatCount(rowMessages[i]);
439                 return;
440             }
441         }
442
443         var rowMessage = { consoleMessage: msg };
444         rowMessages.push(rowMessage);
445
446         var imageURL;
447         switch (msg.level) {
448             case WebInspector.ConsoleMessage.MessageLevel.Error:
449                 messageBubbleElement.addStyleClass("webkit-html-error-message");
450                 imageURL = "Images/errorIcon.png";
451                 break;
452             case WebInspector.ConsoleMessage.MessageLevel.Warning:
453                 messageBubbleElement.addStyleClass("webkit-html-warning-message");
454                 imageURL = "Images/warningIcon.png";
455                 break;
456         }
457
458         var messageLineElement = document.createElement("div");
459         messageLineElement.className = "webkit-html-message-line";
460         messageBubbleElement.appendChild(messageLineElement);
461
462         // Create the image element in the Inspector's document so we can use relative image URLs.
463         var image = document.createElement("img");
464         image.src = imageURL;
465         image.className = "webkit-html-message-icon";
466         messageLineElement.appendChild(image);
467         messageLineElement.appendChild(document.createTextNode(msg.message));
468
469         rowMessage.element = messageLineElement;
470         rowMessage.repeatCount = msg.totalRepeatCount;
471         this._updateMessageRepeatCount(rowMessage);
472     },
473
474     _updateMessageRepeatCount: function(rowMessage)
475     {
476         if (rowMessage.repeatCount < 2)
477             return;
478
479         if (!rowMessage.repeatCountElement) {
480             var repeatCountElement = document.createElement("span");
481             rowMessage.element.appendChild(repeatCountElement);
482             rowMessage.repeatCountElement = repeatCountElement;
483         }
484
485         rowMessage.repeatCountElement.textContent = WebInspector.UIString(" (repeated %d times)", rowMessage.repeatCount);
486     },
487
488     populateLineGutterContextMenu: function(lineNumber, contextMenu)
489     {
490     },
491
492     populateTextAreaContextMenu: function(contextMenu)
493     {
494         if (!window.getSelection().isCollapsed)
495             return;
496         WebInspector.populateResourceContextMenu(contextMenu, this._url);
497     },
498
499     suggestedFileName: function()
500     {
501     },
502
503     inheritScrollPositions: function(sourceFrame)
504     {
505         this._textViewer.inheritScrollPositions(sourceFrame._textViewer);
506     },
507
508     _editButtonClicked: function()
509     {
510         if (!this.canEditSource())
511             return;
512
513         const shouldStartEditing = !this._editButton.toggled;
514         if (shouldStartEditing)
515             this.startEditing();
516         else
517             this.commitEditing();
518     },
519
520     canEditSource: function()
521     {
522         return false;
523     },
524
525     startEditing: function()
526     {
527         if (!this.canEditSource())
528             return false;
529
530         if (this._commitEditingInProgress)
531             return false;
532
533         this.setReadOnly(false);
534         return true;
535     },
536
537     commitEditing: function()
538     {
539         if (!this._viewerState) {
540             // No editing was actually done.
541             this.setReadOnly(true);
542             return;
543         }
544
545         this._commitEditingInProgress = true;
546         this._textViewer.readOnly = true;
547         this._editButton.toggled = false;
548         this.editContent(this._textModel.text, this.didEditContent.bind(this));
549     },
550
551     didEditContent: function(error)
552     {
553         this._commitEditingInProgress = false;
554         this._textViewer.readOnly = false;
555
556         if (error) {
557             if (error.message)
558                 WebInspector.log(error.message, WebInspector.ConsoleMessage.MessageLevel.Error, true);
559             return;
560         }
561
562         delete this._viewerState;
563     },
564
565     editContent: function(newContent, callback)
566     {
567     },
568
569     cancelEditing: function()
570     {
571         this._restoreViewerState();
572         this.setReadOnly(true);
573     },
574
575     get readOnly()
576     {
577         return this._textViewer.readOnly;
578     },
579
580     setReadOnly: function(readOnly)
581     {
582         this._textViewer.readOnly = readOnly;
583         this._editButton.toggled = !readOnly;
584         WebInspector.markBeingEdited(this._textViewer.element, !readOnly);
585     }
586 }
587
588 WebInspector.SourceFrame.prototype.__proto__ = WebInspector.View.prototype;
589
590
591 /**
592  * @implements {WebInspector.TextViewerDelegate}
593  * @constructor
594  */
595 WebInspector.TextViewerDelegateForSourceFrame = function(sourceFrame)
596 {
597     this._sourceFrame = sourceFrame;
598 }
599
600 WebInspector.TextViewerDelegateForSourceFrame.prototype = {
601     doubleClick: function(lineNumber)
602     {
603         this._sourceFrame.startEditing(lineNumber);
604     },
605
606     beforeTextChanged: function()
607     {
608         this._sourceFrame.beforeTextChanged();
609     },
610
611     afterTextChanged: function(oldRange, newRange)
612     {
613         this._sourceFrame.afterTextChanged(oldRange, newRange);
614     },
615
616     commitEditing: function()
617     {
618         this._sourceFrame.commitEditing();
619     },
620
621     cancelEditing: function()
622     {
623         this._sourceFrame.cancelEditing();
624     },
625
626     populateLineGutterContextMenu: function(lineNumber, contextMenu)
627     {
628         this._sourceFrame.populateLineGutterContextMenu(lineNumber, contextMenu);
629     },
630
631     populateTextAreaContextMenu: function(contextMenu)
632     {
633         this._sourceFrame.populateTextAreaContextMenu(contextMenu);
634     },
635
636     suggestedFileName: function()
637     {
638         return this._sourceFrame.suggestedFileName();
639     }
640 }
641
642 WebInspector.TextViewerDelegateForSourceFrame.prototype.__proto__ = WebInspector.TextViewerDelegate.prototype;