diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/css/css-get-background-colors-expected.txt b/third_party/WebKit/LayoutTests/inspector-protocol/css/css-get-background-colors-expected.txt index ae710eda5efc87..1da7010fab53d0 100644 --- a/third_party/WebKit/LayoutTests/inspector-protocol/css/css-get-background-colors-expected.txt +++ b/third_party/WebKit/LayoutTests/inspector-protocol/css/css-get-background-colors-expected.txt @@ -6,101 +6,101 @@ No text: should be null Running test: testNoBgColor No background color: should be white -{"backgroundColors":["rgb(255, 255, 255)"]} +{"backgroundColors":["rgb(255, 255, 255)"],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testOpaqueBgColor Opaque background color: should be red -{"backgroundColors":["rgb(255, 0, 0)"]} +{"backgroundColors":["rgb(255, 0, 0)"],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testLayeredOpaqueBgColors Opaque background color in front of another opaque background color: should be blue -{"backgroundColors":["rgb(0, 0, 255)"]} +{"backgroundColors":["rgb(0, 0, 255)"],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testOneSemitransparentBgColor Semi-transparent background color: should be light red -{"backgroundColors":["rgb(255, 127, 127)"]} +{"backgroundColors":["rgb(255, 127, 127)"],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testTwoSemitransparentBgColors Two layered semi-transparent background colors: should be medium red -{"backgroundColors":["rgb(255, 63, 63)"]} +{"backgroundColors":["rgb(255, 63, 63)"],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testOpaqueGradientBackground Opaque gradient: should be red and black -{"backgroundColors":["rgb(255, 0, 0)","rgb(0, 0, 0)"]} +{"backgroundColors":["rgb(255, 0, 0)","rgb(0, 0, 0)"],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testOpaqueGradientBackgroundBehindScrim Opaque gradient behind semi-transparent color: should be light red and 50% grey -{"backgroundColors":["rgb(255, 128, 128)","rgb(128, 128, 128)"]} +{"backgroundColors":["rgb(255, 128, 128)","rgb(128, 128, 128)"],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testOpaqueGradientBackgroundWithColorBackground Opaque gradient and solid color background on same element: should be red and black -{"backgroundColors":["rgb(255, 0, 0)","rgb(0, 0, 0)"]} +{"backgroundColors":["rgb(255, 0, 0)","rgb(0, 0, 0)"],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testPartiallyTransparentGradientBackground Semi-transparent gradient: should be light red and 50% grey -{"backgroundColors":["rgb(255, 127, 127)","rgb(127, 127, 127)"]} +{"backgroundColors":["rgb(255, 127, 127)","rgb(127, 127, 127)"],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testPartiallyTransparentGradientAndColorBackground Semi-transparent gradient and solid color on same element: should be dark red and 50% grey -{"backgroundColors":["rgb(128, 0, 0)","rgb(128, 128, 128)"]} +{"backgroundColors":["rgb(128, 0, 0)","rgb(128, 128, 128)"],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testTwoPartiallyTransparentGradientBackgrounds Layered semi-transparent gradients: should be empty array -{"backgroundColors":[]} +{"backgroundColors":[],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testPartiallyOverlappingBackground Partially overlapping background: should be empty array -{"backgroundColors":[]} +{"backgroundColors":[],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: smallerBackground Background smaller than text: should be empty array -{"backgroundColors":[]} +{"backgroundColors":[],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testObscuredPartiallyOverlappingBackground Red background obscuring partially overlapping blue background: should be red only -{"backgroundColors":["rgb(255, 0, 0)"]} +{"backgroundColors":["rgb(255, 0, 0)"],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testBackgroundImage Background image: should be empty array -{"backgroundColors":[]} +{"backgroundColors":[],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testBackgroundImageAndBgColor Background image with background color: should be empty array -{"backgroundColors":[]} +{"backgroundColors":[],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testBackgroundImageBehindScrim Background image behind scrim: should be semi-transparent white -{"backgroundColors":["rgba(255, 255, 255, 0.5)"]} +{"backgroundColors":["rgba(255, 255, 255, 0.5)"],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testForegroundImage Image behind text: should be empty array -{"backgroundColors":[]} +{"backgroundColors":[],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testForegroundImageBehindScrim Image behind scrim: should be semi-transparent white -{"backgroundColors":["rgba(255, 255, 255, 0.5)"]} +{"backgroundColors":["rgba(255, 255, 255, 0.5)"],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testForegroundCanvas Canvas behind text: should be empty array -{"backgroundColors":[]} +{"backgroundColors":[],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testForegroundEmbed Embed behind text: should be empty array -{"backgroundColors":[]} +{"backgroundColors":[],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testForegroundObject Object behind text: should be empty array -{"backgroundColors":[]} +{"backgroundColors":[],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testForegroundPicture Picture behind text: should be empty array -{"backgroundColors":[]} +{"backgroundColors":[],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testForegroundSVG SVG behind text: should be empty array -{"backgroundColors":[]} +{"backgroundColors":[],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} Running test: testForegroundVideo Video behind text: should be empty array -{"backgroundColors":[]} +{"backgroundColors":[],"computedFontSize":"16px","computedFontWeight":"400","computedBodyFontSize":"16px"} diff --git a/third_party/WebKit/Source/core/inspector/InspectorCSSAgent.cpp b/third_party/WebKit/Source/core/inspector/InspectorCSSAgent.cpp index 3a54c71a50d15d..97ba0a308fdd43 100644 --- a/third_party/WebKit/Source/core/inspector/InspectorCSSAgent.cpp +++ b/third_party/WebKit/Source/core/inspector/InspectorCSSAgent.cpp @@ -2228,7 +2228,10 @@ Response InspectorCSSAgent::setEffectivePropertyValueForNode( Response InspectorCSSAgent::getBackgroundColors( int node_id, - Maybe>* result) { + Maybe>* background_colors, + Maybe* computed_font_size, + Maybe* computed_font_weight, + Maybe* computed_body_font_size) { Element* element = nullptr; Response response = dom_agent_->AssertElement(node_id, element); if (!response.isSuccess()) @@ -2274,9 +2277,28 @@ Response InspectorCSSAgent::getBackgroundColors( } } - *result = protocol::Array::create(); - for (auto color : colors) - result->fromJust()->addItem(color.SerializedAsCSSComponentValue()); + *background_colors = protocol::Array::create(); + for (auto color : colors) { + background_colors->fromJust()->addItem( + color.SerializedAsCSSComponentValue()); + } + + CSSComputedStyleDeclaration* computed_style_info = + CSSComputedStyleDeclaration::Create(element, true); + const CSSValue* font_size = + computed_style_info->GetPropertyCSSValue(CSSPropertyFontSize); + *computed_font_size = font_size->CssText(); + const CSSValue* font_weight = + computed_style_info->GetPropertyCSSValue(CSSPropertyFontWeight); + *computed_font_weight = font_weight->CssText(); + + HTMLElement* body = element->GetDocument().body(); + CSSComputedStyleDeclaration* computed_style_body = + CSSComputedStyleDeclaration::Create(body, true); + const CSSValue* body_font_size = + computed_style_body->GetPropertyCSSValue(CSSPropertyFontSize); + *computed_body_font_size = body_font_size->CssText(); + return Response::OK(); } diff --git a/third_party/WebKit/Source/core/inspector/InspectorCSSAgent.h b/third_party/WebKit/Source/core/inspector/InspectorCSSAgent.h index 8639c4e72f76a8..0f4af8c744489d 100644 --- a/third_party/WebKit/Source/core/inspector/InspectorCSSAgent.h +++ b/third_party/WebKit/Source/core/inspector/InspectorCSSAgent.h @@ -191,7 +191,10 @@ class CORE_EXPORT InspectorCSSAgent final const String& value) override; protocol::Response getBackgroundColors( int node_id, - protocol::Maybe>* background_colors) override; + protocol::Maybe>* background_colors, + protocol::Maybe* computed_font_size, + protocol::Maybe* computed_font_weight, + protocol::Maybe* computed_body_font_size) override; protocol::Response startRuleUsageTracking() override; protocol::Response takeCoverageDelta( diff --git a/third_party/WebKit/Source/core/inspector/browser_protocol.json b/third_party/WebKit/Source/core/inspector/browser_protocol.json index 154fc77d9f2028..0e7a54e1b16ea2 100644 --- a/third_party/WebKit/Source/core/inspector/browser_protocol.json +++ b/third_party/WebKit/Source/core/inspector/browser_protocol.json @@ -3326,7 +3326,11 @@ { "name": "nodeId", "$ref": "DOM.NodeId", "description": "Id of the node to get background colors for." } ], "returns": [ - { "name": "backgroundColors", "type": "array", "items": { "type": "string" }, "description": "The range of background colors behind this element, if it contains any visible text. If no visible text is present, this will be undefined. In the case of a flat background color, this will consist of simply that color. In the case of a gradient, this will consist of each of the color stops. For anything more complicated, this will be an empty array. Images will be ignored (as if the image had failed to load).", "optional": true } + { "name": "backgroundColors", "type": "array", "items": { "type": "string" }, "description": "The range of background colors behind this element, if it contains any visible text. If no visible text is present, this will be undefined. In the case of a flat background color, this will consist of simply that color. In the case of a gradient, this will consist of each of the color stops. For anything more complicated, this will be an empty array. Images will be ignored (as if the image had failed to load).", "optional": true }, + { "name": "computedFontSize", "type": "string", "description": "The computed font size for this node, as a CSS computed value string (e.g. '12px').", "optional": true }, + { "name": "computedFontWeight", "type": "string", "description": "The computed font weight for this node, as a CSS computed value string (e.g. 'normal' or '100').", "optional": true }, + + { "name": "computedBodyFontSize", "type": "string", "description": "The computed font size for the document body, as a computed CSS value string (e.g. '16px').", "optional": true } ], "experimental": true }, diff --git a/third_party/WebKit/Source/devtools/BUILD.gn b/third_party/WebKit/Source/devtools/BUILD.gn index 67f6f69bfce384..0ffc52c7935782 100644 --- a/third_party/WebKit/Source/devtools/BUILD.gn +++ b/third_party/WebKit/Source/devtools/BUILD.gn @@ -104,6 +104,7 @@ all_devtools_files = [ "front_end/cm_web_modes/htmlmixed.js", "front_end/cm_web_modes/javascript.js", "front_end/cm_web_modes/xml.js", + "front_end/color_picker/Contrast.js", "front_end/color_picker/module.json", "front_end/color_picker/spectrum.css", "front_end/color_picker/Spectrum.js", diff --git a/third_party/WebKit/Source/devtools/front_end/Images/mediumIcons.png b/third_party/WebKit/Source/devtools/front_end/Images/mediumIcons.png index dce7e5ce9657ad..5cd9f460bf4515 100644 Binary files a/third_party/WebKit/Source/devtools/front_end/Images/mediumIcons.png and b/third_party/WebKit/Source/devtools/front_end/Images/mediumIcons.png differ diff --git a/third_party/WebKit/Source/devtools/front_end/Images/mediumIcons_2x.png b/third_party/WebKit/Source/devtools/front_end/Images/mediumIcons_2x.png index b39d28ba4e29f1..730c3ea87bea16 100644 Binary files a/third_party/WebKit/Source/devtools/front_end/Images/mediumIcons_2x.png and b/third_party/WebKit/Source/devtools/front_end/Images/mediumIcons_2x.png differ diff --git a/third_party/WebKit/Source/devtools/front_end/Images/smallIcons.png b/third_party/WebKit/Source/devtools/front_end/Images/smallIcons.png index 04e76802645f9b..c13b06d6e36970 100644 Binary files a/third_party/WebKit/Source/devtools/front_end/Images/smallIcons.png and b/third_party/WebKit/Source/devtools/front_end/Images/smallIcons.png differ diff --git a/third_party/WebKit/Source/devtools/front_end/Images/smallIcons_2x.png b/third_party/WebKit/Source/devtools/front_end/Images/smallIcons_2x.png index 731ba43df06b1c..7cf24d7b87235a 100644 Binary files a/third_party/WebKit/Source/devtools/front_end/Images/smallIcons_2x.png and b/third_party/WebKit/Source/devtools/front_end/Images/smallIcons_2x.png differ diff --git a/third_party/WebKit/Source/devtools/front_end/Images/src/mediumIcons.svg b/third_party/WebKit/Source/devtools/front_end/Images/src/mediumIcons.svg index 92237b99edddab..682d2145bb999b 100644 --- a/third_party/WebKit/Source/devtools/front_end/Images/src/mediumIcons.svg +++ b/third_party/WebKit/Source/devtools/front_end/Images/src/mediumIcons.svg @@ -1,91 +1,440 @@ - - + + - + image/svg+xml - + - - - + + + - - + + - - + + - - + + - - - - - - + + + + + + - - + + - - + + - - + + - - - - - + + + + + + + + - - + + + + - - + + + + - - + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + - - + 1 + 2 + 3 + 4 + a + b + c + d + + + - 1 - 2 - 3 - 4 - a - b - c - d - diff --git a/third_party/WebKit/Source/devtools/front_end/Images/src/optimize_png.hashes b/third_party/WebKit/Source/devtools/front_end/Images/src/optimize_png.hashes index c91f6e8e5c7258..656c2c3fff07a4 100644 --- a/third_party/WebKit/Source/devtools/front_end/Images/src/optimize_png.hashes +++ b/third_party/WebKit/Source/devtools/front_end/Images/src/optimize_png.hashes @@ -4,8 +4,8 @@ "breakpointConditional.svg": "4cf90210b2af2ed84db2f60b07bcde28", "checkboxCheckmark.svg": "f039bf85cee42ad5c30ca3bfdce7912a", "errorWave.svg": "e183fa242a22ed4784a92f6becbc2c45", - "smallIcons.svg": "a0d37d8680ac0f0fbfa6ab053de90b04", - "mediumIcons.svg": "e63ac6385b0a6efb783d9838517e5e44", + "smallIcons.svg": "911ffe3e3692229022105c5a51d20bb9", + "mediumIcons.svg": "84090b8a5c439d6d3022f5fbc4dd8221", "breakpoint.svg": "69cd92d807259c022791112809b97799", "treeoutlineTriangles.svg": "017d2f89437df0afc6b9cd5ff43735d9", "chevrons.svg": "79b4b527771e30b6388ce664077b3409" diff --git a/third_party/WebKit/Source/devtools/front_end/Images/src/smallIcons.svg b/third_party/WebKit/Source/devtools/front_end/Images/src/smallIcons.svg index dabd05aac7b5d6..b784b81ce22d3c 100644 --- a/third_party/WebKit/Source/devtools/front_end/Images/src/smallIcons.svg +++ b/third_party/WebKit/Source/devtools/front_end/Images/src/smallIcons.svg @@ -36,17 +36,17 @@ guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" - inkscape:window-width="938" - inkscape:window-height="826" + inkscape:window-width="1272" + inkscape:window-height="1376" id="namedview4455" showgrid="true" - inkscape:zoom="3.7083823" - inkscape:cx="22.472364" - inkscape:cy="66.155904" - inkscape:window-x="1280" + inkscape:zoom="20.977778" + inkscape:cx="54.131178" + inkscape:cy="94.103295" + inkscape:window-x="980" inkscape:window-y="0" inkscape:window-maximized="0" - inkscape:current-layer="svg4185"> + inkscape:current-layer="g74"> 6 + + + + + + + + diff --git a/third_party/WebKit/Source/devtools/front_end/Images/src/svg2png.hashes b/third_party/WebKit/Source/devtools/front_end/Images/src/svg2png.hashes index c91f6e8e5c7258..656c2c3fff07a4 100644 --- a/third_party/WebKit/Source/devtools/front_end/Images/src/svg2png.hashes +++ b/third_party/WebKit/Source/devtools/front_end/Images/src/svg2png.hashes @@ -4,8 +4,8 @@ "breakpointConditional.svg": "4cf90210b2af2ed84db2f60b07bcde28", "checkboxCheckmark.svg": "f039bf85cee42ad5c30ca3bfdce7912a", "errorWave.svg": "e183fa242a22ed4784a92f6becbc2c45", - "smallIcons.svg": "a0d37d8680ac0f0fbfa6ab053de90b04", - "mediumIcons.svg": "e63ac6385b0a6efb783d9838517e5e44", + "smallIcons.svg": "911ffe3e3692229022105c5a51d20bb9", + "mediumIcons.svg": "84090b8a5c439d6d3022f5fbc4dd8221", "breakpoint.svg": "69cd92d807259c022791112809b97799", "treeoutlineTriangles.svg": "017d2f89437df0afc6b9cd5ff43735d9", "chevrons.svg": "79b4b527771e30b6388ce664077b3409" diff --git a/third_party/WebKit/Source/devtools/front_end/color_picker/Contrast.js b/third_party/WebKit/Source/devtools/front_end/color_picker/Contrast.js new file mode 100644 index 00000000000000..ea0e71cb646e79 --- /dev/null +++ b/third_party/WebKit/Source/devtools/front_end/color_picker/Contrast.js @@ -0,0 +1,555 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +ColorPicker.ContrastInfo = class { + constructor() { + /** @type {?Array} */ + this._hsva = null; + + /** @type {?Common.Color} */ + this._bgColor = null; + + /** @type {?number} */ + this._contrastRatio = null; + + /** @type {?Object} */ + this._contrastRatioThresholds = null; + + /** @type {string} */ + this._colorString = ''; + } + + /** + * @param {?SDK.CSSModel.ContrastInfo} contrastInfo + */ + setContrastInfo(contrastInfo) { + this._contrastRatio = null; + this._contrastRatioThresholds = null; + this._bgColor = null; + + // TODO(aboxhall): distinguish between !backgroundColors (no text) and + // !backgroundColors.length (no computed bg color) + if (!contrastInfo.backgroundColors || !contrastInfo.backgroundColors.length) + return; + + if (contrastInfo.computedFontSize && contrastInfo.computedFontWeight && contrastInfo.computedBodyFontSize) { + var isLargeFont = ColorPicker.ContrastInfo.computeIsLargeFont( + contrastInfo.computedFontSize, contrastInfo.computedFontWeight, contrastInfo.computedBodyFontSize); + + this._contrastRatioThresholds = + ColorPicker.ContrastInfo._ContrastThresholds[(isLargeFont ? 'largeFont' : 'normalFont')]; + } + + // TODO(aboxhall): figure out what to do in the case of multiple background colors (i.e. gradients) + var bgColorText = contrastInfo.backgroundColors[0]; + var bgColor = Common.Color.parse(bgColorText); + if (!bgColor) + return; + + this.setBgColor(bgColor); + } + + /** + * @param {!Array} hsva + * @param {string} colorString + */ + setColor(hsva, colorString) { + this._hsva = hsva; + this._colorString = colorString; + this._updateContrastRatio(); + } + + /** + * @return {string} + */ + colorString() { + return this._colorString; + } + + /** + * @return {?Array} + */ + hsva() { + return this._hsva; + } + + /** + * @param {!Common.Color} bgColor + */ + setBgColor(bgColor) { + this._bgColor = bgColor; + + if (!this._hsva) + return; + + var fgRGBA = []; + Common.Color.hsva2rgba(this._hsva, fgRGBA); + + // If we have a semi-transparent background color over an unknown + // background, draw the line for the "worst case" scenario: where + // the unknown background is the same color as the text. + if (bgColor.hasAlpha) { + var blendedRGBA = []; + Common.Color.blendColors(bgColor.rgba(), fgRGBA, blendedRGBA); + this._bgColor = new Common.Color(blendedRGBA, Common.Color.Format.RGBA); + } + + var bgRGBA = this._bgColor.rgba(); + this._contrastRatio = Common.Color.calculateContrastRatio(fgRGBA, bgRGBA); + } + + /** + * @return {?number} + */ + contrastRatio() { + return this._contrastRatio; + } + + /** + * @return {?Common.Color} + */ + bgColor() { + return this._bgColor; + } + + _updateContrastRatio() { + if (!this._bgColor || !this._hsva) + return; + var fgRGBA = []; + Common.Color.hsva2rgba(this._hsva, fgRGBA); + var bgRGBA = this._bgColor.rgba(); + this._contrastRatio = Common.Color.calculateContrastRatio(fgRGBA, bgRGBA); + } + + /** + * @param {string} level + * @return {?number} + */ + contrastRatioThreshold(level) { + return this._contrastRatioThresholds[level]; + } + + /** + * @param {string} fontSize + * @param {string} fontWeight + * @param {?string} bodyFontSize + * @return {boolean} + */ + static computeIsLargeFont(fontSize, fontWeight, bodyFontSize) { + const boldWeights = ['bold', 'bolder', '600', '700', '800', '900']; + + var fontSizePx = parseFloat(fontSize.replace('px', '')); + var isBold = (boldWeights.indexOf(fontWeight) !== -1); + + if (bodyFontSize) { + var bodyFontSizePx = parseFloat(bodyFontSize.replace('px', '')); + if (isBold) { + if (fontSizePx >= (bodyFontSizePx * 1.2)) + return true; + } else if (fontSizePx >= (bodyFontSizePx * 1.5)) { + return true; + } + return false; + } + + var fontSizePt = Math.ceil(fontSizePx * 72 / 96); + if (isBold) + return fontSizePt >= 14; + else + return fontSizePt >= 18; + } +}; + +ColorPicker.ContrastInfo._ContrastThresholds = { + largeFont: {AA: 3.0, AAA: 4.5}, + normalFont: {AA: 4.5, AAA: 7.0} +}; + +ColorPicker.ContrastOverlay = class { + /** + * @param {!Element} colorElement + * @param {!Element} contentElement + * @param {function(boolean=, !Common.Event=)} toggleMainColorPickerCallback + */ + constructor(colorElement, contentElement, toggleMainColorPickerCallback) { + this._contrastInfo = new ColorPicker.ContrastInfo(); + + var contrastRatioSVG = colorElement.createSVGChild('svg', 'spectrum-contrast-container fill'); + this._contrastRatioLine = contrastRatioSVG.createSVGChild('path', 'spectrum-contrast-line'); + + this._contrastValueBubble = colorElement.createChild('div', 'spectrum-contrast-info'); + this._contrastValueBubble.classList.add('force-white-icons'); + this._contrastValueBubble.createChild('span', 'low-contrast').textContent = Common.UIString('Low contrast'); + this._contrastValue = this._contrastValueBubble.createChild('span', 'value'); + this._contrastValueBubble.appendChild(UI.Icon.create('smallicon-contrast-ratio')); + this._contrastValueBubble.title = Common.UIString('Click to toggle contrast ratio details'); + this._contrastValueBubble.addEventListener('mousedown', this._toggleContrastDetails.bind(this), true); + + this._contrastDetails = new ColorPicker.ContrastDetails( + this._contrastInfo, contentElement, toggleMainColorPickerCallback, this._update.bind(this)); + + this._width = 0; + this._height = 0; + } + + /** + * @param {?SDK.CSSModel.ContrastInfo} contrastInfo + */ + setContrastInfo(contrastInfo) { + this._contrastInfo.setContrastInfo(contrastInfo); + this._update(); + } + + /** + * @param {!Array} hsva + * @param {string} colorString + */ + setColor(hsva, colorString) { + this._contrastInfo.setColor(hsva, colorString); + this._update(); + } + + /** + * @param {number} x + * @param {number} y + */ + moveAwayFrom(x, y) { + var bubble = this._contrastValueBubble; + if (!bubble.boxInWindow().contains(x, y)) + return; + + if (bubble.offsetWidth > ((bubble.offsetParent.offsetWidth / 2) - 10)) + bubble.classList.toggle('contrast-info-top'); + else + bubble.classList.toggle('contrast-info-left'); + } + + _update() { + if (this._contrastInfo.contrastRatio() === null) + return; + + this._contrastValue.textContent = this._contrastInfo.contrastRatio().toFixed(2); + + var AA = this._contrastInfo.contrastRatioThreshold('AA'); + if (!AA) + return; + + // TODO(aboxhall): only redraw line if hue value changes + this._drawContrastRatioLine(AA, this._width, this._height); + + var passesAA = this._contrastInfo.contrastRatio() >= AA; + this._contrastValueBubble.classList.toggle('contrast-fail', !passesAA); + + this._contrastDetails.update(); + } + + /** + * @param {number} width + * @param {number} height + * @param {!AnchorBox} draggerBox + */ + show(width, height, draggerBox) { + if (this._contrastInfo.contrastRatio() === null) { + this.hide(); + return; + } + + this._width = width; + this._height = height; + this._update(); + + this._contrastValueBubble.classList.remove('hidden'); + + var dragX = draggerBox.x + (draggerBox.width / 2); + var dragY = draggerBox.y + (draggerBox.height / 2); + this.moveAwayFrom(dragX, dragY); + } + + hide() { + this._contrastValueBubble.classList.add('hidden'); + } + + /** + * @param {!Event} event + */ + _toggleContrastDetails(event) { + if ('button' in event && event.button !== 0) + return; + event.consume(); + this._contrastDetails.toggleVisible(); + } + + /** + * @param {number} requiredContrast + * @param {number} width + * @param {number} height + */ + _drawContrastRatioLine(requiredContrast, width, height) { + // TODO(aboxhall): throttle this to avoid being called in rapid succession when using eyedropper + if (!width || !height) + return; + + var hsva = this._contrastInfo.hsva(); + var bgColor = this._contrastInfo.bgColor(); + if (!hsva || !bgColor) + return; + + const dS = 0.02; + const epsilon = 0.002; + const H = 0; + const S = 1; + const V = 2; + const A = 3; + + var fgRGBA = []; + Common.Color.hsva2rgba(hsva, fgRGBA); + var bgRGBA = bgColor.rgba(); + var bgLuminance = Common.Color.luminance(bgRGBA); + var blendedRGBA = []; + Common.Color.blendColors(fgRGBA, bgRGBA, blendedRGBA); + var fgLuminance = Common.Color.luminance(blendedRGBA); + this._contrastValueBubble.classList.toggle('light', fgLuminance > 0.5); + var fgIsLighter = fgLuminance > bgLuminance; + var desiredLuminance = Common.Color.desiredLuminance(bgLuminance, requiredContrast, fgIsLighter); + + var lastV = hsva[V]; + var currentSlope = 0; + var candidateHSVA = [hsva[H], 0, 0, hsva[A]]; + var pathBuilder = []; + var candidateRGBA = []; + Common.Color.hsva2rgba(candidateHSVA, candidateRGBA); + Common.Color.blendColors(candidateRGBA, bgRGBA, blendedRGBA); + + /** + * Approach the desired contrast ratio by modifying the given component + * from the given starting value. + * @param {number} index + * @param {number} x + * @param {boolean} onAxis + * @return {?number} + */ + function approach(index, x, onAxis) { + while (0 <= x && x <= 1) { + candidateHSVA[index] = x; + Common.Color.hsva2rgba(candidateHSVA, candidateRGBA); + Common.Color.blendColors(candidateRGBA, bgRGBA, blendedRGBA); + var fgLuminance = Common.Color.luminance(blendedRGBA); + var dLuminance = fgLuminance - desiredLuminance; + + if (Math.abs(dLuminance) < (onAxis ? epsilon / 10 : epsilon)) + return x; + else + x += (index === V ? -dLuminance : dLuminance); + } + return null; + } + + for (var s = 0; s < 1 + dS; s += dS) { + s = Math.min(1, s); + candidateHSVA[S] = s; + + var v = lastV; + v = lastV + currentSlope * dS; + + v = approach(V, v, s === 0); + if (v === null) + break; + + currentSlope = (v - lastV) / dS; + + pathBuilder.push(pathBuilder.length ? 'L' : 'M'); + pathBuilder.push(s * width); + pathBuilder.push((1 - v) * height); + } + + if (s < 1 + dS) { + s -= dS; + candidateHSVA[V] = 1; + s = approach(S, s, true); + if (s !== null) + pathBuilder = pathBuilder.concat(['L', s * width, -1]); + } + + this._contrastRatioLine.setAttribute('d', pathBuilder.join(' ')); + } +}; + +ColorPicker.ContrastDetails = class { + /** + * @param {!ColorPicker.ContrastInfo} contrastInfo + * @param {!Element} contentElement + * @param {function(boolean=, !Common.Event=)} toggleMainColorPickerCallback + * @param {function()} backgroundColorPickedCallback + */ + constructor(contrastInfo, contentElement, toggleMainColorPickerCallback, backgroundColorPickedCallback) { + /** @type {!ColorPicker.ContrastInfo} */ + this._contrastInfo = contrastInfo; + + /** @type {function(boolean=, !Common.Event=)} */ + this._toggleMainColorPicker = toggleMainColorPickerCallback; + + /** @type {function()} */ + this._backgroundColorPickedCallback = backgroundColorPickedCallback; + + this._contrastDetails = contentElement.createChild('div', 'spectrum-contrast-details'); + var contrastValueRow = this._contrastDetails.createChild('div'); + contrastValueRow.createTextChild(Common.UIString('Contrast Ratio')); + + var contrastLink = contrastValueRow.appendChild(UI.createExternalLink( + 'https://developers.google.com/web/fundamentals/accessibility/accessible-styles#color_and_contrast', + 'Color and contrast on Web Fundamentals', 'contrast-link')); + contrastLink.textContent = ''; + contrastLink.appendChild(UI.Icon.create('mediumicon-info')); + + this._contrastValueBubble = contrastValueRow.createChild('span', 'contrast-details-value force-white-icons'); + this._contrastValue = this._contrastValueBubble.createChild('span'); + this._contrastValueBubbleIcons = []; + this._contrastValueBubbleIcons.push( + this._contrastValueBubble.appendChild(UI.Icon.create('smallicon-checkmark-square'))); + this._contrastValueBubbleIcons.push( + this._contrastValueBubble.appendChild(UI.Icon.create('smallicon-checkmark-behind'))); + this._contrastValueBubbleIcons.push(this._contrastValueBubble.appendChild(UI.Icon.create('smallicon-no'))); + this._contrastValueBubble.addEventListener('mouseenter', this._toggleContrastValueHovered.bind(this)); + this._contrastValueBubble.addEventListener('mouseleave', this._toggleContrastValueHovered.bind(this)); + + var toolbar = new UI.Toolbar('', contrastValueRow); + var closeButton = new UI.ToolbarButton('Hide contrast ratio details', 'largeicon-delete'); + closeButton.addEventListener(UI.ToolbarButton.Events.Click, this.hide.bind(this)); + toolbar.appendToolbarItem(closeButton); + + this._contrastThresholds = this._contrastDetails.createChild('div', 'contrast-thresholds'); + this._contrastAA = this._contrastThresholds.createChild('div', 'contrast-threshold'); + this._contrastAA.appendChild(UI.Icon.create('smallicon-checkmark-square')); + this._contrastAA.appendChild(UI.Icon.create('smallicon-no')); + this._contrastPassFailAA = this._contrastAA.createChild('span', 'contrast-pass-fail'); + + this._contrastAAA = this._contrastThresholds.createChild('div', 'contrast-threshold'); + this._contrastAAA.appendChild(UI.Icon.create('smallicon-checkmark-square')); + this._contrastAAA.appendChild(UI.Icon.create('smallicon-no')); + this._contrastPassFailAAA = this._contrastAAA.createChild('span', 'contrast-pass-fail'); + + var bgColorRow = this._contrastDetails.createChild('div'); + bgColorRow.createTextChild(Common.UIString('Background color:')); + this._bgColorSwatch = new ColorPicker.Spectrum.Swatch(bgColorRow, 'contrast'); + + this._bgColorPicker = bgColorRow.createChild('button', 'background-color-picker'); + this._bgColorPicker.appendChild(UI.Icon.create('largeicon-eyedropper')); + this._bgColorPicker.addEventListener('click', this._toggleBackgroundColorPicker.bind(this, undefined)); + this._bgColorPickedBound = this._bgColorPicked.bind(this); + } + + update() { + var contrastRatio = this._contrastInfo.contrastRatio(); + var bgColor = this._contrastInfo.bgColor(); + if (!contrastRatio || !bgColor) + return; + + this._contrastValue.textContent = contrastRatio.toFixed(2); + this._bgColorSwatch.setColor(bgColor); + + var AA = this._contrastInfo.contrastRatioThreshold('AA'); + var AAA = this._contrastInfo.contrastRatioThreshold('AAA'); + + var passesAA = this._contrastInfo.contrastRatio() >= AA; + this._contrastPassFailAA.textContent = ''; + this._contrastPassFailAA.createTextChild(passesAA ? Common.UIString('Passes ') : Common.UIString('Fails ')); + this._contrastPassFailAA.createChild('strong').textContent = Common.UIString('AA (%s)', AA.toFixed(1)); + this._contrastAA.classList.toggle('pass', passesAA); + this._contrastAA.classList.toggle('fail', !passesAA); + + var passesAAA = this._contrastInfo.contrastRatio() >= AAA; + this._contrastPassFailAAA.textContent = ''; + this._contrastPassFailAAA.createTextChild(passesAAA ? Common.UIString('Passes ') : Common.UIString('Fails ')); + this._contrastPassFailAAA.createChild('strong').textContent = Common.UIString('AAA (%s)', AAA.toFixed(1)); + this._contrastAAA.classList.toggle('pass', passesAAA); + this._contrastAAA.classList.toggle('fail', !passesAAA); + + this._contrastValueBubble.classList.toggle('contrast-fail', !passesAA); + this._contrastValueBubble.classList.toggle('contrast-aa', passesAA); + this._contrastValueBubble.classList.toggle('contrast-aaa', passesAAA); + this._contrastValueBubble.style.color = this._contrastInfo.colorString(); + for (var i = 0; i < this._contrastValueBubbleIcons.length; i++) + this._contrastValueBubbleIcons[i].style.setProperty('background', this._contrastInfo.colorString(), 'important'); + + var isWhite = (this._contrastInfo.bgColor().hsla()[2] > 0.9); + this._contrastValueBubble.style.background = + /** @type {string} */ (this._contrastInfo.bgColor().asString(Common.Color.Format.RGBA)); + this._contrastValueBubble.classList.toggle('contrast-color-white', isWhite); + + if (isWhite) { + this._contrastValueBubble.style.removeProperty('border-color'); + } else { + this._contrastValueBubble.style.borderColor = + /** @type {string} */ (this._contrastInfo.bgColor().asString(Common.Color.Format.RGBA)); + } + } + + toggleVisible() { + this._contrastDetails.classList.toggle('visible'); + if (this._contrastDetails.classList.contains('visible')) + this._toggleMainColorPicker(false); + else + this._toggleBackgroundColorPicker(false); + } + + hide() { + this._contrastDetails.classList.remove('visible'); + this._toggleMainColorPicker(false); + } + + /** + * @param {boolean=} enabled + */ + _toggleBackgroundColorPicker(enabled) { + if (enabled === undefined) { + this._bgColorPicker.classList.toggle('active'); + enabled = this._bgColorPicker.classList.contains('active'); + } else { + this._bgColorPicker.classList.toggle('active', enabled); + } + UI.ARIAUtils.setPressed(this._bgColorPicker, enabled); + + InspectorFrontendHost.setEyeDropperActive(enabled); + if (enabled) { + InspectorFrontendHost.events.addEventListener( + InspectorFrontendHostAPI.Events.EyeDropperPickedColor, this._bgColorPickedBound); + } else { + InspectorFrontendHost.events.removeEventListener( + InspectorFrontendHostAPI.Events.EyeDropperPickedColor, this._bgColorPickedBound); + } + } + + /** + * @param {!Common.Event} event + */ + _bgColorPicked(event) { + var rgbColor = /** @type {!{r: number, g: number, b: number, a: number}} */ (event.data); + var rgba = [rgbColor.r, rgbColor.g, rgbColor.b, (rgbColor.a / 2.55 | 0) / 100]; + var color = Common.Color.fromRGBA(rgba); + this._contrastInfo.setBgColor(color); + this.update(); + this._backgroundColorPickedCallback(); + InspectorFrontendHost.bringToFront(); + } + + /** + * @param {!Event} event + */ + _toggleContrastValueHovered(event) { + if (!this._contrastValueBubble.classList.contains('contrast-fail')) + return; + + if (event.type === 'mouseenter') { + this._contrastValueBubble.classList.add('hover'); + for (var i = 0; i < this._contrastValueBubbleIcons.length; i++) + this._contrastValueBubbleIcons[i].style.setProperty('background', '#333', 'important'); + } else { + this._contrastValueBubble.classList.remove('hover'); + for (var i = 0; i < this._contrastValueBubbleIcons.length; i++) { + this._contrastValueBubbleIcons[i].style.setProperty( + 'background', this._contrastInfo.colorString(), 'important'); + } + } + } +}; diff --git a/third_party/WebKit/Source/devtools/front_end/color_picker/Spectrum.js b/third_party/WebKit/Source/devtools/front_end/color_picker/Spectrum.js index 8bc5ae758d94c9..78493e7bbe711a 100644 --- a/third_party/WebKit/Source/devtools/front_end/color_picker/Spectrum.js +++ b/third_party/WebKit/Source/devtools/front_end/color_picker/Spectrum.js @@ -53,8 +53,11 @@ ColorPicker.Spectrum = class extends UI.VBox { .createChild('div', 'spectrum-val fill') .createChild('div', 'spectrum-dragger'); - if (Runtime.experiments.isEnabled('colorContrastRatio')) - this._setUpContrastRatioUI(); + if (Runtime.experiments.isEnabled('colorContrastRatio')) { + var boundToggleColorPicker = this._toggleColorPicker.bind(this); + this._contrastOverlay = + new ColorPicker.ContrastOverlay(this._colorElement, this.contentElement, boundToggleColorPicker); + } var toolbar = new UI.Toolbar('spectrum-eye-dropper', this.contentElement); this._colorPickerButton = new UI.ToolbarToggle(Common.UIString('Toggle color picker'), 'largeicon-eyedropper'); @@ -63,14 +66,7 @@ ColorPicker.Spectrum = class extends UI.VBox { UI.ToolbarButton.Events.Click, this._toggleColorPicker.bind(this, undefined)); toolbar.appendToolbarItem(this._colorPickerButton); - var swatchElement = this.contentElement.createChild('span', 'swatch'); - this._swatchInnerElement = swatchElement.createChild('span', 'swatch-inner'); - this._swatchOverlayElement = swatchElement.createChild('span', 'swatch-overlay'); - this._swatchOverlayElement.addEventListener('click', this._onCopyIconClick.bind(this)); - this._swatchOverlayElement.addEventListener('mouseout', this._onCopyIconMouseout.bind(this)); - this._swatchCopyIcon = UI.Icon.create('largeicon-copy', 'copy-color-icon'); - this._swatchCopyIcon.title = Common.UIString('Copy color to clipboard'); - this._swatchOverlayElement.appendChild(this._swatchCopyIcon); + this._swatch = new ColorPicker.Spectrum.Swatch(this.contentElement); this._hueElement = this.contentElement.createChild('div', 'spectrum-hue'); this._hueSlider = this._hueElement.createChild('div', 'spectrum-slider'); @@ -191,34 +187,11 @@ ColorPicker.Spectrum = class extends UI.VBox { hsva[1] = Number.constrain((event.x - this._colorOffset.left) / this.dragWidth, 0, 1); hsva[2] = Number.constrain(1 - (event.y - this._colorOffset.top) / this.dragHeight, 0, 1); - if (Runtime.experiments.isEnabled('colorContrastRatio')) - positionContrastInfo(this._contrastInfo, event); + if (this._contrastOverlay) + this._contrastOverlay.moveAwayFrom(event.x, event.y); this._innerSetColor(hsva, '', undefined, ColorPicker.Spectrum._ChangeSource.Other); } - - /** - * @param {!Element} info - * @param {!Event} event - */ - function positionContrastInfo(info, event) { - if (!info.boxInWindow().contains(event.x, event.y)) - return; - - if (info.offsetWidth > ((info.offsetParent.offsetWidth / 2) - 10)) - info.classList.toggle('contrast-info-top'); - else - info.classList.toggle('contrast-info-left'); - } - } - - _onCopyIconClick() { - this._swatchCopyIcon.setIconType('largeicon-checkmark'); - InspectorFrontendHost.copyText(this.colorString()); - } - - _onCopyIconMouseout() { - this._swatchCopyIcon.setIconType('largeicon-copy'); } _updatePalettePanel() { @@ -590,6 +563,14 @@ ColorPicker.Spectrum = class extends UI.VBox { this._innerSetColor(color.hsva(), '', colorFormat, ColorPicker.Spectrum._ChangeSource.Model); } + /** + * @param {?SDK.CSSModel.ContrastInfo} contrastInfo + */ + setContrastInfo(contrastInfo) { + if (this._contrastOverlay) + this._contrastOverlay.setContrastInfo(contrastInfo); + } + /** * @param {!Array|undefined} hsva * @param {string|undefined} colorString @@ -615,6 +596,9 @@ ColorPicker.Spectrum = class extends UI.VBox { this._colorFormat = colorFormat; } + if (hsva && this._contrastOverlay) + this._contrastOverlay.setColor(hsva, this.colorString()); + this._updateHelperLocations(); this._updateUI(); @@ -624,14 +608,6 @@ ColorPicker.Spectrum = class extends UI.VBox { this.dispatchEventToListeners(ColorPicker.Spectrum.Events.ColorChanged, this.colorString()); } - /** - * @param {!Common.Color} color - */ - setContrastColor(color) { - this._contrastColor = color; - this._updateUI(); - } - /** * @return {!Common.Color} */ @@ -718,154 +694,17 @@ ColorPicker.Spectrum = class extends UI.VBox { } } - /** - * @param {number} requiredContrast - * @param {!Array} bgRGBA - * @param {!Array} fgRGBA - */ - _drawContrastRatioLine(requiredContrast, bgRGBA, fgRGBA) { - if (!this._contrastColor || !this.dragWidth || !this.dragHeight) - return; - - /** const */ var width = this.dragWidth; - /** const */ var height = this.dragHeight; - /** const */ var dS = 0.02; - /** const */ var epsilon = 0.002; - /** const */ var H = 0; - /** const */ var S = 1; - /** const */ var V = 2; - /** const */ var A = 3; - - var bgLuminance = Common.Color.luminance(bgRGBA); - var blendedRGBA = []; - Common.Color.blendColors(fgRGBA, bgRGBA, blendedRGBA); - var fgLuminance = Common.Color.luminance(blendedRGBA); - var fgIsLighter = fgLuminance > bgLuminance; - var desiredLuminance = Common.Color.desiredLuminance(bgLuminance, requiredContrast, fgIsLighter); - - var lastV = this._hsv[V]; - var currentSlope = 0; - var candidateHSVA = [this._hsv[H], 0, 0, this._hsv[A]]; - var pathBuilder = []; - var candidateRGBA = []; - Common.Color.hsva2rgba(candidateHSVA, candidateRGBA); - Common.Color.blendColors(candidateRGBA, bgRGBA, blendedRGBA); - - /** - * Approach the desired contrast ratio by modifying the given component - * from the given starting value. - * @param {number} index - * @param {number} x - * @param {boolean} onAxis - * @return {?number} - */ - function approach(index, x, onAxis) { - while (0 <= x && x <= 1) { - candidateHSVA[index] = x; - Common.Color.hsva2rgba(candidateHSVA, candidateRGBA); - Common.Color.blendColors(candidateRGBA, bgRGBA, blendedRGBA); - var fgLuminance = Common.Color.luminance(blendedRGBA); - var dLuminance = fgLuminance - desiredLuminance; - - if (Math.abs(dLuminance) < (onAxis ? epsilon / 10 : epsilon)) - return x; - else - x += (index === V ? -dLuminance : dLuminance); - } - return null; - } - - for (var s = 0; s < 1 + dS; s += dS) { - s = Math.min(1, s); - candidateHSVA[S] = s; - - var v = lastV; - v = lastV + currentSlope * dS; - - v = approach(V, v, s === 0); - if (v === null) - break; - - currentSlope = (v - lastV) / dS; - - pathBuilder.push(pathBuilder.length ? 'L' : 'M'); - pathBuilder.push(s * width); - pathBuilder.push((1 - v) * height); - } - - if (s < 1 + dS) { - s -= dS; - candidateHSVA[V] = 1; - s = approach(S, s, true); - if (s !== null) - pathBuilder = pathBuilder.concat(['L', s * width, -1]); - } - - this._contrastRatioLine.setAttribute('d', pathBuilder.join(' ')); - } - - _setUpContrastRatioUI() { - var contrastRatioSVG = this._colorElement.createSVGChild('svg', 'spectrum-contrast-container fill'); - this._contrastRatioLine = contrastRatioSVG.createSVGChild('path', 'spectrum-contrast-line'); - this._contrastInfo = this._colorElement.createChild('div', 'spectrum-contrast-info'); - this._contrastInfo.classList.add('force-white-icons'); - this._contrastInfo.createChild('span', 'low-contrast').textContent = Common.UIString('Low contrast'); - this._contrastInfo.createChild('span', 'value'); - this._contrastInfo.appendChild(UI.Icon.create('smallicon-contrast-ratio')); - } - - - /** - * @param {boolean} show - */ - _setContrastInfoVisible(show) { - var info = this._contrastInfo; - if (!show) { - info.classList.add('hidden'); - return; - } - - info.classList.remove('hidden'); - - var fgRGBA = []; - Common.Color.hsva2rgba(this._hsv, fgRGBA); - var bgRGBA = this._contrastColor.rgba(); - var contrastRatio = Common.Color.calculateContrastRatio(fgRGBA, bgRGBA); - - // TODO(aboxhall): Determine size of text and switch between AA/AAA ratings. - var requiredContrast = 4.5; - - this._contrastInfo.querySelector('.value').textContent = contrastRatio.toFixed(2); - this._contrastInfo.classList.toggle('contrast-fail', (contrastRatio < requiredContrast)); - this._drawContrastRatioLine(requiredContrast, bgRGBA, fgRGBA); - - var draggerBox = this._colorDragElement.boxInWindow(); - var dragX = draggerBox.x + (draggerBox.width / 2); - var dragY = draggerBox.y + (draggerBox.height / 2); - var infoBox = info.boxInWindow(); - if (infoBox.contains(dragX, dragY)) { - if (info.offsetWidth > ((info.offsetParent.offsetWidth / 2) - 10)) - info.classList.toggle('contrast-info-top'); - else - info.classList.toggle('contrast-info-left'); - } - } - - _updateUI() { var h = Common.Color.fromHSVA([this._hsv[0], 1, 1, 1]); this._colorElement.style.backgroundColor = /** @type {string} */ (h.asString(Common.Color.Format.RGB)); - if (Runtime.experiments.isEnabled('colorContrastRatio')) { - if (this.dragWidth && this._contrastColor) - this._setContrastInfoVisible(true); + if (this._contrastOverlay) { + if (this.dragWidth) + this._contrastOverlay.show(this.dragWidth, this.dragHeight, this._colorDragElement.boxInWindow()); else - this._setContrastInfoVisible(false); + this._contrastOverlay.hide(); } - this._swatchInnerElement.style.backgroundColor = - /** @type {string} */ (this._color().asString(Common.Color.Format.RGBA)); - // Show border if the swatch is white. - this._swatchInnerElement.classList.toggle('swatch-inner-white', this._color().hsla()[2] > 0.9); + this._swatch.setColor(this._color(), this.colorString()); this._colorDragElement.style.backgroundColor = /** @type {string} */ (this._color().asString(Common.Color.Format.RGBA)); var noAlpha = Common.Color.fromHSVA(this._hsv.slice(0, 3).concat(1)); @@ -1125,3 +964,51 @@ ColorPicker.Spectrum.MaterialPalette = { matchUserFormat: true, colors: Object.keys(ColorPicker.Spectrum.MaterialPaletteShades) }; + +ColorPicker.Spectrum.Swatch = class { + /** + * @param {!Element} parentElement + * @param {string=} className + */ + constructor(parentElement, className) { + /** @type {?string} */ + this._colorString; + + var swatchElement = parentElement.createChild('span', 'swatch'); + if (className) + swatchElement.classList.add(className); + this._swatchInnerElement = swatchElement.createChild('span', 'swatch-inner'); + + this._swatchOverlayElement = swatchElement.createChild('span', 'swatch-overlay'); + this._swatchOverlayElement.addEventListener('click', this._onCopyIconClick.bind(this)); + this._swatchOverlayElement.addEventListener('mouseout', this._onCopyIconMouseout.bind(this)); + this._swatchCopyIcon = UI.Icon.create('largeicon-copy', 'copy-color-icon'); + this._swatchCopyIcon.title = Common.UIString('Copy color to clipboard'); + this._swatchOverlayElement.appendChild(this._swatchCopyIcon); + } + + /** + * @param {!Common.Color} color + * @param {string=} colorString + */ + setColor(color, colorString) { + this._swatchInnerElement.style.backgroundColor = + /** @type {string} */ (color.asString(Common.Color.Format.RGBA)); + // Show border if the swatch is white. + this._swatchInnerElement.classList.toggle('swatch-inner-white', color.hsla()[2] > 0.9); + this._colorString = colorString || null; + if (colorString) + this._swatchOverlayElement.hidden = false; + else + this._swatchOverlayElement.hidden = true; + } + + _onCopyIconClick() { + this._swatchCopyIcon.setIconType('largeicon-checkmark'); + InspectorFrontendHost.copyText(this._colorString); + } + + _onCopyIconMouseout() { + this._swatchCopyIcon.setIconType('largeicon-copy'); + } +}; diff --git a/third_party/WebKit/Source/devtools/front_end/color_picker/module.json b/third_party/WebKit/Source/devtools/front_end/color_picker/module.json index b8e7cb6f3a21f0..d5b79a535e22ca 100644 --- a/third_party/WebKit/Source/devtools/front_end/color_picker/module.json +++ b/third_party/WebKit/Source/devtools/front_end/color_picker/module.json @@ -4,6 +4,7 @@ "sdk" ], "scripts": [ + "Contrast.js", "Spectrum.js" ], "resources": [ diff --git a/third_party/WebKit/Source/devtools/front_end/color_picker/spectrum.css b/third_party/WebKit/Source/devtools/front_end/color_picker/spectrum.css index 405c437fadbe38..90d66c79455d4e 100644 --- a/third_party/WebKit/Source/devtools/front_end/color_picker/spectrum.css +++ b/third_party/WebKit/Source/devtools/front_end/color_picker/spectrum.css @@ -131,6 +131,123 @@ margin-right: 4px; } +.spectrum-contrast-details { + position: absolute; + background-color: white; + width: 100%; + height: 111px; + top: 124px; + z-index: 13; + font-size: 13px; + color: #333; + display: none; + line-height: initial; +} + +.spectrum-contrast-details div.toolbar { + position: absolute; + right: 6px; + top: 6px; + margin: 0; +} + +.spectrum-contrast-details div.toolbar [is=ui-icon] { + position: absolute; + right: 6px; + top: 6px; + margin: 0; + background: transparent; +} + +.spectrum-contrast-details.visible { + display: initial; +} + +.spectrum-contrast-details div { + margin: 12px; +} + +.contrast-pass-fail { + margin-left: 0.5em; +} + +.contrast-threshold .smallicon-checkmark-square, +.contrast-threshold .smallicon-no{ + display: none; +} + +.contrast-threshold.pass .smallicon-checkmark-square { + display: inline-block; + background-color: #00b06f; +} + +.contrast-threshold.fail .smallicon-no { + display: inline-block; + background-color: #b40202; +} + +.contrast-threshold-value { + font-weight: bold; +} +.spectrum-contrast-details .contrast-thresholds div { + margin: 0; +} + +.contrast-link { + margin-left: 0.5em; +} + +.contrast-link .mediumicon-info { + margin-top: -3px; + background-color: #333; +} + +.contrast-details-value { + color: white; + border-radius: 2px; + margin-left: 0.5em; + padding: 1px 3px; + align-items: baseline; +} + +.contrast-details-value.contrast-color-white { + border: 1px solid #ddd; +} + +.contrast-details-value.contrast-fail.hover { + color: #333 !important; + background-color: white !important; +} + +.contrast-details-value.contrast-fail.hover [is=ui-icon] { + background-color: black !important; +} + +.contrast-details-value [is=ui-icon] { + display: none; + margin-left: 0.5em; +} + +.contrast-details-value .smallicon-checkmark-behind { + margin-left: -6px; +} + +.contrast-details-value.contrast-fail .smallicon-no { + display: inline-block; +} + +.contrast-details-value.contrast-fail .smallicon-no { + display: inline-block; +} + +.contrast-details-value.contrast-aa .smallicon-checkmark-square { + display: inline-block; +} + +.contrast-details-value.contrast-aaa .smallicon-checkmark-behind { + display: inline-block; +} + .swatch { width: 32px; height: 32px; @@ -460,3 +577,37 @@ div.palette-preview { background-color: #ddd; color: white; } + +.swatch.contrast { + width: 20px; + height: 20px; + margin: -10px; + position: relative; + top: -5px; + left: 17px; + background-image: url(Images/checker.png); + border-radius: 10px; + display: inline-block; +} + +.swatch.contrast .swatch-overlay { + padding: 0; +} + +.swatch.contrast [is=ui-icon] { + margin: -2px; +} + +button.background-color-picker { + border: 0; + padding: 0; + background: none; + position: relative; + margin-top: -12px; + left: 26px; + top: 7px; +} + +button.background-color-picker.active [is=ui-icon].largeicon-eyedropper.icon-mask{ + background-color: hsl(218, 81%, 59%); +} diff --git a/third_party/WebKit/Source/devtools/front_end/elements/ColorSwatchPopoverIcon.js b/third_party/WebKit/Source/devtools/front_end/elements/ColorSwatchPopoverIcon.js index 3f8336c709c1f1..68cd7b24ad235a 100644 --- a/third_party/WebKit/Source/devtools/front_end/elements/ColorSwatchPopoverIcon.js +++ b/third_party/WebKit/Source/devtools/front_end/elements/ColorSwatchPopoverIcon.js @@ -102,7 +102,7 @@ Elements.ColorSwatchPopoverIcon = class { var shiftClickMessage = Common.UIString('Shift + Click to change color format.'); this._swatch.iconElement().title = Common.UIString('Open color picker. %s', shiftClickMessage); this._swatch.iconElement().addEventListener('click', this._iconClick.bind(this)); - this._contrastColor = null; + this._contrastInfo = null; this._boundSpectrumChanged = this._spectrumChanged.bind(this); this._boundOnScroll = this._onScroll.bind(this); @@ -117,12 +117,12 @@ Elements.ColorSwatchPopoverIcon = class { } /** - * @param {!Common.Color} color + * @param {?SDK.CSSModel.ContrastInfo} contrastInfo */ - setContrastColor(color) { - this._contrastColor = color; + setContrastInfo(contrastInfo) { + this._contrastInfo = contrastInfo; if (this._spectrum) - this._spectrum.setContrastColor(this._contrastColor); + this._spectrum.setContrastInfo(contrastInfo); } /** @@ -145,8 +145,8 @@ Elements.ColorSwatchPopoverIcon = class { format = color.format(); this._spectrum = new ColorPicker.Spectrum(); this._spectrum.setColor(color, format); - if (this._contrastColor) - this._spectrum.setContrastColor(this._contrastColor); + if (this._contrastInfo) + this._spectrum.setContrastInfo(this._contrastInfo); this._spectrum.addEventListener(ColorPicker.Spectrum.Events.SizeChanged, this._spectrumResized, this); this._spectrum.addEventListener(ColorPicker.Spectrum.Events.ColorChanged, this._boundSpectrumChanged); diff --git a/third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js b/third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js index 7a0491aad84be2..70f2bf7194e99a 100644 --- a/third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js +++ b/third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js @@ -2016,29 +2016,10 @@ Elements.StylePropertyTreeElement = class extends UI.TreeElement { var swatchIcon = new Elements.ColorSwatchPopoverIcon(this, swatchPopoverHelper, swatch); /** - * @param {?Array} backgroundColors + * @param {?SDK.CSSModel.ContrastInfo} contrastInfo */ - function computedCallback(backgroundColors) { - // TODO(aboxhall): distinguish between !backgroundColors (no text) and - // !backgroundColors.length (no computed bg color) - if (!backgroundColors || !backgroundColors.length) - return; - // TODO(samli): figure out what to do in the case of multiple background colors (i.e. gradients) - var bgColorText = backgroundColors[0]; - var bgColor = Common.Color.parse(bgColorText); - if (!bgColor) - return; - - // If we have a semi-transparent background color over an unknown - // background, draw the line for the "worst case" scenario: where - // the unknown background is the same color as the text. - if (bgColor.hasAlpha) { - var blendedRGBA = []; - Common.Color.blendColors(bgColor.rgba(), color.rgba(), blendedRGBA); - bgColor = new Common.Color(blendedRGBA, Common.Color.Format.RGBA); - } - - swatchIcon.setContrastColor(bgColor); + function computedCallback(contrastInfo) { + swatchIcon.setContrastInfo(contrastInfo); } if (Runtime.experiments.isEnabled('colorContrastRatio') && this.property.name === 'color' && diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/CSSModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/CSSModel.js index 8f0d1aa35008ab..30e324f8f50e7d 100644 --- a/third_party/WebKit/Source/devtools/front_end/sdk/CSSModel.js +++ b/third_party/WebKit/Source/devtools/front_end/sdk/CSSModel.js @@ -362,10 +362,14 @@ SDK.CSSModel = class extends SDK.SDKModel { /** * @param {number} nodeId - * @return {!Promise>} + * @return {!Promise} */ - backgroundColorsPromise(nodeId) { - return this._agent.getBackgroundColors(nodeId); + async backgroundColorsPromise(nodeId) { + var response = this._agent.invoke_getBackgroundColors({nodeId}); + if (response[Protocol.Error]) + return null; + + return response; } /** @@ -748,6 +752,9 @@ SDK.SDKModel.register(SDK.CSSModel, SDK.Target.Capability.DOM, true); /** @typedef {!{range: !Protocol.CSS.SourceRange, styleSheetId: !Protocol.CSS.StyleSheetId, wasUsed: boolean}} */ SDK.CSSModel.RuleUsage; +/** @typedef {{backgroundColors: ?Array, computedFontSize: string, computedFontWeights: string, computedBodyFontSize: string}} */ +SDK.CSSModel.ContrastInfo; + /** @enum {symbol} */ SDK.CSSModel.Events = { FontsUpdated: Symbol('FontsUpdated'), diff --git a/third_party/WebKit/Source/devtools/front_end/ui/Icon.js b/third_party/WebKit/Source/devtools/front_end/ui/Icon.js index f2be3d00dbfbde..134be3620bb987 100644 --- a/third_party/WebKit/Source/devtools/front_end/ui/Icon.js +++ b/third_party/WebKit/Source/devtools/front_end/ui/Icon.js @@ -119,6 +119,8 @@ UI.Icon.SpriteSheets = { UI.Icon.Descriptors = { 'smallicon-bezier': {position: 'a5', spritesheet: 'smallicons', isMask: true}, 'smallicon-checkmark': {position: 'b5', spritesheet: 'smallicons'}, + 'smallicon-checkmark-square': {position: 'b6', spritesheet: 'smallicons', isMask: true}, + 'smallicon-checkmark-behind': {position: 'd6', spritesheet: 'smallicons', isMask: true}, 'smallicon-command-result': {position: 'a4', spritesheet: 'smallicons'}, 'smallicon-contrast-ratio': {position: 'a6', spritesheet: 'smallicons', isMask: true}, 'smallicon-cross': {position: 'b4', spritesheet: 'smallicons'}, @@ -129,6 +131,7 @@ UI.Icon.Descriptors = { 'smallicon-info': {position: 'c3', spritesheet: 'smallicons'}, 'smallicon-inline-breakpoint-conditional': {position: 'd5', spritesheet: 'smallicons'}, 'smallicon-inline-breakpoint': {position: 'd4', spritesheet: 'smallicons'}, + 'smallicon-no': {position: 'c6', spritesheet: 'smallicons', isMask: true}, 'smallicon-orange-ball': {position: 'd3', spritesheet: 'smallicons'}, 'smallicon-red-ball': {position: 'a2', spritesheet: 'smallicons'}, 'smallicon-shadow': {position: 'b2', spritesheet: 'smallicons', isMask: true}, @@ -147,6 +150,7 @@ UI.Icon.Descriptors = { 'mediumicon-clear-storage': {position: 'a4', spritesheet: 'mediumicons', isMask: true}, 'mediumicon-cookie': {position: 'b4', spritesheet: 'mediumicons', isMask: true}, 'mediumicon-database': {position: 'c4', spritesheet: 'mediumicons', isMask: true}, + 'mediumicon-info': {position: 'c1', spritesheet: 'mediumicons', isMask: true}, 'mediumicon-manifest': {position: 'd4', spritesheet: 'mediumicons', isMask: true}, 'mediumicon-service-worker': {position: 'a3', spritesheet: 'mediumicons', isMask: true}, 'mediumicon-table': {position: 'b3', spritesheet: 'mediumicons', isMask: true},