Web Inspector: further align front-end configurations: get rid of saveAsAvailable...
[WebKit-https.git] / Source / WebCore / inspector / front-end / UIUtils.js
1 /*
2  * Copyright (C) 2011 Google Inc.  All rights reserved.
3  * Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
4  * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com).
5  * Copyright (C) 2009 Joseph Pecoraro
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1.  Redistributions of source code must retain the above copyright
12  *     notice, this list of conditions and the following disclaimer.
13  * 2.  Redistributions in binary form must reproduce the above copyright
14  *     notice, this list of conditions and the following disclaimer in the
15  *     documentation and/or other materials provided with the distribution.
16  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
17  *     its contributors may be used to endorse or promote products derived
18  *     from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
21  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
24  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor)
33 {
34     if (WebInspector._elementDraggingEventListener || WebInspector._elementEndDraggingEventListener)
35         WebInspector.elementDragEnd(event);
36
37     if (element) {
38         // Install glass pane
39         if (WebInspector._elementDraggingGlassPane)
40             WebInspector._elementDraggingGlassPane.parentElement.removeChild(WebInspector._elementDraggingGlassPane);
41
42         var glassPane = document.createElement("div");
43         glassPane.style.cssText = "position:absolute;top:0;bottom:0;left:0;right:0;opacity:0;z-index:1";
44         glassPane.id = "glass-pane-for-drag";
45         element.ownerDocument.body.appendChild(glassPane);
46         WebInspector._elementDraggingGlassPane = glassPane;
47     }
48
49     WebInspector._elementDraggingEventListener = dividerDrag;
50     WebInspector._elementEndDraggingEventListener = elementDragEnd;
51
52     var targetDocument = event.target.ownerDocument;
53     targetDocument.addEventListener("mousemove", dividerDrag, true);
54     targetDocument.addEventListener("mouseup", elementDragEnd, true);
55
56     targetDocument.body.style.cursor = cursor;
57
58     event.preventDefault();
59 }
60
61 WebInspector.elementDragEnd = function(event)
62 {
63     var targetDocument = event.target.ownerDocument;
64     targetDocument.removeEventListener("mousemove", WebInspector._elementDraggingEventListener, true);
65     targetDocument.removeEventListener("mouseup", WebInspector._elementEndDraggingEventListener, true);
66
67     targetDocument.body.style.removeProperty("cursor");
68
69     if (WebInspector._elementDraggingGlassPane)
70         WebInspector._elementDraggingGlassPane.parentElement.removeChild(WebInspector._elementDraggingGlassPane);
71
72     delete WebInspector._elementDraggingGlassPane;
73     delete WebInspector._elementDraggingEventListener;
74     delete WebInspector._elementEndDraggingEventListener;
75
76     event.preventDefault();
77 }
78
79 WebInspector.animateStyle = function(animations, duration, callback)
80 {
81     var interval;
82     var complete = 0;
83     var hasCompleted = false;
84
85     const intervalDuration = (1000 / 30); // 30 frames per second.
86     const animationsLength = animations.length;
87     const propertyUnit = {opacity: ""};
88     const defaultUnit = "px";
89
90     function cubicInOut(t, b, c, d)
91     {
92         if ((t/=d/2) < 1) return c/2*t*t*t + b;
93         return c/2*((t-=2)*t*t + 2) + b;
94     }
95
96     // Pre-process animations.
97     for (var i = 0; i < animationsLength; ++i) {
98         var animation = animations[i];
99         var element = null, start = null, end = null, key = null;
100         for (key in animation) {
101             if (key === "element")
102                 element = animation[key];
103             else if (key === "start")
104                 start = animation[key];
105             else if (key === "end")
106                 end = animation[key];
107         }
108
109         if (!element || !end)
110             continue;
111
112         if (!start) {
113             var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element);
114             start = {};
115             for (key in end)
116                 start[key] = parseInt(computedStyle.getPropertyValue(key), 10);
117             animation.start = start;
118         } else
119             for (key in start)
120                 element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
121     }
122
123     function animateLoop()
124     {
125         if (hasCompleted)
126             return;
127         
128         // Advance forward.
129         complete += intervalDuration;
130         var next = complete + intervalDuration;
131
132         // Make style changes.
133         for (var i = 0; i < animationsLength; ++i) {
134             var animation = animations[i];
135             var element = animation.element;
136             var start = animation.start;
137             var end = animation.end;
138             if (!element || !end)
139                 continue;
140
141             var style = element.style;
142             for (key in end) {
143                 var endValue = end[key];
144                 if (next < duration) {
145                     var startValue = start[key];
146                     var newValue = cubicInOut(complete, startValue, endValue - startValue, duration);
147                     style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
148                 } else
149                     style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
150             }
151         }
152
153         // End condition.
154         if (complete >= duration) {
155             hasCompleted = true;
156             clearInterval(interval);
157             if (callback)
158                 callback();
159         }
160     }
161
162     function forceComplete()
163     {
164         if (hasCompleted)
165             return;
166
167         complete = duration;
168         animateLoop();
169     }
170
171     function cancel()
172     {
173         hasCompleted = true;
174         clearInterval(interval);
175     }
176
177     interval = setInterval(animateLoop, intervalDuration);
178     return {
179         cancel: cancel,
180         forceComplete: forceComplete
181     };
182 }
183
184 WebInspector.isBeingEdited = function(element)
185 {
186     return element.__editing;
187 }
188
189 WebInspector.markBeingEdited = function(element, value)
190 {
191     if (value) {
192         if (element.__editing)
193             return false;
194         element.__editing = true;
195         WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1;
196     } else {
197         if (!element.__editing)
198             return false;
199         delete element.__editing;
200         --WebInspector.__editingCount;
201     }
202     return true;
203 }
204
205 WebInspector.isEditingAnyField = function()
206 {
207     return !!WebInspector.__editingCount;
208 }
209
210 /**
211  * @constructor
212  * @param {function(Element,string,string,*,string)} commitHandler
213  * @param {function(Element,*)} cancelHandler
214  * @param {*=} context
215  */
216 WebInspector.EditingConfig = function(commitHandler, cancelHandler, context)
217 {
218     this.commitHandler = commitHandler;
219     this.cancelHandler = cancelHandler
220     this.context = context;
221
222     /**
223      * Handles the "paste" event, return values are the same as those for customFinishHandler
224      * @type {function(Element)|undefined}
225      */
226     this.pasteHandler;
227
228     /** 
229      * Whether the edited element is multiline
230      * @type {boolean|undefined}
231      */
232     this.multiline;
233
234     /**
235      * Custom finish handler for the editing session (invoked on keydown)
236      * @type {function(Element,*)|undefined}
237      */
238     this.customFinishHandler;
239 }
240
241 WebInspector.EditingConfig.prototype = {
242     setPasteHandler: function(pasteHandler)
243     {
244         this.pasteHandler = pasteHandler;
245     },
246
247     setMultiline: function(multiline)
248     {
249         this.multiline = multiline;
250     },
251
252     setCustomFinishHandler: function(customFinishHandler)
253     {
254         this.customFinishHandler = customFinishHandler;
255     }
256 }
257
258 /** 
259  * @param {Element} element
260  * @param {WebInspector.EditingConfig=} config
261  */
262 WebInspector.startEditing = function(element, config)
263 {
264     if (!WebInspector.markBeingEdited(element, true))
265         return;
266
267     config = config || new WebInspector.EditingConfig(function() {}, function() {});
268     var committedCallback = config.commitHandler;
269     var cancelledCallback = config.cancelHandler;
270     var pasteCallback = config.pasteHandler;
271     var context = config.context;
272     var oldText = getContent(element);
273     var moveDirection = "";
274
275     element.addStyleClass("editing");
276
277     var oldTabIndex = element.tabIndex;
278     if (element.tabIndex < 0)
279         element.tabIndex = 0;
280
281     function blurEventListener() {
282         editingCommitted.call(element);
283     }
284
285     function getContent(element) {
286         if (element.tagName === "INPUT" && element.type === "text")
287             return element.value;
288         else
289             return element.textContent;
290     }
291
292     /** @this {Element} */
293     function cleanUpAfterEditing()
294     {
295         WebInspector.markBeingEdited(element, false);
296
297         this.removeStyleClass("editing");
298         this.tabIndex = oldTabIndex;
299         this.scrollTop = 0;
300         this.scrollLeft = 0;
301
302         element.removeEventListener("blur", blurEventListener, false);
303         element.removeEventListener("keydown", keyDownEventListener, true);
304         if (pasteCallback)
305             element.removeEventListener("paste", pasteEventListener, true);
306
307         if (element === WebInspector.currentFocusElement() || element.isAncestor(WebInspector.currentFocusElement()))
308             WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement());
309     }
310
311     /** @this {Element} */
312     function editingCancelled()
313     {
314         if (this.tagName === "INPUT" && this.type === "text")
315             this.value = oldText;
316         else
317             this.textContent = oldText;
318
319         cleanUpAfterEditing.call(this);
320
321         cancelledCallback(this, context);
322     }
323
324     /** @this {Element} */
325     function editingCommitted()
326     {
327         cleanUpAfterEditing.call(this);
328
329         committedCallback(this, getContent(this), oldText, context, moveDirection);
330     }
331
332     function defaultFinishHandler(event)
333     {
334         var isMetaOrCtrl = WebInspector.isMac() ?
335             event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey :
336             event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
337         if (isEnterKey(event) && (event.isMetaOrCtrlForTest || !config.multiline || isMetaOrCtrl))
338             return "commit";
339         else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B")
340             return "cancel";
341         else if (event.keyIdentifier === "U+0009") // Tab key
342             return "move-" + (event.shiftKey ? "backward" : "forward");
343     }
344
345     function handleEditingResult(result, event)
346     {
347         if (result === "commit") {
348             editingCommitted.call(element);
349             event.preventDefault();
350             event.stopPropagation();
351         } else if (result === "cancel") {
352             editingCancelled.call(element);
353             event.preventDefault();
354             event.stopPropagation();
355         } else if (result && result.indexOf("move-") === 0) {
356             moveDirection = result.substring(5);
357             if (event.keyIdentifier !== "U+0009")
358                 blurEventListener();
359         }
360     }
361
362     function pasteEventListener(event)
363     {
364         var result = pasteCallback(event);
365         handleEditingResult(result, event);
366     }
367
368     function keyDownEventListener(event)
369     {
370         var handler = config.customFinishHandler || defaultFinishHandler;
371         var result = handler(event);
372         handleEditingResult(result, event);
373     }
374
375     element.addEventListener("blur", blurEventListener, false);
376     element.addEventListener("keydown", keyDownEventListener, true);
377     if (pasteCallback)
378         element.addEventListener("paste", pasteEventListener, true);
379
380     WebInspector.setCurrentFocusElement(element);
381     return {
382         cancel: editingCancelled.bind(element),
383         commit: editingCommitted.bind(element)
384     };
385 }
386
387 /**
388  * @param {boolean=} higherResolution
389  */
390 Number.secondsToString = function(seconds, higherResolution)
391 {
392     if (seconds === 0)
393         return "0";
394
395     var ms = seconds * 1000;
396     if (higherResolution && ms < 1000)
397         return WebInspector.UIString("%.3fms", ms);
398     else if (ms < 1000)
399         return WebInspector.UIString("%.0fms", ms);
400
401     if (seconds < 60)
402         return WebInspector.UIString("%.2fs", seconds);
403
404     var minutes = seconds / 60;
405     if (minutes < 60)
406         return WebInspector.UIString("%.1fmin", minutes);
407
408     var hours = minutes / 60;
409     if (hours < 24)
410         return WebInspector.UIString("%.1fhrs", hours);
411
412     var days = hours / 24;
413     return WebInspector.UIString("%.1f days", days);
414 }
415
416 /**
417  * @param {boolean=} higherResolution
418  */
419 Number.bytesToString = function(bytes, higherResolution)
420 {
421     if (typeof higherResolution === "undefined")
422         higherResolution = true;
423
424     if (bytes < 1024)
425         return WebInspector.UIString("%.0fB", bytes);
426
427     var kilobytes = bytes / 1024;
428     if (higherResolution && kilobytes < 1024)
429         return WebInspector.UIString("%.2fKB", kilobytes);
430     else if (kilobytes < 1024)
431         return WebInspector.UIString("%.0fKB", kilobytes);
432
433     var megabytes = kilobytes / 1024;
434     if (higherResolution)
435         return WebInspector.UIString("%.2fMB", megabytes);
436     else
437         return WebInspector.UIString("%.0fMB", megabytes);
438 }
439
440 WebInspector._missingLocalizedStrings = {};
441
442 /**
443  * @param {string} string
444  * @param {...*} vararg
445  */
446 WebInspector.UIString = function(string, vararg)
447 {
448     if (Preferences.localizeUI) {
449         if (window.localizedStrings && string in window.localizedStrings)
450             string = window.localizedStrings[string];
451         else {
452             if (!(string in WebInspector._missingLocalizedStrings)) {
453                 console.warn("Localized string \"" + string + "\" not found.");
454                 WebInspector._missingLocalizedStrings[string] = true;
455             }
456     
457             if (Preferences.showMissingLocalizedStrings)
458                 string += " (not localized)";
459         }
460     }
461     return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
462 }
463
464 WebInspector.useLowerCaseMenuTitles = function()
465 {
466     return WebInspector.platform() === "windows" && Preferences.useLowerCaseMenuTitlesOnWindows;
467 }
468
469 WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append)
470 {
471     return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append);
472 }
473
474 WebInspector.openLinkExternallyLabel = function()
475 {
476     return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open link in new tab" : "Open Link in New Tab");
477 }
478
479 WebInspector.openInNetworkPanelLabel = function()
480 {
481     return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open in network panel" : "Open in Network Panel");
482 }
483
484 WebInspector.copyLinkAddressLabel = function()
485 {
486     return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy link address" : "Copy Link Address");
487 }
488
489 WebInspector.platform = function()
490 {
491     if (!WebInspector._platform)
492         WebInspector._platform = InspectorFrontendHost.platform();
493     return WebInspector._platform;
494 }
495
496 WebInspector.isMac = function()
497 {
498     if (typeof WebInspector._isMac === "undefined")
499         WebInspector._isMac = WebInspector.platform() === "mac";
500
501     return WebInspector._isMac;
502 }
503
504 WebInspector.PlatformFlavor = {
505     WindowsVista: "windows-vista",
506     MacTiger: "mac-tiger",
507     MacLeopard: "mac-leopard",
508     MacSnowLeopard: "mac-snowleopard"
509 }
510
511 WebInspector.platformFlavor = function()
512 {
513     function detectFlavor()
514     {
515         const userAgent = navigator.userAgent;
516
517         if (WebInspector.platform() === "windows") {
518             var match = userAgent.match(/Windows NT (\d+)\.(?:\d+)/);
519             if (match && match[1] >= 6)
520                 return WebInspector.PlatformFlavor.WindowsVista;
521             return null;
522         } else if (WebInspector.platform() === "mac") {
523             var match = userAgent.match(/Mac OS X\s*(?:(\d+)_(\d+))?/);
524             if (!match || match[1] != 10)
525                 return WebInspector.PlatformFlavor.MacSnowLeopard;
526             switch (Number(match[2])) {
527                 case 4:
528                     return WebInspector.PlatformFlavor.MacTiger;
529                 case 5:
530                     return WebInspector.PlatformFlavor.MacLeopard;
531                 case 6:
532                 default:
533                     return WebInspector.PlatformFlavor.MacSnowLeopard;
534             }
535         }
536     }
537
538     if (!WebInspector._platformFlavor)
539         WebInspector._platformFlavor = detectFlavor();
540
541     return WebInspector._platformFlavor;
542 }
543
544 WebInspector.port = function()
545 {
546     if (!WebInspector._port)
547         WebInspector._port = InspectorFrontendHost.port();
548
549     return WebInspector._port;
550 }
551
552 WebInspector.installPortStyles = function()
553 {
554     var platform = WebInspector.platform();
555     document.body.addStyleClass("platform-" + platform);
556     var flavor = WebInspector.platformFlavor();
557     if (flavor)
558         document.body.addStyleClass("platform-" + flavor);
559     var port = WebInspector.port();
560     document.body.addStyleClass("port-" + port);
561 }
562
563 WebInspector._windowFocused = function(event)
564 {
565     if (event.target.document.nodeType === Node.DOCUMENT_NODE)
566         document.body.removeStyleClass("inactive");
567 }
568
569 WebInspector._windowBlurred = function(event)
570 {
571     if (event.target.document.nodeType === Node.DOCUMENT_NODE)
572         document.body.addStyleClass("inactive");
573 }
574
575 WebInspector.previousFocusElement = function()
576 {
577     return WebInspector._previousFocusElement;
578 }
579
580 WebInspector.currentFocusElement = function()
581 {
582     return WebInspector._currentFocusElement;
583 }
584
585 WebInspector._focusChanged = function(event)
586 {
587     WebInspector.setCurrentFocusElement(event.target);
588 }
589
590 WebInspector.setCurrentFocusElement = function(x)
591 {
592     if (WebInspector._currentFocusElement !== x)
593         WebInspector._previousFocusElement = WebInspector._currentFocusElement;
594     WebInspector._currentFocusElement = x;
595
596     if (WebInspector._currentFocusElement) {
597         WebInspector._currentFocusElement.focus();
598
599         // Make a caret selection inside the new element if there isn't a range selection and
600         // there isn't already a caret selection inside.
601         var selection = window.getSelection();
602         if (selection.isCollapsed && !WebInspector._currentFocusElement.isInsertionCaretInside()) {
603             var selectionRange = WebInspector._currentFocusElement.ownerDocument.createRange();
604             selectionRange.setStart(WebInspector._currentFocusElement, 0);
605             selectionRange.setEnd(WebInspector._currentFocusElement, 0);
606
607             selection.removeAllRanges();
608             selection.addRange(selectionRange);
609         }
610     } else if (WebInspector._previousFocusElement)
611         WebInspector._previousFocusElement.blur();
612 }
613
614 WebInspector.setToolbarColors = function(backgroundColor, color)
615 {
616     if (!WebInspector._themeStyleElement) {
617         WebInspector._themeStyleElement = document.createElement("style");
618         document.head.appendChild(WebInspector._themeStyleElement);
619     }
620     WebInspector._themeStyleElement.textContent =
621         "#toolbar {\
622              background-image: none !important;\
623              background-color: " + backgroundColor + " !important;\
624          }\
625          \
626          .toolbar-label {\
627              color: " + color + " !important;\
628              text-shadow: none;\
629          }";
630 }
631
632 WebInspector.resetToolbarColors = function()
633 {
634     if (WebInspector._themeStyleElement)
635         WebInspector._themeStyleElement.textContent = "";
636
637 }
638
639 ;(function() {
640
641 function windowLoaded()
642 {
643     window.addEventListener("focus", WebInspector._windowFocused, false);
644     window.addEventListener("blur", WebInspector._windowBlurred, false);
645     document.addEventListener("focus", WebInspector._focusChanged.bind(this), true);
646     window.removeEventListener("DOMContentLoaded", windowLoaded, false);
647 }
648
649 window.addEventListener("DOMContentLoaded", windowLoaded, false);
650
651 })();