Bug 960984 - Implement the list attribute for <input type=color>. r=emilio,geckoview-reviewers,m_kato

UI support on Windows and Linux. macOS and Android are not supported.

Differential Revision: https://phabricator.services.mozilla.com/D163796
This commit is contained in:
Tom Schuster 2022-12-14 18:17:59 +00:00
parent d3e3ee5bb1
commit a93e3a2add
21 changed files with 176 additions and 45 deletions

View file

@ -56,6 +56,8 @@
#include "nsMappedAttributes.h"
#include "nsIFormControl.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/HTMLDataListElement.h"
#include "mozilla/dom/HTMLOptionElement.h"
#include "nsIFormControlFrame.h"
#include "nsITextControlFrame.h"
#include "nsIFrame.h"
@ -687,6 +689,33 @@ static bool IsPopupBlocked(Document* aDoc) {
return true;
}
nsTArray<nsString> HTMLInputElement::GetColorsFromList() {
RefPtr<HTMLDataListElement> dataList = GetList();
if (!dataList) {
return {};
}
nsTArray<nsString> colors;
RefPtr<nsContentList> options = dataList->Options();
uint32_t length = options->Length(true);
for (uint32_t i = 0; i < length; ++i) {
auto* option = HTMLOptionElement::FromNodeOrNull(options->Item(i, false));
if (!option) {
continue;
}
nsString value;
option->GetValue(value);
if (IsValidSimpleColor(value)) {
ToLowerCase(value);
colors.AppendElement(value);
}
}
return colors;
}
nsresult HTMLInputElement::InitColorPicker() {
MOZ_ASSERT(IsMutable());
@ -719,7 +748,8 @@ nsresult HTMLInputElement::InitColorPicker() {
nsAutoString initialValue;
GetNonFileValueInternal(initialValue);
nsresult rv = colorPicker->Init(win, title, initialValue);
nsTArray<nsString> colors = GetColorsFromList();
nsresult rv = colorPicker->Init(win, title, initialValue, colors);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIColorPickerShownCallback> callback =
@ -4764,6 +4794,22 @@ void HTMLInputElement::SanitizeValue(nsAString& aValue,
}
}
Maybe<nscolor> HTMLInputElement::ParseSimpleColor(const nsAString& aColor) {
// Input color string should be 7 length (i.e. a string representing a valid
// simple color)
if (aColor.Length() != 7 || aColor.First() != '#') {
return {};
}
const nsAString& withoutHash = StringTail(aColor, 6);
nscolor color;
if (!NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) {
return {};
}
return Some(color);
}
bool HTMLInputElement::IsValidSimpleColor(const nsAString& aValue) const {
if (aValue.Length() != 7 || aValue.First() != '#') {
return false;

View file

@ -9,6 +9,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/Decimal.h"
#include "mozilla/Maybe.h"
#include "mozilla/TextControlElement.h"
#include "mozilla/TextControlState.h"
#include "mozilla/UniquePtr.h"
@ -858,6 +859,9 @@ class HTMLInputElement final : public TextControlElement,
*/
bool IsValueEmpty() const;
// Parse a simple (hex) color.
static mozilla::Maybe<nscolor> ParseSimpleColor(const nsAString& aColor);
protected:
MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual ~HTMLInputElement();
@ -1384,6 +1388,11 @@ class HTMLInputElement final : public TextControlElement,
*/
nsresult MaybeInitPickers(EventChainPostVisitor& aVisitor);
/**
* Returns all valid colors in the <datalist> for the input with type=color.
*/
nsTArray<nsString> GetColorsFromList();
enum FilePickerType { FILE_PICKER_FILE, FILE_PICKER_DIRECTORY };
nsresult InitFilePicker(FilePickerType aType);
nsresult InitColorPicker();

View file

@ -30,6 +30,7 @@ support-files = file_double_submit.html
[test_formnovalidate_attribute.html]
[test_input_attributes_reflection.html]
[test_input_color_input_change_events.html]
[test_input_color_picker_datalist.html]
[test_input_color_picker_initial.html]
[test_input_color_picker_popup.html]
[test_input_color_picker_update.html]

View file

@ -0,0 +1,42 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script>
SimpleTest.waitForExplicitFinish();
function runTest() {
let MockColorPicker = SpecialPowers.MockColorPicker;
MockColorPicker.init(window);
MockColorPicker.showCallback = (picker) => {
is(picker.defaultColors.length, 2);
is(picker.defaultColors[0], "#112233");
is(picker.defaultColors[1], "#00ffaa");
MockColorPicker.cleanup();
SimpleTest.finish();
}
let input = document.querySelector("input");
synthesizeMouseAtCenter(input, {});
}
SimpleTest.waitForFocus(runTest);
</script>
</head>
<body>
<input type="color" list="color-list">
<datalist id="color-list">
<option value="#112233"></option>
<option value="black"></option> <!-- invalid -->
<option value="#000000" disabled></option>
<option value="#00FFAA"></option>
<option></option>
</datalist>
</body>
</html>

View file

@ -2272,8 +2272,8 @@ bool BrowserChild::DeallocPDocAccessibleChild(
}
#endif
PColorPickerChild* BrowserChild::AllocPColorPickerChild(const nsAString&,
const nsAString&) {
PColorPickerChild* BrowserChild::AllocPColorPickerChild(
const nsAString&, const nsAString&, const nsTArray<nsString>&) {
MOZ_CRASH("unused");
return nullptr;
}

View file

@ -436,8 +436,9 @@ class BrowserChild final : public nsMessageManagerScriptExecutor,
bool DeallocPDocAccessibleChild(PDocAccessibleChild*);
#endif
PColorPickerChild* AllocPColorPickerChild(const nsAString& aTitle,
const nsAString& aInitialColor);
PColorPickerChild* AllocPColorPickerChild(
const nsAString& aTitle, const nsAString& aInitialColor,
const nsTArray<nsString>& aDefaultColors);
bool DeallocPColorPickerChild(PColorPickerChild* aActor);

View file

@ -3402,8 +3402,9 @@ BrowserParent::GetAuthPrompt(uint32_t aPromptReason, const nsIID& iid,
}
PColorPickerParent* BrowserParent::AllocPColorPickerParent(
const nsString& aTitle, const nsString& aInitialColor) {
return new ColorPickerParent(aTitle, aInitialColor);
const nsString& aTitle, const nsString& aInitialColor,
const nsTArray<nsString>& aDefaultColors) {
return new ColorPickerParent(aTitle, aInitialColor, aDefaultColors);
}
bool BrowserParent::DeallocPColorPickerParent(PColorPickerParent* actor) {

View file

@ -423,8 +423,9 @@ class BrowserParent final : public PBrowserParent,
const ScrollAxis& aHorizontal, const ScrollFlags& aScrollFlags,
const int32_t& aAppUnitsPerDevPixel);
PColorPickerParent* AllocPColorPickerParent(const nsString& aTitle,
const nsString& aInitialColor);
PColorPickerParent* AllocPColorPickerParent(
const nsString& aTitle, const nsString& aInitialColor,
const nsTArray<nsString>& aDefaultColors);
bool DeallocPColorPickerParent(PColorPickerParent* aColorPicker);

View file

@ -53,7 +53,8 @@ bool ColorPickerParent::CreateColorPicker() {
return false;
}
return NS_SUCCEEDED(mPicker->Init(window, mTitle, mInitialColor));
return NS_SUCCEEDED(
mPicker->Init(window, mTitle, mInitialColor, mDefaultColors));
}
mozilla::ipc::IPCResult ColorPickerParent::RecvOpen() {

View file

@ -14,8 +14,11 @@ namespace mozilla::dom {
class ColorPickerParent : public PColorPickerParent {
public:
ColorPickerParent(const nsString& aTitle, const nsString& aInitialColor)
: mTitle(aTitle), mInitialColor(aInitialColor) {}
ColorPickerParent(const nsString& aTitle, const nsString& aInitialColor,
const nsTArray<nsString>& aDefaultColors)
: mTitle(aTitle),
mInitialColor(aInitialColor),
mDefaultColors(aDefaultColors.Clone()) {}
virtual mozilla::ipc::IPCResult RecvOpen() override;
virtual void ActorDestroy(ActorDestroyReason aWhy) override;
@ -45,6 +48,7 @@ class ColorPickerParent : public PColorPickerParent {
nsString mTitle;
nsString mInitialColor;
nsTArray<nsString> mDefaultColors;
};
} // namespace mozilla::dom

View file

@ -436,7 +436,7 @@ parent:
* Create an asynchronous color picker on the parent side,
* but don't open it yet.
*/
async PColorPicker(nsString title, nsString initialColor);
async PColorPicker(nsString title, nsString initialColor, nsString[] defaultColors);
async PFilePicker(nsString aTitle, int16_t aMode);

View file

@ -22,7 +22,8 @@ XPCOMUtils.defineLazyModuleGetters(lazy, {
const { debug, warn } = GeckoViewUtils.initLogging("ColorPickerDelegate");
class ColorPickerDelegate {
init(aParent, aTitle, aInitialColor) {
// TODO(bug 1805397): Implement default colors
init(aParent, aTitle, aInitialColor, aDefaultColors) {
this._prompt = new lazy.GeckoViewPrompter(aParent);
this._msg = {
type: "color",

View file

@ -70,9 +70,10 @@ function MockColorPickerInstance(window) {
}
MockColorPickerInstance.prototype = {
QueryInterface: ChromeUtils.generateQI(["nsIColorPicker"]),
init(aParent, aTitle, aInitialColor) {
init(aParent, aTitle, aInitialColor, aDefaultColors) {
this.parent = aParent;
this.initialColor = aInitialColor;
this.defaultColors = aDefaultColors;
},
initialColor: "",
parent: null,

View file

@ -19,10 +19,7 @@ class mozIDOMWindowProxy;
class nsColorPicker final : public nsIColorPicker {
public:
NS_DECL_ISUPPORTS
NS_IMETHOD Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
const nsAString& aInitialColor) override;
NS_IMETHOD Open(nsIColorPickerShownCallback* aCallback) override;
NS_DECL_NSICOLORPICKER
// For NSColorPanelWrapper.
void Update(NSColor* aColor);

View file

@ -93,9 +93,10 @@ nsColorPicker::~nsColorPicker() {
}
}
// TODO(bug 1805397): Implement default colors
NS_IMETHODIMP
nsColorPicker::Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
const nsAString& aInitialColor) {
const nsAString& aInitialColor, const nsTArray<nsString>& aDefaultColors) {
MOZ_ASSERT(NS_IsMainThread(), "Color pickers can only be opened from main thread currently");
mTitle = aTitle;
mColor = aInitialColor;

View file

@ -5,6 +5,8 @@
#include <gtk/gtk.h>
#include "mozilla/Maybe.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "nsColor.h"
#include "nsColorPicker.h"
#include "nsGtkUtils.h"
@ -12,6 +14,8 @@
#include "WidgetUtils.h"
#include "nsPIDOMWindow.h"
using mozilla::dom::HTMLInputElement;
NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker)
#if defined(ACTIVATE_GTK3_COLOR_PICKER)
@ -59,28 +63,24 @@ GtkColorSelection* nsColorPicker::WidgetGetColorSelection(GtkWidget* widget) {
NS_IMETHODIMP nsColorPicker::Init(mozIDOMWindowProxy* aParent,
const nsAString& title,
const nsAString& initialColor) {
const nsAString& initialColor,
const nsTArray<nsString>& aDefaultColors) {
auto* parent = nsPIDOMWindowOuter::From(aParent);
mParentWidget = mozilla::widget::WidgetUtils::DOMWindowToWidget(parent);
mTitle = title;
mInitialColor = initialColor;
mDefaultColors.Assign(aDefaultColors);
return NS_OK;
}
NS_IMETHODIMP nsColorPicker::Open(
nsIColorPickerShownCallback* aColorPickerShownCallback) {
// Input color string should be 7 length (i.e. a string representing a valid
// simple color)
if (mInitialColor.Length() != 7) {
return NS_ERROR_FAILURE;
}
const nsAString& withoutHash = StringTail(mInitialColor, 6);
nscolor color;
if (!NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) {
auto maybeColor = HTMLInputElement::ParseSimpleColor(mInitialColor);
if (maybeColor.isNothing()) {
return NS_ERROR_FAILURE;
}
nscolor color = maybeColor.value();
if (mCallback) {
// It means Open has already been called: this is not allowed
@ -106,6 +106,16 @@ NS_IMETHODIMP nsColorPicker::Open(
}
gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(color_chooser), FALSE);
// Setting the default colors will put them into "Custom" colors list.
for (const nsString& defaultColor : mDefaultColors) {
if (auto color = HTMLInputElement::ParseSimpleColor(defaultColor)) {
GdkRGBA color_rgba = convertToRgbaColor(*color);
gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(color_chooser), &color_rgba);
}
}
// The initial color needs to be set last.
GdkRGBA color_rgba = convertToRgbaColor(color);
gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(color_chooser), &color_rgba);

View file

@ -65,6 +65,7 @@ class nsColorPicker final : public nsIColorPicker {
nsString mTitle;
nsString mColor;
nsString mInitialColor;
nsTArray<nsString> mDefaultColors;
};
#endif // nsColorPicker_h__

View file

@ -14,14 +14,15 @@ NS_IMPL_ISUPPORTS(nsColorPickerProxy, nsIColorPicker)
NS_IMETHODIMP
nsColorPickerProxy::Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
const nsAString& aInitialColor) {
const nsAString& aInitialColor,
const nsTArray<nsString>& aDefaultColors) {
BrowserChild* browserChild = BrowserChild::GetFrom(aParent);
if (!browserChild) {
return NS_ERROR_FAILURE;
}
browserChild->SendPColorPickerConstructor(this, nsString(aTitle),
nsString(aInitialColor));
browserChild->SendPColorPickerConstructor(this, aTitle, aInitialColor,
aDefaultColors);
NS_ADDREF_THIS();
return NS_OK;
}

View file

@ -62,7 +62,7 @@ interface nsIColorPicker : nsISupports
* parameter has to follow the format specified on top
* of this file.
*/
void init(in mozIDOMWindowProxy parent, in AString title, in AString initialColor);
void init(in mozIDOMWindowProxy parent, in AString title, in AString initialColor, in Array<AString> defaultColors);
/**
* Opens the color dialog asynchrounously.

View file

@ -8,6 +8,7 @@
#include <shlwapi.h>
#include "mozilla/ArrayUtils.h"
#include "mozilla/AutoRestore.h"
#include "nsIWidget.h"
#include "nsString.h"
@ -86,18 +87,18 @@ static void BGRIntToRGBString(DWORD color, nsAString& aResult) {
static AsyncColorChooser* gColorChooser;
AsyncColorChooser::AsyncColorChooser(COLORREF aInitialColor,
const nsTArray<nsString>& aDefaultColors,
nsIWidget* aParentWidget,
nsIColorPickerShownCallback* aCallback)
: mozilla::Runnable("AsyncColorChooser"),
mInitialColor(aInitialColor),
mDefaultColors(aDefaultColors.Clone()),
mColor(aInitialColor),
mParentWidget(aParentWidget),
mCallback(aCallback) {}
NS_IMETHODIMP
AsyncColorChooser::Run() {
static COLORREF sCustomColors[16] = {0};
MOZ_ASSERT(NS_IsMainThread(),
"Color pickers can only be opened from main thread currently");
@ -112,12 +113,21 @@ AsyncColorChooser::Run() {
? mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW)
: nullptr));
COLORREF customColors[16];
for (size_t i = 0; i < mozilla::ArrayLength(customColors); i++) {
if (i < mDefaultColors.Length()) {
customColors[i] = ColorStringToRGB(mDefaultColors[i]);
} else {
customColors[i] = 0x00FFFFFF;
}
}
CHOOSECOLOR options;
options.lStructSize = sizeof(options);
options.hwndOwner = adtw.get();
options.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ENABLEHOOK;
options.rgbResult = mInitialColor;
options.lpCustColors = sCustomColors;
options.lpCustColors = customColors;
options.lpfnHook = HookProc;
mColor = ChooseColor(&options) ? options.rgbResult : mInitialColor;
@ -191,19 +201,21 @@ NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker)
NS_IMETHODIMP
nsColorPicker::Init(mozIDOMWindowProxy* parent, const nsAString& title,
const nsAString& aInitialColor) {
const nsAString& aInitialColor,
const nsTArray<nsString>& aDefaultColors) {
MOZ_ASSERT(parent,
"Null parent passed to colorpicker, no color picker for you!");
mParentWidget =
WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(parent));
mInitialColor = ColorStringToRGB(aInitialColor);
mDefaultColors.Assign(aDefaultColors);
return NS_OK;
}
NS_IMETHODIMP
nsColorPicker::Open(nsIColorPickerShownCallback* aCallback) {
NS_ENSURE_ARG(aCallback);
nsCOMPtr<nsIRunnable> event =
new AsyncColorChooser(mInitialColor, mParentWidget, aCallback);
nsCOMPtr<nsIRunnable> event = new AsyncColorChooser(
mInitialColor, mDefaultColors, mParentWidget, aCallback);
return NS_DispatchToMainThread(event);
}

View file

@ -18,7 +18,9 @@ class nsIWidget;
class AsyncColorChooser : public mozilla::Runnable {
public:
AsyncColorChooser(COLORREF aInitialColor, nsIWidget* aParentWidget,
AsyncColorChooser(COLORREF aInitialColor,
const nsTArray<nsString>& aDefaultColors,
nsIWidget* aParentWidget,
nsIColorPickerShownCallback* aCallback);
NS_IMETHOD Run() override;
@ -29,6 +31,7 @@ class AsyncColorChooser : public mozilla::Runnable {
LPARAM aLParam);
COLORREF mInitialColor;
nsTArray<nsString> mDefaultColors;
COLORREF mColor;
nsCOMPtr<nsIWidget> mParentWidget;
nsCOMPtr<nsIColorPickerShownCallback> mCallback;
@ -41,13 +44,11 @@ class nsColorPicker : public nsIColorPicker {
nsColorPicker();
NS_DECL_ISUPPORTS
NS_IMETHOD Init(mozIDOMWindowProxy* parent, const nsAString& title,
const nsAString& aInitialColor) override;
NS_IMETHOD Open(nsIColorPickerShownCallback* aCallback) override;
NS_DECL_NSICOLORPICKER
private:
COLORREF mInitialColor;
nsTArray<nsString> mDefaultColors;
nsCOMPtr<nsIWidget> mParentWidget;
};