UNPKG

@dcloudio/uni-debugger

Version:

uni-app debugger

641 lines (577 loc) 21.9 kB
// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @fileoverview using private properties isn't a Closure violation in tests. * @suppress {accessControls} */ /** @typedef {function(!Element, !SDK.ConsoleMessage=):string} */ ConsoleTestRunner.Formatter; /** * @param {boolean=} printOriginatingCommand * @param {boolean=} dumpClassNames * @param {!ConsoleTestRunner.Formatter=} formatter */ ConsoleTestRunner.dumpConsoleMessages = function(printOriginatingCommand, dumpClassNames, formatter) { TestRunner.addResults( ConsoleTestRunner.dumpConsoleMessagesIntoArray(printOriginatingCommand, dumpClassNames, formatter)); }; ConsoleTestRunner.renderCompleteMessages = async function() { const consoleView = Console.ConsoleView.instance(); if (consoleView._needsFullUpdate) consoleView._updateMessageList(); const viewMessages = consoleView._visibleViewMessages; await Promise.all(viewMessages.map(uiMessage => uiMessage.completeElementForTest())); }; /** * @param {boolean=} printOriginatingCommand * @param {boolean=} dumpClassNames * @param {!ConsoleTestRunner.Formatter=} formatter * @return {!Array<string>} */ ConsoleTestRunner.dumpConsoleMessagesIntoArray = function(printOriginatingCommand, dumpClassNames, formatter) { formatter = formatter || ConsoleTestRunner.prepareConsoleMessageText; const result = []; ConsoleTestRunner.disableConsoleViewport(); const consoleView = Console.ConsoleView.instance(); if (consoleView._needsFullUpdate) consoleView._updateMessageList(); const viewMessages = consoleView._visibleViewMessages; for (let i = 0; i < viewMessages.length; ++i) { const uiMessage = viewMessages[i]; const message = uiMessage.consoleMessage(); const element = uiMessage.element(); let classNames; if (dumpClassNames) { classNames = ['']; for (let node = element.firstChild; node; node = node.traverseNextNode(element)) { if (node.nodeType === Node.ELEMENT_NODE && node.className) { let depth = 0; let depthTest = node; while (depthTest !== element) { if (depthTest.nodeType === Node.ELEMENT_NODE && depthTest.className) depth++; depthTest = depthTest.parentNodeOrShadowHost(); } classNames.push( ' '.repeat(depth) + node.className.replace('platform-linux', 'platform-*') .replace('platform-mac', 'platform-*') .replace('platform-windows', 'platform-*')); } } } if (ConsoleTestRunner.dumpConsoleTableMessage(uiMessage, false, result)) { if (dumpClassNames) result.push(classNames.join('\n')); } else { let messageText = formatter(element, message); messageText = messageText.replace(/VM\d+/g, 'VM'); result.push(messageText + (dumpClassNames ? ' ' + classNames.join('\n') : '')); } if (printOriginatingCommand && uiMessage.consoleMessage().originatingMessage()) result.push('Originating from: ' + uiMessage.consoleMessage().originatingMessage().messageText); } return result; }; /** * @param {!Element} messageElement * @return {string} */ ConsoleTestRunner.prepareConsoleMessageText = function(messageElement) { let messageText = messageElement.deepTextContent().replace(/\u200b/g, ''); // Replace scriptIds with generic scriptId string to avoid flakiness. messageText = messageText.replace(/VM\d+/g, 'VM'); // Remove line and column of evaluate method. messageText = messageText.replace(/(at eval \(eval at evaluate) \(:\d+:\d+\)/, '$1'); if (messageText.startsWith('Navigated to')) { const fileName = messageText.split(' ').pop().split('/').pop(); messageText = 'Navigated to ' + fileName; } // The message might be extremely long in case of dumping stack overflow message. messageText = messageText.substring(0, 1024); return messageText; }; /** * @param {!Console.ConsoleViewMessage} viewMessage * @param {boolean} forceInvalidate * @param {!Array<string>} results * @return {boolean} */ ConsoleTestRunner.dumpConsoleTableMessage = function(viewMessage, forceInvalidate, results) { if (forceInvalidate) Console.ConsoleView.instance()._viewport.invalidate(); const table = viewMessage.element(); const headers = table.querySelectorAll('th > div:first-child'); if (!headers.length) return false; let headerLine = ''; for (let i = 0; i < headers.length; i++) headerLine += headers[i].textContent + ' | '; addResult('HEADER ' + headerLine); const rows = table.querySelectorAll('.data-container tr'); for (let i = 0; i < rows.length; i++) { const row = rows[i]; let rowLine = ''; const items = row.querySelectorAll('td > span'); for (let j = 0; j < items.length; j++) rowLine += items[j].textContent + ' | '; if (rowLine.trim()) addResult('ROW ' + rowLine); } /** * @param {string} x */ function addResult(x) { if (results) results.push(x); else TestRunner.addResult(x); } return true; }; ConsoleTestRunner.disableConsoleViewport = function() { ConsoleTestRunner.fixConsoleViewportDimensions(600, 2000); }; /** * @param {number} width * @param {number} height */ ConsoleTestRunner.fixConsoleViewportDimensions = function(width, height) { const viewport = Console.ConsoleView.instance()._viewport; viewport.element.style.width = width + 'px'; viewport.element.style.height = height + 'px'; viewport.element.style.position = 'absolute'; viewport.invalidate(); }; ConsoleTestRunner.selectMainExecutionContext = function() { const executionContexts = TestRunner.runtimeModel.executionContexts(); for (const context of executionContexts) { if (context.isDefault) { UI.context.setFlavor(SDK.ExecutionContext, context); return; } } }; /** * @param {string} code * @param {!Function=} callback * @param {boolean=} dontForceMainContext */ ConsoleTestRunner.evaluateInConsole = function(code, callback, dontForceMainContext) { if (!dontForceMainContext) ConsoleTestRunner.selectMainExecutionContext(); callback = TestRunner.safeWrap(callback); const consoleView = Console.ConsoleView.instance(); consoleView._prompt._appendCommand(code, true); ConsoleTestRunner.addConsoleViewSniffer(function(commandResult) { callback(commandResult.toMessageElement().deepTextContent()); }); }; /** * @param {string} code * @param {boolean=} dontForceMainContext * @return {!Promise} */ ConsoleTestRunner.evaluateInConsolePromise = function(code, dontForceMainContext) { return new Promise(fulfill => ConsoleTestRunner.evaluateInConsole(code, fulfill, dontForceMainContext)); }; /** * @param {!Function} override * @param {boolean=} opt_sticky */ ConsoleTestRunner.addConsoleViewSniffer = function(override, opt_sticky) { TestRunner.addSniffer(Console.ConsoleView.prototype, '_consoleMessageAddedForTest', override, opt_sticky); }; ConsoleTestRunner.waitForPendingViewportUpdates = async function() { const refreshPromise = Console.ConsoleView.instance()._scheduledRefreshPromiseForTest || Promise.resolve(); await refreshPromise; }; /** * @param {string} code * @param {!Function=} callback * @param {boolean=} dontForceMainContext */ ConsoleTestRunner.evaluateInConsoleAndDump = function(code, callback, dontForceMainContext) { callback = TestRunner.safeWrap(callback); /** * @param {string} text */ function mycallback(text) { text = text.replace(/\bVM\d+/g, 'VM'); TestRunner.addResult(code + ' = ' + text); callback(text); } ConsoleTestRunner.evaluateInConsole(code, mycallback, dontForceMainContext); }; /** * @return {number} */ ConsoleTestRunner.consoleMessagesCount = function() { const consoleView = Console.ConsoleView.instance(); return consoleView._consoleMessages.length; }; /** * @param {function(!Element):string|undefined} messageFormatter * @param {!Element} node * @return {string} */ ConsoleTestRunner.formatterIgnoreStackFrameUrls = function(messageFormatter, node) { /** * @param {string} string */ function isNotEmptyLine(string) { return string.trim().length > 0; } /** * @param {string} string */ function ignoreStackFrameAndMutableData(string) { let buffer = string.replace(/\u200b/g, ''); buffer = buffer.replace(/VM\d+/g, 'VM'); return buffer.replace(/^\s+at [^\]]+(]?)$/, '$1'); } messageFormatter = messageFormatter || TestRunner.textContentWithLineBreaks; const buffer = messageFormatter(node); return buffer.split('\n').map(ignoreStackFrameAndMutableData).filter(isNotEmptyLine).join('\n'); }; /** * @param {!Element} element * @param {!SDK.ConsoleMessage} message * @return {string} */ ConsoleTestRunner.simpleFormatter = function(element, message) { return message.messageText + ':' + message.line + ':' + message.column; }; /** * @param {boolean=} printOriginatingCommand * @param {boolean=} dumpClassNames * @param {!ConsoleTestRunner.Formatter=} messageFormatter */ ConsoleTestRunner.dumpConsoleMessagesIgnoreErrorStackFrames = function( printOriginatingCommand, dumpClassNames, messageFormatter) { TestRunner.addResults(ConsoleTestRunner.dumpConsoleMessagesIntoArray( printOriginatingCommand, dumpClassNames, ConsoleTestRunner.formatterIgnoreStackFrameUrls.bind(this, messageFormatter))); }; ConsoleTestRunner.dumpConsoleMessagesWithStyles = function() { const messageViews = Console.ConsoleView.instance()._visibleViewMessages; for (let i = 0; i < messageViews.length; ++i) { const element = messageViews[i].element(); const messageText = ConsoleTestRunner.prepareConsoleMessageText(element); TestRunner.addResult(messageText); const spans = element.querySelectorAll('.console-message-text *'); for (let j = 0; j < spans.length; ++j) TestRunner.addResult('Styled text #' + j + ': ' + (spans[j].style.cssText || 'NO STYLES DEFINED')); } }; /** * @param {boolean=} sortMessages */ ConsoleTestRunner.dumpConsoleMessagesWithClasses = function(sortMessages) { const result = []; const messageViews = Console.ConsoleView.instance()._visibleViewMessages; for (let i = 0; i < messageViews.length; ++i) { const element = messageViews[i].element(); const contentElement = messageViews[i].contentElement(); const messageText = ConsoleTestRunner.prepareConsoleMessageText(element); result.push(messageText + ' ' + element.getAttribute('class') + ' > ' + contentElement.getAttribute('class')); } if (sortMessages) result.sort(); TestRunner.addResults(result); }; ConsoleTestRunner.dumpConsoleClassesBrief = function() { const messageViews = Console.ConsoleView.instance()._visibleViewMessages; for (let i = 0; i < messageViews.length; ++i) { const repeatText = messageViews[i].repeatCount() > 1 ? (' x' + messageViews[i].repeatCount()) : ''; TestRunner.addResult(messageViews[i].toMessageElement().className + repeatText); } }; ConsoleTestRunner.dumpConsoleCounters = async function() { const counter = ConsoleCounters.WarningErrorCounter._instanceForTest; if (counter._updatingForTest) await TestRunner.addSnifferPromise(counter, '_updatedForTest'); for (let index = 0; index < counter._titles.length; ++index) TestRunner.addResult(counter._titles[index]); ConsoleTestRunner.dumpConsoleClassesBrief(); }; /** * @param {!Function} callback * @param {function(!Element):boolean} deepFilter * @param {function(!ObjectUI.ObjectPropertiesSection):boolean} sectionFilter */ ConsoleTestRunner.expandConsoleMessages = function(callback, deepFilter, sectionFilter) { Console.ConsoleView.instance()._invalidateViewport(); const messageViews = Console.ConsoleView.instance()._visibleViewMessages; // Initiate round-trips to fetch necessary data for further rendering. for (let i = 0; i < messageViews.length; ++i) messageViews[i].element(); TestRunner.deprecatedRunAfterPendingDispatches(expandTreeElements); function expandTreeElements() { for (let i = 0; i < messageViews.length; ++i) { const element = messageViews[i].element(); for (let node = element; node; node = node.traverseNextNode(element)) { if (node.treeElementForTest) node.treeElementForTest.expand(); if (node._expandStackTraceForTest) node._expandStackTraceForTest(); if (!node._section) continue; if (sectionFilter && !sectionFilter(node._section)) continue; node._section.expand(); if (!deepFilter) continue; const treeElements = node._section.rootElement().children(); for (let j = 0; j < treeElements.length; ++j) { for (let treeElement = treeElements[j]; treeElement; treeElement = treeElement.traverseNextTreeElement(true, null, true)) { if (deepFilter(treeElement)) treeElement.expand(); } } } } TestRunner.deprecatedRunAfterPendingDispatches(callback); } }; /** * @param {function(!Element):boolean} deepFilter * @param {function(!ObjectUI.ObjectPropertiesSection):boolean} sectionFilter * @return {!Promise} */ ConsoleTestRunner.expandConsoleMessagesPromise = function(deepFilter, sectionFilter) { return new Promise(fulfill => ConsoleTestRunner.expandConsoleMessages(fulfill, deepFilter, sectionFilter)); }; /** * @param {!Function} callback */ ConsoleTestRunner.expandGettersInConsoleMessages = function(callback) { const messageViews = Console.ConsoleView.instance()._visibleViewMessages; const properties = []; let propertiesCount = 0; TestRunner.addSniffer(ObjectUI.ObjectPropertyTreeElement.prototype, '_updateExpandable', propertyExpandableUpdated); for (let i = 0; i < messageViews.length; ++i) { const element = messageViews[i].element(); for (let node = element; node; node = node.traverseNextNode(element)) { if (node.classList && node.classList.contains('object-value-calculate-value-button')) { ++propertiesCount; node.click(); properties.push(node.parentElement.parentElement); } } } function propertyExpandableUpdated() { --propertiesCount; if (propertiesCount === 0) { for (let i = 0; i < properties.length; ++i) properties[i].click(); TestRunner.deprecatedRunAfterPendingDispatches(callback); } else { TestRunner.addSniffer( ObjectUI.ObjectPropertyTreeElement.prototype, '_updateExpandable', propertyExpandableUpdated); } } }; /** * @param {!Function} callback */ ConsoleTestRunner.expandConsoleMessagesErrorParameters = function(callback) { const messageViews = Console.ConsoleView.instance()._visibleViewMessages; // Initiate round-trips to fetch necessary data for further rendering. for (let i = 0; i < messageViews.length; ++i) messageViews[i].element(); TestRunner.deprecatedRunAfterPendingDispatches(callback); }; /** * @param {!Function} callback */ ConsoleTestRunner.waitForRemoteObjectsConsoleMessages = function(callback) { const messages = Console.ConsoleView.instance()._visibleViewMessages; for (let i = 0; i < messages.length; ++i) messages[i].toMessageElement(); TestRunner.deprecatedRunAfterPendingDispatches(callback); }; /** * @return {!Promise} */ ConsoleTestRunner.waitUntilConsoleEditorLoaded = function() { let fulfill; const promise = new Promise(x => (fulfill = x)); const prompt = Console.ConsoleView.instance()._prompt; if (prompt._editor) fulfill(prompt._editor); else TestRunner.addSniffer(Console.ConsolePrompt.prototype, '_editorSetForTest', _ => fulfill(prompt._editor)); return promise; }; /** * @param {!Function} callback */ ConsoleTestRunner.waitUntilMessageReceived = function(callback) { TestRunner.addSniffer(SDK.consoleModel, 'addMessage', callback, false); }; /** * @return {!Promise} */ ConsoleTestRunner.waitUntilMessageReceivedPromise = function() { return new Promise(fulfill => ConsoleTestRunner.waitUntilMessageReceived(fulfill)); }; /** * @param {number} count * @param {!Function} callback */ ConsoleTestRunner.waitUntilNthMessageReceived = function(count, callback) { function override() { if (--count === 0) TestRunner.safeWrap(callback)(); else TestRunner.addSniffer(SDK.consoleModel, 'addMessage', override, false); } TestRunner.addSniffer(SDK.consoleModel, 'addMessage', override, false); }; /** * @param {number} count * @return {!Promise} */ ConsoleTestRunner.waitUntilNthMessageReceivedPromise = function(count) { return new Promise(fulfill => ConsoleTestRunner.waitUntilNthMessageReceived(count, fulfill)); }; /** * @param {string} namePrefix */ ConsoleTestRunner.changeExecutionContext = function(namePrefix) { const selector = Console.ConsoleView.instance()._consoleContextSelector; for (const executionContext of selector._items) { if (selector.titleFor(executionContext).startsWith(namePrefix)) { UI.context.setFlavor(SDK.ExecutionContext, executionContext); return; } } TestRunner.addResult('FAILED: context with prefix: ' + namePrefix + ' not found in the context list'); }; /** * @param {number} expectedCount * @param {!Function} callback */ ConsoleTestRunner.waitForConsoleMessages = function(expectedCount, callback) { const consoleView = Console.ConsoleView.instance(); checkAndReturn(); function checkAndReturn() { if (consoleView._visibleViewMessages.length === expectedCount) { TestRunner.addResult('Message count: ' + expectedCount); callback(); } else { TestRunner.addSniffer(consoleView, '_messageAppendedForTests', checkAndReturn); } } }; /** * @param {number} expectedCount * @return {!Promise} */ ConsoleTestRunner.waitForConsoleMessagesPromise = async function(expectedCount) { await new Promise(fulfill => ConsoleTestRunner.waitForConsoleMessages(expectedCount, fulfill)); return ConsoleTestRunner.renderCompleteMessages(); }; /** * @param {number} fromMessage * @param {number} fromTextOffset * @param {number} toMessage * @param {number} toTextOffset * @suppressGlobalPropertiesCheck */ ConsoleTestRunner.selectConsoleMessages = function(fromMessage, fromTextOffset, toMessage, toTextOffset) { const consoleView = Console.ConsoleView.instance(); const from = selectionContainerAndOffset(consoleView.itemElement(fromMessage).element(), fromTextOffset); const to = selectionContainerAndOffset(consoleView.itemElement(toMessage).element(), toTextOffset); window.getSelection().setBaseAndExtent(from.container, from.offset, to.container, to.offset); /** * @param {!Node} container * @param {number} offset * @return {?{container: !Node, offset: number}} */ function selectionContainerAndOffset(container, offset) { /** @type {?Node} */ let node = container; if (offset === 0 && container.nodeType !== Node.TEXT_NODE) { container = /** @type {!Node} */ (container.traverseNextTextNode()); node = container; } let charCount = 0; while ((node = node.traverseNextTextNode(container))) { const length = node.textContent.length; if (charCount + length >= offset) return {container: node, offset: offset - charCount}; charCount += length; } return null; } }; /** * @param {!Function} override * @param {boolean=} opt_sticky */ ConsoleTestRunner.addConsoleSniffer = function(override, opt_sticky) { TestRunner.addSniffer(SDK.ConsoleModel.prototype, 'addMessage', override, opt_sticky); }; /** * @param {!Function} func * @return {!Function} */ ConsoleTestRunner.wrapListener = function(func) { /** * @this {*} */ async function wrapper() { await Promise.resolve(); func.apply(this, arguments); } return wrapper; }; ConsoleTestRunner.dumpStackTraces = function() { const viewMessages = Console.ConsoleView.instance()._visibleViewMessages; for (let i = 0; i < viewMessages.length; ++i) { const m = viewMessages[i].consoleMessage(); TestRunner.addResult( 'Message[' + i + ']: ' + Bindings.displayNameForURL(m.url || '') + ':' + m.line + ' ' + m.messageText); const trace = m.stackTrace ? m.stackTrace.callFrames : null; if (!trace) { TestRunner.addResult('FAIL: no stack trace attached to message #' + i); } else { TestRunner.addResult('Stack Trace:\n'); TestRunner.addResult(' url: ' + trace[0].url); TestRunner.addResult(' function: ' + trace[0].functionName); TestRunner.addResult(' line: ' + trace[0].lineNumber); } } }; /** * Returns actual visible indices. Messages in the margin are treated as NOT visible. * @return {!{first: number, last: number, count: number}} */ ConsoleTestRunner.visibleIndices = function() { const consoleView = Console.ConsoleView.instance(); const viewport = consoleView._viewport; const viewportRect = viewport.element.getBoundingClientRect(); const viewportPadding = parseFloat(window.getComputedStyle(viewport.element).paddingTop); let first = -1; let last = -1; let count = 0; for (let i = 0; i < consoleView._visibleViewMessages.length; i++) { // Created message elements may have a bounding rect, but not be connected to DOM. const item = consoleView._visibleViewMessages[i]; if (!item._element || !item._element.isConnected) continue; const itemRect = item._element.getBoundingClientRect(); const isVisible = (itemRect.bottom > viewportRect.top + viewportPadding + 1) && (itemRect.top <= viewportRect.bottom - viewportPadding - 1); if (isVisible) { first = first === -1 ? i : first; last = i; count++; } } return {first, last, count}; };