Bug 150494

Summary: Web Inspector: Add support for Gradients in the Visual sidebar background editor
Product: WebKit Reporter: Devin Rousso <hi>
Component: Web InspectorAssignee: Devin Rousso <hi>
Status: RESOLVED FIXED    
Severity: Normal CC: bburg, commit-queue, graouts, joepeck, mattbaker, nvasilyev, timothy, webkit-bug-importer
Priority: P2 Keywords: InRadar
Version: WebKit Nightly Build   
Hardware: All   
OS: All   
Bug Depends on:    
Bug Blocks: 147563    
Attachments:
Description Flags
Patch
timothy: review+
After patch is applied
none
Patch none

Description Devin Rousso 2015-10-23 01:15:57 PDT
Suggested in https://bugs.webkit.org/show_bug.cgi?id=148310
Comment 1 Radar WebKit Bug Importer 2015-10-23 01:16:45 PDT
<rdar://problem/23232369>
Comment 2 Devin Rousso 2015-11-15 18:49:23 PST
Created attachment 265567 [details]
Patch
Comment 3 Devin Rousso 2015-11-15 18:49:51 PST
Created attachment 265568 [details]
After patch is applied
Comment 4 Devin Rousso 2015-11-15 19:40:45 PST
By the way, I worked on this with less than 3 hours of sleep during HackSC, so there may be obvious errors...
Comment 5 Joseph Pecoraro 2015-11-16 10:39:05 PST
Comment on attachment 265567 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=265567&action=review

Cool! I'll let someone else review, but this looks great.

> Source/WebInspectorUI/ChangeLog:112
> +        Reworked into VisualStyleBackgroundEditor.js.

Typo: VisualStyleBackgroundPicker.js

> Source/WebInspectorUI/UserInterface/Views/GradientEditor.js:2
> + * Copyright (C) 2015 Devin Rousso <dcrousso+webkit@gmail.com>. All rights reserved.

Since a lot of this code came from an existing file with a copyright, it should have both you and the original ("Copyright (C) 2014 Apple Inc. All rights reserved.")

