@@ -62,24 +62,80 @@ async function _updateSubtree(selectedNode) {
6262
6363 const { attributes, nodeName } = node ;
6464 issues . push (
65- ..._webcompat . getHTMLElementIssues ( nodeName , attributes , _targetBrowsers ) ) ;
65+ ...
66+ _webcompat . getHTMLElementIssues ( nodeName , attributes , _targetBrowsers )
67+ . map ( issue => {
68+ issue . node = node ;
69+ return issue ;
70+ } )
71+ )
6672 }
6773
6874 progressEl . textContent = "Getting all descendants of the selected node" ;
6975 const declarationBlocks = await browser . experiments . inspectedNode . getStylesInSubtree ( ) ;
7076
7177 progressEl . textContent = "Getting web compatibility issues for CSS styles" ;
72- for ( const { declarations } of declarationBlocks ) {
78+ for ( const { node , declarations } of declarationBlocks ) {
7379 issues . push (
74- ..._webcompat . getCSSDeclarationBlockIssues ( declarations , _targetBrowsers ) ) ;
80+ ...
81+ _webcompat . getCSSDeclarationBlockIssues ( declarations , _targetBrowsers )
82+ . map ( issue => {
83+ issue . node = node ;
84+ return issue ;
85+ } )
86+ )
7587 }
7688
89+ progressEl . textContent = "Grouping all issues" ;
90+ const issueGroups = _groupIssues ( issues ) ;
91+
7792 progressEl . textContent = "Rendering all issues" ;
78- _render ( issues , issueListEl ) ;
93+ _render ( issueGroups , issueListEl ) ;
7994
8095 subtreeEl . classList . remove ( "processing" ) ;
8196}
8297
98+ /**
99+ * Group by the issue cause.
100+ * @param {Array } issues
101+ * The issue list which WebCompat library returns. Also the issue in the list
102+ * assume to contain the node information additionaly.
103+ * @return {Array }
104+ * Array of issues grouped. The issue has `nodes` attribute which contains the
105+ * node informations where caused the issue.
106+ */
107+ function _groupIssues ( issues ) {
108+ const issueGroups = [ ] ;
109+
110+ for ( const issue of issues ) {
111+ let issueGroup = issueGroups . find ( i => {
112+ return i . type === issue . type &&
113+ i . property === issue . property &&
114+ i . element === issue . element &&
115+ i . attribute === issue . attribute &&
116+ i . value === issue . value ;
117+ } ) ;
118+
119+ if ( ! issueGroup ) {
120+ issueGroup = Object . assign ( { } , issue , { nodes : [ ] , node : undefined } ) ;
121+ issueGroups . push ( issueGroup ) ;
122+ }
123+
124+ const isNodeContainedInGroup = issueGroup . nodes . some ( n => {
125+ return n . nodeName === issue . node . nodeName &&
126+ n . nodeType === issue . node . nodeType &&
127+ n . id === issue . node . id &&
128+ n . className === issue . node . className ;
129+ } ) ;
130+
131+ if ( ! isNodeContainedInGroup ) {
132+ issueGroup . nodes . push ( issue . node ) ;
133+ }
134+ }
135+
136+ return issueGroups ;
137+ }
138+
83139function _isValidElement ( { nodeType, isCustomElement } ) {
84140 return nodeType === Node . ELEMENT_NODE && ! isCustomElement ;
85141}
@@ -107,9 +163,47 @@ function _renderIssue(issue) {
107163
108164 issueEl . classList . add ( ( issue . deprecated ? "warning" : "information" ) ) ;
109165
166+ if ( issue . nodes ) {
167+ issueEl . append ( _renderOccurrences ( issue ) ) ;
168+ }
169+
110170 return issueEl ;
111171}
112172
173+ function _renderOccurrences ( { nodes } ) {
174+ const occurrencesEl = document . createElement ( "section" ) ;
175+ occurrencesEl . classList . add ( "occurrences" ) ;
176+
177+ const nodelistEl = document . createElement ( "ul" ) ;
178+ for ( const { id, className, nodeName } of nodes ) {
179+ const nodeEl = document . createElement ( "li" ) ;
180+ nodeEl . append ( _renderTerm ( nodeName . toLowerCase ( ) , [ "node-name" ] ) ) ;
181+
182+ if ( id ) {
183+ nodeEl . append ( _renderTerm ( `#${ id } ` , [ "node-id" ] ) ) ;
184+ } else if ( className . length ) {
185+ nodeEl . append ( _renderTerm ( `.${ className . replace ( / \s + / g, "." ) } ` , [ "node-class" ] ) ) ;
186+ }
187+
188+ nodeEl . addEventListener ( "click" , _onClickNodeSelector ) ;
189+ nodelistEl . append ( nodeEl ) ;
190+ }
191+
192+ if ( nodes . length !== 1 ) {
193+ const summaryEl = document . createElement ( "summary" ) ;
194+ summaryEl . textContent = `${ nodes . length } occurrences` ;
195+
196+ const detailsEl = document . createElement ( "details" ) ;
197+ detailsEl . append ( summaryEl , nodelistEl ) ;
198+
199+ occurrencesEl . append ( detailsEl ) ;
200+ } else {
201+ occurrencesEl . append ( nodelistEl ) ;
202+ }
203+
204+ return occurrencesEl ;
205+ }
206+
113207function _renderSubject ( issue ) {
114208 const { type, url } = issue ;
115209 const subjectEl = document . createElement ( "span" ) ;
@@ -283,6 +377,13 @@ function _onClickLink(e) {
283377 browser . tabs . create ( { url : e . target . href } ) ;
284378}
285379
380+ function _onClickNodeSelector ( e ) {
381+ e . stopPropagation ( ) ;
382+ e . preventDefault ( ) ;
383+ const selector = e . target . closest ( "li" ) . textContent ;
384+ browser . experiments . highlighter . highlight ( selector ) ;
385+ }
386+
286387async function _updateCSSValueEnabled ( ) {
287388 const isCSSValueEnabled = await _userSettings . isCSSValueEnabled ( ) ;
288389 _webcompat . setCSSValueEnabled ( isCSSValueEnabled ) ;
0 commit comments