commit ee0b7e0291d72b43755fc0f5683d9e9a86e49e41
parent a128674e7848553362cdbf8020d8a84a7171c0ee
Author: Cristina Horotan <chorotan@mozilla.com>
Date: Wed, 17 Dec 2025 20:34:20 +0200
Revert "Bug 1629388, Bug 2000644, Bug 2004409 - Use HTML panel for color picker r=desktop-theme-reviewers,webidl,frontend-codestyle-reviewers,tschuster,emilio" for causing bc failures on browser_all_files_referenced.js
This reverts commit a0d863ae052c99ee8acb5acbcb990d42abd33e2d.
Revert "Bug 2000644 - Introduce a superclass of color picker to prepare for multiple subclasses r=devtools-reviewers,nchevobbe,desktop-theme-reviewers,frontend-codestyle-reviewers,hjones"
This reverts commit 11f938e4771fdca428eea3e3a430fcdf983721c4.
Revert "Bug 2004409 - Split window opener logic from DateTimePicker classes r=emilio"
This reverts commit 16894a9e464cc5492d773f885716a6e8a9f59135.
Diffstat:
29 files changed, 965 insertions(+), 1642 deletions(-)
diff --git a/.stylelintrc.js b/.stylelintrc.js
@@ -421,8 +421,6 @@ module.exports = {
// Testing does not use design tokens
"testing/**",
// UA Widgets should not use design tokens
- "toolkit/themes/shared/colorpicker-common.css",
- "toolkit/themes/shared/colorpicker.css",
"toolkit/themes/shared/media/pipToggle.css",
"toolkit/themes/shared/media/videocontrols.css",
"toolkit/content/widgets/datetimebox.css",
diff --git a/devtools/client/shared/widgets/Spectrum.js b/devtools/client/shared/widgets/Spectrum.js
@@ -4,10 +4,6 @@
"use strict";
-const { ColorPickerCommon } = ChromeUtils.importESModule(
- "chrome://global/content/bindings/colorpicker-common.mjs"
-);
-
const EventEmitter = require("resource://devtools/shared/event-emitter.js");
const {
MultiLocalizationHelper,
@@ -27,7 +23,21 @@ const L10N = new MultiLocalizationHelper(
"devtools/client/locales/accessibility.properties",
"devtools/client/locales/inspector.properties"
);
+const ARROW_KEYS = ["ArrowUp", "ArrowRight", "ArrowDown", "ArrowLeft"];
+const [ArrowUp, ArrowRight, ArrowDown, ArrowLeft] = ARROW_KEYS;
const XHTML_NS = "http://www.w3.org/1999/xhtml";
+const SLIDER = {
+ hue: {
+ MIN: "0",
+ MAX: "128",
+ STEP: "1",
+ },
+ alpha: {
+ MIN: "0",
+ MAX: "1",
+ STEP: "0.01",
+ },
+};
/**
* Spectrum creates a color picker widget in any container you give it.
@@ -52,15 +62,22 @@ const XHTML_NS = "http://www.w3.org/1999/xhtml";
* Fires the following events:
* - changed : When the user changes the current color
*/
-class Spectrum extends ColorPickerCommon {
+class Spectrum {
constructor(parentEl, rgb) {
- const element = parentEl.ownerDocument.createElement("div");
+ EventEmitter.decorate(this);
+
+ this.document = parentEl.ownerDocument;
+ this.element = parentEl.ownerDocument.createElementNS(XHTML_NS, "div");
+ this.parentEl = parentEl;
+
+ this.element.className = "spectrum-container";
// eslint-disable-next-line no-unsanitized/property
- element.innerHTML = `
+ this.element.innerHTML = `
<section class="spectrum-color-picker">
<div class="spectrum-color spectrum-box"
tabindex="0"
role="slider"
+ title="${ColorPickerBundle.formatValueSync("colorpicker-tooltip-spectrum-dragger-title")}"
aria-describedby="spectrum-dragger">
<div class="spectrum-sat">
<div class="spectrum-val">
@@ -94,10 +111,20 @@ class Spectrum extends ColorPickerCommon {
</div>
</section>
`;
- super(element);
- EventEmitter.decorate(this);
- parentEl.appendChild(this.element);
+ this.onElementClick = this.onElementClick.bind(this);
+ this.element.addEventListener("click", this.onElementClick);
+
+ this.parentEl.appendChild(this.element);
+
+ // Color spectrum dragger.
+ this.dragger = this.element.querySelector(".spectrum-color");
+ this.dragHelper = this.element.querySelector(".spectrum-dragger");
+ draggable(this.dragger, this.dragHelper, this.onDraggerMove.bind(this));
+
+ // Here we define the components for the "controls" section of the color picker.
+ this.controls = this.element.querySelector(".spectrum-controls");
+ this.colorPreview = this.element.querySelector(".spectrum-color-preview");
// Create the eyedropper.
const eyedropper = this.document.createElementNS(XHTML_NS, "button");
@@ -110,6 +137,14 @@ class Spectrum extends ColorPickerCommon {
);
this.controls.insertBefore(eyedropper, this.colorPreview);
+ // Hue slider and alpha slider
+ this.hueSlider = this.createSlider("hue", this.onHueSliderMove.bind(this));
+ this.hueSlider.setAttribute("aria-describedby", this.dragHelper.id);
+ this.alphaSlider = this.createSlider(
+ "alpha",
+ this.onAlphaSliderMove.bind(this)
+ );
+
// Color contrast
this.spectrumContrast = this.element.querySelector(
".spectrum-color-contrast"
@@ -143,6 +178,11 @@ class Spectrum extends ColorPickerCommon {
: null;
}
+ /** @param {[number, number, number, number]} color */
+ set rgb([r, g, b, a]) {
+ this.hsv = [...InspectorUtils.rgbToHsv(r / 255, g / 255, b / 255), a];
+ }
+
set backgroundColorData(colorData) {
this._backgroundColorData = colorData;
}
@@ -155,11 +195,117 @@ class Spectrum extends ColorPickerCommon {
return this._textProps;
}
+ #toRgbInt(rgbFloat) {
+ return rgbFloat.map(c => Math.round(c * 255));
+ }
+
+ get rgbFloat() {
+ const [h, s, v, a] = this.hsv;
+ return [...InspectorUtils.hsvToRgb(h, s, v), a];
+ }
+
+ get rgb() {
+ const [r, g, b, a] = this.rgbFloat;
+ return [...this.#toRgbInt([r, g, b]), a];
+ }
+
+ /**
+ * Map current rgb to the closest color available in the database by
+ * calculating the delta-E between each available color and the current rgb
+ *
+ * @return {string}
+ * Color name or closest color name
+ */
+ get colorName() {
+ const [r, g, b] = this.rgbFloat;
+ const { exact, colorName } = InspectorUtils.rgbToNearestColorName(r, g, b);
+ return exact
+ ? colorName
+ : ColorPickerBundle.formatValueSync(
+ "colorpicker-tooltip-color-name-title",
+ { colorName }
+ );
+ }
+
+ get rgbNoSatVal() {
+ return [
+ ...this.#toRgbInt(InspectorUtils.hsvToRgb(this.hsv[0], 1, 1)),
+ this.hsv[3],
+ ];
+ }
+
+ get rgbCssString() {
+ const rgb = this.rgb;
+ return (
+ "rgba(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ", " + rgb[3] + ")"
+ );
+ }
+
+ show() {
+ this.dragWidth = this.dragger.offsetWidth;
+ this.dragHeight = this.dragger.offsetHeight;
+ this.dragHelperHeight = this.dragHelper.offsetHeight;
+
+ this.updateUI();
+ }
+
+ onElementClick(e) {
+ e.stopPropagation();
+ }
+
+ onHueSliderMove() {
+ this.hsv[0] = this.hueSlider.value / this.hueSlider.max;
+ this.updateUI();
+ this.onChange();
+ }
+
+ onDraggerMove(dragX, dragY) {
+ this.hsv[1] = dragX / this.dragWidth;
+ this.hsv[2] = (this.dragHeight - dragY) / this.dragHeight;
+ this.updateUI();
+ this.onChange();
+ }
+
+ onAlphaSliderMove() {
+ this.hsv[3] = this.alphaSlider.value / this.alphaSlider.max;
+ this.updateUI();
+ this.onChange();
+ }
+
onChange() {
this.emit("changed", this.rgb, this.rgbCssString);
}
/**
+ * Creates and initializes a slider element, attaches it to its parent container
+ * based on the slider type and returns it
+ *
+ * @param {string} sliderType
+ * The type of the slider (i.e. alpha or hue)
+ * @param {Function} onSliderMove
+ * The function to tie the slider to on input
+ * @return {DOMNode}
+ * Newly created slider
+ */
+ createSlider(sliderType, onSliderMove) {
+ const container = this.element.querySelector(`.spectrum-${sliderType}`);
+
+ const slider = this.document.createElementNS(XHTML_NS, "input");
+ slider.className = `spectrum-${sliderType}-input`;
+ slider.type = "range";
+ slider.min = SLIDER[sliderType].MIN;
+ slider.max = SLIDER[sliderType].MAX;
+ slider.step = SLIDER[sliderType].STEP;
+ slider.title = ColorPickerBundle.formatValueSync(
+ `colorpicker-tooltip-${sliderType}-slider-title`
+ );
+ slider.addEventListener("input", onSliderMove);
+
+ container.appendChild(slider);
+ return slider;
+ }
+
+ /**
* Updates the contrast label with appropriate content (i.e. large text indicator
* if the contrast is calculated for large text, or a base label otherwise)
*
@@ -228,6 +374,81 @@ class Spectrum extends ColorPickerCommon {
);
}
+ updateAlphaSlider() {
+ // Set alpha slider background
+ const rgb = this.rgb;
+
+ const rgbNoAlpha = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
+ const rgbAlpha0 = "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ", 0)";
+ const alphaGradient =
+ "linear-gradient(to right, " + rgbAlpha0 + ", " + rgbNoAlpha + ")";
+ this.alphaSlider.style.background = alphaGradient;
+ }
+
+ updateColorPreview() {
+ // Overlay the rgba color over a checkered image background.
+ this.colorPreview.style.setProperty("--overlay-color", this.rgbCssString);
+
+ // We should be able to distinguish the color preview on high luminance rgba values.
+ // Give the color preview a light grey border if the luminance of the current rgba
+ // tuple is great.
+ const colorLuminance = InspectorUtils.relativeLuminance(...this.rgbFloat);
+ this.colorPreview.classList.toggle("high-luminance", colorLuminance > 0.85);
+
+ // Set title on color preview for better UX
+ this.colorPreview.title = this.colorName;
+ }
+
+ updateDragger() {
+ // Set dragger background color
+ const flatColor =
+ "rgb(" +
+ this.rgbNoSatVal[0] +
+ ", " +
+ this.rgbNoSatVal[1] +
+ ", " +
+ this.rgbNoSatVal[2] +
+ ")";
+ this.dragger.style.backgroundColor = flatColor;
+
+ // Set dragger aria attributes
+ this.dragger.setAttribute("aria-valuetext", this.rgbCssString);
+ }
+
+ updateHueSlider() {
+ // Set hue slider aria attributes
+ this.hueSlider.setAttribute("aria-valuetext", this.rgbCssString);
+ }
+
+ updateHelperLocations() {
+ const h = this.hsv[0];
+ const s = this.hsv[1];
+ const v = this.hsv[2];
+
+ // Placing the color dragger
+ let dragX = s * this.dragWidth;
+ let dragY = this.dragHeight - v * this.dragHeight;
+ const helperDim = this.dragHelperHeight / 2;
+
+ dragX = Math.max(
+ -helperDim,
+ Math.min(this.dragWidth - helperDim, dragX - helperDim)
+ );
+ dragY = Math.max(
+ -helperDim,
+ Math.min(this.dragHeight - helperDim, dragY - helperDim)
+ );
+
+ this.dragHelper.style.top = dragY + "px";
+ this.dragHelper.style.left = dragX + "px";
+
+ // Placing the hue slider
+ this.hueSlider.value = h * this.hueSlider.max;
+
+ // Placing the alpha slider
+ this.alphaSlider.value = this.hsv[3] * this.alphaSlider.max;
+ }
+
/* Calculates the contrast ratio for the currently selected
* color against a single or range of background colors and displays contrast ratio section
* components depending on the contrast ratio calculated.
@@ -324,18 +545,128 @@ class Spectrum extends ColorPickerCommon {
}
updateUI() {
- super.updateUI();
+ this.updateHelperLocations();
+
+ this.updateColorPreview();
+ this.updateDragger();
+ this.updateHueSlider();
+ this.updateAlphaSlider();
this.updateContrast();
}
destroy() {
- super.destroy();
+ this.element.removeEventListener("click", this.onElementClick);
+ this.hueSlider.removeEventListener("input", this.onHueSliderMove);
+ this.alphaSlider.removeEventListener("input", this.onAlphaSliderMove);
+
+ this.parentEl.removeChild(this.element);
+
+ this.dragger = this.dragHelper = null;
+ this.alphaSlider = null;
+ this.hueSlider = null;
+ this.colorPreview = null;
+ this.element = null;
+ this.parentEl = null;
this.spectrumContrast = null;
this.contrastValue = this.contrastValueMin = this.contrastValueMax = null;
this.contrastLabel = null;
}
}
+function draggable(element, dragHelper, onmove) {
+ onmove = onmove || function () {};
+
+ const doc = element.ownerDocument;
+ let dragging = false;
+ let offset = {};
+ let maxHeight = 0;
+ let maxWidth = 0;
+
+ function setDraggerDimensionsAndOffset() {
+ maxHeight = element.offsetHeight;
+ maxWidth = element.offsetWidth;
+ offset = element.getBoundingClientRect();
+ }
+
+ function prevent(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ function move(e) {
+ if (dragging) {
+ if (e.buttons === 0) {
+ // The button is no longer pressed but we did not get a mouseup event.
+ stop();
+ return;
+ }
+ const pageX = e.pageX;
+ const pageY = e.pageY;
+
+ const dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth));
+ const dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight));
+
+ onmove.apply(element, [dragX, dragY]);
+ }
+ }
+
+ function start(e) {
+ const rightClick = e.which === 3;
+
+ if (!rightClick && !dragging) {
+ dragging = true;
+ setDraggerDimensionsAndOffset();
+
+ move(e);
+
+ doc.addEventListener("selectstart", prevent);
+ doc.addEventListener("dragstart", prevent);
+ doc.addEventListener("mousemove", move);
+ doc.addEventListener("mouseup", stop);
+
+ prevent(e);
+ }
+ }
+
+ function stop() {
+ if (dragging) {
+ doc.removeEventListener("selectstart", prevent);
+ doc.removeEventListener("dragstart", prevent);
+ doc.removeEventListener("mousemove", move);
+ doc.removeEventListener("mouseup", stop);
+ }
+ dragging = false;
+ }
+
+ function onKeydown(e) {
+ const { key } = e;
+
+ if (!ARROW_KEYS.includes(key)) {
+ return;
+ }
+
+ setDraggerDimensionsAndOffset();
+ const { offsetHeight, offsetTop, offsetLeft } = dragHelper;
+ let dragX = offsetLeft + offsetHeight / 2;
+ let dragY = offsetTop + offsetHeight / 2;
+
+ if (key === ArrowLeft && dragX > 0) {
+ dragX -= 1;
+ } else if (key === ArrowRight && dragX < maxWidth) {
+ dragX += 1;
+ } else if (key === ArrowUp && dragY > 0) {
+ dragY -= 1;
+ } else if (key === ArrowDown && dragY < maxHeight) {
+ dragY += 1;
+ }
+
+ onmove.apply(element, [dragX, dragY]);
+ }
+
+ element.addEventListener("mousedown", start);
+ element.addEventListener("keydown", onKeydown);
+}
+
/**
* Calculates the contrast ratio for a DOM node's computed style against
* a given background.
diff --git a/devtools/client/shared/widgets/spectrum.css b/devtools/client/shared/widgets/spectrum.css
@@ -2,8 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-@import "chrome://global/skin/colorpicker-common.css";
-
:root {
--accessibility-contrast-swatch-border-color: var(--grey-40);
--learn-more-underline: light-dark(var(--grey-30), var(--grey-50));
@@ -29,10 +27,45 @@
/* Mix-in classes */
+.spectrum-checker {
+ background-color: #eee;
+ background-image:
+ linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
+ linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
+ background-size: 12px 12px;
+ background-position:
+ 0 0,
+ 6px 6px;
+ /* Make sure that the background color is properly set in High Contrast Mode */
+ forced-color-adjust: none;
+}
+
+.spectrum-box {
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ border-radius: 2px;
+ background-clip: content-box;
+
+ :root[forced-colors-active] & {
+ border-color: initial;
+ }
+}
+
+/* Elements */
+
#spectrum-tooltip {
padding: 5px;
}
+/**
+ * Spectrum controls set the layout for the controls section of the color picker.
+ */
+.spectrum-controls {
+ display: flex;
+ justify-content: space-between;
+ margin-block-start: 10px;
+ margin-inline-end: 5px;
+}
+
.spectrum-controls {
width: 200px;
}
@@ -44,6 +77,147 @@
padding-block-end: 6px;
}
+/**
+ * This styles the color preview and adds a checkered background overlay inside of it. The overlay
+ * can be manipulated using the --overlay-color variable.
+ */
+.spectrum-color-preview {
+ --overlay-color: transparent;
+ border: 1px solid transparent;
+ border-radius: 50%;
+ width: 27px;
+ height: 27px;
+ background-color: #fff;
+ background-image:
+ linear-gradient(var(--overlay-color), var(--overlay-color)), linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%),
+ linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%);
+ background-size: 12px 12px;
+ background-position:
+ 0 0,
+ 6px 6px;
+ /* Make sure that the background color is properly set in High Contrast Mode */
+ forced-color-adjust: none;
+
+ :root[forced-colors-active] & {
+ border-color: CanvasText;
+ }
+}
+
+.spectrum-color-preview.high-luminance {
+ border-color: #ccc;
+}
+
+.spectrum-slider-container {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+ width: 130px;
+ margin-inline-start: 10px;
+ height: 30px;
+}
+
+/* Keep aspect ratio:
+http://www.briangrinstead.com/blog/keep-aspect-ratio-with-html-and-css */
+.spectrum-color-picker {
+ position: relative;
+ width: 205px;
+ height: 120px;
+ /* Make sure that the background color is properly set in High Contrast Mode */
+ forced-color-adjust: none;
+}
+
+.spectrum-color {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+}
+
+.spectrum-sat,
+.spectrum-val {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+}
+
+.spectrum-alpha {
+ margin-block-start: 3px;
+}
+
+.spectrum-alpha,
+.spectrum-hue {
+ position: relative;
+ height: 8px;
+}
+
+.spectrum-alpha-input,
+.spectrum-hue-input {
+ width: 100%;
+ margin: 0;
+ position: absolute;
+ height: 8px;
+ border-radius: 2px;
+ direction: initial;
+}
+
+.spectrum-hue-input,
+.spectrum-alpha-input {
+ outline-offset: 4px;
+}
+
+.spectrum-hue-input::-moz-range-thumb,
+.spectrum-alpha-input::-moz-range-thumb {
+ cursor: pointer;
+ height: 12px;
+ width: 12px;
+ box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
+ background: #fff;
+ border-radius: 50%;
+ opacity: 0.9;
+ border: none;
+}
+
+:root[forced-colors-active] :is(.spectrum-hue-input, .spectrum-alpha-input)::-moz-range-thumb {
+ background: ButtonFace;
+ border: 2px solid ButtonText;
+}
+
+:root[forced-colors-active] :is(.spectrum-hue-input, .spectrum-alpha-input):is(:hover, :focus-visible)::-moz-range-thumb {
+ border-color: SelectedItem;
+}
+
+.spectrum-hue-input::-moz-range-track {
+ border-radius: 2px;
+ height: 8px;
+ background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
+ /* Make sure that the background color is properly set in High Contrast Mode */
+ forced-color-adjust: none;
+}
+
+.spectrum-sat {
+ background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0));
+}
+
+.spectrum-val {
+ background-image: linear-gradient(to top, #000000, rgba(204, 154, 129, 0));
+}
+
+.spectrum-dragger {
+ user-select: none;
+ position: absolute;
+ top: 0;
+ left: 0;
+ cursor: pointer;
+ border-radius: 50%;
+ height: 8px;
+ width: 8px;
+ border: 1px solid white;
+ box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
+}
+
.spectrum-color-contrast {
padding-block-start: 8px;
padding-inline-start: 4px;
diff --git a/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js b/devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip.js
@@ -189,6 +189,9 @@ class SwatchColorPickerTooltip extends SwatchBasedEditorTooltip {
learnMoreButton.addEventListener("keydown", e => e.stopPropagation());
}
+ // Add focus to the first focusable element in the tooltip and attach keydown
+ // event listener to tooltip
+ this.focusableElements[0].focus();
this.tooltip.container.addEventListener(
"keydown",
this._onTooltipKeydown,
diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp
@@ -23,7 +23,6 @@
#include "mozilla/PresShell.h"
#include "mozilla/PresState.h"
#include "mozilla/ServoCSSParser.h"
-#include "mozilla/ServoComputedData.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_signon.h"
#include "mozilla/TextControlState.h"
@@ -804,16 +803,6 @@ nsresult HTMLInputElement::InitColorPicker() {
return NS_OK;
}
- // NOTE(krosylight): Android doesn't support HTML widgets. We can modify
- // GeckoView to handle MozOpenColorPicker and let it keep using its current
- // picker, but for now this is ok.
-#ifndef ANDROID
- if (StaticPrefs::dom_forms_html_color_picker_enabled()) {
- OpenColorPicker();
- return NS_OK;
- }
-#endif
-
// Get Loc title
nsAutoString title;
nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
@@ -2057,46 +2046,6 @@ Decimal HTMLInputElement::GetStepBase() const {
return kDefaultStepBase;
}
-void HTMLInputElement::GetColor(InputPickerColor& aValue) {
- MOZ_ASSERT(mType == FormControlType::InputColor,
- "getColor is only for type=color.");
-
- nsAutoString value;
- GetValue(value, CallerType::System);
-
- StyleAbsoluteColor color =
- MaybeComputeColor(OwnerDoc(), value).valueOr(StyleAbsoluteColor::BLACK);
- aValue.mComponent1 = color.components._0;
- aValue.mComponent2 = color.components._1;
- aValue.mComponent3 = color.components._2;
- // aValue.mAlpha = color.alpha;
- // aValue.mColorSpace = mColorSpace;
-}
-
-void HTMLInputElement::SetUserInputColor(const InputPickerColor& aValue) {
- MOZ_ASSERT(mType == FormControlType::InputColor,
- "setUserInputColor is only for type=color.");
-
- // TODO(krosylight): We should ultimately get a helper method where the compat
- // serialization happens only conditionally
- nsAutoString serialized;
- SerializeColorForHTMLCompatibility(
- StyleAbsoluteColor{
- .components =
- StyleColorComponents{
- ._0 = aValue.mComponent1,
- ._1 = aValue.mComponent2,
- ._2 = aValue.mComponent3,
- },
- .alpha = 1,
- .color_space = StyleColorSpace::Srgb,
- },
- serialized);
-
- // (We are either Chrome/UA but the principal doesn't matter for color inputs)
- SetUserInput(serialized, *NodePrincipal());
-}
-
Decimal HTMLInputElement::GetValueIfStepped(int32_t aStep,
StepCallerType aCallerType,
ErrorResult& aRv) {
@@ -2359,7 +2308,7 @@ void HTMLInputElement::OpenDateTimePicker(const DateTimeValue& aInitialValue) {
}
mDateTimeInputBoxValue = MakeUnique<DateTimeValue>(aInitialValue);
- nsContentUtils::DispatchChromeEvent(OwnerDoc(), this,
+ nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
u"MozOpenDateTimePicker"_ns,
CanBubble::eYes, Cancelable::eYes);
}
@@ -2369,7 +2318,7 @@ void HTMLInputElement::CloseDateTimePicker() {
return;
}
- nsContentUtils::DispatchChromeEvent(OwnerDoc(), this,
+ nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
u"MozCloseDateTimePicker"_ns,
CanBubble::eYes, Cancelable::eYes);
}
@@ -2378,16 +2327,6 @@ void HTMLInputElement::SetOpenState(bool aIsOpen) {
SetStates(ElementState::OPEN, aIsOpen);
}
-void HTMLInputElement::OpenColorPicker() {
- if (NS_WARN_IF(mType != FormControlType::InputColor)) {
- return;
- }
-
- nsContentUtils::DispatchChromeEvent(OwnerDoc(), this,
- u"MozOpenColorPicker"_ns, CanBubble::eYes,
- Cancelable::eYes);
-}
-
void HTMLInputElement::SetFocusState(bool aIsFocused) {
if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
return;
diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h
@@ -790,8 +790,6 @@ class HTMLInputElement final : public TextControlElement,
*/
void SetOpenState(bool aIsOpen);
- void OpenColorPicker();
-
/*
* Called from datetime input box binding when inner text fields are focused
* or blurred.
@@ -813,16 +811,6 @@ class HTMLInputElement final : public TextControlElement,
double GetMinimumAsDouble() { return GetMinimum().toDouble(); }
double GetMaximumAsDouble() { return GetMaximum().toDouble(); }
- /**
- * Return the current value as InputPickerColor.
- */
- void GetColor(InputPickerColor& aValue);
-
- /**
- * Converts the InputPickerColor into a string and set it as user input.
- */
- void SetUserInputColor(const InputPickerColor& aValue);
-
void StartNumberControlSpinnerSpin();
enum SpinnerStopState { eAllowDispatchingEvents, eDisallowDispatchingEvents };
void StopNumberControlSpinnerSpin(
diff --git a/dom/webidl/HTMLInputElement.webidl b/dom/webidl/HTMLInputElement.webidl
@@ -246,8 +246,6 @@ partial interface HTMLInputElement {
attribute boolean webkitdirectory;
};
-// Chrome-only functions for datetime picker
-
dictionary DateTimeValue {
long hour;
long minute;
@@ -290,23 +288,3 @@ partial interface HTMLInputElement {
[Func="IsChromeOrUAWidget", BinaryName="getStepBaseAsDouble"]
double getStepBase();
};
-
-// Chrome-only functions for color picker
-
-dictionary InputPickerColor {
- required float component1;
- required float component2;
- required float component3;
-
- // bug 1919718
- // required float alpha;
- // required InputColorSpace colorSpace;
-};
-
-partial interface HTMLInputElement {
- [Func="IsChromeOrUAWidget"]
- InputPickerColor getColor();
-
- [Func="IsChromeOrUAWidget"]
- undefined setUserInputColor(InputPickerColor aColor);
-};
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
@@ -3477,13 +3477,6 @@
value: @IS_ANDROID@
mirror: always
-#ifndef ANDROID
-- name: dom.forms.html_color_picker.enabled
- type: bool
- value: @IS_NIGHTLY_BUILD@
- mirror: always
-#endif
-
- name: dom.forms.always_allow_pointer_events.enabled
type: bool
value: true
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/color-picker-value.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/color-picker-value.html
@@ -1,29 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/resources/testdriver.js"></script>
-<script src="/resources/testdriver-vendor.js"></script>
-<input type="color" id="input" value="#ffffff">
-<script>
-promise_setup(async () => {
- await SpecialPowers.pushPrefEnv({
- set: [
- ["dom.forms.html_color_picker.enabled", true],
- // The pref sets `color(srgb 0.1 0.2 0.3)` on showPicker
- ["dom.forms.html_color_picker.testing", true],
- ]
- });
-});
-
-promise_test(async () => {
- await test_driver.bless();
- input.showPicker();
-
- const { promise, resolve } = Promise.withResolvers();
- input.addEventListener("change", resolve, { once: true });
- await promise;
-
- assert_equals(input.value, "#1a334d", "HTML compatibility serialization should happen");
-}, "Input picker value without alpha nor colorspace attributes");
-</script>
diff --git a/toolkit/actors/ColorPickerChild.sys.mjs b/toolkit/actors/ColorPickerChild.sys.mjs
@@ -1,56 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { InputPickerChildCommon } from "./InputPickerChildCommon.sys.mjs";
-
-export class ColorPickerChild extends InputPickerChildCommon {
- initialValue = null;
-
- constructor() {
- super("ColorPicker");
- }
-
- /**
- * Cleanup function called when picker is closed.
- *
- * @param {HTMLInputElement} inputElement
- */
- closeImpl(inputElement) {
- inputElement.setOpenState(false);
- if (this.initialValue !== inputElement.value) {
- inputElement.dispatchEvent(new inputElement.ownerGlobal.Event("change"));
- }
- }
-
- /**
- * Element updater function called when the picker value is changed.
- *
- * @param {ReceiveMessageArgument} aMessage
- * @param {HTMLInputElement} inputElement
- */
- pickerValueChangedImpl(aMessage, inputElement) {
- if (!aMessage.data) {
- inputElement.setUserInput(this.initialValue);
- return;
- }
-
- const { rgb } = aMessage.data;
- inputElement.setUserInputColor({
- component1: rgb[0],
- component2: rgb[1],
- component3: rgb[2],
- });
- }
-
- /**
- * Picker initialization function called when opening the picker
- *
- * @param {HTMLInputElement} inputElement
- * @returns An argument object to pass to the picker panel, or undefined to stop.
- */
- openPickerImpl(inputElement) {
- this.initialValue = inputElement.value;
- return { value: inputElement.getColor() };
- }
-}
diff --git a/toolkit/actors/ColorPickerParent.sys.mjs b/toolkit/actors/ColorPickerParent.sys.mjs
@@ -1,26 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { InputPickerParentCommon } from "./InputPickerParentCommon.sys.mjs";
-
-const lazy = {};
-ChromeUtils.defineESModuleGetters(lazy, {
- ColorPickerPanel: "moz-src:///toolkit/modules/ColorPickerPanel.sys.mjs",
-});
-
-export class ColorPickerParent extends InputPickerParentCommon {
- constructor() {
- super("ColorPicker");
- }
-
- /**
- * A picker creator function called when showing a picker
- *
- * @param {XULElement} panel A panel element
- * @returns A panel object that manages the element
- */
- createPickerImpl(panel) {
- return new lazy.ColorPickerPanel(panel);
- }
-}
diff --git a/toolkit/actors/DateTimePickerChild.sys.mjs b/toolkit/actors/DateTimePickerChild.sys.mjs
@@ -2,30 +2,77 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-import { InputPickerChildCommon } from "./InputPickerChildCommon.sys.mjs";
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ LayoutUtils: "resource://gre/modules/LayoutUtils.sys.mjs",
+});
-export class DateTimePickerChild extends InputPickerChildCommon {
+/**
+ * DateTimePickerChild is the communication channel between the input box
+ * (content) for date/time input types and its picker (chrome).
+ */
+export class DateTimePickerChild extends JSWindowActorChild {
+ /**
+ * On init, just listen for the event to open the picker, once the picker is
+ * opened, we'll listen for update and close events.
+ */
constructor() {
- super("DateTimePicker");
+ super();
+
+ this._inputElement = null;
}
/**
* Cleanup function called when picker is closed.
- *
- * @param {HTMLInputElement} inputElement
*/
- closeImpl(inputElement) {
- let dateTimeBoxElement = inputElement.dateTimeBoxElement;
+ close() {
+ this.removeListeners(this._inputElement);
+ let dateTimeBoxElement = this._inputElement.dateTimeBoxElement;
if (!dateTimeBoxElement) {
+ this._inputElement = null;
return;
}
// dateTimeBoxElement is within UA Widget Shadow DOM.
// An event dispatch to it can't be accessed by document.
- let win = inputElement.ownerGlobal;
+ let win = this._inputElement.ownerGlobal;
dateTimeBoxElement.dispatchEvent(
new win.CustomEvent("MozSetDateTimePickerState", { detail: false })
);
+
+ this._inputElement = null;
+ }
+
+ /**
+ * Called after picker is opened to start listening for input box update
+ * events.
+ */
+ addListeners(aElement) {
+ aElement.ownerGlobal.addEventListener("pagehide", this);
+ }
+
+ /**
+ * Stop listeneing for events when picker is closed.
+ */
+ removeListeners(aElement) {
+ aElement.ownerGlobal.removeEventListener("pagehide", this);
+ }
+
+ /**
+ * Helper function that returns the CSS direction property of the element.
+ */
+ getComputedDirection(aElement) {
+ return aElement.ownerGlobal
+ .getComputedStyle(aElement)
+ .getPropertyValue("direction");
+ }
+
+ /**
+ * Helper function that returns the rect of the element, which is the position
+ * relative to the left/top of the content area.
+ */
+ getBoundingContentRect(aElement) {
+ return lazy.LayoutUtils.getElementBoundingScreenRect(aElement);
}
getTimePickerPref() {
@@ -33,62 +80,119 @@ export class DateTimePickerChild extends InputPickerChildCommon {
}
/**
- * Element updater function called when the picker value is changed.
- *
- * @param {ReceiveMessageArgument} aMessage
- * @param {HTMLInputElement} inputElement
+ * nsIMessageListener.
*/
- pickerValueChangedImpl(aMessage, inputElement) {
- let dateTimeBoxElement = inputElement.dateTimeBoxElement;
- if (!dateTimeBoxElement) {
- return;
- }
+ receiveMessage(aMessage) {
+ switch (aMessage.name) {
+ case "FormDateTime:PickerClosed": {
+ if (!this._inputElement) {
+ return;
+ }
- let win = inputElement.ownerGlobal;
+ this.close();
+ break;
+ }
+ case "FormDateTime:PickerValueChanged": {
+ if (!this._inputElement) {
+ return;
+ }
- // dateTimeBoxElement is within UA Widget Shadow DOM.
- // An event dispatch to it can't be accessed by document.
- dateTimeBoxElement.dispatchEvent(
- new win.CustomEvent("MozPickerValueChanged", {
- detail: Cu.cloneInto(aMessage.data, win),
- })
- );
+ let dateTimeBoxElement = this._inputElement.dateTimeBoxElement;
+ if (!dateTimeBoxElement) {
+ return;
+ }
+
+ let win = this._inputElement.ownerGlobal;
+
+ // dateTimeBoxElement is within UA Widget Shadow DOM.
+ // An event dispatch to it can't be accessed by document.
+ dateTimeBoxElement.dispatchEvent(
+ new win.CustomEvent("MozPickerValueChanged", {
+ detail: Cu.cloneInto(aMessage.data, win),
+ })
+ );
+ break;
+ }
+ default:
+ break;
+ }
}
/**
- * Picker initialization function called when opening the picker
- *
- * @param {HTMLInputElement} inputElement
- * @returns An argument object to pass to the picker panel, or undefined to stop.
+ * nsIDOMEventListener, for chrome events sent by the input element and other
+ * DOM events.
*/
- openPickerImpl(inputElement) {
- // Time picker is disabled when preffed off
- if (inputElement.type == "time" && !this.getTimePickerPref()) {
- return undefined;
- }
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "MozOpenDateTimePicker": {
+ // Time picker is disabled when preffed off
+ if (
+ !aEvent.originalTarget.ownerGlobal.HTMLInputElement.isInstance(
+ aEvent.originalTarget
+ ) ||
+ (aEvent.originalTarget.type == "time" && !this.getTimePickerPref())
+ ) {
+ return;
+ }
- let dateTimeBoxElement = inputElement.dateTimeBoxElement;
- if (!dateTimeBoxElement) {
- throw new Error("How do we get this event without a UA Widget?");
- }
+ if (this._inputElement) {
+ // This happens when we're trying to open a picker when another picker
+ // is still open. We ignore this request to let the first picker
+ // close gracefully.
+ return;
+ }
- // dateTimeBoxElement is within UA Widget Shadow DOM.
- // An event dispatch to it can't be accessed by document, because
- // the event is not composed.
- let win = inputElement.ownerGlobal;
- dateTimeBoxElement.dispatchEvent(
- new win.CustomEvent("MozSetDateTimePickerState", { detail: true })
- );
+ this._inputElement = aEvent.originalTarget;
+
+ let dateTimeBoxElement = this._inputElement.dateTimeBoxElement;
+ if (!dateTimeBoxElement) {
+ throw new Error("How do we get this event without a UA Widget?");
+ }
+
+ // dateTimeBoxElement is within UA Widget Shadow DOM.
+ // An event dispatch to it can't be accessed by document, because
+ // the event is not composed.
+ let win = this._inputElement.ownerGlobal;
+ dateTimeBoxElement.dispatchEvent(
+ new win.CustomEvent("MozSetDateTimePickerState", { detail: true })
+ );
- let value = inputElement.getDateTimeInputBoxValue();
- return {
- // Pass partial value if it's available, otherwise pass input
- // element's value.
- value: Object.keys(value).length ? value : inputElement.value,
- min: inputElement.getMinimum(),
- max: inputElement.getMaximum(),
- step: inputElement.getStep(),
- stepBase: inputElement.getStepBase(),
- };
+ this.addListeners(this._inputElement);
+
+ let value = this._inputElement.getDateTimeInputBoxValue();
+ this.sendAsyncMessage("FormDateTime:OpenPicker", {
+ rect: this.getBoundingContentRect(this._inputElement),
+ dir: this.getComputedDirection(this._inputElement),
+ type: this._inputElement.type,
+ detail: {
+ // Pass partial value if it's available, otherwise pass input
+ // element's value.
+ value: Object.keys(value).length ? value : this._inputElement.value,
+ min: this._inputElement.getMinimum(),
+ max: this._inputElement.getMaximum(),
+ step: this._inputElement.getStep(),
+ stepBase: this._inputElement.getStepBase(),
+ },
+ });
+ break;
+ }
+ case "MozCloseDateTimePicker": {
+ this.sendAsyncMessage("FormDateTime:ClosePicker", {});
+ this.close();
+ break;
+ }
+ case "pagehide": {
+ if (
+ this._inputElement &&
+ this._inputElement.ownerDocument == aEvent.target
+ ) {
+ this.sendAsyncMessage("FormDateTime:ClosePicker", {});
+ this.close();
+ }
+ break;
+ }
+ default:
+ break;
+ }
}
}
diff --git a/toolkit/actors/DateTimePickerParent.sys.mjs b/toolkit/actors/DateTimePickerParent.sys.mjs
@@ -2,25 +2,141 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-import { InputPickerParentCommon } from "./InputPickerParentCommon.sys.mjs";
+const DEBUG = false;
+function debug(aStr) {
+ if (DEBUG) {
+ dump("-*- DateTimePickerParent: " + aStr + "\n");
+ }
+}
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
- DateTimePickerPanel: "moz-src:///toolkit/modules/DateTimePickerPanel.sys.mjs",
+ DateTimePickerPanel: "resource://gre/modules/DateTimePickerPanel.sys.mjs",
});
-export class DateTimePickerParent extends InputPickerParentCommon {
- constructor() {
- super("DateTimePicker");
+/*
+ * DateTimePickerParent receives message from content side (input box) and
+ * is reposible for opening, closing and updating the picker. Similarly,
+ * DateTimePickerParent listens for picker's events and notifies the content
+ * side (input box) about them.
+ */
+export class DateTimePickerParent extends JSWindowActorParent {
+ receiveMessage(aMessage) {
+ debug("receiveMessage: " + aMessage.name);
+ switch (aMessage.name) {
+ case "FormDateTime:OpenPicker": {
+ this.showPicker(aMessage.data);
+ break;
+ }
+ case "FormDateTime:ClosePicker": {
+ if (!this._picker) {
+ return;
+ }
+ this.close();
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ handleEvent(aEvent) {
+ debug("handleEvent: " + aEvent.type);
+ switch (aEvent.type) {
+ case "DateTimePickerValueCleared": {
+ this.sendAsyncMessage("FormDateTime:PickerValueChanged", null);
+ break;
+ }
+ case "DateTimePickerValueChanged": {
+ this.sendAsyncMessage("FormDateTime:PickerValueChanged", aEvent.detail);
+ break;
+ }
+ case "popuphidden": {
+ this.sendAsyncMessage("FormDateTime:PickerClosed", {});
+ this.close();
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ // Get picker from browser and show it anchored to the input box.
+ showPicker(aData) {
+ let rect = aData.rect;
+ let type = aData.type;
+ let detail = aData.detail;
+
+ debug("Opening picker with details: " + JSON.stringify(detail));
+ let topBC = this.browsingContext.top;
+ let window = topBC.topChromeWindow;
+ if (Services.focus.activeWindow != window) {
+ debug("Not in the active window");
+ return;
+ }
+
+ {
+ let browser = topBC.embedderElement;
+ if (
+ browser &&
+ browser.ownerGlobal.gBrowser &&
+ browser.ownerGlobal.gBrowser.selectedBrowser != browser
+ ) {
+ debug("In background tab");
+ return;
+ }
+ }
+
+ this._cleanupPicker();
+ let doc = window.document;
+ let panel = doc.getElementById("DateTimePickerPanel");
+ if (!panel) {
+ panel = doc.createXULElement("panel");
+ panel.id = "DateTimePickerPanel";
+ panel.setAttribute("type", "arrow");
+ panel.setAttribute("orient", "vertical");
+ panel.setAttribute("ignorekeys", "true");
+ panel.setAttribute("noautofocus", "true");
+ // This ensures that clicks on the anchored input box are never consumed.
+ panel.setAttribute("consumeoutsideclicks", "never");
+ panel.setAttribute("level", "parent");
+ panel.setAttribute("tabspecific", "true");
+ let container =
+ doc.getElementById("mainPopupSet") ||
+ doc.querySelector("popupset") ||
+ doc.documentElement.appendChild(doc.createXULElement("popupset"));
+ container.appendChild(panel);
+ }
+ this._oldFocus = doc.activeElement;
+ this._picker = new lazy.DateTimePickerPanel(panel);
+ this._picker.openPicker(type, rect, detail);
+ this._picker.element.addEventListener("popuphidden", this);
+ this._picker.element.addEventListener("DateTimePickerValueChanged", this);
+ this._picker.element.addEventListener("DateTimePickerValueCleared", this);
+ }
+
+ _cleanupPicker() {
+ if (!this._picker) {
+ return;
+ }
+ this._picker.closePicker();
+ this._picker.element.removeEventListener("popuphidden", this);
+ this._picker.element.removeEventListener(
+ "DateTimePickerValueChanged",
+ this
+ );
+ this._picker.element.removeEventListener(
+ "DateTimePickerValueCleared",
+ this
+ );
+ this._picker = null;
}
- /**
- * A picker creator function called when showing a picker
- *
- * @param {XULElement} panel A panel element
- * @returns A panel object that manages the element
- */
- createPickerImpl(panel) {
- return new lazy.DateTimePickerPanel(panel);
+ // Close the picker and do some cleanup.
+ close() {
+ this._cleanupPicker();
+ // Restore focus to where it was before the picker opened.
+ this._oldFocus?.focus();
+ this._oldFocus = null;
}
}
diff --git a/toolkit/actors/InputPickerChildCommon.sys.mjs b/toolkit/actors/InputPickerChildCommon.sys.mjs
@@ -1,188 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-const lazy = {};
-ChromeUtils.defineESModuleGetters(lazy, {
- LayoutUtils: "resource://gre/modules/LayoutUtils.sys.mjs",
-});
-
-/**
- * InputPickerChildCommon is the communication channel between the input box
- * (content) for each input types and its picker (chrome).
- */
-export class InputPickerChildCommon extends JSWindowActorChild {
- /** @type {HTMLInputElement} */
- #inputElement = null;
- #inputType = "";
- #namespace;
- /** @type {AbortController} */
- #abortController;
-
- /**
- * On init, just listen for the event to open the picker, once the picker is
- * opened, we'll listen for update and close events.
- *
- * @param {string} namespace Affects the event names, e.g. Foo makes it
- * accept FooValueChanged event.
- * Align it with ActorManagerParent declaration.
- */
- constructor(namespace) {
- super();
- this.#namespace = namespace;
- }
-
- /**
- * Cleanup function called when picker is closed.
- */
- close() {
- this.#abortController.abort();
- this.closeImpl(this.#inputElement);
- this.#inputElement = null;
- this.#inputType = "";
- }
-
- /**
- * @param {HTMLInputElement} _inputElement
- */
- closeImpl(_inputElement) {
- throw new Error("Not implemented");
- }
-
- /**
- * Called after picker is opened to start listening for input box update
- * events.
- */
- addListeners(aElement) {
- this.#abortController = new AbortController();
- aElement.ownerGlobal.addEventListener("pagehide", this, {
- signal: this.#abortController.signal,
- });
- }
-
- /**
- * Helper function that returns the CSS direction property of the element.
- */
- getComputedDirection(aElement) {
- return aElement.ownerGlobal
- .getComputedStyle(aElement)
- .getPropertyValue("direction");
- }
-
- /**
- * Helper function that returns the rect of the element, which is the position
- * relative to the left/top of the content area.
- */
- getBoundingContentRect(aElement) {
- return lazy.LayoutUtils.getElementBoundingScreenRect(aElement);
- }
-
- /**
- * MessageListener
- */
- receiveMessage(aMessage) {
- if (!this.#inputElement || this.#inputElement.type !== this.#inputType) {
- // Either we are already closed by content or the input type is changed
- return;
- }
- switch (aMessage.name) {
- case "InputPicker:Closed": {
- this.close();
- break;
- }
- case "InputPicker:ValueChanged": {
- this.pickerValueChangedImpl(aMessage, this.#inputElement);
- break;
- }
- }
- }
-
- /**
- * Element updater function called when the picker value is changed.
- *
- * @param {ReceiveMessageArgument} _aMessage
- * @param {HTMLInputElement} _inputElement
- */
- pickerValueChangedImpl(_aMessage, _inputElement) {
- throw new Error("Not implemented");
- }
-
- /**
- * nsIDOMEventListener, for chrome events sent by the input element and other
- * DOM events.
- */
- handleEvent(aEvent) {
- switch (aEvent.type) {
- case `MozOpen${this.#namespace}`: {
- if (
- !aEvent.originalTarget.ownerGlobal.HTMLInputElement.isInstance(
- aEvent.originalTarget
- )
- ) {
- return;
- }
-
- if (this.#inputElement) {
- // This happens when we're trying to open a picker when another picker
- // is still open. We ignore this request to let the first picker
- // close gracefully.
- return;
- }
-
- /** @type {HTMLInputElement} */
- const inputElement = aEvent.originalTarget;
- const openPickerDetail = this.openPickerImpl(inputElement);
- if (!openPickerDetail) {
- // The impl doesn't want to proceed in this case
- return;
- }
-
- this.#inputElement = inputElement;
- this.#inputType = inputElement.type;
- this.addListeners(inputElement);
-
- this.sendAsyncMessage(`InputPicker:Open`, {
- rect: this.getBoundingContentRect(inputElement),
- dir: this.getComputedDirection(inputElement),
- type: inputElement.type,
- detail: openPickerDetail,
- });
- break;
- }
- case `MozClose${this.#namespace}`: {
- this.sendAsyncMessage(`InputPicker:Close`, {});
- this.close();
- break;
- }
- case "pagehide": {
- if (this.#inputElement?.ownerDocument == aEvent.target) {
- this.sendAsyncMessage(`InputPicker:Close`, {});
- this.close();
- }
- break;
- }
- default:
- break;
- }
- }
-
- /**
- * Picker initialization function called when opening the picker
- *
- * @param {HTMLInputElement} _inputElement
- * @returns An argument object to pass to the picker, or undefined to stop opening one.
- */
- openPickerImpl(_inputElement) {
- throw new Error("Not implemented");
- }
-
- /**
- * Picker updater function when the input value is updated
- *
- * @param {HTMLInputElement} _inputElement
- * @returns An argument object to pass to the picker
- */
- updatePickerImpl(_inputElement) {
- throw new Error("Not implemented");
- }
-}
diff --git a/toolkit/actors/InputPickerParentCommon.sys.mjs b/toolkit/actors/InputPickerParentCommon.sys.mjs
@@ -1,170 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-const DEBUG = false;
-function debug(aStr) {
- if (DEBUG) {
- dump("-*- InputPickerParent: " + aStr + "\n");
- }
-}
-
-/*
- * InputPickerParentCommon receives message from content side (input box) and
- * is reposible for opening, closing and updating the picker. Similarly,
- * InputPickerParentCommon listens for picker's events and notifies the content
- * side (input box) about them.
- */
-export class InputPickerParentCommon extends JSWindowActorParent {
- #namespace;
- #picker;
- /** @type {Element | undefined} */
- #oldFocus;
- /** @type {AbortController} */
- #abortController;
-
- /**
- * @param {string} namespace Affects the input panel id, mostly to prevent
- * accidental mis-pairing of wrong panel and actor.
- */
- constructor(namespace) {
- super();
- this.#namespace = namespace;
- }
-
- receiveMessage(aMessage) {
- debug("receiveMessage: " + aMessage.name);
- switch (aMessage.name) {
- case `InputPicker:Open`: {
- this.showPicker(aMessage.data);
- break;
- }
- case `InputPicker:Close`: {
- if (!this.#picker) {
- return;
- }
- this.close();
- break;
- }
- default:
- break;
- }
- }
-
- handleEvent(aEvent) {
- debug("handleEvent: " + aEvent.type);
- switch (aEvent.type) {
- case "InputPickerValueCleared": {
- this.sendAsyncMessage("InputPicker:ValueChanged", null);
- break;
- }
- case "InputPickerValueChanged": {
- this.sendAsyncMessage("InputPicker:ValueChanged", aEvent.detail);
- break;
- }
- case "popuphidden": {
- this.sendAsyncMessage(`InputPicker:Closed`, {});
- this.close();
- break;
- }
- default:
- break;
- }
- }
-
- /**
- * A panel creator function called when showing a picker
- *
- * @param {XULElement} _panel A panel element
- * @returns A panel object that manages the element
- */
- createPickerImpl(_panel) {
- throw new Error("Not implemented");
- }
-
- // Get picker from browser and show it anchored to the input box.
- showPicker(aData) {
- let rect = aData.rect;
- let type = aData.type;
- let detail = aData.detail;
-
- debug("Opening picker with details: " + JSON.stringify(detail));
- let topBC = this.browsingContext.top;
- let window = topBC.topChromeWindow;
- if (Services.focus.activeWindow != window) {
- debug("Not in the active window");
- return;
- }
-
- {
- let browser = topBC.embedderElement;
- if (
- browser &&
- browser.ownerGlobal.gBrowser &&
- browser.ownerGlobal.gBrowser.selectedBrowser != browser
- ) {
- debug("In background tab");
- return;
- }
- }
-
- this.#cleanupPicker();
- let doc = window.document;
- const id = `${this.#namespace}Panel`;
- let panel = doc.getElementById(id);
- if (!panel) {
- panel = doc.createXULElement("panel");
- panel.id = id;
- panel.setAttribute("type", "arrow");
- panel.setAttribute("orient", "vertical");
- panel.setAttribute("ignorekeys", "true");
- panel.setAttribute("noautofocus", "true");
- // This ensures that clicks on the anchored input box are never consumed.
- panel.setAttribute("consumeoutsideclicks", "never");
- panel.setAttribute("level", "parent");
- panel.setAttribute("tabspecific", "true");
- let container =
- doc.getElementById("mainPopupSet") ||
- doc.querySelector("popupset") ||
- doc.documentElement.appendChild(doc.createXULElement("popupset"));
- container.appendChild(panel);
- }
- this.#oldFocus = doc.activeElement;
- this.#picker = this.createPickerImpl(panel);
- this.#picker.openPicker(type, rect, detail);
- this.addPickerListeners(panel);
- }
-
- #cleanupPicker() {
- if (!this.#picker) {
- return;
- }
- this.#picker.closePicker();
- this.#abortController.abort();
- this.#picker = null;
- }
-
- // Close the picker and do some cleanup.
- close() {
- this.#cleanupPicker();
- // Restore focus to where it was before the picker opened.
- this.#oldFocus?.focus();
- this.#oldFocus = null;
- }
-
- // Listen to picker's event.
- addPickerListeners(panel) {
- if (!this.#picker) {
- return;
- }
- this.#abortController = new AbortController();
- const { signal } = this.#abortController;
- panel.addEventListener("popuphidden", this, { signal });
- panel.addEventListener("InputPickerValueChanged", this, {
- signal,
- });
- panel.addEventListener("InputPickerValueCleared", this, {
- signal,
- });
- }
-}
diff --git a/toolkit/actors/moz.build b/toolkit/actors/moz.build
@@ -48,6 +48,8 @@ FINAL_TARGET_FILES.actors += [
"ContentMetaParent.sys.mjs",
"ControllersChild.sys.mjs",
"ControllersParent.sys.mjs",
+ "DateTimePickerChild.sys.mjs",
+ "DateTimePickerParent.sys.mjs",
"ExtFindChild.sys.mjs",
"FindBarChild.sys.mjs",
"FindBarParent.sys.mjs",
@@ -81,12 +83,3 @@ FINAL_TARGET_FILES.actors += [
"WebChannelChild.sys.mjs",
"WebChannelParent.sys.mjs",
]
-
-MOZ_SRC_FILES += [
- "ColorPickerChild.sys.mjs",
- "ColorPickerParent.sys.mjs",
- "DateTimePickerChild.sys.mjs",
- "DateTimePickerParent.sys.mjs",
- "InputPickerChildCommon.sys.mjs",
- "InputPickerParentCommon.sys.mjs",
-]
diff --git a/toolkit/content/colorpicker.html b/toolkit/content/colorpicker.html
@@ -1,32 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at https://mozilla.org/MPL/2.0/. -->
-<!doctype html>
-<meta http-equiv="Content-Security-Policy" content="default-src chrome:" />
-<link rel="stylesheet" href="chrome://global/skin/colorpicker.css" />
-<script
- src="chrome://global/content/bindings/colorpicker.mjs"
- type="module"
-></script>
-<section class="spectrum-color-picker">
- <div
- class="spectrum-color spectrum-box"
- tabindex="0"
- role="slider"
- aria-describedby="spectrum-dragger"
- >
- <div class="spectrum-sat">
- <div class="spectrum-val">
- <div class="spectrum-dragger" id="spectrum-dragger"></div>
- </div>
- </div>
- </div>
-</section>
-<section class="spectrum-controls">
- <div class="spectrum-color-preview"></div>
- <div class="spectrum-slider-container">
- <div class="spectrum-hue spectrum-box"></div>
- <!-- Unhide alpha in bug 1919718 -->
- <div class="spectrum-alpha spectrum-checker spectrum-box" hidden></div>
- </div>
-</section>
diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn
@@ -50,7 +50,6 @@ toolkit.jar:
content/global/aboutUrlClassifier.css
* content/global/buildconfig.html
content/global/buildconfig.css
- content/global/colorpicker.html
content/global/contentAreaUtils.js
#ifndef MOZ_FENNEC
content/global/editMenuOverlay.js
@@ -78,8 +77,6 @@ toolkit.jar:
#endif
content/global/widgets.css
content/global/bindings/calendar.js (widgets/calendar.js)
- content/global/bindings/colorpicker-common.mjs (widgets/colorpicker-common.mjs)
- content/global/bindings/colorpicker.mjs (widgets/colorpicker.mjs)
content/global/bindings/datekeeper.js (widgets/datekeeper.js)
content/global/bindings/datepicker.js (widgets/datepicker.js)
content/global/bindings/datetimebox.css (widgets/datetimebox.css)
diff --git a/toolkit/content/widgets/colorpicker-common.mjs b/toolkit/content/widgets/colorpicker-common.mjs
@@ -1,371 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-// @ts-nocheck Do this after migration from devtools
-
-const lazy = {};
-ChromeUtils.defineLazyGetter(lazy, "l10n", function () {
- return new Localization(["devtools/client/inspector.ftl"], true);
-});
-
-const ARROW_KEYS = ["ArrowUp", "ArrowRight", "ArrowDown", "ArrowLeft"];
-const [ArrowUp, ArrowRight, ArrowDown, ArrowLeft] = ARROW_KEYS;
-const SLIDER = {
- hue: {
- MIN: "0",
- MAX: "128",
- STEP: "1",
- },
- alpha: {
- MIN: "0",
- MAX: "1",
- STEP: "0.01",
- },
-};
-
-/**
- * ColorPickerCommon creates a color picker widget in a container you give it.
- */
-export class ColorPickerCommon {
- constructor(element) {
- this.document = element.ownerDocument;
- this.element = element;
-
- this.element.className = "spectrum-container";
-
- this.onElementClick = this.onElementClick.bind(this);
- this.element.addEventListener("click", this.onElementClick);
-
- // Color spectrum dragger.
- this.dragger = this.element.querySelector(".spectrum-color");
- this.dragger.title = lazy.l10n.formatValueSync(
- "colorpicker-tooltip-spectrum-dragger-title"
- );
-
- this.dragHelper = this.element.querySelector(".spectrum-dragger");
- draggable(this.dragger, this.dragHelper, this.onDraggerMove.bind(this));
-
- // Here we define the components for the "controls" section of the color picker.
- this.controls = this.element.querySelector(".spectrum-controls");
- this.colorPreview = this.element.querySelector(".spectrum-color-preview");
-
- // Hue slider and alpha slider
- this.hueSlider = this.createSlider("hue", this.onHueSliderMove.bind(this));
- this.hueSlider.setAttribute("aria-describedby", this.dragHelper.id);
- this.alphaSlider = this.createSlider(
- "alpha",
- this.onAlphaSliderMove.bind(this)
- );
- }
-
- /** @param {[number, number, number, number]} color */
- set rgb([r, g, b, a]) {
- this.rgbFloat = [r / 255, g / 255, b / 255, a];
- }
-
- /** @param {[number, number, number, number]} color */
- set rgbFloat([r, g, b, a]) {
- this.hsv = [...InspectorUtils.rgbToHsv(r, g, b), a];
- }
-
- #toRgbInt(rgbFloat) {
- return rgbFloat.map(c => Math.round(c * 255));
- }
-
- get rgbFloat() {
- const [h, s, v, a] = this.hsv;
- return [...InspectorUtils.hsvToRgb(h, s, v), a];
- }
-
- get rgb() {
- const [r, g, b, a] = this.rgbFloat;
- return [...this.#toRgbInt([r, g, b]), a];
- }
-
- /**
- * Map current rgb to the closest color available in the database by
- * calculating the delta-E between each available color and the current rgb
- *
- * @return {string}
- * Color name or closest color name
- */
- get colorName() {
- const [r, g, b] = this.rgbFloat;
- const { exact, colorName } = InspectorUtils.rgbToNearestColorName(r, g, b);
- return exact
- ? colorName
- : lazy.l10n.formatValueSync("colorpicker-tooltip-color-name-title", {
- colorName,
- });
- }
-
- get rgbNoSatVal() {
- return [
- ...this.#toRgbInt(InspectorUtils.hsvToRgb(this.hsv[0], 1, 1)),
- this.hsv[3],
- ];
- }
-
- get rgbCssString() {
- const rgb = this.rgb;
- return (
- "rgba(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ", " + rgb[3] + ")"
- );
- }
-
- show() {
- this.dragWidth = this.dragger.offsetWidth;
- this.dragHeight = this.dragger.offsetHeight;
- this.dragHelperHeight = this.dragHelper.offsetHeight;
- this.dragger.focus({ focusVisible: false });
-
- this.updateUI();
- }
-
- onElementClick(e) {
- e.stopPropagation();
- }
-
- onHueSliderMove() {
- this.hsv[0] = this.hueSlider.value / this.hueSlider.max;
- this.updateUI();
- this.onChange();
- }
-
- onDraggerMove(dragX, dragY) {
- this.hsv[1] = dragX / this.dragWidth;
- this.hsv[2] = (this.dragHeight - dragY) / this.dragHeight;
- this.updateUI();
- this.onChange();
- }
-
- onAlphaSliderMove() {
- this.hsv[3] = this.alphaSlider.value / this.alphaSlider.max;
- this.updateUI();
- this.onChange();
- }
-
- onChange() {
- throw new Error("Not implemented");
- }
-
- /**
- * Creates and initializes a slider element, attaches it to its parent container
- * based on the slider type and returns it
- *
- * @param {"alpha" | "hue"} sliderType
- * The type of the slider (i.e. alpha or hue)
- * @param {Function} onSliderMove
- * The function to tie the slider to on input
- * @return {HTMLInputElement}
- * Newly created slider
- */
- createSlider(sliderType, onSliderMove) {
- const container = this.element.querySelector(`.spectrum-${sliderType}`);
-
- const slider = this.document.createElement("input");
- slider.className = `spectrum-${sliderType}-input`;
- slider.type = "range";
- slider.min = SLIDER[sliderType].MIN;
- slider.max = SLIDER[sliderType].MAX;
- slider.step = SLIDER[sliderType].STEP;
- slider.title = lazy.l10n.formatValueSync(
- `colorpicker-tooltip-${sliderType}-slider-title`
- );
- slider.addEventListener("input", onSliderMove);
-
- container.appendChild(slider);
- return slider;
- }
-
- updateAlphaSlider() {
- // Set alpha slider background
- const rgb = this.rgb;
-
- const rgbNoAlpha = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
- const rgbAlpha0 = "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ", 0)";
- const alphaGradient =
- "linear-gradient(to right, " + rgbAlpha0 + ", " + rgbNoAlpha + ")";
- this.alphaSlider.style.background = alphaGradient;
- }
-
- updateColorPreview() {
- // Overlay the rgba color over a checkered image background.
- this.colorPreview.style.setProperty("--overlay-color", this.rgbCssString);
-
- // We should be able to distinguish the color preview on high luminance rgba values.
- // Give the color preview a light grey border if the luminance of the current rgba
- // tuple is great.
- const colorLuminance = InspectorUtils.relativeLuminance(...this.rgbFloat);
- this.colorPreview.classList.toggle("high-luminance", colorLuminance > 0.85);
-
- // Set title on color preview for better UX
- this.colorPreview.title = this.colorName;
- }
-
- updateDragger() {
- // Set dragger background color
- const flatColor =
- "rgb(" +
- this.rgbNoSatVal[0] +
- ", " +
- this.rgbNoSatVal[1] +
- ", " +
- this.rgbNoSatVal[2] +
- ")";
- this.dragger.style.backgroundColor = flatColor;
-
- // Set dragger aria attributes
- this.dragger.setAttribute("aria-valuetext", this.rgbCssString);
- }
-
- updateHueSlider() {
- // Set hue slider aria attributes
- this.hueSlider.setAttribute("aria-valuetext", this.rgbCssString);
- }
-
- updateHelperLocations() {
- const h = this.hsv[0];
- const s = this.hsv[1];
- const v = this.hsv[2];
-
- // Placing the color dragger
- let dragX = s * this.dragWidth;
- let dragY = this.dragHeight - v * this.dragHeight;
- const helperDim = this.dragHelperHeight / 2;
-
- dragX = Math.max(
- -helperDim,
- Math.min(this.dragWidth - helperDim, dragX - helperDim)
- );
- dragY = Math.max(
- -helperDim,
- Math.min(this.dragHeight - helperDim, dragY - helperDim)
- );
-
- this.dragHelper.style.top = dragY + "px";
- this.dragHelper.style.left = dragX + "px";
-
- // Placing the hue slider
- this.hueSlider.value = h * this.hueSlider.max;
-
- // Placing the alpha slider
- this.alphaSlider.value = this.hsv[3] * this.alphaSlider.max;
- }
-
- updateUI() {
- this.updateHelperLocations();
-
- this.updateColorPreview();
- this.updateDragger();
- this.updateHueSlider();
- this.updateAlphaSlider();
- }
-
- destroy() {
- this.element.removeEventListener("click", this.onElementClick);
- this.hueSlider.removeEventListener("input", this.onHueSliderMove);
- this.alphaSlider.removeEventListener("input", this.onAlphaSliderMove);
-
- this.element.remove();
-
- this.dragger = this.dragHelper = null;
- this.alphaSlider = null;
- this.hueSlider = null;
- this.colorPreview = null;
- this.element = null;
- }
-}
-
-function draggable(element, dragHelper, onmove) {
- const doc = element.ownerDocument;
- let dragging = false;
- let offset = {};
- let maxHeight = 0;
- let maxWidth = 0;
-
- function setDraggerDimensionsAndOffset() {
- maxHeight = element.offsetHeight;
- maxWidth = element.offsetWidth;
- offset = element.getBoundingClientRect();
- }
-
- function prevent(e) {
- e.stopPropagation();
- e.preventDefault();
- }
-
- function move(e) {
- if (dragging) {
- if (e.buttons === 0) {
- // The button is no longer pressed but we did not get a pointerup event.
- stop();
- return;
- }
- const pageX = e.pageX;
- const pageY = e.pageY;
-
- const dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth));
- const dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight));
-
- onmove.apply(element, [dragX, dragY]);
- }
- }
-
- function start(e) {
- const rightClick = e.which === 3;
-
- if (!rightClick && !dragging) {
- dragging = true;
- setDraggerDimensionsAndOffset();
-
- move(e);
-
- doc.addEventListener("selectstart", prevent);
- doc.addEventListener("dragstart", prevent);
- doc.addEventListener("mousemove", move);
- doc.addEventListener("mouseup", stop);
-
- prevent(e);
- }
- }
-
- function stop() {
- if (dragging) {
- doc.removeEventListener("selectstart", prevent);
- doc.removeEventListener("dragstart", prevent);
- doc.removeEventListener("mousemove", move);
- doc.removeEventListener("mouseup", stop);
- }
- dragging = false;
- }
-
- function onKeydown(e) {
- const { key } = e;
-
- if (!ARROW_KEYS.includes(key)) {
- return;
- }
-
- setDraggerDimensionsAndOffset();
- const { offsetHeight, offsetTop, offsetLeft } = dragHelper;
- let dragX = offsetLeft + offsetHeight / 2;
- let dragY = offsetTop + offsetHeight / 2;
-
- if (key === ArrowLeft && dragX > 0) {
- dragX -= 1;
- } else if (key === ArrowRight && dragX < maxWidth) {
- dragX += 1;
- } else if (key === ArrowUp && dragY > 0) {
- dragY -= 1;
- } else if (key === ArrowDown && dragY < maxHeight) {
- dragY += 1;
- }
-
- onmove.apply(element, [dragX, dragY]);
- }
-
- element.addEventListener("mousedown", start);
- element.addEventListener("keydown", onKeydown);
-}
diff --git a/toolkit/content/widgets/colorpicker.mjs b/toolkit/content/widgets/colorpicker.mjs
@@ -1,47 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-// @ts-nocheck Do this after migration from devtools
-
-import { ColorPickerCommon } from "./colorpicker-common.mjs";
-
-class ColorPicker extends ColorPickerCommon {
- onChange() {
- window.postMessage(
- {
- name: "PickerPopupChanged",
- detail: { rgb: this.rgbFloat },
- },
- "*"
- );
- }
-}
-
-let picker = new ColorPicker(document.body);
-window.addEventListener("message", ev => {
- switch (ev.data.name) {
- case "PickerInit": {
- let { value } = ev.data.detail;
- picker.rgbFloat = [
- value.component1,
- value.component2,
- value.component3,
- 1,
- ];
- picker.show();
- }
- }
-});
-
-window.addEventListener("keydown", ev => {
- if (["Enter", "Escape", " "].includes(ev.key)) {
- window.postMessage(
- {
- name: "ClosePopup",
- detail: ev.key === "Escape",
- },
- "*"
- );
- }
-});
diff --git a/toolkit/modules/ActorManagerParent.sys.mjs b/toolkit/modules/ActorManagerParent.sys.mjs
@@ -694,11 +694,11 @@ if (AppConstants.platform != "android") {
// Note that GeckoView handles MozOpenDateTimePicker in GeckoViewPrompt.
JSWINDOWACTORS.DateTimePicker = {
parent: {
- esModuleURI: "moz-src:///toolkit/actors/DateTimePickerParent.sys.mjs",
+ esModuleURI: "resource://gre/actors/DateTimePickerParent.sys.mjs",
},
child: {
- esModuleURI: "moz-src:///toolkit/actors/DateTimePickerChild.sys.mjs",
+ esModuleURI: "resource://gre/actors/DateTimePickerChild.sys.mjs",
events: {
MozOpenDateTimePicker: {},
MozCloseDateTimePicker: {},
@@ -768,23 +768,6 @@ if (AppConstants.platform != "android") {
remoteTypes: ["privilegedabout"],
enablePreference: "browser.translations.enable",
};
-
- JSWINDOWACTORS.ColorPicker = {
- parent: {
- esModuleURI: "moz-src:///toolkit/actors/ColorPickerParent.sys.mjs",
- },
-
- child: {
- esModuleURI: "moz-src:///toolkit/actors/ColorPickerChild.sys.mjs",
- events: {
- MozOpenColorPicker: {},
- MozCloseColorPicker: {},
- },
- },
-
- includeChrome: true,
- allFrames: true,
- };
}
export var ActorManagerParent = {
diff --git a/toolkit/modules/ColorPickerPanel.sys.mjs b/toolkit/modules/ColorPickerPanel.sys.mjs
@@ -1,67 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { InputPickerPanelCommon } from "./InputPickerPanelCommon.sys.mjs";
-
-/** @import {OpenPickerInfo} from "./InputPickerPanelCommon.sys.mjs" */
-
-const COLOR_PICKER_WIDTH = "215px";
-const COLOR_PICKER_HEIGHT = "170px";
-
-export class ColorPickerPanel extends InputPickerPanelCommon {
- constructor(element) {
- super(element, "colorpicker.html");
- }
-
- /**
- * Picker window initialization function called when opening the picker
- *
- * @param {string} type The input element type
- * @returns {OpenPickerInfo}
- */
- openPickerImpl(type) {
- return {
- type,
- width: COLOR_PICKER_WIDTH,
- height: COLOR_PICKER_HEIGHT,
- };
- }
-
- /**
- * Popup frame initialization function called when the picker window is loaded
- *
- * @param {string} _type The picker type
- * @param {object} detail The argument from the child actor's openPickerImpl
- * @returns An argument object to pass to the popup frame
- */
- initPickerImpl(_type, detail) {
- if (
- Services.prefs.getBoolPref("dom.forms.html_color_picker.testing", false)
- ) {
- this.handleMessage({
- data: {
- name: "PickerPopupChanged",
- detail: { rgb: [0.1, 0.2, 0.3] },
- },
- });
- this.handleMessage({ data: { name: "ClosePopup" } });
- }
-
- return {
- value: detail.value,
- };
- }
-
- /**
- * Input element state updater function called when the picker value is changed
- *
- * @param {string} _type
- * @param {object} pickerState
- */
- sendPickerValueChangedImpl(_type, pickerState) {
- return {
- rgb: pickerState.rgb,
- };
- }
-}
diff --git a/toolkit/modules/DateTimePickerPanel.sys.mjs b/toolkit/modules/DateTimePickerPanel.sys.mjs
@@ -2,69 +2,86 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-import { InputPickerPanelCommon } from "./InputPickerPanelCommon.sys.mjs";
-
-/** @import {OpenPickerInfo} from "./InputPickerPanelCommon.sys.mjs" */
+export class DateTimePickerPanel {
+ constructor(element) {
+ this.element = element;
-const TIME_PICKER_WIDTH = "13em";
-const TIME_PICKER_HEIGHT = "22em";
-const DATE_PICKER_WIDTH = "24em";
-const DATE_PICKER_HEIGHT = "27em";
-const DATETIME_PICKER_WIDTH = "40em";
-const DATETIME_PICKER_HEIGHT = "27em";
+ this.TIME_PICKER_WIDTH = "13em";
+ this.TIME_PICKER_HEIGHT = "22em";
+ this.DATE_PICKER_WIDTH = "24em";
+ this.DATE_PICKER_HEIGHT = "27em";
+ this.DATETIME_PICKER_WIDTH = "40em";
+ this.DATETIME_PICKER_HEIGHT = "27em";
+ }
-export class DateTimePickerPanel extends InputPickerPanelCommon {
- constructor(element) {
- super(element, "datetimepicker.xhtml");
+ get dateTimePopupFrame() {
+ let frame = this.element.querySelector("#dateTimePopupFrame");
+ if (!frame) {
+ frame = this.element.ownerDocument.createXULElement("iframe");
+ frame.id = "dateTimePopupFrame";
+ this.element.appendChild(frame);
+ }
+ return frame;
}
- /**
- * Picker window initialization function called when opening the picker
- *
- * @param {string} type The input element type
- * @returns {OpenPickerInfo}
- */
- openPickerImpl(type) {
+ openPicker(type, rect, detail) {
if (
type == "datetime-local" &&
!Services.prefs.getBoolPref("dom.forms.datetime.timepicker")
) {
type = "date";
}
+ this.pickerState = {};
+ // TODO: Resize picker according to content zoom level
+ this.element.style.fontSize = "10px";
+ this.type = type;
+ this.detail = detail;
+ this.dateTimePopupFrame.addEventListener("load", this, true);
+ this.dateTimePopupFrame.setAttribute(
+ "src",
+ "chrome://global/content/datetimepicker.xhtml"
+ );
switch (type) {
case "time": {
- return {
- type,
- width: TIME_PICKER_WIDTH,
- height: TIME_PICKER_HEIGHT,
- };
+ this.dateTimePopupFrame.style.width = this.TIME_PICKER_WIDTH;
+ this.dateTimePopupFrame.style.height = this.TIME_PICKER_HEIGHT;
+ break;
}
case "date": {
- return {
- type,
- width: DATE_PICKER_WIDTH,
- height: DATE_PICKER_HEIGHT,
- };
+ this.dateTimePopupFrame.style.width = this.DATE_PICKER_WIDTH;
+ this.dateTimePopupFrame.style.height = this.DATE_PICKER_HEIGHT;
+ break;
}
case "datetime-local": {
- return {
- type,
- width: DATETIME_PICKER_WIDTH,
- height: DATETIME_PICKER_HEIGHT,
- };
+ this.dateTimePopupFrame.style.width = this.DATETIME_PICKER_WIDTH;
+ this.dateTimePopupFrame.style.height = this.DATETIME_PICKER_HEIGHT;
+ break;
}
}
- throw new Error(`Unexpected type ${type}`);
+ this.element.openPopupAtScreenRect(
+ "after_start",
+ rect.left,
+ rect.top,
+ rect.width,
+ rect.height,
+ false,
+ false
+ );
+ }
+
+ closePicker(clear) {
+ if (clear) {
+ this.element.dispatchEvent(new CustomEvent("DateTimePickerValueCleared"));
+ }
+ this.pickerState = {};
+ this.type = undefined;
+ this.dateTimePopupFrame.removeEventListener("load", this, true);
+ this.dateTimePopupFrame.contentWindow.removeEventListener("message", this);
+ this.dateTimePopupFrame.setAttribute("src", "");
+ this.element.hidePopup();
}
- /**
- * Popup frame initialization function called when the picker window is loaded
- *
- * @param {string} type The picker type
- * @param {object} detail The argument from the child actor's openPickerImpl
- * @returns An argument object to pass to the popup frame
- */
- initPickerImpl(type, detail) {
+ initPicker(detail) {
let locale = new Services.intl.Locale(
Services.locale.webExposedLocales[0],
{
@@ -82,7 +99,7 @@ export class DateTimePickerPanel extends InputPickerPanelCommon {
const { year, month, day, hour, minute } = detail.value;
const flattenDetail = {
- type,
+ type: this.type,
year,
// Month value from input box starts from 1 instead of 0
month: month == undefined ? undefined : month - 1,
@@ -98,7 +115,7 @@ export class DateTimePickerPanel extends InputPickerPanelCommon {
stepBase: detail.stepBase,
};
- if (type !== "time") {
+ if (this.type !== "time") {
const { firstDayOfWeek, weekends } = this.getCalendarInfo(locale);
const monthDisplayNames = new Services.intl.DisplayNames(locale, {
@@ -126,33 +143,59 @@ export class DateTimePickerPanel extends InputPickerPanelCommon {
weekdayStrings,
});
}
- return flattenDetail;
+ this.postMessageToPicker({
+ name: "PickerInit",
+ detail: flattenDetail,
+ });
}
- /**
- * Input element state updater function called when the picker value is changed
- *
- * @param {string} type
- * @param {object} pickerState
- */
- sendPickerValueChangedImpl(type, pickerState) {
- let { year, month, day, hour, minute } = pickerState;
- if (month !== undefined) {
- // Month value from input box starts from 1 instead of 0
- month += 1;
- }
- switch (type) {
+ setInputBoxValue() {
+ const value = {
+ year: this.pickerState.year,
+ month: this.pickerState.month,
+ day: this.pickerState.day,
+ hour: this.pickerState.hour,
+ minute: this.pickerState.minute,
+ };
+ this.sendPickerValueChanged(value);
+ }
+
+ sendPickerValueChanged(value) {
+ let detail = {};
+ switch (this.type) {
case "time": {
- return { hour, minute };
+ detail = {
+ hour: value.hour,
+ minute: value.minute,
+ };
+ break;
}
case "date": {
- return { year, month, day };
+ detail = {
+ year: value.year,
+ // Month value from input box starts from 1 instead of 0
+ month: value.month == undefined ? undefined : value.month + 1,
+ day: value.day,
+ };
+ break;
}
case "datetime-local": {
- return { year, month, day, hour, minute };
+ detail = {
+ year: value.year,
+ // Month value from input box starts from 1 instead of 0
+ month: value.month == undefined ? undefined : value.month + 1,
+ day: value.day,
+ hour: value.hour,
+ minute: value.minute,
+ };
+ break;
}
}
- throw new Error(`Unexpected type ${type}`);
+ this.element.dispatchEvent(
+ new CustomEvent("DateTimePickerValueChanged", {
+ detail,
+ })
+ );
}
getCalendarInfo(locale) {
@@ -175,4 +218,46 @@ export class DateTimePickerPanel extends InputPickerPanelCommon {
weekends,
};
}
+
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "load": {
+ this.initPicker(this.detail);
+ this.dateTimePopupFrame.contentWindow.addEventListener("message", this);
+ break;
+ }
+ case "message": {
+ this.handleMessage(aEvent);
+ break;
+ }
+ }
+ }
+
+ handleMessage(aEvent) {
+ if (
+ !this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal
+ ) {
+ return;
+ }
+
+ switch (aEvent.data.name) {
+ case "PickerPopupChanged": {
+ this.pickerState = aEvent.data.detail;
+ this.setInputBoxValue();
+ break;
+ }
+ case "ClosePopup": {
+ this.closePicker(aEvent.data.detail);
+ break;
+ }
+ }
+ }
+
+ postMessageToPicker(data) {
+ if (
+ this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal
+ ) {
+ this.dateTimePopupFrame.contentWindow.postMessage(data, "*");
+ }
+ }
}
diff --git a/toolkit/modules/InputPickerPanelCommon.sys.mjs b/toolkit/modules/InputPickerPanelCommon.sys.mjs
@@ -1,166 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-export class InputPickerPanelCommon {
- #element;
- #filename;
- #pickerState;
- #type;
- #detail;
- /** @type {AbortController} */
- #abortController;
-
- /**
- * @param {XULElement} element
- * @param {string} filename
- */
- constructor(element, filename) {
- this.#element = element;
- this.#filename = filename;
- }
-
- get #popupFrame() {
- const id = `inputPickerPopupFrame`;
- let frame = this.#element.ownerDocument.getElementById(id);
- if (!frame) {
- frame = this.#element.ownerDocument.createXULElement("iframe");
- frame.id = id;
- this.#element.appendChild(frame);
- }
- return frame;
- }
-
- openPicker(type, rect, detail) {
- const impl = this.openPickerImpl(type);
- this.#pickerState = {};
- // TODO: Resize picker according to content zoom level
- this.#element.style.fontSize = "10px";
- this.#type = impl.type;
- this.#detail = detail;
- this.#abortController = new AbortController();
- this.#popupFrame.addEventListener("load", this, {
- capture: true,
- signal: this.#abortController.signal,
- });
- this.#popupFrame.setAttribute(
- "src",
- `chrome://global/content/${this.#filename}`
- );
- this.#popupFrame.style.width = impl.width;
- this.#popupFrame.style.height = impl.height;
- this.#element.openPopupAtScreenRect(
- "after_start",
- rect.left,
- rect.top,
- rect.width,
- rect.height,
- false,
- false
- );
- }
-
- /**
- * @typedef {object} OpenPickerInfo
- * @property {string} type The picker type
- * @property {string} width The picker width in CSS value
- * @property {string} height The picker height in CSS value
- *
- * Picker window initialization function called when opening the picker
- *
- * @param {string} _type The input element type
- * @returns {OpenPickerInfo}
- */
- openPickerImpl(_type) {
- throw new Error("Not implemented");
- }
-
- closePicker(clear) {
- if (clear) {
- this.#element.dispatchEvent(new CustomEvent(`InputPickerValueCleared`));
- }
- this.#pickerState = {};
- this.#type = undefined;
- this.#abortController.abort();
- this.#popupFrame.setAttribute("src", "");
- this.#element.hidePopup();
- }
-
- initPicker(detail) {
- const implDetail = this.initPickerImpl(this.#type, detail);
- this.postMessageToPicker({
- name: "PickerInit",
- detail: implDetail,
- });
- }
-
- /**
- * Popup frame initialization function called when the picker window is loaded
- *
- * @param {string} _type The picker type
- * @param {object} _detail The argument from the child actor's openPickerImpl
- * @returns An argument object to pass to the popup frame
- */
- initPickerImpl(_type, _detail) {
- throw new Error("Not implemented");
- }
-
- sendPickerValueChanged() {
- let detail = this.sendPickerValueChangedImpl(this.#type, this.#pickerState);
- this.#element.dispatchEvent(
- new CustomEvent(`InputPickerValueChanged`, {
- detail,
- })
- );
- }
-
- /**
- * Input element state updater function called when the picker value is changed
- *
- * @param {string} _type
- * @param {object} _pickerState
- */
- sendPickerValueChangedImpl(_type, _pickerState) {
- throw new Error("Not implemented");
- }
-
- handleEvent(aEvent) {
- switch (aEvent.type) {
- case "load": {
- this.initPicker(this.#detail);
- this.#popupFrame.contentWindow.addEventListener("message", this, {
- signal: this.#abortController.signal,
- });
- break;
- }
- case "message": {
- this.handleMessage(aEvent);
- break;
- }
- }
- }
-
- handleMessage(aEvent) {
- if (!this.#popupFrame.contentDocument.nodePrincipal.isSystemPrincipal) {
- return;
- }
-
- switch (aEvent.data.name) {
- case "PickerPopupChanged": {
- this.#pickerState = aEvent.data.detail;
- this.sendPickerValueChanged();
- break;
- }
- case "ClosePopup": {
- this.closePicker(aEvent.data.detail);
- break;
- }
- }
- }
-
- postMessageToPicker(data) {
- if (this.#popupFrame.contentDocument.nodePrincipal.isSystemPrincipal) {
- this.#popupFrame.contentWindow.postMessage(data, "*");
- }
- }
-}
diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build
@@ -162,6 +162,7 @@ EXTRA_JS_MODULES += [
"Console.sys.mjs",
"ContentDOMReference.sys.mjs",
"CreditCard.sys.mjs",
+ "DateTimePickerPanel.sys.mjs",
"DeferredTask.sys.mjs",
"E10SUtils.sys.mjs",
"EventEmitter.sys.mjs",
@@ -214,12 +215,6 @@ EXTRA_JS_MODULES += [
"WebChannel.sys.mjs",
]
-MOZ_SRC_FILES += [
- "ColorPickerPanel.sys.mjs",
- "DateTimePickerPanel.sys.mjs",
- "InputPickerPanelCommon.sys.mjs",
-]
-
if CONFIG["MOZ_ASAN_REPORTER"]:
EXTRA_JS_MODULES += [
"AsanReporter.sys.mjs",
diff --git a/toolkit/themes/shared/colorpicker-common.css b/toolkit/themes/shared/colorpicker-common.css
@@ -1,183 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/* Mix-in classes */
-
-.spectrum-checker {
- background-color: #eee;
- background-image:
- linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
- linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
- background-size: 12px 12px;
- background-position:
- 0 0,
- 6px 6px;
- /* Make sure that the background color is properly set in High Contrast Mode */
- forced-color-adjust: none;
-}
-
-.spectrum-box {
- background-clip: content-box;
-
- :root[forced-colors-active] & {
- border-color: initial;
- }
-}
-
-/* Elements */
-
-/**
- * Spectrum controls set the layout for the controls section of the color picker.
- */
-.spectrum-controls {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-block-start: 10px;
-}
-
-/**
- * This styles the color preview and adds a checkered background overlay inside of it. The overlay
- * can be manipulated using the --overlay-color variable.
- */
-.spectrum-color-preview {
- --overlay-color: transparent;
- border: 1px solid transparent;
- border-radius: 50%;
- box-sizing: border-box;
- width: 25px;
- height: 25px;
- background-color: #fff;
- background-image:
- linear-gradient(var(--overlay-color), var(--overlay-color)), linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%),
- linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%);
- background-size: 12px 12px;
- background-position:
- 0 0,
- 6px 6px;
- /* Make sure that the background color is properly set in High Contrast Mode */
- forced-color-adjust: none;
-
- :root[forced-colors-active] & {
- border-color: CanvasText;
- }
-}
-
-.spectrum-color-preview.high-luminance {
- border-color: #ccc;
-}
-
-.spectrum-slider-container {
- display: flex;
- flex-direction: column;
- justify-content: space-around;
- flex: 1;
- margin-inline-start: 10px;
- height: 30px;
-}
-
-/* Keep aspect ratio:
-http://www.briangrinstead.com/blog/keep-aspect-ratio-with-html-and-css */
-.spectrum-color-picker {
- position: relative;
- border: 1px solid rgba(0, 0, 0, 0.2);
- border-radius: 2px;
- box-sizing: border-box;
- width: 205px;
- height: 120px;
- /* Make sure that the background color is properly set in High Contrast Mode */
- forced-color-adjust: none;
-}
-
-.spectrum-color {
- position: absolute;
- top: 0;
- left: 0;
- bottom: 0;
- width: 100%;
-}
-
-.spectrum-sat,
-.spectrum-val {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
-}
-
-.spectrum-alpha {
- margin-block-start: 3px;
-}
-
-.spectrum-alpha,
-.spectrum-hue {
- position: relative;
- height: 8px;
-}
-
-.spectrum-alpha-input,
-.spectrum-hue-input {
- width: 100%;
- margin: 0;
- position: absolute;
- height: 8px;
- border-radius: 2px;
- direction: initial;
-}
-
-.spectrum-hue-input,
-.spectrum-alpha-input {
- outline-offset: 4px;
-}
-
-.spectrum-hue-input::-moz-range-thumb,
-.spectrum-alpha-input::-moz-range-thumb {
- cursor: pointer;
- height: 12px;
- width: 12px;
- box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
- background: #fff;
- border-radius: 50%;
- opacity: 0.9;
- border: none;
-}
-
-:root[forced-colors-active] :is(.spectrum-hue-input, .spectrum-alpha-input)::-moz-range-thumb {
- background: ButtonFace;
- border: 2px solid ButtonText;
-}
-
-:root[forced-colors-active] :is(.spectrum-hue-input, .spectrum-alpha-input):is(:hover, :focus-visible)::-moz-range-thumb {
- border-color: SelectedItem;
-}
-
-.spectrum-hue-input::-moz-range-track {
- border-radius: 2px;
- height: 8px;
- background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
- /* Make sure that the background color is properly set in High Contrast Mode */
- forced-color-adjust: none;
-}
-
-.spectrum-sat {
- background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0));
-}
-
-.spectrum-val {
- background-image: linear-gradient(to top, #000000, rgba(204, 154, 129, 0));
-}
-
-.spectrum-dragger {
- user-select: none;
- position: absolute;
- top: 0;
- left: 0;
- cursor: pointer;
- border-radius: 50%;
- height: 8px;
- width: 8px;
- border: 1px solid white;
- box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
-}
diff --git a/toolkit/themes/shared/colorpicker.css b/toolkit/themes/shared/colorpicker.css
@@ -1,9 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-@import "colorpicker-common.css";
-
-body {
- margin: 5px;
-}
diff --git a/toolkit/themes/shared/desktop-jar.inc.mn b/toolkit/themes/shared/desktop-jar.inc.mn
@@ -25,8 +25,6 @@
skin/classic/global/checkbox.css (../../shared/checkbox.css)
skin/classic/global/radio.css (../../shared/radio.css)
skin/classic/global/close-icon.css (../../shared/close-icon.css)
- skin/classic/global/colorpicker-common.css (../../shared/colorpicker-common.css)
- skin/classic/global/colorpicker.css (../../shared/colorpicker.css)
skin/classic/global/commonDialog.css (../../shared/commonDialog.css)
skin/classic/global/datetimeinputpickers.css (../../shared/datetimeinputpickers.css)
skin/classic/global/design-system/text-and-typography.css(../../shared/design-system/src/text-and-typography.css)
diff --git a/tools/@types/generated/lib.gecko.dom.d.ts b/tools/@types/generated/lib.gecko.dom.d.ts
@@ -1623,12 +1623,6 @@ interface InputEventInit extends UIEventInit {
targetRanges?: StaticRange[];
}
-interface InputPickerColor {
- component1: number;
- component2: number;
- component3: number;
-}
-
interface InspectorCSSPropertyDefinition {
fromJS: boolean;
inherits: boolean;
@@ -11844,7 +11838,6 @@ interface HTMLInputElement extends HTMLElement, MozEditableElement, MozImageLoad
checkValidity(): boolean;
closeDateTimePicker(): void;
getAutocompleteInfo(): AutocompleteInfo | null;
- getColor(): InputPickerColor;
getDateTimeInputBoxValue(): DateTimeValue;
getFilesAndDirectories(): Promise<(File | Directory)[]>;
getMaximum(): number;
@@ -11866,7 +11859,6 @@ interface HTMLInputElement extends HTMLElement, MozEditableElement, MozImageLoad
setRangeText(replacement: string): void;
setRangeText(replacement: string, start: number, end: number, selectionMode?: SelectionMode): void;
setSelectionRange(start: number, end: number, direction?: string): void;
- setUserInputColor(aColor: InputPickerColor): void;
showPicker(): void;
stepDown(n?: number): void;
stepUp(n?: number): void;