blob: 14bccb3d0c76694a0c22540872084035b95e8cd6 [file] [log] [blame]
/*
* Copyright (C) 2011 Google Inc. All rights reserved.
* Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
* Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com).
* Copyright (C) 2009 Joseph Pecoraro
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @param {!Element} element
* @param {?function(!MouseEvent): boolean} elementDragStart
* @param {function(!MouseEvent)} elementDrag
* @param {?function(!MouseEvent)} elementDragEnd
* @param {!string} cursor
* @param {?string=} hoverCursor
*/
WebInspector.installDragHandle = function(element, elementDragStart, elementDrag, elementDragEnd, cursor, hoverCursor)
{
element.addEventListener("mousedown", WebInspector.elementDragStart.bind(WebInspector, elementDragStart, elementDrag, elementDragEnd, cursor), false);
if (hoverCursor !== null)
element.style.cursor = hoverCursor || cursor;
}
/**
* @param {?function(!MouseEvent):boolean} elementDragStart
* @param {function(!MouseEvent)} elementDrag
* @param {?function(!MouseEvent)} elementDragEnd
* @param {string} cursor
* @param {?Event} event
*/
WebInspector.elementDragStart = function(elementDragStart, elementDrag, elementDragEnd, cursor, event)
{
// Only drag upon left button. Right will likely cause a context menu. So will ctrl-click on mac.
if (event.button || (WebInspector.isMac() && event.ctrlKey))
return;
if (WebInspector._elementDraggingEventListener)
return;
if (elementDragStart && !elementDragStart(/** @type {!MouseEvent} */ (event)))
return;
if (WebInspector._elementDraggingGlassPane) {
WebInspector._elementDraggingGlassPane.dispose();
delete WebInspector._elementDraggingGlassPane;
}
var targetDocument = event.target.ownerDocument;
WebInspector._elementDraggingEventListener = elementDrag;
WebInspector._elementEndDraggingEventListener = elementDragEnd;
WebInspector._mouseOutWhileDraggingTargetDocument = targetDocument;
targetDocument.addEventListener("mousemove", WebInspector._elementDragMove, true);
targetDocument.addEventListener("mouseup", WebInspector._elementDragEnd, true);
targetDocument.addEventListener("mouseout", WebInspector._mouseOutWhileDragging, true);
targetDocument.body.style.cursor = cursor;
event.preventDefault();
}
WebInspector._mouseOutWhileDragging = function()
{
WebInspector._unregisterMouseOutWhileDragging();
WebInspector._elementDraggingGlassPane = new WebInspector.GlassPane();
}
WebInspector._unregisterMouseOutWhileDragging = function()
{
if (!WebInspector._mouseOutWhileDraggingTargetDocument)
return;
WebInspector._mouseOutWhileDraggingTargetDocument.removeEventListener("mouseout", WebInspector._mouseOutWhileDragging, true);
delete WebInspector._mouseOutWhileDraggingTargetDocument;
}
/**
* @param {!Event} event
*/
WebInspector._elementDragMove = function(event)
{
if (WebInspector._elementDraggingEventListener(/** @type {!MouseEvent} */ (event)))
WebInspector._cancelDragEvents(event);
}
/**
* @param {!Event} event
*/
WebInspector._cancelDragEvents = function(event)
{
var targetDocument = event.target.ownerDocument;
targetDocument.removeEventListener("mousemove", WebInspector._elementDragMove, true);
targetDocument.removeEventListener("mouseup", WebInspector._elementDragEnd, true);
WebInspector._unregisterMouseOutWhileDragging();
targetDocument.body.style.removeProperty("cursor");
if (WebInspector._elementDraggingGlassPane)
WebInspector._elementDraggingGlassPane.dispose();
delete WebInspector._elementDraggingGlassPane;
delete WebInspector._elementDraggingEventListener;
delete WebInspector._elementEndDraggingEventListener;
}
/**
* @param {!Event} event
*/
WebInspector._elementDragEnd = function(event)
{
var elementDragEnd = WebInspector._elementEndDraggingEventListener;
WebInspector._cancelDragEvents(/** @type {!MouseEvent} */ (event));
event.preventDefault();
if (elementDragEnd)
elementDragEnd(/** @type {!MouseEvent} */ (event));
}
/**
* @constructor
*/
WebInspector.GlassPane = function()
{
this.element = document.createElement("div");
this.element.style.cssText = "position:absolute;top:0;bottom:0;left:0;right:0;background-color:transparent;z-index:1000;";
this.element.id = "glass-pane";
document.body.appendChild(this.element);
WebInspector._glassPane = this;
}
WebInspector.GlassPane.prototype = {
dispose: function()
{
delete WebInspector._glassPane;
if (WebInspector.HelpScreen.isVisible())
WebInspector.HelpScreen.focus();
else
WebInspector.inspectorView.focus();
this.element.remove();
}
}
WebInspector.animateStyle = function(animations, duration, callback)
{
var startTime = new Date().getTime();
var hasCompleted = false;
const animationsLength = animations.length;
const propertyUnit = {opacity: ""};
const defaultUnit = "px";
// Pre-process animations.
for (var i = 0; i < animationsLength; ++i) {
var animation = animations[i];
var element = null, start = null, end = null, key = null;
for (key in animation) {
if (key === "element")
element = animation[key];
else if (key === "start")
start = animation[key];
else if (key === "end")
end = animation[key];
}
if (!element || !end)
continue;
if (!start) {
var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element);
start = {};
for (key in end)
start[key] = parseInt(computedStyle.getPropertyValue(key), 10);
animation.start = start;
} else
for (key in start)
element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
}
function animateLoop()
{
if (hasCompleted)
return;
var complete = new Date().getTime() - startTime;
// Make style changes.
for (var i = 0; i < animationsLength; ++i) {
var animation = animations[i];
var element = animation.element;
var start = animation.start;
var end = animation.end;
if (!element || !end)
continue;
var style = element.style;
for (key in end) {
var endValue = end[key];
if (complete < duration) {
var startValue = start[key];
// Linear animation.
var newValue = startValue + (endValue - startValue) * complete / duration;
style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
} else
style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
}
}
// End condition.
if (complete >= duration)
hasCompleted = true;
if (callback)
callback(hasCompleted);
if (!hasCompleted)
window.requestAnimationFrame(animateLoop);
}
function forceComplete()
{
if (hasCompleted)
return;
duration = 0;
animateLoop();
}
window.requestAnimationFrame(animateLoop);
return {
forceComplete: forceComplete
};
}
WebInspector.isBeingEdited = function(element)
{
if (element.classList.contains("text-prompt") || element.nodeName === "INPUT" || element.nodeName === "TEXTAREA")
return true;
if (!WebInspector.__editingCount)
return false;
while (element) {
if (element.__editing)
return true;
element = element.parentElement;
}
return false;
}
WebInspector.markBeingEdited = function(element, value)
{
if (value) {
if (element.__editing)
return false;
element.classList.add("being-edited");
element.__editing = true;
WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1;
} else {
if (!element.__editing)
return false;
element.classList.remove("being-edited");
delete element.__editing;
--WebInspector.__editingCount;
}
return true;
}
/**
* @constructor
* @param {function(!Element,string,string,T,string)} commitHandler
* @param {function(!Element,T)} cancelHandler
* @param {T=} context
* @template T
*/
WebInspector.EditingConfig = function(commitHandler, cancelHandler, context)
{
this.commitHandler = commitHandler;
this.cancelHandler = cancelHandler
this.context = context;
/**
* Handles the "paste" event, return values are the same as those for customFinishHandler
* @type {function(!Element)|undefined}
*/
this.pasteHandler;
/**
* Whether the edited element is multiline
* @type {boolean|undefined}
*/
this.multiline;
/**
* Custom finish handler for the editing session (invoked on keydown)
* @type {function(!Element,*)|undefined}
*/
this.customFinishHandler;
}
WebInspector.EditingConfig.prototype = {
setPasteHandler: function(pasteHandler)
{
this.pasteHandler = pasteHandler;
},
/**
* @param {string} initialValue
* @param {!Object} mode
* @param {string} theme
* @param {boolean=} lineWrapping
* @param {boolean=} smartIndent
*/
setMultilineOptions: function(initialValue, mode, theme, lineWrapping, smartIndent)
{
this.multiline = true;
this.initialValue = initialValue;
this.mode = mode;
this.theme = theme;
this.lineWrapping = lineWrapping;
this.smartIndent = smartIndent;
},
setCustomFinishHandler: function(customFinishHandler)
{
this.customFinishHandler = customFinishHandler;
}
}
WebInspector.CSSNumberRegex = /^(-?(?:\d+(?:\.\d+)?|\.\d+))$/;
WebInspector.StyleValueDelimiters = " \xA0\t\n\"':;,/()";
/**
* @param {!Event} event
* @return {?string}
*/
WebInspector._valueModificationDirection = function(event)
{
var direction = null;
if (event.type === "mousewheel") {
if (event.wheelDeltaY > 0)
direction = "Up";
else if (event.wheelDeltaY < 0)
direction = "Down";
} else {
if (event.keyIdentifier === "Up" || event.keyIdentifier === "PageUp")
direction = "Up";
else if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown")
direction = "Down";
}
return direction;
}
/**
* @param {string} hexString
* @param {!Event} event
*/
WebInspector._modifiedHexValue = function(hexString, event)
{
var direction = WebInspector._valueModificationDirection(event);
if (!direction)
return hexString;
var number = parseInt(hexString, 16);
if (isNaN(number) || !isFinite(number))
return hexString;
var maxValue = Math.pow(16, hexString.length) - 1;
var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
var delta;
if (arrowKeyOrMouseWheelEvent)
delta = (direction === "Up") ? 1 : -1;
else
delta = (event.keyIdentifier === "PageUp") ? 16 : -16;
if (event.shiftKey)
delta *= 16;
var result = number + delta;
if (result < 0)
result = 0; // Color hex values are never negative, so clamp to 0.
else if (result > maxValue)
return hexString;
// Ensure the result length is the same as the original hex value.
var resultString = result.toString(16).toUpperCase();
for (var i = 0, lengthDelta = hexString.length - resultString.length; i < lengthDelta; ++i)
resultString = "0" + resultString;
return resultString;
}
/**
* @param {number} number
* @param {!Event} event
*/
WebInspector._modifiedFloatNumber = function(number, event)
{
var direction = WebInspector._valueModificationDirection(event);
if (!direction)
return number;
var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
// Jump by 10 when shift is down or jump by 0.1 when Alt/Option is down.
// Also jump by 10 for page up and down, or by 100 if shift is held with a page key.
var changeAmount = 1;
if (event.shiftKey && !arrowKeyOrMouseWheelEvent)
changeAmount = 100;
else if (event.shiftKey || !arrowKeyOrMouseWheelEvent)
changeAmount = 10;
else if (event.altKey)
changeAmount = 0.1;
if (direction === "Down")
changeAmount *= -1;
// Make the new number and constrain it to a precision of 6, this matches numbers the engine returns.
// Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1.
var result = Number((number + changeAmount).toFixed(6));
if (!String(result).match(WebInspector.CSSNumberRegex))
return null;
return result;
}
/**
* @param {?Event} event
* @param {!Element} element
* @param {function(string,string)=} finishHandler
* @param {function(string)=} suggestionHandler
* @param {function(number):number=} customNumberHandler
* @return {boolean}
*/
WebInspector.handleElementValueModifications = function(event, element, finishHandler, suggestionHandler, customNumberHandler)
{
var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown");
if (!arrowKeyOrMouseWheelEvent && !pageKeyPressed)
return false;
var selection = window.getSelection();
if (!selection.rangeCount)
return false;
var selectionRange = selection.getRangeAt(0);
if (!selectionRange.commonAncestorContainer.isSelfOrDescendant(element))
return false;
var originalValue = element.textContent;
var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, WebInspector.StyleValueDelimiters, element);
var wordString = wordRange.toString();
if (suggestionHandler && suggestionHandler(wordString))
return false;
var replacementString;
var prefix, suffix, number;
var matches;
matches = /(.*#)([\da-fA-F]+)(.*)/.exec(wordString);
if (matches && matches.length) {
prefix = matches[1];
suffix = matches[3];
number = WebInspector._modifiedHexValue(matches[2], event);
if (customNumberHandler)
number = customNumberHandler(number);
replacementString = prefix + number + suffix;
} else {
matches = /(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/.exec(wordString);
if (matches && matches.length) {
prefix = matches[1];
suffix = matches[3];
number = WebInspector._modifiedFloatNumber(parseFloat(matches[2]), event);
// Need to check for null explicitly.
if (number === null)
return false;
if (customNumberHandler)
number = customNumberHandler(number);
replacementString = prefix + number + suffix;
}
}
if (replacementString) {
var replacementTextNode = document.createTextNode(replacementString);
wordRange.deleteContents();
wordRange.insertNode(replacementTextNode);
var finalSelectionRange = document.createRange();
finalSelectionRange.setStart(replacementTextNode, 0);
finalSelectionRange.setEnd(replacementTextNode, replacementString.length);
selection.removeAllRanges();
selection.addRange(finalSelectionRange);
event.handled = true;
event.preventDefault();
if (finishHandler)
finishHandler(originalValue, replacementString);
return true;
}
return false;
}
/**
* @param {!Element} element
* @param {!WebInspector.EditingConfig=} config
* @return {?{cancel: function(), commit: function(), codeMirror: !CodeMirror, setWidth: function(number)}}
*/
WebInspector.startEditing = function(element, config)
{
if (!WebInspector.markBeingEdited(element, true))
return null;
config = config || new WebInspector.EditingConfig(function() {}, function() {});
var committedCallback = config.commitHandler;
var cancelledCallback = config.cancelHandler;
var pasteCallback = config.pasteHandler;
var context = config.context;
var isMultiline = config.multiline || false;
var oldText = isMultiline ? config.initialValue : getContent(element);
var moveDirection = "";
var oldTabIndex;
var codeMirror;
var cssLoadView;
/**
* @param {?Event} e
*/
function consumeCopy(e)
{
e.consume();
}
if (isMultiline) {
loadScript("CodeMirrorTextEditor.js");
cssLoadView = new WebInspector.CodeMirrorCSSLoadView();
cssLoadView.show(element);
WebInspector.setCurrentFocusElement(element);
element.addEventListener("copy", consumeCopy, false);
codeMirror = window.CodeMirror(element, {
mode: config.mode,
lineWrapping: config.lineWrapping,
smartIndent: config.smartIndent,
autofocus: true,
theme: config.theme,
value: oldText
});
codeMirror.getWrapperElement().classList.add("source-code");
codeMirror.on("cursorActivity", function(cm) {
cm.display.cursor.scrollIntoViewIfNeeded(false);
});
} else {
element.classList.add("editing");
oldTabIndex = element.getAttribute("tabIndex");
if (typeof oldTabIndex !== "number" || oldTabIndex < 0)
element.tabIndex = 0;
WebInspector.setCurrentFocusElement(element);
}
/**
* @param {number} width
*/
function setWidth(width)
{
const padding = 30;
codeMirror.getWrapperElement().style.width = (width - codeMirror.getWrapperElement().offsetLeft - padding) + "px";
codeMirror.refresh();
}
/**
* @param {?Event=} e
*/
function blurEventListener(e) {
if (!isMultiline || !e || !e.relatedTarget || !e.relatedTarget.isSelfOrDescendant(element))
editingCommitted.call(element);
}
function getContent(element) {
if (isMultiline)
return codeMirror.getValue();
if (element.tagName === "INPUT" && element.type === "text")
return element.value;
return element.textContent;
}
/** @this {Element} */
function cleanUpAfterEditing()
{
WebInspector.markBeingEdited(element, false);
element.removeEventListener("blur", blurEventListener, isMultiline);
element.removeEventListener("keydown", keyDownEventListener, true);
if (pasteCallback)
element.removeEventListener("paste", pasteEventListener, true);
WebInspector.restoreFocusFromElement(element);
if (isMultiline) {
element.removeEventListener("copy", consumeCopy, false);
cssLoadView.detach();
return;
}
this.classList.remove("editing");
if (typeof oldTabIndex !== "number")
element.removeAttribute("tabIndex");
else
this.tabIndex = oldTabIndex;
this.scrollTop = 0;
this.scrollLeft = 0;
}
/** @this {Element} */
function editingCancelled()
{
if (isMultiline)
codeMirror.setValue(oldText);
else {
if (this.tagName === "INPUT" && this.type === "text")
this.value = oldText;
else
this.textContent = oldText;
}
cleanUpAfterEditing.call(this);
cancelledCallback(this, context);
}
/** @this {Element} */
function editingCommitted()
{
cleanUpAfterEditing.call(this);
committedCallback(this, getContent(this), oldText, context, moveDirection);
}
function defaultFinishHandler(event)
{
var isMetaOrCtrl = WebInspector.isMac() ?
event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey :
event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
if (isEnterKey(event) && (event.isMetaOrCtrlForTest || !isMultiline || isMetaOrCtrl))
return "commit";
else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B")
return "cancel";
else if (!isMultiline && event.keyIdentifier === "U+0009") // Tab key
return "move-" + (event.shiftKey ? "backward" : "forward");
}
function handleEditingResult(result, event)
{
if (result === "commit") {
editingCommitted.call(element);
event.consume(true);
} else if (result === "cancel") {
editingCancelled.call(element);
event.consume(true);
} else if (result && result.startsWith("move-")) {
moveDirection = result.substring(5);
if (event.keyIdentifier !== "U+0009")
blurEventListener();
}
}
function pasteEventListener(event)
{
var result = pasteCallback(event);
handleEditingResult(result, event);
}
function keyDownEventListener(event)
{
var handler = config.customFinishHandler || defaultFinishHandler;
var result = handler(event);
handleEditingResult(result, event);
}
element.addEventListener("blur", blurEventListener, isMultiline);
element.addEventListener("keydown", keyDownEventListener, true);
if (pasteCallback)
element.addEventListener("paste", pasteEventListener, true);
return {
cancel: editingCancelled.bind(element),
commit: editingCommitted.bind(element),
codeMirror: codeMirror, // For testing.
setWidth: setWidth
};
}
/**
* @param {number} seconds
* @param {boolean=} higherResolution
* @return {string}
*/
Number.secondsToString = function(seconds, higherResolution)
{
if (!isFinite(seconds))
return "-";
if (seconds === 0)
return "0";
var ms = seconds * 1000;
if (higherResolution && ms < 1000)
return WebInspector.UIString("%.3f\u2009ms", ms);
else if (ms < 1000)
return WebInspector.UIString("%.0f\u2009ms", ms);
if (seconds < 60)
return WebInspector.UIString("%.2f\u2009s", seconds);
var minutes = seconds / 60;
if (minutes < 60)
return WebInspector.UIString("%.1f\u2009min", minutes);
var hours = minutes / 60;
if (hours < 24)
return WebInspector.UIString("%.1f\u2009hrs", hours);
var days = hours / 24;
return WebInspector.UIString("%.1f\u2009days", days);
}
/**
* @param {number} bytes
* @return {string}
*/
Number.bytesToString = function(bytes)
{
if (bytes < 1024)
return WebInspector.UIString("%.0f\u2009B", bytes);
var kilobytes = bytes / 1024;
if (kilobytes < 100)
return WebInspector.UIString("%.1f\u2009KB", kilobytes);
if (kilobytes < 1024)
return WebInspector.UIString("%.0f\u2009KB", kilobytes);
var megabytes = kilobytes / 1024;
if (megabytes < 100)
return WebInspector.UIString("%.1f\u2009MB", megabytes);
else
return WebInspector.UIString("%.0f\u2009MB", megabytes);
}
Number.withThousandsSeparator = function(num)
{
var str = num + "";
var re = /(\d+)(\d{3})/;
while (str.match(re))
str = str.replace(re, "$1\u2009$2"); // \u2009 is a thin space.
return str;
}
WebInspector.useLowerCaseMenuTitles = function()
{
return WebInspector.platform() === "windows";
}
WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append)
{
return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append);
}
WebInspector.openLinkExternallyLabel = function()
{
return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open link in new tab" : "Open Link in New Tab");
}
WebInspector.copyLinkAddressLabel = function()
{
return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy link address" : "Copy Link Address");
}
WebInspector.platform = function()
{
if (!WebInspector._platform)
WebInspector._platform = InspectorFrontendHost.platform();
return WebInspector._platform;
}
WebInspector.isMac = function()
{
if (typeof WebInspector._isMac === "undefined")
WebInspector._isMac = WebInspector.platform() === "mac";
return WebInspector._isMac;
}
WebInspector.isWin = function()
{
if (typeof WebInspector._isWin === "undefined")
WebInspector._isWin = WebInspector.platform() === "windows";
return WebInspector._isWin;
}
WebInspector.PlatformFlavor = {
WindowsVista: "windows-vista",
MacTiger: "mac-tiger",
MacLeopard: "mac-leopard",
MacSnowLeopard: "mac-snowleopard",
MacLion: "mac-lion",
MacMountainLion: "mac-mountain-lion"
}
WebInspector.platformFlavor = function()
{
function detectFlavor()
{
const userAgent = navigator.userAgent;
if (WebInspector.platform() === "windows") {
var match = userAgent.match(/Windows NT (\d+)\.(?:\d+)/);
if (match && match[1] >= 6)
return WebInspector.PlatformFlavor.WindowsVista;
return null;
} else if (WebInspector.platform() === "mac") {
var match = userAgent.match(/Mac OS X\s*(?:(\d+)_(\d+))?/);
if (!match || match[1] != 10)
return WebInspector.PlatformFlavor.MacSnowLeopard;
switch (Number(match[2])) {
case 4:
return WebInspector.PlatformFlavor.MacTiger;
case 5:
return WebInspector.PlatformFlavor.MacLeopard;
case 6:
return WebInspector.PlatformFlavor.MacSnowLeopard;
case 7:
return WebInspector.PlatformFlavor.MacLion;
case 8:
return WebInspector.PlatformFlavor.MacMountainLion;
default:
return "";
}
}
}
if (!WebInspector._platformFlavor)
WebInspector._platformFlavor = detectFlavor();
return WebInspector._platformFlavor;
}
WebInspector.port = function()
{
if (!WebInspector._port)
WebInspector._port = InspectorFrontendHost.port();
return WebInspector._port;
}
WebInspector.installPortStyles = function()
{
var platform = WebInspector.platform();
document.body.classList.add("platform-" + platform);
var flavor = WebInspector.platformFlavor();
if (flavor)
document.body.classList.add("platform-" + flavor);
var port = WebInspector.port();
document.body.classList.add("port-" + port);
}
WebInspector._windowFocused = function(event)
{
if (event.target.document.nodeType === Node.DOCUMENT_NODE)
document.body.classList.remove("inactive");
}
WebInspector._windowBlurred = function(event)
{
if (event.target.document.nodeType === Node.DOCUMENT_NODE)
document.body.classList.add("inactive");
}
WebInspector.previousFocusElement = function()
{
return WebInspector._previousFocusElement;
}
WebInspector.currentFocusElement = function()
{
return WebInspector._currentFocusElement;
}
WebInspector._focusChanged = function(event)
{
WebInspector.setCurrentFocusElement(event.target);
}
WebInspector._textInputTypes = ["text", "search", "tel", "url", "email", "password"].keySet();
WebInspector._isTextEditingElement = function(element)
{
if (element instanceof HTMLInputElement)
return element.type in WebInspector._textInputTypes;
if (element instanceof HTMLTextAreaElement)
return true;
return false;
}
WebInspector.setCurrentFocusElement = function(x)
{
if (WebInspector._glassPane && x && !WebInspector._glassPane.element.isAncestor(x))
return;
if (WebInspector._currentFocusElement !== x)
WebInspector._previousFocusElement = WebInspector._currentFocusElement;
WebInspector._currentFocusElement = x;
if (WebInspector._currentFocusElement) {
WebInspector._currentFocusElement.focus();
// Make a caret selection inside the new element if there isn't a range selection and there isn't already a caret selection inside.
// This is needed (at least) to remove caret from console when focus is moved to some element in the panel.
// The code below should not be applied to text fields and text areas, hence _isTextEditingElement check.
var selection = window.getSelection();
if (!WebInspector._isTextEditingElement(WebInspector._currentFocusElement) && selection.isCollapsed && !WebInspector._currentFocusElement.isInsertionCaretInside()) {
var selectionRange = WebInspector._currentFocusElement.ownerDocument.createRange();
selectionRange.setStart(WebInspector._currentFocusElement, 0);
selectionRange.setEnd(WebInspector._currentFocusElement, 0);
selection.removeAllRanges();
selection.addRange(selectionRange);
}
} else if (WebInspector._previousFocusElement)
WebInspector._previousFocusElement.blur();
}
WebInspector.restoreFocusFromElement = function(element)
{
if (element && element.isSelfOrAncestor(WebInspector.currentFocusElement()))
WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement());
}
WebInspector.setToolbarColors = function(backgroundColor, color)
{
if (!WebInspector._themeStyleElement) {
WebInspector._themeStyleElement = document.createElement("style");
document.head.appendChild(WebInspector._themeStyleElement);
}
var parsedColor = WebInspector.Color.parse(color);
var shadowColor = parsedColor ? parsedColor.invert().setAlpha(0.33).toString(WebInspector.Color.Format.RGBA) : "white";
var prefix = WebInspector.isMac() ? "body:not(.undocked)" : "";
WebInspector._themeStyleElement.textContent =
String.sprintf(
"%s .toolbar-background {\
background-image: none !important;\
background-color: %s !important;\
color: %s !important;\
}", prefix, backgroundColor, color) +
String.sprintf(
"%s .toolbar-background button.status-bar-item .glyph, %s .toolbar-background button.status-bar-item .long-click-glyph {\
background-color: %s;\
}", prefix, prefix, color) +
String.sprintf(
"%s .toolbar-background button.status-bar-item .glyph.shadow, %s .toolbar-background button.status-bar-item .long-click-glyph.shadow {\
background-color: %s;\
}", prefix, prefix, shadowColor);
}
WebInspector.resetToolbarColors = function()
{
if (WebInspector._themeStyleElement)
WebInspector._themeStyleElement.textContent = "";
}
/**
* @param {!Element} element
* @param {number} offset
* @param {number} length
* @param {!Array.<!Object>=} domChanges
*/
WebInspector.highlightSearchResult = function(element, offset, length, domChanges)
{
var result = WebInspector.highlightSearchResults(element, [new WebInspector.SourceRange(offset, length)], domChanges);
return result.length ? result[0] : null;
}
/**
* @param {!Element} element
* @param {!Array.<!WebInspector.SourceRange>} resultRanges
* @param {!Array.<!Object>=} changes
*/
WebInspector.highlightSearchResults = function(element, resultRanges, changes)
{
return WebInspector.highlightRangesWithStyleClass(element, resultRanges, "highlighted-search-result", changes);
}
/**
* @param {!Element} element
* @param {!Array.<!WebInspector.SourceRange>} resultRanges
* @param {string} styleClass
* @param {!Array.<!Object>=} changes
*/
WebInspector.highlightRangesWithStyleClass = function(element, resultRanges, styleClass, changes)
{
changes = changes || [];
var highlightNodes = [];
var lineText = element.textContent;
var ownerDocument = element.ownerDocument;
var textNodeSnapshot = ownerDocument.evaluate(".//text()", element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
var snapshotLength = textNodeSnapshot.snapshotLength;
if (snapshotLength === 0)
return highlightNodes;
var nodeRanges = [];
var rangeEndOffset = 0;
for (var i = 0; i < snapshotLength; ++i) {
var range = {};
range.offset = rangeEndOffset;
range.length = textNodeSnapshot.snapshotItem(i).textContent.length;
rangeEndOffset = range.offset + range.length;
nodeRanges.push(range);
}
var startIndex = 0;
for (var i = 0; i < resultRanges.length; ++i) {
var startOffset = resultRanges[i].offset;
var endOffset = startOffset + resultRanges[i].length;
while (startIndex < snapshotLength && nodeRanges[startIndex].offset + nodeRanges[startIndex].length <= startOffset)
startIndex++;
var endIndex = startIndex;
while (endIndex < snapshotLength && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset)
endIndex++;
if (endIndex === snapshotLength)
break;
var highlightNode = ownerDocument.createElement("span");
highlightNode.className = styleClass;
highlightNode.textContent = lineText.substring(startOffset, endOffset);
var lastTextNode = textNodeSnapshot.snapshotItem(endIndex);
var lastText = lastTextNode.textContent;
lastTextNode.textContent = lastText.substring(endOffset - nodeRanges[endIndex].offset);
changes.push({ node: lastTextNode, type: "changed", oldText: lastText, newText: lastTextNode.textContent });
if (startIndex === endIndex) {
lastTextNode.parentElement.insertBefore(highlightNode, lastTextNode);
changes.push({ node: highlightNode, type: "added", nextSibling: lastTextNode, parent: lastTextNode.parentElement });
highlightNodes.push(highlightNode);
var prefixNode = ownerDocument.createTextNode(lastText.substring(0, startOffset - nodeRanges[startIndex].offset));
lastTextNode.parentElement.insertBefore(prefixNode, highlightNode);
changes.push({ node: prefixNode, type: "added", nextSibling: highlightNode, parent: lastTextNode.parentElement });
} else {
var firstTextNode = textNodeSnapshot.snapshotItem(startIndex);
var firstText = firstTextNode.textContent;
var anchorElement = firstTextNode.nextSibling;
firstTextNode.parentElement.insertBefore(highlightNode, anchorElement);
changes.push({ node: highlightNode, type: "added", nextSibling: anchorElement, parent: firstTextNode.parentElement });
highlightNodes.push(highlightNode);
firstTextNode.textContent = firstText.substring(0, startOffset - nodeRanges[startIndex].offset);
changes.push({ node: firstTextNode, type: "changed", oldText: firstText, newText: firstTextNode.textContent });
for (var j = startIndex + 1; j < endIndex; j++) {
var textNode = textNodeSnapshot.snapshotItem(j);
var text = textNode.textContent;
textNode.textContent = "";
changes.push({ node: textNode, type: "changed", oldText: text, newText: textNode.textContent });
}
}
startIndex = endIndex;
nodeRanges[startIndex].offset = endOffset;
nodeRanges[startIndex].length = lastTextNode.textContent.length;
}
return highlightNodes;
}
WebInspector.applyDomChanges = function(domChanges)
{
for (var i = 0, size = domChanges.length; i < size; ++i) {
var entry = domChanges[i];
switch (entry.type) {
case "added":
entry.parent.insertBefore(entry.node, entry.nextSibling);
break;
case "changed":
entry.node.textContent = entry.newText;
break;
}
}
}
WebInspector.revertDomChanges = function(domChanges)
{
for (var i = domChanges.length - 1; i >= 0; --i) {
var entry = domChanges[i];
switch (entry.type) {
case "added":
entry.node.remove();
break;
case "changed":
entry.node.textContent = entry.oldText;
break;
}
}
}
WebInspector._coalescingLevel = 0;
WebInspector.startBatchUpdate = function()
{
if (!WebInspector._coalescingLevel)
WebInspector._postUpdateHandlers = new Map();
WebInspector._coalescingLevel++;
}
WebInspector.endBatchUpdate = function()
{
if (--WebInspector._coalescingLevel)
return;
var handlers = WebInspector._postUpdateHandlers;
delete WebInspector._postUpdateHandlers;
var keys = handlers.keys();
for (var i = 0; i < keys.length; ++i) {
var object = keys[i];
var methods = handlers.get(object).keys();
for (var j = 0; j < methods.length; ++j)
methods[j].call(object);
}
}
/**
* @param {!Object} object
* @param {function()} method
*/
WebInspector.invokeOnceAfterBatchUpdate = function(object, method)
{
if (!WebInspector._coalescingLevel) {
method.call(object);
return;
}
var methods = WebInspector._postUpdateHandlers.get(object);
if (!methods) {
methods = new Map();
WebInspector._postUpdateHandlers.put(object, methods);
}
methods.put(method);
}
/**
* This bogus view is needed to load/unload CodeMirror-related CSS on demand.
*
* @constructor
* @extends {WebInspector.View}
*/
WebInspector.CodeMirrorCSSLoadView = function()
{
WebInspector.View.call(this);
this.element.classList.add("hidden");
this.registerRequiredCSS("cm/codemirror.css");
this.registerRequiredCSS("cm/cmdevtools.css");
}
WebInspector.CodeMirrorCSSLoadView.prototype = {
__proto__: WebInspector.View.prototype
}
;(function() {
/**
* @this {Window}
*/
function windowLoaded()
{
window.addEventListener("focus", WebInspector._windowFocused, false);
window.addEventListener("blur", WebInspector._windowBlurred, false);
document.addEventListener("focus", WebInspector._focusChanged.bind(this), true);
window.removeEventListener("DOMContentLoaded", windowLoaded, false);
}
window.addEventListener("DOMContentLoaded", windowLoaded, false);
})();