Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,12 @@ export default class DomPathUtils {
let slotParts = parts[2].match(/(slot\[\d+\])\/([^[]*)\[(\d+)\]/)!;
let slot = this.docDomPathToElement(doc, parts[1]+slotParts[1]);
let count = parseInt(slotParts[3]);
for (let slotIdx=0; slotIdx < (slot as any).assignedNodes().length; ++slotIdx) {
let slotNode = (slot as any).assignedNodes()[slotIdx];
// Use assignedElements() if available, otherwise filter assignedNodes() for element nodes
let assignedElems = (slot as any).assignedElements
? (slot as any).assignedElements()
: (slot as any).assignedNodes().filter((n: Node) => n.nodeType === 1);
for (let slotIdx=0; slotIdx < assignedElems.length; ++slotIdx) {
let slotNode = assignedElems[slotIdx];
if (slotNode.nodeName.toLowerCase() === slotParts[2].toLowerCase()) {
--count;
if (count === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
MenuItemSelectable
} from "@carbon/react";
import { UtilIssue } from '../../util/UtilIssue';
import { PathMatcher } from '../../util/PathMatcher';

import {
Information
Expand Down Expand Up @@ -190,11 +191,11 @@ export class ReportSection extends React.Component<ReportSectionProps, ReportSec
reportIssues = reportIssues.filter((issue: UIIssue) => {
issue.ignored = this.state.ignoredIssues.some(ignoredIssue => issueBaselineMatch(ignoredIssue, issue));
const checked = this.devtoolsAppController.getLevelFilters();
let retVal = ( ((checked["Hidden"] && issue.ignored) || checked[UtilIssue.valueToStringSingular(issue.value) as eFilterLevel])
let retVal = ( ((checked["Hidden"] && issue.ignored) || checked[UtilIssue.valueToStringSingular(issue.value) as eFilterLevel])
&& (!this.state.focusMode
|| !this.state.selectedPath
|| issue.path.dom.startsWith(this.state.selectedPath)
)
|| PathMatcher.matchesPath(issue.path.dom, this.state.selectedPath)
)
);
if (!checked["Hidden"] && issue.ignored) {
return false; // JCH is this an override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { ePanel, getDevtoolsController, ViewState } from '../devtoolsController'
import { UtilIssue } from '../../util/UtilIssue';
import { UtilIssueReact } from '../../util/UtilIssueReact';
import { getBGController, issueBaselineMatch } from '../../background/backgroundController';
import { PathMatcher } from '../../util/PathMatcher';

export interface IRowGroup {
id: string;
Expand Down Expand Up @@ -783,7 +784,7 @@ export class ReportTreeGrid<RowType extends IRowGroup> extends React.Component<R
let selectedNode: boolean = !!this.props.selectedPath
&& this.props.selectedPath === thisIssue.path.dom;
let selectedDescendant: boolean = !!this.props.selectedPath
&& thisIssue.path.dom.startsWith(this.props.selectedPath);
&& PathMatcher.matchesPath(thisIssue.path.dom, this.props.selectedPath);
let focused: boolean = this.state.tabRowId === rowId
bodyContent.push(
<Grid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import ReactDOM from 'react-dom';
import { IIssue, IReport, UIIssue, eFilterLevel } from '../../interfaces/interfaces';
import { UtilIssue } from '../../util/UtilIssue';
import { UtilIssueReact } from '../../util/UtilIssueReact';
import { PathMatcher } from '../../util/PathMatcher';
import { getDevtoolsController, ScanningState, ViewState } from '../devtoolsController';
import { getBGController, issueBaselineMatch, TabChangeType } from '../../background/backgroundController';
import {
Expand Down Expand Up @@ -176,6 +177,7 @@ export class ScanSection extends React.Component<{}, ScanSectionState> {
})
this.devtoolsController.addSelectedElementPathListener(async (newPath) => {
this.setState( { selectedElemPath: newPath });
this.setPath(newPath);
})
this.devtoolsController.addFocusModeListener(async (newValue) => {
this.setState({ focusMode: newValue })
Expand All @@ -196,10 +198,12 @@ export class ScanSection extends React.Component<{}, ScanSectionState> {
}
})
this.reportListener((await this.devtoolsController.getReport())!);
this.setState({
viewState: (await this.devtoolsController.getViewState())!,
const currentPath = (await this.devtoolsController.getSelectedElementPath())!;
this.setState({
viewState: (await this.devtoolsController.getViewState())!,
storeReports: (await this.devtoolsController.getStoreReports()),
selectedElemPath: (await this.devtoolsController.getSelectedElementPath())! || "/html",
selectedElemPath: currentPath || "/html",
selectedPath: currentPath || null,
focusMode: (await this.devtoolsController.getFocusMode()),
storedReportsCount: (await this.devtoolsController.getStoredReportsMeta()).length,
canScan: (await this.bgController.getTabInfo(tabId)).canScan
Expand Down Expand Up @@ -259,7 +263,7 @@ export class ScanSection extends React.Component<{}, ScanSectionState> {
for (const issue of issues) {
let sing = UtilIssue.valueToStringSingular(issue.value);
++counts[sing as eLevel].total;
if (!this.state.selectedPath || issue.path.dom.startsWith(this.state.selectedPath)) {
if (!this.state.selectedPath || PathMatcher.matchesPath(issue.path.dom, this.state.selectedPath)) {
++counts[sing as eLevel].focused;
}
}
Expand All @@ -268,12 +272,12 @@ export class ScanSection extends React.Component<{}, ScanSectionState> {
for (const ignoredIssue of this.state.ignoredIssues) {
if (!issues.some(issue => issueBaselineMatch(issue, ignoredIssue))) continue;
++counts["Hidden" as eLevel].total;
if (!this.state.selectedPath || ignoredIssue.path.dom.startsWith(this.state.selectedPath)) {
if (!this.state.selectedPath || PathMatcher.matchesPath(ignoredIssue.path.dom, this.state.selectedPath)) {
++counts["Hidden" as eLevel].focused;
}
let sing = UtilIssue.valueToStringSingular(ignoredIssue.value);
--counts[sing as eLevel].total;
if (!this.state.selectedPath || ignoredIssue.path.dom.startsWith(this.state.selectedPath)) {
if (!this.state.selectedPath || PathMatcher.matchesPath(ignoredIssue.path.dom, this.state.selectedPath)) {
--counts[sing as eLevel].focused;
}
}
Expand All @@ -300,7 +304,7 @@ export class ScanSection extends React.Component<{}, ScanSectionState> {
let retVal = (this.state.checked[UtilIssue.valueToStringSingular(issue.value) as eLevel]
&& (!this.state.focusMode
|| !this.state.selectedPath
|| issue.path.dom.startsWith(this.state.selectedPath)
|| PathMatcher.matchesPath(issue.path.dom, this.state.selectedPath)
)
);
return retVal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,53 +153,104 @@ export class DevtoolsAppController {
public hookSelectionChange() {
chrome.devtools.panels.elements.onSelectionChanged.addListener(() => {
chrome.devtools.inspectedWindow.eval(`((node) => {
let countNode = (node) => {
let count = 0;
let findName = node.nodeName;
while (node) {
if (node.nodeName === findName) {
++count;
function getIndex(n) {
if (n.assignedSlot) {
const assigned = n.assignedSlot.assignedElements
? n.assignedSlot.assignedElements()
: Array.from(n.assignedSlot.assignedNodes()).filter(x => x.nodeType === 1);
let count = 0;
for (const el of assigned) {
if (el === n) break;
if (el.localName === n.localName) count++;
}
node = node.previousElementSibling;
return "/" + n.localName + "[" + (count + 1) + "]";
}
const parent = n.parentNode;
if (!parent) return "/" + n.localName + "[1]";
let count = 0;
const children = parent.children;
for (let i = 0; i < children.length; i++) {
if (children[i] === n) break;
if (children[i].localName === n.localName) count++;
}
return "/" + n.localName + "[" + (count + 1) + "]";
}
function getSlotIndex(slot) {
let count = 1;
let sib = slot.previousElementSibling;
while (sib) {
if (sib.localName === 'slot') count++;
sib = sib.previousElementSibling;
}
return "/"+findName.toLowerCase()+"["+count+"]";
return count;
}
function getIframePrefixPath(iframeElem) {
let prefix = "";
let current = iframeElem;
while (current && current.nodeType === 1) {
prefix = getIndex(current) + prefix;
const parent = current.parentNode;
if (!parent) break;
if (parent.nodeType === 9) break;
else if (parent.nodeType === 11) { prefix = "/#document-fragment[1]" + prefix; current = parent.host; }
else if (parent.nodeType === 1) current = parent;
else break;
}
return prefix;
}
try {
let retVal = "";
while (node && node.nodeType === 1) {
retVal = countNode(node)+retVal;
if (node.parentElement) {
node = node.parentElement;
if (!node || node.nodeType !== 1) return "";
let segments = "";
let current = node;
while (current && current.nodeType === 1) {
const assignedSlot = current.assignedSlot;
if (assignedSlot) {
segments = getIndex(current) + segments;
segments = "/slot[" + getSlotIndex(assignedSlot) + "]" + segments;
const slotParent = assignedSlot.parentNode;
if (!slotParent) break;
if (slotParent.nodeType === 11) { segments = "/#document-fragment[1]" + segments; current = slotParent.host; }
else if (slotParent.nodeType === 9) {
try {
const parentWin = slotParent.defaultView;
if (parentWin && parentWin.parent && parentWin.parent !== parentWin) {
const iframes = parentWin.parent.document.querySelectorAll("iframe");
for (const iframe of iframes) {
try { if (iframe.contentDocument === slotParent) { segments = getIframePrefixPath(iframe) + segments; break; } } catch(e) {}
}
}
} catch(e) {}
break;
}
else if (slotParent.nodeType === 1) current = slotParent;
else break;
} else {
let parentElement = null;
try {
// Check if we're in a shadow DOM
if (node.parentNode && node.parentNode.nodeType === 11) {
parentElement = node.parentNode.host;
retVal = "/#document-fragment[1]"+retVal;
} else {
// Check if we're in an iframe
let parentWin = node.ownerDocument.defaultView.parent;
let iframes = parentWin.document.documentElement.querySelectorAll("iframe");
for (const iframe of iframes) {
try {
if (iframe.contentDocument === node.ownerDocument) {
parentElement = iframe;
break;
}
} catch (e) {}
segments = getIndex(current) + segments;
const parent = current.parentNode;
if (!parent) break;
if (parent.nodeType === 11) { segments = "/#document-fragment[1]" + segments; current = parent.host; }
else if (parent.nodeType === 9) {
try {
const parentWin = parent.defaultView;
if (parentWin && parentWin.parent && parentWin.parent !== parentWin) {
const iframes = parentWin.parent.document.querySelectorAll("iframe");
for (const iframe of iframes) {
try { if (iframe.contentDocument === parent) { segments = getIframePrefixPath(iframe) + segments; break; } } catch(e) {}
}
}
}
} catch (e) {}
node = parentElement;
} catch(e) {}
break;
}
else if (parent.nodeType === 1) current = parent;
else break;
}
}
return retVal;
} catch (err) {
console.error(err);
}
return segments;
} catch(err) { return ""; }
})($0)`, async (result: string) => {
await this.devToolsController.setSelectedElementPath(result, true);
if (result) {
await this.devToolsController.setSelectedElementPath(result, true);
}
});
});
chrome.devtools.inspectedWindow.eval(`inspect(document.documentElement);`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*****************************************************************************/

import { getBGController, issueBaselineMatch, TabChangeType } from "../background/backgroundController";
import { PathMatcher } from "../util/PathMatcher";
import { IBasicTableRowRecord, IIssue, IMessage, IReport, IStoredReportMeta, UIIssue } from "../interfaces/interfaces";
import { CommonMessaging } from "../messaging/commonMessaging";
import { Controller, eControllerType, ListenerType } from "../messaging/controller";
Expand Down Expand Up @@ -431,36 +432,38 @@ export class DevtoolsController extends Controller {
*/
public async setSelectedElementPath(path: string | null, fromElemChange?: boolean) : Promise<void> {
return this.hook("setSelectedElementPath", { path, fromElemChange }, async () => {
const previousPath = devtoolsState!.lastElementPath;
devtoolsState!.lastElementPath = path;
if (fromElemChange === true) {
// This path came from the Elements panel selection changing
if (!this.programmaticInspect) {
// User clicked on this
if (Config.ELEM_FOCUS_MODE) {
await this.setFocusMode(true);
}
if (path && path !== devtoolsState!.lastElementPath) {
// if (path) {
let report = await this.getReport();
if (report) {
let newIssue : IIssue | null = null;
for (const issue of report.results) {
if (issue.value[1] !== "PASS" && issue.path.dom === path) {
newIssue = issue;
break;
} else if (!newIssue && issue.value[1] !== "PASS" && issue.path.dom.startsWith(path)) {
newIssue = issue;
}
// This path came from the Elements panel selection changing.
// Always activate focus mode and update the selected element path.
// The programmaticInspect flag only suppresses auto-selecting a new
// issue — it must not block focus mode, because inspectPath is called
// precisely WHEN the user already has an issue selected and we want
// to keep showing the filtered view for that element.
if (Config.ELEM_FOCUS_MODE) {
await this.setFocusMode(true);
}
if (this.programmaticInspect) {
// Echo from inspectPath's own inspect(element) call — clear flag,
// do not auto-select a different issue.
this.programmaticInspect = false;
} else if (path && path !== previousPath) {
// Genuine user navigation — auto-select the nearest matching issue.
let report = await this.getReport();
if (report) {
let newIssue : IIssue | null = null;
for (const issue of report.results) {
if (issue.value[1] !== "PASS" && issue.path.dom === path) {
newIssue = issue;
break;
} else if (!newIssue && issue.value[1] !== "PASS" && PathMatcher.matchesPath(issue.path.dom, path)) {
newIssue = issue;
}
await this.setSelectedIssue(newIssue);
}
if (newIssue) await this.setSelectedIssue(newIssue);
}
} else {
// Side effect of inspectPath
this.programmaticInspect = false;
}
} else {
// Called by some other process
}
setTimeout(() => {
this.notifyEventListeners("DT_onSelectedElementPath", this.ctrlDest.tabId, path);
Expand Down
Loading
Loading