UNPKG

monaca-lib

Version:

Monaca cloud API bindings for JavaScript

1,450 lines (1,266 loc) 45.8 kB
/* * 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"; /** * @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; 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); var targetElement = /** @type {!Element} */ (event.target); 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 (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)); } /** * @constructor * @param {!Document} document */ WebInspector.GlassPane = function(document) { this.element = 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.GlassPane.DefaultFocusedViewStack.length) WebInspector.GlassPane.DefaultFocusedViewStack.peekLast().focus(); this.element.remove(); } } /** * @type {!Array.<!WebInspector.View|!WebInspector.Dialog>} */ WebInspector.GlassPane.DefaultFocusedViewStack = []; /** * @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") { 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(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.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel"); var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "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; 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 false; replacementString = customNumberHandler ? customNumberHandler(prefix, number, suffix) : prefix + number + suffix; } } 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("%.3f\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 * @param {!Object.<string, function(string, ...):*>} formatters * @param {string} initialValue * @param {function(string, string): ?} append * @return {!{formattedResult: string, unusedSubstitutions: ?ArrayLike}}; */ WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append) { return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append); } /** * @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.manageBlackboxingButtonLabel = function() { return WebInspector.UIString("Manage framework blackboxing..."); } /** * @param {!Element} element * @return {boolean} */ WebInspector.installComponentRootStyles = function(element) { var wasInstalled = element.classList.contains("component-root"); if (wasInstalled) return false; element.classList.add("component-root", "platform-" + WebInspector.platform()); return true; } /** * @param {!Element} element */ WebInspector.uninstallComponentRootStyles = function(element) { element.classList.remove("component-root", "platform-" + WebInspector.platform()); } /** * @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 {!Document} document * @param {!Event} event */ WebInspector._focusChanged = function(document, event) { var node = document.activeElement; while (node && node.shadowRoot) node = node.shadowRoot.activeElement; 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 (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 = x.getComponentSelection(); 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()); } /** * @param {!Document} document * @param {string} backgroundColor * @param {string} color */ WebInspector.setToolbarColors = function(document, backgroundColor, color) { if (!WebInspector._themeStyleElement) { WebInspector._themeStyleElement = createElement("style"); document.head.appendChild(WebInspector._themeStyleElement); } var colorWithAlpha = WebInspector.Color.parse(color).setAlpha(0.8).asString(WebInspector.Color.Format.RGBA); var prefix = WebInspector.isMac() ? "body:not(.undocked)" : "body"; WebInspector._themeStyleElement.textContent = String.sprintf( "%s .inspector-view-tabbed-pane.tabbed-pane::shadow .tabbed-pane-header {" + " background-image: none !important;" + " background-color: %s !important;" + " color: %s !important;" + "}", prefix, backgroundColor, colorWithAlpha) + String.sprintf( "%s .inspector-view-tabbed-pane.tabbed-pane::shadow .tabbed-pane-header-tab:hover {" + " color: %s;" + "}", prefix, color) + String.sprintf( "%s .inspector-view-toolbar.status-bar::shadow .status-bar-item {" + " color: %s;" + "}", prefix, colorWithAlpha) + String.sprintf( "%s .inspector-view-toolbar.status-bar::shadow .status-bar-button-theme {" + " background-color: %s;" + "}", prefix, colorWithAlpha); } WebInspector.resetToolbarColors = function() { if (WebInspector._themeStyleElement) WebInspector._themeStyleElement.textContent = ""; } /** * @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} styleClass */ WebInspector.removeSearchResultsHighlight = function(element, styleClass) { var highlightBits = element.querySelectorAll("." + styleClass); for (var i = 0; i < highlightBits.length; ++i) { var span = highlightBits[i]; span.parentElement.replaceChild(createTextNode(span.textContent), span); } } /** * @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 lineText = element.deepTextContent(); var ownerDocument = element.ownerDocument; var textNodes = element.childTextNodes(); 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 */ WebInspector.LongClickController = function(element) { this._element = element; } /** * @enum {string} */ WebInspector.LongClickController.Events = { LongClick: "LongClick", LongPress: "LongPress" }; 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); var longClicks = 0; this._longClickData = { mouseUp: boundMouseUp, mouseDown: boundMouseDown, reset: boundReset }; /** * @param {!Event} e * @this {WebInspector.LongClickController} */ function mouseDown(e) { if (e.which !== 1) return; longClicks = 0; this._longClickInterval = setInterval(longClicked.bind(this, e), 200); } /** * @param {!Event} e * @this {WebInspector.LongClickController} */ function mouseUp(e) { if (e.which !== 1) return; this.reset(); } /** * @param {!Event} e * @this {WebInspector.LongClickController} */ function longClicked(e) { ++longClicks; this.dispatchEventToListeners(longClicks === 1 ? WebInspector.LongClickController.Events.LongClick : WebInspector.LongClickController.Events.LongPress, e); } }, disable: 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 {string} url * @param {string=} linkText * @param {string=} classes * @return {!Element} */ WebInspector.createExternalAnchor = function(url, linkText, classes) { var anchor = createElementWithClass("a", "link"); var href = sanitizeHref(url); if (href) anchor.href = href; anchor.title = url; if (!linkText) linkText = url; anchor.className = classes; anchor.textContent = linkText; anchor.setAttribute("target", "_blank"); /** * @param {!Event} event */ function clickHandler(event) { event.consume(true); InspectorFrontendHost.openInNewTab(anchor.href); } anchor.addEventListener("click", clickHandler, false); return anchor; } /** * @param {string} article * @param {string} title * @return {!Element} */ WebInspector.createDocumentationAnchor = function(article, title) { return WebInspector.createExternalAnchor("https://developer.chrome.com/devtools/docs/" + article, title); } /** * @param {!Window} window */ WebInspector.initializeUIUtils = function(window) { window.addEventListener("focus", WebInspector._windowFocused.bind(WebInspector, window.document), false); window.addEventListener("blur", WebInspector._windowBlurred.bind(WebInspector, window.document), false); window.document.addEventListener("focus", WebInspector._focusChanged.bind(WebInspector, window.document), true); window.document.addEventListener("blur", WebInspector._documentBlurred.bind(WebInspector, window.document), true); } /** * @param {string} name * @return {string} */ WebInspector.beautifyFunctionName = function(name) { return name || WebInspector.UIString("(anonymous function)"); } /** * @param {string} localName * @param {string} typeExtension * @param {function(new:T)} extendedType * @param {!Object.<string, function(...*)>} protoTemplate * @param {string=} styleSheet * @suppressGlobalPropertiesCheck * @template T */ function registerCustomElement(localName, typeExtension, extendedType, protoTemplate, styleSheet) { var proto = Object.create(extendedType.prototype); for (var p in protoTemplate) proto[p] = protoTemplate[p]; if (!protoTemplate["createdCallback"]) { /** * @this {Element} */ proto.createdCallback = function() { var root = this.createShadowRoot(); root.appendChild(createElement("content")); if (styleSheet) root.appendChild(WebInspector.View.createStyleElement(styleSheet)); }; } document.registerElement(typeExtension, { prototype: proto, 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 {boolean=} checked * @return {!Element} */ function createCheckboxLabel(title, checked) { 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; } return element; } ;(function() { registerCustomElement("button", "text-button", HTMLButtonElement, { /** * @this {Element} */ createdCallback: function() { this.type = "button"; var root = this.createShadowRoot(); root.appendChild(WebInspector.View.createStyleElement("ui/textButton.css")); root.createChild("content"); } }, "ui/textButton.css"); registerCustomElement("label", "dt-radio", HTMLLabelElement, { /** * @this {Element} */ createdCallback: function() { this.radioElement = this.createChild("input", "dt-radio-button"); this.radioElement.type = "radio"; var root = this.createShadowRoot(); root.appendChild(WebInspector.View.createStyleElement("ui/radioButton.css")); root.createChild("content").select = ".dt-radio-button"; root.createChild("content"); this.addEventListener("click", radioClickHandler, false); } }); /** * @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", HTMLLabelElement, { /** * @this {Element} */ createdCallback: function() { var root = this.createShadowRoot(); root.appendChild(WebInspector.View.createStyleElement("ui/checkboxTextLabel.css")); this.checkboxElement = this.createChild("input", "dt-checkbox-button"); this.checkboxElement.type = "checkbox"; root.createChild("content").select = ".dt-checkbox-button"; root.createChild("content"); } }); })(); /** * @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; } }