Source code
Revision control
Copy as Markdown
Other Tools
/* 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
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");
}
}