| /* |
| * Copyright (C) 2018 Apple Inc. All rights reserved. |
| * |
| * 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. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 INC. 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. |
| */ |
| |
| WI.AuditTestCaseContentView = class AuditTestCaseContentView extends WI.AuditTestContentView |
| { |
| constructor(representedObject) |
| { |
| console.assert(representedObject instanceof WI.AuditTestCase || representedObject instanceof WI.AuditTestCaseResult); |
| |
| super(representedObject); |
| |
| this.element.classList.add("audit-test-case"); |
| |
| this._resultDataGeneralContainer = null; |
| this._resultDataDOMNodesContainer = null; |
| this._resultDataErrorsContainer = null; |
| } |
| |
| // Protected |
| |
| initialLayout() |
| { |
| super.initialLayout(); |
| |
| let informationContainer = this.headerView.element.appendChild(document.createElement("div")); |
| informationContainer.classList.add("information"); |
| |
| let nameContainer = informationContainer.appendChild(document.createElement("h1")); |
| |
| this._resultImageElement = nameContainer.appendChild(document.createElement("img")); |
| |
| nameContainer.appendChild(this.createNameElement("span")); |
| |
| informationContainer.appendChild(this.createDescriptionElement("p")); |
| |
| if (this.representedObject instanceof WI.AuditTestCase) |
| informationContainer.appendChild(this.createControlsTableElement()); |
| |
| this._metadataElement = this.headerView.element.appendChild(document.createElement("div")); |
| this._metadataElement.classList.add("metadata"); |
| } |
| |
| layout() |
| { |
| if (this.layoutReason !== WI.View.LayoutReason.Dirty) |
| return; |
| |
| super.layout(); |
| |
| this._metadataElement.removeChildren(); |
| this.contentView.element.removeChildren(); |
| |
| if (WI.auditManager.editing) { |
| this._resultImageElement.src = "Images/Pencil.svg"; |
| this._resultImageElement.title = WI.UIString("Editing audit", "Editing Audit @ Audit Tab - Test Case", "Title of icon indiciating that the selected audit is being edited."); |
| |
| let testEditorElement = this.contentView.element.appendChild(document.createElement("div")); |
| testEditorElement.className = "editor"; |
| |
| // Give the rest of the view a chance to load. |
| setTimeout(() => { |
| this._testCodeMirror = WI.CodeMirrorEditor.create(testEditorElement, { |
| autoCloseBrackets: true, |
| lineNumbers: true, |
| lineWrapping: true, |
| matchBrackets: true, |
| mode: "text/javascript", |
| readOnly: this.representedObject.editable ? false : "nocursor", |
| styleSelectedText: true, |
| value: this.representedObject.test, |
| }); |
| }); |
| return; |
| } |
| |
| this._resultImageElement.src = "Images/AuditTestNoResult.svg"; |
| this._resultImageElement.title = WI.UIString("Not yet run", "Not yet run @ Audit Tab - Test Case", "Title of icon indicating that the selected audit has not been run yet."); |
| |
| let result = this.representedObject.result; |
| if (!result) { |
| if (this.representedObject.runningState === WI.AuditManager.RunningState.Inactive) |
| this.showNoResultPlaceholder(); |
| else if (this.representedObject.runningState === WI.AuditManager.RunningState.Active) |
| this.showRunningPlaceholder(); |
| else if (this.representedObject.runningState === WI.AuditManager.RunningState.Stopping) |
| this.showStoppingPlaceholder(); |
| |
| return; |
| } |
| |
| if (result.didError) { |
| this._resultImageElement.src = "Images/AuditTestError.svg"; |
| this._resultImageElement.title = WI.UIString("Error", "Error @ Audit Tab - Test Case", "Title of icon indicating that the selected audit threw an error."); |
| } else if (result.didFail) { |
| this._resultImageElement.src = "Images/AuditTestFail.svg"; |
| this._resultImageElement.title = WI.UIString("Fail", "Fail @ Audit Tab - Test Case", "Title of icon indicating that the selected audit failed."); |
| } else if (result.didWarn) { |
| this._resultImageElement.src = "Images/AuditTestWarn.svg"; |
| this._resultImageElement.title = WI.UIString("Warn", "Warn @ Audit Tab - Test Case", "Title of icon indicating that the selected audit passed with issues (i.e. warnings)."); |
| } else if (result.didPass) { |
| this._resultImageElement.src = "Images/AuditTestPass.svg"; |
| this._resultImageElement.title = WI.UIString("Pass", "Pass @ Audit Tab - Test Case", "Title of icon indicating that the selected audit passed with no issues."); |
| } else if (result.unsupported) { |
| this._resultImageElement.src = "Images/AuditTestUnsupported.svg"; |
| this._resultImageElement.title = WI.UIString("Unsupported", "Unsupported @ Audit Tab - Test Case", "Title of icon indicating that the selected audit is not able to be run (i.e. unsupported)."); |
| } |
| |
| let metadata = result.metadata; |
| if (metadata) { |
| let sourceContainer = this._metadataElement.appendChild(document.createElement("div")); |
| sourceContainer.classList.add("source"); |
| |
| if (metadata.startTimestamp) { |
| let timeElement = sourceContainer.appendChild(document.createElement("time")); |
| timeElement.datetime = metadata.startTimestamp.toISOString(); |
| timeElement.textContent = metadata.startTimestamp.toLocaleString(); |
| |
| if (metadata.endTimestamp) { |
| let totalDuration = Number.secondsToString((metadata.endTimestamp - metadata.startTimestamp) / 1000); |
| |
| let durationElement = this._metadataElement.appendChild(document.createElement("span")); |
| durationElement.classList.add("duration"); |
| durationElement.textContent = totalDuration; |
| |
| if (metadata.asyncTimestamp) { |
| let evalDuration = Number.secondsToString((metadata.asyncTimestamp - metadata.startTimestamp) / 1000); |
| let asyncDuration = Number.secondsToString((metadata.endTimestamp - metadata.asyncTimestamp) / 1000); |
| |
| durationElement.classList.add("async"); |
| durationElement.title = WI.UIString("%s eval\n%s async").format(evalDuration, asyncDuration); |
| } |
| } |
| } |
| |
| if (metadata.url && (metadata.url !== WI.networkManager.mainFrame.url || this.representedObject instanceof WI.AuditTestResultBase)) { |
| let url = new URL(metadata.url); |
| let origin = url.origin; |
| if (url.pathname.startsWith("/")) |
| origin += "/"; |
| let linkElement = WI.linkifyURLAsNode(url.href, origin + url.href.substring(origin.length).truncateStart(20)); |
| linkElement.title = url.href; |
| sourceContainer.appendChild(linkElement); |
| } |
| } |
| |
| let resultData = result.data; |
| |
| if (!this._resultDataGeneralContainer) { |
| let nonSpecialData = Object.filter(resultData, (key) => key !== "domNodes" && key !== "errors"); |
| if (!isEmptyObject(nonSpecialData)) { |
| this._resultDataGeneralContainer = document.createElement("div"); |
| |
| let expression = "(" + JSON.stringify(nonSpecialData) + ")"; |
| const options = { |
| objectGroup: WI.AuditTestBase.ObjectGroup, |
| doNotPauseOnExceptionsAndMuteConsole: true, |
| }; |
| WI.runtimeManager.evaluateInInspectedWindow(expression, options, (nonSpecialDataRemoteObject, wasThrown) => { |
| console.assert(!wasThrown); |
| if (!nonSpecialDataRemoteObject) |
| return; |
| |
| if (!this.representedObject.result || this.representedObject.result.data !== resultData) |
| return; |
| |
| const propertyPath = null; |
| const forceExpanding = true; |
| let element = WI.FormattedValue.createObjectTreeOrFormattedValueForRemoteObject(nonSpecialDataRemoteObject, propertyPath, forceExpanding); |
| |
| let objectTree = element.__objectTree; |
| if (objectTree) { |
| objectTree.showOnlyJSON(); |
| objectTree.expand(); |
| } |
| |
| this._resultDataGeneralContainer.appendChild(element); |
| |
| this.hidePlaceholder(); |
| }); |
| } |
| } |
| |
| if (this._resultDataGeneralContainer) |
| this.contentView.element.appendChild(this._resultDataGeneralContainer); |
| |
| if (!this._resultDataDOMNodesContainer && resultData.domNodes && resultData.domNodes.length) { |
| this._resultDataDOMNodesContainer = document.createElement("div"); |
| this._resultDataDOMNodesContainer.classList.add("dom-nodes"); |
| |
| let domNodeText = this._resultDataDOMNodesContainer.appendChild(document.createElement("h1")); |
| domNodeText.textContent = WI.UIString("DOM Nodes:"); |
| |
| let tableContainer = this._resultDataDOMNodesContainer.appendChild(document.createElement("table")); |
| |
| resultData.domNodes.forEach((domNode, index) => { |
| domNode = result.resolvedDOMNodes[index] || domNode; |
| |
| let rowElement = tableContainer.appendChild(document.createElement("tr")); |
| |
| let indexElement = rowElement.appendChild(document.createElement("td")); |
| indexElement.textContent = index + 1; |
| |
| let dataElement = rowElement.appendChild(document.createElement("td")); |
| |
| if (domNode instanceof WI.DOMNode) { |
| let treeOutline = new WI.DOMTreeOutline({selectable: false}); |
| treeOutline.setVisible(true); |
| treeOutline.rootDOMNode = domNode; |
| |
| let rootTreeElement = treeOutline.children[0]; |
| rootTreeElement.showGoToArrow = true; |
| if (!rootTreeElement.hasChildren) |
| treeOutline.element.classList.add("single-node"); |
| |
| if (resultData.domAttributes) { |
| for (let domAttribute of resultData.domAttributes) { |
| rootTreeElement.highlightAttribute(domAttribute); |
| rootTreeElement.updateTitle(); |
| } |
| } |
| |
| dataElement.appendChild(treeOutline.element); |
| } else if (typeof domNode === "string") { |
| let codeMirror = WI.CodeMirrorEditor.create(dataElement.appendChild(document.createElement("code")), { |
| mode: "css", |
| readOnly: true, |
| lineWrapping: true, |
| styleSelectedText: true, |
| }); |
| codeMirror.setValue(domNode); |
| |
| if (resultData.domAttributes) { |
| for (let domAttribute of resultData.domAttributes) { |
| let regex = null; |
| if (domAttribute === "id") |
| regex = /(\#[^\#|\.|\[|\s|$]+)/g; |
| else if (domAttribute === "class") |
| regex = /(\.[^\#|\.|\[|\s|$]+)/g; |
| else |
| regex = new RegExp(`\\[\\s*(${domAttribute})\\s*=`, "g"); |
| |
| while (true) { |
| let match = regex.exec(domNode); |
| if (!match) |
| break; |
| |
| let start = match.index + match[0].indexOf(match[1]); |
| codeMirror.markText({line: 0, ch: start}, {line: 0, ch: start + match[1].length}, {className: "mark"}); |
| } |
| } |
| } |
| |
| setTimeout(() => { |
| codeMirror.refresh(); |
| }); |
| } |
| }); |
| } |
| |
| if (this._resultDataDOMNodesContainer) |
| this.contentView.element.appendChild(this._resultDataDOMNodesContainer); |
| |
| if (!this._resultDataErrorsContainer && resultData.errors && resultData.errors.length) { |
| this._resultDataErrorsContainer = document.createElement("div"); |
| this._resultDataErrorsContainer.classList.add("errors"); |
| |
| let errorText = this._resultDataErrorsContainer.appendChild(document.createElement("h1")); |
| errorText.textContent = WI.UIString("Errors:"); |
| |
| let tableContainer = this._resultDataErrorsContainer.appendChild(document.createElement("table")); |
| |
| resultData.errors.forEach((error, index) => { |
| let rowElement = tableContainer.appendChild(document.createElement("tr")); |
| |
| let indexElement = rowElement.appendChild(document.createElement("td")); |
| indexElement.textContent = index + 1; |
| |
| let dataElement = rowElement.appendChild(document.createElement("td")); |
| |
| let errorElement = dataElement.appendChild(document.createElement("div")); |
| errorElement.classList.add("error"); |
| errorElement.textContent = error; |
| }); |
| } |
| |
| if (this._resultDataErrorsContainer) |
| this.contentView.element.appendChild(this._resultDataErrorsContainer); |
| |
| if (!this.contentView.element.children.length) |
| this.showNoResultDataPlaceholder(); |
| } |
| |
| handleResultChanged(event) |
| { |
| super.handleResultChanged(event); |
| |
| this._resultDataGeneralContainer = null; |
| this._resultDataDOMNodesContainer = null; |
| this._resultDataErrorsContainer = null; |
| } |
| |
| saveEditedData() |
| { |
| super.saveEditedData(); |
| |
| this.representedObject.test = this._testCodeMirror.getValue().trim(); |
| } |
| |
| showRunningPlaceholder() |
| { |
| if (!this.placeholderElement || !this.placeholderElement.__placeholderRunning) { |
| this.placeholderElement = WI.createMessageTextView(WI.UIString("Running the \u201C%s\u201D audit").format(this.representedObject.name)); |
| this.placeholderElement.__placeholderRunning = true; |
| |
| let spinner = new WI.IndeterminateProgressSpinner; |
| this.placeholderElement.appendChild(spinner.element); |
| |
| let stopAuditNavigationItem = new WI.ButtonNavigationItem("stop-audit", WI.UIString("Stop"), "Images/AuditStop.svg", 13, 13); |
| stopAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; |
| stopAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, function(event) { |
| WI.auditManager.stop(); |
| }, stopAuditNavigationItem); |
| |
| let stopAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop running."), stopAuditNavigationItem); |
| this.placeholderElement.appendChild(stopAuditHelpElement); |
| |
| this.placeholderElement.appendChild(WI.createReferencePageLink("audit-tab")); |
| } |
| |
| super.showRunningPlaceholder(); |
| } |
| }; |