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
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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.
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.
32 WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor)
34 if (WebInspector._elementDraggingEventListener || WebInspector._elementEndDraggingEventListener)
35 WebInspector.elementDragEnd(event);
37 WebInspector._elementDraggingEventListener = dividerDrag;
38 WebInspector._elementEndDraggingEventListener = elementDragEnd;
40 var targetDocument = event.target.ownerDocument;
41 targetDocument.addEventListener("mousemove", dividerDrag, true);
42 targetDocument.addEventListener("mouseup", elementDragEnd, true);
44 targetDocument.body.style.cursor = cursor;
46 event.preventDefault();
49 WebInspector.elementDragEnd = function(event)
51 var targetDocument = event.target.ownerDocument;
52 targetDocument.removeEventListener("mousemove", WebInspector._elementDraggingEventListener, true);
53 targetDocument.removeEventListener("mouseup", WebInspector._elementEndDraggingEventListener, true);
55 targetDocument.body.style.removeProperty("cursor");
57 delete WebInspector._elementDraggingEventListener;
58 delete WebInspector._elementEndDraggingEventListener;
60 event.preventDefault();
63 WebInspector.animateStyle = function(animations, duration, callback)
67 var hasCompleted = false;
69 const intervalDuration = (1000 / 30); // 30 frames per second.
70 const animationsLength = animations.length;
71 const propertyUnit = {opacity: ""};
72 const defaultUnit = "px";
74 function cubicInOut(t, b, c, d)
76 if ((t/=d/2) < 1) return c/2*t*t*t + b;
77 return c/2*((t-=2)*t*t + 2) + b;
80 // Pre-process animations.
81 for (var i = 0; i < animationsLength; ++i) {
82 var animation = animations[i];
83 var element = null, start = null, end = null, key = null;
84 for (key in animation) {
85 if (key === "element")
86 element = animation[key];
87 else if (key === "start")
88 start = animation[key];
89 else if (key === "end")
97 var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element);
100 start[key] = parseInt(computedStyle.getPropertyValue(key), 10);
101 animation.start = start;
104 element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
107 function animateLoop()
113 complete += intervalDuration;
114 var next = complete + intervalDuration;
116 // Make style changes.
117 for (var i = 0; i < animationsLength; ++i) {
118 var animation = animations[i];
119 var element = animation.element;
120 var start = animation.start;
121 var end = animation.end;
122 if (!element || !end)
125 var style = element.style;
127 var endValue = end[key];
128 if (next < duration) {
129 var startValue = start[key];
130 var newValue = cubicInOut(complete, startValue, endValue - startValue, duration);
131 style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
133 style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
138 if (complete >= duration) {
140 clearInterval(interval);
146 function forceComplete()
158 clearInterval(interval);
161 interval = setInterval(animateLoop, intervalDuration);
164 forceComplete: forceComplete
168 WebInspector.isBeingEdited = function(element)
170 return element.__editing;
173 WebInspector.markBeingEdited = function(element, value)
176 if (element.__editing)
178 element.__editing = true;
179 WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1;
181 if (!element.__editing)
183 delete element.__editing;
184 --WebInspector.__editingCount;
189 WebInspector.isEditingAnyField = function()
191 return !!WebInspector.__editingCount;
196 * @param {function(Element,string,string,*,string)} commitHandler
197 * @param {function(Element,*)} cancelHandler
198 * @param {*=} context
200 WebInspector.EditingConfig = function(commitHandler, cancelHandler, context)
202 this.commitHandler = commitHandler;
203 this.cancelHandler = cancelHandler
204 this.context = context;
207 * Handles the "paste" event, return values are the same as those for customFinishHandler
208 * @type {function(Element)|undefined}
213 * Whether the edited element is multiline
214 * @type {boolean|undefined}
219 * Custom finish handler for the editing session (invoked on keydown)
220 * @type {function(Element,*)|undefined}
222 this.customFinishHandler;
225 WebInspector.EditingConfig.prototype = {
226 setPasteHandler: function(pasteHandler)
228 this.pasteHandler = pasteHandler;
231 setMultiline: function(multiline)
233 this.multiline = multiline;
236 setCustomFinishHandler: function(customFinishHandler)
238 this.customFinishHandler = customFinishHandler;
243 * @param {Element} element
244 * @param {WebInspector.EditingConfig=} config
246 WebInspector.startEditing = function(element, config)
248 if (!WebInspector.markBeingEdited(element, true))
251 config = config || new WebInspector.EditingConfig(function() {}, function() {});
252 var committedCallback = config.commitHandler;
253 var cancelledCallback = config.cancelHandler;
254 var pasteCallback = config.pasteHandler;
255 var context = config.context;
256 var oldText = getContent(element);
257 var moveDirection = "";
259 element.addStyleClass("editing");
261 var oldTabIndex = element.tabIndex;
262 if (element.tabIndex < 0)
263 element.tabIndex = 0;
265 function blurEventListener() {
266 editingCommitted.call(element);
269 function getContent(element) {
270 if (element.tagName === "INPUT" && element.type === "text")
271 return element.value;
273 return element.textContent;
276 /** @this {Element} */
277 function cleanUpAfterEditing()
279 WebInspector.markBeingEdited(element, false);
281 this.removeStyleClass("editing");
282 this.tabIndex = oldTabIndex;
286 element.removeEventListener("blur", blurEventListener, false);
287 element.removeEventListener("keydown", keyDownEventListener, true);
289 element.removeEventListener("paste", pasteEventListener, true);
291 if (element === WebInspector.currentFocusElement() || element.isAncestor(WebInspector.currentFocusElement()))
292 WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement());
295 /** @this {Element} */
296 function editingCancelled()
298 if (this.tagName === "INPUT" && this.type === "text")
299 this.value = oldText;
301 this.textContent = oldText;
303 cleanUpAfterEditing.call(this);
305 cancelledCallback(this, context);
308 /** @this {Element} */
309 function editingCommitted()
311 cleanUpAfterEditing.call(this);
313 committedCallback(this, getContent(this), oldText, context, moveDirection);
316 function defaultFinishHandler(event)
318 var isMetaOrCtrl = WebInspector.isMac() ?
319 event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey :
320 event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
321 if (isEnterKey(event) && (event.isMetaOrCtrlForTest || !config.multiline || isMetaOrCtrl))
323 else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B")
325 else if (event.keyIdentifier === "U+0009") // Tab key
326 return "move-" + (event.shiftKey ? "backward" : "forward");
329 function handleEditingResult(result, event)
331 if (result === "commit") {
332 editingCommitted.call(element);
333 event.preventDefault();
334 event.stopPropagation();
335 } else if (result === "cancel") {
336 editingCancelled.call(element);
337 event.preventDefault();
338 event.stopPropagation();
339 } else if (result && result.indexOf("move-") === 0) {
340 moveDirection = result.substring(5);
341 if (event.keyIdentifier !== "U+0009")
346 function pasteEventListener(event)
348 var result = pasteCallback(event);
349 handleEditingResult(result, event);
352 function keyDownEventListener(event)
354 var handler = config.customFinishHandler || defaultFinishHandler;
355 var result = handler(event);
356 handleEditingResult(result, event);
359 element.addEventListener("blur", blurEventListener, false);
360 element.addEventListener("keydown", keyDownEventListener, true);
362 element.addEventListener("paste", pasteEventListener, true);
364 WebInspector.setCurrentFocusElement(element);
366 cancel: editingCancelled.bind(element),
367 commit: editingCommitted.bind(element)
372 * @param {boolean=} higherResolution
374 Number.secondsToString = function(seconds, higherResolution)
379 var ms = seconds * 1000;
380 if (higherResolution && ms < 1000)
381 return WebInspector.UIString("%.3fms", ms);
383 return WebInspector.UIString("%.0fms", ms);
386 return WebInspector.UIString("%.2fs", seconds);
388 var minutes = seconds / 60;
390 return WebInspector.UIString("%.1fmin", minutes);
392 var hours = minutes / 60;
394 return WebInspector.UIString("%.1fhrs", hours);
396 var days = hours / 24;
397 return WebInspector.UIString("%.1f days", days);
401 * @param {boolean=} higherResolution
403 Number.bytesToString = function(bytes, higherResolution)
405 if (typeof higherResolution === "undefined")
406 higherResolution = true;
409 return WebInspector.UIString("%.0fB", bytes);
411 var kilobytes = bytes / 1024;
412 if (higherResolution && kilobytes < 1024)
413 return WebInspector.UIString("%.2fKB", kilobytes);
414 else if (kilobytes < 1024)
415 return WebInspector.UIString("%.0fKB", kilobytes);
417 var megabytes = kilobytes / 1024;
418 if (higherResolution)
419 return WebInspector.UIString("%.2fMB", megabytes);
421 return WebInspector.UIString("%.0fMB", megabytes);
424 WebInspector._missingLocalizedStrings = {};
427 * @param {string} string
428 * @param {...*} vararg
430 WebInspector.UIString = function(string, vararg)
432 if (Preferences.localizeUI) {
433 if (window.localizedStrings && string in window.localizedStrings)
434 string = window.localizedStrings[string];
436 if (!(string in WebInspector._missingLocalizedStrings)) {
437 console.warn("Localized string \"" + string + "\" not found.");
438 WebInspector._missingLocalizedStrings[string] = true;
441 if (Preferences.showMissingLocalizedStrings)
442 string += " (not localized)";
445 return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
448 WebInspector.useLowerCaseMenuTitles = function()
450 return WebInspector.platform() === "windows" && Preferences.useLowerCaseMenuTitlesOnWindows;
453 WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append)
455 return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append);
458 WebInspector.openLinkExternallyLabel = function()
460 return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open link in new tab" : "Open Link in New Tab");
463 WebInspector.openInNetworkPanelLabel = function()
465 return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open in network panel" : "Open in Network Panel");
468 WebInspector.copyLinkAddressLabel = function()
470 return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy link address" : "Copy Link Address");
473 WebInspector.platform = function()
475 if (!WebInspector._platform)
476 WebInspector._platform = InspectorFrontendHost.platform();
477 return WebInspector._platform;
480 WebInspector.isMac = function()
482 if (typeof WebInspector._isMac === "undefined")
483 WebInspector._isMac = WebInspector.platform() === "mac";
485 return WebInspector._isMac;
488 WebInspector.PlatformFlavor = {
489 WindowsVista: "windows-vista",
490 MacTiger: "mac-tiger",
491 MacLeopard: "mac-leopard",
492 MacSnowLeopard: "mac-snowleopard"
495 WebInspector.platformFlavor = function()
497 function detectFlavor()
499 const userAgent = navigator.userAgent;
501 if (WebInspector.platform() === "windows") {
502 var match = userAgent.match(/Windows NT (\d+)\.(?:\d+)/);
503 if (match && match[1] >= 6)
504 return WebInspector.PlatformFlavor.WindowsVista;
506 } else if (WebInspector.platform() === "mac") {
507 var match = userAgent.match(/Mac OS X\s*(?:(\d+)_(\d+))?/);
508 if (!match || match[1] != 10)
509 return WebInspector.PlatformFlavor.MacSnowLeopard;
510 switch (Number(match[2])) {
512 return WebInspector.PlatformFlavor.MacTiger;
514 return WebInspector.PlatformFlavor.MacLeopard;
517 return WebInspector.PlatformFlavor.MacSnowLeopard;
522 if (!WebInspector._platformFlavor)
523 WebInspector._platformFlavor = detectFlavor();
525 return WebInspector._platformFlavor;
528 WebInspector.port = function()
530 if (!WebInspector._port)
531 WebInspector._port = InspectorFrontendHost.port();
533 return WebInspector._port;
536 WebInspector.installPortStyles = function()
538 var platform = WebInspector.platform();
539 document.body.addStyleClass("platform-" + platform);
540 var flavor = WebInspector.platformFlavor();
542 document.body.addStyleClass("platform-" + flavor);
543 var port = WebInspector.port();
544 document.body.addStyleClass("port-" + port);
547 WebInspector._windowFocused = function(event)
549 if (event.target.document.nodeType === Node.DOCUMENT_NODE)
550 document.body.removeStyleClass("inactive");
553 WebInspector._windowBlurred = function(event)
555 if (event.target.document.nodeType === Node.DOCUMENT_NODE)
556 document.body.addStyleClass("inactive");
559 WebInspector.previousFocusElement = function()
561 return WebInspector._previousFocusElement;
564 WebInspector.currentFocusElement = function()
566 return WebInspector._currentFocusElement;
569 WebInspector._focusChanged = function(event)
571 WebInspector.setCurrentFocusElement(event.target);
574 WebInspector.setCurrentFocusElement = function(x)
576 if (WebInspector._currentFocusElement !== x)
577 WebInspector._previousFocusElement = WebInspector._currentFocusElement;
578 WebInspector._currentFocusElement = x;
580 if (WebInspector._currentFocusElement) {
581 WebInspector._currentFocusElement.focus();
583 // Make a caret selection inside the new element if there isn't a range selection and
584 // there isn't already a caret selection inside.
585 var selection = window.getSelection();
586 if (selection.isCollapsed && !WebInspector._currentFocusElement.isInsertionCaretInside()) {
587 var selectionRange = WebInspector._currentFocusElement.ownerDocument.createRange();
588 selectionRange.setStart(WebInspector._currentFocusElement, 0);
589 selectionRange.setEnd(WebInspector._currentFocusElement, 0);
591 selection.removeAllRanges();
592 selection.addRange(selectionRange);
594 } else if (WebInspector._previousFocusElement)
595 WebInspector._previousFocusElement.blur();
598 WebInspector.setToolbarColors = function(backgroundColor, color)
600 if (!WebInspector._themeStyleElement) {
601 WebInspector._themeStyleElement = document.createElement("style");
602 document.head.appendChild(WebInspector._themeStyleElement);
604 WebInspector._themeStyleElement.textContent =
606 background-image: none !important;\
607 background-color: " + backgroundColor + " !important;\
611 color: " + color + " !important;\
616 WebInspector.resetToolbarColors = function()
618 if (WebInspector._themeStyleElement)
619 WebInspector._themeStyleElement.textContent = "";
625 function windowLoaded()
627 window.addEventListener("focus", WebInspector._windowFocused, false);
628 window.addEventListener("blur", WebInspector._windowBlurred, false);
629 document.addEventListener("focus", WebInspector._focusChanged.bind(this), true);
630 window.removeEventListener("DOMContentLoaded", windowLoaded, false);
633 window.addEventListener("DOMContentLoaded", windowLoaded, false);