Paul Lewis | 8cddf993 | 2019-09-27 16:40:07 | [diff] [blame] | 1 | // Copyright 2019 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Tim van der Lippe | 7696157 | 2021-04-06 10:48:07 | [diff] [blame] | 5 | import * as Common from '../../core/common/common.js'; |
Tim van der Lippe | d8caac4 | 2021-03-31 14:40:44 | [diff] [blame] | 6 | import * as Root from '../../core/root/root.js'; |
Tim van der Lippe | e00b92f | 2021-03-31 16:52:17 | [diff] [blame] | 7 | import * as SDK from '../../core/sdk/sdk.js'; |
Tim van der Lippe | 51ae9e1 | 2021-04-13 12:41:06 | [diff] [blame] | 8 | import * as ColorPicker from '../../ui/legacy/components/color_picker/color_picker.js'; |
Tim van der Lippe | 03ca595 | 2021-05-12 13:16:25 | [diff] [blame] | 9 | import type * as ProtocolProxyApi from '../../generated/protocol-proxy-api.js'; |
Tim van der Lippe | 229a54f | 2021-05-14 16:59:05 | [diff] [blame] | 10 | import * as Protocol from '../../generated/protocol.js'; |
Tim van der Lippe | 7946bbc | 2020-02-13 13:58:42 | [diff] [blame] | 11 | |
Jack Franklin | a75ae7c | 2021-05-11 13:22:54 | [diff] [blame] | 12 | import type {ContrastIssue} from './CSSOverviewCompletedView.js'; |
| 13 | import type {UnusedDeclaration} from './CSSOverviewUnusedDeclarations.js'; |
| 14 | import {CSSOverviewUnusedDeclarations} from './CSSOverviewUnusedDeclarations.js'; |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 15 | |
| 16 | interface NodeStyleStats { |
| 17 | elementCount: number; |
Philip Pfaffe | 8071e53 | 2021-09-20 08:27:15 | [diff] [blame] | 18 | backgroundColors: Map<string, Set<Protocol.DOM.BackendNodeId>>; |
| 19 | textColors: Map<string, Set<Protocol.DOM.BackendNodeId>>; |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 20 | textColorContrastIssues: Map<string, ContrastIssue[]>; |
Philip Pfaffe | 8071e53 | 2021-09-20 08:27:15 | [diff] [blame] | 21 | fillColors: Map<string, Set<Protocol.DOM.BackendNodeId>>; |
| 22 | borderColors: Map<string, Set<Protocol.DOM.BackendNodeId>>; |
| 23 | fontInfo: Map<string, Map<string, Map<string, Protocol.DOM.BackendNodeId[]>>>; |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 24 | unusedDeclarations: Map<string, UnusedDeclaration[]>; |
| 25 | } |
| 26 | |
| 27 | export interface GlobalStyleStats { |
| 28 | styleRules: number; |
| 29 | inlineStyles: number; |
| 30 | externalSheets: number; |
| 31 | stats: { |
| 32 | // Simple. |
| 33 | type: number, |
| 34 | class: number, |
| 35 | id: number, |
| 36 | universal: number, |
| 37 | attribute: number, |
| 38 | |
| 39 | // Non-simple. |
| 40 | nonSimple: number, |
| 41 | }; |
| 42 | } |
| 43 | |
Simon Zünd | dbb8de4 | 2021-09-22 07:56:48 | [diff] [blame] | 44 | export class CSSOverviewModel extends SDK.SDKModel.SDKModel<void> { |
Tim van der Lippe | 1e4e83c | 2021-10-12 16:16:58 | [diff] [blame^] | 45 | readonly #runtimeAgent: ProtocolProxyApi.RuntimeApi; |
| 46 | readonly #cssAgent: ProtocolProxyApi.CSSApi; |
| 47 | readonly #domAgent: ProtocolProxyApi.DOMApi; |
| 48 | readonly #domSnapshotAgent: ProtocolProxyApi.DOMSnapshotApi; |
| 49 | readonly #overlayAgent: ProtocolProxyApi.OverlayApi; |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 50 | |
Sigurd Schneider | 79e812e | 2021-06-04 06:48:23 | [diff] [blame] | 51 | constructor(target: SDK.Target.Target) { |
Paul Lewis | 8cddf993 | 2019-09-27 16:40:07 | [diff] [blame] | 52 | super(target); |
| 53 | |
Tim van der Lippe | 1e4e83c | 2021-10-12 16:16:58 | [diff] [blame^] | 54 | this.#runtimeAgent = target.runtimeAgent(); |
| 55 | this.#cssAgent = target.cssAgent(); |
| 56 | this.#domAgent = target.domAgent(); |
| 57 | this.#domSnapshotAgent = target.domsnapshotAgent(); |
| 58 | this.#overlayAgent = target.overlayAgent(); |
Paul Lewis | 8cddf993 | 2019-09-27 16:40:07 | [diff] [blame] | 59 | } |
| 60 | |
Sigurd Schneider | e18ce8e | 2021-07-06 08:57:07 | [diff] [blame] | 61 | highlightNode(node: Protocol.DOM.BackendNodeId): void { |
Alex Rudenko | c88c76b | 2020-11-20 15:08:07 | [diff] [blame] | 62 | const highlightConfig = { |
| 63 | contentColor: Common.Color.PageHighlight.Content.toProtocolRGBA(), |
| 64 | showInfo: true, |
| 65 | contrastAlgorithm: Root.Runtime.experiments.isEnabled('APCA') ? Protocol.Overlay.ContrastAlgorithm.Apca : |
| 66 | Protocol.Overlay.ContrastAlgorithm.Aa, |
| 67 | }; |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 68 | |
Tim van der Lippe | 1e4e83c | 2021-10-12 16:16:58 | [diff] [blame^] | 69 | this.#overlayAgent.invoke_hideHighlight(); |
| 70 | this.#overlayAgent.invoke_highlightNode({backendNodeId: node, highlightConfig}); |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 71 | } |
| 72 | |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 73 | async getNodeStyleStats(): Promise<NodeStyleStats> { |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 74 | const backgroundColors = new Map(); |
| 75 | const textColors = new Map(); |
Alex Rudenko | d55e18c | 2020-09-23 11:37:06 | [diff] [blame] | 76 | const textColorContrastIssues = new Map(); |
Paul Lewis | 12bc63e | 2019-10-23 10:53:29 | [diff] [blame] | 77 | const fillColors = new Map(); |
| 78 | const borderColors = new Map(); |
Paul Lewis | 7d10a73 | 2019-11-06 16:11:51 | [diff] [blame] | 79 | const fontInfo = new Map(); |
Paul Lewis | ed808ce | 2019-11-06 14:21:54 | [diff] [blame] | 80 | const unusedDeclarations = new Map(); |
Paul Lewis | 12bc63e | 2019-10-23 10:53:29 | [diff] [blame] | 81 | const snapshotConfig = { |
| 82 | computedStyles: [ |
Paul Lewis | 8aef901 | 2019-10-29 14:15:17 | [diff] [blame] | 83 | 'background-color', |
| 84 | 'color', |
| 85 | 'fill', |
| 86 | 'border-top-width', |
| 87 | 'border-top-color', |
| 88 | 'border-bottom-width', |
| 89 | 'border-bottom-color', |
| 90 | 'border-left-width', |
| 91 | 'border-left-color', |
| 92 | 'border-right-width', |
| 93 | 'border-right-color', |
Paul Lewis | 7d10a73 | 2019-11-06 16:11:51 | [diff] [blame] | 94 | 'font-family', |
Paul Lewis | 8aef901 | 2019-10-29 14:15:17 | [diff] [blame] | 95 | 'font-size', |
| 96 | 'font-weight', |
Paul Lewis | 7d10a73 | 2019-11-06 16:11:51 | [diff] [blame] | 97 | 'line-height', |
Paul Lewis | 8aef901 | 2019-10-29 14:15:17 | [diff] [blame] | 98 | 'position', |
| 99 | 'top', |
| 100 | 'right', |
| 101 | 'bottom', |
| 102 | 'left', |
| 103 | 'display', |
| 104 | 'width', |
Paul Lewis | aae7b10 | 2019-10-30 14:18:21 | [diff] [blame] | 105 | 'height', |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 106 | 'vertical-align', |
| 107 | ], |
Alex Rudenko | 1c7ae0e | 2021-04-15 09:37:58 | [diff] [blame] | 108 | includeTextColorOpacities: true, |
| 109 | includeBlendedBackgroundColors: true, |
Paul Lewis | 12bc63e | 2019-10-23 10:53:29 | [diff] [blame] | 110 | }; |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 111 | |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 112 | const formatColor = (color: Common.Color.Color): string|null => { |
Alex Rudenko | d55e18c | 2020-09-23 11:37:06 | [diff] [blame] | 113 | return color.hasAlpha() ? color.asString(Common.Color.Format.HEXA) : color.asString(Common.Color.Format.HEX); |
| 114 | }; |
| 115 | |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 116 | const storeColor = (id: number, nodeId: number, target: Map<string, Set<number>>): Common.Color.Color|undefined => { |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 117 | if (id === -1) { |
| 118 | return; |
| 119 | } |
| 120 | |
| 121 | // Parse the color, discard transparent ones. |
| 122 | const colorText = strings[id]; |
Paul Lewis | be87fed | 2020-07-07 12:44:19 | [diff] [blame] | 123 | if (!colorText) { |
| 124 | return; |
| 125 | } |
| 126 | |
Tim van der Lippe | 7946bbc | 2020-02-13 13:58:42 | [diff] [blame] | 127 | const color = Common.Color.Color.parse(colorText); |
Paul Lewis | 4ab492e | 2019-10-28 10:28:54 | [diff] [blame] | 128 | if (!color || color.rgba()[3] === 0) { |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 129 | return; |
| 130 | } |
| 131 | |
| 132 | // Format the color and use as the key. |
Alex Rudenko | d55e18c | 2020-09-23 11:37:06 | [diff] [blame] | 133 | const colorFormatted = formatColor(color); |
Andres Olivares | 1e6d9c6 | 2020-09-03 22:47:38 | [diff] [blame] | 134 | if (!colorFormatted) { |
| 135 | return; |
| 136 | } |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 137 | |
| 138 | // Get the existing set of nodes with the color, or create a new set. |
| 139 | const colorValues = target.get(colorFormatted) || new Set(); |
| 140 | colorValues.add(nodeId); |
| 141 | |
| 142 | // Store. |
| 143 | target.set(colorFormatted, colorValues); |
Alex Rudenko | d55e18c | 2020-09-23 11:37:06 | [diff] [blame] | 144 | |
| 145 | return color; |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 146 | }; |
| 147 | |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 148 | const isSVGNode = (nodeName: string): boolean => { |
Paul Lewis | 69fb9b6 | 2019-10-29 16:22:33 | [diff] [blame] | 149 | const validNodes = new Set([ |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 150 | 'altglyph', |
| 151 | 'circle', |
| 152 | 'ellipse', |
| 153 | 'path', |
| 154 | 'polygon', |
| 155 | 'polyline', |
| 156 | 'rect', |
| 157 | 'svg', |
| 158 | 'text', |
| 159 | 'textpath', |
| 160 | 'tref', |
| 161 | 'tspan', |
Paul Lewis | 69fb9b6 | 2019-10-29 16:22:33 | [diff] [blame] | 162 | ]); |
| 163 | return validNodes.has(nodeName.toLowerCase()); |
| 164 | }; |
| 165 | |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 166 | const isReplacedContent = (nodeName: string): boolean => { |
Paul Lewis | 69fb9b6 | 2019-10-29 16:22:33 | [diff] [blame] | 167 | const validNodes = new Set(['iframe', 'video', 'embed', 'img']); |
| 168 | return validNodes.has(nodeName.toLowerCase()); |
Paul Lewis | 12bc63e | 2019-10-23 10:53:29 | [diff] [blame] | 169 | }; |
| 170 | |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 171 | const isTableElementWithDefaultStyles = (nodeName: string, display: string): boolean => { |
Paul Lewis | aae7b10 | 2019-10-30 14:18:21 | [diff] [blame] | 172 | const validNodes = new Set(['tr', 'td', 'thead', 'tbody']); |
| 173 | return validNodes.has(nodeName.toLowerCase()) && display.startsWith('table'); |
| 174 | }; |
| 175 | |
Paul Lewis | 8aef901 | 2019-10-29 14:15:17 | [diff] [blame] | 176 | let elementCount = 0; |
Andres Olivares | 1e6d9c6 | 2020-09-03 22:47:38 | [diff] [blame] | 177 | |
Tim van der Lippe | 1e4e83c | 2021-10-12 16:16:58 | [diff] [blame^] | 178 | const {documents, strings} = await this.#domSnapshotAgent.invoke_captureSnapshot(snapshotConfig); |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 179 | for (const {nodes, layout} of documents) { |
Paul Lewis | 8aef901 | 2019-10-29 14:15:17 | [diff] [blame] | 180 | // Track the number of elements in the documents. |
| 181 | elementCount += layout.nodeIndex.length; |
| 182 | |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 183 | for (let idx = 0; idx < layout.styles.length; idx++) { |
| 184 | const styles = layout.styles[idx]; |
| 185 | const nodeIdx = layout.nodeIndex[idx]; |
Andres Olivares | 1e6d9c6 | 2020-09-03 22:47:38 | [diff] [blame] | 186 | if (!nodes.backendNodeId || !nodes.nodeName) { |
| 187 | continue; |
| 188 | } |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 189 | const nodeId = nodes.backendNodeId[nodeIdx]; |
Paul Lewis | 12bc63e | 2019-10-23 10:53:29 | [diff] [blame] | 190 | const nodeName = nodes.nodeName[nodeIdx]; |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 191 | |
Paul Lewis | 7d10a73 | 2019-11-06 16:11:51 | [diff] [blame] | 192 | const [backgroundColorIdx, textColorIdx, fillIdx, borderTopWidthIdx, borderTopColorIdx, borderBottomWidthIdx, borderBottomColorIdx, borderLeftWidthIdx, borderLeftColorIdx, borderRightWidthIdx, borderRightColorIdx, fontFamilyIdx, fontSizeIdx, fontWeightIdx, lineHeightIdx, positionIdx, topIdx, rightIdx, bottomIdx, leftIdx, displayIdx, widthIdx, heightIdx, verticalAlignIdx] = |
Paul Lewis | aae7b10 | 2019-10-30 14:18:21 | [diff] [blame] | 193 | styles; |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 194 | |
Alex Rudenko | 1c7ae0e | 2021-04-15 09:37:58 | [diff] [blame] | 195 | storeColor(backgroundColorIdx, nodeId, backgroundColors); |
Alex Rudenko | d55e18c | 2020-09-23 11:37:06 | [diff] [blame] | 196 | const textColor = storeColor(textColorIdx, nodeId, textColors); |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 197 | |
Paul Lewis | 12bc63e | 2019-10-23 10:53:29 | [diff] [blame] | 198 | if (isSVGNode(strings[nodeName])) { |
| 199 | storeColor(fillIdx, nodeId, fillColors); |
| 200 | } |
| 201 | |
| 202 | if (strings[borderTopWidthIdx] !== '0px') { |
| 203 | storeColor(borderTopColorIdx, nodeId, borderColors); |
| 204 | } |
| 205 | |
| 206 | if (strings[borderBottomWidthIdx] !== '0px') { |
| 207 | storeColor(borderBottomColorIdx, nodeId, borderColors); |
| 208 | } |
| 209 | |
| 210 | if (strings[borderLeftWidthIdx] !== '0px') { |
| 211 | storeColor(borderLeftColorIdx, nodeId, borderColors); |
| 212 | } |
| 213 | |
| 214 | if (strings[borderRightWidthIdx] !== '0px') { |
| 215 | storeColor(borderRightColorIdx, nodeId, borderColors); |
| 216 | } |
| 217 | |
Paul Lewis | 7d10a73 | 2019-11-06 16:11:51 | [diff] [blame] | 218 | /** |
| 219 | * Create a structure like this for font info: |
| 220 | * |
| 221 | * / size (Map) -- nodes (Array) |
| 222 | * / |
| 223 | * Font family (Map) ----- weight (Map) -- nodes (Array) |
| 224 | * \ |
| 225 | * \ line-height (Map) -- nodes (Array) |
| 226 | */ |
Paul Lewis | be87fed | 2020-07-07 12:44:19 | [diff] [blame] | 227 | if (fontFamilyIdx && fontFamilyIdx !== -1) { |
Paul Lewis | 7d10a73 | 2019-11-06 16:11:51 | [diff] [blame] | 228 | const fontFamily = strings[fontFamilyIdx]; |
| 229 | const fontFamilyInfo = fontInfo.get(fontFamily) || new Map(); |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 230 | |
Paul Lewis | 7d10a73 | 2019-11-06 16:11:51 | [diff] [blame] | 231 | const sizeLabel = 'font-size'; |
| 232 | const weightLabel = 'font-weight'; |
| 233 | const lineHeightLabel = 'line-height'; |
| 234 | |
| 235 | const size = fontFamilyInfo.get(sizeLabel) || new Map(); |
| 236 | const weight = fontFamilyInfo.get(weightLabel) || new Map(); |
| 237 | const lineHeight = fontFamilyInfo.get(lineHeightLabel) || new Map(); |
| 238 | |
| 239 | if (fontSizeIdx !== -1) { |
| 240 | const fontSizeValue = strings[fontSizeIdx]; |
| 241 | const nodes = size.get(fontSizeValue) || []; |
| 242 | nodes.push(nodeId); |
| 243 | size.set(fontSizeValue, nodes); |
| 244 | } |
| 245 | |
| 246 | if (fontWeightIdx !== -1) { |
| 247 | const fontWeightValue = strings[fontWeightIdx]; |
| 248 | const nodes = weight.get(fontWeightValue) || []; |
| 249 | nodes.push(nodeId); |
| 250 | weight.set(fontWeightValue, nodes); |
| 251 | } |
| 252 | |
| 253 | if (lineHeightIdx !== -1) { |
| 254 | const lineHeightValue = strings[lineHeightIdx]; |
| 255 | const nodes = lineHeight.get(lineHeightValue) || []; |
| 256 | nodes.push(nodeId); |
| 257 | lineHeight.set(lineHeightValue, nodes); |
| 258 | } |
| 259 | |
| 260 | // Set the data back. |
| 261 | fontFamilyInfo.set(sizeLabel, size); |
| 262 | fontFamilyInfo.set(weightLabel, weight); |
| 263 | fontFamilyInfo.set(lineHeightLabel, lineHeight); |
| 264 | fontInfo.set(fontFamily, fontFamilyInfo); |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 265 | } |
Paul Lewis | 8aef901 | 2019-10-29 14:15:17 | [diff] [blame] | 266 | |
Alex Rudenko | 1c7ae0e | 2021-04-15 09:37:58 | [diff] [blame] | 267 | const blendedBackgroundColor = |
| 268 | textColor && layout.blendedBackgroundColors && layout.blendedBackgroundColors[idx] !== -1 ? |
| 269 | Common.Color.Color.parse(strings[layout.blendedBackgroundColors[idx]]) : |
| 270 | null; |
| 271 | if (textColor && blendedBackgroundColor) { |
Alex Rudenko | d55e18c | 2020-09-23 11:37:06 | [diff] [blame] | 272 | const contrastInfo = new ColorPicker.ContrastInfo.ContrastInfo({ |
Alex Rudenko | 1c7ae0e | 2021-04-15 09:37:58 | [diff] [blame] | 273 | backgroundColors: [blendedBackgroundColor.asString(Common.Color.Format.HEXA) as string], |
Alex Rudenko | d55e18c | 2020-09-23 11:37:06 | [diff] [blame] | 274 | computedFontSize: fontSizeIdx !== -1 ? strings[fontSizeIdx] : '', |
| 275 | computedFontWeight: fontWeightIdx !== -1 ? strings[fontWeightIdx] : '', |
| 276 | }); |
Alex Rudenko | 1c7ae0e | 2021-04-15 09:37:58 | [diff] [blame] | 277 | const blendedTextColor = |
| 278 | textColor.blendWithAlpha(layout.textColorOpacities ? layout.textColorOpacities[idx] : 1); |
| 279 | contrastInfo.setColor(blendedTextColor); |
| 280 | const formattedTextColor = formatColor(blendedTextColor); |
| 281 | const formattedBackgroundColor = formatColor(blendedBackgroundColor); |
Alex Rudenko | 69c377e | 2020-11-27 07:40:35 | [diff] [blame] | 282 | const key = `${formattedTextColor}_${formattedBackgroundColor}`; |
| 283 | if (Root.Runtime.experiments.isEnabled('APCA')) { |
| 284 | const contrastRatio = contrastInfo.contrastRatioAPCA(); |
| 285 | const threshold = contrastInfo.contrastRatioAPCAThreshold(); |
| 286 | const passes = contrastRatio && threshold ? Math.abs(contrastRatio) >= threshold : false; |
| 287 | if (!passes) { |
| 288 | const issue = { |
| 289 | nodeId, |
| 290 | contrastRatio, |
Alex Rudenko | 1c7ae0e | 2021-04-15 09:37:58 | [diff] [blame] | 291 | textColor: blendedTextColor, |
| 292 | backgroundColor: blendedBackgroundColor, |
Alex Rudenko | 69c377e | 2020-11-27 07:40:35 | [diff] [blame] | 293 | thresholdsViolated: { |
| 294 | aa: false, |
| 295 | aaa: false, |
| 296 | apca: true, |
| 297 | }, |
| 298 | }; |
| 299 | if (textColorContrastIssues.has(key)) { |
| 300 | textColorContrastIssues.get(key).push(issue); |
| 301 | } else { |
| 302 | textColorContrastIssues.set(key, [issue]); |
| 303 | } |
| 304 | } |
| 305 | } else { |
| 306 | const aaThreshold = contrastInfo.contrastRatioThreshold('aa') || 0; |
| 307 | const aaaThreshold = contrastInfo.contrastRatioThreshold('aaa') || 0; |
| 308 | const contrastRatio = contrastInfo.contrastRatio() || 0; |
| 309 | if (aaThreshold > contrastRatio || aaaThreshold > contrastRatio) { |
| 310 | const issue = { |
| 311 | nodeId, |
| 312 | contrastRatio, |
Alex Rudenko | 1c7ae0e | 2021-04-15 09:37:58 | [diff] [blame] | 313 | textColor: blendedTextColor, |
| 314 | backgroundColor: blendedBackgroundColor, |
Alex Rudenko | 69c377e | 2020-11-27 07:40:35 | [diff] [blame] | 315 | thresholdsViolated: { |
| 316 | aa: aaThreshold > contrastRatio, |
| 317 | aaa: aaaThreshold > contrastRatio, |
| 318 | apca: false, |
| 319 | }, |
| 320 | }; |
| 321 | if (textColorContrastIssues.has(key)) { |
| 322 | textColorContrastIssues.get(key).push(issue); |
| 323 | } else { |
| 324 | textColorContrastIssues.set(key, [issue]); |
| 325 | } |
Alex Rudenko | d55e18c | 2020-09-23 11:37:06 | [diff] [blame] | 326 | } |
| 327 | } |
| 328 | } |
| 329 | |
Tim van der Lippe | 9d0cb5f | 2020-01-09 14:10:38 | [diff] [blame] | 330 | CSSOverviewUnusedDeclarations.checkForUnusedPositionValues( |
Paul Lewis | ed808ce | 2019-11-06 14:21:54 | [diff] [blame] | 331 | unusedDeclarations, nodeId, strings, positionIdx, topIdx, leftIdx, rightIdx, bottomIdx); |
Paul Lewis | bcaccf1 | 2019-10-29 14:54:59 | [diff] [blame] | 332 | |
Paul Lewis | 69fb9b6 | 2019-10-29 16:22:33 | [diff] [blame] | 333 | // Ignore SVG elements as, despite being inline by default, they can have width & height specified. |
| 334 | // Also ignore replaced content, for similar reasons. |
| 335 | if (!isSVGNode(strings[nodeName]) && !isReplacedContent(strings[nodeName])) { |
Tim van der Lippe | 9d0cb5f | 2020-01-09 14:10:38 | [diff] [blame] | 336 | CSSOverviewUnusedDeclarations.checkForUnusedWidthAndHeightValues( |
Paul Lewis | ed808ce | 2019-11-06 14:21:54 | [diff] [blame] | 337 | unusedDeclarations, nodeId, strings, displayIdx, widthIdx, heightIdx); |
Paul Lewis | bcaccf1 | 2019-10-29 14:54:59 | [diff] [blame] | 338 | } |
Paul Lewis | aae7b10 | 2019-10-30 14:18:21 | [diff] [blame] | 339 | |
| 340 | if (verticalAlignIdx !== -1 && !isTableElementWithDefaultStyles(strings[nodeName], strings[displayIdx])) { |
Tim van der Lippe | 9d0cb5f | 2020-01-09 14:10:38 | [diff] [blame] | 341 | CSSOverviewUnusedDeclarations.checkForInvalidVerticalAlignment( |
Paul Lewis | ed808ce | 2019-11-06 14:21:54 | [diff] [blame] | 342 | unusedDeclarations, nodeId, strings, displayIdx, verticalAlignIdx); |
Paul Lewis | aae7b10 | 2019-10-30 14:18:21 | [diff] [blame] | 343 | } |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 344 | } |
| 345 | } |
| 346 | |
Alex Rudenko | d55e18c | 2020-09-23 11:37:06 | [diff] [blame] | 347 | return { |
| 348 | backgroundColors, |
| 349 | textColors, |
| 350 | textColorContrastIssues, |
| 351 | fillColors, |
| 352 | borderColors, |
| 353 | fontInfo, |
| 354 | unusedDeclarations, |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 355 | elementCount, |
Alex Rudenko | d55e18c | 2020-09-23 11:37:06 | [diff] [blame] | 356 | }; |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 357 | } |
| 358 | |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 359 | getComputedStyleForNode(nodeId: Protocol.DOM.NodeId): Promise<Protocol.CSS.GetComputedStyleForNodeResponse> { |
Tim van der Lippe | 1e4e83c | 2021-10-12 16:16:58 | [diff] [blame^] | 360 | return this.#cssAgent.invoke_getComputedStyleForNode({nodeId}); |
Paul Lewis | 8cddf993 | 2019-09-27 16:40:07 | [diff] [blame] | 361 | } |
| 362 | |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 363 | async getMediaQueries(): Promise<Map<string, Protocol.CSS.CSSMedia[]>> { |
Tim van der Lippe | 1e4e83c | 2021-10-12 16:16:58 | [diff] [blame^] | 364 | const queries = await this.#cssAgent.invoke_getMediaQueries(); |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 365 | const queryMap = new Map<string, Protocol.CSS.CSSMedia[]>(); |
Paul Lewis | ed808ce | 2019-11-06 14:21:54 | [diff] [blame] | 366 | |
| 367 | if (!queries) { |
| 368 | return queryMap; |
| 369 | } |
| 370 | |
Andres Olivares | 1e6d9c6 | 2020-09-03 22:47:38 | [diff] [blame] | 371 | for (const query of queries.medias) { |
Paul Lewis | ed808ce | 2019-11-06 14:21:54 | [diff] [blame] | 372 | // Ignore media queries applied to stylesheets; instead only use declared media rules. |
| 373 | if (query.source === 'linkedSheet') { |
| 374 | continue; |
| 375 | } |
| 376 | |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 377 | const entries = queryMap.get(query.text) || ([] as Protocol.CSS.CSSMedia[]); |
Paul Lewis | ed808ce | 2019-11-06 14:21:54 | [diff] [blame] | 378 | entries.push(query); |
| 379 | queryMap.set(query.text, entries); |
| 380 | } |
| 381 | |
| 382 | return queryMap; |
Paul Lewis | ebc4719 | 2019-10-09 15:06:41 | [diff] [blame] | 383 | } |
| 384 | |
Jan Scheffler | 830467c | 2021-02-25 13:44:47 | [diff] [blame] | 385 | async getGlobalStylesheetStats(): Promise<GlobalStyleStats|void> { |
Paul Lewis | 8cddf993 | 2019-09-27 16:40:07 | [diff] [blame] | 386 | // There are no ways to pull CSSOM values directly today, due to its unserializable format, |
| 387 | // so instead we execute some JS within the page that extracts the relevant data and send that instead. |
| 388 | const expression = `(function() { |
| 389 | let styleRules = 0; |
Paul Lewis | 8cddf993 | 2019-09-27 16:40:07 | [diff] [blame] | 390 | let inlineStyles = 0; |
| 391 | let externalSheets = 0; |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 392 | const stats = { |
| 393 | // Simple. |
| 394 | type: new Set(), |
| 395 | class: new Set(), |
| 396 | id: new Set(), |
| 397 | universal: new Set(), |
| 398 | attribute: new Set(), |
| 399 | |
| 400 | // Non-simple. |
| 401 | nonSimple: new Set() |
| 402 | }; |
| 403 | |
| 404 | for (const styleSheet of document.styleSheets) { |
| 405 | if (styleSheet.href) { |
Paul Lewis | 8cddf993 | 2019-09-27 16:40:07 | [diff] [blame] | 406 | externalSheets++; |
| 407 | } else { |
| 408 | inlineStyles++; |
| 409 | } |
| 410 | |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 411 | // Attempting to grab rules can trigger a DOMException. |
| 412 | // Try it and if it fails skip to the next stylesheet. |
| 413 | let rules; |
| 414 | try { |
| 415 | rules = styleSheet.rules; |
| 416 | } catch (err) { |
| 417 | continue; |
| 418 | } |
| 419 | |
Paul Lewis | 8cddf993 | 2019-09-27 16:40:07 | [diff] [blame] | 420 | for (const rule of rules) { |
| 421 | if ('selectorText' in rule) { |
| 422 | styleRules++; |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 423 | |
| 424 | // Each group that was used. |
| 425 | for (const selectorGroup of rule.selectorText.split(',')) { |
| 426 | // Each selector in the group. |
| 427 | for (const selector of selectorGroup.split(\/[\\t\\n\\f\\r ]+\/g)) { |
| 428 | if (selector.startsWith('.')) { |
| 429 | // Class. |
| 430 | stats.class.add(selector); |
| 431 | } else if (selector.startsWith('#')) { |
| 432 | // Id. |
| 433 | stats.id.add(selector); |
| 434 | } else if (selector.startsWith('*')) { |
| 435 | // Universal. |
| 436 | stats.universal.add(selector); |
| 437 | } else if (selector.startsWith('[')) { |
| 438 | // Attribute. |
| 439 | stats.attribute.add(selector); |
| 440 | } else { |
| 441 | // Type or non-simple selector. |
| 442 | const specialChars = \/[#\.:\\[\\]|\\+>~]\/; |
| 443 | if (specialChars.test(selector)) { |
| 444 | stats.nonSimple.add(selector); |
| 445 | } else { |
| 446 | stats.type.add(selector); |
| 447 | } |
| 448 | } |
| 449 | } |
| 450 | } |
Paul Lewis | 8cddf993 | 2019-09-27 16:40:07 | [diff] [blame] | 451 | } |
| 452 | } |
| 453 | } |
| 454 | |
| 455 | return { |
| 456 | styleRules, |
Paul Lewis | 8cddf993 | 2019-09-27 16:40:07 | [diff] [blame] | 457 | inlineStyles, |
Paul Lewis | de0224c | 2019-10-22 16:23:15 | [diff] [blame] | 458 | externalSheets, |
| 459 | stats: { |
| 460 | // Simple. |
| 461 | type: stats.type.size, |
| 462 | class: stats.class.size, |
| 463 | id: stats.id.size, |
| 464 | universal: stats.universal.size, |
| 465 | attribute: stats.attribute.size, |
| 466 | |
| 467 | // Non-simple. |
| 468 | nonSimple: stats.nonSimple.size |
| 469 | } |
Paul Lewis | 8cddf993 | 2019-09-27 16:40:07 | [diff] [blame] | 470 | } |
| 471 | })()`; |
Tim van der Lippe | 1e4e83c | 2021-10-12 16:16:58 | [diff] [blame^] | 472 | const {result} = await this.#runtimeAgent.invoke_evaluate({expression, returnByValue: true}); |
Paul Lewis | 8cddf993 | 2019-09-27 16:40:07 | [diff] [blame] | 473 | |
| 474 | // TODO(paullewis): Handle errors properly. |
Tim van der Lippe | 1d6e57a | 2019-09-30 11:55:34 | [diff] [blame] | 475 | if (result.type !== 'object') { |
Paul Lewis | 8cddf993 | 2019-09-27 16:40:07 | [diff] [blame] | 476 | return; |
Tim van der Lippe | 1d6e57a | 2019-09-30 11:55:34 | [diff] [blame] | 477 | } |
Paul Lewis | 8cddf993 | 2019-09-27 16:40:07 | [diff] [blame] | 478 | |
| 479 | return result.value; |
| 480 | } |
Paul Lewis | 4da3b30 | 2019-11-21 14:23:47 | [diff] [blame] | 481 | } |
Paul Lewis | 8cddf993 | 2019-09-27 16:40:07 | [diff] [blame] | 482 | |
Sigurd Schneider | 79e812e | 2021-06-04 06:48:23 | [diff] [blame] | 483 | SDK.SDKModel.SDKModel.register(CSSOverviewModel, {capabilities: SDK.Target.Capability.DOM, autostart: false}); |