tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 037dbf17576969c6f94780f1bd18c0793d71c99d
parent ebe1431f84facd6f3bfc7ec221011420c73b8076
Author: Florian Zia <fzia@mozilla.com>
Date:   Fri,  2 Jan 2026 21:57:27 +0000

Bug 2003063 - Part 4: Add basic multiline editor r=mak,ai-frontend-reviewers,fluent-reviewers,flod

Replaces contenteditable proof of concept with a ProseMirror-based multiline editor:
- Adds MultilineEditor custom element using ProseMirror
- Updates SmartbarInputController with full API
- Updates SmartbarInputUtils to create editor

Differential Revision: https://phabricator.services.mozilla.com/D275514

Diffstat:
Mbrowser/components/moz.build | 1+
Abrowser/components/multilineeditor/jar.mn | 9+++++++++
Abrowser/components/multilineeditor/moz.build | 8++++++++
Abrowser/components/multilineeditor/multiline-editor.css | 39+++++++++++++++++++++++++++++++++++++++
Abrowser/components/multilineeditor/multiline-editor.mjs | 553+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abrowser/components/multilineeditor/multiline-editor.stories.mjs | 26++++++++++++++++++++++++++
Mbrowser/components/storybook/.storybook/main.js | 3+++
Mbrowser/components/urlbar/content/SmartbarInput.mjs | 229+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mbrowser/components/urlbar/content/SmartbarInputController.mjs | 218++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mbrowser/components/urlbar/content/SmartbarInputUtils.mjs | 113++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mbrowser/locales-preview/aiWindow.ftl | 5+++++
Mthird_party/js/prosemirror/moz.build | 1+
Mtools/@types/generated/lib.gecko.modules.d.ts | 23+++++++++++++++++++++--
Mtools/@types/generated/tspaths.json | 66+++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
14 files changed, 1163 insertions(+), 131 deletions(-)

