900 lines
41 KiB
JavaScript
900 lines
41 KiB
JavaScript
/*
|
|
* Copyright (C) 2013-2016 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.DOMNodeDetailsSidebarPanel = class DOMNodeDetailsSidebarPanel extends WI.DOMDetailsSidebarPanel
|
|
{
|
|
constructor()
|
|
{
|
|
super("dom-node-details", WI.UIString("Node"));
|
|
|
|
this._eventListenerGroupingMethodSetting = new WI.Setting("dom-node-event-listener-grouping-method", WI.DOMNodeDetailsSidebarPanel.EventListenerGroupingMethod.Event);
|
|
|
|
this.element.classList.add("dom-node");
|
|
|
|
this._nodeRemoteObject = null;
|
|
}
|
|
|
|
// Public
|
|
|
|
closed()
|
|
{
|
|
if (this.didInitialLayout) {
|
|
WI.domManager.removeEventListener(WI.DOMManager.Event.AttributeModified, this._attributesChanged, this);
|
|
WI.domManager.removeEventListener(WI.DOMManager.Event.AttributeRemoved, this._attributesChanged, this);
|
|
WI.domManager.removeEventListener(WI.DOMManager.Event.CharacterDataModified, this._characterDataModified, this);
|
|
WI.domManager.removeEventListener(WI.DOMManager.Event.CustomElementStateChanged, this._customElementStateChanged, this);
|
|
}
|
|
|
|
super.closed();
|
|
}
|
|
|
|
// Protected
|
|
|
|
initialLayout()
|
|
{
|
|
super.initialLayout();
|
|
|
|
WI.domManager.addEventListener(WI.DOMManager.Event.AttributeModified, this._attributesChanged, this);
|
|
WI.domManager.addEventListener(WI.DOMManager.Event.AttributeRemoved, this._attributesChanged, this);
|
|
WI.domManager.addEventListener(WI.DOMManager.Event.CharacterDataModified, this._characterDataModified, this);
|
|
WI.domManager.addEventListener(WI.DOMManager.Event.CustomElementStateChanged, this._customElementStateChanged, this);
|
|
|
|
this._identityNodeTypeRow = new WI.DetailsSectionSimpleRow(WI.UIString("Type"));
|
|
this._identityNodeNameRow = new WI.DetailsSectionSimpleRow(WI.UIString("Name"));
|
|
this._identityNodeValueRow = new WI.DetailsSectionSimpleRow(WI.UIString("Value"));
|
|
this._identityNodeContentSecurityPolicyHashRow = new WI.DetailsSectionSimpleRow(WI.UIString("CSP Hash"));
|
|
|
|
var identityGroup = new WI.DetailsSectionGroup([this._identityNodeTypeRow, this._identityNodeNameRow, this._identityNodeValueRow, this._identityNodeContentSecurityPolicyHashRow]);
|
|
var identitySection = new WI.DetailsSection("dom-node-identity", WI.UIString("Identity"), [identityGroup]);
|
|
this.contentView.element.appendChild(identitySection.element);
|
|
|
|
this._attributesDataGridRow = new WI.DetailsSectionDataGridRow(null, WI.UIString("No Attributes"));
|
|
|
|
var attributesGroup = new WI.DetailsSectionGroup([this._attributesDataGridRow]);
|
|
var attributesSection = new WI.DetailsSection("dom-node-attributes", WI.UIString("Attributes"), [attributesGroup]);
|
|
this.contentView.element.appendChild(attributesSection.element);
|
|
|
|
if (InspectorBackend.hasCommand("DOM.resolveNode")) {
|
|
this._propertiesRow = new WI.DetailsSectionRow;
|
|
let propertiesGroup = new WI.DetailsSectionGroup([this._propertiesRow]);
|
|
let propertiesSection = new WI.DetailsSection("dom-node-properties", WI.UIString("Properties"), [propertiesGroup]);
|
|
this.contentView.element.appendChild(propertiesSection.element);
|
|
}
|
|
|
|
let eventListenersFilterElement = WI.ImageUtilities.useSVGSymbol("Images/Filter.svg", "filter", WI.UIString("Grouping Method"));
|
|
WI.addMouseDownContextMenuHandlers(eventListenersFilterElement, this._populateEventListenersFilterContextMenu.bind(this));
|
|
|
|
this._eventListenersSectionGroup = new WI.DetailsSectionGroup;
|
|
let eventListenersSection = new WI.DetailsSection("dom-node-event-listeners", WI.UIString("Event Listeners"), [this._eventListenersSectionGroup], eventListenersFilterElement);
|
|
this.contentView.element.appendChild(eventListenersSection.element);
|
|
|
|
if (InspectorBackend.hasCommand("DOM.getDataBindingsForNode")) {
|
|
this._dataBindingsSection = new WI.DetailsSection("dom-node-data-bindings", WI.UIString("Data Bindings"), []);
|
|
this.contentView.element.appendChild(this._dataBindingsSection.element);
|
|
}
|
|
|
|
if (InspectorBackend.hasCommand("DOM.getAssociatedDataForNode")) {
|
|
this._associatedDataGrid = new WI.DetailsSectionRow(WI.UIString("No Associated Data"));
|
|
|
|
let associatedDataGroup = new WI.DetailsSectionGroup([this._associatedDataGrid]);
|
|
|
|
let associatedSection = new WI.DetailsSection("dom-node-associated-data", WI.UIString("Associated Data"), [associatedDataGroup]);
|
|
this.contentView.element.appendChild(associatedSection.element);
|
|
}
|
|
|
|
if (this._accessibilitySupported()) {
|
|
this._accessibilityEmptyRow = new WI.DetailsSectionRow(WI.UIString("No Accessibility Information"));
|
|
this._accessibilityNodeActiveDescendantRow = new WI.DetailsSectionSimpleRow(WI.UIString("Shared Focus"));
|
|
this._accessibilityNodeBusyRow = new WI.DetailsSectionSimpleRow(WI.UIString("Busy"));
|
|
this._accessibilityNodeCheckedRow = new WI.DetailsSectionSimpleRow(WI.UIString("Checked"));
|
|
this._accessibilityNodeChildrenRow = new WI.DetailsSectionSimpleRow(WI.UIString("Children"));
|
|
this._accessibilityNodeControlsRow = new WI.DetailsSectionSimpleRow(WI.UIString("Controls"));
|
|
this._accessibilityNodeCurrentRow = new WI.DetailsSectionSimpleRow(WI.UIString("Current"));
|
|
this._accessibilityNodeDisabledRow = new WI.DetailsSectionSimpleRow(WI.UIString("Disabled"));
|
|
this._accessibilityNodeExpandedRow = new WI.DetailsSectionSimpleRow(WI.UIString("Expanded"));
|
|
this._accessibilityNodeFlowsRow = new WI.DetailsSectionSimpleRow(WI.UIString("Flows"));
|
|
this._accessibilityNodeFocusedRow = new WI.DetailsSectionSimpleRow(WI.UIString("Focused"));
|
|
this._accessibilityNodeHeadingLevelRow = new WI.DetailsSectionSimpleRow(WI.UIString("Heading Level"));
|
|
this._accessibilityNodehierarchyLevelRow = new WI.DetailsSectionSimpleRow(WI.UIString("Hierarchy Level"));
|
|
this._accessibilityNodeIgnoredRow = new WI.DetailsSectionSimpleRow(WI.UIString("Ignored"));
|
|
this._accessibilityNodeInvalidRow = new WI.DetailsSectionSimpleRow(WI.UIString("Invalid"));
|
|
this._accessibilityNodeLiveRegionStatusRow = new WI.DetailsSectionSimpleRow(WI.UIString("Live"));
|
|
this._accessibilityNodeMouseEventRow = new WI.DetailsSectionSimpleRow("");
|
|
this._accessibilityNodeLabelRow = new WI.DetailsSectionSimpleRow(WI.UIString("Label"));
|
|
this._accessibilityNodeOwnsRow = new WI.DetailsSectionSimpleRow(WI.UIString("Owns"));
|
|
this._accessibilityNodeParentRow = new WI.DetailsSectionSimpleRow(WI.UIString("Parent"));
|
|
this._accessibilityNodePressedRow = new WI.DetailsSectionSimpleRow(WI.UIString("Pressed"));
|
|
this._accessibilityNodeReadonlyRow = new WI.DetailsSectionSimpleRow(WI.UIString("Readonly"));
|
|
this._accessibilityNodeRequiredRow = new WI.DetailsSectionSimpleRow(WI.UIString("Required"));
|
|
this._accessibilityNodeRoleRow = new WI.DetailsSectionSimpleRow(WI.UIString("Role"));
|
|
this._accessibilityNodeSelectedRow = new WI.DetailsSectionSimpleRow(WI.UIString("Selected"));
|
|
this._accessibilityNodeSelectedChildrenRow = new WI.DetailsSectionSimpleRow(WI.UIString("Selected Items"));
|
|
|
|
this._accessibilityGroup = new WI.DetailsSectionGroup([this._accessibilityEmptyRow]);
|
|
var accessibilitySection = new WI.DetailsSection("dom-node-accessibility", WI.UIString("Accessibility"), [this._accessibilityGroup]);
|
|
|
|
this.contentView.element.appendChild(accessibilitySection.element);
|
|
}
|
|
}
|
|
|
|
layout()
|
|
{
|
|
super.layout();
|
|
|
|
if (!this.domNode || this.domNode.destroyed)
|
|
return;
|
|
|
|
this._refreshIdentity();
|
|
this._refreshAttributes();
|
|
this._refreshProperties();
|
|
this._refreshEventListeners();
|
|
this._refreshDataBindings();
|
|
this._refreshAssociatedData();
|
|
this._refreshAccessibility();
|
|
}
|
|
|
|
sizeDidChange()
|
|
{
|
|
super.sizeDidChange();
|
|
|
|
// FIXME: <https://webkit.org/b/152269> Web Inspector: Convert DetailsSection classes to use View
|
|
this._attributesDataGridRow.sizeDidChange();
|
|
}
|
|
|
|
attached()
|
|
{
|
|
super.attached();
|
|
|
|
WI.DOMNode.addEventListener(WI.DOMNode.Event.EventListenersChanged, this._eventListenersChanged, this);
|
|
}
|
|
|
|
detached()
|
|
{
|
|
WI.DOMNode.removeEventListener(WI.DOMNode.Event.EventListenersChanged, this._eventListenersChanged, this);
|
|
|
|
super.detached();
|
|
}
|
|
|
|
// Private
|
|
|
|
_accessibilitySupported()
|
|
{
|
|
return InspectorBackend.hasCommand("DOM.getAccessibilityPropertiesForNode");
|
|
}
|
|
|
|
_refreshIdentity()
|
|
{
|
|
const domNode = this.domNode;
|
|
this._identityNodeTypeRow.value = this._nodeTypeDisplayName();
|
|
this._identityNodeNameRow.value = domNode.nodeNameInCorrectCase();
|
|
this._identityNodeValueRow.value = domNode.nodeValue();
|
|
this._identityNodeContentSecurityPolicyHashRow.value = domNode.contentSecurityPolicyHash();
|
|
}
|
|
|
|
_refreshAttributes()
|
|
{
|
|
let domNode = this.domNode;
|
|
if (!domNode || !domNode.hasAttributes()) {
|
|
// Remove the DataGrid to show the placeholder text.
|
|
this._attributesDataGridRow.dataGrid = null;
|
|
return;
|
|
}
|
|
|
|
let dataGrid = this._attributesDataGridRow.dataGrid;
|
|
if (!dataGrid) {
|
|
const columns = {
|
|
name: {title: WI.UIString("Name"), width: "30%"},
|
|
value: {title: WI.UIString("Value")},
|
|
};
|
|
dataGrid = this._attributesDataGridRow.dataGrid = new WI.DataGrid(columns);
|
|
}
|
|
|
|
dataGrid.removeChildren();
|
|
|
|
let attributes = domNode.attributes();
|
|
attributes.sort((a, b) => a.name.extendedLocaleCompare(b.name));
|
|
for (let attribute of attributes) {
|
|
let dataGridNode = new WI.EditableDataGridNode(attribute);
|
|
dataGridNode.addEventListener(WI.EditableDataGridNode.Event.ValueChanged, this._attributeNodeValueChanged, this);
|
|
dataGrid.appendChild(dataGridNode);
|
|
}
|
|
|
|
dataGrid.updateLayoutIfNeeded();
|
|
}
|
|
|
|
_refreshProperties()
|
|
{
|
|
if (!this._propertiesRow)
|
|
return;
|
|
|
|
if (this._nodeRemoteObject) {
|
|
this._nodeRemoteObject.release();
|
|
this._nodeRemoteObject = null;
|
|
}
|
|
|
|
let target = WI.assumingMainTarget();
|
|
|
|
const objectGroup = "dom-node-details-sidebar-properties-object-group";
|
|
target.RuntimeAgent.releaseObjectGroup(objectGroup);
|
|
|
|
let domNode = this.domNode;
|
|
WI.RemoteObject.resolveNode(domNode, objectGroup).then((object) => {
|
|
// Bail if the DOM node changed while we were waiting for the async response.
|
|
if (this.domNode !== domNode)
|
|
return;
|
|
|
|
this._nodeRemoteObject = object;
|
|
|
|
function inspectedPage_node_collectPrototypes()
|
|
{
|
|
// This builds an object with numeric properties. This is easier than dealing with arrays
|
|
// with the way RemoteObject works. Start at 1 since we use parseInt later and parseInt
|
|
// returns 0 for non-numeric strings make it ambiguous.
|
|
var prototype = this;
|
|
var result = [];
|
|
var counter = 1;
|
|
|
|
while (prototype) {
|
|
result[counter++] = prototype;
|
|
prototype = prototype.__proto__;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
const args = undefined;
|
|
const generatePreview = false;
|
|
object.callFunction(inspectedPage_node_collectPrototypes, args, generatePreview, nodePrototypesReady.bind(this));
|
|
}).catch((error) => {
|
|
// Bail if the DOM node changed while we were waiting for the async response.
|
|
if (this.domNode !== domNode)
|
|
return;
|
|
|
|
console.assert(false, "Cannot resolve node.", error, domNode);
|
|
});
|
|
|
|
function nodePrototypesReady(error, object, wasThrown)
|
|
{
|
|
if (error || wasThrown || !object)
|
|
return;
|
|
|
|
// Bail if the DOM node changed while we were waiting for the async response.
|
|
if (this.domNode !== domNode)
|
|
return;
|
|
|
|
object.getPropertyDescriptors(fillSection.bind(this), {ownProperties: true});
|
|
}
|
|
|
|
function fillSection(prototypes)
|
|
{
|
|
if (!prototypes)
|
|
return;
|
|
|
|
// Bail if the DOM node changed while we were waiting for the async response.
|
|
if (this.domNode !== domNode)
|
|
return;
|
|
|
|
let element = this._propertiesRow.element;
|
|
element.removeChildren();
|
|
|
|
let propertyPath = new WI.PropertyPath(this._nodeRemoteObject, "node");
|
|
|
|
let initialSection = true;
|
|
for (let i = 0; i < prototypes.length; ++i) {
|
|
// The only values we care about are numeric, as assigned in collectPrototypes.
|
|
if (!parseInt(prototypes[i].name, 10))
|
|
continue;
|
|
|
|
let prototype = prototypes[i].value;
|
|
let prototypeName = prototype.description;
|
|
let title = prototypeName;
|
|
if (/Prototype$/.test(title)) {
|
|
prototypeName = prototypeName.replace(/Prototype$/, "");
|
|
title = prototypeName + WI.UIString(" (Prototype)");
|
|
} else if (title === "Object")
|
|
title = title + WI.UIString(" (Prototype)");
|
|
|
|
let mode = initialSection ? WI.ObjectTreeView.Mode.Properties : WI.ObjectTreeView.Mode.PureAPI;
|
|
let objectTree = new WI.ObjectTreeView(prototype, mode, propertyPath);
|
|
objectTree.showOnlyProperties();
|
|
objectTree.setPrototypeNameOverride(prototypeName);
|
|
|
|
let detailsSection = new WI.DetailsSection(prototype.description.hash + "-prototype-properties", title, null, null, true);
|
|
detailsSection.groups[0].rows = [new WI.ObjectPropertiesDetailSectionRow(objectTree, detailsSection)];
|
|
|
|
element.appendChild(detailsSection.element);
|
|
|
|
initialSection = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
async _refreshEventListeners()
|
|
{
|
|
var domNode = this.domNode;
|
|
if (!domNode)
|
|
return;
|
|
|
|
let {listeners} = await domNode.getEventListeners();
|
|
|
|
// Bail if the DOM node changed while we were waiting for the async response.
|
|
if (this.domNode !== domNode)
|
|
return;
|
|
|
|
if (!listeners.length) {
|
|
let emptyRow = new WI.DetailsSectionRow(WI.UIString("No Event Listeners"));
|
|
emptyRow.showEmptyMessage();
|
|
this._eventListenersSectionGroup.rows = [emptyRow];
|
|
return;
|
|
}
|
|
|
|
switch (this._eventListenerGroupingMethodSetting.value) {
|
|
case WI.DOMNodeDetailsSidebarPanel.EventListenerGroupingMethod.Event:
|
|
this._eventListenersSectionGroup.rows = WI.EventListenerSectionGroup.groupIntoSectionsByEvent(listeners);
|
|
break;
|
|
|
|
case WI.DOMNodeDetailsSidebarPanel.EventListenerGroupingMethod.Target:
|
|
this._eventListenersSectionGroup.rows = WI.EventListenerSectionGroup.groupIntoSectionsByTarget(listeners, domNode);
|
|
break;
|
|
}
|
|
}
|
|
|
|
_refreshDataBindings()
|
|
{
|
|
if (!this._dataBindingsSection)
|
|
return;
|
|
|
|
let domNode = this.domNode;
|
|
if (!domNode)
|
|
return;
|
|
|
|
let target = WI.assumingMainTarget();
|
|
target.DOMAgent.getDataBindingsForNode(this.domNode.id).then(({dataBindings}) => {
|
|
if (this.domNode !== domNode)
|
|
return;
|
|
|
|
if (!dataBindings.length) {
|
|
let emptyRow = new WI.DetailsSectionRow(WI.UIString("No Data Bindings"));
|
|
emptyRow.showEmptyMessage();
|
|
this._dataBindingsSection.groups = [new WI.DetailsSectionGroup([emptyRow])];
|
|
return;
|
|
}
|
|
|
|
let groups = [];
|
|
for (let {binding, type, value} of dataBindings) {
|
|
groups.push(new WI.DetailsSectionGroup([
|
|
new WI.DetailsSectionSimpleRow(WI.UIString("Binding"), binding),
|
|
new WI.DetailsSectionSimpleRow(WI.UIString("Type"), type),
|
|
new WI.DetailsSectionSimpleRow(WI.UIString("Value"), value),
|
|
]));
|
|
}
|
|
this._dataBindingsSection.groups = groups;
|
|
});
|
|
}
|
|
|
|
_refreshAssociatedData()
|
|
{
|
|
if (!this._associatedDataGrid)
|
|
return;
|
|
|
|
let target = WI.assumingMainTarget();
|
|
|
|
const objectGroup = "dom-node-details-sidebar-associated-data-object-group";
|
|
target.RuntimeAgent.releaseObjectGroup(objectGroup);
|
|
|
|
let domNode = this.domNode;
|
|
if (!domNode)
|
|
return;
|
|
|
|
target.DOMAgent.getAssociatedDataForNode(domNode.id).then(({associatedData}) => {
|
|
if (this.domNode !== domNode)
|
|
return;
|
|
|
|
if (!associatedData) {
|
|
this._associatedDataGrid.showEmptyMessage();
|
|
return;
|
|
}
|
|
|
|
let expression = associatedData;
|
|
const options = {
|
|
objectGroup,
|
|
doNotPauseOnExceptionsAndMuteConsole: true,
|
|
};
|
|
WI.runtimeManager.evaluateInInspectedWindow(expression, options, (result, wasThrown) => {
|
|
console.assert(!wasThrown);
|
|
|
|
if (!result) {
|
|
this._associatedDataGrid.showEmptyMessage();
|
|
return;
|
|
}
|
|
|
|
this._associatedDataGrid.hideEmptyMessage();
|
|
|
|
const propertyPath = null;
|
|
const forceExpanding = true;
|
|
let element = WI.FormattedValue.createObjectTreeOrFormattedValueForRemoteObject(result, propertyPath, forceExpanding);
|
|
|
|
let objectTree = element.__objectTree;
|
|
if (objectTree) {
|
|
objectTree.showOnlyJSON();
|
|
objectTree.expand();
|
|
}
|
|
|
|
this._associatedDataGrid.element.appendChild(element);
|
|
});
|
|
});
|
|
}
|
|
|
|
_refreshAccessibility()
|
|
{
|
|
if (!this._accessibilitySupported())
|
|
return;
|
|
|
|
var domNode = this.domNode;
|
|
if (!domNode)
|
|
return;
|
|
|
|
var properties = {};
|
|
|
|
function booleanValueToLocalizedStringIfTrue(property) {
|
|
if (properties[property])
|
|
return WI.UIString("Yes");
|
|
return "";
|
|
}
|
|
|
|
function booleanValueToLocalizedStringIfPropertyDefined(property) {
|
|
if (properties[property] !== undefined) {
|
|
if (properties[property])
|
|
return WI.UIString("Yes");
|
|
else
|
|
return WI.UIString("No");
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function linkForNodeId(nodeId) {
|
|
var link = null;
|
|
if (nodeId !== undefined && typeof nodeId === "number") {
|
|
var node = WI.domManager.nodeForId(nodeId);
|
|
if (node)
|
|
link = WI.linkifyAccessibilityNodeReference(node);
|
|
}
|
|
return link;
|
|
}
|
|
|
|
function linkListForNodeIds(nodeIds) {
|
|
if (!nodeIds)
|
|
return null;
|
|
|
|
const itemsToShow = 5;
|
|
let hasLinks = false;
|
|
let listItemCount = 0;
|
|
let container = document.createElement("div");
|
|
container.classList.add("list-container");
|
|
let linkList = container.createChild("ul", "node-link-list");
|
|
let initiallyHiddenItems = [];
|
|
for (let nodeId of nodeIds) {
|
|
let node = WI.domManager.nodeForId(nodeId);
|
|
if (!node)
|
|
continue;
|
|
let link = WI.linkifyAccessibilityNodeReference(node);
|
|
hasLinks = true;
|
|
let li = linkList.createChild("li");
|
|
li.appendChild(link);
|
|
if (listItemCount >= itemsToShow) {
|
|
li.hidden = true;
|
|
initiallyHiddenItems.push(li);
|
|
}
|
|
listItemCount++;
|
|
}
|
|
container.appendChild(linkList);
|
|
if (listItemCount > itemsToShow) {
|
|
let moreNodesButton = container.createChild("button", "expand-list-button");
|
|
moreNodesButton.textContent = WI.UIString("%d More\u2026").format(listItemCount - itemsToShow);
|
|
moreNodesButton.addEventListener("click", () => {
|
|
initiallyHiddenItems.forEach((element) => { element.hidden = false; });
|
|
moreNodesButton.remove();
|
|
});
|
|
}
|
|
if (hasLinks)
|
|
return container;
|
|
|
|
return null;
|
|
}
|
|
|
|
function accessibilityPropertiesCallback(accessibilityProperties) {
|
|
if (this.domNode !== domNode)
|
|
return;
|
|
|
|
// Make sure the current set of properties is available in the closure scope for the helper functions.
|
|
properties = accessibilityProperties;
|
|
|
|
if (accessibilityProperties && accessibilityProperties.exists) {
|
|
|
|
var activeDescendantLink = linkForNodeId(accessibilityProperties.activeDescendantNodeId);
|
|
var busy = booleanValueToLocalizedStringIfPropertyDefined("busy");
|
|
|
|
var checked = "";
|
|
if (accessibilityProperties.checked !== undefined) {
|
|
if (accessibilityProperties.checked === InspectorBackend.Enum.DOM.AccessibilityPropertiesChecked.True)
|
|
checked = WI.UIString("Yes");
|
|
else if (accessibilityProperties.checked === InspectorBackend.Enum.DOM.AccessibilityPropertiesChecked.Mixed)
|
|
checked = WI.UIString("Mixed");
|
|
else // InspectorBackend.Enum.DOM.AccessibilityPropertiesChecked.False
|
|
checked = WI.UIString("No");
|
|
}
|
|
|
|
// Accessibility tree children are not a 1:1 mapping with DOM tree children.
|
|
var childNodeLinkList = linkListForNodeIds(accessibilityProperties.childNodeIds);
|
|
var controlledNodeLinkList = linkListForNodeIds(accessibilityProperties.controlledNodeIds);
|
|
|
|
var current = "";
|
|
switch (accessibilityProperties.current) {
|
|
case InspectorBackend.Enum.DOM.AccessibilityPropertiesCurrent.True:
|
|
current = WI.UIString("True");
|
|
break;
|
|
case InspectorBackend.Enum.DOM.AccessibilityPropertiesCurrent.Page:
|
|
current = WI.UIString("Page");
|
|
break;
|
|
case InspectorBackend.Enum.DOM.AccessibilityPropertiesCurrent.Location:
|
|
current = WI.UIString("Location");
|
|
break;
|
|
case InspectorBackend.Enum.DOM.AccessibilityPropertiesCurrent.Step:
|
|
current = WI.UIString("Step");
|
|
break;
|
|
case InspectorBackend.Enum.DOM.AccessibilityPropertiesCurrent.Date:
|
|
current = WI.UIString("Date");
|
|
break;
|
|
case InspectorBackend.Enum.DOM.AccessibilityPropertiesCurrent.Time:
|
|
current = WI.UIString("Time");
|
|
break;
|
|
default:
|
|
current = "";
|
|
}
|
|
|
|
var disabled = booleanValueToLocalizedStringIfTrue("disabled");
|
|
var expanded = booleanValueToLocalizedStringIfPropertyDefined("expanded");
|
|
var flowedNodeLinkList = linkListForNodeIds(accessibilityProperties.flowedNodeIds);
|
|
var focused = booleanValueToLocalizedStringIfPropertyDefined("focused");
|
|
|
|
var ignored = "";
|
|
if (accessibilityProperties.ignored) {
|
|
ignored = WI.UIString("Yes");
|
|
if (accessibilityProperties.hidden)
|
|
ignored = WI.UIString("%s (hidden)").format(ignored);
|
|
else if (accessibilityProperties.ignoredByDefault)
|
|
ignored = WI.UIString("%s (default)").format(ignored);
|
|
}
|
|
|
|
var invalid = "";
|
|
if (accessibilityProperties.invalid === InspectorBackend.Enum.DOM.AccessibilityPropertiesInvalid.True)
|
|
invalid = WI.UIString("Yes");
|
|
else if (accessibilityProperties.invalid === InspectorBackend.Enum.DOM.AccessibilityPropertiesInvalid.Grammar)
|
|
invalid = WI.UIString("Grammar");
|
|
else if (accessibilityProperties.invalid === InspectorBackend.Enum.DOM.AccessibilityPropertiesInvalid.Spelling)
|
|
invalid = WI.UIString("Spelling");
|
|
|
|
var label = accessibilityProperties.label;
|
|
|
|
var liveRegionStatus = "";
|
|
var liveRegionStatusNode = null;
|
|
var liveRegionStatusToken = accessibilityProperties.liveRegionStatus;
|
|
switch (liveRegionStatusToken) {
|
|
case InspectorBackend.Enum.DOM.AccessibilityPropertiesLiveRegionStatus.Assertive:
|
|
liveRegionStatus = WI.UIString("Assertive");
|
|
break;
|
|
case InspectorBackend.Enum.DOM.AccessibilityPropertiesLiveRegionStatus.Polite:
|
|
liveRegionStatus = WI.UIString("Polite");
|
|
break;
|
|
default:
|
|
liveRegionStatus = "";
|
|
}
|
|
if (liveRegionStatus) {
|
|
var liveRegionRelevant = accessibilityProperties.liveRegionRelevant;
|
|
// Append @aria-relevant values. E.g. "Live: Assertive (Additions, Text)".
|
|
if (liveRegionRelevant && liveRegionRelevant.length) {
|
|
// @aria-relevant="all" is exposed as ["additions","removals","text"], in order.
|
|
// This order is controlled in WebCore and expected in WebInspectorUI.
|
|
if (liveRegionRelevant.length === 3
|
|
&& liveRegionRelevant[0] === InspectorBackend.Enum.DOM.LiveRegionRelevant.Additions
|
|
&& liveRegionRelevant[1] === InspectorBackend.Enum.DOM.LiveRegionRelevant.Removals
|
|
&& liveRegionRelevant[2] === InspectorBackend.Enum.DOM.LiveRegionRelevant.Text)
|
|
liveRegionRelevant = [WI.UIString("All Changes")];
|
|
else {
|
|
// Reassign localized strings in place: ["additions","text"] becomes ["Additions","Text"].
|
|
liveRegionRelevant = liveRegionRelevant.map(function(value) {
|
|
switch (value) {
|
|
case InspectorBackend.Enum.DOM.LiveRegionRelevant.Additions:
|
|
return WI.UIString("Additions");
|
|
case InspectorBackend.Enum.DOM.LiveRegionRelevant.Removals:
|
|
return WI.UIString("Removals");
|
|
case InspectorBackend.Enum.DOM.LiveRegionRelevant.Text:
|
|
return WI.UIString("Text");
|
|
default: // If WebCore sends a new unhandled value, display as a String.
|
|
return "\"" + value + "\"";
|
|
}
|
|
});
|
|
}
|
|
liveRegionStatus += " (" + liveRegionRelevant.join(", ") + ")";
|
|
}
|
|
// Clarify @aria-atomic if necessary.
|
|
if (accessibilityProperties.liveRegionAtomic) {
|
|
liveRegionStatusNode = document.createElement("div");
|
|
liveRegionStatusNode.className = "value-with-clarification";
|
|
liveRegionStatusNode.setAttribute("role", "text");
|
|
liveRegionStatusNode.append(liveRegionStatus);
|
|
var clarificationNode = document.createElement("div");
|
|
clarificationNode.className = "clarification";
|
|
clarificationNode.append(WI.UIString("Region announced in its entirety."));
|
|
liveRegionStatusNode.appendChild(clarificationNode);
|
|
}
|
|
}
|
|
|
|
var mouseEventNodeId = accessibilityProperties.mouseEventNodeId;
|
|
var mouseEventTextValue = "";
|
|
var mouseEventNodeLink = null;
|
|
if (mouseEventNodeId) {
|
|
if (mouseEventNodeId === accessibilityProperties.nodeId)
|
|
mouseEventTextValue = WI.UIString("Yes");
|
|
else
|
|
mouseEventNodeLink = linkForNodeId(mouseEventNodeId);
|
|
}
|
|
|
|
var ownedNodeLinkList = linkListForNodeIds(accessibilityProperties.ownedNodeIds);
|
|
|
|
// Accessibility tree parent is not a 1:1 mapping with the DOM tree parent.
|
|
var parentNodeLink = linkForNodeId(accessibilityProperties.parentNodeId);
|
|
|
|
var pressed = booleanValueToLocalizedStringIfPropertyDefined("pressed");
|
|
var readonly = booleanValueToLocalizedStringIfTrue("readonly");
|
|
var required = booleanValueToLocalizedStringIfPropertyDefined("required");
|
|
|
|
var role = accessibilityProperties.role;
|
|
let hasPopup = accessibilityProperties.isPopupButton;
|
|
let roleType = null;
|
|
let buttonType = null;
|
|
let buttonTypePopupString = WI.UIString("popup");
|
|
let buttonTypeToggleString = WI.UIString("toggle");
|
|
let buttonTypePopupToggleString = WI.UIString("popup, toggle");
|
|
|
|
if (role === "" || role === "unknown")
|
|
role = WI.UIString("No matching ARIA role");
|
|
else if (role) {
|
|
if (role === "button") {
|
|
if (pressed)
|
|
buttonType = buttonTypeToggleString;
|
|
|
|
// In cases where an element is a toggle button, but it also has
|
|
// aria-haspopup, we concatenate the button types. If it is just
|
|
// a popup button, we only include "popup".
|
|
if (hasPopup)
|
|
buttonType = buttonType ? buttonTypePopupToggleString : buttonTypePopupString;
|
|
}
|
|
|
|
if (!domNode.getAttribute("role"))
|
|
roleType = WI.UIString("default");
|
|
else if (buttonType || domNode.getAttribute("role") !== role)
|
|
roleType = WI.UIString("computed");
|
|
|
|
if (buttonType && roleType)
|
|
role = WI.UIString("%s (%s, %s)").format(role, buttonType, roleType);
|
|
else if (roleType || buttonType) {
|
|
let extraInfo = roleType || buttonType;
|
|
role = WI.UIString("%s (%s)").format(role, extraInfo);
|
|
}
|
|
}
|
|
|
|
var selected = booleanValueToLocalizedStringIfTrue("selected");
|
|
var selectedChildNodeLinkList = linkListForNodeIds(accessibilityProperties.selectedChildNodeIds);
|
|
|
|
var headingLevel = accessibilityProperties.headingLevel;
|
|
var hierarchyLevel = accessibilityProperties.hierarchyLevel;
|
|
// Assign all the properties to their respective views.
|
|
this._accessibilityNodeActiveDescendantRow.value = activeDescendantLink || "";
|
|
this._accessibilityNodeBusyRow.value = busy;
|
|
this._accessibilityNodeCheckedRow.value = checked;
|
|
this._accessibilityNodeChildrenRow.value = childNodeLinkList || "";
|
|
this._accessibilityNodeControlsRow.value = controlledNodeLinkList || "";
|
|
this._accessibilityNodeCurrentRow.value = current;
|
|
this._accessibilityNodeDisabledRow.value = disabled;
|
|
this._accessibilityNodeExpandedRow.value = expanded;
|
|
this._accessibilityNodeFlowsRow.value = flowedNodeLinkList || "";
|
|
this._accessibilityNodeFocusedRow.value = focused;
|
|
this._accessibilityNodeHeadingLevelRow.value = headingLevel || "";
|
|
this._accessibilityNodehierarchyLevelRow.value = hierarchyLevel || "";
|
|
this._accessibilityNodeIgnoredRow.value = ignored;
|
|
this._accessibilityNodeInvalidRow.value = invalid;
|
|
this._accessibilityNodeLabelRow.value = label;
|
|
this._accessibilityNodeLiveRegionStatusRow.value = liveRegionStatusNode || liveRegionStatus;
|
|
|
|
// Row label changes based on whether the value is a delegate node link.
|
|
this._accessibilityNodeMouseEventRow.label = mouseEventNodeLink ? WI.UIString("Click Listener") : WI.UIString("Clickable");
|
|
this._accessibilityNodeMouseEventRow.value = mouseEventNodeLink || mouseEventTextValue;
|
|
|
|
this._accessibilityNodeOwnsRow.value = ownedNodeLinkList || "";
|
|
this._accessibilityNodeParentRow.value = parentNodeLink || "";
|
|
this._accessibilityNodePressedRow.value = pressed;
|
|
this._accessibilityNodeReadonlyRow.value = readonly;
|
|
this._accessibilityNodeRequiredRow.value = required;
|
|
this._accessibilityNodeRoleRow.value = role;
|
|
this._accessibilityNodeSelectedRow.value = selected;
|
|
|
|
this._accessibilityNodeSelectedChildrenRow.label = WI.UIString("Selected Items");
|
|
this._accessibilityNodeSelectedChildrenRow.value = selectedChildNodeLinkList || "";
|
|
if (selectedChildNodeLinkList && accessibilityProperties.selectedChildNodeIds.length === 1)
|
|
this._accessibilityNodeSelectedChildrenRow.label = WI.UIString("Selected Item");
|
|
|
|
// Display order, not alphabetical as above.
|
|
this._accessibilityGroup.rows = [
|
|
// Global properties for all elements.
|
|
this._accessibilityNodeIgnoredRow,
|
|
this._accessibilityNodeRoleRow,
|
|
this._accessibilityNodeLabelRow,
|
|
this._accessibilityNodeParentRow,
|
|
this._accessibilityNodeActiveDescendantRow,
|
|
this._accessibilityNodeSelectedChildrenRow,
|
|
this._accessibilityNodeChildrenRow,
|
|
this._accessibilityNodeOwnsRow,
|
|
this._accessibilityNodeControlsRow,
|
|
this._accessibilityNodeFlowsRow,
|
|
this._accessibilityNodeMouseEventRow,
|
|
this._accessibilityNodeFocusedRow,
|
|
this._accessibilityNodeHeadingLevelRow,
|
|
this._accessibilityNodehierarchyLevelRow,
|
|
this._accessibilityNodeBusyRow,
|
|
this._accessibilityNodeLiveRegionStatusRow,
|
|
this._accessibilityNodeCurrentRow,
|
|
|
|
// Properties exposed for all input-type elements.
|
|
this._accessibilityNodeDisabledRow,
|
|
this._accessibilityNodeInvalidRow,
|
|
this._accessibilityNodeRequiredRow,
|
|
|
|
// Role-specific properties.
|
|
this._accessibilityNodeCheckedRow,
|
|
this._accessibilityNodeExpandedRow,
|
|
this._accessibilityNodePressedRow,
|
|
this._accessibilityNodeReadonlyRow,
|
|
this._accessibilityNodeSelectedRow
|
|
];
|
|
|
|
this._accessibilityEmptyRow.hideEmptyMessage();
|
|
|
|
} else {
|
|
this._accessibilityGroup.rows = [this._accessibilityEmptyRow];
|
|
this._accessibilityEmptyRow.showEmptyMessage();
|
|
}
|
|
}
|
|
|
|
domNode.accessibilityProperties(accessibilityPropertiesCallback.bind(this));
|
|
}
|
|
|
|
_populateEventListenersFilterContextMenu(contextMenu)
|
|
{
|
|
let addGroupingMethodCheckboxItem = (label, groupingMethod) => {
|
|
contextMenu.appendCheckboxItem(label, () => {
|
|
this._eventListenerGroupingMethodSetting.value = groupingMethod;
|
|
|
|
this._refreshEventListeners();
|
|
}, this._eventListenerGroupingMethodSetting.value === groupingMethod);
|
|
};
|
|
|
|
addGroupingMethodCheckboxItem(WI.UIString("Group by Event", "Group by Event @ Node Event Listeners", "Group DOM event listeners by DOM event"), WI.DOMNodeDetailsSidebarPanel.EventListenerGroupingMethod.Event);
|
|
addGroupingMethodCheckboxItem(WI.UIString("Group by Target", "Group by Target @ Node Event Listeners", "Group DOM event listeners by DOM node"), WI.DOMNodeDetailsSidebarPanel.EventListenerGroupingMethod.Target);
|
|
}
|
|
|
|
_eventListenersChanged(event)
|
|
{
|
|
if (event.target === this.domNode || event.target.isAncestor(this.domNode))
|
|
this._refreshEventListeners();
|
|
}
|
|
|
|
_attributesChanged(event)
|
|
{
|
|
if (event.data.node !== this.domNode)
|
|
return;
|
|
this._refreshAttributes();
|
|
this._refreshAccessibility();
|
|
this._refreshDataBindings();
|
|
}
|
|
|
|
_attributeNodeValueChanged(event)
|
|
{
|
|
let change = event.data;
|
|
let data = event.target.data;
|
|
|
|
if (change.columnIdentifier === "name") {
|
|
this.domNode.removeAttribute(data[change.columnIdentifier], (error) => {
|
|
this.domNode.setAttribute(change.value, `${change.value}="${data.value}"`);
|
|
});
|
|
} else if (change.columnIdentifier === "value")
|
|
this.domNode.setAttributeValue(data.name, change.value);
|
|
}
|
|
|
|
_characterDataModified(event)
|
|
{
|
|
if (event.data.node !== this.domNode)
|
|
return;
|
|
this._identityNodeValueRow.value = this.domNode.nodeValue();
|
|
}
|
|
|
|
_customElementStateChanged(event)
|
|
{
|
|
if (event.data.node !== this.domNode)
|
|
return;
|
|
this._refreshIdentity();
|
|
}
|
|
|
|
_nodeTypeDisplayName()
|
|
{
|
|
switch (this.domNode.nodeType()) {
|
|
case Node.ELEMENT_NODE: {
|
|
const nodeName = WI.UIString("Element");
|
|
const state = this._customElementState();
|
|
return state === null ? nodeName : `${nodeName} (${state})`;
|
|
}
|
|
case Node.TEXT_NODE:
|
|
return WI.UIString("Text Node");
|
|
case Node.COMMENT_NODE:
|
|
return WI.UIString("Comment");
|
|
case Node.DOCUMENT_NODE:
|
|
return WI.UIString("Document");
|
|
case Node.DOCUMENT_TYPE_NODE:
|
|
return WI.UIString("Document Type");
|
|
case Node.DOCUMENT_FRAGMENT_NODE:
|
|
return WI.UIString("Document Fragment");
|
|
case Node.CDATA_SECTION_NODE:
|
|
return WI.UIString("Character Data");
|
|
case Node.PROCESSING_INSTRUCTION_NODE:
|
|
return WI.UIString("Processing Instruction");
|
|
default:
|
|
console.error("Unknown DOM node type: ", this.domNode.nodeType());
|
|
return this.domNode.nodeType();
|
|
}
|
|
}
|
|
|
|
_customElementState()
|
|
{
|
|
const state = this.domNode.customElementState();
|
|
switch (state) {
|
|
case WI.DOMNode.CustomElementState.Builtin:
|
|
return null;
|
|
case WI.DOMNode.CustomElementState.Custom:
|
|
return WI.UIString("Custom");
|
|
case WI.DOMNode.CustomElementState.Waiting:
|
|
return WI.UIString("Undefined custom element");
|
|
case WI.DOMNode.CustomElementState.Failed:
|
|
return WI.UIString("Failed to upgrade");
|
|
}
|
|
console.error("Unknown DOM custom element state: ", state);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
WI.DOMNodeDetailsSidebarPanel.EventListenerGroupingMethod = {
|
|
Event: "event",
|
|
Target: "target",
|
|
};
|