> Source/WebInspectorUI/UserInterface/Views/VisualStyleBackgroundPicker.js:194
> +    _valueInputValueChanged(event) {

Style: Brace on new line

> Source/WebInspectorUI/UserInterface/Views/VisualStyleBackgroundPicker.js:207
> +    _handleKeywordChanged(event) {

Style: Brace on new line
Comment 6 Devin Rousso 2015-11-16 10:45:21 PST
Comment on attachment 265567 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=265567&action=review

>> Source/WebInspectorUI/UserInterface/Views/GradientEditor.js:2
>> + * Copyright (C) 2015 Devin Rousso <dcrousso+webkit@gmail.com>. All rights reserved.
> 
> Since a lot of this code came from an existing file with a copyright, it should have both you and the original ("Copyright (C) 2014 Apple Inc. All rights reserved.")

Ah OK gotcha.  I wasn't really sure how to go about doing that, but what you wrote makes sense.  Thanks!

>> Source/WebInspectorUI/UserInterface/Views/VisualStyleBackgroundPicker.js:194
>> +    _valueInputValueChanged(event) {
> 
> Style: Brace on new line

Oops.
Comment 7 Timothy Hatcher 2015-11-17 16:49:08 PST
Comment on attachment 265567 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=265567&action=review

> Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorGradientEditingController.js:63
> +        this._gradientEditor.addEventListener(WebInspector.GradientEditor.Event.ColorPickerToggled, popover.update, popover);

It is usually better to have a wrapper listener to make sure the function isn't confused by the first argument being an event object.
Comment 8 Devin Rousso 2015-11-19 22:28:39 PST
Created attachment 265942 [details]
Patch
Comment 9 Devin Rousso 2015-11-20 14:28:39 PST
Comment on attachment 265942 [details]
Patch

>diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog
>index bb02dcf..591e236 100644
>--- a/Source/WebInspectorUI/ChangeLog
>+++ b/Source/WebInspectorUI/ChangeLog
>@@ -1,3 +1,116 @@
>+2015-11-20  Devin Rousso  <dcrousso+webkit@gmail.com>
>+
>+        Web Inspector: Add support for Gradients in the Visual sidebar background editor
>+        https://bugs.webkit.org/show_bug.cgi?id=150494
>+
>+        Reviewed by Timothy Hatcher.
>+
>+        Allows the editors in the Visual sidebar Background Style section to
>+        work with gradients and data URIs.
>+
>+        * Localizations/en.lproj/localizedStrings.js:
>+        * UserInterface/Controllers/CodeMirrorGradientEditingController.css: Deleted.
>+        Some styling was reused in VisualStyleBackgroundPicker.css.
>+
>+        * UserInterface/Controllers/CodeMirrorGradientEditingController.js:
>+        (WebInspector.CodeMirrorGradientEditingController):
>+        (WebInspector.CodeMirrorGradientEditingController.prototype.popoverWillPresent):
>+        (WebInspector.CodeMirrorGradientEditingController.prototype.popoverDidPresent):
>+        (WebInspector.CodeMirrorGradientEditingController.prototype._gradientEditorGradientChanged):
>+        (WebInspector.CodeMirrorGradientEditingController.prototype.handleEvent): Deleted.
>+        (WebInspector.CodeMirrorGradientEditingController.prototype.gradientSliderStopsDidChange): Deleted.
>+        (WebInspector.CodeMirrorGradientEditingController.prototype.gradientSliderStopWasSelected): Deleted.
>+        (WebInspector.CodeMirrorGradientEditingController.prototype.dragToAdjustControllerWasAdjustedByAmount): Deleted.
>+        (WebInspector.CodeMirrorGradientEditingController.prototype._handleInputEvent): Deleted.
>+        (WebInspector.CodeMirrorGradientEditingController.prototype._angleInputValueDidChange): Deleted.
>+        (WebInspector.CodeMirrorGradientEditingController.prototype._handleChangeEvent): Deleted.
>+        (WebInspector.CodeMirrorGradientEditingController.prototype._colorPickerColorChanged): Deleted.
>+        (WebInspector.CodeMirrorGradientEditingController.prototype._updateCSSClassForGradientType): Deleted.
>+        Reworked gradient editor popup into WebInspector.GradientEditor.
>+
>+        * UserInterface/Main.html:
>+        * UserInterface/Models/Gradient.js:
>+        (WebInspector.Gradient.fromString):
>+        (WebInspector.Gradient.stopsWithComponents):
>+        (WebInspector.LinearGradient.linearGradientWithComponents):
>+        Removed console.error statements as they didn't do anything but clog the console.
>+
>+        * UserInterface/Views/GradientEditor.css: Added.
>+        (.gradient-editor):
>+        (.gradient-editor.radial-gradient):
>+        (.gradient-editor.editing-color):
>+        (.gradient-editor.radial-gradient.editing-color):
>+        (.gradient-editor > .gradient-type-select):
>+        (.gradient-editor > .gradient-slider):
>+        (.gradient-editor > .color-picker):
>+        (.gradient-editor > .color-picker > .slider):
>+        (.gradient-editor > .color-picker > .brightness):
>+        (.gradient-editor > .color-picker > .opacity):
>+        (.gradient-editor > .gradient-angle):
>+        (.gradient-editor.radial-gradient > .gradient-angle):
>+        (.gradient-editor > .gradient-angle > input):
>+
>+        * UserInterface/Views/GradientEditor.js: Added.
>+        (WebInspector.GradientEditor):
>+        (WebInspector.GradientEditor.prototype.get element):
>+        (WebInspector.GradientEditor.prototype.set gradient):
>+        (WebInspector.GradientEditor.prototype.get gradient):
>+        (WebInspector.GradientEditor.prototype.gradientSliderStopsDidChange):
>+        (WebInspector.GradientEditor.prototype.gradientSliderStopWasSelected):
>+        (WebInspector.GradientEditor.prototype.dragToAdjustControllerWasAdjustedByAmount):
>+        (WebInspector.GradientEditor.prototype._updateCSSClassForGradientType):
>+        (WebInspector.GradientEditor.prototype._gradientTypeChanged):
>+        (WebInspector.GradientEditor.prototype._colorPickerColorChanged):
>+        (WebInspector.GradientEditor.prototype._angleChanged):
>+        (WebInspector.GradientEditor.prototype._angleInputValueDidChange):
>+        New standalone editor for CSS Gradients.
>+
>+        * UserInterface/Views/VisualStyleBackgroundPicker.css: Added.
>+        (.visual-style-property-container.background-picker > .visual-style-property-value-container):
>+        (.visual-style-property-container.background-picker > .visual-style-property-value-container > .color-gradient-swatch):
>+        (.visual-style-property-container.background-picker > .visual-style-property-value-container > .color-gradient-swatch:hover):
>+        (.visual-style-property-container.background-picker > .visual-style-property-value-container > .color-gradient-swatch:active):
>+        (.visual-style-property-container.background-picker > .visual-style-property-value-container > .color-gradient-swatch > span):
>+        (.visual-style-property-container.background-picker > .visual-style-property-value-container.gradient-value > .color-gradient-swatch):
>+        (.visual-style-property-container.background-picker > .visual-style-property-value-container.gradient-value > .color-gradient-swatch + .value-input):
>+        (.visual-style-property-container.background-picker > .visual-style-property-value-container > .value-input):
>+        (.visual-style-property-container.background-picker > .visual-style-property-value-container > .value-input[disabled]):
>+        (.visual-style-property-container.background-picker > .visual-style-property-value-container > .value-type-picker-select):
>+
>+        * UserInterface/Views/VisualStyleBackgroundPicker.js: Added.
>+        (WebInspector.VisualStyleBackgroundPicker):
>+        (WebInspector.VisualStyleBackgroundPicker.prototype.get value):
>+        (WebInspector.VisualStyleBackgroundPicker.prototype.set value):
>+        (WebInspector.VisualStyleBackgroundPicker.prototype.get synthesizedValue):
>+        (WebInspector.VisualStyleBackgroundPicker.prototype.parseValue):
>+        (WebInspector.VisualStyleBackgroundPicker.prototype._updateValueInput):
>+        (WebInspector.VisualStyleBackgroundPicker.prototype._updateGradientSwatch):
>+        (WebInspector.VisualStyleBackgroundPicker.prototype._gradientSwatchClicked):
>+        (WebInspector.VisualStyleBackgroundPicker.prototype._gradientEditorGradientChanged):
>+        (WebInspector.VisualStyleBackgroundPicker.prototype._valueInputValueChanged):
>+        (WebInspector.VisualStyleBackgroundPicker.prototype._keywordSelectMouseDown):
>+        (WebInspector.VisualStyleBackgroundPicker.prototype._handleKeywordChanged):
>+        (WebInspector.VisualStyleBackgroundPicker.prototype._createValueOptions):
>+        (WebInspector.VisualStyleBackgroundPicker.prototype._addAdvancedValues):
>+        (WebInspector.VisualStyleBackgroundPicker.prototype._removeAdvancedValues):
>+        (WebInspector.VisualStyleBackgroundPicker.prototype._toggleTabbingOfSelectableElements):
>+        Visual property editor for the CSS background-image property. Supports
>+        limited keywords, as well as url() and gradients.
>+
>+        * UserInterface/Views/VisualStyleCommaSeparatedKeywordEditor.js:
>+        (WebInspector.VisualStyleCommaSeparatedKeywordEditor.prototype.set value):
>+        Fixed regular expression parsing to not match commas inside parenthesis.
>+
>+        * UserInterface/Views/VisualStyleDetailsPanel.js:
>+        (WebInspector.VisualStyleDetailsPanel.prototype._populateBackgroundStyleSection):
>+
>+        * UserInterface/Views/VisualStylePropertyEditorLink.css:
>+        (.visual-style-property-editor-link:not(.link-all) > .visual-style-property-editor-link-border):
>+        Fixed spacing of link line.
>+
>+        * UserInterface/Views/VisualStyleURLInput.js: Removed.
>+        Reworked into VisualStyleBackgroundPicker.js.
>+
> 2015-11-19  Brian Burg  <bburg@apple.com>
> 
>         Web Inspector: yank/kill shortcuts (CTRL+Y, K) don't work in Console / QuickConsole
>diff --git a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
>index 60c7fe9bb19d9ca823f11df69ecf21ff3b5b8c7e..2b542f7c7786794dadc4266f1db45d8ae0ec7374 100644
>GIT binary patch
>delta 145
>zcmezLgr(yJ%Z7N{$^2#<lWlE;7}F;!JdvLK*Gve?i+f@@Ip36PbG+>f_sPoZg(h>a
>z=b9YZDgb0#O`h2*u-U!!#$tPKhE#@1hH{2{h9ZU(1_g#NhD3%OhEkxY5<@JIZwtgq
>d4519g44Djh4Cz1~ijK(vo6WZiYcj@j0stifD;fX*
>
>delta 29
>lcmeBp!Sd+|%Z7N{&3|oQxKFNa<Jg?sc46`Mty+x1oB-)@4p#sG
>
>diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorGradientEditingController.css b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorGradientEditingController.css
>deleted file mode 100644
>index 30e1af9..0000000
>--- a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorGradientEditingController.css
>+++ /dev/null
>@@ -1,117 +0,0 @@
>-/*
>- * Copyright (C) 2014 Apple Inc. All rights reserved.
>- *
>- * Redistribution and use in source and binary forms, with or without
>- * modification, are permitted provided that the following conditions
>- * are met:
>- * 1. Redistributions of source code must retain the above copyright
>- *    notice, this list of conditions and the following disclaimer.
>- * 2. Redistributions in binary form must reproduce the above copyright
>- *    notice, this list of conditions and the following disclaimer in the
>- *    documentation and/or other materials provided with the distribution.
>- *
>- * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
>- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
>- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
>- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
>- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
>- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
>- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
>- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
>- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
>- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
>- * THE POSSIBILITY OF SUCH DAMAGE.
>- */
>-
>-.gradient-editing-controller {
>-    width: 260px;
>-    height: 109px;
>-}
>-
>-.gradient-editing-controller.edits-color {
>-    height: 306px;
>-}
>-
>-.gradient-editing-controller.radial-gradient {
>-    height: 78px;
>-}
>-
>-.gradient-editing-controller.edits-color.radial-gradient {
>-    height: 275px;
>-}
>-
>-.gradient-editing-controller select {
>-    position: absolute;
>-    left: 17px;
>-    top: 9px;
>-    width: 237px;
>-
>-    font-size: 16px;
>-}
>-
>-.gradient-editing-controller .gradient-slider {
>-    left: 16px;
>-    right: 26px;
>-    top: 42px;
>-}
>-
>-.gradient-editing-controller .color-picker {
>-    position: absolute;
>-
>-    top: 86px;
>-    width: 202px;
>-    height: 172px;
>-    padding: 0 10px;
>-}
>-
>-.gradient-editing-controller > .color-picker > .slider {
>-    top: 2px;
>-    width: 186px;
>-}
>-
>-.gradient-editing-controller > .color-picker > .brightness {
>-    left: 214px;
>-}
>-
>-.gradient-editing-controller > .color-picker > .opacity {
>-    left: 237px;
>-}
>-
>-.gradient-editing-controller > label {
>-    position: absolute;
>-    top: 85px;
>-    right: 16px;
>-
>-    text-align: right;
>-    font-size: 13px;
>-}
>-
>-.gradient-editing-controller.radial-gradient > label {
>-    display: none;
>-}
>-
>-.gradient-editing-controller.edits-color > label {
>-    top: 283px;
>-}
>-
>-.gradient-editing-controller > label > input {
>-    width: 48px;
>-
>-    padding-right: 4px;
>-    margin-left: 5px;
>-
>-    background-color: white;
>-
>-    border-radius: 4px;
>-    border: 1px solid hsl(0, 0%, 60%);
>-    box-shadow: inset 0 0 1px 1px hsl(0, 0%, 89%);
>-    outline: 0;
>-
>-    font-size: 13px;
>-    text-align: right;
>-}
>-
>-.gradient-editing-controller > label.drag-to-adjust,
>-.gradient-editing-controller > label.drag-to-adjust > input {
>-    cursor: col-resize;
>-}
>diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorGradientEditingController.js b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorGradientEditingController.js
>index e1e16a5..7ff38e0 100644
>--- a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorGradientEditingController.js
>+++ b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorGradientEditingController.js
>@@ -28,34 +28,6 @@ WebInspector.CodeMirrorGradientEditingController = class CodeMirrorGradientEditi
>     constructor(codeMirror, marker)
>     {
>         super(codeMirror, marker);
>-
>-        if (!WebInspector.CodeMirrorGradientEditingController.GradientTypes) {
>-            WebInspector.CodeMirrorGradientEditingController.GradientTypes = {
>-                "linear-gradient": {
>-                    type: WebInspector.LinearGradient,
>-                    label: WebInspector.UIString("Linear Gradient"),
>-                    repeats: false
>-                },
>-
>-                "radial-gradient": {
>-                    type: WebInspector.RadialGradient,
>-                    label: WebInspector.UIString("Radial Gradient"),
>-                    repeats: false
>-                },
>-
>-                "repeating-linear-gradient": {
>-                    type: WebInspector.LinearGradient,
>-                    label: WebInspector.UIString("Repeating Linear Gradient"),
>-                    repeats: true
>-                },
>-
>-                "repeating-radial-gradient": {
>-                    type: WebInspector.RadialGradient,
>-                    label: WebInspector.UIString("Repeating Radial Gradient"),
>-                    repeats: true
>-                }
>-            };
>-        }
>     }
> 
>     // Public
>@@ -86,162 +58,26 @@ WebInspector.CodeMirrorGradientEditingController = class CodeMirrorGradientEditi
> 
>     popoverWillPresent(popover)
>     {
>-        this._container = document.createElement("div");
>-        this._container.className = WebInspector.CodeMirrorGradientEditingController.StyleClassName;
>-
>-        this._gradientTypePicker = this._container.appendChild(document.createElement("select"));
>-        for (var type in WebInspector.CodeMirrorGradientEditingController.GradientTypes) {
>-            var option = this._gradientTypePicker.appendChild(document.createElement("option"));
>-            option.value = type;
>-            option.innerText = WebInspector.CodeMirrorGradientEditingController.GradientTypes[type].label;
>+        function handleColorPickerToggled(event)
>+        {
>+            popover.update();
>         }
>-        this._gradientTypePicker.addEventListener("change", this);
>-
>-        this._gradientSlider = new WebInspector.GradientSlider;
>-        this._container.appendChild(this._gradientSlider.element);
> 
>-        this._colorPicker = new WebInspector.ColorPicker;
>-        this._colorPicker.colorWheel.dimension = 190;
>-        this._colorPicker.addEventListener(WebInspector.ColorPicker.Event.ColorChanged, this._colorPickerColorChanged, this);
>-
>-        var angleLabel = this._container.appendChild(document.createElement("label"));
>-        angleLabel.textContent = WebInspector.UIString("Angle");
>-
>-        this._angleInput = document.createElement("input");
>-        this._angleInput.type = "text";
>-        this._angleInput.size = 3;
>-        this._angleInput.addEventListener("input", this);
>-        angleLabel.appendChild(this._angleInput);
>-
>-        var dragToAdjustController = new WebInspector.DragToAdjustController(this);
>-        dragToAdjustController.element = angleLabel;
>-        dragToAdjustController.enabled = true;
>-
>-        this._updateCSSClassForGradientType();
>-
>-        popover.content = this._container;
>+        this._gradientEditor = new WebInspector.GradientEditor;
>+        this._gradientEditor.addEventListener(WebInspector.GradientEditor.Event.GradientChanged, this._gradientEditorGradientChanged, this);
>+        this._gradientEditor.addEventListener(WebInspector.GradientEditor.Event.ColorPickerToggled, handleColorPickerToggled, this);
>+        popover.content = this._gradientEditor.element;
>     }
> 
>     popoverDidPresent(popover)
>     {
>-        this._gradientSlider.stops = this.value.stops;
>-
>-        if (this.value instanceof WebInspector.LinearGradient) {
>-            this._gradientTypePicker.value = this.value.repeats ? "repeating-linear-gradient" : "linear-gradient";
>-            this._angleInput.value = this.value.angle + "\u00B0";
>-        } else
>-            this._gradientTypePicker.value = this.value.repeats ? "repeating-radial-gradient" : "radial-gradient";
>-
>-        this._gradientSlider.delegate = this;
>-    }
>-
>-    // Protected
>-
>-    handleEvent(event)
>-    {
>-        if (event.type === "input")
>-            this._handleInputEvent(event);
>-        else if (event.type === "change")
>-            this._handleChangeEvent(event);
>-    }
>-
>-    gradientSliderStopsDidChange(gradientSlider)
>-    {
>-        this.text = this.value.toString();
>-    }
>-
>-    gradientSliderStopWasSelected(gradientSlider, stop)
>-    {
>-        var selectedStop = gradientSlider.selectedStop;
>-
>-        if (selectedStop && !this._container.classList.contains(WebInspector.CodeMirrorGradientEditingController.EditsColorClassName)) {
>-            this._container.appendChild(this._colorPicker.element);
>-            this._container.classList.add(WebInspector.CodeMirrorGradientEditingController.EditsColorClassName);
>-            this._colorPicker.color = selectedStop.color;
>-        } else if (!selectedStop) {
>-            this._colorPicker.element.remove();
>-            this._container.classList.remove(WebInspector.CodeMirrorGradientEditingController.EditsColorClassName);
>-        }
>-
>-        // Ensure the angle input is not focused since, if it were, it'd make a scrollbar appear as we
>-        // animate the popover's frame to fit its new content.
>-        this._angleInput.blur();
>-
>-        this.popover.update();
>-    }
>-
>-    dragToAdjustControllerWasAdjustedByAmount(dragToAdjustController, amount)
>-    {
>-        var angle = parseFloat(this._angleInput.value) + amount;
>-        if (Math.round(angle) !== angle)
>-            angle = angle.toFixed(1);
>-
>-        this._angleInput.value = angle;
>-        this._angleInputValueDidChange(angle);
>+        this._gradientEditor.gradient = this.value;
>     }
> 
>     // Private
> 
>-    _handleInputEvent(event)
>+    _gradientEditorGradientChanged(event)
>     {
>-        var angle = parseFloat(this._angleInput.value);
>-        if (isNaN(angle))
>-            return;
>-
>-        this._angleInputValueDidChange(angle);
>-    }
>-
>-    _angleInputValueDidChange(angle)
>-    {
>-        this.value.angle = angle;
>-        this.text = this.value.toString();
>-
>-        var matches = this._angleInput.value.match(/\u00B0/g);
>-        if (!matches || matches.length !== 1) {
>-            var selectionStart = this._angleInput.selectionStart;
>-            this._angleInput.value = angle + "\u00B0";
>-            this._angleInput.selectionStart = selectionStart;
>-            this._angleInput.selectionEnd = selectionStart;
>-        }
>-    }
>-
>-    _handleChangeEvent(event)
>-    {
>-        var descriptor = WebInspector.CodeMirrorGradientEditingController.GradientTypes[this._gradientTypePicker.value];
>-        if (!(this.value instanceof descriptor.type)) {
>-            if (descriptor.type === WebInspector.LinearGradient) {
>-                this.value = new WebInspector.LinearGradient(180, this.value.stops);
>-                this._angleInput.value = "180\u00B0";
>-            } else
>-                this.value = new WebInspector.RadialGradient("", this.value.stops);
>-
>-            this._updateCSSClassForGradientType();
>-            this.popover.update();
>-        }
>-        this.value.repeats = descriptor.repeats;
>-        this.text = this.value.toString();
>-    }
>-
>-    _colorPickerColorChanged(event)
>-    {
>-        this._gradientSlider.selectedStop.color = event.target.color;
>-        this._gradientSlider.stops = this.value.stops;
>-        this.text = this.value.toString();
>-    }
>-
>-    _updateCSSClassForGradientType()
>-    {
>-        if (this.value instanceof WebInspector.LinearGradient)
>-            this._container.classList.remove(WebInspector.CodeMirrorGradientEditingController.RadialGradientClassName);
>-        else
>-            this._container.classList.add(WebInspector.CodeMirrorGradientEditingController.RadialGradientClassName);
>+        this.value = event.data.gradient;
>     }
> };
>-
>-WebInspector.CodeMirrorGradientEditingController.StyleClassName = "gradient-editing-controller";
>-WebInspector.CodeMirrorGradientEditingController.EditsColorClassName = "edits-color";
>-WebInspector.CodeMirrorGradientEditingController.RadialGradientClassName = "radial-gradient";
>-
>-// Lazily populated in the WebInspector.CodeMirrorGradientEditingController constructor.
>-// It needs to be lazy to use UIString after Main.js and localizedStrings.js loads.
>-WebInspector.CodeMirrorGradientEditingController.GradientTypes = null;
>diff --git a/Source/WebInspectorUI/UserInterface/Main.html b/Source/WebInspectorUI/UserInterface/Main.html
>index 6be42c2..8b5f72c 100644
>--- a/Source/WebInspectorUI/UserInterface/Main.html
>+++ b/Source/WebInspectorUI/UserInterface/Main.html
>@@ -87,6 +87,7 @@
>     <link rel="stylesheet" href="Views/FontResourceContentView.css">
>     <link rel="stylesheet" href="Views/FormattedValue.css">
>     <link rel="stylesheet" href="Views/GoToLineDialog.css">
>+    <link rel="stylesheet" href="Views/GradientEditor.css">
>     <link rel="stylesheet" href="Views/GradientSlider.css">
>     <link rel="stylesheet" href="Views/HierarchicalPathComponent.css">
>     <link rel="stylesheet" href="Views/HoverMenu.css">
>@@ -164,6 +165,7 @@
>     <link rel="stylesheet" href="Views/TypeTreeElement.css">
>     <link rel="stylesheet" href="Views/TypeTreeView.css">
>     <link rel="stylesheet" href="Views/Variables.css">
>+    <link rel="stylesheet" href="Views/VisualStyleBackgroundPicker.css">
>     <link rel="stylesheet" href="Views/VisualStyleColorPicker.css">
>     <link rel="stylesheet" href="Views/VisualStyleCommaSeparatedKeywordEditor.css">
>     <link rel="stylesheet" href="Views/VisualStyleDetailsPanel.css">
>@@ -181,7 +183,6 @@
> 
>     <link rel="stylesheet" href="Controllers/CodeMirrorCompletionController.css">
>     <link rel="stylesheet" href="Controllers/CodeMirrorDragToAdjustNumberController.css">
>-    <link rel="stylesheet" href="Controllers/CodeMirrorGradientEditingController.css">
>     <link rel="stylesheet" href="Controllers/CodeMirrorTokenTrackingController.css">
> 
>     <script src="External/CodeMirror/codemirror.js"></script>
>@@ -470,6 +471,7 @@
>     <script src="Views/GeneralTreeElementPathComponent.js"></script>
>     <script src="Views/GenericResourceContentView.js"></script>
>     <script src="Views/GoToLineDialog.js"></script>
>+    <script src="Views/GradientEditor.js"></script>
>     <script src="Views/GradientSlider.js"></script>
>     <script src="Views/HierarchicalPathNavigationItem.js"></script>
>     <script src="Views/HoverMenu.js"></script>
>@@ -566,6 +568,7 @@
> 
>     <script src="Views/VisualStyleDetailsPanel.js"></script>
>     <script src="Views/VisualStylePropertyEditor.js"></script>
>+    <script src="Views/VisualStyleBackgroundPicker.js"></script>
>     <script src="Views/VisualStyleColorPicker.js"></script>
>     <script src="Views/VisualStyleCommaSeparatedKeywordEditor.js"></script>
>     <script src="Views/VisualStyleFontFamilyListEditor.js"></script>
>@@ -583,7 +586,6 @@
>     <script src="Views/VisualStyleTabbedPropertiesRow.js"></script>
>     <script src="Views/VisualStyleTimingEditor.js"></script>
>     <script src="Views/VisualStyleUnitSlider.js"></script>
>-    <script src="Views/VisualStyleURLInput.js"></script>
> 
>     <script src="Controllers/Annotator.js"></script>
>     <script src="Controllers/CodeMirrorEditingController.js"></script>
>diff --git a/Source/WebInspectorUI/UserInterface/Models/Gradient.js b/Source/WebInspectorUI/UserInterface/Models/Gradient.js
>index 3e4ff6c..a094f3b 100644
>--- a/Source/WebInspectorUI/UserInterface/Models/Gradient.js
>+++ b/Source/WebInspectorUI/UserInterface/Models/Gradient.js
>@@ -39,10 +39,8 @@ WebInspector.Gradient = {
>             type = WebInspector.Gradient.Types.Linear;
>         else if (typeString.indexOf(WebInspector.Gradient.Types.Radial) !== -1)
>             type = WebInspector.Gradient.Types.Radial;
>-        else {
>-            console.error("Couldn't parse angle \"" + typeString + "\"");
>+        else
>             return null;
>-        }
> 
>         var components = [];
>         var currentParams = [];
>@@ -112,10 +110,8 @@ WebInspector.Gradient = {
>             }
>         });
> 
>-        if (!stops.length) {
>-            console.error("Couldn't parse any stops");
>+        if (!stops.length)
>             return null;
>-        }
> 
>         for (var i = 0, count = stops.length; i < count; ++i) {
>             var stop = stops[i];
>@@ -190,7 +186,6 @@ WebInspector.LinearGradient = class LinearGradient
>                 angle = 315;
>                 break;
>             default:
>-                console.error("Couldn't parse angle \"to " + components[0].join(" ") + "\"");
>                 return null;
>             }
>             components.shift();
>diff --git a/Source/WebInspectorUI/UserInterface/Views/GradientEditor.css b/Source/WebInspectorUI/UserInterface/Views/GradientEditor.css
>new file mode 100644
>index 0000000..8446318
>--- /dev/null
>+++ b/Source/WebInspectorUI/UserInterface/Views/GradientEditor.css
>@@ -0,0 +1,99 @@
>+/*
>+ * Copyright (C) 2015 Devin Rousso <dcrousso+webkit@gmail.com>. All rights reserved.
>+ *
>+ * Redistribution and use in source and binary forms, with or without
>+ * modification, are permitted provided that the following conditions
>+ * are met:
>+ * 1. Redistributions of source code must retain the above copyright
>+ *    notice, this list of conditions and the following disclaimer.
>+ * 2. Redistributions in binary form must reproduce the above copyright
>+ *    notice, this list of conditions and the following disclaimer in the
>+ *    documentation and/or other materials provided with the distribution.
>+ *
>+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
>+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
>+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
>+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
>+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
>+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
>+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
>+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
>+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
>+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
>+ * THE POSSIBILITY OF SUCH DAMAGE.
>+ */
>+
>+.gradient-editor {
>+    position: relative;
>+    margin: 5px 11px;
>+    padding-bottom: 45px;
>+}
>+
>+.gradient-editor.radial-gradient {
>+    padding-bottom: 15px;
>+}
>+
>+.gradient-editor.editing-color {
>+    padding-bottom: 30px;
>+}
>+
>+.gradient-editor.radial-gradient.editing-color {
>+    padding-bottom: 0;
>+}
>+
>+.gradient-editor > .gradient-type-select {
>+    width: 237px;
>+    margin-left: 1px;
>+    font-size: 16px;
>+}
>+
>+.gradient-editor > .gradient-slider {
>+    position: relative;
>+    left: auto;
>+    margin-top: 7px;
>+}
>+
>+.gradient-editor > .color-picker {
>+    width: 238px;
>+    height: auto;
>+    margin-top: 25px;
>+    padding: 0;
>+}
>+
>+.gradient-editor > .color-picker > .slider {
>+    top: 2px;
>+    width: 186px;
>+}
>+
>+.gradient-editor > .color-picker > .brightness {
>+    left: 202px;
>+}
>+
>+.gradient-editor > .color-picker > .opacity {
>+    left: 228px;
>+}
>+
>+.gradient-editor > .gradient-angle {
>+    position: absolute;
>+    right: 0;
>+    bottom: 0;
>+    text-align: right;
>+    font-size: 13px;
>+}
>+
>+.gradient-editor.radial-gradient > .gradient-angle {
>+    display: none;
>+}
>+
>+.gradient-editor > .gradient-angle > input {
>+    width: 48px;
>+    margin-left: 5px;
>+    padding-right: 4px;
>+    text-align: right;
>+    font-size: 13px;
>+    background-color: white;
>+    border-radius: 4px;
>+    border: 1px solid hsl(0, 0%, 60%);
>+    box-shadow: inset 0 0 1px 1px hsl(0, 0%, 89%);
>+    outline: none;
>+}
>diff --git a/Source/WebInspectorUI/UserInterface/Views/GradientEditor.js b/Source/WebInspectorUI/UserInterface/Views/GradientEditor.js
>new file mode 100644
>index 0000000..441701e
>--- /dev/null
>+++ b/Source/WebInspectorUI/UserInterface/Views/GradientEditor.js
>@@ -0,0 +1,229 @@
>+/*
>+ * Copyright (C) 2014 Apple Inc. All rights reserved.
>+ * Copyright (C) 2015 Devin Rousso <dcrousso+webkit@gmail.com>. All rights reserved.
>+ *
>+ * Redistribution and use in source and binary forms, with or without
>+ * modification, are permitted provided that the following conditions
>+ * are met:
>+ * 1. Redistributions of source code must retain the above copyright
>+ *    notice, this list of conditions and the following disclaimer.
>+ * 2. Redistributions in binary form must reproduce the above copyright
>+ *    notice, this list of conditions and the following disclaimer in the
>+ *    documentation and/or other materials provided with the distribution.
>+ *
>+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
>+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
>+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
>+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
>+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
>+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
>+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
>+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
>+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
>+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
>+ * THE POSSIBILITY OF SUCH DAMAGE.
>+ */
>+
>+WebInspector.GradientEditor = class GradientEditor extends WebInspector.Object
>+{
>+    constructor()
>+    {
>+        super();
>+
>+        this._element = document.createElement("div");
>+        this._element.classList.add("gradient-editor");
>+
>+        this._gradient = null;
>+        this._gradientTypes = {
>+            "linear-gradient": {
>+                type: WebInspector.LinearGradient,
>+                label: WebInspector.UIString("Linear Gradient"),
>+                repeats: false
>+            },
>+            "radial-gradient": {
>+                type: WebInspector.RadialGradient,
>+                label: WebInspector.UIString("Radial Gradient"),
>+                repeats: false
>+            },
>+            "repeating-linear-gradient": {
>+                type: WebInspector.LinearGradient,
>+                label: WebInspector.UIString("Repeating Linear Gradient"),
>+                repeats: true
>+            },
>+            "repeating-radial-gradient": {
>+                type: WebInspector.RadialGradient,
>+                label: WebInspector.UIString("Repeating Radial Gradient"),
>+                repeats: true
>+            }
>+        };
>+        this._editingColor = false;
>+
>+        this._gradientTypePicker = this._element.appendChild(document.createElement("select"));
>+        this._gradientTypePicker.classList.add("gradient-type-select");
>+        for (let type in this._gradientTypes) {
>+            let option = this._gradientTypePicker.appendChild(document.createElement("option"));
>+            option.value = type;
>+            option.text = this._gradientTypes[type].label;
>+        }
>+        this._gradientTypePicker.addEventListener("change", this._gradientTypeChanged.bind(this));
>+
>+        this._gradientSlider = new WebInspector.GradientSlider(this);
>+        this._element.appendChild(this._gradientSlider.element);
>+
>+        this._colorPicker = new WebInspector.ColorPicker;
>+        this._colorPicker.colorWheel.dimension = 190;
>+        this._colorPicker.addEventListener(WebInspector.ColorPicker.Event.ColorChanged, this._colorPickerColorChanged, this);
>+
>+        let angleLabel = this._element.appendChild(document.createElement("label"));
>+        angleLabel.classList.add("gradient-angle");
>+        angleLabel.append(WebInspector.UIString("Angle"));
>+
>+        this._angleInput = angleLabel.appendChild(document.createElement("input"));
>+        this._angleInput.type = "text";
>+        this._angleInput.addEventListener("input", this._angleChanged.bind(this));
>+
>+        let dragToAdjustController = new WebInspector.DragToAdjustController(this);
>+        dragToAdjustController.element = angleLabel;
>+        dragToAdjustController.enabled = true;
>+    }
>+
>+    get element()
>+    {
>+        return this._element;
>+    }
>+
>+    set gradient(gradient)
>+    {
>+        if (!gradient)
>+            return;
>+
>+        const isLinear = gradient instanceof WebInspector.LinearGradient;
>+        const isRadial = gradient instanceof WebInspector.RadialGradient;
>+        console.assert(isLinear || isRadial);
>+        if (!isLinear && !isRadial)
>+            return;
>+
>+        this._gradient = gradient;
>+        this._gradientSlider.stops = this._gradient.stops;
>+        if (isLinear) {
>+            this._gradientTypePicker.value = this._gradient.repeats ? "repeating-linear-gradient" : "linear-gradient";
>+            this._angleInput.value = this._gradient.angle + "\u00B0";
>+        } else
>+            this._gradientTypePicker.value = this._gradient.repeats ? "repeating-radial-gradient" : "radial-gradient";
>+
>+        this._updateCSSClassForGradientType();
>+    }
>+
>+    get gradient()
>+    {
>+        return this._gradient;
>+    }
>+
>+    // Protected
>+
>+    gradientSliderStopsDidChange(gradientSlider)
>+    {
>+        this._gradient.stops = gradientSlider.stops;
>+
>+        this.dispatchEventToListeners(WebInspector.GradientEditor.Event.GradientChanged, {gradient: this._gradient});
>+    }
>+
>+    gradientSliderStopWasSelected(gradientSlider, stop)
>+    {
>+        const selectedStop = gradientSlider.selectedStop;
>+        if (selectedStop && !this._editingColor) {
>+            this._element.appendChild(this._colorPicker.element);
>+            this._element.classList.add("editing-color");
>+            this._colorPicker.color = selectedStop.color;
>+            this._editingColor = true;
>+        } else if (!selectedStop) {
>+            this._colorPicker.element.remove();
>+            this._element.classList.remove("editing-color");
>+            this._editingColor = false;
>+        }
>+
>+        // Ensure the angle input is not focused since, if it were, it'd make a scrollbar appear as we
>+        // animate the popover's frame to fit its new content.
>+        this._angleInput.blur();
>+
>+        this.dispatchEventToListeners(WebInspector.GradientEditor.Event.ColorPickerToggled);
>+        this.dispatchEventToListeners(WebInspector.GradientEditor.Event.GradientChanged, {gradient: this._gradient});
>+    }
>+
>+    dragToAdjustControllerWasAdjustedByAmount(dragToAdjustController, amount)
>+    {
>+        const angleInputValue = parseFloat(this._angleInput.value);
>+        if (isNaN(angleInputValue))
>+            return;
>+
>+        let angle = angleInputValue + amount;
>+        if (Math.round(angle) !== angle)
>+            angle = angle.toFixed(1);
>+
>+        this._angleInput.value = angle;
>+        this._angleInputValueDidChange(angle);
>+    }
>+
>+    // Private
>+
>+    _updateCSSClassForGradientType()
>+    {
>+        const isRadial = this._gradient instanceof WebInspector.RadialGradient;
>+        this._element.classList.toggle("radial-gradient", isRadial);
>+
>+        this.dispatchEventToListeners(WebInspector.GradientEditor.Event.ColorPickerToggled);
>+    }
>+
>+    _gradientTypeChanged(event)
>+    {
>+        const descriptor = this._gradientTypes[this._gradientTypePicker.value];
>+        if (!(this._gradient instanceof descriptor.type)) {
>+            if (descriptor.type === WebInspector.LinearGradient) {
>+                this._gradient = new WebInspector.LinearGradient(180, this._gradient.stops);
>+                this._angleInput.value = "180\u00B0";
>+            } else
>+                this._gradient = new WebInspector.RadialGradient("", this._gradient.stops);
>+
>+            this._updateCSSClassForGradientType();
>+        }
>+        this._gradient.repeats = descriptor.repeats;
>+
>+        this.dispatchEventToListeners(WebInspector.GradientEditor.Event.GradientChanged, {gradient: this._gradient});
>+    }
>+
>+    _colorPickerColorChanged(event)
>+    {
>+        this._gradientSlider.selectedStop.color = event.target.color;
>+        this._gradientSlider.stops = this._gradient.stops;
>+
>+        this.dispatchEventToListeners(WebInspector.GradientEditor.Event.GradientChanged, {gradient: this._gradient});
>+    }
>+
>+    _angleChanged(event)
>+    {
>+        const angle = parseFloat(this._angleInput.value) || 0;
>+        if (isNaN(angle))
>+            return;
>+
>+        this._angleInputValueDidChange(angle);
>+    }
>+
>+    _angleInputValueDidChange(angle)
>+    {
>+        this._gradient.angle = angle;
>+        const matches = this._angleInput.value.match(/\u00B0/g);
>+        if (!matches || matches.length !== 1) {
>+            const savedStart = this._angleInput.selectionStart;
>+            this._angleInput.value = angle + "\u00B0";
>+            this._angleInput.selectionStart = savedStart;
>+            this._angleInput.selectionEnd = savedStart;
>+        }
>+
>+        this.dispatchEventToListeners(WebInspector.GradientEditor.Event.GradientChanged, {gradient: this._gradient});
>+    }
>+}
>+
>+WebInspector.GradientEditor.Event = {
>+    GradientChanged: "gradient-editor-gradient-changed",
>+    ColorPickerToggled: "gradient-editor-color-picker-toggled"
>+};
>diff --git a/Source/WebInspectorUI/UserInterface/Views/VisualStyleBackgroundPicker.css b/Source/WebInspectorUI/UserInterface/Views/VisualStyleBackgroundPicker.css
>new file mode 100644
>index 0000000..5fbe439
>--- /dev/null
>+++ b/Source/WebInspectorUI/UserInterface/Views/VisualStyleBackgroundPicker.css
>@@ -0,0 +1,93 @@
>+/*
>+ * Copyright (C) 2015 Devin Rousso <dcrousso+webkit@gmail.com>. All rights reserved.
>+ *
>+ * Redistribution and use in source and binary forms, with or without
>+ * modification, are permitted provided that the following conditions
>+ * are met:
>+ * 1. Redistributions of source code must retain the above copyright
>+ *    notice, this list of conditions and the following disclaimer.
>+ * 2. Redistributions in binary form must reproduce the above copyright
>+ *    notice, this list of conditions and the following disclaimer in the
>+ *    documentation and/or other materials provided with the distribution.
>+ *
>+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
>+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
>+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
>+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
>+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
>+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
>+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
>+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
>+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
>+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
>+ * THE POSSIBILITY OF SUCH DAMAGE.
>+ */
>+
>+.visual-style-property-container.background-picker > .visual-style-property-value-container {
>+    display: flex;
>+}
>+
>+.visual-style-property-container.background-picker > .visual-style-property-value-container > .color-gradient-swatch {
>+    display: none;
>+    position: relative;
>+    width: 38px;
>+    height: 18px;
>+    margin-top: 1px;
>+    /* Make a checkered background for transparent colors to show against. */
>+    background-image: linear-gradient(to bottom, hsl(0, 0%, 80%), hsl(0, 0%, 80%)),
>+                      linear-gradient(to bottom, hsl(0, 0%, 80%), hsl(0, 0%, 80%));
>+    background-color: white;
>+    background-size: 50%;
>+    background-position: top left, bottom right;
>+    background-repeat: no-repeat;
>+    border: 1px solid hsla(0, 0%, 25%, 0.4);
>+    border-radius: 4px;
>+    overflow: hidden;
>+    cursor: default;
>+}
>+
>+.visual-style-property-container.background-picker > .visual-style-property-value-container > .color-gradient-swatch:hover {
>+    border-color: hsla(0, 0%, 25%, 0.8);
>+}
>+
>+.visual-style-property-container.background-picker > .visual-style-property-value-container > .color-gradient-swatch:active {
>+    border-color: hsl(0, 0%, 25%);
>+}
>+
>+.visual-style-property-container.background-picker > .visual-style-property-value-container > .color-gradient-swatch > span {
>+    position: absolute;
>+    top: 0;
>+    right: 0;
>+    bottom: 0;
>+    left: 0;
>+}
>+
>+.visual-style-property-container.background-picker > .visual-style-property-value-container.gradient-value > .color-gradient-swatch {
>+    display: block;
>+}
>+
>+.visual-style-property-container.background-picker > .visual-style-property-value-container.gradient-value > .color-gradient-swatch + .value-input {
>+    margin-left: -3px;
>+    padding-left: 5px;
>+    border-top-left-radius: 0;
>+    border-bottom-left-radius: 0;
>+    border-left: none;
>+}
>+
>+.visual-style-property-container.background-picker > .visual-style-property-value-container > .value-input {
>+    margin: 1px 4px 3px 0;
>+    padding: 0 3px;
>+    border-radius: 4px;
>+    border: solid 1px hsl(0, 0%, 83%);
>+    text-align: left;
>+    font-size: 10px;
>+}
>+
>+.visual-style-property-container.background-picker > .visual-style-property-value-container > .value-input[disabled] {
>+    opacity: 0.7;
>+    pointer-events: none;
>+}
>+
>+.visual-style-property-container.background-picker > .visual-style-property-value-container > .value-type-picker-select {
>+    max-width: 170px;
>+}
>diff --git a/Source/WebInspectorUI/UserInterface/Views/VisualStyleBackgroundPicker.js b/Source/WebInspectorUI/UserInterface/Views/VisualStyleBackgroundPicker.js
>new file mode 100644
>index 0000000..79d0c20
>--- /dev/null
>+++ b/Source/WebInspectorUI/UserInterface/Views/VisualStyleBackgroundPicker.js
>@@ -0,0 +1,261 @@
>+/*
>+ * Copyright (C) 2015 Devin Rousso <dcrousso+webkit@gmail.com>. All rights reserved.
>+ *
>+ * Redistribution and use in source and binary forms, with or without
>+ * modification, are permitted provided that the following conditions
>+ * are met:
>+ * 1. Redistributions of source code must retain the above copyright
>+ *    notice, this list of conditions and the following disclaimer.
>+ * 2. Redistributions in binary form must reproduce the above copyright
>+ *    notice, this list of conditions and the following disclaimer in the
>+ *    documentation and/or other materials provided with the distribution.
>+ *
>+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
>+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
>+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
>+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
>+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
>+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
>+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
>+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
>+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
>+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
>+ * THE POSSIBILITY OF SUCH DAMAGE.
>+ */
>+
>+WebInspector.VisualStyleBackgroundPicker = class VisualStyleBackgroundPicker extends WebInspector.VisualStylePropertyEditor
>+{
>+    constructor(propertyNames, text, possibleValues, layoutReversed)
>+    {
>+        super(propertyNames, text, possibleValues, null, "background-picker", layoutReversed);
>+
>+        this._gradientSwatchElement = document.createElement("span");
>+        this._gradientSwatchElement.classList.add("color-gradient-swatch");
>+        this._gradientSwatchElement.title = WebInspector.UIString("Click to select a gradient");
>+        this._gradientSwatchElement.addEventListener("click", this._gradientSwatchClicked.bind(this));
>+
>+        let gradientSwatchInnerElement = document.createElement("span");
>+        this._gradientSwatchElement.appendChild(gradientSwatchInnerElement);
>+
>+        this.contentElement.appendChild(this._gradientSwatchElement);
>+
>+        this._valueInputElement = document.createElement("input");
>+        this._valueInputElement.classList.add("value-input");
>+        this._valueInputElement.type = "url";
>+        this._valueInputElement.placeholder = WebInspector.UIString("Enter a URL");
>+        this._valueInputElement.addEventListener("input", this._valueInputValueChanged.bind(this));
>+        this.contentElement.appendChild(this._valueInputElement);
>+
>+        this._valueTypePickerElement = document.createElement("select");
>+        this._valueTypePickerElement.classList.add("value-type-picker-select");
>+        if (this._possibleValues.advanced)
>+            this._valueTypePickerElement.title = WebInspector.UIString("Option-click to show all values");
>+
>+        let imageOption = document.createElement("option");
>+        imageOption.value = "url";
>+        imageOption.text = WebInspector.UIString("Image");
>+        this._valueTypePickerElement.appendChild(imageOption);
>+
>+        let linearGradientOption = document.createElement("option");
>+        linearGradientOption.value = "linear-gradient";
>+        linearGradientOption.text = WebInspector.UIString("Linear Gradient");
>+        this._valueTypePickerElement.appendChild(linearGradientOption);
>+
>+        let radialGradientOption = document.createElement("option");
>+        radialGradientOption.value = "radial-gradient";
>+        radialGradientOption.text = WebInspector.UIString("Radial Gradient");
>+        this._valueTypePickerElement.appendChild(radialGradientOption);
>+
>+        let repeatingLinearGradientOption = document.createElement("option");
>+        repeatingLinearGradientOption.value = "repeating-linear-gradient";
>+        repeatingLinearGradientOption.text = WebInspector.UIString("Repeating Linear Gradient");
>+        this._valueTypePickerElement.appendChild(repeatingLinearGradientOption);
>+
>+        let repeatingRadialGradientOption = document.createElement("option");
>+        repeatingRadialGradientOption.value = "repeating-radial-gradient";
>+        repeatingRadialGradientOption.text = WebInspector.UIString("Repeating Radial Gradient");
>+        this._valueTypePickerElement.appendChild(repeatingRadialGradientOption);
>+
>+        this._valueTypePickerElement.appendChild(document.createElement("hr"));
>+
>+        this._createValueOptions(this._possibleValues.basic);
>+        this._advancedValuesElements = null;
>+
>+        this._valueTypePickerElement.addEventListener("mousedown", this._keywordSelectMouseDown.bind(this));
>+        this._valueTypePickerElement.addEventListener("change", this._handleKeywordChanged.bind(this));
>+        this.contentElement.appendChild(this._valueTypePickerElement);
>+
>+        this._currentType = "url";
>+        this._gradient = null;
>+
>+        this._updateGradientSwatch();
>+    }
>+
>+    // Public
>+
>+    get value()
>+    {
>+        return this._valueInputElement.value;
>+    }
>+
>+    set value(value)
>+    {
>+        if (!value || !value.length || value === this.value)
>+            return;
>+
>+        const isKeyword = this.valueIsSupportedKeyword(value);
>+        this._currentType = isKeyword ? value : value.substring(0, value.indexOf("("));
>+        this._updateValueInput();
>+        if (!isKeyword)
>+            this._valueInputElement.value = value.substring(value.indexOf("(") + 1, value.length - 1);
>+
>+        this._valueTypePickerElement.value = this._currentType;
>+
>+        if (!this._currentType.includes("gradient"))
>+            return;
>+
>+        this._gradient = WebInspector.Gradient.fromString(value);
>+        if (!this._gradient)
>+            return;
>+
>+        this._updateGradientSwatch();
>+    }
>+
>+    get synthesizedValue()
>+    {
>+        if (this.valueIsSupportedKeyword(this._currentType))
>+            return this._currentType;
>+
>+        return this._currentType + "(" + this.value + ")";
>+    }
>+
>+    // Protected
>+
>+    parseValue(text)
>+    {
>+        const validPrefixes = ["url", "linear-gradient", "radial-gradient", "repeating-linear-gradient", "repeating-radial-gradient"];
>+        return validPrefixes.some((item) => text.startsWith(item)) ? [text, text] : null;
>+    }
>+
>+    // Private
>+
>+    _updateValueInput()
>+    {
>+        const supportedKeyword = this.valueIsSupportedKeyword(this._currentType);
>+        const gradientValue = this._currentType.includes("gradient");
>+        this.contentElement.classList.toggle("gradient-value", !supportedKeyword && gradientValue);
>+        this._valueInputElement.disabled = supportedKeyword;
>+        if (supportedKeyword) {
>+            this._valueInputElement.value = "";
>+            this._valueInputElement.placeholder = WebInspector.UIString("Using Keyword Value");
>+        } else {
>+            if (this._currentType === "image") {
>+                this._valueInputElement.type = "url";
>+                this._valueInputElement.placeholder = WebInspector.UIString("Enter a URL");
>+            } else if (gradientValue) {
>+                this._valueInputElement.type = "text";
>+                this._valueInputElement.placeholder = WebInspector.UIString("Enter a Gradient");
>+            }
>+        }
>+    }
>+
>+    _updateGradientSwatch()
>+    {
>+        this._gradientSwatchElement.firstChild.style.background = "";
>+        const value = this.synthesizedValue;
>+        if (!value || value === this._currentType)
>+            return;
>+
>+        this._gradient = WebInspector.Gradient.fromString(value);
>+        this._gradientSwatchElement.firstChild.style.background = this._gradient ? value : null;
>+    }
>+
>+    _gradientSwatchClicked(event)
>+    {
>+        let bounds = WebInspector.Rect.rectFromClientRect(this._gradientSwatchElement.getBoundingClientRect());
>+        let popover = new WebInspector.Popover(this);
>+
>+        function handleColorPickerToggled(event)
>+        {
>+            popover.update();
>+        }
>+
>+        let gradientEditor = new WebInspector.GradientEditor;
>+        gradientEditor.addEventListener(WebInspector.GradientEditor.Event.GradientChanged, this._gradientEditorGradientChanged, this);
>+        gradientEditor.addEventListener(WebInspector.GradientEditor.Event.ColorPickerToggled, handleColorPickerToggled, this);
>+
>+        popover.content = gradientEditor.element;
>+        popover.present(bounds.pad(2), [WebInspector.RectEdge.MIN_X]);
>+
>+        gradientEditor.gradient = this._gradient;
>+    }
>+
>+    _gradientEditorGradientChanged(event)
>+    {
>+        this.value = event.data.gradient.toString();
>+        this._valueDidChange();
>+    }
>+
>+    _valueInputValueChanged(event)
>+    {
>+        this._updateGradientSwatch();
>+        this._valueDidChange();
>+    }
>+
>+    _keywordSelectMouseDown(event)
>+    {
>+        if (event.altKey)
>+            this._addAdvancedValues();
>+        else if (!this._valueIsSupportedAdvancedKeyword())
>+            this._removeAdvancedValues();
>+    }
>+
>+    _handleKeywordChanged(event)
>+    {
>+        this._currentType = this._valueTypePickerElement.value;
>+        this._updateValueInput();
>+        this._updateGradientSwatch();
>+        this._valueDidChange();
>+    }
>+
>+    _createValueOptions(values)
>+    {
>+        let addedElements = [];
>+        for (let key in values) {
>+            let option = document.createElement("option");
>+            option.value = key;
>+            option.text = values[key];
>+            this._valueTypePickerElement.appendChild(option);
>+            addedElements.push(option);
>+        }
>+        return addedElements;
>+    }
>+
>+    _addAdvancedValues()
>+    {
>+        if (this._advancedValuesElements)
>+            return;
>+
>+        this._valueTypePickerElement.appendChild(document.createElement("hr"));
>+        this._advancedValuesElements = this._createValueOptions(this._possibleValues.advanced);
>+    }
>+
>+    _removeAdvancedValues()
>+    {
>+        if (!this._advancedValuesElements)
>+            return;
>+
>+        this._valueTypePickerElement.removeChild(this._advancedValuesElements[0].previousSibling);
>+        for (let element of this._advancedValuesElements)
>+            this._valueTypePickerElement.removeChild(element);
>+
>+        this._advancedValuesElements = null;
>+    }
>+
>+    _toggleTabbingOfSelectableElements(disabled)
>+    {
>+        let tabIndex = disabled ? "-1" : null;
>+        this._valueInputElement.tabIndex = tabIndex;
>+        this._valueTypePickerElement.tabIndex = tabIndex;
>+    }
>+};
>diff --git a/Source/WebInspectorUI/UserInterface/Views/VisualStyleCommaSeparatedKeywordEditor.js b/Source/WebInspectorUI/UserInterface/Views/VisualStyleCommaSeparatedKeywordEditor.js
>index 1e96016..0684964 100644
>--- a/Source/WebInspectorUI/UserInterface/Views/VisualStyleCommaSeparatedKeywordEditor.js
>+++ b/Source/WebInspectorUI/UserInterface/Views/VisualStyleCommaSeparatedKeywordEditor.js
>@@ -100,9 +100,10 @@ WebInspector.VisualStyleCommaSeparatedKeywordEditor = class VisualStyleCommaSepa
>             return;
>         }
> 
>-        let values = commaSeparatedValue.split(/\s*,\s*(?![^\(]*\))/);
>+        // It is necessary to add the beginning \) to ensure inner parenthesis are not matched.
>+        let values = commaSeparatedValue.split(/\)\s*,\s*(?![^\(\)]*\))/);
>         for (let value of values)
>-            this._addCommaSeparatedKeyword(value);
>+            this._addCommaSeparatedKeyword(value + (value.endsWith(")") ? "" : ")"));
> 
>         this._commaSeparatedKeywords.children[0].select(true);
>     }
>diff --git a/Source/WebInspectorUI/UserInterface/Views/VisualStyleDetailsPanel.js b/Source/WebInspectorUI/UserInterface/Views/VisualStyleDetailsPanel.js
>index 2ed76c1..0b0ab26 100644
>--- a/Source/WebInspectorUI/UserInterface/Views/VisualStyleDetailsPanel.js
>+++ b/Source/WebInspectorUI/UserInterface/Views/VisualStyleDetailsPanel.js
>@@ -790,7 +790,7 @@ WebInspector.VisualStyleDetailsPanel = class VisualStyleDetailsPanel extends Web
> 
>         let backgroundImageRow = new WebInspector.DetailsSectionRow;
> 
>-        let backgroundImage = new WebInspector.VisualStyleURLInput("background-image", WebInspector.UIString("Image"), this._keywords.defaults.concat(["None"]));
>+        let backgroundImage = new WebInspector.VisualStyleBackgroundPicker("background-image", WebInspector.UIString("Type"), this._keywords.defaults.concat(["None"]));
> 
>         backgroundImageRow.element.appendChild(backgroundImage.element);
> 
>diff --git a/Source/WebInspectorUI/UserInterface/Views/VisualStylePropertyEditorLink.css b/Source/WebInspectorUI/UserInterface/Views/VisualStylePropertyEditorLink.css
>index a3b4869..e16a31c 100644
>--- a/Source/WebInspectorUI/UserInterface/Views/VisualStylePropertyEditorLink.css
>+++ b/Source/WebInspectorUI/UserInterface/Views/VisualStylePropertyEditorLink.css
>@@ -89,7 +89,7 @@
> 
> .visual-style-property-editor-link:not(.link-all) > .visual-style-property-editor-link-border {
>     width: 10px;
>-    top: -webkit-calc(50% - 1px);
>+    top: 50%;
> }
> 
> .visual-style-property-editor-link:not(.link-all).linked > .visual-style-property-editor-link-border {
>diff --git a/Source/WebInspectorUI/UserInterface/Views/VisualStyleURLInput.js b/Source/WebInspectorUI/UserInterface/Views/VisualStyleURLInput.js
>deleted file mode 100644
>index 2587946..0000000
>--- a/Source/WebInspectorUI/UserInterface/Views/VisualStyleURLInput.js
>+++ /dev/null
>@@ -1,75 +0,0 @@
>-/*
>- * Copyright (C) 2015 Apple Inc. All rights reserved.
>- *
>- * Redistribution and use in source and binary forms, with or without
>- * modification, are permitted provided that the following conditions
>- * are met:
>- * 1. Redistributions of source code must retain the above copyright
>- *    notice, this list of conditions and the following disclaimer.
>- * 2. Redistributions in binary form must reproduce the above copyright
>- *    notice, this list of conditions and the following disclaimer in the
>- *    documentation and/or other materials provided with the distribution.
>- *
>- * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
>- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
>- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
>- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
>- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
>- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
>- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
>- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
>- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
>- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
>- * THE POSSIBILITY OF SUCH DAMAGE.
>- */
>-
>-WebInspector.VisualStyleURLInput = class VisualStyleURLInput extends WebInspector.VisualStylePropertyEditor
>-{
>-    constructor(propertyNames, text, possibleValues, layoutReversed)
>-    {
>-        super(propertyNames, text, possibleValues, null, "url-input", layoutReversed);
>-
>-        this._urlInputElement = document.createElement("input");
>-        this._urlInputElement.type = "url";
>-        this._urlInputElement.placeholder = WebInspector.UIString("Enter a URL");
>-        this._urlInputElement.addEventListener("keyup", this._valueDidChange.bind(this));
>-        this.contentElement.appendChild(this._urlInputElement);
>-    }
>-
>-    // Public
>-
>-    get value()
>-    {
>-        return this._urlInputElement.value;
>-    }
>-
>-    set value(value)
>-    {
>-        if (value && value === this.value)
>-            return;
>-
>-        this._urlInputElement.value = value;
>-    }
>-
>-    get synthesizedValue()
>-    {
>-        let value = this.value;
>-        if (!value || !value.length)
>-            return null;
>-
>-        if (this.valueIsSupportedKeyword(value))
>-            return value;
>-
>-        return "url(" + value + ")";
>-    }
>-
>-    // Protected
>-
>-    parseValue(text)
>-    {
>-        if (this.valueIsSupportedKeyword(text))
>-            return [text, text];
>-
>-        return /^(?:url\(\s*)([^\)]*)(?:\s*\)\s*;?)$/.exec(text);
>-    }
>-};
Comment 10 WebKit Commit Bot 2015-11-20 15:16:23 PST
Comment on attachment 265942 [details]
Patch

Clearing flags on attachment: 265942

Committed r192705: <http://trac.webkit.org/changeset/192705>
Comment 11 WebKit Commit Bot 2015-11-20 15:16:28 PST
All reviewed patches have been landed.  Closing bug.