| /* |
| * 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. |
| */ |
| |
| WebInspector.highlightedSearchResultClassName = "highlighted-search-result"; |
| WebInspector.highlightedCurrentSearchResultClassName = "current-search-result"; |
| |
| /** |
| * @param {!Element} element |
| * @param {?function(!MouseEvent): boolean} elementDragStart |
| * @param {function(!MouseEvent)} elementDrag |
| * @param {?function(!MouseEvent)} elementDragEnd |
| * @param {string} cursor |
| * @param {?string=} hoverCursor |
| * @param {number=} startDelay |
| */ |
| WebInspector.installDragHandle = function(element, elementDragStart, elementDrag, elementDragEnd, cursor, hoverCursor, startDelay) |
| { |
| /** |
| * @param {!Event} event |
| */ |
| function onMouseDown(event) |
| { |
| var dragStart = WebInspector.elementDragStart.bind(WebInspector, element, elementDragStart, elementDrag, elementDragEnd, cursor, event); |
| if (!startDelay) |
| dragStart(); |
| startTimer = setTimeout(dragStart, startDelay || 0); |
| } |
| |
| function onMouseUp() |
| { |
| if (startTimer) |
| clearInterval(startTimer); |
| startTimer = null; |
| } |
| |
| var startTimer; |
| element.addEventListener("mousedown", onMouseDown, false); |
| if (startDelay) |
| element.addEventListener("mouseup", onMouseUp, false); |
| if (hoverCursor !== null) |
| element.style.cursor = hoverCursor || cursor; |
| } |
| |
| /** |
| * @param {!Element} targetElement |
| * @param {?function(!MouseEvent):boolean} elementDragStart |
| * @param {function(!MouseEvent)} elementDrag |
| * @param {?function(!MouseEvent)} elementDragEnd |
| * @param {string} cursor |
| * @param {!Event} event |
| */ |
| WebInspector.elementDragStart = function(targetElement, 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; |
| WebInspector._dragEventsTargetDocument = targetDocument; |
| WebInspector._dragEventsTargetDocumentTop = targetDocument.defaultView.top.document; |
| |
| targetDocument.addEventListener("mousemove", WebInspector._elementDragMove, true); |
| targetDocument.addEventListener("mouseup", WebInspector._elementDragEnd, true); |
| targetDocument.addEventListener("mouseout", WebInspector._mouseOutWhileDragging, true); |
| if (targetDocument !== WebInspector._dragEventsTargetDocumentTop) |
| WebInspector._dragEventsTargetDocumentTop.addEventListener("mouseup", WebInspector._elementDragEnd, true); |
| |
| if (typeof cursor === "string") { |
| WebInspector._restoreCursorAfterDrag = restoreCursor.bind(null, targetElement.style.cursor); |
| targetElement.style.cursor = cursor; |
| targetDocument.body.style.cursor = cursor; |
| } |
| function restoreCursor(oldCursor) |
| { |
| targetDocument.body.style.removeProperty("cursor"); |
| targetElement.style.cursor = oldCursor; |
| WebInspector._restoreCursorAfterDrag = null; |
| } |
| event.preventDefault(); |
| } |
| |
| WebInspector._mouseOutWhileDragging = function() |
| { |
| var document = WebInspector._mouseOutWhileDraggingTargetDocument; |
| WebInspector._unregisterMouseOutWhileDragging(); |
| WebInspector._elementDraggingGlassPane = new WebInspector.GlassPane(document); |
| } |
| |
| WebInspector._unregisterMouseOutWhileDragging = function() |
| { |
| if (!WebInspector._mouseOutWhileDraggingTargetDocument) |
| return; |
| WebInspector._mouseOutWhileDraggingTargetDocument.removeEventListener("mouseout", WebInspector._mouseOutWhileDragging, true); |
| delete WebInspector._mouseOutWhileDraggingTargetDocument; |
| } |
| |
| WebInspector._unregisterDragEvents = function() |
| { |
| if (!WebInspector._dragEventsTargetDocument) |
| return; |
| WebInspector._dragEventsTargetDocument.removeEventListener("mousemove", WebInspector._elementDragMove, true); |
| WebInspector._dragEventsTargetDocument.removeEventListener("mouseup", WebInspector._elementDragEnd, true); |
| if (WebInspector._dragEventsTargetDocument !== WebInspector._dragEventsTargetDocumentTop) |
| WebInspector._dragEventsTargetDocumentTop.removeEventListener("mouseup", WebInspector._elementDragEnd, true); |
| delete WebInspector._dragEventsTargetDocument; |
| delete WebInspector._dragEventsTargetDocumentTop; |
| } |
| |
| /** |
| * @param {!Event} event |
| */ |
| WebInspector._elementDragMove = function(event) |
| { |
| if (event.buttons !== 1) { |
| WebInspector._elementDragEnd(event); |
| return; |
| } |
| |
| if (WebInspector._elementDraggingEventListener(/** @type {!MouseEvent} */ (event))) |
| WebInspector._cancelDragEvents(event); |
| } |
| |
| /** |
| * @param {!Event} event |
| */ |
| WebInspector._cancelDragEvents = function(event) |
| { |
| WebInspector._unregisterDragEvents(); |
| WebInspector._unregisterMouseOutWhileDragging(); |
| |
| if (WebInspector._restoreCursorAfterDrag) |
| WebInspector._restoreCursorAfterDrag(); |
| |
| 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)); |
| } |
| |
| /** |
| * @param {!Element} element |
| * @param {function(number, number, !MouseEvent): boolean} elementDragStart |
| * @param {function(number, number)} elementDrag |
| * @param {function(number, number)} elementDragEnd |
| * @param {string} cursor |
| * @param {?string=} hoverCursor |
| * @param {number=} startDelay |
| * @param {number=} friction |
| */ |
| WebInspector.installInertialDragHandle = function(element, elementDragStart, elementDrag, elementDragEnd, cursor, hoverCursor, startDelay, friction) |
| { |
| WebInspector.installDragHandle(element, drag.bind(null, elementDragStart), drag.bind(null, elementDrag), dragEnd, cursor, hoverCursor, startDelay); |
| if (typeof friction !== "number") |
| friction = 50; |
| var lastX; |
| var lastY; |
| var lastTime; |
| var velocityX; |
| var velocityY; |
| var holding = false; |
| |
| /** |
| * @param {function(number, number, !MouseEvent): boolean} callback |
| * @param {!MouseEvent} event |
| * @return {boolean} |
| */ |
| function drag(callback, event) |
| { |
| lastTime = window.performance.now(); |
| lastX = event.pageX; |
| lastY = event.pageY; |
| holding = true; |
| return callback(lastX, lastY, event); |
| } |
| |
| /** |
| * @param {!MouseEvent} event |
| */ |
| function dragEnd(event) |
| { |
| var now = window.performance.now(); |
| var duration = now - lastTime || 1; |
| const maxVelocity = 4; // 4px per millisecond. |
| velocityX = Number.constrain((event.pageX - lastX) / duration, -maxVelocity, maxVelocity); |
| velocityY = Number.constrain((event.pageY - lastY) / duration, -maxVelocity, maxVelocity); |
| lastX = event.pageX; |
| lastY = event.pageY; |
| lastTime = now; |
| holding = false; |
| animationStep(); |
| } |
| |
| function animationStep() |
| { |
| var v2 = velocityX * velocityX + velocityY * velocityY; |
| if (v2 < 0.001 || holding) { |
| elementDragEnd(lastX, lastY); |
| return; |
| } |
| element.window().requestAnimationFrame(animationStep); |
| var now = window.performance.now(); |
| var duration = now - lastTime; |
| if (!duration) |
| return; |
| lastTime = now; |
| lastX += velocityX * duration; |
| lastY += velocityY * duration; |
| var k = Math.pow(1 / (1 + friction), duration / 1000); |
| velocityX *= k; |
| velocityY *= k; |
| elementDrag(lastX, lastY); |
| } |
| } |
| |
| /** |
| * @constructor |
| * @param {!Document} document |
| * @param {boolean=} dimmed |
| */ |
| WebInspector.GlassPane = function(document, dimmed) |
| { |
| this.element = createElement("div"); |
| var background = dimmed ? "rgba(255, 255, 255, 0.5)" : "transparent"; |
| this._zIndex = WebInspector._glassPane ? WebInspector._glassPane._zIndex + 1000 : 3000; // Deliberately starts with 3000 to hide other z-indexed elements below. |
| this.element.style.cssText = "position:absolute;top:0;bottom:0;left:0;right:0;background-color:" + background + ";z-index:" + this._zIndex + ";overflow:hidden;"; |
| document.body.appendChild(this.element); |
| WebInspector._glassPane = this; |
| } |
| |
| WebInspector.GlassPane.prototype = { |
| dispose: function() |
| { |
| delete WebInspector._glassPane; |
| this.element.remove(); |
| } |
| } |
| |
| /** @type {!WebInspector.GlassPane|undefined} */ |
| WebInspector._glassPane; |
| |
| /** |
| * @param {?Node=} node |
| * @return {boolean} |
| */ |
| WebInspector.isBeingEdited = function(node) |
| { |
| if (!node || node.nodeType !== Node.ELEMENT_NODE) |
| return false; |
| var element = /** {!Element} */ (node); |
| 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.parentElementOrShadowHost(); |
| } |
| return false; |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| WebInspector.isEditing = function() |
| { |
| if (WebInspector.__editingCount) |
| return true; |
| |
| var element = WebInspector.currentFocusElement(); |
| if (!element) |
| return false; |
| return element.classList.contains("text-prompt") || element.nodeName === "INPUT" || element.nodeName === "TEXTAREA"; |
| } |
| |
| /** |
| * @param {!Element} element |
| * @param {boolean} value |
| * @return {boolean} |
| */ |
| 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; |
| } |
| |
| 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") { |
| // When shift is pressed while spinning mousewheel, delta comes as wheelDeltaX. |
| if (event.wheelDeltaY > 0 || event.wheelDeltaX > 0) |
| direction = "Up"; |
| else if (event.wheelDeltaY < 0 || event.wheelDeltaX < 0) |
| direction = "Down"; |
| } else { |
| if (event.key === "ArrowUp" || event.key === "PageUp") |
| direction = "Up"; |
| else if (event.key === "ArrowDown" || event.key === "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 mouseEvent = /** @type {!MouseEvent} */(event); |
| var number = parseInt(hexString, 16); |
| if (isNaN(number) || !isFinite(number)) |
| return hexString; |
| |
| var hexStrLen = hexString.length; |
| var channelLen = hexStrLen / 3; |
| |
| // Colors are either rgb or rrggbb. |
| if (channelLen !== 1 && channelLen !== 2) |
| return hexString; |
| |
| // Precision modifier keys work with both mousewheel and up/down keys. |
| // When ctrl is pressed, increase R by 1. |
| // When shift is pressed, increase G by 1. |
| // When alt is pressed, increase B by 1. |
| // If no shortcut keys are pressed then increase hex value by 1. |
| // Keys can be pressed together to increase RGB channels. e.g trying different shades. |
| var delta = 0; |
| if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(mouseEvent)) |
| delta += Math.pow(16, channelLen * 2); |
| if (mouseEvent.shiftKey) |
| delta += Math.pow(16, channelLen); |
| if (mouseEvent.altKey) |
| delta += 1; |
| if (delta === 0) |
| delta = 1; |
| if (direction === "Down") |
| delta *= -1; |
| |
| // Increase hex value by 1 and clamp from 0 ... maxValue. |
| var maxValue = Math.pow(16, hexStrLen) - 1; |
| var result = Number.constrain(number + delta, 0, maxValue); |
| |
| // Ensure the result length is the same as the original hex value. |
| var resultString = result.toString(16).toUpperCase(); |
| for (var i = 0, lengthDelta = hexStrLen - 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 mouseEvent = /** @type {!MouseEvent} */(event); |
| |
| // Precision modifier keys work with both mousewheel and up/down keys. |
| // When ctrl is pressed, increase by 100. |
| // When shift is pressed, increase by 10. |
| // When alt is pressed, increase by 0.1. |
| // Otherwise increase by 1. |
| var delta = 1; |
| if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(mouseEvent)) |
| delta = 100; |
| else if (mouseEvent.shiftKey) |
| delta = 10; |
| else if (mouseEvent.altKey) |
| delta = 0.1; |
| |
| if (direction === "Down") |
| delta *= -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 + delta).toFixed(6)); |
| if (!String(result).match(WebInspector.CSSNumberRegex)) |
| return null; |
| |
| return result; |
| } |
| |
| /** |
| * @param {string} wordString |
| * @param {!Event} event |
| * @param {function(string, number, string):string=} customNumberHandler |
| * @return {?string} |
| */ |
| WebInspector.createReplacementString = function(wordString, event, customNumberHandler) |
| { |
| 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); |
| |
| replacementString = customNumberHandler ? customNumberHandler(prefix, number, suffix) : 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 null; |
| |
| replacementString = customNumberHandler ? customNumberHandler(prefix, number, suffix) : prefix + number + suffix; |
| } |
| } |
| return replacementString || null; |
| } |
| |
| /** |
| * @param {!Event} event |
| * @param {!Element} element |
| * @param {function(string,string)=} finishHandler |
| * @param {function(string)=} suggestionHandler |
| * @param {function(string, number, string):string=} customNumberHandler |
| * @return {boolean} |
| */ |
| WebInspector.handleElementValueModifications = function(event, element, finishHandler, suggestionHandler, customNumberHandler) |
| { |
| /** |
| * @return {?Range} |
| * @suppressGlobalPropertiesCheck |
| */ |
| function createRange() |
| { |
| return document.createRange(); |
| } |
| |
| var arrowKeyOrMouseWheelEvent = (event.key === "ArrowUp" || event.key === "ArrowDown" || event.type === "mousewheel"); |
| var pageKeyPressed = (event.key === "PageUp" || event.key === "PageDown"); |
| if (!arrowKeyOrMouseWheelEvent && !pageKeyPressed) |
| return false; |
| |
| var selection = element.getComponentSelection(); |
| 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 = WebInspector.createReplacementString(wordString, event, customNumberHandler); |
| |
| if (replacementString) { |
| var replacementTextNode = createTextNode(replacementString); |
| |
| wordRange.deleteContents(); |
| wordRange.insertNode(replacementTextNode); |
| |
| var finalSelectionRange = 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 {number} ms |
| * @param {number=} precision |
| * @return {string} |
| */ |
| Number.preciseMillisToString = function(ms, precision) |
| { |
| precision = precision || 0; |
| var format = "%." + precision + "f\u2009ms"; |
| return WebInspector.UIString(format, ms); |
| } |
| |
| /** @type {!WebInspector.UIStringFormat} */ |
| WebInspector._subMillisFormat = new WebInspector.UIStringFormat("%.2f\u2009ms"); |
| |
| /** @type {!WebInspector.UIStringFormat} */ |
| WebInspector._millisFormat = new WebInspector.UIStringFormat("%.0f\u2009ms"); |
| |
| /** @type {!WebInspector.UIStringFormat} */ |
| WebInspector._secondsFormat = new WebInspector.UIStringFormat("%.2f\u2009s"); |
| |
| /** @type {!WebInspector.UIStringFormat} */ |
| WebInspector._minutesFormat = new WebInspector.UIStringFormat("%.1f\u2009min"); |
| |
| /** @type {!WebInspector.UIStringFormat} */ |
| WebInspector._hoursFormat = new WebInspector.UIStringFormat("%.1f\u2009hrs"); |
| |
| /** @type {!WebInspector.UIStringFormat} */ |
| WebInspector._daysFormat = new WebInspector.UIStringFormat("%.1f\u2009days"); |
| |
| /** |
| * @param {number} ms |
| * @param {boolean=} higherResolution |
| * @return {string} |
| */ |
| Number.millisToString = function(ms, higherResolution) |
| { |
| if (!isFinite(ms)) |
| return "-"; |
| |
| if (ms === 0) |
| return "0"; |
| |
| if (higherResolution && ms < 1000) |
| return WebInspector._subMillisFormat.format(ms); |
| else if (ms < 1000) |
| return WebInspector._millisFormat.format(ms); |
| |
| var seconds = ms / 1000; |
| if (seconds < 60) |
| return WebInspector._secondsFormat.format(seconds); |
| |
| var minutes = seconds / 60; |
| if (minutes < 60) |
| return WebInspector._minutesFormat.format(minutes); |
| |
| var hours = minutes / 60; |
| if (hours < 24) |
| return WebInspector._hoursFormat.format(hours); |
| |
| var days = hours / 24; |
| return WebInspector._daysFormat.format(days); |
| } |
| |
| /** |
| * @param {number} seconds |
| * @param {boolean=} higherResolution |
| * @return {string} |
| */ |
| Number.secondsToString = function(seconds, higherResolution) |
| { |
| if (!isFinite(seconds)) |
| return "-"; |
| return Number.millisToString(seconds * 1000, higherResolution); |
| } |
| |
| /** |
| * @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); |
| } |
| |
| /** |
| * @param {number} num |
| * @return {string} |
| */ |
| 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; |
| } |
| |
| /** |
| * @param {string} format |
| * @param {?ArrayLike} substitutions |
| * @return {!Element} |
| */ |
| WebInspector.formatLocalized = function(format, substitutions) |
| { |
| var formatters = { |
| s: substitution => substitution |
| }; |
| /** |
| * @param {!Element} a |
| * @param {string|!Element} b |
| * @return {!Element} |
| */ |
| function append(a, b) |
| { |
| a.appendChild(typeof b === "string" ? createTextNode(b) : b); |
| return a; |
| } |
| return String.format(WebInspector.UIString(format), substitutions, formatters, createElement("span"), append).formattedResult; |
| } |
| |
| /** |
| * @return {string} |
| */ |
| WebInspector.openLinkExternallyLabel = function() |
| { |
| return WebInspector.UIString.capitalize("Open ^link in ^new ^tab"); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| WebInspector.copyLinkAddressLabel = function() |
| { |
| return WebInspector.UIString.capitalize("Copy ^link ^address"); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| WebInspector.anotherProfilerActiveLabel = function() |
| { |
| return WebInspector.UIString("Another profiler is already active"); |
| } |
| |
| /** |
| * @param {string|undefined} description |
| * @return {string} |
| */ |
| WebInspector.asyncStackTraceLabel = function(description) |
| { |
| if (description) |
| return description + " " + WebInspector.UIString("(async)"); |
| return WebInspector.UIString("Async Call"); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| WebInspector.manageBlackboxingSettingsTabLabel = function() |
| { |
| return WebInspector.UIString("Blackboxing"); |
| } |
| |
| /** |
| * @param {!Element} element |
| */ |
| WebInspector.installComponentRootStyles = function(element) |
| { |
| WebInspector.appendStyle(element, "ui/inspectorCommon.css"); |
| WebInspector.themeSupport.injectHighlightStyleSheets(element); |
| element.classList.add("platform-" + WebInspector.platform()); |
| } |
| |
| /** |
| * @param {!Element} element |
| * @param {string=} cssFile |
| * @return {!DocumentFragment} |
| */ |
| WebInspector.createShadowRootWithCoreStyles = function(element, cssFile) |
| { |
| var shadowRoot = element.createShadowRoot(); |
| WebInspector.appendStyle(shadowRoot, "ui/inspectorCommon.css"); |
| WebInspector.themeSupport.injectHighlightStyleSheets(shadowRoot); |
| if (cssFile) |
| WebInspector.appendStyle(shadowRoot, cssFile); |
| shadowRoot.addEventListener("focus", WebInspector._focusChanged.bind(WebInspector), true); |
| return shadowRoot; |
| } |
| |
| /** |
| * @param {!Document} document |
| * @param {!Event} event |
| */ |
| WebInspector._windowFocused = function(document, event) |
| { |
| if (event.target.document.nodeType === Node.DOCUMENT_NODE) |
| document.body.classList.remove("inactive"); |
| } |
| |
| /** |
| * @param {!Document} document |
| * @param {!Event} event |
| */ |
| WebInspector._windowBlurred = function(document, event) |
| { |
| if (event.target.document.nodeType === Node.DOCUMENT_NODE) |
| document.body.classList.add("inactive"); |
| } |
| |
| /** |
| * @return {!Element} |
| */ |
| WebInspector.previousFocusElement = function() |
| { |
| return WebInspector._previousFocusElement; |
| } |
| |
| /** |
| * @return {!Element} |
| */ |
| WebInspector.currentFocusElement = function() |
| { |
| return WebInspector._currentFocusElement; |
| } |
| |
| /** |
| * @param {!Event} event |
| */ |
| WebInspector._focusChanged = function(event) |
| { |
| var node = event.deepActiveElement(); |
| WebInspector.setCurrentFocusElement(node); |
| } |
| |
| /** |
| * @param {!Document} document |
| * @param {!Event} event |
| */ |
| WebInspector._documentBlurred = function(document, event) |
| { |
| // We want to know when currentFocusElement loses focus to nowhere. |
| // This is the case when event.relatedTarget is null (no element is being focused) |
| // and document.activeElement is reset to default (this is not a window blur). |
| if (!event.relatedTarget && document.activeElement === document.body) |
| WebInspector.setCurrentFocusElement(null); |
| } |
| |
| 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; |
| } |
| |
| /** |
| * @param {?Node} x |
| */ |
| WebInspector.setCurrentFocusElement = function(x) |
| { |
| if (WebInspector._glassPane && x && !WebInspector._glassPane.element.isAncestor(x)) |
| return; |
| if (x && !x.ownerDocument.isAncestor(x)) |
| return; |
| if (WebInspector._currentFocusElement !== x) |
| WebInspector._previousFocusElement = WebInspector._currentFocusElement; |
| WebInspector._currentFocusElement = x; |
| |
| if (x) { |
| x.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 = x.getComponentSelection(); |
| if (!WebInspector._isTextEditingElement(x) && selection.isCollapsed && !x.isInsertionCaretInside()) { |
| var selectionRange = x.ownerDocument.createRange(); |
| selectionRange.setStart(x, 0); |
| selectionRange.setEnd(x, 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()); |
| } |
| |
| /** |
| * @param {!Element} element |
| * @param {number} offset |
| * @param {number} length |
| * @param {!Array.<!Object>=} domChanges |
| * @return {?Element} |
| */ |
| 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 |
| * @return {!Array.<!Element>} |
| */ |
| WebInspector.highlightSearchResults = function(element, resultRanges, changes) |
| { |
| return WebInspector.highlightRangesWithStyleClass(element, resultRanges, WebInspector.highlightedSearchResultClassName, changes); |
| } |
| |
| /** |
| * @param {!Element} element |
| * @param {string} className |
| */ |
| WebInspector.runCSSAnimationOnce = function(element, className) |
| { |
| function animationEndCallback() |
| { |
| element.classList.remove(className); |
| element.removeEventListener("webkitAnimationEnd", animationEndCallback, false); |
| } |
| |
| if (element.classList.contains(className)) |
| element.classList.remove(className); |
| |
| element.addEventListener("webkitAnimationEnd", animationEndCallback, false); |
| element.classList.add(className); |
| } |
| |
| /** |
| * @param {!Element} element |
| * @param {!Array.<!WebInspector.SourceRange>} resultRanges |
| * @param {string} styleClass |
| * @param {!Array.<!Object>=} changes |
| * @return {!Array.<!Element>} |
| */ |
| WebInspector.highlightRangesWithStyleClass = function(element, resultRanges, styleClass, changes) |
| { |
| changes = changes || []; |
| var highlightNodes = []; |
| var textNodes = element.childTextNodes(); |
| var lineText = textNodes.map(function(node) { return node.textContent; }).join(""); |
| var ownerDocument = element.ownerDocument; |
| |
| if (textNodes.length === 0) |
| return highlightNodes; |
| |
| var nodeRanges = []; |
| var rangeEndOffset = 0; |
| for (var i = 0; i < textNodes.length; ++i) { |
| var range = {}; |
| range.offset = rangeEndOffset; |
| range.length = textNodes[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 < textNodes.length && nodeRanges[startIndex].offset + nodeRanges[startIndex].length <= startOffset) |
| startIndex++; |
| var endIndex = startIndex; |
| while (endIndex < textNodes.length && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset) |
| endIndex++; |
| if (endIndex === textNodes.length) |
| break; |
| |
| var highlightNode = ownerDocument.createElement("span"); |
| highlightNode.className = styleClass; |
| highlightNode.textContent = lineText.substring(startOffset, endOffset); |
| |
| var lastTextNode = textNodes[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 = textNodes[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 = textNodes[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; |
| } |
| } |
| } |
| |
| /** |
| * @param {!Element} element |
| * @param {?Element=} containerElement |
| * @return {!Size} |
| */ |
| WebInspector.measurePreferredSize = function(element, containerElement) |
| { |
| containerElement = containerElement || element.ownerDocument.body; |
| containerElement.appendChild(element); |
| element.positionAt(0, 0); |
| var result = new Size(element.offsetWidth, element.offsetHeight); |
| element.positionAt(undefined, undefined); |
| element.remove(); |
| return result; |
| } |
| |
| /** |
| * @constructor |
| * @param {boolean} autoInvoke |
| */ |
| WebInspector.InvokeOnceHandlers = function(autoInvoke) |
| { |
| this._handlers = null; |
| this._autoInvoke = autoInvoke; |
| } |
| |
| WebInspector.InvokeOnceHandlers.prototype = { |
| /** |
| * @param {!Object} object |
| * @param {function()} method |
| */ |
| add: function(object, method) |
| { |
| if (!this._handlers) { |
| this._handlers = new Map(); |
| if (this._autoInvoke) |
| this.scheduleInvoke(); |
| } |
| var methods = this._handlers.get(object); |
| if (!methods) { |
| methods = new Set(); |
| this._handlers.set(object, methods); |
| } |
| methods.add(method); |
| }, |
| |
| /** |
| * @suppressGlobalPropertiesCheck |
| */ |
| scheduleInvoke: function() |
| { |
| if (this._handlers) |
| requestAnimationFrame(this._invoke.bind(this)); |
| }, |
| |
| _invoke: function() |
| { |
| var handlers = this._handlers; |
| this._handlers = null; |
| var keys = handlers.keysArray(); |
| for (var i = 0; i < keys.length; ++i) { |
| var object = keys[i]; |
| var methods = handlers.get(object).valuesArray(); |
| for (var j = 0; j < methods.length; ++j) |
| methods[j].call(object); |
| } |
| } |
| } |
| |
| WebInspector._coalescingLevel = 0; |
| WebInspector._postUpdateHandlers = null; |
| |
| WebInspector.startBatchUpdate = function() |
| { |
| if (!WebInspector._coalescingLevel++) |
| WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(false); |
| } |
| |
| WebInspector.endBatchUpdate = function() |
| { |
| if (--WebInspector._coalescingLevel) |
| return; |
| WebInspector._postUpdateHandlers.scheduleInvoke(); |
| WebInspector._postUpdateHandlers = null; |
| } |
| |
| /** |
| * @param {!Object} object |
| * @param {function()} method |
| */ |
| WebInspector.invokeOnceAfterBatchUpdate = function(object, method) |
| { |
| if (!WebInspector._postUpdateHandlers) |
| WebInspector._postUpdateHandlers = new WebInspector.InvokeOnceHandlers(true); |
| WebInspector._postUpdateHandlers.add(object, method); |
| } |
| |
| /** |
| * @param {!Window} window |
| * @param {!Function} func |
| * @param {!Array.<{from:number, to:number}>} params |
| * @param {number} frames |
| * @param {function()=} animationComplete |
| * @return {function()} |
| */ |
| WebInspector.animateFunction = function(window, func, params, frames, animationComplete) |
| { |
| var values = new Array(params.length); |
| var deltas = new Array(params.length); |
| for (var i = 0; i < params.length; ++i) { |
| values[i] = params[i].from; |
| deltas[i] = (params[i].to - params[i].from) / frames; |
| } |
| |
| var raf = window.requestAnimationFrame(animationStep); |
| |
| var framesLeft = frames; |
| |
| function animationStep() |
| { |
| if (--framesLeft < 0) { |
| if (animationComplete) |
| animationComplete(); |
| return; |
| } |
| for (var i = 0; i < params.length; ++i) { |
| if (params[i].to > params[i].from) |
| values[i] = Number.constrain(values[i] + deltas[i], params[i].from, params[i].to); |
| else |
| values[i] = Number.constrain(values[i] + deltas[i], params[i].to, params[i].from); |
| } |
| func.apply(null, values); |
| raf = window.requestAnimationFrame(animationStep); |
| } |
| |
| function cancelAnimation() |
| { |
| window.cancelAnimationFrame(raf); |
| } |
| |
| return cancelAnimation; |
| } |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.Object} |
| * @param {!Element} element |
| * @param {function(!Event)} callback |
| */ |
| WebInspector.LongClickController = function(element, callback) |
| { |
| this._element = element; |
| this._callback = callback; |
| this._enable(); |
| } |
| |
| WebInspector.LongClickController.prototype = { |
| reset: function() |
| { |
| if (this._longClickInterval) { |
| clearInterval(this._longClickInterval); |
| delete this._longClickInterval; |
| } |
| }, |
| |
| _enable: function() |
| { |
| if (this._longClickData) |
| return; |
| var boundMouseDown = mouseDown.bind(this); |
| var boundMouseUp = mouseUp.bind(this); |
| var boundReset = this.reset.bind(this); |
| |
| this._element.addEventListener("mousedown", boundMouseDown, false); |
| this._element.addEventListener("mouseout", boundReset, false); |
| this._element.addEventListener("mouseup", boundMouseUp, false); |
| this._element.addEventListener("click", boundReset, true); |
| |
| this._longClickData = { mouseUp: boundMouseUp, mouseDown: boundMouseDown, reset: boundReset }; |
| |
| /** |
| * @param {!Event} e |
| * @this {WebInspector.LongClickController} |
| */ |
| function mouseDown(e) |
| { |
| if (e.which !== 1) |
| return; |
| var callback = this._callback; |
| this._longClickInterval = setTimeout(callback.bind(null, e), 200); |
| } |
| |
| /** |
| * @param {!Event} e |
| * @this {WebInspector.LongClickController} |
| */ |
| function mouseUp(e) |
| { |
| if (e.which !== 1) |
| return; |
| this.reset(); |
| } |
| }, |
| |
| dispose: function() |
| { |
| if (!this._longClickData) |
| return; |
| this._element.removeEventListener("mousedown", this._longClickData.mouseDown, false); |
| this._element.removeEventListener("mouseout", this._longClickData.reset, false); |
| this._element.removeEventListener("mouseup", this._longClickData.mouseUp, false); |
| this._element.addEventListener("click", this._longClickData.reset, true); |
| delete this._longClickData; |
| }, |
| |
| __proto__: WebInspector.Object.prototype |
| } |
| |
| /** |
| * @param {!Document} document |
| * @param {!WebInspector.Setting} themeSetting |
| */ |
| WebInspector.initializeUIUtils = function(document, themeSetting) |
| { |
| document.defaultView.addEventListener("focus", WebInspector._windowFocused.bind(WebInspector, document), false); |
| document.defaultView.addEventListener("blur", WebInspector._windowBlurred.bind(WebInspector, document), false); |
| document.addEventListener("focus", WebInspector._focusChanged.bind(WebInspector), true); |
| document.addEventListener("blur", WebInspector._documentBlurred.bind(WebInspector, document), true); |
| |
| if (!WebInspector.themeSupport) |
| WebInspector.themeSupport = new WebInspector.ThemeSupport(themeSetting); |
| WebInspector.themeSupport.applyTheme(document); |
| |
| var body = /** @type {!Element} */ (document.body); |
| WebInspector.appendStyle(body, "ui/inspectorStyle.css"); |
| WebInspector.appendStyle(body, "ui/popover.css"); |
| WebInspector.appendStyle(body, "ui/sidebarPane.css"); |
| |
| } |
| |
| /** |
| * @param {string} name |
| * @return {string} |
| */ |
| WebInspector.beautifyFunctionName = function(name) |
| { |
| return name || WebInspector.UIString("(anonymous function)"); |
| } |
| |
| /** |
| * @param {string} localName |
| * @param {string} typeExtension |
| * @param {!Object} prototype |
| * @return {function()} |
| * @suppressGlobalPropertiesCheck |
| * @template T |
| */ |
| function registerCustomElement(localName, typeExtension, prototype) |
| { |
| return document.registerElement(typeExtension, { |
| prototype: Object.create(prototype), |
| extends: localName |
| }); |
| } |
| |
| /** |
| * @param {string} text |
| * @param {function(!Event)=} clickHandler |
| * @param {string=} className |
| * @param {string=} title |
| * @return {!Element} |
| */ |
| function createTextButton(text, clickHandler, className, title) |
| { |
| var element = createElementWithClass("button", className || "", "text-button"); |
| element.textContent = text; |
| if (clickHandler) |
| element.addEventListener("click", clickHandler, false); |
| if (title) |
| element.title = title; |
| return element; |
| } |
| |
| /** |
| * @param {string} name |
| * @param {string} title |
| * @param {boolean=} checked |
| * @return {!Element} |
| */ |
| function createRadioLabel(name, title, checked) |
| { |
| var element = createElement("label", "dt-radio"); |
| element.radioElement.name = name; |
| element.radioElement.checked = !!checked; |
| element.createTextChild(title); |
| return element; |
| } |
| |
| /** |
| * @param {string} title |
| * @param {string} iconClass |
| * @return {!Element} |
| */ |
| function createLabel(title, iconClass) |
| { |
| var element = createElement("label", "dt-icon-label"); |
| element.createChild("span").textContent = title; |
| element.type = iconClass; |
| return element; |
| } |
| |
| /** |
| * @param {string=} title |
| * @param {boolean=} checked |
| * @param {string=} subtitle |
| * @return {!Element} |
| */ |
| function createCheckboxLabel(title, checked, subtitle) |
| { |
| var element = createElement("label", "dt-checkbox"); |
| element.checkboxElement.checked = !!checked; |
| if (title !== undefined) { |
| element.textElement = element.createChild("div", "dt-checkbox-text"); |
| element.textElement.textContent = title; |
| if (subtitle !== undefined) { |
| element.subtitleElement = element.textElement.createChild("div", "dt-checkbox-subtitle"); |
| element.subtitleElement.textContent = subtitle; |
| } |
| } |
| return element; |
| } |
| |
| /** |
| * @param {!Node} node |
| * @param {string} cssFile |
| * @suppressGlobalPropertiesCheck |
| */ |
| WebInspector.appendStyle = function(node, cssFile) |
| { |
| var content = Runtime.cachedResources[cssFile] || ""; |
| if (!content) |
| console.error(cssFile + " not preloaded. Check module.json"); |
| var styleElement = createElement("style"); |
| styleElement.type = "text/css"; |
| styleElement.textContent = content; |
| node.appendChild(styleElement); |
| |
| var themeStyleSheet = WebInspector.themeSupport.themeStyleSheet(cssFile, content); |
| if (themeStyleSheet) { |
| styleElement = createElement("style"); |
| styleElement.type = "text/css"; |
| styleElement.textContent = themeStyleSheet + "\n" + Runtime.resolveSourceURL(cssFile + ".theme"); |
| node.appendChild(styleElement); |
| } |
| } |
| |
| ;(function() { |
| registerCustomElement("button", "text-button", { |
| /** |
| * @this {Element} |
| */ |
| createdCallback: function() |
| { |
| this.type = "button"; |
| var root = WebInspector.createShadowRootWithCoreStyles(this, "ui/textButton.css"); |
| root.createChild("content"); |
| }, |
| |
| __proto__: HTMLButtonElement.prototype |
| }); |
| |
| registerCustomElement("label", "dt-radio", { |
| /** |
| * @this {Element} |
| */ |
| createdCallback: function() |
| { |
| this.radioElement = this.createChild("input", "dt-radio-button"); |
| this.radioElement.type = "radio"; |
| var root = WebInspector.createShadowRootWithCoreStyles(this, "ui/radioButton.css"); |
| root.createChild("content").select = ".dt-radio-button"; |
| root.createChild("content"); |
| this.addEventListener("click", radioClickHandler, false); |
| }, |
| |
| __proto__: HTMLLabelElement.prototype |
| }); |
| |
| /** |
| * @param {!Event} event |
| * @suppressReceiverCheck |
| * @this {Element} |
| */ |
| function radioClickHandler(event) |
| { |
| if (this.radioElement.checked || this.radioElement.disabled) |
| return; |
| this.radioElement.checked = true; |
| this.radioElement.dispatchEvent(new Event("change")); |
| } |
| |
| registerCustomElement("label", "dt-checkbox", { |
| /** |
| * @this {Element} |
| */ |
| createdCallback: function() |
| { |
| this._root = WebInspector.createShadowRootWithCoreStyles(this, "ui/checkboxTextLabel.css"); |
| var checkboxElement = createElementWithClass("input", "dt-checkbox-button"); |
| checkboxElement.type = "checkbox"; |
| this._root.appendChild(checkboxElement); |
| this.checkboxElement = checkboxElement; |
| |
| this.addEventListener("click", toggleCheckbox.bind(this)); |
| |
| /** |
| * @param {!Event} event |
| * @this {Node} |
| */ |
| function toggleCheckbox(event) |
| { |
| if (event.target !== checkboxElement && event.target !== this) { |
| event.consume(); |
| checkboxElement.click(); |
| } |
| } |
| |
| this._root.createChild("content"); |
| }, |
| |
| /** |
| * @param {string} color |
| * @this {Element} |
| */ |
| set backgroundColor(color) |
| { |
| this.checkboxElement.classList.add("dt-checkbox-themed"); |
| this.checkboxElement.style.backgroundColor = color; |
| }, |
| |
| /** |
| * @param {string} color |
| * @this {Element} |
| */ |
| set checkColor(color) |
| { |
| this.checkboxElement.classList.add("dt-checkbox-themed"); |
| var stylesheet = createElement("style"); |
| stylesheet.textContent = "input.dt-checkbox-themed:checked:after { background-color: " + color + "}"; |
| this._root.appendChild(stylesheet); |
| }, |
| |
| /** |
| * @param {string} color |
| * @this {Element} |
| */ |
| set borderColor(color) |
| { |
| this.checkboxElement.classList.add("dt-checkbox-themed"); |
| this.checkboxElement.style.borderColor = color; |
| }, |
| |
| __proto__: HTMLLabelElement.prototype |
| }); |
| |
| registerCustomElement("label", "dt-icon-label", { |
| /** |
| * @this {Element} |
| */ |
| createdCallback: function() |
| { |
| var root = WebInspector.createShadowRootWithCoreStyles(this, "ui/smallIcon.css"); |
| this._iconElement = root.createChild("div"); |
| root.createChild("content"); |
| }, |
| |
| /** |
| * @param {string} type |
| * @this {Element} |
| */ |
| set type(type) |
| { |
| this._iconElement.className = type; |
| }, |
| |
| __proto__: HTMLLabelElement.prototype |
| }); |
| |
| registerCustomElement("div", "dt-close-button", { |
| /** |
| * @this {Element} |
| */ |
| createdCallback: function() |
| { |
| var root = WebInspector.createShadowRootWithCoreStyles(this, "ui/closeButton.css"); |
| this._buttonElement = root.createChild("div", "close-button"); |
| }, |
| |
| /** |
| * @param {boolean} gray |
| * @this {Element} |
| */ |
| set gray(gray) |
| { |
| this._buttonElement.className = gray ? "close-button-gray" : "close-button"; |
| }, |
| |
| __proto__: HTMLDivElement.prototype |
| }); |
| })(); |
| |
| /** |
| * @param {!Element} input |
| * @param {function(string)} apply |
| * @param {function(string):boolean} validate |
| * @param {boolean} numeric |
| * @return {function(string)} |
| */ |
| WebInspector.bindInput = function(input, apply, validate, numeric) |
| { |
| input.addEventListener("change", onChange, false); |
| input.addEventListener("input", onInput, false); |
| input.addEventListener("keydown", onKeyDown, false); |
| input.addEventListener("focus", input.select.bind(input), false); |
| |
| function onInput() |
| { |
| input.classList.toggle("error-input", !validate(input.value)); |
| } |
| |
| function onChange() |
| { |
| var valid = validate(input.value); |
| input.classList.toggle("error-input", !valid); |
| if (valid) |
| apply(input.value); |
| } |
| |
| /** |
| * @param {!Event} event |
| */ |
| function onKeyDown(event) |
| { |
| if (isEnterKey(event)) { |
| if (validate(input.value)) |
| apply(input.value); |
| return; |
| } |
| |
| if (!numeric) |
| return; |
| |
| var increment = event.key === "ArrowUp" ? 1 : event.key === "ArrowDown" ? -1 : 0; |
| if (!increment) |
| return; |
| if (event.shiftKey) |
| increment *= 10; |
| |
| var value = input.value; |
| if (!validate(value) || !value) |
| return; |
| |
| value = (value ? Number(value) : 0) + increment; |
| var stringValue = value ? String(value) : ""; |
| if (!validate(stringValue) || !value) |
| return; |
| |
| input.value = stringValue; |
| apply(input.value); |
| event.preventDefault(); |
| } |
| |
| /** |
| * @param {string} value |
| */ |
| function setValue(value) |
| { |
| if (value === input.value) |
| return; |
| var valid = validate(value); |
| input.classList.toggle("error-input", !valid); |
| input.value = value; |
| } |
| |
| return setValue; |
| } |
| |
| /** |
| * @constructor |
| */ |
| WebInspector.StringFormatter = function() |
| { |
| this._processors = []; |
| this._regexes = []; |
| } |
| |
| WebInspector.StringFormatter.prototype = { |
| /** |
| * @param {!RegExp} regex |
| * @param {function(string):!Node} handler |
| */ |
| addProcessor: function(regex, handler) |
| { |
| this._regexes.push(regex); |
| this._processors.push(handler); |
| }, |
| |
| /** |
| * @param {string} text |
| * @return {!Node} |
| */ |
| formatText: function(text) |
| { |
| return this._runProcessor(0, text); |
| }, |
| |
| /** |
| * @param {number} processorIndex |
| * @param {string} text |
| * @return {!Node} |
| */ |
| _runProcessor: function(processorIndex, text) |
| { |
| if (processorIndex >= this._processors.length) |
| return createTextNode(text); |
| |
| var container = createDocumentFragment(); |
| var regex = this._regexes[processorIndex]; |
| var processor = this._processors[processorIndex]; |
| |
| // Due to the nature of regex, |items| array has matched elements on its even indexes. |
| var items = text.replace(regex, "\0$1\0").split("\0"); |
| for (var i = 0; i < items.length; ++i) { |
| var processedNode = i % 2 ? processor(items[i]) : this._runProcessor(processorIndex + 1, items[i]); |
| container.appendChild(processedNode); |
| } |
| |
| return container; |
| } |
| } |
| |
| /** |
| * @constructor |
| * @param {!WebInspector.Setting} setting |
| */ |
| WebInspector.ThemeSupport = function(setting) |
| { |
| this._themeName = setting.get() || "default"; |
| this._themableProperties = new Set([ |
| "color", "box-shadow", "text-shadow", "outline-color", |
| "background-image", "background-color", |
| "border-left-color", "border-right-color", "border-top-color", "border-bottom-color", |
| "-webkit-border-image"]); |
| /** @type {!Map<string, string>} */ |
| this._cachedThemePatches = new Map(); |
| this._setting = setting; |
| } |
| |
| /** |
| * @enum {number} |
| */ |
| WebInspector.ThemeSupport.ColorUsage = { |
| Unknown: 0, |
| Foreground: 1 << 0, |
| Background: 1 << 1, |
| Selection: 1 << 2, |
| }; |
| |
| WebInspector.ThemeSupport.prototype = { |
| /** |
| * @return {boolean} |
| */ |
| hasTheme: function() |
| { |
| return this._themeName !== "default"; |
| }, |
| |
| /** |
| * @param {!Element} element |
| */ |
| injectHighlightStyleSheets: function(element) |
| { |
| this._injectingStyleSheet = true; |
| WebInspector.appendStyle(element, "ui/inspectorSyntaxHighlight.css"); |
| if (this._themeName === "dark") |
| WebInspector.appendStyle(element, "ui/inspectorSyntaxHighlightDark.css"); |
| this._injectingStyleSheet = false; |
| }, |
| |
| /** |
| * @param {!Document} document |
| */ |
| applyTheme: function(document) |
| { |
| if (!this.hasTheme()) |
| return; |
| |
| if (this._themeName === "dark") |
| document.body.classList.add("-theme-with-dark-background"); |
| |
| var styleSheets = document.styleSheets; |
| var result = []; |
| for (var i = 0; i < styleSheets.length; ++i) |
| result.push(this._patchForTheme(styleSheets[i].href, styleSheets[i])); |
| result.push("/*# sourceURL=inspector.css.theme */"); |
| |
| var styleElement = createElement("style"); |
| styleElement.type = "text/css"; |
| styleElement.textContent = result.join("\n"); |
| document.head.appendChild(styleElement); |
| }, |
| |
| /** |
| * @param {string} id |
| * @param {string} text |
| * @return {string} |
| * @suppressGlobalPropertiesCheck |
| */ |
| themeStyleSheet: function(id, text) |
| { |
| if (!this.hasTheme() || this._injectingStyleSheet) |
| return ""; |
| |
| var patch = this._cachedThemePatches.get(id); |
| if (!patch) { |
| var styleElement = createElement("style"); |
| styleElement.type = "text/css"; |
| styleElement.textContent = text; |
| document.body.appendChild(styleElement); |
| patch = this._patchForTheme(id, styleElement.sheet); |
| document.body.removeChild(styleElement); |
| } |
| return patch; |
| }, |
| |
| /** |
| * @param {string} id |
| * @param {!StyleSheet} styleSheet |
| * @return {string} |
| */ |
| _patchForTheme: function(id, styleSheet) |
| { |
| var cached = this._cachedThemePatches.get(id); |
| if (cached) |
| return cached; |
| |
| try { |
| var rules = styleSheet.cssRules; |
| var result = []; |
| for (var j = 0; j < rules.length; ++j) { |
| if (rules[j] instanceof CSSImportRule) { |
| result.push(this._patchForTheme(rules[j].styleSheet.href, rules[j].styleSheet)); |
| continue; |
| } |
| var output = []; |
| var style = rules[j].style; |
| var selectorText = rules[j].selectorText; |
| for (var i = 0; style && i < style.length; ++i) |
| this._patchProperty(selectorText, style, style[i], output); |
| if (output.length) |
| result.push(rules[j].selectorText + "{" + output.join("") + "}"); |
| } |
| |
| var fullText = result.join("\n"); |
| this._cachedThemePatches.set(id, fullText); |
| return fullText; |
| } catch (e) { |
| this._setting.set("default"); |
| return ""; |
| } |
| }, |
| |
| /** |
| * @param {string} selectorText |
| * @param {!CSSStyleDeclaration} style |
| * @param {string} name |
| * @param {!Array<string>} output |
| * |
| * Theming API is primarily targeted at making dark theme look good. |
| * - If rule has ".-theme-preserve" in selector, it won't be affected. |
| * - If rule has ".selection" or "selected" or "-theme-selection-color" in selector, its hue is rotated 180deg in dark themes. |
| * - One can create specializations for dark themes via body.-theme-with-dark-background selector in host context. |
| */ |
| _patchProperty: function(selectorText, style, name, output) |
| { |
| if (!this._themableProperties.has(name)) |
| return; |
| |
| var value = style.getPropertyValue(name); |
| if (!value || value === "none" || value === "inherit" || value === "initial" || value === "transparent") |
| return; |
| if (name === "background-image" && value.indexOf("gradient") === -1) |
| return; |
| |
| var isSelection = selectorText.indexOf(".-theme-selection-color") !== -1; |
| if (selectorText.indexOf("-theme-") !== -1 && !isSelection) |
| return; |
| |
| if (name === "-webkit-border-image") { |
| output.push("-webkit-filter: invert(100%)"); |
| return; |
| } |
| |
| isSelection = isSelection || selectorText.indexOf("selected") !== -1 || selectorText.indexOf(".selection") !== -1; |
| var colorUsage = WebInspector.ThemeSupport.ColorUsage.Unknown; |
| if (isSelection) |
| colorUsage |= WebInspector.ThemeSupport.ColorUsage.Selection; |
| if (name.indexOf("background") === 0 || name.indexOf("border") === 0) |
| colorUsage |= WebInspector.ThemeSupport.ColorUsage.Background; |
| if (name.indexOf("background") === -1) |
| colorUsage |= WebInspector.ThemeSupport.ColorUsage.Foreground; |
| |
| var colorRegex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b\w+\b(?!-))/g; |
| output.push(name); |
| output.push(":"); |
| var items = value.replace(colorRegex, "\0$1\0").split("\0"); |
| for (var i = 0; i < items.length; ++i) |
| output.push(this.patchColor(items[i], colorUsage)); |
| if (style.getPropertyPriority(name)) |
| output.push(" !important"); |
| output.push(";"); |
| }, |
| |
| /** |
| * @param {string} text |
| * @param {!WebInspector.ThemeSupport.ColorUsage} colorUsage |
| * @return {string} |
| */ |
| patchColor: function(text, colorUsage) |
| { |
| var color = WebInspector.Color.parse(text); |
| if (!color) |
| return text; |
| |
| var hsla = color.hsla(); |
| this._patchHSLA(hsla, colorUsage); |
| var rgba = []; |
| WebInspector.Color.hsl2rgb(hsla, rgba); |
| var outColor = new WebInspector.Color(rgba, color.format()); |
| var outText = outColor.asString(null); |
| if (!outText) |
| outText = outColor.asString(outColor.hasAlpha() ? WebInspector.Color.Format.RGBA : WebInspector.Color.Format.RGB); |
| return outText || text; |
| }, |
| |
| /** |
| * @param {!Array<number>} hsla |
| * @param {!WebInspector.ThemeSupport.ColorUsage} colorUsage |
| */ |
| _patchHSLA: function(hsla, colorUsage) |
| { |
| var hue = hsla[0]; |
| var sat = hsla[1]; |
| var lit = hsla[2]; |
| var alpha = hsla[3] |
| |
| switch (this._themeName) { |
| case "dark": |
| if (colorUsage & WebInspector.ThemeSupport.ColorUsage.Selection) |
| hue = (hue + 0.5) % 1; |
| var minCap = colorUsage & WebInspector.ThemeSupport.ColorUsage.Background ? 0.14 : 0; |
| var maxCap = colorUsage & WebInspector.ThemeSupport.ColorUsage.Foreground ? 0.9 : 1; |
| lit = 1 - lit; |
| if (lit < minCap * 2) |
| lit = minCap + lit / 2; |
| else if (lit > 2 * maxCap - 1) |
| lit = maxCap - 1 / 2 + lit / 2; |
| |
| break; |
| } |
| hsla[0] = Number.constrain(hue, 0, 1); |
| hsla[1] = Number.constrain(sat, 0, 1); |
| hsla[2] = Number.constrain(lit, 0, 1); |
| hsla[3] = Number.constrain(alpha, 0, 1); |
| } |
| } |
| |
| /** |
| * @param {?NetworkAgent.ResourcePriority} priority |
| * @return {string} |
| */ |
| WebInspector.uiLabelForPriority = function(priority) |
| { |
| var labelMap = WebInspector.uiLabelForPriority._priorityToUILabel; |
| if (!labelMap) { |
| labelMap = new Map([ |
| [NetworkAgent.ResourcePriority.VeryLow, WebInspector.UIString("Lowest")], |
| [NetworkAgent.ResourcePriority.Low, WebInspector.UIString("Low")], |
| [NetworkAgent.ResourcePriority.Medium, WebInspector.UIString("Medium")], |
| [NetworkAgent.ResourcePriority.High, WebInspector.UIString("High")], |
| [NetworkAgent.ResourcePriority.VeryHigh, WebInspector.UIString("Highest")] |
| ]); |
| WebInspector.uiLabelForPriority._priorityToUILabel = labelMap; |
| } |
| return labelMap.get(priority) || WebInspector.UIString("Unknown"); |
| } |
| |
| /** |
| * @param {string} url |
| * @param {string=} linkText |
| * @param {string=} classes |
| * @param {boolean=} isExternal |
| * @param {string=} tooltipText |
| * @return {!Element} |
| */ |
| WebInspector.linkifyURLAsNode = function(url, linkText, classes, isExternal, tooltipText) |
| { |
| if (!linkText) |
| linkText = url; |
| |
| var a = createElementWithClass("a", classes); |
| var href = url; |
| if (url.trim().toLowerCase().startsWith("javascript:")) |
| href = null; |
| if (isExternal && WebInspector.ParsedURL.isRelativeURL(url)) |
| href = null; |
| if (href !== null) { |
| a.href = href; |
| a.classList.add(isExternal ? "webkit-html-external-link" : "webkit-html-resource-link"); |
| } |
| if (!tooltipText && linkText !== url) |
| a.title = url; |
| else if (tooltipText) |
| a.title = tooltipText; |
| a.textContent = linkText.trimMiddle(150); |
| if (isExternal) |
| a.setAttribute("target", "_blank"); |
| |
| return a; |
| } |
| |
| /** |
| * @param {string} article |
| * @param {string} title |
| * @return {!Element} |
| */ |
| WebInspector.linkifyDocumentationURLAsNode = function(article, title) |
| { |
| return WebInspector.linkifyURLAsNode("https://developers.google.com/web/tools/chrome-devtools/" + article, title, undefined, true); |
| } |
| |
| /** @type {!WebInspector.ThemeSupport} */ |
| WebInspector.themeSupport; |