diff --git a/browser/components/moz.build b/browser/components/moz.build @@ -47,6 +47,7 @@ DIRS += [ "messagepreview", "migration", "mozcachedohttp", + "multilineeditor", "newtab", "originattributes", "pagedata", diff --git a/browser/components/multilineeditor/jar.mn b/browser/components/multilineeditor/jar.mn @@ -0,0 +1,9 @@ +# 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/. + +browser.jar: + content/browser/multilineeditor/multiline-editor.css + content/browser/multilineeditor/multiline-editor.mjs + content/browser/multilineeditor/prosemirror.bundle.mjs (../../../third_party/js/prosemirror/prosemirror.bundle.mjs) + content/browser/multilineeditor/prosemirror.css (../../../third_party/js/prosemirror/prosemirror-view/style/prosemirror.css) diff --git a/browser/components/multilineeditor/moz.build b/browser/components/multilineeditor/moz.build @@ -0,0 +1,8 @@ +# 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/. + +with Files("**"): + BUG_COMPONENT = ("Firefox", "Address Bar") + +JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/components/multilineeditor/multiline-editor.css b/browser/components/multilineeditor/multiline-editor.css @@ -0,0 +1,39 @@ +/* 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/. */ + +:host { + display: inline-flex; + width: 100%; +} + +.multiline-editor { + flex: 1; +} + +.multiline-editor .ProseMirror { + height: 100%; +} + +.multiline-editor .ProseMirror:focus { + outline: none; +} + +.multiline-editor .ProseMirror p { + margin: 0; + padding: 0; +} + +/* Force empty paragraphs to not collapse */ +.multiline-editor .ProseMirror p BR.ProseMirror-trailingBreak, +.multiline-editor .ProseMirror p br.ProseMirror-trailingBreak { + display: inline-flex; +} + +.multiline-editor .ProseMirror p.placeholder::before { + content: attr(data-placeholder); + position: absolute; + pointer-events: none; + color: var(--text-color-deemphasized); + user-select: none; +} diff --git a/browser/components/multilineeditor/multiline-editor.mjs b/browser/components/multilineeditor/multiline-editor.mjs @@ -0,0 +1,553 @@ +/* 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 { html } from "chrome://global/content/vendor/lit.all.mjs"; +import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; +import { + Decoration, + DecorationSet, + EditorState, + EditorView, + Plugin as PmPlugin, + TextSelection, + baseKeymap, + basicSchema, + history as historyPlugin, + keymap, + redo as historyRedo, + undo as historyUndo, +} from "chrome://browser/content/multilineeditor/prosemirror.bundle.mjs"; + +/** + * @class MultilineEditor + * + * A ProseMirror-based multiline editor. + * + * @property {string} placeholder - Placeholder text for the editor. + * @property {boolean} readOnly - Whether the editor is read-only. + */ +export class MultilineEditor extends MozLitElement { + static shadowRootOptions = { + ...MozLitElement.shadowRootOptions, + delegatesFocus: true, + }; + + static properties = { + placeholder: { type: String, reflect: true, fluent: true }, + readOnly: { type: Boolean, reflect: true, attribute: "readonly" }, + }; + + static schema = basicSchema; + + #pendingValue = ""; + #placeholderPlugin; + #plugins; + #suppressInputEvent = false; + #view; + + constructor() { + super(); + + this.placeholder = ""; + this.readOnly = false; + this.#placeholderPlugin = this.#createPlaceholderPlugin(); + const plugins = [ + historyPlugin(), + keymap({ + Enter: () => true, + "Shift-Enter": (state, dispatch) => + this.#insertParagraph(state, dispatch), + "Mod-z": historyUndo, + "Mod-y": historyRedo, + "Shift-Mod-z": historyRedo, + }), + keymap(baseKeymap), + this.#placeholderPlugin, + ]; + + if (document.contentType === "application/xhtml+xml") { + plugins.push(this.#createCleanupOrphanedBreaksPlugin()); + } + + this.#plugins = plugins; + } + + /** + * Whether the editor is composing. + * + * @type {boolean} + */ + get composing() { + return this.#view?.composing ?? false; + } + + /** + * The current text content of the editor. + * + * @type {string} + */ + get value() { + if (!this.#view) { + return this.#pendingValue; + } + return this.#view.state.doc.textBetween( + 0, + this.#view.state.doc.content.size, + "\n", + "\n" + ); + } + + /** + * Set the text content of the editor. + * + * @param {string} val + */ + set value(val) { + if (!this.#view) { + this.#pendingValue = val; + return; + } + + if (val === this.value) { + return; + } + + const state = this.#view.state; + const schema = state.schema; + const lines = val.split("\n"); + const paragraphs = lines.map(line => { + const content = line ? [schema.text(line)] : []; + return schema.node("paragraph", null, content); + }); + const doc = schema.node("doc", null, paragraphs); + + const tr = state.tr.replaceWith(0, state.doc.content.size, doc.content); + tr.setMeta("addToHistory", false); + + const cursorPos = this.#posFromTextOffset(val.length, tr.doc); + // Suppress input events when updating only the text selection. + this.#suppressInputEvent = true; + try { + this.#view.dispatch( + tr.setSelection( + TextSelection.between( + tr.doc.resolve(cursorPos), + tr.doc.resolve(cursorPos) + ) + ) + ); + } finally { + this.#suppressInputEvent = false; + } + } + + /** + * The start offset of the selection. + * + * @type {number} + */ + get selectionStart() { + if (!this.#view) { + return 0; + } + return this.#textOffsetFromPos(this.#view.state.selection.from); + } + + /** + * Set the start offset of the selection. + * + * @param {number} val + */ + set selectionStart(val) { + this.setSelectionRange(val, this.selectionEnd ?? val); + } + + /** + * The end offset of the selection. + * + * @type {number} + */ + get selectionEnd() { + if (!this.#view) { + return 0; + } + return this.#textOffsetFromPos(this.#view.state.selection.to); + } + + /** + * Set the end offset of the selection. + * + * @param {number} val + */ + set selectionEnd(val) { + this.setSelectionRange(this.selectionStart ?? 0, val); + } + + /** + * Set the selection range in the editor. + * + * @param {number} start + * @param {number} end + */ + setSelectionRange(start, end) { + if (!this.#view) { + return; + } + + const doc = this.#view.state.doc; + const docSize = doc.content.size; + const maxOffset = this.#textLength(doc); + const fromOffset = Math.max(0, Math.min(start ?? 0, maxOffset)); + const toOffset = Math.max(0, Math.min(end ?? fromOffset, maxOffset)); + const from = Math.max( + 0, + Math.min(this.#posFromTextOffset(fromOffset, doc), docSize) + ); + const to = Math.max( + 0, + Math.min(this.#posFromTextOffset(toOffset, doc), docSize) + ); + + if ( + this.#view.state.selection.from === from && + this.#view.state.selection.to === to + ) { + return; + } + + let selection; + try { + selection = TextSelection.between(doc.resolve(from), doc.resolve(to)); + } catch (_e) { + const anchor = Math.max(0, Math.min(to, docSize)); + selection = TextSelection.near(doc.resolve(anchor)); + } + this.#view.dispatch( + this.#view.state.tr.setSelection(selection).scrollIntoView() + ); + this.#dispatchSelectionChange(); + } + + /** + * Select all text in the editor. + */ + select() { + this.setSelectionRange(0, this.value.length); + } + + /** + * Focus the editor. + */ + focus() { + this.#view?.focus(); + super.focus(); + } + + /** + * Called when the element is added to the DOM. + */ + connectedCallback() { + super.connectedCallback(); + this.setAttribute("role", "presentation"); + } + + /** + * Called when the element is removed from the DOM. + */ + disconnectedCallback() { + this.#destroyView(); + this.#pendingValue = ""; + super.disconnectedCallback(); + } + + /** + * Called after the element’s DOM has been rendered for the first time. + */ + firstUpdated() { + this.#createView(); + } + + /** + * Called when the element’s properties are updated. + * + * @param {Map} changedProps + */ + updated(changedProps) { + if (changedProps.has("placeholder") || changedProps.has("readOnly")) { + this.#refreshView(); + } + } + + #createView() { + const mount = this.renderRoot.querySelector(".multiline-editor"); + if (!mount) { + return; + } + + const state = EditorState.create({ + schema: MultilineEditor.schema, + plugins: this.#plugins, + }); + + this.#view = new EditorView(mount, { + state, + attributes: this.#viewAttributes(), + editable: () => !this.readOnly, + dispatchTransaction: this.#dispatchTransaction, + }); + + if (this.#pendingValue) { + this.value = this.#pendingValue; + this.#pendingValue = ""; + } + } + + #destroyView() { + this.#view?.destroy(); + this.#view = null; + } + + #dispatchTransaction = tr => { + if (!this.#view) { + return; + } + + const prevText = this.value; + const prevSelection = this.#view.state.selection; + const nextState = this.#view.state.apply(tr); + this.#view.updateState(nextState); + + const selectionChanged = + tr.selectionSet && + (prevSelection.from !== nextState.selection.from || + prevSelection.to !== nextState.selection.to); + + if (selectionChanged) { + this.#dispatchSelectionChange(); + } + + if (tr.docChanged && !this.#suppressInputEvent) { + const nextText = this.value; + let insertedText = ""; + for (const step of tr.steps) { + insertedText += step.slice?.content?.textBetween( + 0, + step.slice.content.size, + "", + "" + ); + } + this.dispatchEvent( + new InputEvent("input", { + bubbles: true, + composed: true, + data: insertedText || null, + inputType: + insertedText || nextText.length >= prevText.length + ? "insertText" + : "deleteContentBackward", + }) + ); + } + }; + + #dispatchSelectionChange() { + this.dispatchEvent( + new Event("selectionchange", { bubbles: true, composed: true }) + ); + } + + #insertParagraph(state, dispatch) { + const paragraph = state.schema.nodes.paragraph; + if (!paragraph) { + return false; + } + const { $from } = state.selection; + let tr = state.tr; + if (!state.selection.empty) { + tr = tr.deleteSelection(); + } + tr = tr.split(tr.mapping.map($from.pos)).scrollIntoView(); + dispatch(tr); + return true; + } + + /** + * Creates a plugin that shows a placeholder when the editor is empty. + * + * @returns {PmPlugin} + */ + #createPlaceholderPlugin() { + return new PmPlugin({ + props: { + decorations: ({ doc }) => { + if ( + doc.childCount !== 1 || + !doc.firstChild.isTextblock || + doc.firstChild.content.size !== 0 || + !this.placeholder + ) { + return null; + } + + return DecorationSet.create(doc, [ + Decoration.node(0, doc.firstChild.nodeSize, { + class: "placeholder", + "data-placeholder": this.placeholder, + }), + ]); + }, + }, + }); + } + + /** + * Creates a plugin that removes orphaned hard breaks from empty paragraphs. + * + * In XHTML contexts the trailing break element in paragraphs are rendered as + * uppercase (<BR> instead of <br>). ProseMirror seems to have issues parsing + * these breaks, which leads to orphaned breaks after deleting text content. + * + * @returns {PmPlugin} + */ + #createCleanupOrphanedBreaksPlugin() { + return new PmPlugin({ + appendTransaction(transactions, prevState, nextState) { + if (!transactions.some(tr => tr.docChanged)) { + return null; + } + + const tr = nextState.tr; + let modified = false; + + nextState.doc.descendants((nextNode, nextPos) => { + if ( + nextNode.type.name !== "paragraph" || + nextNode.textContent || + nextNode.childCount === 0 + ) { + return true; + } + + for (let i = 0; i < nextNode.childCount; i++) { + if (nextNode.child(i).type.name === "hard_break") { + const prevNode = prevState.doc.nodeAt(nextPos); + if (prevNode?.type.name === "paragraph" && prevNode.textContent) { + tr.replaceWith( + nextPos + 1, + nextPos + nextNode.content.size + 1, + [] + ); + modified = true; + } + break; + } + } + + return true; + }); + + return modified ? tr : null; + }, + }); + } + + #refreshView() { + if (!this.#view) { + return; + } + + this.#view.setProps({ + attributes: this.#viewAttributes(), + editable: () => !this.readOnly, + }); + this.#view.dispatch(this.#view.state.tr); + } + + #textOffsetFromPos(pos, doc = this.#view?.state.doc) { + if (!doc) { + return 0; + } + return doc.textBetween(0, pos, "\n", "\n").length; + } + + #posFromTextOffset(offset, doc = this.#view?.state.doc) { + if (!doc) { + return 0; + } + const target = Math.max(0, Math.min(offset ?? 0, this.#textLength(doc))); + let seen = 0; + let pos = doc.content.size; + let found = false; + let paragraphCount = 0; + doc.descendants((node, nodePos) => { + if (found) { + return false; + } + if (node.type.name === "paragraph") { + if (paragraphCount > 0) { + if (target <= seen + 1) { + pos = nodePos; + found = true; + return false; + } + seen += 1; + } + paragraphCount++; + } + if (node.isText) { + const textNodeLength = node.text.length; + const start = nodePos; + if (target <= seen + textNodeLength) { + pos = start + (target - seen); + found = true; + return false; + } + seen += textNodeLength; + } else if (node.type.name === "hard_break") { + if (target <= seen + 1) { + pos = nodePos; + found = true; + return false; + } + seen += 1; + } + return true; + }); + return pos; + } + + #textLength(doc) { + if (!doc) { + return 0; + } + return doc.textBetween(0, doc.content.size, "\n", "\n").length; + } + + #viewAttributes() { + return { + "aria-label": this.placeholder, + "aria-multiline": "true", + "aria-readonly": this.readOnly ? "true" : "false", + role: "textbox", + }; + } + + render() { + return html` + <link + rel="stylesheet" + href="chrome://browser/content/multilineeditor/prosemirror.css" + /> + <link + rel="stylesheet" + href="chrome://browser/content/multilineeditor/multiline-editor.css" + /> + <div class="multiline-editor"></div> + `; + } +} + +customElements.define("moz-multiline-editor", MultilineEditor); diff --git a/browser/components/multilineeditor/multiline-editor.stories.mjs b/browser/components/multilineeditor/multiline-editor.stories.mjs @@ -0,0 +1,26 @@ +/* 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 { html } from "chrome://global/content/vendor/lit.all.mjs"; +import "chrome://browser/content/multilineeditor/multiline-editor.mjs"; + +export default { + title: "UI Widgets/Multiline Editor", + component: "moz-multiline-editor", + argTypes: { + action: { + options: [null, "chat", "search", "navigate"], + control: { type: "select" }, + }, + }, +}; + +const Template = ({ placeholder }) => html` + <moz-multiline-editor .placeholder=${placeholder}></moz-multiline-editor> +`; + +export const Default = Template.bind({}); +Default.args = { + placeholder: "Placeholder text", +}; diff --git a/browser/components/storybook/.storybook/main.js b/browser/components/storybook/.storybook/main.js @@ -39,6 +39,8 @@ module.exports = { `${projectRoot}/browser/components/webrtc/content/**/*.stories.mjs`, // AI Window components stories `${projectRoot}/browser/components/aiwindow/ui/**/*.stories.mjs`, + // Multiline editor components stories + `${projectRoot}/browser/components/multilineeditor/**/*.stories.mjs`, // Everything else "../stories/**/*.stories.@(js|jsx|mjs|ts|tsx|md)", // Design system files @@ -88,6 +90,7 @@ module.exports = { // Make whatever fine-grained changes you need config.resolve.alias = { browser: `${projectRoot}/browser`, + third_party: `${projectRoot}/third_party`, toolkit: `${projectRoot}/toolkit`, "toolkit-widgets": `${projectRoot}/toolkit/content/widgets/`, "lit.all.mjs": `${projectRoot}/toolkit/content/widgets/vendor/lit.all.mjs`, diff --git a/browser/components/urlbar/content/SmartbarInput.mjs b/browser/components/urlbar/content/SmartbarInput.mjs @@ -2,6 +2,8 @@ * 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 { createEditor } from "chrome://browser/content/urlbar/SmartbarInputUtils.mjs"; + const { XPCOMUtils } = ChromeUtils.importESModule( "resource://gre/modules/XPCOMUtils.sys.mjs" ); @@ -12,7 +14,6 @@ const { AppConstants } = ChromeUtils.importESModule( /** * @import {UrlbarSearchOneOffs} from "moz-src:///browser/components/urlbar/UrlbarSearchOneOffs.sys.mjs" - * @import {SmartbarInputController} from "chrome://browser/content/urlbar/SmartbarInputController.mjs" */ const lazy = XPCOMUtils.declareLazy({ @@ -68,10 +69,6 @@ const lazy = XPCOMUtils.declareLazy({ pref: "privacy.query_stripping.strip_on_share.enabled", default: false, }, - createEditor: () => - ChromeUtils.importESModule( - "chrome://browser/content/urlbar/SmartbarInputUtils.mjs" - ).createEditor, logger: () => lazy.UrlbarUtils.getLogger({ prefix: "SmartbarInput" }), }); @@ -136,7 +133,7 @@ export class SmartbarInput extends HTMLElement { aria-controls="urlbar-results" aria-autocomplete="both" inputmode="mozAwesomebar" - data-l10n-id="urlbar-placeholder"/> + data-l10n-id="smartbar-placeholder"/> </moz-input-box> <moz-urlbar-slot name="revert-button"> </moz-urlbar-slot> <image class="urlbar-icon urlbar-go-button" @@ -201,6 +198,8 @@ export class SmartbarInput extends HTMLElement { "selectionchange", ]; + static #validSmartbarModes = ["chat", "search", "navigate"]; + #allowBreakout = false; #gBrowserListenersAdded = false; #breakoutBlockerCount = 0; @@ -214,10 +213,9 @@ export class SmartbarInput extends HTMLElement { */ #isSmartbarMode = false; #sapName = ""; - /** @type {object | null} */ #smartbarEditor = null; - /** @type {SmartbarEditorController | null} */ - #smartbarEditorController = null; + #smartbarInputController = null; + #smartbarMode = "search"; _userTypedValue = ""; _actionOverrideKeyCount = 0; _lastValidURLStr = ""; @@ -549,22 +547,27 @@ export class SmartbarInput extends HTMLElement { } #initSmartbarEditor() { - const adapter = lazy.createEditor(this.inputField); - if (!adapter) { - return; - } - this.#smartbarEditorController = new lazy.SmartbarInputController(adapter); + const adapter = createEditor(this.inputField); + this.#smartbarInputController = new lazy.SmartbarInputController(adapter); this.inputField = adapter.input; this.#smartbarEditor = adapter.editor; } #ensureSmartbarEditor() { - if (!this.#smartbarEditorController) { + if (!this.#smartbarInputController) { this.#initSmartbarEditor(); } return this.#smartbarEditor; } + #setInputValue(val) { + if (this.#smartbarInputController) { + this.#smartbarInputController.setValue(val); + } else { + this.inputField.value = val; + } + } + #lazy = XPCOMUtils.declareLazy({ valueFormatter: () => new lazy.UrlbarValueFormatter(this), addSearchEngineHelper: () => new AddSearchEngineHelper(this), @@ -585,29 +588,118 @@ export class SmartbarInput extends HTMLElement { return this.#sapName; } + get smartbarMode() { + const mode = this.getAttribute("smartbar-mode") || this.#smartbarMode; + return SmartbarInput.#validSmartbarModes.includes(mode) + ? mode + : SmartbarInput.#validSmartbarModes[0]; + } + + set smartbarMode(mode) { + if (!SmartbarInput.#validSmartbarModes.includes(mode)) { + return; + } + this.#smartbarMode = mode; + this.setAttribute("smartbar-mode", mode); + } + blur() { - this.inputField.blur(); + if (this.#smartbarInputController) { + this.#smartbarInputController.blur(); + } else { + this.inputField.blur(); + } + } + + /** + * @type {typeof HTMLInputElement.prototype.placeholder} + */ + get placeholder() { + return ( + this.#smartbarInputController?.placeholder ?? this.inputField?.placeholder + ); } /** * @type {typeof HTMLInputElement.prototype.placeholder} */ - placeholder; + set placeholder(val) { + if (this.#smartbarInputController) { + this.#smartbarInputController.placeholder = val; + return; + } + if (this.inputField) { + this.inputField.placeholder = val; + } + } /** * @type {typeof HTMLInputElement.prototype.readOnly} */ - readOnly; + get readOnly() { + return this.#smartbarInputController?.readOnly ?? this.inputField?.readOnly; + } + + /** + * @type {typeof HTMLInputElement.prototype.readOnly} + */ + set readOnly(val) { + if (this.#smartbarInputController) { + this.#smartbarInputController.readOnly = val; + return; + } + if (this.inputField) { + this.inputField.readOnly = val; + } + } /** * @type {typeof HTMLInputElement.prototype.selectionStart} */ - selectionStart; + get selectionStart() { + return ( + this.#smartbarInputController?.selectionStart ?? + this.inputField?.selectionStart ?? + 0 + ); + } + + /** + * @type {typeof HTMLInputElement.prototype.selectionStart} + */ + set selectionStart(val) { + if (this.#smartbarInputController) { + this.#smartbarInputController.selectionStart = val; + return; + } + if (this.inputField) { + this.inputField.selectionStart = val; + } + } + + /** + * @type {typeof HTMLInputElement.prototype.selectionEnd} + */ + get selectionEnd() { + return ( + this.#smartbarInputController?.selectionEnd ?? + this.inputField?.selectionEnd ?? + 0 + ); + } /** * @type {typeof HTMLInputElement.prototype.selectionEnd} */ - selectionEnd; + set selectionEnd(val) { + if (this.#smartbarInputController) { + this.#smartbarInputController.selectionEnd = val; + return; + } + if (this.inputField) { + this.inputField.selectionEnd = val; + } + } /** * Called when a urlbar or urlbar related pref changes. @@ -657,7 +749,11 @@ export class SmartbarInput extends HTMLElement { return; } - this.inputField.focus(); + if (this.#smartbarInputController) { + this.#smartbarInputController.focus(); + } else { + this.inputField.focus(); + } } select() { @@ -673,7 +769,11 @@ export class SmartbarInput extends HTMLElement { // See _on_select(). HTMLInputElement.select() dispatches a "select" // event but does not set the primary selection. this._suppressPrimaryAdjustment = true; - this.inputField.select(); + if (this.#smartbarInputController) { + this.#smartbarInputController?.select(); + } else { + this.inputField.select(); + } this._suppressPrimaryAdjustment = false; } @@ -690,7 +790,14 @@ export class SmartbarInput extends HTMLElement { // See _on_select(). HTMLInputElement.select() dispatches a "select" // event but does not set the primary selection. this._suppressPrimaryAdjustment = true; - this.inputField.setSelectionRange(selectionStart, selectionEnd); + if (this.#smartbarInputController) { + this.#smartbarInputController.setSelectionRange( + selectionStart, + selectionEnd + ); + } else { + this.inputField.setSelectionRange(selectionStart, selectionEnd); + } this._suppressPrimaryAdjustment = false; } @@ -856,7 +963,7 @@ export class SmartbarInput extends HTMLElement { ) { // If the same text is in the same place as the previously selected text, // the selection is kept. - this.inputField.setSelectionRange( + this.setSelectionRange( previousSelectionStart - offset, previousSelectionEnd - offset ); @@ -868,11 +975,11 @@ export class SmartbarInput extends HTMLElement { // If the previous end caret is not 0 and the caret is at the end of the // input or its position is beyond the end of the new value, keep the // position at the end. - this.inputField.setSelectionRange(value.length, value.length); + this.setSelectionRange(value.length, value.length); } else { // Otherwise clear selection and set the caret position to the previous // caret end position. - this.inputField.setSelectionRange( + this.setSelectionRange( previousSelectionEnd - offset, previousSelectionEnd - offset ); @@ -1061,11 +1168,12 @@ export class SmartbarInput extends HTMLElement { handleNavigation({ event, oneOffParams, triggeringPrincipal }) { if (this.#isSmartbarMode) { const committedValue = this.untrimmedValue; - lazy.logger.debug(`commit: ${committedValue}`); + const mode = this.smartbarMode; + lazy.logger.debug(`commit (${mode}): ${committedValue}`); this.#clearSmartbarInput(); this.dispatchEvent( new CustomEvent("smartbar-commit", { - detail: { value: committedValue, event }, + detail: { value: committedValue, event, mode }, }) ); if (!this.window.gBrowser) { @@ -2028,9 +2136,8 @@ export class SmartbarInput extends HTMLElement { let currentSelectionEnd = this.selectionEnd; // Overriding this value clears the selection. - this.inputField.value = this.value.substring( - 0, - this._autofillPlaceholder.selectionStart + this.#setInputValue( + this.value.substring(0, this._autofillPlaceholder.selectionStart) ); this._autofillPlaceholder = null; // Restore selection @@ -2209,7 +2316,7 @@ export class SmartbarInput extends HTMLElement { value += " "; } } - this.inputField.value = value; + this.#setInputValue(value); // Avoid selecting the text if this method is called twice in a row. this.selectionStart = -1; @@ -2286,7 +2393,7 @@ export class SmartbarInput extends HTMLElement { this._lastSearchString = ""; if (this.#isAddressbar) { - this.inputField.value = url; + this.#setInputValue(url); } this.selectionStart = -1; @@ -2568,7 +2675,10 @@ export class SmartbarInput extends HTMLElement { } get focused() { - return this.document.activeElement == this.inputField; + return ( + this.document.activeElement == + (this.#smartbarInputController?.input ?? this.inputField) + ); } get goButton() { @@ -2576,7 +2686,7 @@ export class SmartbarInput extends HTMLElement { } get value() { - return this.inputField.value; + return this.#smartbarInputController?.value ?? this.inputField.value; } set value(val) { @@ -3096,7 +3206,7 @@ export class SmartbarInput extends HTMLElement { this.valueIsTyped = valueIsTyped; this._resultForCurrentValue = null; - this.inputField.value = val; + this.#setInputValue(val); this.formatValue(); if (actionType !== undefined) { @@ -3295,6 +3405,13 @@ export class SmartbarInput extends HTMLElement { ) { let autofillValue = value + this._autofillPlaceholder.value.substring(value.length); + if ( + this.value === autofillValue && + this.selectionStart === value.length && + this.selectionEnd === autofillValue.length + ) { + return true; + } this._autofillValue({ value: autofillValue, selectionStart: value.length, @@ -3687,14 +3804,29 @@ export class SmartbarInput extends HTMLElement { adaptiveHistoryInput, untrimmedValue, }) { + const valueMatches = this.value === value; + const selectionMatches = + this.selectionStart === selectionStart && + this.selectionEnd === selectionEnd; + if (valueMatches && selectionMatches) { + return; + } // The autofilled value may be a URL that includes a scheme at the // beginning. Do not allow it to be trimmed. - this._setValue(value, { untrimmedValue }); - this.inputField.setSelectionRange(selectionStart, selectionEnd); + if (!valueMatches) { + this._setValue(value, { untrimmedValue }); + } + this.setSelectionRange(selectionStart, selectionEnd); // Ensure selection state is cached for contenteditable and events fire. - this.inputField.dispatchEvent( - new Event("selectionchange", { bubbles: true, cancelable: false }) - ); + if (!selectionMatches) { + if (this.#smartbarInputController) { + this.#smartbarInputController.dispatchSelectionChange(); + } else { + this.inputField.dispatchEvent( + new Event("selectionchange", { bubbles: true, cancelable: false }) + ); + } + } this._autofillPlaceholder = { value, type, @@ -3936,7 +4068,7 @@ export class SmartbarInput extends HTMLElement { if (!params.avoidBrowserFocus) { browser.focus(); // Make sure the domain name stays visible for spoof protection and usability. - this.inputField.setSelectionRange(0, 0); + this.setSelectionRange(0, 0); } if (openUILinkWhere != "current") { @@ -4675,6 +4807,11 @@ export class SmartbarInput extends HTMLElement { * The name of the engine or null to use the default placeholder. */ _setPlaceholder(engineName) { + if (this.#isSmartbarMode) { + this.document.l10n.setAttributes(this.inputField, "smartbar-placeholder"); + return; + } + if (!this.#isAddressbar) { this.document.l10n.setAttributes(this.inputField, "searchbar-input"); return; @@ -4707,7 +4844,7 @@ export class SmartbarInput extends HTMLElement { !this._preventClickSelectsAll && this.#compositionState != lazy.UrlbarUtils.COMPOSITION.COMPOSING && this.focused && - this.inputField.selectionStart == this.inputField.selectionEnd + this.selectionStart == this.selectionEnd ) { this.select(); } @@ -4950,7 +5087,7 @@ export class SmartbarInput extends HTMLElement { // Clear any previous selection unless we are focused, to ensure it // doesn't affect drag selection. if (this.focusedViaMousedown) { - this.inputField.setSelectionRange(0, 0); + this.setSelectionRange(0, 0); } // Do not suppress the focus border if we are already focused. If we @@ -5223,7 +5360,7 @@ export class SmartbarInput extends HTMLElement { // Fix up cursor/selection: let newCursorPos = oldStart.length + pasteData.length; - this.inputField.setSelectionRange(newCursorPos, newCursorPos); + this.setSelectionRange(newCursorPos, newCursorPos); this.startQuery({ searchString: this.value, @@ -5479,7 +5616,7 @@ export class SmartbarInput extends HTMLElement { if (this.window.gBrowser.selectedBrowser === loadingBrowser) { loadingBrowser.focus(); // Make sure the domain name stays visible for spoof protection and usability. - this.inputField.setSelectionRange(0, 0); + this.setSelectionRange(0, 0); } } catch (ex) { // Not all the Enter actions in the urlbar will cause a navigation, then it diff --git a/browser/components/urlbar/content/SmartbarInputController.mjs b/browser/components/urlbar/content/SmartbarInputController.mjs @@ -3,32 +3,214 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** + * @import {MultilineEditor} from "chrome://browser/content/multilineeditor/multiline-editor.mjs" + */ + +/** * Controller for the Smartbar editor. * * The controller abstracts the underlying editor implementation allowing * the Smartbar to also work with a standard HTML input. - * - * @property {Element} input - The input element or object. - * @property {object} editor - The editor object. - * @property {object} adapter - The adapter providing input and editor references. - * @property {boolean} readOnly - Whether the input is read-only. - * @property {string} placeholder - The placeholder text. - * @property {string} value - The current value of the input. - * @property {number} selectionStart - The start offset of the selection. - * @property {number} selectionEnd - The end offset of the selection. - * @property {boolean} composing - Whether the editor is in composition mode. - * @property {number} selectionRangeCount - The number of selection ranges. */ -class SmartbarInputController { +export class SmartbarInputController { + /** + * @param {object} adapter + * Adapter with input and editor references. + * @param {MultilineEditor | HTMLInputElement} adapter.input + * The input element. + * @param {object} adapter.editor + * The editor object. + */ constructor(adapter) { - this.adapter = adapter; - this.input = adapter?.input ?? null; - this.editor = adapter?.editor ?? null; + /** @type {MultilineEditor | HTMLInputElement} */ + this.input = adapter.input; + /** @type {object} */ + this.editor = adapter.editor; } + /** + * Focuses the input element. + */ focus() { - this.input?.focus(); + this.input.focus(); + } + + /** + * Removes focus from the input element. + */ + blur() { + this.input.blur(); + } + + /** + * Whether the input is read-only. + * + * @type {boolean} + */ + get readOnly() { + return this.input.readOnly; + } + + /** + * Sets the read-only state of the input. + * + * @param {boolean} val + */ + set readOnly(val) { + this.input.readOnly = val; + } + + /** + * The placeholder text for the input. + * + * @type {string} + */ + get placeholder() { + return this.input.placeholder ?? ""; + } + + /** + * Sets the placeholder text for the input. + * + * @param {string} val + */ + set placeholder(val) { + this.input.placeholder = val ?? ""; + } + + /** + * The current value of the input. + * + * @type {string} + */ + get value() { + return this.input.value ?? ""; + } + + /** + * Sets the value of the input. + * + * @param {string} val + */ + setValue(val) { + this.input.value = val ?? ""; + } + + /** + * The start offset of the selection. + * + * @type {number} + */ + get selectionStart() { + return this.input.selectionStart ?? 0; } -} -export { SmartbarInputController }; + /** + * Sets the start offset of the selection. + * + * @param {number} val + */ + set selectionStart(val) { + this.setSelectionRange(val, this.selectionEnd ?? val); + } + + /** + * The end offset of the selection. + * + * @type {number} + */ + get selectionEnd() { + return this.input.selectionEnd ?? 0; + } + + /** + * Sets the end offset of the selection. + * + * @param {number} val + */ + set selectionEnd(val) { + this.setSelectionRange(this.selectionStart ?? 0, val); + } + + /** + * Sets the selection range in the input. + * + * @param {number} start + * The start offset. + * @param {number} end + * The end offset. + */ + setSelectionRange(start, end) { + if (!this.input.setSelectionRange) { + return; + } + const from = Math.max(0, start ?? 0); + const to = Math.max(from, end ?? from); + this.input.setSelectionRange(from, to); + } + + /** + * Selects all text in the input. + */ + select() { + this.input.select?.(); + } + + /** + * Dispatches an input event on the input element. + * + * @param {object} [options] + * The event options. + * @param {string} [options.inputType="insertText"] + * The input type. + * @param {string} [options.data=""] + * The data being inserted. + */ + dispatchInput({ inputType = "insertText", data = "" } = {}) { + this.input.dispatchEvent( + new InputEvent("input", { + bubbles: true, + composed: true, + inputType, + data, + }) + ); + } + + /** + * Dispatches a selectionchange event on the input element. + */ + dispatchSelectionChange() { + this.input.dispatchEvent( + new Event("selectionchange", { bubbles: true, composed: true }) + ); + } + + /** + * Whether the editor is in composition mode. + * + * @type {boolean} + */ + get composing() { + return this.editor.composing ?? false; + } + + /** + * The number of selection ranges in the editor. + * + * @type {number} + */ + get selectionRangeCount() { + return this.editor.selection?.rangeCount ?? 0; + } + + /** + * Returns the string representation of the current selection with formatting. + * + * @returns {string} + * The formatted selection string. + */ + selectionToStringWithFormat() { + return this.editor.selection?.toStringWithFormat() ?? ""; + } +} diff --git a/browser/components/urlbar/content/SmartbarInputUtils.mjs b/browser/components/urlbar/content/SmartbarInputUtils.mjs @@ -2,86 +2,87 @@ * 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 { MozLitElement } from "chrome://global/content/lit-utils.mjs"; +import { MultilineEditor } from "chrome://browser/content/multilineeditor/multiline-editor.mjs"; /** - * Minimal multiline editor placeholder for SmartBarInput. + * Creates a Smartbar editor element. + * + * @param {HTMLInputElement | MultilineEditor} inputElement + * The input element to replace. + * @returns {{ + * input: MultilineEditor, + * editor: object + * } | null} + * An object with the new editor element and the adapter. */ -class SmartbarEditor extends MozLitElement { - createRenderRoot() { - return this; - } - - get value() { - return this.textContent ?? ""; +export function createEditor(inputElement) { + if (!inputElement) { + return null; } - set value(val) { - this.textContent = val ?? ""; + if (inputElement instanceof MultilineEditor) { + return { + input: inputElement, + editor: createEditorAdapter(inputElement), + }; } - initializeFromInput(inputElement) { - if (!inputElement) { - return null; - } + const doc = inputElement.ownerDocument; + const editorElement = /** @type {MultilineEditor} */ ( + doc.createElement("moz-multiline-editor") + ); - // Copy existing input attributes. - for (const inputAttribute of inputElement.attributes) { - this.setAttribute(inputAttribute.name, inputAttribute.value); + // Copy attributes except those that don’t apply. + for (const attr of inputElement.attributes) { + if (attr.name == "type" || attr.name == "value") { + continue; } + editorElement.setAttribute(attr.name, attr.value); + } - this.setAttribute("contenteditable", "true"); - this.setAttribute("aria-multiline", "true"); - this.removeAttribute("type"); - this.removeAttribute("value"); - this.className = inputElement.className; - this.textContent = inputElement.value ?? ""; + editorElement.className = inputElement.className; + editorElement.id = inputElement.id; + editorElement.value = inputElement.value ?? ""; - inputElement.replaceWith(this); - return { input: this, editor: createEditorAdapter(this) }; - } + inputElement.replaceWith(editorElement); + return { + input: editorElement, + editor: createEditorAdapter(editorElement), + }; } -function createEditor(inputElement) { - if (!inputElement) { - return null; - } - if (inputElement.localName == "moz-smartbar-editor") { - return { - input: inputElement, - editor: createEditorAdapter(inputElement), - }; - } - const host = inputElement.ownerDocument.createElement("moz-smartbar-editor"); - return host.initializeFromInput(inputElement); -} +/** + * Creates an adapter for the Smartbar editor element. + * + * @param {MultilineEditor} editorElement + * The editor element. + */ +export function createEditorAdapter(editorElement) { + const getSelectionBounds = () => { + let start = editorElement.selectionStart ?? 0; + let end = editorElement.selectionEnd ?? start; + if (start > end) { + [start, end] = [end, start]; + } + return { start, end }; + }; -// Adapter for Smartbar editor element. -function createEditorAdapter(editorElement) { return { get composing() { - return false; + return !!editorElement.composing; }, selection: { get rangeCount() { - const selection = editorElement.ownerGlobal.getSelection(); - return selection?.rangeCount ?? 0; + const { start, end } = getSelectionBounds(); + return start === end && editorElement.value === "" ? 0 : 1; }, toStringWithFormat() { - const selection = editorElement.ownerGlobal.getSelection(); - if ( - !selection || - !selection.rangeCount || - !editorElement.contains(selection.anchorNode) - ) { + const { start, end } = getSelectionBounds(); + if (start == null || end == null) { return ""; } - return selection.toString(); + return editorElement.value?.substring(start, end); }, }, }; } - -customElements.define("moz-smartbar-editor", SmartbarEditor); - -export { createEditor, createEditorAdapter }; diff --git a/browser/locales-preview/aiWindow.ftl b/browser/locales-preview/aiWindow.ftl @@ -28,6 +28,11 @@ aiwindow-input-cta-label-chat = Chat aiwindow-input-cta-label-search = Search aiwindow-input-cta-label-navigate = Navigate +## Smartbar + +smartbar-placeholder = + .placeholder = Ask, search, or type a URL + ## Firstrun onboarding aiwindow-firstrun-title = Welcome to Smart Window diff --git a/third_party/js/prosemirror/moz.build b/third_party/js/prosemirror/moz.build @@ -8,5 +8,6 @@ with Files("**"): BUG_COMPONENT = ("Firefox", "General") MOZ_SRC_FILES += [ + "prosemirror-view/style/prosemirror.css", "prosemirror.bundle.mjs", ] diff --git a/tools/@types/generated/lib.gecko.modules.d.ts b/tools/@types/generated/lib.gecko.modules.d.ts @@ -19,11 +19,13 @@ export interface Modules { "chrome://browser/content/sidebar/sidebar-panel-header.mjs": typeof import("chrome://browser/content/sidebar/sidebar-panel-header.mjs"), "chrome://browser/content/tabbrowser/tab-hover-preview.mjs": typeof import("chrome://browser/content/tabbrowser/tab-hover-preview.mjs"), "chrome://browser/content/translations/TranslationsPanelShared.sys.mjs": typeof import("chrome://browser/content/translations/TranslationsPanelShared.sys.mjs"), + "chrome://browser/content/urlbar/SmartbarInput.mjs": typeof import("chrome://browser/content/urlbar/SmartbarInput.mjs"), "chrome://browser/content/urlbar/UrlbarInput.mjs": typeof import("chrome://browser/content/urlbar/UrlbarInput.mjs"), "chrome://browser/content/webrtc/webrtc-preview.mjs": typeof import("chrome://browser/content/webrtc/webrtc-preview.mjs"), "chrome://devtools-startup/content/DevToolsShim.sys.mjs": typeof import("chrome://devtools-startup/content/DevToolsShim.sys.mjs"), "chrome://formautofill/content/manageDialog.mjs": typeof import("chrome://formautofill/content/manageDialog.mjs"), "chrome://global/content/aboutLogging/profileStorage.mjs": typeof import("chrome://global/content/aboutLogging/profileStorage.mjs"), + "chrome://global/content/bindings/colorpicker-common.mjs": typeof import("chrome://global/content/bindings/colorpicker-common.mjs"), "chrome://global/content/certviewer/certDecoder.mjs": typeof import("chrome://global/content/certviewer/certDecoder.mjs"), "chrome://global/content/elements/browser-custom-element.mjs": typeof import("chrome://global/content/elements/browser-custom-element.mjs"), "chrome://global/content/ml/BlockWords.sys.mjs": typeof import("chrome://global/content/ml/BlockWords.sys.mjs"), @@ -42,6 +44,12 @@ export interface Modules { "chrome://global/content/ml/backends/OpenAIPipeline.mjs": typeof import("chrome://global/content/ml/backends/OpenAIPipeline.mjs"), "chrome://global/content/ml/backends/Pipeline.mjs": typeof import("chrome://global/content/ml/backends/Pipeline.mjs"), "chrome://global/content/ml/backends/StaticEmbeddingsPipeline.mjs": typeof import("chrome://global/content/ml/backends/StaticEmbeddingsPipeline.mjs"), + "chrome://global/content/ml/security/ConditionEvaluator.sys.mjs": typeof import("chrome://global/content/ml/security/ConditionEvaluator.sys.mjs"), + "chrome://global/content/ml/security/DecisionTypes.sys.mjs": typeof import("chrome://global/content/ml/security/DecisionTypes.sys.mjs"), + "chrome://global/content/ml/security/PolicyEvaluator.sys.mjs": typeof import("chrome://global/content/ml/security/PolicyEvaluator.sys.mjs"), + "chrome://global/content/ml/security/SecurityLogger.sys.mjs": typeof import("chrome://global/content/ml/security/SecurityLogger.sys.mjs"), + "chrome://global/content/ml/security/SecurityOrchestrator.sys.mjs": typeof import("chrome://global/content/ml/security/SecurityOrchestrator.sys.mjs"), + "chrome://global/content/ml/security/SecurityUtils.sys.mjs": typeof import("chrome://global/content/ml/security/SecurityUtils.sys.mjs"), "chrome://global/content/preferences/Preferences.mjs": typeof import("chrome://global/content/preferences/Preferences.mjs"), "chrome://global/content/translations/TranslationsTelemetry.sys.mjs": typeof import("chrome://global/content/translations/TranslationsTelemetry.sys.mjs"), "chrome://global/content/translations/TranslationsUtils.mjs": typeof import("chrome://global/content/translations/TranslationsUtils.mjs"), @@ -124,6 +132,7 @@ export interface Modules { "chrome://remote/content/shared/listeners/BeforeStopRequestListener.sys.mjs": typeof import("chrome://remote/content/shared/listeners/BeforeStopRequestListener.sys.mjs"), "chrome://remote/content/shared/listeners/BrowsingContextListener.sys.mjs": typeof import("chrome://remote/content/shared/listeners/BrowsingContextListener.sys.mjs"), "chrome://remote/content/shared/listeners/CachedResourceListener.sys.mjs": typeof import("chrome://remote/content/shared/listeners/CachedResourceListener.sys.mjs"), + "chrome://remote/content/shared/listeners/ChromeWindowListener.sys.mjs": typeof import("chrome://remote/content/shared/listeners/ChromeWindowListener.sys.mjs"), "chrome://remote/content/shared/listeners/ConsoleAPIListener.sys.mjs": typeof import("chrome://remote/content/shared/listeners/ConsoleAPIListener.sys.mjs"), "chrome://remote/content/shared/listeners/ConsoleListener.sys.mjs": typeof import("chrome://remote/content/shared/listeners/ConsoleListener.sys.mjs"), "chrome://remote/content/shared/listeners/ContextualIdentityListener.sys.mjs": typeof import("chrome://remote/content/shared/listeners/ContextualIdentityListener.sys.mjs"), @@ -200,20 +209,26 @@ export interface Modules { "moz-src:///browser/components/StartupTelemetry.sys.mjs": typeof import("moz-src:///browser/components/StartupTelemetry.sys.mjs"), "moz-src:///browser/components/aiwindow/models/Chat.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/Chat.sys.mjs"), "moz-src:///browser/components/aiwindow/models/ChatUtils.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/ChatUtils.sys.mjs"), + "moz-src:///browser/components/aiwindow/models/ConversationSuggestions.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/ConversationSuggestions.sys.mjs"), "moz-src:///browser/components/aiwindow/models/Insights.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/Insights.sys.mjs"), "moz-src:///browser/components/aiwindow/models/InsightsChatSource.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/InsightsChatSource.sys.mjs"), "moz-src:///browser/components/aiwindow/models/InsightsConstants.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/InsightsConstants.sys.mjs"), + "moz-src:///browser/components/aiwindow/models/InsightsConversationScheduler.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/InsightsConversationScheduler.sys.mjs"), "moz-src:///browser/components/aiwindow/models/InsightsDriftDetector.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/InsightsDriftDetector.sys.mjs"), + "moz-src:///browser/components/aiwindow/models/InsightsHistoryScheduler.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/InsightsHistoryScheduler.sys.mjs"), "moz-src:///browser/components/aiwindow/models/InsightsHistorySource.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/InsightsHistorySource.sys.mjs"), "moz-src:///browser/components/aiwindow/models/InsightsManager.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/InsightsManager.sys.mjs"), "moz-src:///browser/components/aiwindow/models/IntentClassifier.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/IntentClassifier.sys.mjs"), "moz-src:///browser/components/aiwindow/models/SearchBrowsingHistory.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/SearchBrowsingHistory.sys.mjs"), + "moz-src:///browser/components/aiwindow/models/SearchBrowsingHistoryDomainBoost.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/SearchBrowsingHistoryDomainBoost.sys.mjs"), "moz-src:///browser/components/aiwindow/models/TitleGeneration.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/TitleGeneration.sys.mjs"), "moz-src:///browser/components/aiwindow/models/Tools.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/Tools.sys.mjs"), "moz-src:///browser/components/aiwindow/models/Utils.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/Utils.sys.mjs"), "moz-src:///browser/components/aiwindow/models/prompts/InsightsPrompts.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/models/prompts/InsightsPrompts.sys.mjs"), "moz-src:///browser/components/aiwindow/services/InsightStore.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/services/InsightStore.sys.mjs"), "moz-src:///browser/components/aiwindow/ui/modules/AIWindow.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/ui/modules/AIWindow.sys.mjs"), + "moz-src:///browser/components/aiwindow/ui/modules/AIWindowAccountAuth.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/ui/modules/AIWindowAccountAuth.sys.mjs"), + "moz-src:///browser/components/aiwindow/ui/modules/AIWindowMenu.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/ui/modules/AIWindowMenu.sys.mjs"), "moz-src:///browser/components/aiwindow/ui/modules/ChatMessage.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/ui/modules/ChatMessage.sys.mjs"), "moz-src:///browser/components/aiwindow/ui/modules/ChatStore.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/ui/modules/ChatStore.sys.mjs"), "moz-src:///browser/components/aiwindow/ui/modules/ChatUtils.sys.mjs": typeof import("moz-src:///browser/components/aiwindow/ui/modules/ChatUtils.sys.mjs"), @@ -352,6 +367,8 @@ export interface Modules { "moz-src:///toolkit/components/uniffi-bindgen-gecko-js/components/generated/RustWebextstorage.sys.mjs": typeof import("moz-src:///toolkit/components/uniffi-bindgen-gecko-js/components/generated/RustWebextstorage.sys.mjs"), "moz-src:///toolkit/components/uniffi-bindgen-gecko-js/tests/generated/RustUniffiBindingsTests.sys.mjs": typeof import("moz-src:///toolkit/components/uniffi-bindgen-gecko-js/tests/generated/RustUniffiBindingsTests.sys.mjs"), "moz-src:///toolkit/components/uniffi-bindgen-gecko-js/tests/generated/RustUniffiBindingsTestsExternalTypes.sys.mjs": typeof import("moz-src:///toolkit/components/uniffi-bindgen-gecko-js/tests/generated/RustUniffiBindingsTestsExternalTypes.sys.mjs"), + "moz-src:///toolkit/modules/ColorPickerPanel.sys.mjs": typeof import("moz-src:///toolkit/modules/ColorPickerPanel.sys.mjs"), + "moz-src:///toolkit/modules/DateTimePickerPanel.sys.mjs": typeof import("moz-src:///toolkit/modules/DateTimePickerPanel.sys.mjs"), "moz-src:///toolkit/modules/PrefUtils.sys.mjs": typeof import("moz-src:///toolkit/modules/PrefUtils.sys.mjs"), "moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs": typeof import("moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs"), "resource:///actors/AboutLoginsParent.sys.mjs": typeof import("resource:///actors/AboutLoginsParent.sys.mjs"), @@ -361,7 +378,6 @@ export interface Modules { "resource:///actors/AboutReaderParent.sys.mjs": typeof import("resource:///actors/AboutReaderParent.sys.mjs"), "resource:///actors/AboutWelcomeParent.sys.mjs": typeof import("resource:///actors/AboutWelcomeParent.sys.mjs"), "resource:///actors/ClickHandlerParent.sys.mjs": typeof import("resource:///actors/ClickHandlerParent.sys.mjs"), - "resource:///actors/ContentSearchParent.sys.mjs": typeof import("resource:///actors/ContentSearchParent.sys.mjs"), "resource:///actors/ContextMenuChild.sys.mjs": typeof import("resource:///actors/ContextMenuChild.sys.mjs"), "resource:///actors/LinkHandlerParent.sys.mjs": typeof import("resource:///actors/LinkHandlerParent.sys.mjs"), "resource:///actors/LinkPreviewChild.sys.mjs": typeof import("resource:///actors/LinkPreviewChild.sys.mjs"), @@ -477,6 +493,7 @@ export interface Modules { "resource:///modules/backup/BackupError.mjs": typeof import("resource:///modules/backup/BackupError.mjs"), "resource:///modules/backup/BackupResource.sys.mjs": typeof import("resource:///modules/backup/BackupResource.sys.mjs"), "resource:///modules/backup/BackupService.sys.mjs": typeof import("resource:///modules/backup/BackupService.sys.mjs"), + "resource:///modules/backup/BookmarksBackupResource.sys.mjs": typeof import("resource:///modules/backup/BookmarksBackupResource.sys.mjs"), "resource:///modules/backup/CookiesBackupResource.sys.mjs": typeof import("resource:///modules/backup/CookiesBackupResource.sys.mjs"), "resource:///modules/backup/CredentialsAndSecurityBackupResource.sys.mjs": typeof import("resource:///modules/backup/CredentialsAndSecurityBackupResource.sys.mjs"), "resource:///modules/backup/FormHistoryBackupResource.sys.mjs": typeof import("resource:///modules/backup/FormHistoryBackupResource.sys.mjs"), @@ -485,6 +502,7 @@ export interface Modules { "resource:///modules/backup/PlacesBackupResource.sys.mjs": typeof import("resource:///modules/backup/PlacesBackupResource.sys.mjs"), "resource:///modules/backup/PreferencesBackupResource.sys.mjs": typeof import("resource:///modules/backup/PreferencesBackupResource.sys.mjs"), "resource:///modules/backup/SessionStoreBackupResource.sys.mjs": typeof import("resource:///modules/backup/SessionStoreBackupResource.sys.mjs"), + "resource:///modules/backup/SiteSettingsBackupResource.sys.mjs": typeof import("resource:///modules/backup/SiteSettingsBackupResource.sys.mjs"), "resource:///modules/distribution.sys.mjs": typeof import("resource:///modules/distribution.sys.mjs"), "resource:///modules/firefox-view-synced-tabs-error-handler.sys.mjs": typeof import("resource:///modules/firefox-view-synced-tabs-error-handler.sys.mjs"), "resource:///modules/firefox-view-tabs-setup-manager.sys.mjs": typeof import("resource:///modules/firefox-view-tabs-setup-manager.sys.mjs"), @@ -701,7 +719,6 @@ export interface Modules { "resource://gre/modules/DAPSender.sys.mjs": typeof import("resource://gre/modules/DAPSender.sys.mjs"), "resource://gre/modules/DAPTelemetrySender.sys.mjs": typeof import("resource://gre/modules/DAPTelemetrySender.sys.mjs"), "resource://gre/modules/DAPVisitCounter.sys.mjs": typeof import("resource://gre/modules/DAPVisitCounter.sys.mjs"), - "resource://gre/modules/DateTimePickerPanel.sys.mjs": typeof import("resource://gre/modules/DateTimePickerPanel.sys.mjs"), "resource://gre/modules/DeferredTask.sys.mjs": typeof import("resource://gre/modules/DeferredTask.sys.mjs"), "resource://gre/modules/DelayedInit.sys.mjs": typeof import("resource://gre/modules/DelayedInit.sys.mjs"), "resource://gre/modules/DownloadCore.sys.mjs": typeof import("resource://gre/modules/DownloadCore.sys.mjs"), @@ -979,6 +996,7 @@ export interface Modules { "resource://gre/modules/narrate/NarrateControls.sys.mjs": typeof import("resource://gre/modules/narrate/NarrateControls.sys.mjs"), "resource://gre/modules/policies/WindowsGPOParser.sys.mjs": typeof import("resource://gre/modules/policies/WindowsGPOParser.sys.mjs"), "resource://gre/modules/policies/macOSPoliciesParser.sys.mjs": typeof import("resource://gre/modules/policies/macOSPoliciesParser.sys.mjs"), + "resource://gre/modules/psm/QWACs.sys.mjs": typeof import("resource://gre/modules/psm/QWACs.sys.mjs"), "resource://gre/modules/psm/RemoteSecuritySettings.sys.mjs": typeof import("resource://gre/modules/psm/RemoteSecuritySettings.sys.mjs"), "resource://gre/modules/psm/X509.sys.mjs": typeof import("resource://gre/modules/psm/X509.sys.mjs"), "resource://gre/modules/psm/pippki.sys.mjs": typeof import("resource://gre/modules/psm/pippki.sys.mjs"), @@ -1216,6 +1234,7 @@ export interface Modules { "resource://test/non_shared_nest_import_shared_target_1.sys.mjs": typeof import("resource://test/non_shared_nest_import_shared_target_1.sys.mjs"), "resource://test/non_shared_nest_import_shared_target_2.sys.mjs": typeof import("resource://test/non_shared_nest_import_shared_target_2.sys.mjs"), "resource://test/not_found.mjs": typeof import("resource://test/not_found.mjs"), + "resource://testing-common/AIWindowTestUtils.sys.mjs": typeof import("resource://testing-common/AIWindowTestUtils.sys.mjs"), "resource://testing-common/AddonTestUtils.sys.mjs": typeof import("resource://testing-common/AddonTestUtils.sys.mjs"), "resource://testing-common/AllJavascriptTypes.mjs": typeof import("resource://testing-common/AllJavascriptTypes.mjs"), "resource://testing-common/AppData.sys.mjs": typeof import("resource://testing-common/AppData.sys.mjs"), diff --git a/tools/@types/generated/tspaths.json b/tools/@types/generated/tspaths.json @@ -17,6 +17,12 @@ "chrome://browser/content/aboutlogins/components/login-message-popup.mjs": [ "browser/components/aboutlogins/content/components/login-message-popup.mjs" ], + "chrome://browser/content/aiwindow/components/ai-chat-content.mjs": [ + "browser/components/aiwindow/ui/components/ai-chat-content/ai-chat-content.mjs" + ], + "chrome://browser/content/aiwindow/components/ai-chat-message.mjs": [ + "browser/components/aiwindow/ui/components/ai-chat-message/ai-chat-message.mjs" + ], "chrome://browser/content/aiwindow/components/input-cta.mjs": [ "browser/components/aiwindow/ui/components/input-cta/input-cta.mjs" ], @@ -95,15 +101,18 @@ "chrome://browser/content/ipprotection/ipprotection-status-card.mjs": [ "browser/components/ipprotection/content/ipprotection-status-card.mjs" ], - "chrome://browser/content/ipprotection/ipprotection-timer.mjs": [ - "browser/components/ipprotection/content/ipprotection-timer.mjs" - ], "chrome://browser/content/migration/migration-wizard-constants.mjs": [ "browser/components/migration/content/migration-wizard-constants.mjs" ], "chrome://browser/content/migration/migration-wizard.mjs": [ "browser/components/migration/content/migration-wizard.mjs" ], + "chrome://browser/content/multilineeditor/multiline-editor.mjs": [ + "browser/components/multilineeditor/multiline-editor.mjs" + ], + "chrome://browser/content/multilineeditor/prosemirror.bundle.mjs": [ + "third_party/js/prosemirror/prosemirror.bundle.mjs" + ], "chrome://browser/content/nsContextMenu.sys.mjs": [ "browser/base/content/nsContextMenu.sys.mjs" ], @@ -173,6 +182,15 @@ "chrome://browser/content/translations/TranslationsPanelShared.sys.mjs": [ "browser/components/translations/content/TranslationsPanelShared.sys.mjs" ], + "chrome://browser/content/urlbar/SmartbarInput.mjs": [ + "browser/components/urlbar/content/SmartbarInput.mjs" + ], + "chrome://browser/content/urlbar/SmartbarInputController.mjs": [ + "browser/components/urlbar/content/SmartbarInputController.mjs" + ], + "chrome://browser/content/urlbar/SmartbarInputUtils.mjs": [ + "browser/components/urlbar/content/SmartbarInputUtils.mjs" + ], "chrome://browser/content/urlbar/UrlbarInput.mjs": [ "browser/components/urlbar/content/UrlbarInput.mjs" ], @@ -224,6 +242,9 @@ "chrome://global/content/aboutwebrtc/graphdb.mjs": [ "toolkit/content/aboutwebrtc/graphdb.mjs" ], + "chrome://global/content/bindings/colorpicker-common.mjs": [ + "toolkit/content/widgets/colorpicker-common.mjs" + ], "chrome://global/content/certviewer/certDecoder.mjs": [ "toolkit/components/certviewer/content/certDecoder.mjs" ], @@ -350,6 +371,24 @@ "chrome://global/content/ml/ort.webgpu-dev.mjs": [ "toolkit/components/ml/vendor/ort.webgpu-dev.mjs" ], + "chrome://global/content/ml/security/ConditionEvaluator.sys.mjs": [ + "toolkit/components/ml/security/ConditionEvaluator.sys.mjs" + ], + "chrome://global/content/ml/security/DecisionTypes.sys.mjs": [ + "toolkit/components/ml/security/DecisionTypes.sys.mjs" + ], + "chrome://global/content/ml/security/PolicyEvaluator.sys.mjs": [ + "toolkit/components/ml/security/PolicyEvaluator.sys.mjs" + ], + "chrome://global/content/ml/security/SecurityLogger.sys.mjs": [ + "toolkit/components/ml/security/SecurityLogger.sys.mjs" + ], + "chrome://global/content/ml/security/SecurityOrchestrator.sys.mjs": [ + "toolkit/components/ml/security/SecurityOrchestrator.sys.mjs" + ], + "chrome://global/content/ml/security/SecurityUtils.sys.mjs": [ + "toolkit/components/ml/security/SecurityUtils.sys.mjs" + ], "chrome://global/content/ml/transformers-dev.js": [ "toolkit/components/ml/vendor/transformers-dev.js" ], @@ -656,6 +695,9 @@ "chrome://remote/content/shared/listeners/CachedResourceListener.sys.mjs": [ "remote/shared/listeners/CachedResourceListener.sys.mjs" ], + "chrome://remote/content/shared/listeners/ChromeWindowListener.sys.mjs": [ + "remote/shared/listeners/ChromeWindowListener.sys.mjs" + ], "chrome://remote/content/shared/listeners/ConsoleAPIListener.sys.mjs": [ "remote/shared/listeners/ConsoleAPIListener.sys.mjs" ], @@ -893,9 +935,6 @@ "resource:///actors/ClickHandlerParent.sys.mjs": [ "browser/actors/ClickHandlerParent.sys.mjs" ], - "resource:///actors/ContentSearchParent.sys.mjs": [ - "browser/actors/ContentSearchParent.sys.mjs" - ], "resource:///actors/ContextMenuChild.sys.mjs": [ "browser/actors/ContextMenuChild.sys.mjs" ], @@ -1256,6 +1295,9 @@ "resource:///modules/backup/BackupService.sys.mjs": [ "browser/components/backup/BackupService.sys.mjs" ], + "resource:///modules/backup/BookmarksBackupResource.sys.mjs": [ + "browser/components/backup/resources/BookmarksBackupResource.sys.mjs" + ], "resource:///modules/backup/CookiesBackupResource.sys.mjs": [ "browser/components/backup/resources/CookiesBackupResource.sys.mjs" ], @@ -1280,6 +1322,9 @@ "resource:///modules/backup/SessionStoreBackupResource.sys.mjs": [ "browser/components/backup/resources/SessionStoreBackupResource.sys.mjs" ], + "resource:///modules/backup/SiteSettingsBackupResource.sys.mjs": [ + "browser/components/backup/resources/SiteSettingsBackupResource.sys.mjs" + ], "resource:///modules/distribution.sys.mjs": [ "browser/components/distribution.sys.mjs" ], @@ -5444,9 +5489,6 @@ "resource://gre/modules/DAPVisitCounter.sys.mjs": [ "toolkit/components/dap/DAPVisitCounter.sys.mjs" ], - "resource://gre/modules/DateTimePickerPanel.sys.mjs": [ - "toolkit/modules/DateTimePickerPanel.sys.mjs" - ], "resource://gre/modules/DeferredTask.sys.mjs": [ "toolkit/modules/DeferredTask.sys.mjs" ], @@ -6338,6 +6380,9 @@ "resource://gre/modules/psm/DER.sys.mjs": [ "security/manager/ssl/DER.sys.mjs" ], + "resource://gre/modules/psm/QWACs.sys.mjs": [ + "security/manager/ssl/QWACs.sys.mjs" + ], "resource://gre/modules/psm/RemoteSecuritySettings.sys.mjs": [ "security/manager/ssl/RemoteSecuritySettings.sys.mjs" ], @@ -7115,6 +7160,9 @@ "resource://test/webextension-helpers.js": [ "devtools/server/tests/xpcshell/webextension-helpers.js" ], + "resource://testing-common/AIWindowTestUtils.sys.mjs": [ + "browser/components/aiwindow/ui/test/AIWindowTestUtils.sys.mjs" + ], "resource://testing-common/AddonTestUtils.sys.mjs": [ "toolkit/mozapps/extensions/internal/AddonTestUtils.sys.mjs" ],