blob: fc0395177f4bad5bdf879abd1e4bce4a47b8316e [file] [log] [blame]
/*
* 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()
{
WI.domManager.removeEventListener(null, null, 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._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._propertiesRow = new WI.DetailsSectionRow;
var propertiesGroup = new WI.DetailsSectionGroup([this._propertiesRow]);
var propertiesSection = new WI.DetailsSection("dom-node-properties", WI.UIString("Properties"), [propertiesGroup]);
let eventListenersFilterElement = WI.ImageUtilities.useSVGSymbol("Images/FilterFieldGlyph.svg", "filter", WI.UIString("Grouping Method"));
let eventListenersGroupMethodSelectElement = eventListenersFilterElement.appendChild(document.createElement("select"));
eventListenersGroupMethodSelectElement.addEventListener("change", (event) => {
this._eventListenerGroupingMethodSetting.value = eventListenersGroupMethodSelectElement.value;
this._refreshEventListeners();
});
function createOption(text, value) {
let optionElement = eventListenersGroupMethodSelectElement.appendChild(document.createElement("option"));
optionElement.value = value;
optionElement.textContent = text;
}
createOption(WI.UIString("Group by Event"), WI.DOMNodeDetailsSidebarPanel.EventListenerGroupingMethod.Event);
createOption(WI.UIString("Group by Target"), WI.DOMNodeDetailsSidebarPanel.EventListenerGroupingMethod.Target);
eventListenersGroupMethodSelectElement.value = this._eventListenerGroupingMethodSetting.value;
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(identitySection.element);
this.contentView.element.appendChild(attributesSection.element);
this.contentView.element.appendChild(propertiesSection.element);
this.contentView.element.appendChild(eventListenersSection.element);
if (WI.sharedApp.hasExtraDomains) {
let target = WI.assumingMainTarget();
if (target.hasCommand("DOM.getDataBindingsForNode")) {
this._dataBindingsSection = new WI.DetailsSection("dom-node-data-bindings", WI.UIString("Data Bindings"), []);
this.contentView.element.appendChild(this._dataBindingsSection.element);
}
if (target.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)
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._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));
});
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;
}
}
}
_refreshEventListeners()
{
var domNode = this.domNode;
if (!domNode)
return;
const windowTargetIdentifier = Symbol("window");
function createEventListenerSection(title, eventListeners, options = {}) {
let groups = eventListeners.map((eventListener) => new WI.EventListenerSectionGroup(eventListener, options));
let optionsElement = null;
if ((WI.DOMManager.supportsDisablingEventListeners() || WI.DOMManager.supportsEventListenerBreakpoints()) && groups.some((group) => group.supportsStateModification)) {
optionsElement = WI.ImageUtilities.useSVGSymbol("Images/Gear.svg", "event-listener-options", WI.UIString("Options"));
WI.addMouseDownContextMenuHandlers(optionsElement, (contextMenu) => {
if (WI.DOMManager.supportsDisablingEventListeners()) {
let shouldDisable = groups.some((eventListener) => !eventListener.isEventListenerDisabled);
contextMenu.appendItem(shouldDisable ? WI.UIString("Disable Event Listeners") : WI.UIString("Enable Event Listeners"), () => {
for (let group of groups)
group.isEventListenerDisabled = shouldDisable;
});
}
if (WI.DOMManager.supportsEventListenerBreakpoints()) {
let shouldBreakpoint = groups.some((eventListener) => !eventListener.hasEventListenerBreakpoint);
contextMenu.appendItem(shouldBreakpoint ? WI.UIString("Add Breakpoints") : WI.UIString("Delete Breakpoints"), () => {
for (let group of groups)
group.hasEventListenerBreakpoint = shouldBreakpoint;
});
}
});
}
const defaultCollapsedSettingValue = true;
let section = new WI.DetailsSection(`${title}-event-listener-section`, title, groups, optionsElement, defaultCollapsedSettingValue);
section.element.classList.add("event-listener-section");
return section;
}
function generateGroupsByEvent(eventListeners) {
let eventListenerTypes = new Map;
for (let eventListener of eventListeners) {
console.assert(eventListener.nodeId || eventListener.onWindow);
if (eventListener.nodeId)
eventListener.node = WI.domManager.nodeForId(eventListener.nodeId);
let eventListenersForType = eventListenerTypes.get(eventListener.type);
if (!eventListenersForType)
eventListenerTypes.set(eventListener.type, eventListenersForType = []);
eventListenersForType.push(eventListener);
}
let rows = [];
let types = Array.from(eventListenerTypes.keys());
types.sort();
for (let type of types)
rows.push(createEventListenerSection(type, eventListenerTypes.get(type), {hideType: true}));
return rows;
}
function generateGroupsByTarget(eventListeners) {
let eventListenerTargets = new Map;
for (let eventListener of eventListeners) {
console.assert(eventListener.nodeId || eventListener.onWindow);
if (eventListener.nodeId)
eventListener.node = WI.domManager.nodeForId(eventListener.nodeId);
let target = eventListener.onWindow ? windowTargetIdentifier : eventListener.node;
let eventListenersForTarget = eventListenerTargets.get(target);
if (!eventListenersForTarget)
eventListenerTargets.set(target, eventListenersForTarget = []);
eventListenersForTarget.push(eventListener);
}
let rows = [];
function generateSectionForTarget(target) {
let eventListenersForTarget = eventListenerTargets.get(target);
if (!eventListenersForTarget)
return;
eventListenersForTarget.sort((a, b) => a.type.toLowerCase().extendedLocaleCompare(b.type.toLowerCase()));
let title = target === windowTargetIdentifier ? WI.unlocalizedString("window") : target.displayName;
let section = createEventListenerSection(title, eventListenersForTarget, {hideTarget: true});
if (target instanceof WI.DOMNode)
WI.bindInteractionsForNodeToElement(target, section.titleElement, {ignoreClick: true});
rows.push(section);
}
let currentNode = domNode;
do {
generateSectionForTarget(currentNode);
} while (currentNode = currentNode.parentNode);
generateSectionForTarget(windowTargetIdentifier);
return rows;
}
function eventListenersCallback(error, eventListeners)
{
if (error)
return;
// Bail if the DOM node changed while we were waiting for the async response.
if (this.domNode !== domNode)
return;
if (!eventListeners.length) {
var 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 = generateGroupsByEvent.call(this, eventListeners);
break;
case WI.DOMNodeDetailsSidebarPanel.EventListenerGroupingMethod.Target:
this._eventListenersSectionGroup.rows = generateGroupsByTarget.call(this, eventListeners);
break;
}
}
domNode.getEventListeners(eventListenersCallback.bind(this));
}
_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));
}
_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",
};