Update On Tue Aug 6 20:49:55 CEST 2024
This commit is contained in:
parent
d64d696764
commit
18b2188e4e
566 changed files with 17199 additions and 10292 deletions
|
@ -958,11 +958,7 @@ pref("browser.tabs.tooltipsShowPidAndActiveness", true);
|
|||
pref("browser.tabs.tooltipsShowPidAndActiveness", false);
|
||||
#endif
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
pref("browser.tabs.hoverPreview.enabled", true);
|
||||
#else
|
||||
pref("browser.tabs.hoverPreview.enabled", false);
|
||||
#endif
|
||||
pref("browser.tabs.hoverPreview.showThumbnails", true);
|
||||
|
||||
pref("browser.tabs.firefox-view.logLevel", "Warn");
|
||||
|
|
|
@ -109,14 +109,21 @@
|
|||
noautofocus="true"
|
||||
hidden="true" />
|
||||
|
||||
<panel id="searchmode-switcher-popup" noautofocus="true" orient="vertical" type="arrow">
|
||||
<panel id="searchmode-switcher-popup"
|
||||
class="panel-no-padding"
|
||||
orient="vertical"
|
||||
type="autocomplete-richlistbox"
|
||||
role="menu"
|
||||
aria-labelledby="searchmode-switcher-popup-description">
|
||||
<label data-l10n-id="urlbar-searchmode-popup-description" id="searchmode-switcher-popup-description" />
|
||||
<toolbarseparator />
|
||||
<vbox class="panel-subview-body"></vbox>
|
||||
<toolbarseparator />
|
||||
<toolbarbutton oncommand="window.openPreferences('paneSearch');"
|
||||
class="subviewbutton subviewbutton-iconic"
|
||||
id="searchmode-switcher-popup-search-settings-button">
|
||||
<toolbarbutton class="subviewbutton subviewbutton-iconic panel-subview-footer-button"
|
||||
data-action="openpreferences"
|
||||
id="searchmode-switcher-popup-search-settings-button"
|
||||
role="button"
|
||||
tabindex="0">
|
||||
<image id="searchmode-switcher-popup-search-settings-icon" />
|
||||
<label data-l10n-id="urlbar-searchmode-popup-search-settings" />
|
||||
</toolbarbutton>
|
||||
|
|
|
@ -313,15 +313,26 @@
|
|||
</box>
|
||||
</box>
|
||||
<toolbarbutton id="urlbar-searchmode-switcher"
|
||||
data-l10n-id="urlbar-searchmode-button"
|
||||
data-action="openpopup"
|
||||
align="center" noautofocus="true" tooltip="dynamic-shortcut-tooltip" class="chromeclass-toolbar-additional">
|
||||
align="center"
|
||||
role="button"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
tooltip="dynamic-shortcut-tooltip"
|
||||
class="chromeclass-toolbar-additional">
|
||||
<image id="searchmode-switcher-icon" />
|
||||
<label id="searchmode-switcher-title" />
|
||||
<image id="searchmode-switcher-dropmarker" data-l10n-id="urlbar-searchmode-dropmarker"
|
||||
<image id="searchmode-switcher-dropmarker"
|
||||
data-l10n-id="urlbar-searchmode-dropmarker"
|
||||
class="toolbarbutton-icon toolbarbutton-combined-buttons-dropmarker" />
|
||||
<toolbarbutton id="searchmode-switcher-close" data-action="exitsearchmode" noautofocus="true" class="toolbarbutton-icon close-button" />
|
||||
</toolbarbutton>
|
||||
<box id="searchmode-switcher-chicklet">
|
||||
<label id="searchmode-switcher-title" />
|
||||
<toolbarbutton id="searchmode-switcher-close"
|
||||
data-action="exitsearchmode"
|
||||
role="button"
|
||||
tooltip="dynamic-shortcut-tooltip"
|
||||
class="toolbarbutton-icon close-button" />
|
||||
</box>
|
||||
<box id="urlbar-label-box" align="center">
|
||||
<label id="urlbar-label-switchtab" class="urlbar-label" data-l10n-id="urlbar-switch-to-tab"/>
|
||||
<label id="urlbar-label-extension" class="urlbar-label" data-l10n-id="urlbar-extension"/>
|
||||
|
|
|
@ -429,17 +429,29 @@ class TabTracker extends TabTrackerBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the opener of `tab` to the ID `openerTab`. Both tabs must be in the
|
||||
* same window, or this function will throw a type error.
|
||||
* Sets the opener of `tab` to the ID `openerTabId`. Both tabs must be in the
|
||||
* same window, or this function will throw an error. if `openerTabId` is `-1`
|
||||
* the opener tab is cleared.
|
||||
*
|
||||
* @param {Element} tab The tab for which to set the owner.
|
||||
* @param {Element} openerTab The opener of <tab>.
|
||||
* @param {Element} nativeTab The tab for which to set the owner.
|
||||
* @param {number} openerTabId The openerTabId of <tab>.
|
||||
*/
|
||||
setOpener(tab, openerTab) {
|
||||
if (tab.ownerDocument !== openerTab.ownerDocument) {
|
||||
throw new Error("Tab must be in the same window as its opener");
|
||||
setOpener(nativeTab, openerTabId) {
|
||||
let nativeOpenerTab = null;
|
||||
|
||||
if (openerTabId > -1) {
|
||||
nativeOpenerTab = tabTracker.getTab(openerTabId);
|
||||
if (nativeTab.ownerDocument !== nativeOpenerTab.ownerDocument) {
|
||||
throw new ExtensionError(
|
||||
"Opener tab must be in the same window as the tab being updated"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (nativeTab.openerTab !== nativeOpenerTab) {
|
||||
nativeTab.openerTab = nativeOpenerTab;
|
||||
this.emit("tab-openerTabId", { nativeTab, openerTabId });
|
||||
}
|
||||
tab.openerTab = openerTab;
|
||||
}
|
||||
|
||||
deferredForTabOpen(nativeTab) {
|
||||
|
|
|
@ -160,6 +160,7 @@ const allProperties = new Set([
|
|||
"hidden",
|
||||
"isArticle",
|
||||
"mutedInfo",
|
||||
"openerTabId",
|
||||
"pinned",
|
||||
"sharingState",
|
||||
"status",
|
||||
|
@ -503,6 +504,11 @@ this.tabs = class extends ExtensionAPIPersistent {
|
|||
}
|
||||
};
|
||||
|
||||
let openerTabIdChangeListener = (_, { nativeTab, openerTabId }) => {
|
||||
let tab = tabManager.getWrapper(nativeTab);
|
||||
fireForTab(tab, { openerTabId }, nativeTab);
|
||||
};
|
||||
|
||||
let listeners = new Map();
|
||||
if (filter.properties.has("status") || filter.properties.has("url")) {
|
||||
listeners.set("status", statusListener);
|
||||
|
@ -531,6 +537,10 @@ this.tabs = class extends ExtensionAPIPersistent {
|
|||
tabTracker.on("tab-isarticle", isArticleChangeListener);
|
||||
}
|
||||
|
||||
if (filter.properties.has("openerTabId")) {
|
||||
tabTracker.on("tab-openerTabId", openerTabIdChangeListener);
|
||||
}
|
||||
|
||||
return {
|
||||
unregister() {
|
||||
for (let [name, listener] of listeners) {
|
||||
|
@ -540,6 +550,10 @@ this.tabs = class extends ExtensionAPIPersistent {
|
|||
if (filter.properties.has("isArticle")) {
|
||||
tabTracker.off("tab-isarticle", isArticleChangeListener);
|
||||
}
|
||||
|
||||
if (filter.properties.has("openerTabId")) {
|
||||
tabTracker.off("tab-openerTabId", openerTabIdChangeListener);
|
||||
}
|
||||
},
|
||||
convert(_fire, _context) {
|
||||
fire = _fire;
|
||||
|
@ -939,14 +953,7 @@ this.tabs = class extends ExtensionAPIPersistent {
|
|||
}
|
||||
}
|
||||
if (updateProperties.openerTabId !== null) {
|
||||
let opener = tabTracker.getTab(updateProperties.openerTabId);
|
||||
if (opener.ownerDocument !== nativeTab.ownerDocument) {
|
||||
return Promise.reject({
|
||||
message:
|
||||
"Opener tab must be in the same window as the tab being updated",
|
||||
});
|
||||
}
|
||||
tabTracker.setOpener(nativeTab, opener);
|
||||
tabTracker.setOpener(nativeTab, updateProperties.openerTabId);
|
||||
}
|
||||
if (updateProperties.successorTabId !== null) {
|
||||
let successor = null;
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
},
|
||||
"openerTabId": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"minimum": -1,
|
||||
"optional": true,
|
||||
"description": "The ID of the tab that opened this tab, if any. This property is only present if the opener tab still exists."
|
||||
},
|
||||
|
@ -623,7 +623,7 @@
|
|||
},
|
||||
"openerTabId": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"minimum": -1,
|
||||
"optional": true,
|
||||
"description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as the newly created tab."
|
||||
},
|
||||
|
@ -828,7 +828,7 @@
|
|||
},
|
||||
"openerTabId": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"minimum": -1,
|
||||
"optional": true,
|
||||
"description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as this tab."
|
||||
},
|
||||
|
@ -967,7 +967,7 @@
|
|||
},
|
||||
"openerTabId": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"minimum": -1,
|
||||
"optional": true,
|
||||
"description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as this tab."
|
||||
},
|
||||
|
|
|
@ -128,3 +128,114 @@ add_task(async function () {
|
|||
BrowserTestUtils.removeTab(tab1);
|
||||
BrowserTestUtils.removeTab(tab2);
|
||||
});
|
||||
|
||||
add_task(async function test_tabs_onUpdated_fired_on_openerTabId_change() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
permissions: ["tabs"],
|
||||
},
|
||||
|
||||
async background() {
|
||||
let changedOpenerTabIds = [];
|
||||
let promise = new Promise(resolve => {
|
||||
browser.tabs.onUpdated.addListener((tabId, changeInfo, _tab) => {
|
||||
const { openerTabId } = changeInfo;
|
||||
if (openerTabId) {
|
||||
browser.test.assertDeepEq(
|
||||
{ openerTabId },
|
||||
changeInfo,
|
||||
`"openerTabId" is the only key in changeInfo for tab ${tabId}`
|
||||
);
|
||||
changedOpenerTabIds.push(openerTabId);
|
||||
if (openerTabId === -1) {
|
||||
// The last part of the test changes openerTabId back to -1.
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
let tab1 = await browser.tabs.create({});
|
||||
let tab2 = await browser.tabs.create({});
|
||||
|
||||
browser.test.assertDeepEq(
|
||||
[],
|
||||
changedOpenerTabIds,
|
||||
"No tabs.onUpdated fired with openerTabId at tab creation"
|
||||
);
|
||||
|
||||
// Not changed, should not emit event:
|
||||
await browser.tabs.update(tab1.id, { openerTabId: -1 });
|
||||
// Should emit event:
|
||||
await browser.tabs.update(tab1.id, { openerTabId: tab2.id });
|
||||
// Not changed, should not emit event:
|
||||
await browser.tabs.update(tab1.id, { openerTabId: tab2.id });
|
||||
// Should emit event:
|
||||
await browser.tabs.update(tab1.id, { openerTabId: -1 });
|
||||
await promise;
|
||||
|
||||
browser.test.assertDeepEq(
|
||||
[tab2.id, -1],
|
||||
changedOpenerTabIds,
|
||||
"Got expected tabs.onUpdated for openerTabId changes"
|
||||
);
|
||||
|
||||
await browser.tabs.remove(tab1.id);
|
||||
await browser.tabs.remove(tab2.id);
|
||||
|
||||
browser.test.notifyPass("tab-onUpdated-opener");
|
||||
} catch (e) {
|
||||
browser.test.fail(`${e} :: ${e.stack}`);
|
||||
browser.test.notifyFail("tab-onUpdated-opener");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitFinish("tab-onUpdated-opener");
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_errors_on_openerTabId_change() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
permissions: ["tabs"],
|
||||
},
|
||||
|
||||
async background() {
|
||||
let tab = await browser.tabs.create({});
|
||||
|
||||
await browser.test.assertRejects(
|
||||
browser.tabs.update(tab.id, { openerTabId: 123456789 }),
|
||||
"Invalid tab ID: 123456789",
|
||||
"Got error when openerTabId is invalid"
|
||||
);
|
||||
|
||||
let win = await browser.windows.create({ url: "about:blank" });
|
||||
|
||||
await browser.test.assertRejects(
|
||||
browser.tabs.update(tab.id, { openerTabId: win.tabs[0].id }),
|
||||
"Opener tab must be in the same window as the tab being updated",
|
||||
"Got error when openerTabId belongs to a different window"
|
||||
);
|
||||
|
||||
tab = await browser.tabs.get(tab.id);
|
||||
|
||||
browser.test.assertEq(
|
||||
undefined,
|
||||
tab.openerTabId,
|
||||
"Got initial tab.openerTabId after failing updates"
|
||||
);
|
||||
|
||||
await browser.windows.remove(win.id);
|
||||
await browser.tabs.remove(tab.id);
|
||||
|
||||
browser.test.notifyPass("tab-opener-with-wrong-window");
|
||||
},
|
||||
});
|
||||
|
||||
await extension.startup();
|
||||
await extension.awaitFinish("tab-opener-with-wrong-window");
|
||||
await extension.unload();
|
||||
});
|
||||
|
|
|
@ -13,8 +13,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
|
||||
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
|
||||
PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs",
|
||||
clearTimeout: "resource://gre/modules/Timer.sys.mjs",
|
||||
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
||||
});
|
||||
ChromeUtils.defineLazyGetter(
|
||||
lazy,
|
||||
|
@ -89,12 +87,6 @@ XPCOMUtils.defineLazyPreferenceGetter(
|
|||
"chatShortcutsCustom",
|
||||
"browser.ml.chat.shortcuts.custom"
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"chatShortcutsDebounce",
|
||||
"browser.ml.chat.shortcutsDebounce",
|
||||
200
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"chatSidebar",
|
||||
|
@ -329,7 +321,7 @@ export const GenAI = {
|
|||
case "GenAI:HideShortcuts":
|
||||
hide();
|
||||
break;
|
||||
case "GenAI:SelectionChange":
|
||||
case "GenAI:ShowShortcuts": {
|
||||
// Add shortcuts to the current tab's brower stack if it doesn't exist
|
||||
if (!shortcuts) {
|
||||
shortcuts = stack.appendChild(document.createElement("div"));
|
||||
|
@ -392,36 +384,29 @@ export const GenAI = {
|
|||
});
|
||||
}
|
||||
|
||||
// Immediately hide shortcuts and debounce multiple selection changes
|
||||
hide();
|
||||
if (shortcuts.timeout) {
|
||||
lazy.clearTimeout(shortcuts.timeout);
|
||||
}
|
||||
// Save the latest selection so it can be used by timeout and popup
|
||||
// Save the latest selection so it can be used by popup
|
||||
shortcuts.selection = data.selection;
|
||||
shortcuts.timeout = lazy.setTimeout(() => {
|
||||
// Pref might have changed since the timeout started
|
||||
if (!lazy.chatShortcuts || shortcuts.hasAttribute("shown")) {
|
||||
return;
|
||||
}
|
||||
if (shortcuts.hasAttribute("shown")) {
|
||||
return;
|
||||
}
|
||||
|
||||
shortcuts.toggleAttribute("shown");
|
||||
Glean.genaiChatbot.shortcutsDisplayed.record({
|
||||
selection: shortcuts.selection.length,
|
||||
});
|
||||
shortcuts.toggleAttribute("shown");
|
||||
Glean.genaiChatbot.shortcutsDisplayed.record({
|
||||
selection: shortcuts.selection.length,
|
||||
});
|
||||
|
||||
// Position the shortcuts relative to the browser's top-left corner
|
||||
const rect = browser.getBoundingClientRect();
|
||||
shortcuts.style.setProperty(
|
||||
"--shortcuts-x",
|
||||
data.x - window.screenX - rect.x + "px"
|
||||
);
|
||||
shortcuts.style.setProperty(
|
||||
"--shortcuts-y",
|
||||
data.y - window.screenY - rect.y + "px"
|
||||
);
|
||||
}, lazy.chatShortcutsDebounce);
|
||||
// Position the shortcuts relative to the browser's top-left corner
|
||||
const rect = browser.getBoundingClientRect();
|
||||
shortcuts.style.setProperty(
|
||||
"--shortcuts-x",
|
||||
data.x - window.screenX - rect.x + "px"
|
||||
);
|
||||
shortcuts.style.setProperty(
|
||||
"--shortcuts-y",
|
||||
data.y - window.screenY - rect.y + "px"
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -2,44 +2,49 @@
|
|||
* 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/. */
|
||||
|
||||
// Additional events to listen with others to create the actor in BrowserGlue
|
||||
const EVENTS = ["mousedown", "mouseup"];
|
||||
|
||||
/**
|
||||
* JSWindowActor to detect content page events to send GenAI related data.
|
||||
*/
|
||||
export class GenAIChild extends JSWindowActorChild {
|
||||
actorCreated() {
|
||||
this.document.addEventListener("selectionchange", this);
|
||||
EVENTS.forEach(ev => this.contentWindow.addEventListener(ev, this));
|
||||
}
|
||||
|
||||
didDestroy() {
|
||||
this.document.removeEventListener("selectionchange", this);
|
||||
EVENTS.forEach(ev => this.contentWindow?.removeEventListener(ev, this));
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
const sendHide = () => this.sendQuery("GenAI:HideShortcuts", event.type);
|
||||
switch (event.type) {
|
||||
case "mousemove":
|
||||
// Track the pointer's screen position to avoid container positioning
|
||||
this.lastX = event.screenX;
|
||||
this.lastY = event.screenY;
|
||||
case "mousedown":
|
||||
this.downTime = Date.now();
|
||||
sendHide();
|
||||
break;
|
||||
case "mouseup": {
|
||||
// Show immediately on selection or allow long press with no selection
|
||||
const selection =
|
||||
this.contentWindow.getSelection()?.toString().trim() ?? "";
|
||||
if (selection || Date.now() - (this.downTime ?? 0) > 200) {
|
||||
this.sendQuery("GenAI:ShowShortcuts", {
|
||||
x: event.screenX,
|
||||
y: event.screenY,
|
||||
selection,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "resize":
|
||||
case "scroll":
|
||||
case "selectionchange":
|
||||
// Hide if selection might have shifted away from shortcuts
|
||||
sendHide();
|
||||
break;
|
||||
case "selectionchange": {
|
||||
const selection = this.contentWindow.getSelection().toString().trim();
|
||||
if (!selection) {
|
||||
sendHide();
|
||||
break;
|
||||
}
|
||||
this.sendQuery("GenAI:SelectionChange", {
|
||||
x: this.lastX ?? 0,
|
||||
y: this.lastY ?? 0,
|
||||
selection,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,20 +24,26 @@ add_task(async function test_no_shortcuts() {
|
|||
add_task(async function test_show_shortcuts() {
|
||||
Services.fog.testResetFOG();
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["browser.ml.chat.shortcuts", true],
|
||||
["browser.ml.chat.shortcutsDebounce", 0],
|
||||
],
|
||||
set: [["browser.ml.chat.shortcuts", true]],
|
||||
});
|
||||
await BrowserTestUtils.withNewTab("data:text/plain,hi", async browser => {
|
||||
await SimpleTest.promiseFocus(browser);
|
||||
const selectPromise = SpecialPowers.spawn(browser, [], () => {
|
||||
ContentTaskUtils.waitForCondition(() => content.getSelection());
|
||||
});
|
||||
goDoCommand("cmd_selectAll");
|
||||
await selectPromise;
|
||||
BrowserTestUtils.synthesizeMouseAtCenter(
|
||||
browser,
|
||||
{ type: "mouseup" },
|
||||
browser
|
||||
);
|
||||
const shortcuts = await TestUtils.waitForCondition(() =>
|
||||
document.querySelector(".content-shortcuts[shown]")
|
||||
);
|
||||
Assert.ok(shortcuts, "Shortcuts added on select");
|
||||
let events = Glean.genaiChatbot.shortcutsDisplayed.testGetValue();
|
||||
Assert.ok(events.length, "Shortcuts shown");
|
||||
Assert.equal(events.length, 1, "Shortcuts shown once");
|
||||
Assert.equal(events[0].extra.selection, 2, "Selected hi");
|
||||
|
||||
const popup = document.getElementById("ask-chat-shortcuts");
|
||||
|
|
|
@ -64,6 +64,22 @@
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
> a {
|
||||
color: var(--link-color);
|
||||
|
||||
&:hover {
|
||||
color: var(--link-color-hover);
|
||||
}
|
||||
|
||||
&:hover:active {
|
||||
color: var(--link-color-active);
|
||||
}
|
||||
|
||||
&:visited {
|
||||
color: var(--link-color-visited);
|
||||
}
|
||||
}
|
||||
|
||||
.button-group {
|
||||
gap: var(--space-medium);
|
||||
display: flex;
|
||||
|
|
|
@ -5579,6 +5579,18 @@ main section {
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.topic-selection-container .modal-footer > a {
|
||||
color: var(--link-color);
|
||||
}
|
||||
.topic-selection-container .modal-footer > a:hover {
|
||||
color: var(--link-color-hover);
|
||||
}
|
||||
.topic-selection-container .modal-footer > a:hover:active {
|
||||
color: var(--link-color-active);
|
||||
}
|
||||
.topic-selection-container .modal-footer > a:visited {
|
||||
color: var(--link-color-visited);
|
||||
}
|
||||
.topic-selection-container .modal-footer .button-group {
|
||||
gap: var(--space-medium);
|
||||
display: flex;
|
||||
|
|
|
@ -5583,6 +5583,18 @@ main section {
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.topic-selection-container .modal-footer > a {
|
||||
color: var(--link-color);
|
||||
}
|
||||
.topic-selection-container .modal-footer > a:hover {
|
||||
color: var(--link-color-hover);
|
||||
}
|
||||
.topic-selection-container .modal-footer > a:hover:active {
|
||||
color: var(--link-color-active);
|
||||
}
|
||||
.topic-selection-container .modal-footer > a:visited {
|
||||
color: var(--link-color-visited);
|
||||
}
|
||||
.topic-selection-container .modal-footer .button-group {
|
||||
gap: var(--space-medium);
|
||||
display: flex;
|
||||
|
|
|
@ -5579,6 +5579,18 @@ main section {
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.topic-selection-container .modal-footer > a {
|
||||
color: var(--link-color);
|
||||
}
|
||||
.topic-selection-container .modal-footer > a:hover {
|
||||
color: var(--link-color-hover);
|
||||
}
|
||||
.topic-selection-container .modal-footer > a:hover:active {
|
||||
color: var(--link-color-active);
|
||||
}
|
||||
.topic-selection-container .modal-footer > a:visited {
|
||||
color: var(--link-color-visited);
|
||||
}
|
||||
.topic-selection-container .modal-footer .button-group {
|
||||
gap: var(--space-medium);
|
||||
display: flex;
|
||||
|
|
|
@ -320,6 +320,13 @@
|
|||
return this.querySelector(".tab-close-button");
|
||||
}
|
||||
|
||||
get group() {
|
||||
if (this.parentElement.tagName == "tab-group") {
|
||||
return this.parentElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
updateLastAccessed(aDate) {
|
||||
this._lastAccessed = this.selected ? Infinity : aDate || Date.now();
|
||||
}
|
||||
|
|
|
@ -850,7 +850,6 @@
|
|||
if (this.tabContainer.verticalMode) {
|
||||
let wasFocused = document.activeElement == this.selectedTab;
|
||||
let oldPosition = aTab._tPos;
|
||||
this.tabContainer._invalidateCachedTabs();
|
||||
this.verticalPinnedTabsContainer.appendChild(aTab);
|
||||
this._updateAfterMoveTabTo(aTab, oldPosition, wasFocused);
|
||||
} else {
|
||||
|
@ -873,7 +872,6 @@
|
|||
// the moving of a tab from the vertical pinned tabs container
|
||||
// and back into arrowscrollbox.
|
||||
aTab.removeAttribute("pinned");
|
||||
this.tabContainer._invalidateCachedTabs();
|
||||
this.tabContainer.arrowScrollbox.prepend(aTab);
|
||||
this._updateAfterMoveTabTo(aTab, oldPosition, wasFocused);
|
||||
} else {
|
||||
|
@ -2741,7 +2739,8 @@
|
|||
}
|
||||
let openerTab =
|
||||
(openerBrowser && this.getTabForBrowser(openerBrowser)) ||
|
||||
(relatedToCurrent && this.selectedTab);
|
||||
(relatedToCurrent && this.selectedTab) ||
|
||||
null;
|
||||
|
||||
// When overflowing, new tabs are scrolled into view smoothly, which
|
||||
// doesn't go well together with the width transition. So we skip the
|
||||
|
@ -2930,6 +2929,7 @@
|
|||
group.color = color;
|
||||
group.label = label;
|
||||
this.tabContainer.appendChild(group);
|
||||
return group;
|
||||
},
|
||||
|
||||
_determineURIToLoad(uriString, createLazyBrowser) {
|
||||
|
@ -5331,13 +5331,29 @@
|
|||
neighbor.after(aTab);
|
||||
}
|
||||
|
||||
// We want to clear _allTabs after moving nodes because the order of
|
||||
// vertical tabs may have changed.
|
||||
this.tabContainer._invalidateCachedTabs();
|
||||
this._updateAfterMoveTabTo(aTab, oldPosition, wasFocused);
|
||||
},
|
||||
|
||||
moveTabToGroup(aTab, aGroup) {
|
||||
if (aTab.pinned) {
|
||||
return;
|
||||
}
|
||||
if (aTab.group && aTab.group.id === aGroup.id) {
|
||||
return;
|
||||
}
|
||||
let wasFocused = document.activeElement == this.selectedTab;
|
||||
aGroup.appendChild(aTab);
|
||||
|
||||
// pass -1 to oldPosition because a move occurred even if position
|
||||
// hasn't changed
|
||||
this._updateAfterMoveTabTo(aTab, -1, wasFocused);
|
||||
},
|
||||
|
||||
_updateAfterMoveTabTo(aTab, oldPosition, wasFocused = null) {
|
||||
// We want to clear _allTabs after moving nodes because the order of
|
||||
// vertical tabs may have changed.
|
||||
this.tabContainer._invalidateCachedTabs();
|
||||
|
||||
this._updateTabsAfterInsert();
|
||||
|
||||
if (wasFocused) {
|
||||
|
|
|
@ -57,6 +57,21 @@
|
|||
set label(val) {
|
||||
this.setAttribute("label", val);
|
||||
}
|
||||
|
||||
get tabs() {
|
||||
return Array.from(this.children).filter(node => node.matches("tab"));
|
||||
}
|
||||
|
||||
/**
|
||||
* add tabs to the group
|
||||
*
|
||||
* @param tabs array of tabs to add
|
||||
*/
|
||||
addTabs(tabs) {
|
||||
for (let tab of tabs) {
|
||||
gBrowser.moveTabToGroup(tab, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("tab-group", MozTabbrowserTabGroup);
|
||||
|
|
|
@ -1139,6 +1139,13 @@
|
|||
// remove arrowScrollbox periphery element
|
||||
children.pop();
|
||||
|
||||
// explode tab groups
|
||||
Array.from(children).forEach((node, index) => {
|
||||
if (node.tagName == "tab-group") {
|
||||
children.splice(index, 1, ...node.tabs);
|
||||
}
|
||||
});
|
||||
|
||||
let allChildren = [...verticalPinnedTabsContainer.children, ...children];
|
||||
this._allTabs = allChildren;
|
||||
return allChildren;
|
||||
|
|
|
@ -317,6 +317,8 @@ skip-if = ["true"] # Bug 1616418 Bug 1549985
|
|||
|
||||
["browser_tab_a11y_description.js"]
|
||||
|
||||
["browser_tab_groups.js"]
|
||||
|
||||
["browser_tab_label_during_reload.js"]
|
||||
|
||||
["browser_tab_label_picture_in_picture.js"]
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/* 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/. */
|
||||
|
||||
add_task(async function test_tabGroups() {
|
||||
let group = gBrowser.addTabGroup("blue", "test");
|
||||
|
||||
Assert.ok(group.id, "group has id");
|
||||
|
||||
let tab1 = BrowserTestUtils.addTab(gBrowser, "about:blank");
|
||||
group.addTabs([tab1]);
|
||||
|
||||
Assert.ok(group.tabs.includes(tab1), "tab1 is in group");
|
||||
|
||||
// TODO add API to remove group
|
||||
BrowserTestUtils.removeTab(tab1);
|
||||
group.remove();
|
||||
});
|
|
@ -20,7 +20,9 @@ ChromeUtils.defineLazyGetter(lazy, "SearchModeSwitcherL10n", () => {
|
|||
*/
|
||||
export class SearchModeSwitcher {
|
||||
#engineListNeedsRebuild = true;
|
||||
#input = null;
|
||||
#popup;
|
||||
#input;
|
||||
#toolbarbutton;
|
||||
|
||||
constructor(input) {
|
||||
this.#input = input;
|
||||
|
@ -31,11 +33,13 @@ export class SearchModeSwitcher {
|
|||
]);
|
||||
Services.obs.addObserver(this, "browser-search-engine-modified", true);
|
||||
|
||||
let toolbarbutton = input.document.querySelector(
|
||||
this.#popup = input.document.getElementById("searchmode-switcher-popup");
|
||||
|
||||
this.#toolbarbutton = input.document.querySelector(
|
||||
"#urlbar-searchmode-switcher"
|
||||
);
|
||||
toolbarbutton.addEventListener("mousedown", this);
|
||||
toolbarbutton.addEventListener("keypress", this);
|
||||
this.#toolbarbutton.addEventListener("mousedown", this);
|
||||
this.#toolbarbutton.addEventListener("keypress", this);
|
||||
|
||||
let closebutton = input.document.querySelector(
|
||||
"#searchmode-switcher-close"
|
||||
|
@ -43,6 +47,12 @@ export class SearchModeSwitcher {
|
|||
closebutton.addEventListener("mousedown", this);
|
||||
closebutton.addEventListener("keypress", this);
|
||||
|
||||
let prefsbutton = input.document.querySelector(
|
||||
"#searchmode-switcher-popup-search-settings-button"
|
||||
);
|
||||
prefsbutton.addEventListener("mousedown", this);
|
||||
prefsbutton.addEventListener("keypress", this);
|
||||
|
||||
input.window.addEventListener(
|
||||
"MozAfterPaint",
|
||||
() => this.#updateSearchIcon(),
|
||||
|
@ -57,8 +67,18 @@ export class SearchModeSwitcher {
|
|||
* The event that triggered the opening of the popup.
|
||||
*/
|
||||
async openPanel(event) {
|
||||
if (
|
||||
(event.type == "click" && event.button != 0) ||
|
||||
(event.type == "keypress" &&
|
||||
event.charCode != KeyEvent.DOM_VK_SPACE &&
|
||||
event.keyCode != KeyEvent.DOM_VK_RETURN &&
|
||||
event.keyCode != KeyEvent.DOM_VK_DOWN)
|
||||
) {
|
||||
return; // Left click, down arrow, space or enter only
|
||||
}
|
||||
|
||||
let anchor = event.target;
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
if (this.#input.document.documentElement.hasAttribute("customizing")) {
|
||||
return;
|
||||
|
@ -71,23 +91,47 @@ export class SearchModeSwitcher {
|
|||
if (anchor.getAttribute("open") != "true") {
|
||||
this.#input.view.hideTemporarily();
|
||||
|
||||
this.#getPopup().addEventListener(
|
||||
this.#popup.addEventListener(
|
||||
"popuphidden",
|
||||
() => {
|
||||
anchor.removeAttribute("open");
|
||||
anchor.setAttribute("aria-expanded", false);
|
||||
this.#input.view.restoreVisibility();
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
anchor.setAttribute("open", true);
|
||||
anchor.setAttribute("aria-expanded", true);
|
||||
|
||||
lazy.PanelMultiView.openPopup(this.#getPopup(), anchor, {
|
||||
this.#popup.addEventListener(
|
||||
"popupshown",
|
||||
() => {
|
||||
this.#popup.querySelector("toolbarbutton").focus();
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
lazy.PanelMultiView.openPopup(this.#popup, anchor, {
|
||||
position: "bottomleft topleft",
|
||||
triggerEvent: event,
|
||||
}).catch(console.error);
|
||||
}
|
||||
}
|
||||
|
||||
#openPreferences(event) {
|
||||
if (
|
||||
(event.type == "click" && event.button != 0) ||
|
||||
(event.type == "keypress" &&
|
||||
event.charCode != KeyEvent.DOM_VK_SPACE &&
|
||||
event.keyCode != KeyEvent.DOM_VK_RETURN)
|
||||
) {
|
||||
return; // Left click, space or enter only
|
||||
}
|
||||
|
||||
this.#input.window.openPreferences("paneSearch");
|
||||
this.#popup.hidePopup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit the engine specific searchMode.
|
||||
*
|
||||
|
@ -95,7 +139,6 @@ export class SearchModeSwitcher {
|
|||
* The event that triggered the searchMode exit.
|
||||
*/
|
||||
exitSearchMode(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this.#input.searchMode = null;
|
||||
}
|
||||
|
@ -119,6 +162,10 @@ export class SearchModeSwitcher {
|
|||
this.exitSearchMode(event);
|
||||
break;
|
||||
}
|
||||
case "openpreferences": {
|
||||
this.#openPreferences(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,70 +178,63 @@ export class SearchModeSwitcher {
|
|||
}
|
||||
}
|
||||
|
||||
#popup = null;
|
||||
|
||||
#getPopup() {
|
||||
if (!this.#popup) {
|
||||
this.#popup = this.#input.document.getElementById(
|
||||
"searchmode-switcher-popup"
|
||||
);
|
||||
}
|
||||
return this.#popup;
|
||||
}
|
||||
|
||||
async #updateSearchIcon() {
|
||||
try {
|
||||
await lazy.UrlbarSearchUtils.init();
|
||||
} catch {
|
||||
// We should still work if the SearchService is not working.
|
||||
}
|
||||
let button = this.#input.document.getElementById(
|
||||
"searchmode-switcher-icon"
|
||||
let { label, icon } = await this.#getDisplayedEngineDetails(
|
||||
this.#input.searchMode
|
||||
);
|
||||
let iconURL = await this.#getIconForSearchMode(this.#input.searchMode);
|
||||
if (iconURL) {
|
||||
button.style.listStyleImage = `url(${iconURL})`;
|
||||
}
|
||||
let label = this.#input.document.getElementById(
|
||||
let iconUrl = icon ? `url(${icon})` : "";
|
||||
this.#input.document.getElementById(
|
||||
"searchmode-switcher-icon"
|
||||
).style.listStyleImage = iconUrl;
|
||||
this.#input.document.l10n.setAttributes(
|
||||
this.#toolbarbutton,
|
||||
"urlbar-searchmode-button",
|
||||
{ engine: label }
|
||||
);
|
||||
let labelEl = this.#input.document.getElementById(
|
||||
"searchmode-switcher-title"
|
||||
);
|
||||
if (!this.#input.searchMode) {
|
||||
label.replaceChildren();
|
||||
} else if (this.#input.searchMode?.engineName) {
|
||||
label.textContent = this.#input.searchMode.engineName;
|
||||
} else if (this.#input.searchMode?.source) {
|
||||
// Certainly the wrong way to do this, the same string is used
|
||||
// as a <label and as a <toolbarbutton label= and not sure
|
||||
// how to define an l10n ID to be used both ways.
|
||||
let mode = lazy.UrlbarUtils.LOCAL_SEARCH_MODES.find(
|
||||
m => m.source == this.#input.searchMode.source
|
||||
);
|
||||
let [str] = await lazy.SearchModeSwitcherL10n.formatMessages([
|
||||
{
|
||||
id: mode.uiLabel,
|
||||
args: { restrict: mode.restrict },
|
||||
},
|
||||
]);
|
||||
label.textContent = str.attributes[0].value;
|
||||
labelEl.replaceChildren();
|
||||
} else if (this.#input.searchMode) {
|
||||
labelEl.textContent = label;
|
||||
}
|
||||
}
|
||||
|
||||
async #getIconForSearchMode(searchMode = null) {
|
||||
async #getSearchModeLabel(source) {
|
||||
let mode = lazy.UrlbarUtils.LOCAL_SEARCH_MODES.find(
|
||||
m => m.source == source
|
||||
);
|
||||
let [str] = await lazy.SearchModeSwitcherL10n.formatMessages([
|
||||
{ id: mode.uiLabel },
|
||||
]);
|
||||
return str.attributes[0].value;
|
||||
}
|
||||
|
||||
async #getDisplayedEngineDetails(searchMode = null) {
|
||||
if (!searchMode || searchMode.engineName) {
|
||||
let engine = searchMode
|
||||
? lazy.UrlbarSearchUtils.getEngineByName(searchMode.engineName)
|
||||
: lazy.UrlbarSearchUtils.getDefaultEngine();
|
||||
return engine.getIconURL();
|
||||
return { label: engine.name, icon: await engine.getIconURL() };
|
||||
}
|
||||
|
||||
let mode = lazy.UrlbarUtils.LOCAL_SEARCH_MODES.find(
|
||||
m => m.source == searchMode.source
|
||||
);
|
||||
return mode.icon;
|
||||
return {
|
||||
label: await this.#getSearchModeLabel(searchMode.source),
|
||||
icon: mode.icon,
|
||||
};
|
||||
}
|
||||
|
||||
async #rebuildSearchModeList() {
|
||||
let container = this.#getPopup().querySelector(".panel-subview-body");
|
||||
let container = this.#popup.querySelector(".panel-subview-body");
|
||||
container.replaceChildren();
|
||||
let engines = await Services.search.getVisibleEngines();
|
||||
let frag = this.#input.document.createDocumentFragment();
|
||||
|
@ -202,6 +242,12 @@ export class SearchModeSwitcher {
|
|||
remoteContainer.className = "remote-options";
|
||||
frag.appendChild(remoteContainer);
|
||||
|
||||
let fireCommand = e => {
|
||||
if (e.keyCode == KeyEvent.DOM_VK_RETURN) {
|
||||
e.target.doCommand();
|
||||
}
|
||||
};
|
||||
|
||||
for (let engine of engines) {
|
||||
if (engine.hideOneOffButton) {
|
||||
continue;
|
||||
|
@ -211,11 +257,13 @@ export class SearchModeSwitcher {
|
|||
menuitem.setAttribute("class", "subviewbutton subviewbutton-iconic");
|
||||
menuitem.setAttribute("label", engine.name);
|
||||
menuitem.setAttribute("tabindex", "0");
|
||||
menuitem.setAttribute("role", "menuitem");
|
||||
menuitem.engine = engine;
|
||||
menuitem.setAttribute(
|
||||
"oncommand",
|
||||
"gURLBar.searchModeSwitcher.search(event, { engine: this.engine })"
|
||||
);
|
||||
menuitem.addEventListener("keypress", fireCommand);
|
||||
menuitem.addEventListener("command", () => {
|
||||
this.search({ engine });
|
||||
});
|
||||
|
||||
menuitem.setAttribute("image", await engine.getIconURL());
|
||||
remoteContainer.appendChild(menuitem);
|
||||
}
|
||||
|
@ -232,18 +280,21 @@ export class SearchModeSwitcher {
|
|||
let button = this.#input.document.createXULElement("toolbarbutton");
|
||||
button.id = `search-button-${name}`;
|
||||
button.setAttribute("class", "subviewbutton subviewbutton-iconic");
|
||||
let iconUrl = await this.#getIconForSearchMode({
|
||||
button.setAttribute("tabindex", "0");
|
||||
button.setAttribute("role", "menuitem");
|
||||
let { icon } = await this.#getDisplayedEngineDetails({
|
||||
source,
|
||||
pref,
|
||||
restrict,
|
||||
});
|
||||
if (iconUrl) {
|
||||
button.setAttribute("image", iconUrl);
|
||||
if (icon) {
|
||||
button.setAttribute("image", icon);
|
||||
}
|
||||
button.setAttribute(
|
||||
"oncommand",
|
||||
"gURLBar.searchModeSwitcher.search(event, { restrict: this.restrict })"
|
||||
);
|
||||
button.addEventListener("keypress", fireCommand);
|
||||
button.addEventListener("command", () => {
|
||||
this.search({ restrict });
|
||||
});
|
||||
|
||||
this.#input.document.l10n.setAttributes(
|
||||
button,
|
||||
`urlbar-searchmode-${name}`,
|
||||
|
@ -258,7 +309,7 @@ export class SearchModeSwitcher {
|
|||
container.appendChild(frag);
|
||||
}
|
||||
|
||||
search(event, { engine = null, restrict = null } = {}) {
|
||||
search({ engine = null, restrict = null } = {}) {
|
||||
let gBrowser = this.#input.window.gBrowser;
|
||||
let search = "";
|
||||
let opts = null;
|
||||
|
@ -270,6 +321,6 @@ export class SearchModeSwitcher {
|
|||
opts = { searchModeEntry: "searchbutton" };
|
||||
}
|
||||
this.#input.search(search, opts);
|
||||
this.#getPopup().hidePopup();
|
||||
this.#popup.hidePopup();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1238,19 +1238,13 @@ export class UrlbarInput {
|
|||
return;
|
||||
}
|
||||
case lazy.UrlbarUtils.RESULT_TYPE.DYNAMIC: {
|
||||
if (url) {
|
||||
break;
|
||||
}
|
||||
url = result.payload.url;
|
||||
// Keep the searchMode for telemetry since handleRevert sets it to null.
|
||||
const searchMode = this.searchMode;
|
||||
// Do not revert the Urlbar if we're going to navigate. We want the URL
|
||||
// populated so we can navigate to it.
|
||||
if (!url || !result.payload.shouldNavigate) {
|
||||
if (!url) {
|
||||
// If we're not loading a URL, the engagement is done. First revert
|
||||
// and then record the engagement since providers expect the urlbar to
|
||||
// be reverted when they're notified of the engagement, but before
|
||||
// reverting, copy the search mode since it's nulled on revert.
|
||||
const { searchMode } = this;
|
||||
this.handleRevert();
|
||||
}
|
||||
// If we won't be navigating, this is the end of the engagement.
|
||||
if (!url || !result.payload.shouldNavigate) {
|
||||
this.controller.engagementEvent.record(event, {
|
||||
result,
|
||||
element,
|
||||
|
|
|
@ -816,10 +816,10 @@ class MuxerUnifiedComplete extends UrlbarMuxer {
|
|||
return false;
|
||||
}
|
||||
|
||||
// For tab-to-search results, result.payload.url is the engine's domain
|
||||
// with the public suffix already stripped, for example "www.mozilla.".
|
||||
// `searchUrlDomainWithoutSuffix` is the engine's domain with the public
|
||||
// suffix already stripped, for example "www.mozilla.".
|
||||
let [engineDomain] = UrlbarUtils.stripPrefixAndTrim(
|
||||
result.payload.url,
|
||||
result.payload.searchUrlDomainWithoutSuffix,
|
||||
{
|
||||
stripWww: true,
|
||||
}
|
||||
|
|
|
@ -228,52 +228,46 @@ class ProviderQuickSuggest extends UrlbarProvider {
|
|||
}
|
||||
}
|
||||
|
||||
onLegacyEngagement(state, queryContext, details, controller) {
|
||||
// Ignore engagements on other results that didn't end the session.
|
||||
if (details.result?.providerName != this.name && details.isSessionOngoing) {
|
||||
return;
|
||||
onImpression(state, queryContext, controller, providerVisibleResults) {
|
||||
// Legacy Suggest telemetry should be recorded when a Suggest result is
|
||||
// visible at the end of an engagement on any result.
|
||||
this.#sessionResult =
|
||||
state == "engagement" ? providerVisibleResults[0].result : null;
|
||||
}
|
||||
|
||||
onEngagement(queryContext, controller, details) {
|
||||
if (details.isSessionOngoing) {
|
||||
// When the session remains ongoing -- e.g., a result is dismissed --
|
||||
// tests expect legacy telemetry to be recorded immediately on engagement,
|
||||
// not deferred until the session ends, so record it now.
|
||||
this.#recordEngagement(queryContext, details.result, details);
|
||||
}
|
||||
|
||||
let feature = this.#getFeatureByResult(details.result);
|
||||
if (feature?.handleCommand) {
|
||||
feature.handleCommand(
|
||||
controller.view,
|
||||
details.result,
|
||||
details.selType,
|
||||
this._trimmedSearchString
|
||||
);
|
||||
} else if (details.selType == "dismiss") {
|
||||
// Handle dismissals.
|
||||
this.#dismissResult(controller, details.result);
|
||||
}
|
||||
|
||||
feature?.onEngagement?.(queryContext, controller, details);
|
||||
}
|
||||
|
||||
onSearchSessionEnd(queryContext, controller, details) {
|
||||
// Reset the Merino session ID when a session ends. By design for the user's
|
||||
// privacy, we don't keep it around between engagements.
|
||||
if (!details.isSessionOngoing) {
|
||||
this.#merino?.resetSession();
|
||||
}
|
||||
this.#merino?.resetSession();
|
||||
|
||||
// Impression and clicked telemetry are both recorded on engagement. We
|
||||
// define "impression" to mean a quick suggest result was present in the
|
||||
// view when any result was picked.
|
||||
if (state == "engagement" && queryContext) {
|
||||
// Get the result that's visible in the view. `details.result` is the
|
||||
// engaged result, if any; if it's from this provider, then that's the
|
||||
// visible result. Otherwise fall back to #getVisibleResultFromLastQuery.
|
||||
let { result } = details;
|
||||
if (result?.providerName != this.name) {
|
||||
result = this.#getVisibleResultFromLastQuery(controller.view);
|
||||
}
|
||||
|
||||
this.#recordEngagement(queryContext, result, details);
|
||||
}
|
||||
|
||||
if (details.result?.providerName == this.name) {
|
||||
let feature = this.#getFeatureByResult(details.result);
|
||||
if (feature?.handleCommand) {
|
||||
feature.handleCommand(
|
||||
controller.view,
|
||||
details.result,
|
||||
details.selType,
|
||||
this._trimmedSearchString
|
||||
);
|
||||
} else if (details.selType == "dismiss") {
|
||||
// Handle dismissals.
|
||||
this.#dismissResult(controller, details.result);
|
||||
}
|
||||
|
||||
if (state == "engagement" && feature?.onEngagement) {
|
||||
feature.onEngagement(queryContext, controller, details);
|
||||
}
|
||||
}
|
||||
// Record legacy Suggest telemetry.
|
||||
this.#recordEngagement(queryContext, this.#sessionResult, details);
|
||||
|
||||
this.#sessionResult = null;
|
||||
this.#resultFromLastQuery = null;
|
||||
}
|
||||
|
||||
|
@ -451,22 +445,6 @@ class ProviderQuickSuggest extends UrlbarProvider {
|
|||
);
|
||||
}
|
||||
|
||||
#getVisibleResultFromLastQuery(view) {
|
||||
let result = this.#resultFromLastQuery;
|
||||
|
||||
if (
|
||||
result?.rowIndex >= 0 &&
|
||||
view?.visibleResults?.[result.rowIndex] == result
|
||||
) {
|
||||
// The result was visible.
|
||||
return result;
|
||||
}
|
||||
|
||||
// Find a visible result. Quick suggest results typically appear last in the
|
||||
// view, so do a reverse search.
|
||||
return view?.visibleResults?.findLast(r => r.providerName == this.name);
|
||||
}
|
||||
|
||||
#dismissResult(controller, result) {
|
||||
if (!result.payload.isBlockable) {
|
||||
this.logger.info("Dismissals disabled, ignoring dismissal");
|
||||
|
@ -493,8 +471,8 @@ class ProviderQuickSuggest extends UrlbarProvider {
|
|||
* end of the engagement or that was dismissed. Null if no quick suggest
|
||||
* result was present.
|
||||
* @param {object} details
|
||||
* The `details` object that was passed to `onLegacyEngagement()`. It must
|
||||
* look like this: `{ selType, selIndex }`
|
||||
* The `details` object that was passed to `onEngagement()` or
|
||||
* `onSearchSessionEnd()`.
|
||||
*/
|
||||
#recordEngagement(queryContext, result, details) {
|
||||
let resultSelType = "";
|
||||
|
@ -785,8 +763,8 @@ class ProviderQuickSuggest extends UrlbarProvider {
|
|||
* True if the main part of the result's row was clicked; false if a button
|
||||
* like help or dismiss was clicked or if no part of the row was clicked.
|
||||
* @param {object} options.details
|
||||
* The `details` object that was passed to `onLegacyEngagement()`. It must
|
||||
* look like this: `{ selType, selIndex }`
|
||||
* The `details` object that was passed to `onEngagement()` or
|
||||
* `onSearchSessionEnd()`.
|
||||
*/
|
||||
#recordNavSuggestionTelemetry({
|
||||
queryContext,
|
||||
|
@ -945,9 +923,13 @@ class ProviderQuickSuggest extends UrlbarProvider {
|
|||
return this.#merino;
|
||||
}
|
||||
|
||||
// The result we added during the most recent query.
|
||||
// The result we added during the most recent query. It may not be visible.
|
||||
#resultFromLastQuery = null;
|
||||
|
||||
// The result from this provider that was visible at the end of the current
|
||||
// search session, if the session ended in an engagement.
|
||||
#sessionResult;
|
||||
|
||||
// The Merino client.
|
||||
#merino = null;
|
||||
}
|
||||
|
|
|
@ -404,16 +404,12 @@ class ProviderTabToSearch extends UrlbarProvider {
|
|||
}
|
||||
|
||||
function makeOnboardingResult(engine, satisfiesAutofillThreshold = false) {
|
||||
let [url] = UrlbarUtils.stripPrefixAndTrim(engine.searchUrlDomain, {
|
||||
stripWww: true,
|
||||
});
|
||||
url = url.substr(0, url.length - engine.searchUrlPublicSuffix.length);
|
||||
let result = new lazy.UrlbarResult(
|
||||
UrlbarUtils.RESULT_TYPE.DYNAMIC,
|
||||
UrlbarUtils.RESULT_SOURCE.SEARCH,
|
||||
{
|
||||
engine: engine.name,
|
||||
url,
|
||||
searchUrlDomainWithoutSuffix: searchUrlDomainWithoutSuffix(engine),
|
||||
providesSearchMode: true,
|
||||
icon: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
dynamicType: DYNAMIC_RESULT_TYPE,
|
||||
|
@ -426,17 +422,13 @@ function makeOnboardingResult(engine, satisfiesAutofillThreshold = false) {
|
|||
}
|
||||
|
||||
function makeResult(context, engine, satisfiesAutofillThreshold = false) {
|
||||
let [url] = UrlbarUtils.stripPrefixAndTrim(engine.searchUrlDomain, {
|
||||
stripWww: true,
|
||||
});
|
||||
url = url.substr(0, url.length - engine.searchUrlPublicSuffix.length);
|
||||
let result = new lazy.UrlbarResult(
|
||||
UrlbarUtils.RESULT_TYPE.SEARCH,
|
||||
UrlbarUtils.RESULT_SOURCE.SEARCH,
|
||||
...lazy.UrlbarResult.payloadAndSimpleHighlights(context.tokens, {
|
||||
engine: engine.name,
|
||||
isGeneralPurposeEngine: engine.isGeneralPurposeEngine,
|
||||
url,
|
||||
searchUrlDomainWithoutSuffix: searchUrlDomainWithoutSuffix(engine),
|
||||
providesSearchMode: true,
|
||||
icon: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
query: "",
|
||||
|
@ -447,5 +439,12 @@ function makeResult(context, engine, satisfiesAutofillThreshold = false) {
|
|||
return result;
|
||||
}
|
||||
|
||||
function searchUrlDomainWithoutSuffix(engine) {
|
||||
let [value] = UrlbarUtils.stripPrefixAndTrim(engine.searchUrlDomain, {
|
||||
stripWww: true,
|
||||
});
|
||||
return value.substr(0, value.length - engine.searchUrlPublicSuffix.length);
|
||||
}
|
||||
|
||||
export var UrlbarProviderTabToSearch = new ProviderTabToSearch();
|
||||
initializeDynamicResult();
|
||||
|
|
|
@ -212,7 +212,7 @@ class ProviderWeather extends UrlbarProvider {
|
|||
* A non-empty string means the user picked the weather row or some part of
|
||||
* it, and both impression and click telemetry will be recorded. The
|
||||
* non-empty-string values come from the `details.selType` passed in to
|
||||
* `onLegacyEngagement()`; see `TelemetryEvent.typeFromElement()`.
|
||||
* `onEngagement()`; see `TelemetryEvent.typeFromElement()`.
|
||||
*/
|
||||
#recordEngagementTelemetry(result, isPrivate, selType) {
|
||||
// Indexes recorded in quick suggest telemetry are 1-based, so add 1 to the
|
||||
|
|
|
@ -442,7 +442,8 @@ class ProvidersManager {
|
|||
this.#notifySearchSessionEnd(
|
||||
this.providersByNotificationType.onSearchSessionEnd,
|
||||
queryContext,
|
||||
controller
|
||||
controller,
|
||||
details
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -500,9 +501,19 @@ class ProvidersManager {
|
|||
}
|
||||
}
|
||||
|
||||
#notifySearchSessionEnd(searchSessionEndProviders, queryContext, controller) {
|
||||
#notifySearchSessionEnd(
|
||||
searchSessionEndProviders,
|
||||
queryContext,
|
||||
controller,
|
||||
details
|
||||
) {
|
||||
for (const provider of searchSessionEndProviders) {
|
||||
provider.tryMethod("onSearchSessionEnd", queryContext, controller);
|
||||
provider.tryMethod(
|
||||
"onSearchSessionEnd",
|
||||
queryContext,
|
||||
controller,
|
||||
details
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -570,38 +570,34 @@ export var UrlbarUtils = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Extracts an url from a result, if possible.
|
||||
* Extracts the URL from a result.
|
||||
*
|
||||
* @param {UrlbarResult} result The result to extract from.
|
||||
* @returns {object} a {url, postData} object, or null if a url can't be built
|
||||
* from this result.
|
||||
* @param {UrlbarResult} result
|
||||
* The result to extract from.
|
||||
* @returns {object}
|
||||
* An object: `{ url, postData }`
|
||||
* `url` will be null if the result doesn't have a URL. `postData` will be
|
||||
* null if the result doesn't have post data.
|
||||
*/
|
||||
getUrlFromResult(result) {
|
||||
switch (result.type) {
|
||||
case UrlbarUtils.RESULT_TYPE.URL:
|
||||
case UrlbarUtils.RESULT_TYPE.REMOTE_TAB:
|
||||
case UrlbarUtils.RESULT_TYPE.TAB_SWITCH:
|
||||
return { url: result.payload.url, postData: null };
|
||||
case UrlbarUtils.RESULT_TYPE.KEYWORD:
|
||||
return {
|
||||
url: result.payload.url,
|
||||
postData: result.payload.postData
|
||||
? this.getPostDataStream(result.payload.postData)
|
||||
: null,
|
||||
};
|
||||
case UrlbarUtils.RESULT_TYPE.SEARCH: {
|
||||
if (result.payload.engine) {
|
||||
const engine = Services.search.getEngineByName(result.payload.engine);
|
||||
let [url, postData] = this.getSearchQueryUrl(
|
||||
engine,
|
||||
result.payload.suggestion || result.payload.query
|
||||
);
|
||||
return { url, postData };
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (
|
||||
result.type == UrlbarUtils.RESULT_TYPE.SEARCH &&
|
||||
result.payload.engine
|
||||
) {
|
||||
const engine = Services.search.getEngineByName(result.payload.engine);
|
||||
let [url, postData] = this.getSearchQueryUrl(
|
||||
engine,
|
||||
result.payload.suggestion || result.payload.query
|
||||
);
|
||||
return { url, postData };
|
||||
}
|
||||
return { url: null, postData: null };
|
||||
|
||||
return {
|
||||
url: result.payload.url ?? null,
|
||||
postData: result.payload.postData
|
||||
? this.getPostDataStream(result.payload.postData)
|
||||
: null,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1722,6 +1718,9 @@ UrlbarUtils.RESULT_PAYLOAD_SCHEMA = {
|
|||
satisfiesAutofillThreshold: {
|
||||
type: "boolean",
|
||||
},
|
||||
searchUrlDomainWithoutSuffix: {
|
||||
type: "string",
|
||||
},
|
||||
suggestion: {
|
||||
type: "string",
|
||||
},
|
||||
|
@ -2090,12 +2089,6 @@ UrlbarUtils.RESULT_PAYLOAD_SCHEMA = {
|
|||
dynamicType: {
|
||||
type: "string",
|
||||
},
|
||||
// If `shouldNavigate` is `true` and the payload contains a `url`
|
||||
// property, when the result is selected the browser will navigate to the
|
||||
// `url`.
|
||||
shouldNavigate: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
[UrlbarUtils.RESULT_TYPE.RESTRICT]: {
|
||||
|
|
|
@ -398,23 +398,19 @@ urlbar-firefox-suggest-contextual-opt-in-dismiss = Not now
|
|||
|
||||
## Searchmode Switcher button
|
||||
|
||||
# Variables:
|
||||
# $engine (String): the current default search engine.
|
||||
urlbar-searchmode-button =
|
||||
.label = Pick a Search Engine
|
||||
.tooltiptext = Pick a Search Engine
|
||||
.label = { $engine }, Pick a Search Engine
|
||||
.tooltiptext = { $engine }, Pick a Search Engine
|
||||
urlbar-searchmode-dropmarker =
|
||||
.tooltiptext = Pick a Search Engine
|
||||
# Variables:
|
||||
# $restrict (Character): the character used to filter results of type.
|
||||
urlbar-searchmode-bookmarks =
|
||||
.label = Bookmarks ({ $restrict })
|
||||
# Variables:
|
||||
# $restrict (Character): the character used to filter results of type.
|
||||
.label = Bookmarks
|
||||
urlbar-searchmode-tabs =
|
||||
.label = Tabs ({ $restrict })
|
||||
# Variables:
|
||||
# $restrict (Character): the character used to filter results of type.
|
||||
.label = Tabs
|
||||
urlbar-searchmode-history =
|
||||
.label = History ({ $restrict })
|
||||
.label = History
|
||||
|
||||
urlbar-searchmode-popup-description = This time search with:
|
||||
urlbar-searchmode-popup-search-settings = Search Settings
|
||||
|
|
|
@ -614,10 +614,9 @@ useful in this case.
|
|||
URL Navigation
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
If a result's payload includes a string ``url`` property and a boolean
|
||||
``shouldNavigate: true`` property, then picking the result will navigate to the
|
||||
URL. The ``onLegacyEngagement`` method of the result's provider will still be called
|
||||
before navigation.
|
||||
If a result's payload includes a string ``url`` property, picking the result
|
||||
will navigate to the URL. The ``onEngagement`` method of the result's provider
|
||||
will be called before navigation.
|
||||
|
||||
Text Highlighting
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -130,7 +130,6 @@ export class FakespotSuggestions extends BaseFeature {
|
|||
totalReviews: Number(suggestion.totalReviews),
|
||||
fakespotGrade: suggestion.fakespotGrade,
|
||||
fakespotProvider: this.#parseProvider(suggestion),
|
||||
shouldNavigate: true,
|
||||
dynamicType: "fakespot",
|
||||
};
|
||||
|
||||
|
|
|
@ -361,7 +361,6 @@ export class Weather extends BaseFeature {
|
|||
forecast: suggestion.forecast.summary,
|
||||
high: suggestion.forecast.high[unit],
|
||||
low: suggestion.forecast.low[unit],
|
||||
shouldNavigate: true,
|
||||
}
|
||||
),
|
||||
{
|
||||
|
|
|
@ -460,7 +460,7 @@ add_task(async function pick() {
|
|||
// Tests picking elements in a dynamic result.
|
||||
add_task(async function shouldNavigate() {
|
||||
/**
|
||||
* A dummy provider that providers results with a `shouldNavigate` property.
|
||||
* A dummy provider that providers results with a `url` property.
|
||||
*/
|
||||
class TestShouldNavigateProvider extends TestProvider {
|
||||
/**
|
||||
|
@ -470,7 +470,6 @@ add_task(async function shouldNavigate() {
|
|||
async startQuery(context, addCallback) {
|
||||
for (let result of this.results) {
|
||||
result.payload.searchString = context.searchString;
|
||||
result.payload.shouldNavigate = true;
|
||||
result.payload.url = DUMMY_PAGE;
|
||||
addCallback(this, result);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
ChromeUtils.defineESModuleGetters(this, {
|
||||
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
||||
});
|
||||
|
||||
add_setup(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.urlbar.scotchBonnet.enableOverride", true]],
|
||||
|
@ -104,3 +108,130 @@ add_task(async function detect_searchmode_changes() {
|
|||
);
|
||||
}, "The searchMode name has been removed when we exit search mode");
|
||||
});
|
||||
|
||||
function focusSwitcher() {
|
||||
EventUtils.synthesizeKey("l", { accelKey: true });
|
||||
EventUtils.synthesizeKey("KEY_Escape");
|
||||
EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Test we can open the SearchModeSwitcher with various keys
|
||||
*
|
||||
* @param {string} openKey - The keyboard character used to open the popup.
|
||||
*/
|
||||
async function test_open_switcher(openKey) {
|
||||
let popup = UrlbarTestUtils.searchModeSwitcherPopup(window);
|
||||
let promiseMenuOpen = BrowserTestUtils.waitForEvent(popup, "popupshown");
|
||||
|
||||
info("Open the urlbar and open the switcher via keyboard");
|
||||
focusSwitcher();
|
||||
EventUtils.synthesizeKey(openKey);
|
||||
await promiseMenuOpen;
|
||||
|
||||
EventUtils.synthesizeKey("KEY_Escape");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that not all characters will open the SearchModeSwitcher
|
||||
*
|
||||
* @param {string} dontOpenKey - The keyboard character we will ignore.
|
||||
*/
|
||||
async function test_dont_open_switcher(dontOpenKey) {
|
||||
let popup = UrlbarTestUtils.searchModeSwitcherPopup(window);
|
||||
|
||||
let popupOpened = false;
|
||||
let opened = () => {
|
||||
popupOpened = true;
|
||||
};
|
||||
info("Open the urlbar and open the switcher via keyboard");
|
||||
popup.addEventListener("popupshown", opened);
|
||||
focusSwitcher();
|
||||
EventUtils.synthesizeKey(dontOpenKey);
|
||||
|
||||
/* eslint-disable mozilla/no-arbitrary-setTimeout */
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
Assert.ok(!popupOpened, "The popup was not opened");
|
||||
popup.removeEventListener("popupshown", opened);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test we can navigate the SearchModeSwitcher with various keys
|
||||
*
|
||||
* @param {string} navKey - The keyboard character used to navigate.
|
||||
* @param {Int} navTimes - The number of times we press that key.
|
||||
* @param {object} searchMode - The searchMode that we expect to select.
|
||||
*/
|
||||
async function test_navigate_switcher(navKey, navTimes, searchMode) {
|
||||
let popup = UrlbarTestUtils.searchModeSwitcherPopup(window);
|
||||
let promiseMenuOpen = BrowserTestUtils.waitForEvent(popup, "popupshown");
|
||||
|
||||
info("Open the urlbar and open the switcher via keyboard");
|
||||
focusSwitcher();
|
||||
EventUtils.synthesizeKey("KEY_Enter");
|
||||
await promiseMenuOpen;
|
||||
|
||||
info("Select first result and enter search mode");
|
||||
for (let i = 0; i < navTimes; i++) {
|
||||
EventUtils.synthesizeKey(navKey);
|
||||
}
|
||||
EventUtils.synthesizeKey("KEY_Enter");
|
||||
|
||||
await UrlbarTestUtils.assertSearchMode(window, searchMode);
|
||||
|
||||
info("Escape Search mode");
|
||||
EventUtils.synthesizeKey("KEY_Escape");
|
||||
EventUtils.synthesizeKey("KEY_Escape");
|
||||
|
||||
await UrlbarTestUtils.assertSearchMode(window, null);
|
||||
}
|
||||
|
||||
let amazonSearchMode = {
|
||||
engineName: "Amazon.com",
|
||||
entry: "other",
|
||||
isPreview: false,
|
||||
isGeneralPurposeEngine: true,
|
||||
};
|
||||
let bingSearchMode = {
|
||||
engineName: "Bing",
|
||||
isGeneralPurposeEngine: true,
|
||||
source: 3,
|
||||
isPreview: false,
|
||||
entry: "other",
|
||||
};
|
||||
|
||||
add_task(async function test_keyboard_nav() {
|
||||
await test_open_switcher("KEY_Enter");
|
||||
await test_open_switcher("KEY_ArrowDown");
|
||||
await test_open_switcher(" ");
|
||||
|
||||
await test_dont_open_switcher("a");
|
||||
await test_dont_open_switcher("KEY_ArrowUp");
|
||||
await test_dont_open_switcher("x");
|
||||
|
||||
await test_navigate_switcher("KEY_Tab", 1, amazonSearchMode);
|
||||
await test_navigate_switcher("KEY_ArrowDown", 1, amazonSearchMode);
|
||||
await test_navigate_switcher("KEY_Tab", 2, bingSearchMode);
|
||||
await test_navigate_switcher("KEY_ArrowDown", 2, bingSearchMode);
|
||||
});
|
||||
|
||||
add_task(async function open_settings() {
|
||||
let popup = UrlbarTestUtils.searchModeSwitcherPopup(window);
|
||||
let promiseMenuOpen = BrowserTestUtils.waitForEvent(popup, "popupshown");
|
||||
|
||||
info("Open the urlbar and open the switcher via keyboard");
|
||||
focusSwitcher();
|
||||
EventUtils.synthesizeKey("KEY_Enter");
|
||||
await promiseMenuOpen;
|
||||
|
||||
let pageLoaded = BrowserTestUtils.browserLoaded(window);
|
||||
EventUtils.synthesizeKey("KEY_ArrowUp");
|
||||
EventUtils.synthesizeKey("KEY_Enter");
|
||||
await pageLoaded;
|
||||
|
||||
Assert.equal(
|
||||
window.gBrowser.selectedBrowser.currentURI.spec,
|
||||
"about:preferences#search",
|
||||
"Opened settings page"
|
||||
);
|
||||
});
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
// Tests the `fakespot_engagement` event.
|
||||
|
||||
const FAKESPOT_URL = "https://example.com/fakespot";
|
||||
const AMP_URL = "https://example.com/amp";
|
||||
|
||||
add_setup(async function test_setup() {
|
||||
Services.fog.testResetFOG();
|
||||
|
||||
|
@ -17,8 +20,8 @@ add_setup(async function test_setup() {
|
|||
type: "fakespot-suggestions",
|
||||
attachment: [
|
||||
{
|
||||
url: "https://example.com/maybe-good-item",
|
||||
title: "Maybe Good Item",
|
||||
url: FAKESPOT_URL,
|
||||
title: "Fakespot suggestion",
|
||||
rating: 4.8,
|
||||
total_reviews: 1234567,
|
||||
fakespot_grade: "A",
|
||||
|
@ -29,19 +32,8 @@ add_setup(async function test_setup() {
|
|||
},
|
||||
{
|
||||
type: "data",
|
||||
attachment: [
|
||||
{
|
||||
id: 1,
|
||||
url: "https://example.com/sponsored",
|
||||
title: "Sponsored suggestion",
|
||||
keywords: ["sponsored"],
|
||||
click_url: "https://example.com/click",
|
||||
impression_url: "https://example.com/impression",
|
||||
advertiser: "TestAdvertiser",
|
||||
iab_category: "22 - Shopping",
|
||||
icon: "1234",
|
||||
},
|
||||
],
|
||||
// eslint-disable-next-line mozilla/valid-lazy
|
||||
attachment: [lazy.QuickSuggestTestUtils.ampRemoteSettings()],
|
||||
},
|
||||
],
|
||||
prefs: [["fakespot.featureGate", true]],
|
||||
|
@ -53,14 +45,20 @@ add_setup(async function test_setup() {
|
|||
});
|
||||
});
|
||||
|
||||
// Clicks a Fakespot suggestion.
|
||||
add_task(async function engagement_fakespot() {
|
||||
await BrowserTestUtils.withNewTab("about:blank", async () => {
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
window,
|
||||
value: "maybe",
|
||||
value: "fakespot",
|
||||
});
|
||||
|
||||
await selectRowByProvider("UrlbarProviderQuickSuggest");
|
||||
Assert.ok(
|
||||
await getRowByURL(FAKESPOT_URL),
|
||||
"Fakespot row should be present"
|
||||
);
|
||||
|
||||
await selectRowByURL(FAKESPOT_URL);
|
||||
await doEnter();
|
||||
|
||||
assertGleanTelemetry("fakespot_engagement", [
|
||||
|
@ -71,14 +69,21 @@ add_task(async function engagement_fakespot() {
|
|||
Services.fog.testResetFOG();
|
||||
});
|
||||
|
||||
add_task(async function engagement_notFakespot() {
|
||||
// Clicks a non-Fakespot suggestion without matching Fakespot.
|
||||
add_task(async function engagement_other_fakespotAbsent() {
|
||||
await BrowserTestUtils.withNewTab("about:blank", async () => {
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
window,
|
||||
value: "sponsored",
|
||||
value: "amp",
|
||||
});
|
||||
|
||||
await selectRowByURL("https://example.com/sponsored");
|
||||
Assert.ok(
|
||||
!(await getRowByURL(FAKESPOT_URL)),
|
||||
"Fakespot row should be absent"
|
||||
);
|
||||
Assert.ok(await getRowByURL(AMP_URL), "AMP row should be present");
|
||||
|
||||
await selectRowByURL(AMP_URL);
|
||||
await doEnter();
|
||||
|
||||
assertGleanTelemetry("fakespot_engagement", []);
|
||||
|
@ -87,14 +92,48 @@ add_task(async function engagement_notFakespot() {
|
|||
Services.fog.testResetFOG();
|
||||
});
|
||||
|
||||
// Clicks a non-Fakespot suggestion after also matching Fakespot.
|
||||
add_task(async function engagement_other_fakespotPresent() {
|
||||
let visitUrl = "https://example.com/some-other-suggestion";
|
||||
await PlacesTestUtils.addVisits([visitUrl]);
|
||||
|
||||
await BrowserTestUtils.withNewTab("about:blank", async () => {
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
window,
|
||||
value: "suggestion",
|
||||
});
|
||||
|
||||
Assert.ok(
|
||||
await getRowByURL(FAKESPOT_URL),
|
||||
"Fakespot row should be present"
|
||||
);
|
||||
Assert.ok(
|
||||
await getRowByURL(visitUrl),
|
||||
"Visit/history row should be present"
|
||||
);
|
||||
|
||||
await selectRowByURL(visitUrl);
|
||||
await doEnter();
|
||||
|
||||
assertGleanTelemetry("fakespot_engagement", []);
|
||||
});
|
||||
|
||||
Services.fog.testResetFOG();
|
||||
});
|
||||
|
||||
// Abandons the search session after after matching Fakespot.
|
||||
add_task(async function abandonment() {
|
||||
await BrowserTestUtils.withNewTab("about:blank", async () => {
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
window,
|
||||
value: "maybe",
|
||||
value: "fakespot",
|
||||
});
|
||||
|
||||
await selectRowByProvider("UrlbarProviderQuickSuggest");
|
||||
Assert.ok(
|
||||
await getRowByURL(FAKESPOT_URL),
|
||||
"Fakespot row should be present"
|
||||
);
|
||||
|
||||
await doBlur();
|
||||
|
||||
assertGleanTelemetry("fakespot_engagement", []);
|
||||
|
@ -102,3 +141,13 @@ add_task(async function abandonment() {
|
|||
|
||||
Services.fog.testResetFOG();
|
||||
});
|
||||
|
||||
async function getRowByURL(url) {
|
||||
for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
|
||||
const detail = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
|
||||
if (detail.url === url) {
|
||||
return detail;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -551,10 +551,10 @@ class MockMerinoServer {
|
|||
provider: "adm",
|
||||
full_keyword: "amp",
|
||||
title: "Amp Suggestion",
|
||||
url: "http://example.com/amp",
|
||||
url: "https://example.com/amp",
|
||||
icon: null,
|
||||
impression_url: "http://example.com/amp-impression",
|
||||
click_url: "http://example.com/amp-click",
|
||||
impression_url: "https://example.com/amp-impression",
|
||||
click_url: "https://example.com/amp-click",
|
||||
block_id: 1,
|
||||
advertiser: "Amp",
|
||||
iab_category: "22 - Shopping",
|
||||
|
|
|
@ -393,7 +393,7 @@ class _QuickSuggestTestUtils {
|
|||
*/
|
||||
ampRemoteSettings({
|
||||
keywords = ["amp"],
|
||||
url = "http://example.com/amp",
|
||||
url = "https://example.com/amp",
|
||||
title = "Amp Suggestion",
|
||||
score = 0.3,
|
||||
} = {}) {
|
||||
|
@ -403,8 +403,8 @@ class _QuickSuggestTestUtils {
|
|||
title,
|
||||
score,
|
||||
id: 1,
|
||||
click_url: "http://example.com/amp-click",
|
||||
impression_url: "http://example.com/amp-impression",
|
||||
click_url: "https://example.com/amp-click",
|
||||
impression_url: "https://example.com/amp-impression",
|
||||
advertiser: "Amp",
|
||||
iab_category: "22 - Shopping",
|
||||
icon: "1234",
|
||||
|
@ -420,7 +420,7 @@ class _QuickSuggestTestUtils {
|
|||
*/
|
||||
wikipediaRemoteSettings({
|
||||
keywords = ["wikipedia"],
|
||||
url = "http://example.com/wikipedia",
|
||||
url = "https://example.com/wikipedia",
|
||||
title = "Wikipedia Suggestion",
|
||||
score = 0.2,
|
||||
} = {}) {
|
||||
|
@ -430,8 +430,8 @@ class _QuickSuggestTestUtils {
|
|||
title,
|
||||
score,
|
||||
id: 2,
|
||||
click_url: "http://example.com/wikipedia-click",
|
||||
impression_url: "http://example.com/wikipedia-impression",
|
||||
click_url: "https://example.com/wikipedia-click",
|
||||
impression_url: "https://example.com/wikipedia-impression",
|
||||
advertiser: "Wikipedia",
|
||||
iab_category: "5 - Education",
|
||||
icon: "1234",
|
||||
|
@ -447,7 +447,7 @@ class _QuickSuggestTestUtils {
|
|||
*/
|
||||
amoRemoteSettings({
|
||||
keywords = ["amo"],
|
||||
url = "http://example.com/amo",
|
||||
url = "https://example.com/amo",
|
||||
title = "Amo Suggestion",
|
||||
score = 0.2,
|
||||
} = {}) {
|
||||
|
@ -725,8 +725,8 @@ class _QuickSuggestTestUtils {
|
|||
* substrings. For example:
|
||||
* ```js
|
||||
* {
|
||||
* url: "http://example.com/foo-%YYYYMMDDHH%",
|
||||
* sponsoredClickUrl: "http://example.com/bar-%YYYYMMDDHH%",
|
||||
* url: "https://example.com/foo-%YYYYMMDDHH%",
|
||||
* sponsoredClickUrl: "https://example.com/bar-%YYYYMMDDHH%",
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
|
|
@ -361,10 +361,10 @@ async function doEngagementWithoutAddingResultToView(
|
|||
let getPriorityStub = sandbox.stub(UrlbarProviderQuickSuggest, "getPriority");
|
||||
getPriorityStub.returns(Infinity);
|
||||
|
||||
// Spy on `UrlbarProviderQuickSuggest.onLegacyEngagement()`.
|
||||
let onLegacyEngagementSpy = sandbox.spy(
|
||||
// Spy on `UrlbarProviderQuickSuggest.onSearchSessionEnd()`.
|
||||
let onSearchSessionEndSpy = sandbox.spy(
|
||||
UrlbarProviderQuickSuggest,
|
||||
"onLegacyEngagement"
|
||||
"onSearchSessionEnd"
|
||||
);
|
||||
|
||||
let sandboxCleanup = () => {
|
||||
|
@ -442,11 +442,8 @@ async function doEngagementWithoutAddingResultToView(
|
|||
});
|
||||
await loadPromise;
|
||||
|
||||
let engagementCalls = onLegacyEngagementSpy.getCalls().filter(call => {
|
||||
let state = call.args[0];
|
||||
return state == "engagement";
|
||||
});
|
||||
Assert.equal(engagementCalls.length, 1, "One engagement occurred");
|
||||
let spyCalls = onSearchSessionEndSpy.getCalls();
|
||||
Assert.equal(spyCalls.length, 1, "One session occurred");
|
||||
|
||||
// Clean up.
|
||||
resolveQuery();
|
||||
|
|
|
@ -155,12 +155,12 @@ function makeWikipediaResult({
|
|||
provider,
|
||||
keyword = "wikipedia",
|
||||
title = "Wikipedia Suggestion",
|
||||
url = "http://example.com/wikipedia",
|
||||
originalUrl = "http://example.com/wikipedia",
|
||||
url = "https://example.com/wikipedia",
|
||||
originalUrl = "https://example.com/wikipedia",
|
||||
icon = null,
|
||||
iconBlob = new Blob([new Uint8Array([])]),
|
||||
impressionUrl = "http://example.com/wikipedia-impression",
|
||||
clickUrl = "http://example.com/wikipedia-click",
|
||||
impressionUrl = "https://example.com/wikipedia-impression",
|
||||
clickUrl = "https://example.com/wikipedia-click",
|
||||
blockId = 2,
|
||||
advertiser = "Wikipedia",
|
||||
iabCategory = "5 - Education",
|
||||
|
@ -221,12 +221,12 @@ function makeAmpResult({
|
|||
provider,
|
||||
keyword = "amp",
|
||||
title = "Amp Suggestion",
|
||||
url = "http://example.com/amp",
|
||||
originalUrl = "http://example.com/amp",
|
||||
url = "https://example.com/amp",
|
||||
originalUrl = "https://example.com/amp",
|
||||
icon = null,
|
||||
iconBlob = new Blob([new Uint8Array([])]),
|
||||
impressionUrl = "http://example.com/amp-impression",
|
||||
clickUrl = "http://example.com/amp-click",
|
||||
impressionUrl = "https://example.com/amp-impression",
|
||||
clickUrl = "https://example.com/amp-click",
|
||||
blockId = 1,
|
||||
advertiser = "Amp",
|
||||
iabCategory = "22 - Shopping",
|
||||
|
@ -339,8 +339,8 @@ function makeAmoResult({
|
|||
provider,
|
||||
title = "Amo Suggestion",
|
||||
description = "Amo description",
|
||||
url = "http://example.com/amo",
|
||||
originalUrl = "http://example.com/amo",
|
||||
url = "https://example.com/amo",
|
||||
originalUrl = "https://example.com/amo",
|
||||
icon = null,
|
||||
setUtmParams = true,
|
||||
}) {
|
||||
|
@ -425,7 +425,6 @@ function makeWeatherResult({
|
|||
forecast: MerinoTestUtils.WEATHER_SUGGESTION.forecast.summary,
|
||||
high: MerinoTestUtils.WEATHER_SUGGESTION.forecast.high[temperatureUnit],
|
||||
low: MerinoTestUtils.WEATHER_SUGGESTION.forecast.low[temperatureUnit],
|
||||
shouldNavigate: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -1526,7 +1526,9 @@ add_tasks_with_rust(async function tabToSearch() {
|
|||
makeSearchResult(context, {
|
||||
engineName: engine.name,
|
||||
engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
uri: UrlbarUtils.stripPublicSuffixFromHost(engine.searchUrlDomain),
|
||||
searchUrlDomainWithoutSuffix: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
engine.searchUrlDomain
|
||||
),
|
||||
providesSearchMode: true,
|
||||
query: "",
|
||||
providerName: "TabToSearch",
|
||||
|
@ -1641,8 +1643,8 @@ add_task(async function rustProviders() {
|
|||
"suggest.quicksuggest.sponsored": true,
|
||||
},
|
||||
expectedUrls: [
|
||||
"http://example.com/amp",
|
||||
"http://example.com/wikipedia",
|
||||
"https://example.com/amp",
|
||||
"https://example.com/wikipedia",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -1650,14 +1652,14 @@ add_task(async function rustProviders() {
|
|||
"suggest.quicksuggest.nonsponsored": true,
|
||||
"suggest.quicksuggest.sponsored": false,
|
||||
},
|
||||
expectedUrls: ["http://example.com/wikipedia"],
|
||||
expectedUrls: ["https://example.com/wikipedia"],
|
||||
},
|
||||
{
|
||||
prefs: {
|
||||
"suggest.quicksuggest.nonsponsored": false,
|
||||
"suggest.quicksuggest.sponsored": true,
|
||||
},
|
||||
expectedUrls: ["http://example.com/amp"],
|
||||
expectedUrls: ["https://example.com/amp"],
|
||||
},
|
||||
{
|
||||
prefs: {
|
||||
|
|
|
@ -856,7 +856,6 @@ function makeExpectedResult({
|
|||
totalReviews,
|
||||
fakespotGrade,
|
||||
fakespotProvider,
|
||||
shouldNavigate: true,
|
||||
dynamicType: "fakespot",
|
||||
icon: null,
|
||||
},
|
||||
|
|
|
@ -149,16 +149,31 @@ add_task(async function canceledQueries() {
|
|||
});
|
||||
|
||||
function endEngagement({ controller, context = null, state = "engagement" }) {
|
||||
UrlbarProviderQuickSuggest.onLegacyEngagement(
|
||||
state,
|
||||
context ||
|
||||
createContext("endEngagement", {
|
||||
providers: [UrlbarProviderQuickSuggest.name],
|
||||
isPrivate: false,
|
||||
}),
|
||||
{ selIndex: -1 },
|
||||
controller
|
||||
);
|
||||
context ||= createContext("endEngagement", {
|
||||
providers: [UrlbarProviderQuickSuggest.name],
|
||||
isPrivate: false,
|
||||
});
|
||||
let details = { selIndex: -1, result: { payload: {} } };
|
||||
|
||||
switch (state) {
|
||||
case "engagement":
|
||||
UrlbarProviderQuickSuggest.onEngagement(context, controller, details);
|
||||
UrlbarProviderQuickSuggest.onSearchSessionEnd(
|
||||
context,
|
||||
controller,
|
||||
details
|
||||
);
|
||||
break;
|
||||
case "abandonment":
|
||||
UrlbarProviderQuickSuggest.onSearchSessionEnd(
|
||||
context,
|
||||
controller,
|
||||
details
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unrecognized engagement state: " + state);
|
||||
}
|
||||
|
||||
Assert.strictEqual(
|
||||
gClient.sessionID,
|
||||
|
|
|
@ -724,24 +724,11 @@ add_tasks_with_rust(async function block() {
|
|||
let provider = UrlbarProvidersManager.getProvider(result.providerName);
|
||||
Assert.ok(provider, "Sanity check: Result provider found");
|
||||
|
||||
if (result.providerName === "UrlbarProviderQuickSuggest") {
|
||||
provider.onLegacyEngagement(
|
||||
"engagement",
|
||||
context,
|
||||
{
|
||||
result,
|
||||
selType: "dismiss",
|
||||
selIndex: context.results[0].rowIndex,
|
||||
},
|
||||
controller
|
||||
);
|
||||
} else {
|
||||
provider.onEngagement(context, controller, {
|
||||
result,
|
||||
selType: "dismiss",
|
||||
selIndex: context.results[0].rowIndex,
|
||||
});
|
||||
}
|
||||
provider.onEngagement(context, controller, {
|
||||
result,
|
||||
selType: "dismiss",
|
||||
selIndex: context.results[0].rowIndex,
|
||||
});
|
||||
Assert.ok(
|
||||
!UrlbarPrefs.get("suggest.weather"),
|
||||
"suggest.weather is false after blocking the result"
|
||||
|
|
|
@ -711,6 +711,9 @@ function makeRemoteTabResult(
|
|||
* If the window to test is a private window.
|
||||
* @param {boolean} [options.isPrivateEngine]
|
||||
* If the engine is a private engine.
|
||||
* @param {string} [options.searchUrlDomainWithoutSuffix]
|
||||
* For tab-to-search results, the search engine domain without the public
|
||||
* suffix.
|
||||
* @param {number} [options.type]
|
||||
* The type of the search result. Defaults to UrlbarUtils.RESULT_TYPE.SEARCH.
|
||||
* @param {number} [options.source]
|
||||
|
@ -739,6 +742,7 @@ function makeSearchResult(
|
|||
providerName,
|
||||
inPrivateWindow,
|
||||
isPrivateEngine,
|
||||
searchUrlDomainWithoutSuffix,
|
||||
heuristic = false,
|
||||
trending = false,
|
||||
isRichSuggestion = false,
|
||||
|
@ -786,10 +790,11 @@ function makeSearchResult(
|
|||
payload.url = uri;
|
||||
}
|
||||
if (providerName == "TabToSearch") {
|
||||
payload.satisfiesAutofillThreshold = satisfiesAutofillThreshold;
|
||||
if (payload.url.startsWith("www.")) {
|
||||
payload.url = payload.url.substring(4);
|
||||
if (searchUrlDomainWithoutSuffix.startsWith("www.")) {
|
||||
searchUrlDomainWithoutSuffix = searchUrlDomainWithoutSuffix.substring(4);
|
||||
}
|
||||
payload.searchUrlDomainWithoutSuffix = searchUrlDomainWithoutSuffix;
|
||||
payload.satisfiesAutofillThreshold = satisfiesAutofillThreshold;
|
||||
payload.isGeneralPurposeEngine = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -106,7 +106,9 @@ add_task(
|
|||
makeSearchResult(context, {
|
||||
engineName: engine.name,
|
||||
engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
uri: UrlbarUtils.stripPublicSuffixFromHost(engine.searchUrlDomain),
|
||||
searchUrlDomainWithoutSuffix: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
engine.searchUrlDomain
|
||||
),
|
||||
providesSearchMode: true,
|
||||
query: "",
|
||||
providerName: "TabToSearch",
|
||||
|
|
|
@ -144,12 +144,12 @@ function makeAmpResult({
|
|||
provider,
|
||||
keyword = "amp",
|
||||
title = "Amp Suggestion",
|
||||
url = "http://example.com/amp",
|
||||
originalUrl = "http://example.com/amp",
|
||||
url = "https://example.com/amp",
|
||||
originalUrl = "https://example.com/amp",
|
||||
icon = null,
|
||||
iconBlob = new Blob([new Uint8Array([])]),
|
||||
impressionUrl = "http://example.com/amp-impression",
|
||||
clickUrl = "http://example.com/amp-click",
|
||||
impressionUrl = "https://example.com/amp-impression",
|
||||
clickUrl = "https://example.com/amp-click",
|
||||
blockId = 1,
|
||||
advertiser = "Amp",
|
||||
iabCategory = "22 - Shopping",
|
||||
|
@ -209,12 +209,12 @@ function makeWikipediaResult({
|
|||
provider,
|
||||
keyword = "wikipedia",
|
||||
title = "Wikipedia Suggestion",
|
||||
url = "http://example.com/wikipedia",
|
||||
originalUrl = "http://example.com/wikipedia",
|
||||
url = "https://example.com/wikipedia",
|
||||
originalUrl = "https://example.com/wikipedia",
|
||||
icon = null,
|
||||
iconBlob = new Blob([new Uint8Array([])]),
|
||||
impressionUrl = "http://example.com/wikipedia-impression",
|
||||
clickUrl = "http://example.com/wikipedia-click",
|
||||
impressionUrl = "https://example.com/wikipedia-impression",
|
||||
clickUrl = "https://example.com/wikipedia-click",
|
||||
blockId = 1,
|
||||
advertiser = "Wikipedia",
|
||||
iabCategory = "5 - Education",
|
||||
|
|
|
@ -49,7 +49,9 @@ add_task(async function basic() {
|
|||
makeSearchResult(context, {
|
||||
engineName: testEngine.name,
|
||||
engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
uri: UrlbarUtils.stripPublicSuffixFromHost(testEngine.searchUrlDomain),
|
||||
searchUrlDomainWithoutSuffix: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
testEngine.searchUrlDomain
|
||||
),
|
||||
providesSearchMode: true,
|
||||
query: "",
|
||||
providerName: "TabToSearch",
|
||||
|
@ -94,7 +96,9 @@ add_task(async function noAutofill() {
|
|||
makeSearchResult(context, {
|
||||
engineName: testEngine.name,
|
||||
engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
uri: UrlbarUtils.stripPublicSuffixFromHost(testEngine.searchUrlDomain),
|
||||
searchUrlDomainWithoutSuffix: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
testEngine.searchUrlDomain
|
||||
),
|
||||
providesSearchMode: true,
|
||||
query: "",
|
||||
providerName: "TabToSearch",
|
||||
|
@ -145,7 +149,9 @@ add_task(async function ignoreWww() {
|
|||
makeSearchResult(context, {
|
||||
engineName: testEngine.name,
|
||||
engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
uri: UrlbarUtils.stripPublicSuffixFromHost(testEngine.searchUrlDomain),
|
||||
searchUrlDomainWithoutSuffix: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
testEngine.searchUrlDomain
|
||||
),
|
||||
providesSearchMode: true,
|
||||
query: "",
|
||||
providerName: "TabToSearch",
|
||||
|
@ -179,7 +185,7 @@ add_task(async function ignoreWww() {
|
|||
makeSearchResult(context, {
|
||||
engineName: wwwTestEngine.name,
|
||||
engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
uri: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
searchUrlDomainWithoutSuffix: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
wwwTestEngine.searchUrlDomain
|
||||
),
|
||||
providesSearchMode: true,
|
||||
|
@ -207,7 +213,7 @@ add_task(async function ignoreWww() {
|
|||
makeSearchResult(context, {
|
||||
engineName: wwwTestEngine.name,
|
||||
engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
uri: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
searchUrlDomainWithoutSuffix: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
wwwTestEngine.searchUrlDomain
|
||||
),
|
||||
providesSearchMode: true,
|
||||
|
@ -266,7 +272,7 @@ add_task(async function conflictingEngines() {
|
|||
makeSearchResult(context, {
|
||||
engineName: fooTestEngine.name,
|
||||
engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
uri: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
searchUrlDomainWithoutSuffix: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
fooTestEngine.searchUrlDomain
|
||||
),
|
||||
providesSearchMode: true,
|
||||
|
@ -298,7 +304,7 @@ add_task(async function conflictingEngines() {
|
|||
makeSearchResult(context, {
|
||||
engineName: fooBarTestEngine.name,
|
||||
engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
uri: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
searchUrlDomainWithoutSuffix: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
fooBarTestEngine.searchUrlDomain
|
||||
),
|
||||
providesSearchMode: true,
|
||||
|
@ -361,7 +367,9 @@ add_task(async function multipleEnginesForHostname() {
|
|||
makeSearchResult(context, {
|
||||
engineName: testEngine.name,
|
||||
engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
uri: UrlbarUtils.stripPublicSuffixFromHost(testEngine.searchUrlDomain),
|
||||
searchUrlDomainWithoutSuffix: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
testEngine.searchUrlDomain
|
||||
),
|
||||
providesSearchMode: true,
|
||||
query: "",
|
||||
providerName: "TabToSearch",
|
||||
|
@ -396,7 +404,9 @@ add_task(async function test_casing() {
|
|||
makeSearchResult(context, {
|
||||
engineName: testEngine.name,
|
||||
engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
uri: UrlbarUtils.stripPublicSuffixFromHost(testEngine.searchUrlDomain),
|
||||
searchUrlDomainWithoutSuffix: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
testEngine.searchUrlDomain
|
||||
),
|
||||
providesSearchMode: true,
|
||||
query: "",
|
||||
providerName: "TabToSearch",
|
||||
|
@ -430,7 +440,9 @@ add_task(async function test_publicSuffix() {
|
|||
makeSearchResult(context, {
|
||||
engineName: engine.name,
|
||||
engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
uri: UrlbarUtils.stripPublicSuffixFromHost(engine.searchUrlDomain),
|
||||
searchUrlDomainWithoutSuffix: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
engine.searchUrlDomain
|
||||
),
|
||||
providesSearchMode: true,
|
||||
query: "",
|
||||
providerName: "TabToSearch",
|
||||
|
@ -505,7 +517,9 @@ add_task(async function test_disabledEngine() {
|
|||
makeSearchResult(context, {
|
||||
engineName: engine.name,
|
||||
engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
uri: UrlbarUtils.stripPublicSuffixFromHost(engine.searchUrlDomain),
|
||||
searchUrlDomainWithoutSuffix: UrlbarUtils.stripPublicSuffixFromHost(
|
||||
engine.searchUrlDomain
|
||||
),
|
||||
providesSearchMode: true,
|
||||
query: "",
|
||||
providerName: "TabToSearch",
|
||||
|
|
|
@ -71,7 +71,7 @@ add_task(async function test() {
|
|||
makeSearchResult(context, {
|
||||
engineName: "TestEngine",
|
||||
engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
uri: "en.example.",
|
||||
searchUrlDomainWithoutSuffix: "en.example.",
|
||||
providesSearchMode: true,
|
||||
query: "",
|
||||
providerName: "TabToSearch",
|
||||
|
@ -114,7 +114,7 @@ add_task(async function test() {
|
|||
makeSearchResult(context, {
|
||||
engineName: engine2.name,
|
||||
engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
uri: "www.it.mochi.",
|
||||
searchUrlDomainWithoutSuffix: "www.it.mochi.",
|
||||
providesSearchMode: true,
|
||||
query: "",
|
||||
providerName: "TabToSearch",
|
||||
|
@ -162,7 +162,7 @@ add_task(async function test() {
|
|||
makeSearchResult(context, {
|
||||
engineName: "TestEngine3",
|
||||
engineIconUri: UrlbarUtils.ICON.SEARCH_GLASS,
|
||||
uri: "search.foo.",
|
||||
searchUrlDomainWithoutSuffix: "search.foo.",
|
||||
providesSearchMode: true,
|
||||
query: "",
|
||||
providerName: "TabToSearch",
|
||||
|
|
|
@ -414,6 +414,7 @@ newtab-topic-label-society = Life Hacks
|
|||
newtab-topic-label-sports = Sports
|
||||
newtab-topic-label-tech = Tech
|
||||
newtab-topic-label-travel = Travel
|
||||
newtab-topic-label-home = Home & Garden
|
||||
|
||||
## Topic Selection Modal
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"af": {
|
||||
"pin": false,
|
||||
|
@ -35,7 +35,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"an": {
|
||||
"pin": false,
|
||||
|
@ -54,7 +54,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ar": {
|
||||
"pin": false,
|
||||
|
@ -73,7 +73,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ast": {
|
||||
"pin": false,
|
||||
|
@ -92,7 +92,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"az": {
|
||||
"pin": false,
|
||||
|
@ -111,7 +111,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"be": {
|
||||
"pin": false,
|
||||
|
@ -130,7 +130,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"bg": {
|
||||
"pin": false,
|
||||
|
@ -149,7 +149,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"bn": {
|
||||
"pin": false,
|
||||
|
@ -168,7 +168,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"bo": {
|
||||
"pin": false,
|
||||
|
@ -187,7 +187,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"br": {
|
||||
"pin": false,
|
||||
|
@ -206,7 +206,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"brx": {
|
||||
"pin": false,
|
||||
|
@ -225,7 +225,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"bs": {
|
||||
"pin": false,
|
||||
|
@ -244,7 +244,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ca": {
|
||||
"pin": false,
|
||||
|
@ -263,7 +263,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ca-valencia": {
|
||||
"pin": false,
|
||||
|
@ -282,7 +282,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"cak": {
|
||||
"pin": false,
|
||||
|
@ -301,7 +301,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ckb": {
|
||||
"pin": false,
|
||||
|
@ -320,7 +320,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"cs": {
|
||||
"pin": false,
|
||||
|
@ -339,7 +339,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"cy": {
|
||||
"pin": false,
|
||||
|
@ -358,7 +358,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"da": {
|
||||
"pin": false,
|
||||
|
@ -377,7 +377,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"de": {
|
||||
"pin": false,
|
||||
|
@ -396,7 +396,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"dsb": {
|
||||
"pin": false,
|
||||
|
@ -415,7 +415,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"el": {
|
||||
"pin": false,
|
||||
|
@ -434,7 +434,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"en-CA": {
|
||||
"pin": false,
|
||||
|
@ -453,7 +453,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"en-GB": {
|
||||
"pin": false,
|
||||
|
@ -472,7 +472,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"eo": {
|
||||
"pin": false,
|
||||
|
@ -491,7 +491,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"es-AR": {
|
||||
"pin": false,
|
||||
|
@ -510,7 +510,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"es-CL": {
|
||||
"pin": false,
|
||||
|
@ -529,7 +529,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"es-ES": {
|
||||
"pin": false,
|
||||
|
@ -548,7 +548,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"es-MX": {
|
||||
"pin": false,
|
||||
|
@ -567,7 +567,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"et": {
|
||||
"pin": false,
|
||||
|
@ -586,7 +586,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"eu": {
|
||||
"pin": false,
|
||||
|
@ -605,7 +605,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"fa": {
|
||||
"pin": false,
|
||||
|
@ -624,7 +624,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ff": {
|
||||
"pin": false,
|
||||
|
@ -643,7 +643,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"fi": {
|
||||
"pin": false,
|
||||
|
@ -662,7 +662,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"fr": {
|
||||
"pin": false,
|
||||
|
@ -681,7 +681,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"fur": {
|
||||
"pin": false,
|
||||
|
@ -700,7 +700,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"fy-NL": {
|
||||
"pin": false,
|
||||
|
@ -719,7 +719,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ga-IE": {
|
||||
"pin": false,
|
||||
|
@ -738,7 +738,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"gd": {
|
||||
"pin": false,
|
||||
|
@ -757,7 +757,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"gl": {
|
||||
"pin": false,
|
||||
|
@ -776,7 +776,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"gn": {
|
||||
"pin": false,
|
||||
|
@ -795,7 +795,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"gu-IN": {
|
||||
"pin": false,
|
||||
|
@ -814,7 +814,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"he": {
|
||||
"pin": false,
|
||||
|
@ -833,7 +833,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"hi-IN": {
|
||||
"pin": false,
|
||||
|
@ -852,7 +852,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"hr": {
|
||||
"pin": false,
|
||||
|
@ -871,7 +871,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"hsb": {
|
||||
"pin": false,
|
||||
|
@ -890,7 +890,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"hu": {
|
||||
"pin": false,
|
||||
|
@ -909,7 +909,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"hy-AM": {
|
||||
"pin": false,
|
||||
|
@ -928,7 +928,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"hye": {
|
||||
"pin": false,
|
||||
|
@ -947,7 +947,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ia": {
|
||||
"pin": false,
|
||||
|
@ -966,7 +966,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"id": {
|
||||
"pin": false,
|
||||
|
@ -985,7 +985,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"is": {
|
||||
"pin": false,
|
||||
|
@ -1004,7 +1004,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"it": {
|
||||
"pin": false,
|
||||
|
@ -1023,7 +1023,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ja": {
|
||||
"pin": false,
|
||||
|
@ -1040,7 +1040,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ja-JP-mac": {
|
||||
"pin": false,
|
||||
|
@ -1048,7 +1048,7 @@
|
|||
"macosx64",
|
||||
"macosx64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ka": {
|
||||
"pin": false,
|
||||
|
@ -1067,7 +1067,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"kab": {
|
||||
"pin": false,
|
||||
|
@ -1086,7 +1086,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"kk": {
|
||||
"pin": false,
|
||||
|
@ -1105,7 +1105,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"km": {
|
||||
"pin": false,
|
||||
|
@ -1124,7 +1124,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"kn": {
|
||||
"pin": false,
|
||||
|
@ -1143,7 +1143,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ko": {
|
||||
"pin": false,
|
||||
|
@ -1162,7 +1162,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"lij": {
|
||||
"pin": false,
|
||||
|
@ -1181,7 +1181,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"lo": {
|
||||
"pin": false,
|
||||
|
@ -1200,7 +1200,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"lt": {
|
||||
"pin": false,
|
||||
|
@ -1219,7 +1219,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ltg": {
|
||||
"pin": false,
|
||||
|
@ -1238,7 +1238,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"lv": {
|
||||
"pin": false,
|
||||
|
@ -1257,7 +1257,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"meh": {
|
||||
"pin": false,
|
||||
|
@ -1276,7 +1276,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"mk": {
|
||||
"pin": false,
|
||||
|
@ -1295,7 +1295,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"mr": {
|
||||
"pin": false,
|
||||
|
@ -1314,7 +1314,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ms": {
|
||||
"pin": false,
|
||||
|
@ -1333,7 +1333,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"my": {
|
||||
"pin": false,
|
||||
|
@ -1352,7 +1352,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"nb-NO": {
|
||||
"pin": false,
|
||||
|
@ -1371,7 +1371,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ne-NP": {
|
||||
"pin": false,
|
||||
|
@ -1390,7 +1390,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"nl": {
|
||||
"pin": false,
|
||||
|
@ -1409,7 +1409,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"nn-NO": {
|
||||
"pin": false,
|
||||
|
@ -1428,7 +1428,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"oc": {
|
||||
"pin": false,
|
||||
|
@ -1447,7 +1447,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"pa-IN": {
|
||||
"pin": false,
|
||||
|
@ -1466,7 +1466,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"pl": {
|
||||
"pin": false,
|
||||
|
@ -1485,7 +1485,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"pt-BR": {
|
||||
"pin": false,
|
||||
|
@ -1504,7 +1504,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"pt-PT": {
|
||||
"pin": false,
|
||||
|
@ -1523,7 +1523,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"rm": {
|
||||
"pin": false,
|
||||
|
@ -1542,7 +1542,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ro": {
|
||||
"pin": false,
|
||||
|
@ -1561,7 +1561,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ru": {
|
||||
"pin": false,
|
||||
|
@ -1580,7 +1580,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"sat": {
|
||||
"pin": false,
|
||||
|
@ -1599,7 +1599,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"sc": {
|
||||
"pin": false,
|
||||
|
@ -1618,7 +1618,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"scn": {
|
||||
"pin": false,
|
||||
|
@ -1637,7 +1637,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"sco": {
|
||||
"pin": false,
|
||||
|
@ -1656,7 +1656,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"si": {
|
||||
"pin": false,
|
||||
|
@ -1675,7 +1675,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"sk": {
|
||||
"pin": false,
|
||||
|
@ -1694,7 +1694,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"skr": {
|
||||
"pin": false,
|
||||
|
@ -1713,7 +1713,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"sl": {
|
||||
"pin": false,
|
||||
|
@ -1732,7 +1732,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"son": {
|
||||
"pin": false,
|
||||
|
@ -1751,7 +1751,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"sq": {
|
||||
"pin": false,
|
||||
|
@ -1770,7 +1770,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"sr": {
|
||||
"pin": false,
|
||||
|
@ -1789,7 +1789,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"sv-SE": {
|
||||
"pin": false,
|
||||
|
@ -1808,7 +1808,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"szl": {
|
||||
"pin": false,
|
||||
|
@ -1827,7 +1827,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ta": {
|
||||
"pin": false,
|
||||
|
@ -1846,7 +1846,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"te": {
|
||||
"pin": false,
|
||||
|
@ -1865,7 +1865,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"tg": {
|
||||
"pin": false,
|
||||
|
@ -1884,7 +1884,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"th": {
|
||||
"pin": false,
|
||||
|
@ -1903,7 +1903,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"tl": {
|
||||
"pin": false,
|
||||
|
@ -1922,7 +1922,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"tr": {
|
||||
"pin": false,
|
||||
|
@ -1941,7 +1941,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"trs": {
|
||||
"pin": false,
|
||||
|
@ -1960,7 +1960,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"uk": {
|
||||
"pin": false,
|
||||
|
@ -1979,7 +1979,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"ur": {
|
||||
"pin": false,
|
||||
|
@ -1998,7 +1998,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"uz": {
|
||||
"pin": false,
|
||||
|
@ -2017,7 +2017,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"vi": {
|
||||
"pin": false,
|
||||
|
@ -2036,7 +2036,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"wo": {
|
||||
"pin": false,
|
||||
|
@ -2055,7 +2055,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"xh": {
|
||||
"pin": false,
|
||||
|
@ -2074,7 +2074,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"zh-CN": {
|
||||
"pin": false,
|
||||
|
@ -2093,7 +2093,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
},
|
||||
"zh-TW": {
|
||||
"pin": false,
|
||||
|
@ -2112,6 +2112,6 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "163c5ee9ce09176092a5e1290a8f5134bc7590b8"
|
||||
"revision": "c923bb54ed924cf5d23635b623e6f43c2951ccce"
|
||||
}
|
||||
}
|
|
@ -45,9 +45,6 @@ DEFINES["APP_VERSION"] = CONFIG["MOZ_APP_VERSION"]
|
|||
for cdm in CONFIG["MOZ_EME_MODULES"]:
|
||||
DEFINES["MOZ_%s_EME" % cdm.upper()] = True
|
||||
|
||||
if CONFIG["MOZ_GPSD"]:
|
||||
DEFINES["MOZ_GPSD"] = True
|
||||
|
||||
if CONFIG["MOZ_UPDATE_AGENT"]:
|
||||
DEFINES["MOZ_UPDATE_AGENT"] = True
|
||||
|
||||
|
|
|
@ -29,6 +29,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line media-query-no-invalid */
|
||||
@media (-moz-bool-pref: "browser.urlbar.searchModeSwitcher.featureGate") or (-moz-bool-pref: "browser.urlbar.scotchBonnet.enableOverride") {
|
||||
#identity-box[pageproxystate="invalid"],
|
||||
#identity-box.chromeUI {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.identity-box-button {
|
||||
padding-inline: var(--urlbar-icon-padding);
|
||||
border-radius: var(--urlbar-icon-border-radius);
|
||||
|
|
|
@ -1079,18 +1079,26 @@ moz-input-box > menupopup .context-menu-add-engine > .menu-iconic-left::after {
|
|||
}
|
||||
|
||||
#urlbar-searchmode-switcher {
|
||||
/* Hidden until urlbar opens */
|
||||
display: none;
|
||||
margin-inline-end: var(--urlbar-icon-padding);
|
||||
background-color: var(--toolbar-field-background-color);
|
||||
border-radius: var(--toolbarbutton-border-radius);
|
||||
margin-inline-end: var(--urlbar-icon-padding);
|
||||
padding-block: 0;
|
||||
|
||||
&:focus-visible {
|
||||
outline: var(--focus-outline);
|
||||
outline-offset: var(--focus-outline-inset);
|
||||
}
|
||||
|
||||
#urlbar[searchmode] & {
|
||||
margin-inline-end: 0;
|
||||
border-start-end-radius: 0;
|
||||
border-end-end-radius: 0;
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line media-query-no-invalid */
|
||||
@media (-moz-bool-pref: "browser.urlbar.searchModeSwitcher.featureGate") or (-moz-bool-pref: "browser.urlbar.scotchBonnet.enableOverride") {
|
||||
/* Don't show on mousedown to avoid shifting input field text */
|
||||
#urlbar[focused]:not([focusing-via-mousedown]) & {
|
||||
display: flex;
|
||||
}
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1103,8 +1111,22 @@ moz-input-box > menupopup .context-menu-add-engine > .menu-iconic-left::after {
|
|||
fill: var(--text-color-deemphasized);
|
||||
}
|
||||
|
||||
#searchmode-switcher-chicklet {
|
||||
background-color: var(--toolbar-field-background-color);
|
||||
border-start-end-radius: var(--toolbarbutton-border-radius);
|
||||
border-end-end-radius: var(--toolbarbutton-border-radius);
|
||||
align-items: center;
|
||||
margin-inline-end: var(--urlbar-icon-padding);
|
||||
display: none;
|
||||
|
||||
#urlbar[searchmode] & {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
||||
#searchmode-switcher-title {
|
||||
margin-inline-start: 0px;
|
||||
margin-inline-start: 0;
|
||||
align-items: center;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
|
@ -1114,7 +1136,6 @@ moz-input-box > menupopup .context-menu-add-engine > .menu-iconic-left::after {
|
|||
#searchmode-switcher-close {
|
||||
list-style-image: url("chrome://global/skin/icons/close-12.svg");
|
||||
pointer-events: all;
|
||||
display: none;
|
||||
-moz-context-properties: fill;
|
||||
fill: var(--text-color-deemphasized);
|
||||
|
||||
|
@ -1132,13 +1153,9 @@ moz-input-box > menupopup .context-menu-add-engine > .menu-iconic-left::after {
|
|||
}
|
||||
}
|
||||
|
||||
#searchmode-switcher-popup {
|
||||
--panel-padding: var(--space-small);
|
||||
--arrowpanel-menuitem-margin: 0;
|
||||
}
|
||||
|
||||
#searchmode-switcher-popup-description {
|
||||
margin: var(--space-small);
|
||||
margin-bottom: var(--space-xsmall);
|
||||
}
|
||||
|
||||
#searchmode-switcher-popup-search-settings-icon {
|
||||
|
|
|
@ -316,7 +316,6 @@ system_headers = [
|
|||
"gmodule.h",
|
||||
"gnome.h",
|
||||
"gnu/libc-version.h",
|
||||
"gps.h",
|
||||
"grp.h",
|
||||
"gssapi_generic.h",
|
||||
"gssapi/gssapi_generic.h",
|
||||
|
|
|
@ -49,6 +49,7 @@ if (isNode()) {
|
|||
pref("devtools.debugger.javascript-tracing-on-next-interaction", false);
|
||||
pref("devtools.debugger.javascript-tracing-on-next-load", false);
|
||||
pref("devtools.debugger.javascript-tracing-function-return", false);
|
||||
pref("devtools.debugger.javascript-tracing-native", false);
|
||||
pref("devtools.debugger.hide-ignored-sources", false);
|
||||
pref("devtools.debugger.source-map-ignore-list-enabled", true);
|
||||
pref("devtools.debugger.features.wasm", true);
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const FLASH_TARGETS_SELECTOR = "[class*=theme-fg-color], .force-color-on-flash";
|
||||
|
||||
/**
|
||||
* Apply a 'flashed' background and foreground color to elements. Intended
|
||||
* to be used with flashElementOff as a way of drawing attention to an element.
|
||||
|
@ -32,10 +34,9 @@ function flashElementOn(
|
|||
backgroundElt.classList.add(backgroundClass);
|
||||
|
||||
foregroundElt.classList.add("theme-fg-contrast");
|
||||
[].forEach.call(
|
||||
foregroundElt.querySelectorAll("[class*=theme-fg-color]"),
|
||||
span => span.classList.add("theme-fg-contrast")
|
||||
);
|
||||
foregroundElt
|
||||
.querySelectorAll(FLASH_TARGETS_SELECTOR)
|
||||
.forEach(span => span.classList.add("theme-fg-contrast"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,10 +69,9 @@ function flashElementOff(
|
|||
foregroundElt.classList.remove("theme-fg-contrast");
|
||||
// Make sure the foreground animation class is removed
|
||||
foregroundElt.classList.remove("flash-out");
|
||||
[].forEach.call(
|
||||
foregroundElt.querySelectorAll("[class*=theme-fg-color]"),
|
||||
span => span.classList.remove("theme-fg-contrast")
|
||||
);
|
||||
foregroundElt
|
||||
.querySelectorAll(FLASH_TARGETS_SELECTOR)
|
||||
.forEach(span => span.classList.remove("theme-fg-contrast"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -161,7 +161,7 @@ ElementEditor.prototype = {
|
|||
this.elt.appendChild(open);
|
||||
|
||||
this.tag = this.doc.createElement("span");
|
||||
this.tag.classList.add("tag", "theme-fg-color3");
|
||||
this.tag.classList.add("tag", "force-color-on-flash");
|
||||
this.tag.setAttribute("tabindex", "-1");
|
||||
this.tag.textContent = this.node.displayName;
|
||||
open.appendChild(this.tag);
|
||||
|
@ -194,6 +194,7 @@ ElementEditor.prototype = {
|
|||
this.newAttr.editMode = editableField({
|
||||
element: this.newAttr,
|
||||
multiline: true,
|
||||
inputClass: "newattr-input",
|
||||
maxWidth: () => getAutocompleteMaxWidth(this.newAttr, this.container.elt),
|
||||
trigger: "dblclick",
|
||||
stopOnReturn: true,
|
||||
|
@ -234,7 +235,7 @@ ElementEditor.prototype = {
|
|||
this.elt.appendChild(close);
|
||||
|
||||
this.closeTag = this.doc.createElement("span");
|
||||
this.closeTag.classList.add("tag", "theme-fg-color3");
|
||||
this.closeTag.classList.add("tag");
|
||||
this.closeTag.textContent = this.node.displayName;
|
||||
close.appendChild(this.closeTag);
|
||||
|
||||
|
@ -699,9 +700,9 @@ ElementEditor.prototype = {
|
|||
* " ",
|
||||
* dom.span(
|
||||
* { className: "editable", tabIndex: 0 },
|
||||
* dom.span({ className: "attr-name theme-fg-color1" }, attribute.name),
|
||||
* dom.span({ className: "attr-name" }, attribute.name),
|
||||
* '="',
|
||||
* dom.span({ className: "attr-value theme-fg-color2" }, attribute.value),
|
||||
* dom.span({ className: "attr-value" }, attribute.value),
|
||||
* '"'
|
||||
* )
|
||||
*/
|
||||
|
@ -720,16 +721,14 @@ ElementEditor.prototype = {
|
|||
attr.appendChild(inner);
|
||||
|
||||
const name = this.doc.createElement("span");
|
||||
name.classList.add("attr-name");
|
||||
name.classList.add("theme-fg-color1");
|
||||
name.classList.add("attr-name", "force-color-on-flash");
|
||||
name.textContent = attribute.name;
|
||||
inner.appendChild(name);
|
||||
|
||||
inner.appendChild(this.doc.createTextNode('="'));
|
||||
|
||||
const val = this.doc.createElement("span");
|
||||
val.classList.add("attr-value");
|
||||
val.classList.add("theme-fg-color2");
|
||||
val.classList.add("attr-value", "force-color-on-flash");
|
||||
inner.appendChild(val);
|
||||
|
||||
inner.appendChild(this.doc.createTextNode('"'));
|
||||
|
|
|
@ -15,7 +15,7 @@ function ReadOnlyEditor(container, node) {
|
|||
this.buildMarkup();
|
||||
|
||||
if (node.isPseudoElement) {
|
||||
this.tag.classList.add("theme-fg-color3");
|
||||
this.tag.classList.add("pseudo", "force-color-on-flash");
|
||||
if (node.isMarkerPseudoElement) {
|
||||
this.tag.textContent = "::marker";
|
||||
} else if (node.isBeforePseudoElement) {
|
||||
|
|
|
@ -56,6 +56,7 @@ class Rule {
|
|||
this.pseudoElement = options.pseudoElement || "";
|
||||
this.isSystem = options.isSystem;
|
||||
this.isUnmatched = options.isUnmatched || false;
|
||||
this.darkColorScheme = options.darkColorScheme;
|
||||
this.inherited = options.inherited || null;
|
||||
this.keyframes = options.keyframes || null;
|
||||
this.userAdded = options.rule.userAdded;
|
||||
|
@ -637,6 +638,8 @@ class Rule {
|
|||
*/
|
||||
refresh(options) {
|
||||
this.matchedSelectorIndexes = options.matchedSelectorIndexes || [];
|
||||
const colorSchemeChanged = this.darkColorScheme !== options.darkColorScheme;
|
||||
this.darkColorScheme = options.darkColorScheme;
|
||||
|
||||
const newTextProps = this._getTextProperties();
|
||||
|
||||
|
@ -679,6 +682,17 @@ class Rule {
|
|||
} else {
|
||||
delete prop._visited;
|
||||
}
|
||||
|
||||
// Valid properties that aren't disabled might need to get updated in some condition
|
||||
if (
|
||||
prop.enabled &&
|
||||
prop.isValid() &&
|
||||
// Update if it's using light-dark and the color scheme changed
|
||||
colorSchemeChanged &&
|
||||
prop.value.includes("light-dark")
|
||||
) {
|
||||
prop.updateEditor();
|
||||
}
|
||||
}
|
||||
|
||||
// Add brand new properties.
|
||||
|
|
|
@ -193,6 +193,8 @@ skip-if = [
|
|||
|
||||
["browser_rules_layer.js"]
|
||||
|
||||
["browser_rules_light_dark.js"]
|
||||
|
||||
["browser_rules_lineNumbers.js"]
|
||||
|
||||
["browser_rules_linear-easing-swatch.js"]
|
||||
|
|
157
devtools/client/inspector/rules/test/browser_rules_light_dark.js
Normal file
157
devtools/client/inspector/rules/test/browser_rules_light_dark.js
Normal file
|
@ -0,0 +1,157 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test for light-dark() in rule view.
|
||||
|
||||
const TEST_URI = `data:text/html,<meta charset=utf8>
|
||||
<style>
|
||||
@media not (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: light;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
background-color: light-dark(gold, tomato);
|
||||
}
|
||||
|
||||
.light {
|
||||
color-scheme: light;
|
||||
color: light-dark(red, blue);
|
||||
border-color: light-dark(pink, cyan);
|
||||
}
|
||||
|
||||
.dark {
|
||||
color-scheme: dark;
|
||||
background: linear-gradient(
|
||||
light-dark(blue, darkblue),
|
||||
light-dark(red, darkred)
|
||||
);
|
||||
}
|
||||
</style>
|
||||
<h1>Hello</h1>
|
||||
<div class="light">
|
||||
<pre>color-scheme: light</pre>
|
||||
<div class="dark">
|
||||
<pre>color-scheme: dark</pre>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
add_task(async function () {
|
||||
await addTab(TEST_URI);
|
||||
const { inspector, view } = await openRuleView();
|
||||
|
||||
info("Select node with color-scheme: light");
|
||||
await selectNode(".light", inspector);
|
||||
|
||||
// `color: light-dark(red, blue)`
|
||||
assertColorSpans(view, ".light", "color", [
|
||||
{ color: "red", matched: true },
|
||||
{ color: "blue", matched: false },
|
||||
]);
|
||||
|
||||
// `border-color: light-dark(pink, cyan)`
|
||||
assertColorSpans(view, ".light", "border-color", [
|
||||
{ color: "pink", matched: true },
|
||||
{ color: "cyan", matched: false },
|
||||
]);
|
||||
|
||||
info("Select node with color-scheme: dark");
|
||||
await selectNode(".dark", inspector);
|
||||
|
||||
// `background: linear-gradient(light-dark(blue, darkblue),light-dark(red, darkred))`
|
||||
assertColorSpans(view, ".dark", "background", [
|
||||
{ color: "blue", matched: false },
|
||||
{ color: "darkblue", matched: true },
|
||||
{ color: "red", matched: false },
|
||||
{ color: "darkred", matched: true },
|
||||
]);
|
||||
|
||||
// We show the inherited rule for color, which is matched by `.light`, so here, even if
|
||||
// the node has a dark color scheme, light-dark should mark second parameters as unmatched
|
||||
// `color: light-dark(red, blue)`
|
||||
assertColorSpans(view, ".light", "color", [
|
||||
{ color: "red", matched: true },
|
||||
{ color: "blue", matched: false },
|
||||
]);
|
||||
|
||||
info("Select node without explicit color-scheme property");
|
||||
// h1 has no color-scheme so it inherits from the html one, which depends on the actual
|
||||
// media query value. light-dark in the associated rule should be updated when toggling
|
||||
// simulation.
|
||||
await selectNode("h1", inspector);
|
||||
|
||||
// Read the color scheme to know if we should click on the light or dark button
|
||||
// to trigger a change.
|
||||
info("Enable light mode simulation if needed");
|
||||
const isDarkScheme = await SpecialPowers.spawn(
|
||||
gBrowser.selectedBrowser,
|
||||
[],
|
||||
() => {
|
||||
return content.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
}
|
||||
);
|
||||
if (isDarkScheme) {
|
||||
const onRuleViewRefreshed = view.once("ruleview-refreshed");
|
||||
inspector.panelDoc
|
||||
.querySelector("#color-scheme-simulation-light-toggle")
|
||||
.click();
|
||||
await onRuleViewRefreshed;
|
||||
}
|
||||
|
||||
// `background-color: light-dark(gold, tomato)`
|
||||
assertColorSpans(view, "h1", "background-color", [
|
||||
{ color: "gold", matched: true },
|
||||
{ color: "tomato", matched: false },
|
||||
]);
|
||||
|
||||
info("Trigger dark mode simulation");
|
||||
const onRuleViewRefreshed = view.once("ruleview-refreshed");
|
||||
inspector.panelDoc
|
||||
.querySelector("#color-scheme-simulation-dark-toggle")
|
||||
.click();
|
||||
await onRuleViewRefreshed;
|
||||
|
||||
// `background-color: light-dark(gold, tomato)`
|
||||
assertColorSpans(view, "h1", "background-color", [
|
||||
{ color: "gold", matched: false },
|
||||
{ color: "tomato", matched: true },
|
||||
]);
|
||||
});
|
||||
|
||||
function assertColorSpans(view, ruleSelector, propertyName, expectedColors) {
|
||||
const colorSpans = getRuleViewProperty(
|
||||
view,
|
||||
ruleSelector,
|
||||
propertyName
|
||||
).valueSpan.querySelectorAll("span:has(> .ruleview-color)");
|
||||
is(
|
||||
colorSpans.length,
|
||||
expectedColors.length,
|
||||
"Got expected number of color spans"
|
||||
);
|
||||
for (let i = 0; i < expectedColors.length; i++) {
|
||||
const colorSpan = colorSpans[i];
|
||||
const expectedColorData = expectedColors[i];
|
||||
is(
|
||||
colorSpan.textContent,
|
||||
expectedColorData.color,
|
||||
`Expected color ${i} for ${ruleSelector} ${propertyName}`
|
||||
);
|
||||
is(
|
||||
!colorSpan.classList.contains("ruleview-unmatched"),
|
||||
expectedColorData.matched,
|
||||
`Color ${i} for ${ruleSelector} ${propertyName} is ${
|
||||
expectedColorData.matched ? "matched" : "unmatched"
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
|
@ -614,6 +614,10 @@ TextPropertyEditor.prototype = {
|
|||
),
|
||||
inStartingStyleRule: this.rule.isInStartingStyle(),
|
||||
};
|
||||
|
||||
if (this.rule.darkColorScheme !== undefined) {
|
||||
parserOptions.isDarkColorScheme = this.rule.darkColorScheme;
|
||||
}
|
||||
const frag = outputParser.parseCssProperty(name, val, parserOptions);
|
||||
|
||||
// Save the initial value as the last committed value,
|
||||
|
|
|
@ -96,6 +96,8 @@ function isKeyIn(key, ...keys) {
|
|||
* @param {Object} options: Options for the editable field
|
||||
* @param {Element} options.element:
|
||||
* (required) The span to be edited on focus.
|
||||
* @param {String} options.inputClass:
|
||||
* An optional class to be added to the input.
|
||||
* @param {Function} options.canEdit:
|
||||
* Will be called before creating the inplace editor. Editor
|
||||
* won't be created if canEdit returns false.
|
||||
|
@ -246,6 +248,8 @@ function editableItem(options, callback) {
|
|||
// Mark the element editable field for tab
|
||||
// navigation while editing.
|
||||
element._editable = true;
|
||||
// And an attribute that can be used to target
|
||||
element.setAttribute("editable", "");
|
||||
|
||||
// Save the trigger type so we can dispatch this later
|
||||
element._trigger = trigger;
|
||||
|
@ -423,6 +427,8 @@ class InplaceEditor extends EventEmitter {
|
|||
* Optional aria-label attribute value that will be added to the input.
|
||||
* @param {String} options.inputAriaLabelledBy
|
||||
* Optional aria-labelledby attribute value that will be added to the input.
|
||||
* @param {String} options.inputClass:
|
||||
* Optional class to be added to the input.
|
||||
*/
|
||||
#createInput(options = {}) {
|
||||
this.input = this.doc.createElementNS(
|
||||
|
@ -440,6 +446,9 @@ class InplaceEditor extends EventEmitter {
|
|||
}
|
||||
|
||||
this.input.classList.add("styleinspector-propertyeditor");
|
||||
if (options.inputClass) {
|
||||
this.input.classList.add(options.inputClass);
|
||||
}
|
||||
this.input.value = this.initial;
|
||||
if (options.inputAriaLabel) {
|
||||
this.input.setAttribute("aria-label", options.inputAriaLabel);
|
||||
|
|
|
@ -124,6 +124,7 @@ class OutputParser {
|
|||
#cssProperties;
|
||||
#doc;
|
||||
#parsed = [];
|
||||
#stack = [];
|
||||
|
||||
/**
|
||||
* Parse a CSS property value given a property name.
|
||||
|
@ -221,6 +222,7 @@ class OutputParser {
|
|||
} else if (token.tokenType === "ParenthesisBlock") {
|
||||
++depth;
|
||||
} else if (token.tokenType === "CloseParenthesis") {
|
||||
this.#onCloseParenthesis(options);
|
||||
--depth;
|
||||
if (depth === 0) {
|
||||
break;
|
||||
|
@ -376,9 +378,12 @@ class OutputParser {
|
|||
const subOptions = Object.assign({}, options);
|
||||
subOptions.expectFilter = false;
|
||||
const saveParsed = this.#parsed;
|
||||
const savedStack = this.#stack;
|
||||
this.#parsed = [];
|
||||
this.#stack = [];
|
||||
const rest = this.#doParse(text, subOptions, tokenStream, true);
|
||||
this.#parsed = saveParsed;
|
||||
this.#stack = savedStack;
|
||||
|
||||
const span = this.#createNode("span", secondOpts);
|
||||
span.appendChild(rest);
|
||||
|
@ -412,18 +417,15 @@ class OutputParser {
|
|||
*/
|
||||
// eslint-disable-next-line complexity
|
||||
#doParse(text, options, tokenStream, stopAtCloseParen) {
|
||||
let parenDepth = stopAtCloseParen ? 1 : 0;
|
||||
let outerMostFunctionTakesColor = false;
|
||||
const colorFunctions = [];
|
||||
let fontFamilyNameParts = [];
|
||||
let previousWasBang = false;
|
||||
|
||||
const colorOK = function () {
|
||||
const colorOK = () => {
|
||||
return (
|
||||
options.supportsColor ||
|
||||
(options.expectFilter &&
|
||||
parenDepth === 1 &&
|
||||
outerMostFunctionTakesColor)
|
||||
this.#stack.length !== 0 &&
|
||||
this.#stack.at(-1).isColorTakingFunction)
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -455,6 +457,18 @@ class OutputParser {
|
|||
const isColorTakingFunction = COLOR_TAKING_FUNCTIONS.has(
|
||||
lowerCaseFunctionName
|
||||
);
|
||||
|
||||
this.#stack.push({
|
||||
lowerCaseFunctionName,
|
||||
functionName,
|
||||
isColorTakingFunction,
|
||||
// The position of the function separators ("," or "/") in the `parts` property
|
||||
separatorIndexes: [],
|
||||
// The parsed parts of the function that will be rendered on screen.
|
||||
// This can hold both simple strings and DOMNodes.
|
||||
parts: [],
|
||||
});
|
||||
|
||||
if (
|
||||
isColorTakingFunction ||
|
||||
ANGLE_TAKING_FUNCTIONS.has(lowerCaseFunctionName)
|
||||
|
@ -466,13 +480,6 @@ class OutputParser {
|
|||
this.#appendTextNode(
|
||||
text.substring(token.startOffset, token.endOffset)
|
||||
);
|
||||
if (parenDepth === 0) {
|
||||
outerMostFunctionTakesColor = isColorTakingFunction;
|
||||
}
|
||||
if (isColorTakingFunction) {
|
||||
colorFunctions.push({ parenDepth, functionName });
|
||||
}
|
||||
++parenDepth;
|
||||
} else if (
|
||||
lowerCaseFunctionName === "var" &&
|
||||
options.getVariableData
|
||||
|
@ -483,14 +490,18 @@ class OutputParser {
|
|||
tokenStream,
|
||||
options
|
||||
);
|
||||
|
||||
if (value && colorOK() && InspectorUtils.isValidCSSColor(value)) {
|
||||
const colorFunctionEntry = this.#stack.findLast(
|
||||
entry => entry.isColorTakingFunction
|
||||
);
|
||||
this.#appendColor(value, {
|
||||
...options,
|
||||
variableContainer: variableNode,
|
||||
colorFunction: colorFunctions.at(-1)?.functionName,
|
||||
colorFunction: colorFunctionEntry?.functionName,
|
||||
});
|
||||
} else {
|
||||
this.#parsed.push(variableNode);
|
||||
this.#append(variableNode);
|
||||
}
|
||||
} else {
|
||||
const {
|
||||
|
@ -517,9 +528,13 @@ class OutputParser {
|
|||
colorOK() &&
|
||||
InspectorUtils.isValidCSSColor(computedFunctionText)
|
||||
) {
|
||||
const colorFunctionEntry = this.#stack.findLast(
|
||||
entry => entry.isColorTakingFunction
|
||||
);
|
||||
|
||||
this.#appendColor(computedFunctionText, {
|
||||
...options,
|
||||
colorFunction: colorFunctions.at(-1)?.functionName,
|
||||
colorFunction: colorFunctionEntry?.functionName,
|
||||
valueParts: [
|
||||
functionName,
|
||||
"(",
|
||||
|
@ -535,7 +550,7 @@ class OutputParser {
|
|||
if (typeof data === "string") {
|
||||
this.#appendTextNode(data);
|
||||
} else if (data) {
|
||||
this.#parsed.push(data.node);
|
||||
this.#append(data.node);
|
||||
}
|
||||
}
|
||||
this.#appendTextNode(")");
|
||||
|
@ -583,9 +598,12 @@ class OutputParser {
|
|||
colorOK() &&
|
||||
InspectorUtils.isValidCSSColor(functionText)
|
||||
) {
|
||||
const colorFunctionEntry = this.#stack.findLast(
|
||||
entry => entry.isColorTakingFunction
|
||||
);
|
||||
this.#appendColor(functionText, {
|
||||
...options,
|
||||
colorFunction: colorFunctions.at(-1)?.functionName,
|
||||
colorFunction: colorFunctionEntry?.functionName,
|
||||
});
|
||||
} else if (
|
||||
options.expectShape &&
|
||||
|
@ -622,9 +640,12 @@ class OutputParser {
|
|||
options.gridClass
|
||||
);
|
||||
} else if (colorOK() && InspectorUtils.isValidCSSColor(token.text)) {
|
||||
const colorFunctionEntry = this.#stack.findLast(
|
||||
entry => entry.isColorTakingFunction
|
||||
);
|
||||
this.#appendColor(token.text, {
|
||||
...options,
|
||||
colorFunction: colorFunctions.at(-1)?.functionName,
|
||||
colorFunction: colorFunctionEntry?.functionName,
|
||||
});
|
||||
} else if (angleOK(token.text)) {
|
||||
this.#appendAngle(token.text, options);
|
||||
|
@ -649,9 +670,12 @@ class OutputParser {
|
|||
// color is changed to something like rgb(...).
|
||||
this.#appendTextNode(" ");
|
||||
}
|
||||
const colorFunctionEntry = this.#stack.findLast(
|
||||
entry => entry.isColorTakingFunction
|
||||
);
|
||||
this.#appendColor(original, {
|
||||
...options,
|
||||
colorFunction: colorFunctions.at(-1)?.functionName,
|
||||
colorFunction: colorFunctionEntry?.functionName,
|
||||
});
|
||||
} else {
|
||||
this.#appendTextNode(original);
|
||||
|
@ -698,27 +722,23 @@ class OutputParser {
|
|||
break;
|
||||
|
||||
case "ParenthesisBlock":
|
||||
++parenDepth;
|
||||
this.#stack.push({
|
||||
isParenthesis: true,
|
||||
separatorIndexes: [],
|
||||
});
|
||||
this.#appendTextNode(
|
||||
text.substring(token.startOffset, token.endOffset)
|
||||
);
|
||||
break;
|
||||
|
||||
case "CloseParenthesis":
|
||||
--parenDepth;
|
||||
this.#onCloseParenthesis(options);
|
||||
|
||||
if (colorFunctions.at(-1)?.parenDepth == parenDepth) {
|
||||
colorFunctions.pop();
|
||||
}
|
||||
|
||||
if (stopAtCloseParen && parenDepth === 0) {
|
||||
if (stopAtCloseParen && this.#stack.length === 0) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (parenDepth === 0) {
|
||||
outerMostFunctionTakesColor = false;
|
||||
}
|
||||
this.#appendTextNode(
|
||||
text.substring(token.startOffset, token.endOffset)
|
||||
);
|
||||
|
@ -735,6 +755,14 @@ class OutputParser {
|
|||
fontFamilyNameParts = [];
|
||||
}
|
||||
|
||||
// Add separator for the current function
|
||||
if (this.#stack.length) {
|
||||
this.#appendTextNode(token.text);
|
||||
const entry = this.#stack.at(-1);
|
||||
entry.separatorIndexes.push(entry.parts.length - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
// falls through
|
||||
default:
|
||||
this.#appendTextNode(
|
||||
|
@ -761,6 +789,15 @@ class OutputParser {
|
|||
this.#appendFontFamily(fontFamilyNameParts.join(""), options);
|
||||
}
|
||||
|
||||
// We might never encounter a matching closing parenthesis for a function and still
|
||||
// have a "valid" value (e.g. `background: linear-gradient(90deg, red, blue"`)
|
||||
// In such case, go through the stack and handle each items until we have nothing left.
|
||||
if (this.#stack.length) {
|
||||
while (this.#stack.length !== 0) {
|
||||
this.#onCloseParenthesis(options);
|
||||
}
|
||||
}
|
||||
|
||||
let result = this.#toDOM();
|
||||
|
||||
if (options.expectFilter && !options.filterSwatch) {
|
||||
|
@ -770,6 +807,111 @@ class OutputParser {
|
|||
return result;
|
||||
}
|
||||
|
||||
#onCloseParenthesis(options) {
|
||||
if (!this.#stack.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stackEntry = this.#stack.at(-1);
|
||||
if (
|
||||
stackEntry.lowerCaseFunctionName === "light-dark" &&
|
||||
typeof options.isDarkColorScheme === "boolean" &&
|
||||
// light-dark takes exactly two parameters, so if we don't get exactly 1 separator
|
||||
// at this point, that means that the value is valid at parse time, but is invalid
|
||||
// at computed value time.
|
||||
// TODO: We might want to add a class to indicate that this is invalid at computed
|
||||
// value time (See Bug 1910845)
|
||||
stackEntry.separatorIndexes.length === 1
|
||||
) {
|
||||
const stackEntryParts = this.#getCurrentStackParts();
|
||||
const separatorIndex = stackEntry.separatorIndexes[0];
|
||||
let startIndex;
|
||||
let endIndex;
|
||||
if (options.isDarkColorScheme) {
|
||||
// If we're using a dark color scheme, we want to mark the first param as
|
||||
// not used.
|
||||
|
||||
// The first "part" is `light-dark(`, so we can start after that.
|
||||
// We want to filter out white space character before the first parameter
|
||||
for (startIndex = 1; startIndex < separatorIndex; startIndex++) {
|
||||
const part = stackEntryParts[startIndex];
|
||||
if (typeof part !== "string" || part.trim() !== "") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// same for the end of the parameter, we want to filter out whitespaces
|
||||
// after the parameter and before the comma
|
||||
for (
|
||||
endIndex = separatorIndex - 1;
|
||||
endIndex >= startIndex;
|
||||
endIndex--
|
||||
) {
|
||||
const part = stackEntryParts[endIndex];
|
||||
if (typeof part !== "string" || part.trim() !== "") {
|
||||
// We found a non-whitespace part, we need to include it, so increment the endIndex
|
||||
endIndex++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If we're not using a dark color scheme, we want to mark the second param as
|
||||
// not used.
|
||||
|
||||
// We want to filter out white space character after the comma and before the
|
||||
// second parameter
|
||||
for (
|
||||
startIndex = separatorIndex + 1;
|
||||
startIndex < stackEntryParts.length;
|
||||
startIndex++
|
||||
) {
|
||||
const part = stackEntryParts[startIndex];
|
||||
if (typeof part !== "string" || part.trim() !== "") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// same for the end of the parameter, we want to filter out whitespaces
|
||||
// after the parameter and before the closing parenthesis (which is not yet
|
||||
// included in stackEntryParts)
|
||||
for (
|
||||
endIndex = stackEntryParts.length - 1;
|
||||
endIndex > separatorIndex;
|
||||
endIndex--
|
||||
) {
|
||||
const part = stackEntryParts[endIndex];
|
||||
if (typeof part !== "string" || part.trim() !== "") {
|
||||
// We found a non-whitespace part, we need to include it, so increment the endIndex
|
||||
endIndex++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const parts = stackEntryParts.slice(startIndex, endIndex);
|
||||
|
||||
// If the item we need to mark is already an element (e.g. a parsed color),
|
||||
// just add a class to it.
|
||||
if (parts.length === 1 && Element.isInstance(parts[0])) {
|
||||
parts[0].classList.add(options.unmatchedClass);
|
||||
} else {
|
||||
// Otherwise, we need to wrap our parts into a specific element so we can
|
||||
// style them
|
||||
const node = this.#createNode("span", {
|
||||
class: options.unmatchedClass,
|
||||
});
|
||||
node.append(...parts);
|
||||
stackEntryParts.splice(startIndex, parts.length, node);
|
||||
}
|
||||
}
|
||||
|
||||
// Our job is done here, pop last stack entry
|
||||
const { parts } = this.#stack.pop();
|
||||
// Put all the parts in the "new" last stack, or the main parsed array if there
|
||||
// is no more entry in the stack
|
||||
this.#getCurrentStackParts().push(...parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string.
|
||||
*
|
||||
|
@ -784,6 +926,7 @@ class OutputParser {
|
|||
#parse(text, options = {}) {
|
||||
text = text.trim();
|
||||
this.#parsed.length = 0;
|
||||
this.#stack.length = 0;
|
||||
|
||||
const tokenStream = new InspectorCSSParserWrapper(text);
|
||||
return this.#doParse(text, options, tokenStream, false);
|
||||
|
@ -855,7 +998,7 @@ class OutputParser {
|
|||
);
|
||||
|
||||
container.appendChild(value);
|
||||
this.#parsed.push(container);
|
||||
this.#append(container);
|
||||
}
|
||||
|
||||
#appendLinear(text, options) {
|
||||
|
@ -882,7 +1025,7 @@ class OutputParser {
|
|||
);
|
||||
|
||||
container.appendChild(value);
|
||||
this.#parsed.push(container);
|
||||
this.#append(container);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -907,7 +1050,7 @@ class OutputParser {
|
|||
|
||||
const value = this.#createNode("span", {}, text);
|
||||
container.append(value);
|
||||
this.#parsed.push(container);
|
||||
this.#append(container);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -967,7 +1110,7 @@ class OutputParser {
|
|||
}
|
||||
}
|
||||
|
||||
this.#parsed.push(container);
|
||||
this.#append(container);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1642,7 +1785,7 @@ class OutputParser {
|
|||
);
|
||||
|
||||
container.appendChild(value);
|
||||
this.#parsed.push(container);
|
||||
this.#append(container);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1745,7 +1888,7 @@ class OutputParser {
|
|||
container.appendChild(value);
|
||||
}
|
||||
|
||||
this.#parsed.push(container);
|
||||
this.#append(container);
|
||||
} else {
|
||||
this.#appendTextNode(color);
|
||||
}
|
||||
|
@ -1980,7 +2123,7 @@ class OutputParser {
|
|||
}
|
||||
|
||||
/**
|
||||
* Append a node to the output.
|
||||
* Create and append a node to the output.
|
||||
*
|
||||
* @param {String} tagName
|
||||
* Tag type e.g. "div"
|
||||
|
@ -1996,7 +2139,16 @@ class OutputParser {
|
|||
node.classList.add(TRUNCATE_NODE_CLASSNAME);
|
||||
}
|
||||
|
||||
this.#parsed.push(node);
|
||||
this.#append(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append an element or a text node to the output.
|
||||
*
|
||||
* @param {DOMNode|String} item
|
||||
*/
|
||||
#append(item) {
|
||||
this.#getCurrentStackParts().push(item);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2007,18 +2159,19 @@ class OutputParser {
|
|||
* Text to append
|
||||
*/
|
||||
#appendTextNode(text) {
|
||||
const lastItem = this.#parsed[this.#parsed.length - 1];
|
||||
if (text.length > TRUNCATE_LENGTH_THRESHOLD) {
|
||||
// If the text is too long, force creating a node, which will add the
|
||||
// necessary classname to truncate the property correctly.
|
||||
this.#appendNode("span", {}, text);
|
||||
} else if (typeof lastItem === "string") {
|
||||
this.#parsed[this.#parsed.length - 1] = lastItem + text;
|
||||
} else {
|
||||
this.#parsed.push(text);
|
||||
this.#append(text);
|
||||
}
|
||||
}
|
||||
|
||||
#getCurrentStackParts() {
|
||||
return this.#stack.at(-1)?.parts || this.#parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take all output and append it into a single DocumentFragment.
|
||||
*
|
||||
|
@ -2037,6 +2190,7 @@ class OutputParser {
|
|||
}
|
||||
|
||||
this.#parsed.length = 0;
|
||||
this.#stack.length = 0;
|
||||
return frag;
|
||||
}
|
||||
|
||||
|
@ -2077,6 +2231,7 @@ class OutputParser {
|
|||
* - {RegisteredPropertyResource|undefined} registeredProperty: The registered
|
||||
* property data (syntax, initial value, inherits). Undefined if the variable
|
||||
* is not a registered property.
|
||||
* @param {Boolean} overrides.isDarkColorScheme: Is the currently applied color scheme dark.
|
||||
* @return {Object} Overridden options object
|
||||
*/
|
||||
#mergeOptions(overrides) {
|
||||
|
@ -2101,6 +2256,7 @@ class OutputParser {
|
|||
getVariableData: null,
|
||||
unmatchedClass: null,
|
||||
inStartingStyleRule: false,
|
||||
isDarkColorScheme: null,
|
||||
};
|
||||
|
||||
for (const item in overrides) {
|
||||
|
|
|
@ -18,7 +18,7 @@ add_task(async function () {
|
|||
await testAdvanceCharCommit(doc);
|
||||
await testAdvanceCharsFunction(doc);
|
||||
await testEscapeCancel(doc);
|
||||
await testInputAriaLabel(doc);
|
||||
await testInputAttributes(doc);
|
||||
|
||||
host.destroy();
|
||||
gBrowser.removeCurrentTab();
|
||||
|
@ -155,7 +155,7 @@ function testEscapeCancel(doc) {
|
|||
});
|
||||
}
|
||||
|
||||
function testInputAriaLabel(doc) {
|
||||
function testInputAttributes(doc) {
|
||||
info("Testing that inputAriaLabel works as expected");
|
||||
doc.body.innerHTML = "";
|
||||
|
||||
|
@ -190,6 +190,22 @@ function testInputAriaLabel(doc) {
|
|||
"TEST_ARIA_LABELLED_BY",
|
||||
"Input has expected aria-labelledby"
|
||||
);
|
||||
|
||||
info("Testing that inputClass works as expected");
|
||||
doc.body.innerHTML = "";
|
||||
element = createSpan(doc);
|
||||
editableField({
|
||||
element,
|
||||
inputClass: "TEST_INPUT_CLASS",
|
||||
});
|
||||
|
||||
info("Clicking on the inplace-editor field to turn to edit mode");
|
||||
element.click();
|
||||
input = doc.querySelector("input");
|
||||
ok(
|
||||
input.classList.contains("TEST_INPUT_CLASS"),
|
||||
"Input has expected TEST_INPUT_CLASS class"
|
||||
);
|
||||
}
|
||||
|
||||
function onDone(value, isCommit, resolve) {
|
||||
|
|
|
@ -36,6 +36,7 @@ async function performTest() {
|
|||
testParseVariable(doc, parser);
|
||||
testParseColorVariable(doc, parser);
|
||||
testParseFontFamily(doc, parser);
|
||||
testParseLightDark(doc, parser);
|
||||
|
||||
host.destroy();
|
||||
}
|
||||
|
@ -1051,3 +1052,367 @@ function testParseFontFamily(doc, parser) {
|
|||
"Got expected output for font-family with custom properties"
|
||||
);
|
||||
}
|
||||
|
||||
function testParseLightDark(doc, parser) {
|
||||
const TESTS = [
|
||||
{
|
||||
message:
|
||||
"Not passing isDarkColorScheme doesn't add unmatched classes to parameters",
|
||||
propertyName: "color",
|
||||
propertyValue: "light-dark(red, blue)",
|
||||
expected:
|
||||
// prettier-ignore
|
||||
`light-dark(` +
|
||||
`<span data-color="red">` +
|
||||
`<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>red</span>` +
|
||||
`</span>, ` +
|
||||
`<span data-color="blue">` +
|
||||
`<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>blue</span>` +
|
||||
`</span>` +
|
||||
`)`,
|
||||
},
|
||||
{
|
||||
message: "in light mode, the second parameter gets the unmatched class",
|
||||
propertyName: "color",
|
||||
propertyValue: "light-dark(red, blue)",
|
||||
isDarkColorScheme: false,
|
||||
expected:
|
||||
// prettier-ignore
|
||||
`light-dark(` +
|
||||
`<span data-color="red">` +
|
||||
`<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>red</span>` +
|
||||
`</span>, ` +
|
||||
`<span data-color="blue" class="unmatched-class">` +
|
||||
`<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>blue</span>` +
|
||||
`</span>` +
|
||||
`)`,
|
||||
},
|
||||
{
|
||||
message: "in dark mode, the first parameter gets the unmatched class",
|
||||
propertyName: "color",
|
||||
propertyValue: "light-dark(red, blue)",
|
||||
isDarkColorScheme: true,
|
||||
expected:
|
||||
// prettier-ignore
|
||||
`light-dark(` +
|
||||
`<span data-color="red" class="unmatched-class">` +
|
||||
`<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>red</span>` +
|
||||
`</span>, ` +
|
||||
`<span data-color="blue">` +
|
||||
`<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>blue</span>` +
|
||||
`</span>` +
|
||||
`)`,
|
||||
},
|
||||
{
|
||||
message: "light-dark gets parsed as expected in shorthands in light mode",
|
||||
propertyName: "border",
|
||||
propertyValue: "1px solid light-dark(red, blue)",
|
||||
isDarkColorScheme: false,
|
||||
expected:
|
||||
// prettier-ignore
|
||||
`1px solid light-dark(` +
|
||||
`<span data-color="red">` +
|
||||
`<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>red</span>` +
|
||||
`</span>, ` +
|
||||
`<span data-color="blue" class="unmatched-class">` +
|
||||
`<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>blue</span>` +
|
||||
`</span>` +
|
||||
`)`,
|
||||
},
|
||||
{
|
||||
message: "light-dark gets parsed as expected in shorthands in dark mode",
|
||||
propertyName: "border",
|
||||
propertyValue: "1px solid light-dark(red, blue)",
|
||||
isDarkColorScheme: true,
|
||||
expected:
|
||||
// prettier-ignore
|
||||
`1px solid light-dark(` +
|
||||
`<span data-color="red" class="unmatched-class">` +
|
||||
`<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>red</span>` +
|
||||
`</span>, ` +
|
||||
`<span data-color="blue">` +
|
||||
`<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>blue</span>` +
|
||||
`</span>` +
|
||||
`)`,
|
||||
},
|
||||
{
|
||||
message: "Nested light-dark gets parsed as expected in light mode",
|
||||
propertyName: "background",
|
||||
propertyValue:
|
||||
"linear-gradient(45deg, light-dark(red, blue), light-dark(pink, cyan))",
|
||||
isDarkColorScheme: false,
|
||||
expected:
|
||||
// prettier-ignore
|
||||
`linear-gradient(` +
|
||||
`<span data-angle="45deg"><span>45deg</span></span>, ` +
|
||||
`light-dark(` +
|
||||
`<span data-color="red">` +
|
||||
`<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>`+
|
||||
`<span>red</span>`+
|
||||
`</span>, `+
|
||||
`<span data-color="blue" class="unmatched-class">` +
|
||||
`<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>blue</span>` +
|
||||
`</span>` +
|
||||
`), ` +
|
||||
`light-dark(` +
|
||||
`<span data-color="pink">` +
|
||||
`<span class="test-class" style="background-color:pink" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>pink</span>` +
|
||||
`</span>, ` +
|
||||
`<span data-color="cyan" class="unmatched-class">` +
|
||||
`<span class="test-class" style="background-color:cyan" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>cyan</span>` +
|
||||
`</span>` +
|
||||
`)` +
|
||||
`)`,
|
||||
},
|
||||
{
|
||||
message: "Nested light-dark gets parsed as expected in dark mode",
|
||||
propertyName: "background",
|
||||
propertyValue:
|
||||
"linear-gradient(33deg, light-dark(red, blue), light-dark(pink, cyan))",
|
||||
isDarkColorScheme: true,
|
||||
expected:
|
||||
// prettier-ignore
|
||||
`linear-gradient(` +
|
||||
`<span data-angle="33deg"><span>33deg</span></span>, ` +
|
||||
`light-dark(` +
|
||||
`<span data-color="red" class="unmatched-class">` +
|
||||
`<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>`+
|
||||
`<span>red</span>`+
|
||||
`</span>, `+
|
||||
`<span data-color="blue">` +
|
||||
`<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>blue</span>` +
|
||||
`</span>` +
|
||||
`), ` +
|
||||
`light-dark(` +
|
||||
`<span data-color="pink" class="unmatched-class">` +
|
||||
`<span class="test-class" style="background-color:pink" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>pink</span>` +
|
||||
`</span>, ` +
|
||||
`<span data-color="cyan">` +
|
||||
`<span class="test-class" style="background-color:cyan" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>cyan</span>` +
|
||||
`</span>` +
|
||||
`)` +
|
||||
`)`,
|
||||
},
|
||||
{
|
||||
message:
|
||||
"in light mode, the second parameter gets the unmatched class when it's a variable",
|
||||
propertyName: "color",
|
||||
propertyValue: "light-dark(var(--x), var(--y))",
|
||||
isDarkColorScheme: false,
|
||||
variables: { "--x": "red", "--y": "blue" },
|
||||
expected:
|
||||
// prettier-ignore
|
||||
`light-dark(` +
|
||||
`<span data-color="red">` +
|
||||
`<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>var(` +
|
||||
`<span data-variable="--x = red">--x</span>` +
|
||||
`)</span>` +
|
||||
`</span>, ` +
|
||||
`<span data-color="blue" class="unmatched-class">` +
|
||||
`<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>var(` +
|
||||
`<span data-variable="--y = blue">--y</span>` +
|
||||
`)</span>` +
|
||||
`</span>` +
|
||||
`)`,
|
||||
},
|
||||
{
|
||||
message:
|
||||
"in light mode, the second parameter gets the unmatched class when some param are not parsed",
|
||||
propertyName: "color",
|
||||
// Using `notacolor` so we don't get a wrapping Node for it (contrary to colors).
|
||||
// The value is still valid at parse time since we're using a variable,
|
||||
// so the OutputParser will actually parse the different parts
|
||||
propertyValue: "light-dark(var(--x),notacolor)",
|
||||
isDarkColorScheme: false,
|
||||
variables: { "--x": "red" },
|
||||
expected:
|
||||
// prettier-ignore
|
||||
`light-dark(` +
|
||||
`<span data-color="red">` +
|
||||
`<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>` +
|
||||
`var(<span data-variable="--x = red">--x</span>)` +
|
||||
`</span>` +
|
||||
`</span>,` +
|
||||
`<span class="unmatched-class">notacolor</span>` +
|
||||
`)`,
|
||||
},
|
||||
{
|
||||
message:
|
||||
"in dark mode, the first parameter gets the unmatched class when some param are not parsed",
|
||||
propertyName: "color",
|
||||
// Using `notacolor` so we don't get a wrapping Node for it (contrary to colors).
|
||||
// The value is still valid at parse time since we're using a variable,
|
||||
// so the OutputParser will actually parse the different parts
|
||||
propertyValue: "light-dark(notacolor,var(--x))",
|
||||
isDarkColorScheme: true,
|
||||
variables: { "--x": "red" },
|
||||
expected:
|
||||
// prettier-ignore
|
||||
`light-dark(` +
|
||||
`<span class="unmatched-class">notacolor</span>,` +
|
||||
`<span data-color="red">` +
|
||||
`<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>` +
|
||||
`var(<span data-variable="--x = red">--x</span>)` +
|
||||
`</span>` +
|
||||
`</span>` +
|
||||
`)`,
|
||||
},
|
||||
{
|
||||
message:
|
||||
"in light mode, the second parameter gets the unmatched class, comments are stripped out and whitespace are preserved",
|
||||
propertyName: "color",
|
||||
propertyValue:
|
||||
"light-dark( /* 1st param */ var(--x) /* delim */ , /* 2nd param */ notacolor /* delim */ )",
|
||||
isDarkColorScheme: false,
|
||||
variables: { "--x": "red" },
|
||||
expected:
|
||||
// prettier-ignore
|
||||
`light-dark( ` +
|
||||
`<span data-color="red">` +
|
||||
`<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>` +
|
||||
`var(<span data-variable="--x = red">--x</span>)` +
|
||||
`</span>` +
|
||||
`</span> , ` +
|
||||
`<span class="unmatched-class">notacolor</span> ` +
|
||||
`)`,
|
||||
},
|
||||
{
|
||||
message:
|
||||
"in dark mode, the first parameter gets the unmatched class, comments are stripped out and whitespace are preserved",
|
||||
propertyName: "color",
|
||||
propertyValue:
|
||||
"light-dark( /* 1st param */ notacolor /* delim */ , /* 2nd param */ var(--x) /* delim */ )",
|
||||
isDarkColorScheme: true,
|
||||
variables: { "--x": "red" },
|
||||
expected:
|
||||
// prettier-ignore
|
||||
`light-dark( ` +
|
||||
`<span class="unmatched-class">notacolor</span> , ` +
|
||||
`<span data-color="red">` +
|
||||
`<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>` +
|
||||
`var(<span data-variable="--x = red">--x</span>)` +
|
||||
`</span>` +
|
||||
`</span> ` +
|
||||
`)`,
|
||||
},
|
||||
{
|
||||
message:
|
||||
"in light mode with a single parameter, we don't strike through any parameter (TODO wrap with IACVT - Bug 1910845)",
|
||||
propertyName: "color",
|
||||
propertyValue: "light-dark(var(--x))",
|
||||
isDarkColorScheme: false,
|
||||
variables: { "--x": "red" },
|
||||
expected:
|
||||
// prettier-ignore
|
||||
`light-dark(` +
|
||||
`<span data-color="red">` +
|
||||
`<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>` +
|
||||
`var(<span data-variable="--x = red">--x</span>)` +
|
||||
`</span>` +
|
||||
`</span>` +
|
||||
`)`,
|
||||
},
|
||||
{
|
||||
message:
|
||||
"in dark mode with a single parameter, we don't strike through any parameter (TODO wrap with IACVT - Bug 1910845)",
|
||||
propertyName: "color",
|
||||
propertyValue: "light-dark(var(--x))",
|
||||
isDarkColorScheme: true,
|
||||
variables: { "--x": "red" },
|
||||
expected:
|
||||
// prettier-ignore
|
||||
`light-dark(` +
|
||||
`<span data-color="red">` +
|
||||
`<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>` +
|
||||
`var(<span data-variable="--x = red">--x</span>)` +
|
||||
`</span>` +
|
||||
`</span>` +
|
||||
`)`,
|
||||
},
|
||||
{
|
||||
message:
|
||||
"in light mode with 3 parameters, we don't strike through any parameter (TODO wrap with IACVT - Bug 1910845)",
|
||||
propertyName: "color",
|
||||
propertyValue: "light-dark(var(--x),a,b)",
|
||||
isDarkColorScheme: false,
|
||||
variables: { "--x": "red" },
|
||||
expected:
|
||||
// prettier-ignore
|
||||
`light-dark(` +
|
||||
`<span data-color="red">` +
|
||||
`<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>` +
|
||||
`var(<span data-variable="--x = red">--x</span>)` +
|
||||
`</span>` +
|
||||
`</span>,a,b` +
|
||||
`)`,
|
||||
},
|
||||
{
|
||||
message:
|
||||
"in dark mode with 3 parameters, we don't strike through any parameter (TODO wrap with IACVT - Bug 1910845)",
|
||||
propertyName: "color",
|
||||
propertyValue: "light-dark(var(--x),a,b)",
|
||||
isDarkColorScheme: true,
|
||||
variables: { "--x": "red" },
|
||||
expected:
|
||||
// prettier-ignore
|
||||
`light-dark(` +
|
||||
`<span data-color="red">` +
|
||||
`<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
|
||||
`<span>` +
|
||||
`var(<span data-variable="--x = red">--x</span>)` +
|
||||
`</span>` +
|
||||
`</span>,a,b` +
|
||||
`)`,
|
||||
},
|
||||
];
|
||||
|
||||
for (const test of TESTS) {
|
||||
const frag = parser.parseCssProperty(
|
||||
test.propertyName,
|
||||
test.propertyValue,
|
||||
{
|
||||
isDarkColorScheme: test.isDarkColorScheme,
|
||||
unmatchedClass: "unmatched-class",
|
||||
colorSwatchClass: COLOR_TEST_CLASS,
|
||||
getVariableData: varName => {
|
||||
if (typeof test.variables[varName] === "string") {
|
||||
return { value: test.variables[varName] };
|
||||
}
|
||||
|
||||
return test.variables[varName] || {};
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const target = doc.querySelector("div");
|
||||
target.appendChild(frag);
|
||||
|
||||
is(target.innerHTML, test.expected, test.message);
|
||||
target.innerHTML = "";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
--markup-drag-line: var(--grey-40);
|
||||
--markup-drop-line: var(--blue-55);
|
||||
--markup-overflow-causing-background-color: rgba(128, 0, 215, 0.15);
|
||||
--markup-new-attr-size: 2ch;
|
||||
--markup-new-attr-focused-margin-inline: 1ch 2px;
|
||||
}
|
||||
|
||||
.theme-dark:root {
|
||||
|
@ -169,6 +171,17 @@ ul.children + .tag-line::before {
|
|||
cursor: default;
|
||||
}
|
||||
|
||||
/* Change selected line style when editing an attribute */
|
||||
.tag-line[selected]:has(.styleinspector-propertyeditor:focus) .theme-selected {
|
||||
background-color: var(--theme-body-alternate-emphasized-background);
|
||||
color: unset;
|
||||
|
||||
/* revert the icon to the color it has when the line doesn't have the "selection" background */
|
||||
& ~ .theme-twisty {
|
||||
fill: var(--theme-icon-dimmed-color);
|
||||
}
|
||||
}
|
||||
|
||||
.tag-line[selected] + .children,
|
||||
.tag-line[selected] ~ .tag-line {
|
||||
background-image: linear-gradient(to top, var(--markup-outline), var(--markup-outline));
|
||||
|
@ -334,11 +347,22 @@ ul.children + .tag-line::before {
|
|||
}
|
||||
|
||||
.newattr {
|
||||
margin-right: -13px;
|
||||
}
|
||||
/* offset the element so it doesn't take any space when not focused */
|
||||
margin-inline-start: calc(-1 * var(--markup-new-attr-size));
|
||||
|
||||
.newattr:before {
|
||||
content: "\00a0\00a0";
|
||||
&::before {
|
||||
content: "\00a0\00a0" / "";
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
margin-inline: var(--markup-new-attr-focused-margin-inline);
|
||||
background-image: url("chrome://devtools/skin/images/add.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-size: 90%;
|
||||
background-position: center;
|
||||
-moz-context-properties: fill;
|
||||
fill: var(--theme-icon-color);
|
||||
}
|
||||
}
|
||||
|
||||
.attr-value .link {
|
||||
|
@ -371,10 +395,6 @@ ul.children + .tag-line::before {
|
|||
}
|
||||
}
|
||||
|
||||
.newattr:focus {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.flash-out {
|
||||
transition: background,color .5s;
|
||||
}
|
||||
|
@ -408,8 +428,13 @@ ul.children + .tag-line::before {
|
|||
|
||||
.styleinspector-propertyeditor {
|
||||
vertical-align: top;
|
||||
margin: -1px 0;
|
||||
border: 1px solid #CCC;
|
||||
border: none;
|
||||
|
||||
&.newattr-input {
|
||||
margin-inline: var(--markup-new-attr-focused-margin-inline);
|
||||
/* Apply the same size as the new attr button so we don't shift the layout when dislaying the input */
|
||||
min-width: var(--markup-new-attr-size);
|
||||
}
|
||||
}
|
||||
|
||||
.reveal-link {
|
||||
|
@ -453,26 +478,89 @@ ul.children + .tag-line::before {
|
|||
color: var(--markup-hidden-tag-color);
|
||||
}
|
||||
|
||||
/* Selected nodes in the tree should have light selected text.
|
||||
@layer markup-base {
|
||||
.tag {
|
||||
color: var(--theme-highlight-blue);
|
||||
}
|
||||
.attr-name {
|
||||
color: var(--theme-highlight-red);
|
||||
}
|
||||
.attr-value {
|
||||
color: var(--theme-highlight-purple);
|
||||
}
|
||||
.pseudo {
|
||||
color: var(--theme-highlight-blue);
|
||||
}
|
||||
}
|
||||
|
||||
/* Make focusable elements stand out a bit more when they're focused */
|
||||
.tag-line :is(
|
||||
[editable],
|
||||
/* tag is not always editable (e.g. for `html`, `DOCTYPE`, …), but is always focusable */
|
||||
.tag
|
||||
):focus-visible {
|
||||
border-radius: 2px;
|
||||
outline: var(--theme-focus-outline);
|
||||
outline-offset: var(--theme-outline-offset);
|
||||
}
|
||||
|
||||
/* Selected nodes in the tree should have light selected text (unless there's an active
|
||||
inplace editor input).
|
||||
theme-selected doesn't work in this case since the text is a
|
||||
sibling of the class, not a child. */
|
||||
.theme-selected ~ .editor,
|
||||
.theme-selected ~ .editor.comment,
|
||||
.theme-selected ~ .editor .tag,
|
||||
.theme-selected ~ .editor .theme-fg-color1,
|
||||
.theme-selected ~ .editor .theme-fg-color2,
|
||||
.theme-selected ~ .editor .theme-fg-color3 {
|
||||
.tag-line[selected]:not(:has(.styleinspector-propertyeditor:focus)) :is(
|
||||
.editor,
|
||||
.editor.comment,
|
||||
.editor :is(.attr-name, .attr-value, .tag, .pseudo)
|
||||
) {
|
||||
color: var(--theme-selection-color);
|
||||
|
||||
& :is([editable], .tag):focus-visible {
|
||||
/* When an editable item in the markup view is focused, we want to make it stand out
|
||||
Since the node is selected, we have a blue background on the whole node, so we set
|
||||
the default body background, and set the original color we have when the line is
|
||||
not selected.
|
||||
This is done by using revert-layer, so we'll get colors set in <@layer markup-base>,
|
||||
and not the one set in this rule's parent rule.
|
||||
*/
|
||||
color: revert-layer;
|
||||
background-color: var(--theme-body-background);
|
||||
|
||||
outline-offset: 2px;
|
||||
|
||||
/* Better style for attributes spanning multiple lines */
|
||||
box-decoration-break: clone;
|
||||
mix-blend-mode: lighten;
|
||||
.theme-dark & {
|
||||
mix-blend-mode: darken;
|
||||
}
|
||||
|
||||
/* We also need to take care of items that don't have their color set in <@layer markup-base> */
|
||||
&:not(.attr-name, .attr-value, .tag, .pseudo) {
|
||||
color: var(--theme-body-color);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color: var(--theme-text-color-strong);
|
||||
}
|
||||
}
|
||||
|
||||
& * {
|
||||
color: revert-layer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Selected nodes being flashed in the tree should have dark selected text. Here we target
|
||||
nodes with theme-selected text colors and apply a dark, accessible text color for when
|
||||
the yellow flashing background is applied. */
|
||||
.theme-selected.theme-bg-contrast ~ .editor,
|
||||
.theme-selected ~ .editor .theme-fg-contrast {
|
||||
color: var(--theme-contrast-color);
|
||||
.tag-line[selected] :is(
|
||||
.theme-selected.theme-bg-contrast ~ .editor,
|
||||
.theme-selected ~ .editor .theme-fg-contrast
|
||||
) {
|
||||
color: var(--theme-contrast-color) !important;
|
||||
}
|
||||
|
||||
|
||||
/* Applicable to the DOCTYPE */
|
||||
.doctype {
|
||||
font-style: italic;
|
||||
|
|
|
@ -29,14 +29,7 @@ rawPackets.set(`GET request`, {
|
|||
"cause": {
|
||||
"loadingDocumentUri": "https://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html",
|
||||
"type": "img",
|
||||
"stacktraceAvailable": true,
|
||||
"lastFrame": {
|
||||
"filename": "https://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html",
|
||||
"lineNumber": 3,
|
||||
"columnNumber": 1,
|
||||
"functionName": "triggerPacket",
|
||||
"asyncCause": null
|
||||
}
|
||||
"stacktraceAvailable": false
|
||||
},
|
||||
"httpVersion": "HTTP/1.1",
|
||||
"status": "404",
|
||||
|
@ -68,14 +61,7 @@ rawPackets.set(`GET request update`, {
|
|||
"cause": {
|
||||
"loadingDocumentUri": "https://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html",
|
||||
"type": "img",
|
||||
"stacktraceAvailable": true,
|
||||
"lastFrame": {
|
||||
"filename": "https://example.com/browser/devtools/client/webconsole/test/browser/stub-generators/test-network-event.html",
|
||||
"lineNumber": 3,
|
||||
"columnNumber": 1,
|
||||
"functionName": "triggerPacket",
|
||||
"asyncCause": null
|
||||
}
|
||||
"stacktraceAvailable": false
|
||||
},
|
||||
"httpVersion": "HTTP/1.1",
|
||||
"status": "404",
|
||||
|
|
|
@ -619,6 +619,10 @@ class CssGridHighlighter extends AutoRefreshHighlighter {
|
|||
return fragment.rows.lines[fragment.rows.lines.length - 1].start;
|
||||
}
|
||||
|
||||
getNode(id) {
|
||||
return this.markup.content.root.getElementById(this.ID_CLASS_PREFIX + id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The AutoRefreshHighlighter's _hasMoved method returns true only if the
|
||||
* element's quads have changed. Override it so it also returns true if the
|
||||
|
@ -726,6 +730,18 @@ class CssGridHighlighter extends AutoRefreshHighlighter {
|
|||
// Hide the canvas, grid element highlights and infobar.
|
||||
this._hide();
|
||||
|
||||
// Set z-index.
|
||||
this.markup.content.root.firstElementChild.style.setProperty(
|
||||
"z-index",
|
||||
this.options.zIndex
|
||||
);
|
||||
|
||||
// Update the grid color
|
||||
this.markup.content.root.firstElementChild.style.setProperty(
|
||||
"--grid-color",
|
||||
this.color
|
||||
);
|
||||
|
||||
return this._update();
|
||||
}
|
||||
|
||||
|
@ -1800,24 +1816,7 @@ class CssGridHighlighter extends AutoRefreshHighlighter {
|
|||
_update() {
|
||||
setIgnoreLayoutChanges(true);
|
||||
|
||||
// Set z-index.
|
||||
this.markup.content.root.firstElementChild.style.setProperty(
|
||||
"z-index",
|
||||
this.options.zIndex
|
||||
);
|
||||
|
||||
const root = this.getElement("root");
|
||||
const cells = this.getElement("cells");
|
||||
const areas = this.getElement("areas");
|
||||
|
||||
// Set the grid cells and areas fill to the current grid colour.
|
||||
cells.setAttribute("style", `fill: ${this.color}`);
|
||||
areas.setAttribute("style", `fill: ${this.color}`);
|
||||
|
||||
// Hide the root element and force the reflow in order to get the proper window's
|
||||
// dimensions without increasing them.
|
||||
root.setAttribute("style", "display: none");
|
||||
this.win.document.documentElement.offsetWidth;
|
||||
const root = this.getNode("root");
|
||||
this._winDimensions = getWindowDimensions(this.win);
|
||||
const { width, height } = this._winDimensions;
|
||||
|
||||
|
@ -1866,10 +1865,8 @@ class CssGridHighlighter extends AutoRefreshHighlighter {
|
|||
this._showGrid();
|
||||
this._showGridElements();
|
||||
|
||||
root.setAttribute(
|
||||
"style",
|
||||
`position: absolute; width: ${width}px; height: ${height}px; overflow: hidden`
|
||||
);
|
||||
root.style.setProperty("width", `${width}px`);
|
||||
root.style.setProperty("height", `${height}px`);
|
||||
|
||||
setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement);
|
||||
return true;
|
||||
|
|
|
@ -250,8 +250,20 @@
|
|||
color: var(--grey-40);
|
||||
}
|
||||
|
||||
/* CSS Flexbox Highlighter */
|
||||
|
||||
.flexbox-root {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* CSS Grid Highlighter */
|
||||
|
||||
.css-grid-root {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.css-grid-canvas {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
|
@ -268,6 +280,8 @@
|
|||
.css-grid-cells {
|
||||
opacity: 0.5;
|
||||
stroke: none;
|
||||
/* Set in css-grid.js */
|
||||
fill: var(--grid-color);
|
||||
}
|
||||
|
||||
.css-grid-area-infobar-name,
|
||||
|
|
|
@ -302,6 +302,10 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
|
|||
return pattern;
|
||||
}
|
||||
|
||||
getNode(id) {
|
||||
return this.markup.content.root.getElementById(this.ID_CLASS_PREFIX + id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The AutoRefreshHighlighter's _hasMoved method returns true only if the
|
||||
* element's quads have changed. Override it so it also returns true if the
|
||||
|
@ -829,12 +833,6 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
|
|||
_update() {
|
||||
setIgnoreLayoutChanges(true);
|
||||
|
||||
const root = this.getElement("root");
|
||||
|
||||
// Hide the root element and force the reflow in order to get the proper window's
|
||||
// dimensions without increasing them.
|
||||
root.setAttribute("style", "display: none");
|
||||
this.win.document.documentElement.offsetWidth;
|
||||
this._winDimensions = getWindowDimensions(this.win);
|
||||
const { width, height } = this._winDimensions;
|
||||
|
||||
|
@ -870,10 +868,9 @@ class FlexboxHighlighter extends AutoRefreshHighlighter {
|
|||
this._showFlexbox();
|
||||
this.prevColor = this.color;
|
||||
|
||||
root.setAttribute(
|
||||
"style",
|
||||
`position: absolute; width: ${width}px; height: ${height}px; overflow: hidden`
|
||||
);
|
||||
const root = this.getNode("root");
|
||||
root.style.setProperty("width", `${width}px`);
|
||||
root.style.setProperty("height", `${height}px`);
|
||||
|
||||
setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement);
|
||||
return true;
|
||||
|
|
|
@ -634,6 +634,7 @@ class PageStyleActor extends Actor {
|
|||
* - isSystem Boolean
|
||||
* - inherited Boolean
|
||||
* - pseudoElement String
|
||||
* - darkColorScheme Boolean
|
||||
*/
|
||||
_getAllElementRules(node, inherited, options) {
|
||||
const { bindingElement, pseudo } = CssLogic.getBindingElementAndPseudo(
|
||||
|
@ -650,12 +651,11 @@ class PageStyleActor extends Actor {
|
|||
const showInheritedStyles =
|
||||
inherited && this._hasInheritedProps(bindingElement.style);
|
||||
|
||||
const rule = {
|
||||
rule: elementStyle,
|
||||
const rule = this._getRuleItem(elementStyle, node.rawNode, {
|
||||
pseudoElement: null,
|
||||
isSystem: false,
|
||||
inherited: false,
|
||||
};
|
||||
});
|
||||
|
||||
// First any inline styles
|
||||
if (showElementStyles) {
|
||||
|
@ -717,6 +717,27 @@ class PageStyleActor extends Actor {
|
|||
return rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DOMNode} rawNode
|
||||
* @param {StyleRuleActor} styleRuleActor
|
||||
* @param {Object} params
|
||||
* @param {Boolean} params.inherited
|
||||
* @param {Boolean} params.isSystem
|
||||
* @param {String|null} params.pseudoElement
|
||||
* @returns Object
|
||||
*/
|
||||
_getRuleItem(rule, rawNode, { inherited, isSystem, pseudoElement }) {
|
||||
return {
|
||||
rule,
|
||||
pseudoElement,
|
||||
isSystem,
|
||||
inherited,
|
||||
// We can't compute the value for the whole document as the color scheme
|
||||
// can be set at the node level (e.g. with `color-scheme`)
|
||||
darkColorScheme: InspectorUtils.isUsedColorSchemeDark(rawNode),
|
||||
};
|
||||
}
|
||||
|
||||
_nodeIsTextfieldLike(node) {
|
||||
if (node.nodeName == "TEXTAREA") {
|
||||
return true;
|
||||
|
@ -841,12 +862,13 @@ class PageStyleActor extends Actor {
|
|||
|
||||
const ruleActor = this._styleRef(domRule);
|
||||
|
||||
rules.push({
|
||||
rule: ruleActor,
|
||||
inherited,
|
||||
isSystem,
|
||||
pseudoElement: pseudo,
|
||||
});
|
||||
rules.push(
|
||||
this._getRuleItem(ruleActor, node, {
|
||||
inherited,
|
||||
isSystem,
|
||||
pseudoElement: pseudo,
|
||||
})
|
||||
);
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
|
|
@ -158,12 +158,15 @@ class TracerActor extends Actor {
|
|||
switch (this.logMethod) {
|
||||
case TRACER_LOG_METHODS.STDOUT:
|
||||
ListenerClass = StdoutTracingListener;
|
||||
// Currently only the profiler output is supported with the native tracer.
|
||||
options.useNativeTracing = false;
|
||||
break;
|
||||
case TRACER_LOG_METHODS.CONSOLE:
|
||||
case TRACER_LOG_METHODS.DEBUGGER_SIDEBAR:
|
||||
// Console and debugger sidebar are both using JSTRACE_STATE/JSTRACE_TRACE resources
|
||||
// to receive tracing data.
|
||||
ListenerClass = ResourcesTracingListener;
|
||||
options.useNativeTracing = false;
|
||||
break;
|
||||
case TRACER_LOG_METHODS.PROFILER:
|
||||
ListenerClass = ProfilerTracingListener;
|
||||
|
@ -194,6 +197,8 @@ class TracerActor extends Actor {
|
|||
traceOnNextInteraction: !!options.traceOnNextInteraction,
|
||||
// Notify about frame exit / function call returning
|
||||
traceFunctionReturn: !!options.traceFunctionReturn,
|
||||
// Use the native tracing implementation
|
||||
useNativeTracing: !!options.useNativeTracing,
|
||||
// Ignore frames beyond the given depth
|
||||
maxDepth: options.maxDepth,
|
||||
// Stop the tracing after a number of top level frames
|
||||
|
@ -214,7 +219,9 @@ class TracerActor extends Actor {
|
|||
// Remove before stopping to prevent receiving the stop notification
|
||||
lazy.JSTracer.removeTracingListener(this.tracingListener);
|
||||
// Save the result of the stop request for the profiler and the getProfile RDP method
|
||||
this.#stopResult = this.tracingListener.stop();
|
||||
this.#stopResult = this.tracingListener.stop(
|
||||
lazy.JSTracer.maybeGetNativeTrace()
|
||||
);
|
||||
this.tracingListener = null;
|
||||
|
||||
lazy.JSTracer.stopTracing();
|
||||
|
|
|
@ -70,10 +70,61 @@ class ProfilerTracingListener {
|
|||
/**
|
||||
* Stop the record and return the gecko profiler data.
|
||||
*
|
||||
* @param {Object} nativeTrace
|
||||
* If we're using native tracing, this contains a table of what the
|
||||
* native tracer has collected.
|
||||
* @return {Object}
|
||||
* The Gecko profile object.
|
||||
*/
|
||||
stop() {
|
||||
stop(nativeTrace) {
|
||||
if (nativeTrace) {
|
||||
const KIND_INDEX = 0;
|
||||
|
||||
const LINENO_INDEX = 1;
|
||||
const COLUMN_INDEX = 2;
|
||||
const SCRIPT_ID_INDEX = 3;
|
||||
const FUNCTION_NAME_ID_INDEX = 4;
|
||||
const IMPLEMENTATION_INDEX = 5;
|
||||
const TIME_INDEX = 6;
|
||||
|
||||
const LABEL_INDEX = 1;
|
||||
const LABEL_TIME_INDEX = 2;
|
||||
|
||||
const IMPLEMENTATION_STRINGS = ["interpreter", "baseline", "ion", "wasm"];
|
||||
|
||||
for (const entry of nativeTrace.events) {
|
||||
const kind = entry[KIND_INDEX];
|
||||
switch (kind) {
|
||||
case Debugger.TRACING_EVENT_KIND_FUNCTION_ENTER: {
|
||||
this.#onFramePush(
|
||||
{
|
||||
name: nativeTrace.atoms[entry[FUNCTION_NAME_ID_INDEX]],
|
||||
url: nativeTrace.scriptURLs[entry[SCRIPT_ID_INDEX]],
|
||||
lineNumber: entry[LINENO_INDEX],
|
||||
columnNumber: entry[COLUMN_INDEX],
|
||||
category: IMPLEMENTATION_STRINGS[entry[IMPLEMENTATION_INDEX]],
|
||||
sourceId: entry[SCRIPT_ID_INDEX],
|
||||
},
|
||||
entry[TIME_INDEX]
|
||||
);
|
||||
break;
|
||||
}
|
||||
case Debugger.TRACING_EVENT_KIND_FUNCTION_LEAVE: {
|
||||
this.#onFramePop(entry[TIME_INDEX], false);
|
||||
break;
|
||||
}
|
||||
case Debugger.TRACING_EVENT_KIND_LABEL_ENTER: {
|
||||
this.#logDOMEvent(entry[LABEL_INDEX], entry[LABEL_TIME_INDEX]);
|
||||
break;
|
||||
}
|
||||
case Debugger.TRACING_EVENT_KIND_LABEL_LEAVE: {
|
||||
this.#onFramePop(entry[LABEL_TIME_INDEX], false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the profile to return.
|
||||
const profile = this.#getEmptyProfile();
|
||||
profile.meta.categories = this.#categories;
|
||||
|
@ -413,7 +464,7 @@ class ProfilerTracingListener {
|
|||
this.#logDOMEvent(currentDOMEvent);
|
||||
}
|
||||
|
||||
const frameIndex = this.#getOrCreateFrame({
|
||||
this.#onFramePush({
|
||||
// formatedDisplayName has a lambda at the beginning, remove it.
|
||||
name: formatedDisplayName.replace("λ ", ""),
|
||||
url,
|
||||
|
@ -422,16 +473,6 @@ class ProfilerTracingListener {
|
|||
category: frame.implementation,
|
||||
sourceId: script.source.id,
|
||||
});
|
||||
this.#currentStackIndex = this.#getOrCreateStack(
|
||||
frameIndex,
|
||||
this.#currentStackIndex
|
||||
);
|
||||
|
||||
this.#thread.samples.data.push([
|
||||
this.#currentStackIndex,
|
||||
ChromeUtils.dateNow() - this.#startTime,
|
||||
0, // eventDelay
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -440,8 +481,14 @@ class ProfilerTracingListener {
|
|||
* Called when a DOM Event just fired (and some listener in JS is about to run).
|
||||
*
|
||||
* @param {String} domEventName
|
||||
* @param {Number|undefined} [time=undefined]
|
||||
* The time at which this event occurred
|
||||
*/
|
||||
#logDOMEvent(domEventName) {
|
||||
#logDOMEvent(domEventName, time = undefined) {
|
||||
if (time === undefined) {
|
||||
time = ChromeUtils.dateNow();
|
||||
}
|
||||
|
||||
const frameIndex = this.#getOrCreateLabelFrame(domEventName);
|
||||
this.#currentStackIndex = this.#getOrCreateStack(
|
||||
frameIndex,
|
||||
|
@ -450,7 +497,7 @@ class ProfilerTracingListener {
|
|||
|
||||
this.#thread.samples.data.push([
|
||||
this.#currentStackIndex,
|
||||
ChromeUtils.dateNow() - this.#startTime,
|
||||
time - this.#startTime,
|
||||
0, // eventDelay
|
||||
]);
|
||||
}
|
||||
|
@ -510,13 +557,46 @@ class ProfilerTracingListener {
|
|||
}
|
||||
|
||||
/**
|
||||
* Called when a function call ends and returns.
|
||||
* Called when a new function is called.
|
||||
*
|
||||
* @param {Object} frameInfo
|
||||
* @param {Number|undefined} [time=undefined]
|
||||
* The time at which this event occurred
|
||||
*/
|
||||
#onFramePop() {
|
||||
#onFramePush(frameInfo, time) {
|
||||
if (time === undefined) {
|
||||
time = ChromeUtils.dateNow();
|
||||
}
|
||||
|
||||
const frameIndex = this.#getOrCreateFrame(frameInfo);
|
||||
this.#currentStackIndex = this.#getOrCreateStack(
|
||||
frameIndex,
|
||||
this.#currentStackIndex
|
||||
);
|
||||
|
||||
this.#thread.samples.data.push([
|
||||
this.#currentStackIndex,
|
||||
time - this.#startTime,
|
||||
0, // eventDelay
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a function call ends and returns.
|
||||
* @param {Number|undefined} [time=undefined]
|
||||
* The time at which this event occurred
|
||||
* @param {Boolean} [autoPopLabels=false]
|
||||
* Whether we should automatically pop label frames if we're popping a root
|
||||
*/
|
||||
#onFramePop(time = undefined, autoPopLabels = true) {
|
||||
if (this.#currentStackIndex === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (time === undefined) {
|
||||
time = ChromeUtils.dateNow();
|
||||
}
|
||||
|
||||
this.#currentStackIndex =
|
||||
this.#thread.stackTable.data[this.#currentStackIndex][
|
||||
INDEXES.stacks.prefix
|
||||
|
@ -526,13 +606,13 @@ class ProfilerTracingListener {
|
|||
// so that the frontend considers that the last executed frame stops its execution.
|
||||
this.#thread.samples.data.push([
|
||||
this.#currentStackIndex,
|
||||
ChromeUtils.dateNow() - this.#startTime,
|
||||
time - this.#startTime,
|
||||
0, // eventDelay
|
||||
]);
|
||||
|
||||
// If we popped and now are on a label frame, with a null line,
|
||||
// automatically also pop that label frame.
|
||||
if (this.#currentStackIndex !== null) {
|
||||
if (autoPopLabels && this.#currentStackIndex !== null) {
|
||||
const currentFrameIndex =
|
||||
this.#thread.stackTable.data[this.#currentStackIndex][
|
||||
INDEXES.stacks.frame
|
||||
|
@ -540,7 +620,7 @@ class ProfilerTracingListener {
|
|||
const currentFrameLine =
|
||||
this.#thread.frameTable.data[currentFrameIndex][INDEXES.frames.line];
|
||||
if (currentFrameLine == null) {
|
||||
this.#onFramePop();
|
||||
this.#onFramePop(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -907,6 +907,7 @@ WebConsoleCommandsManager.register({
|
|||
traceFunctionReturn: !!args.returns,
|
||||
traceValues: !!args.values,
|
||||
traceOnNextInteraction: args["on-next-interaction"] || null,
|
||||
useNativeTracing: args["use-native-tracing"] || null,
|
||||
traceDOMMutations,
|
||||
maxDepth: args["max-depth"] || null,
|
||||
maxRecords: args["max-records"] || null,
|
||||
|
|
|
@ -139,6 +139,8 @@ const customLazy = {
|
|||
* @param {Boolean} options.traceFunctionReturn
|
||||
* Optional setting to enable when the tracing should notify about frame exit.
|
||||
* i.e. when a function call returns or throws.
|
||||
* @param {Boolean} options.useNativeTracing
|
||||
* Optional setting to enable the native tracing implementation.
|
||||
* @param {String} options.filterFrameSourceUrl
|
||||
* Optional setting to restrict all traces to only a given source URL.
|
||||
* This is a loose check, so any source whose URL includes the passed string will be traced.
|
||||
|
@ -218,6 +220,7 @@ class JavaScriptTracer {
|
|||
this.traceSteps = !!options.traceSteps;
|
||||
this.traceValues = !!options.traceValues;
|
||||
this.traceFunctionReturn = !!options.traceFunctionReturn;
|
||||
this.useNativeTracing = !!options.useNativeTracing;
|
||||
this.maxDepth = options.maxDepth;
|
||||
this.infiniteLoopDepthLimit = isWorker
|
||||
? 200
|
||||
|
@ -299,7 +302,11 @@ class JavaScriptTracer {
|
|||
#startTracing() {
|
||||
this.isTracing = true;
|
||||
|
||||
this.dbg.onEnterFrame = this.onEnterFrame;
|
||||
if (this.useNativeTracing) {
|
||||
this.dbg.nativeTracing = true;
|
||||
} else {
|
||||
this.dbg.onEnterFrame = this.onEnterFrame;
|
||||
}
|
||||
|
||||
if (this.traceDOMEvents) {
|
||||
this.startTracingDOMEvents();
|
||||
|
@ -334,6 +341,7 @@ class JavaScriptTracer {
|
|||
win.browsingContext.browserId == browserId
|
||||
) {
|
||||
this.dbg.addDebuggee(g);
|
||||
this.dbg.nativeTracing = this.useNativeTracing;
|
||||
this.debuggerNotificationObserver.connect(win);
|
||||
}
|
||||
} catch (e) {}
|
||||
|
@ -498,6 +506,17 @@ class JavaScriptTracer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If native tracing is enabled, get the trace from the native tracer
|
||||
*/
|
||||
maybeGetNativeTrace() {
|
||||
if (this.useNativeTracing) {
|
||||
return this.dbg.collectNativeTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop observing execution.
|
||||
*
|
||||
|
@ -510,7 +529,12 @@ class JavaScriptTracer {
|
|||
return;
|
||||
}
|
||||
|
||||
this.dbg.onEnterFrame = undefined;
|
||||
if (!this.useNativeTracing) {
|
||||
this.dbg.nativeTracing = false;
|
||||
} else {
|
||||
this.dbg.onEnterFrame = undefined;
|
||||
}
|
||||
|
||||
this.dbg.removeAllDebuggees();
|
||||
this.dbg.onNewGlobalObject = undefined;
|
||||
this.dbg = null;
|
||||
|
@ -1028,6 +1052,17 @@ function stopTracing() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If native tracing is enabled, get the trace from the native tracer
|
||||
*/
|
||||
function maybeGetNativeTrace() {
|
||||
if (activeTracer) {
|
||||
return activeTracer.maybeGetNativeTrace();
|
||||
}
|
||||
console.warn("Can't get a native trace as we were not tracing.");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for tracing updates.
|
||||
*
|
||||
|
@ -1128,6 +1163,7 @@ function syncPause(duration) {
|
|||
export const JSTracer = {
|
||||
startTracing,
|
||||
stopTracing,
|
||||
maybeGetNativeTrace,
|
||||
addTracingListener,
|
||||
removeTracingListener,
|
||||
NEXT_INTERACTION_MESSAGE,
|
||||
|
|
|
@ -21,8 +21,8 @@ const createParentProcessRequests = async () => {
|
|||
const EXPECTED_METHOD_NAME = "createParentProcessRequests";
|
||||
const EXPECTED_REQUEST_LINE_1 = 12;
|
||||
const EXPECTED_REQUEST_COL_1 = 9;
|
||||
const EXPECTED_REQUEST_LINE_2 = 17;
|
||||
const EXPECTED_REQUEST_COL_2 = 3;
|
||||
// const EXPECTED_REQUEST_LINE_2 = 17;
|
||||
// const EXPECTED_REQUEST_COL_2 = 3;
|
||||
|
||||
// Test the ResourceCommand API around NETWORK_EVENT for the parent process
|
||||
|
||||
|
@ -133,13 +133,20 @@ add_task(async function testParentProcessRequests() {
|
|||
ok(!firstImageRequest.fromCache, "The first image request isn't cached");
|
||||
ok(firstImageRequest.chromeContext, "The first image request is privileged");
|
||||
|
||||
const firstImageStacktrace = receivedStacktraces[1].lastFrame;
|
||||
is(receivedStacktraces[1].resourceId, firstImageRequest.stacktraceResourceId);
|
||||
const firstImageStacktrace = receivedStacktraces[1].lastFrame;
|
||||
// TODO(bug 1911435).
|
||||
todo(
|
||||
!!firstImageStacktrace,
|
||||
"After bug 1076583, image load is async and we can't get a stack trace"
|
||||
);
|
||||
/*
|
||||
is(firstImageStacktrace.filename, gTestPath);
|
||||
is(firstImageStacktrace.lineNumber, EXPECTED_REQUEST_LINE_2);
|
||||
is(firstImageStacktrace.columnNumber, EXPECTED_REQUEST_COL_2);
|
||||
is(firstImageStacktrace.functionName, EXPECTED_METHOD_NAME);
|
||||
is(firstImageStacktrace.asyncCause, null);
|
||||
*/
|
||||
|
||||
info("Assert the second image request");
|
||||
const secondImageRequest = receivedNetworkEvents[2];
|
||||
|
|
|
@ -106,6 +106,10 @@ class TracerCommand extends EventEmitter {
|
|||
"devtools.debugger.javascript-tracing-function-return",
|
||||
false
|
||||
),
|
||||
useNativeTracing: Services.prefs.getBoolPref(
|
||||
"devtools.debugger.javascript-tracing-native",
|
||||
false
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ types.addDictType("tracer.start.options", {
|
|||
traceOnNextInteraction: "boolean",
|
||||
traceOnNextLoad: "boolean",
|
||||
traceDOMMutations: "nullable:array:string",
|
||||
useNativeTracing: "boolean",
|
||||
});
|
||||
|
||||
const tracerSpec = generateActorSpec({
|
||||
|
|
70
dom/base/CacheExpirationTime.h
Normal file
70
dom/base/CacheExpirationTime.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_CacheExpirationTime_h___
|
||||
#define mozilla_dom_CacheExpirationTime_h___
|
||||
|
||||
#include <stdint.h> // uint32_t
|
||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
#include "prtime.h" // PRTime, PR_USEC_PER_SEC
|
||||
#include "nsICacheEntry.h" // nsICacheEntry
|
||||
|
||||
/*
|
||||
* The expiration time for sub resource cache.
|
||||
*/
|
||||
struct CacheExpirationTime {
|
||||
private:
|
||||
uint32_t mTime;
|
||||
|
||||
static constexpr uint32_t kAlreadyExpired = 0;
|
||||
static constexpr uint32_t kNever = nsICacheEntry::NO_EXPIRATION_TIME;
|
||||
|
||||
constexpr CacheExpirationTime() : mTime(kNever) {}
|
||||
|
||||
explicit constexpr CacheExpirationTime(uint32_t aTime) : mTime(aTime) {}
|
||||
|
||||
static uint32_t SecondsFromPRTime(PRTime aTime) {
|
||||
return uint32_t(int64_t(aTime) / int64_t(PR_USEC_PER_SEC));
|
||||
}
|
||||
|
||||
public:
|
||||
static constexpr CacheExpirationTime AlreadyExpired() {
|
||||
return CacheExpirationTime(kAlreadyExpired);
|
||||
}
|
||||
|
||||
static constexpr CacheExpirationTime Never() {
|
||||
return CacheExpirationTime(kNever);
|
||||
}
|
||||
|
||||
static constexpr CacheExpirationTime ExpireAt(uint32_t aTime) {
|
||||
return CacheExpirationTime(aTime);
|
||||
}
|
||||
|
||||
bool IsExpired() const {
|
||||
if (IsNever()) {
|
||||
return false;
|
||||
}
|
||||
return mTime <= SecondsFromPRTime(PR_Now());
|
||||
}
|
||||
|
||||
bool IsNever() const { return mTime == kNever; }
|
||||
|
||||
bool IsShorterThan(const CacheExpirationTime& aOther) const {
|
||||
return mTime < aOther.mTime;
|
||||
}
|
||||
|
||||
void SetMinimum(const CacheExpirationTime& aOther) {
|
||||
if (aOther.IsNever()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsNever() || aOther.IsShorterThan(*this)) {
|
||||
mTime = aOther.mTime;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* mozilla_dom_CacheExpirationTime_h___ */
|
|
@ -149,7 +149,7 @@ static void LazyLoadCallback(
|
|||
Element* target = entry->Target();
|
||||
if (entry->IsIntersecting()) {
|
||||
if (auto* image = HTMLImageElement::FromNode(target)) {
|
||||
image->StopLazyLoading(HTMLImageElement::StartLoading::Yes);
|
||||
image->StopLazyLoading();
|
||||
} else if (auto* iframe = HTMLIFrameElement::FromNode(target)) {
|
||||
iframe->StopLazyLoading();
|
||||
} else {
|
||||
|
|
|
@ -194,6 +194,7 @@
|
|||
#include "mozilla/dom/HTMLTextAreaElement.h"
|
||||
#include "mozilla/dom/ImageTracker.h"
|
||||
#include "mozilla/dom/InspectorUtils.h"
|
||||
#include "mozilla/dom/InteractiveWidget.h"
|
||||
#include "mozilla/dom/Link.h"
|
||||
#include "mozilla/dom/MediaQueryList.h"
|
||||
#include "mozilla/dom/MediaSource.h"
|
||||
|
@ -1432,6 +1433,7 @@ Document::Document(const char* aContentType)
|
|||
mHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_UNINITIALIZED),
|
||||
mViewportType(Unknown),
|
||||
mViewportFit(ViewportFitType::Auto),
|
||||
mInteractiveWidgetMode(InteractiveWidget::ResizesContent),
|
||||
mHeaderData(nullptr),
|
||||
mServoRestyleRootDirtyBits(0),
|
||||
mThrowOnDynamicMarkupInsertionCounter(0),
|
||||
|
@ -8376,9 +8378,6 @@ void Document::UnblockDOMContentLoaded() {
|
|||
("DOCUMENT %p UnblockDOMContentLoaded", this));
|
||||
|
||||
mDidFireDOMContentLoaded = true;
|
||||
if (PresShell* presShell = GetPresShell()) {
|
||||
presShell->GetRefreshDriver()->NotifyDOMContentLoaded();
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE);
|
||||
if (!mSynchronousDOMContentLoaded) {
|
||||
|
@ -10956,11 +10955,47 @@ ViewportMetaData Document::GetViewportMetaData() const {
|
|||
: ViewportMetaData();
|
||||
}
|
||||
|
||||
static InteractiveWidget ParseInteractiveWidget(
|
||||
const ViewportMetaData& aViewportMetaData) {
|
||||
if (aViewportMetaData.mInteractiveWidgetMode.IsEmpty()) {
|
||||
// The spec defines "use `resizes-visual` if no value specified", but here
|
||||
// we use `resizes-content` for the backward compatibility now.
|
||||
// We will change it in bug 1884807.
|
||||
return InteractiveWidget::ResizesContent;
|
||||
}
|
||||
|
||||
if (aViewportMetaData.mInteractiveWidgetMode.EqualsIgnoreCase(
|
||||
"resizes-visual")) {
|
||||
return InteractiveWidget::ResizesVisual;
|
||||
}
|
||||
if (aViewportMetaData.mInteractiveWidgetMode.EqualsIgnoreCase(
|
||||
"resizes-content")) {
|
||||
return InteractiveWidget::ResizesContent;
|
||||
}
|
||||
if (aViewportMetaData.mInteractiveWidgetMode.EqualsIgnoreCase(
|
||||
"overlays-content")) {
|
||||
return InteractiveWidget::OverlaysContent;
|
||||
}
|
||||
// For the same reason above empty case, we use `resizes-content` here.
|
||||
return InteractiveWidget::ResizesContent;
|
||||
}
|
||||
|
||||
void Document::SetMetaViewportData(UniquePtr<ViewportMetaData> aData) {
|
||||
mLastModifiedViewportMetaData = std::move(aData);
|
||||
// Trigger recomputation of the nsViewportInfo the next time it's queried.
|
||||
mViewportType = Unknown;
|
||||
|
||||
// Parse interactive-widget here anyway. Normally we parse any data in the
|
||||
// meta viewport tag in GetViewportInfo(), but GetViewportInfo() depends on
|
||||
// the document state (e.g. display size, fullscreen, desktop-mode etc.)
|
||||
// whereas interactive-widget is independent from the document state, it's
|
||||
// necessary whatever the document state is.
|
||||
dom::InteractiveWidget interactiveWidget =
|
||||
ParseInteractiveWidget(*mLastModifiedViewportMetaData);
|
||||
if (mInteractiveWidgetMode != interactiveWidget) {
|
||||
mInteractiveWidgetMode = interactiveWidget;
|
||||
}
|
||||
|
||||
AsyncEventDispatcher::RunDOMEventWhenSafe(
|
||||
*this, u"DOMMetaViewportFitChanged"_ns, CanBubble::eYes,
|
||||
ChromeOnlyDispatch::eYes);
|
||||
|
|
|
@ -246,7 +246,6 @@ class FeaturePolicy;
|
|||
class FontFaceSet;
|
||||
class FragmentDirective;
|
||||
class FrameRequestCallback;
|
||||
class ImageTracker;
|
||||
class HighlightRegistry;
|
||||
class HTMLAllCollection;
|
||||
class HTMLBodyElement;
|
||||
|
@ -256,6 +255,8 @@ class HTMLDialogElement;
|
|||
class HTMLSharedElement;
|
||||
class HTMLVideoElement;
|
||||
class HTMLImageElement;
|
||||
class ImageTracker;
|
||||
enum class InteractiveWidget : uint8_t;
|
||||
struct LifecycleCallbackArgs;
|
||||
class Link;
|
||||
class Location;
|
||||
|
@ -3958,6 +3959,10 @@ class Document : public nsINode,
|
|||
public:
|
||||
const OriginTrials& Trials() const { return mTrials; }
|
||||
|
||||
dom::InteractiveWidget InteractiveWidget() const {
|
||||
return mInteractiveWidgetMode;
|
||||
}
|
||||
|
||||
private:
|
||||
void DoCacheAllKnownLangPrefs();
|
||||
void RecomputeLanguageFromCharset();
|
||||
|
@ -5173,6 +5178,9 @@ class Document : public nsINode,
|
|||
// https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor
|
||||
ViewportFitType mViewportFit;
|
||||
|
||||
// https://drafts.csswg.org/css-viewport/#interactive-widget-section
|
||||
dom::InteractiveWidget mInteractiveWidgetMode;
|
||||
|
||||
// XXXdholbert This should really be modernized to a nsTHashMap or similar,
|
||||
// though note that the modernization will need to take care to also convert
|
||||
// the special hash_table_ops logic (e.g. how SubDocClearEntry clears the
|
||||
|
|
19
dom/base/InteractiveWidget.h
Normal file
19
dom/base/InteractiveWidget.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef DOM_INTERACTIVE_WIDGET_H_
|
||||
#define DOM_INTERACTIVE_WIDGET_H_
|
||||
|
||||
namespace mozilla::dom {
|
||||
// https://drafts.csswg.org/css-viewport/#interactive-widget-section
|
||||
enum class InteractiveWidget : uint8_t {
|
||||
OverlaysContent,
|
||||
ResizesContent,
|
||||
ResizesVisual,
|
||||
};
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // DOM_INTERACTIVE_WIDGET_H_
|
|
@ -303,6 +303,58 @@ nsCString SelectionChangeReasonsToCString(int16_t aReasons) {
|
|||
|
||||
} // namespace mozilla
|
||||
|
||||
SelectionNodeCache::SelectionNodeCache(PresShell& aOwningPresShell)
|
||||
: mOwningPresShell(aOwningPresShell) {
|
||||
MOZ_ASSERT(!mOwningPresShell.mSelectionNodeCache);
|
||||
mOwningPresShell.mSelectionNodeCache = this;
|
||||
}
|
||||
|
||||
SelectionNodeCache::~SelectionNodeCache() {
|
||||
mOwningPresShell.mSelectionNodeCache = nullptr;
|
||||
}
|
||||
|
||||
bool SelectionNodeCache::MaybeCollectNodesAndCheckIfFullySelectedInAnyOf(
|
||||
const nsINode* aNode, const nsTArray<Selection*>& aSelections) {
|
||||
for (const auto* sel : aSelections) {
|
||||
if (MaybeCollectNodesAndCheckIfFullySelected(aNode, sel)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const nsTHashSet<const nsINode*>& SelectionNodeCache::MaybeCollect(
|
||||
const Selection* aSelection) {
|
||||
MOZ_ASSERT(aSelection);
|
||||
return mSelectedNodes.LookupOrInsertWith(aSelection, [sel = RefPtr(
|
||||
aSelection)] {
|
||||
nsTHashSet<const nsINode*> fullySelectedNodes;
|
||||
for (size_t rangeIndex = 0; rangeIndex < sel->RangeCount(); ++rangeIndex) {
|
||||
AbstractRange* range = sel->GetAbstractRangeAt(rangeIndex);
|
||||
const RangeBoundary& startRef = range->MayCrossShadowBoundaryStartRef();
|
||||
const RangeBoundary& endRef = range->MayCrossShadowBoundaryEndRef();
|
||||
|
||||
const nsINode* startContainer =
|
||||
startRef.IsStartOfContainer() ? nullptr : startRef.Container();
|
||||
const nsINode* endContainer =
|
||||
endRef.IsEndOfContainer() ? nullptr : endRef.Container();
|
||||
UnsafePreContentIterator iter;
|
||||
iter.Init(range);
|
||||
for (; !iter.IsDone(); iter.Next()) {
|
||||
if (const nsINode* node = iter.GetCurrentNode()) {
|
||||
// Only collect start and end container if they are fully
|
||||
// selected (they are null in that case).
|
||||
if (node == startContainer || node == endContainer) {
|
||||
continue;
|
||||
}
|
||||
fullySelectedNodes.Insert(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
return fullySelectedNodes;
|
||||
});
|
||||
}
|
||||
|
||||
// #define DEBUG_SELECTION // uncomment for printf describing every collapse and
|
||||
// extend. #define DEBUG_NAVIGATION
|
||||
|
||||
|
@ -1954,6 +2006,29 @@ UniquePtr<SelectionDetails> Selection::LookUpSelection(
|
|||
}
|
||||
|
||||
nsTArray<AbstractRange*> overlappingRanges;
|
||||
SelectionNodeCache* cache =
|
||||
GetPresShell() ? GetPresShell()->GetSelectionNodeCache() : nullptr;
|
||||
if (cache && RangeCount() == 1) {
|
||||
const bool isFullySelected =
|
||||
cache->MaybeCollectNodesAndCheckIfFullySelected(aContent, this);
|
||||
if (isFullySelected) {
|
||||
auto newHead = MakeUnique<SelectionDetails>();
|
||||
|
||||
newHead->mNext = std::move(aDetailsHead);
|
||||
newHead->mStart = AssertedCast<int32_t>(0);
|
||||
newHead->mEnd = AssertedCast<int32_t>(aContentLength);
|
||||
newHead->mSelectionType = aSelectionType;
|
||||
newHead->mHighlightData = mHighlightData;
|
||||
StyledRange* rd = mStyledRanges.FindRangeData(GetAbstractRangeAt(0));
|
||||
if (rd) {
|
||||
newHead->mTextRangeStyle = rd->mTextRangeStyle;
|
||||
}
|
||||
auto detailsHead = std::move(newHead);
|
||||
|
||||
return detailsHead;
|
||||
}
|
||||
}
|
||||
|
||||
nsresult rv = GetAbstractRangesForIntervalArray(
|
||||
aContent, aContentOffset, aContent, aContentOffset + aContentLength,
|
||||
false, &overlappingRanges);
|
||||
|
|
|
@ -56,6 +56,71 @@ namespace mozilla {
|
|||
|
||||
namespace dom {
|
||||
|
||||
/**
|
||||
* This cache allows to store all selected nodes during a reflow operation.
|
||||
*
|
||||
* All fully selected nodes are stored in a hash set per-selection instance.
|
||||
* This allows fast paths in `nsINode::IsSelected()` and
|
||||
* `Selection::LookupSelection()`. For partially selected nodes, the old
|
||||
* mechanisms are used. This is okay, because for partially selected nodes
|
||||
* no expensive node traversal is necessary.
|
||||
*
|
||||
* This cache is designed to be used in a context where no script is allowed
|
||||
* to run. It assumes that the selection itself, or any range therein, does not
|
||||
* change during its lifetime.
|
||||
*
|
||||
* By design, this class can only be instantiated in the `PresShell`.
|
||||
*/
|
||||
class MOZ_RAII SelectionNodeCache final {
|
||||
public:
|
||||
~SelectionNodeCache();
|
||||
/**
|
||||
* Returns true if `aNode` is fully selected by any of the given selections.
|
||||
*
|
||||
* This method will collect all fully selected nodes of `aSelections` and
|
||||
* store them internally (therefore this method isn't const).
|
||||
*/
|
||||
bool MaybeCollectNodesAndCheckIfFullySelectedInAnyOf(
|
||||
const nsINode* aNode, const nsTArray<Selection*>& aSelections);
|
||||
|
||||
/**
|
||||
* Returns true if `aNode` is fully selected by any range in `aSelection`.
|
||||
*
|
||||
* This method collects all fully selected nodes from `aSelection` and store
|
||||
* them internally.
|
||||
*/
|
||||
bool MaybeCollectNodesAndCheckIfFullySelected(const nsINode* aNode,
|
||||
const Selection* aSelection) {
|
||||
return MaybeCollect(aSelection).Contains(aNode);
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* This class is supposed to be only created by the PresShell.
|
||||
*/
|
||||
friend PresShell;
|
||||
explicit SelectionNodeCache(PresShell& aOwningPresShell);
|
||||
/**
|
||||
* Collects all nodes from a given list of selections.
|
||||
*
|
||||
* This method assumes that the selections itself won't change during this
|
||||
* object's lifetime. It's not possible to 'update' the cached selected ranges
|
||||
* by calling this method again.
|
||||
*/
|
||||
void MaybeCollect(const nsTArray<Selection*>& aSelections);
|
||||
/**
|
||||
* Iterates all ranges in `aSelection` and collects its fully selected nodes
|
||||
* into a hash set, which is also returned.
|
||||
*
|
||||
* If `aSelection` is already cached, the hash set is returned directly.
|
||||
*/
|
||||
const nsTHashSet<const nsINode*>& MaybeCollect(const Selection* aSelection);
|
||||
|
||||
nsTHashMap<const Selection*, nsTHashSet<const nsINode*>> mSelectedNodes;
|
||||
|
||||
PresShell& mOwningPresShell;
|
||||
};
|
||||
|
||||
// Note, the ownership of mozilla::dom::Selection depends on which way the
|
||||
// object is created. When nsFrameSelection has created Selection,
|
||||
// addreffing/releasing the Selection object is aggregated to nsFrameSelection.
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/SourceLocation.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "jsapi.h"
|
||||
|
||||
|
@ -21,12 +22,30 @@ SourceLocation::SourceLocation(nsCOMPtr<nsIURI>&& aResource, uint32_t aLine,
|
|||
uint32_t aCol)
|
||||
: mResource(std::move(aResource)), mLine(aLine), mColumn(aCol) {}
|
||||
|
||||
static MOZ_THREAD_LOCAL(const JSCallingLocation*) tlsFallback;
|
||||
const JSCallingLocation* JSCallingLocation::GetFallback() {
|
||||
if (!tlsFallback.initialized()) {
|
||||
return nullptr;
|
||||
}
|
||||
return tlsFallback.get();
|
||||
}
|
||||
|
||||
void JSCallingLocation::SetFallback(const JSCallingLocation* aFallback) {
|
||||
if (!tlsFallback.init()) {
|
||||
return;
|
||||
}
|
||||
tlsFallback.set(aFallback);
|
||||
}
|
||||
|
||||
JSCallingLocation JSCallingLocation::Get() {
|
||||
return Get(nsContentUtils::GetCurrentJSContext());
|
||||
}
|
||||
|
||||
JSCallingLocation JSCallingLocation::Get(JSContext* aCx) {
|
||||
JSCallingLocation result;
|
||||
if (const JSCallingLocation* loc = GetFallback()) {
|
||||
result = *loc;
|
||||
}
|
||||
if (!aCx) {
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -40,6 +40,22 @@ struct JSCallingLocation : SourceLocation {
|
|||
|
||||
static JSCallingLocation Get();
|
||||
static JSCallingLocation Get(JSContext*);
|
||||
|
||||
class MOZ_STACK_CLASS AutoFallback {
|
||||
public:
|
||||
explicit AutoFallback(const JSCallingLocation* aFallback)
|
||||
: mOldFallback(GetFallback()) {
|
||||
SetFallback(aFallback);
|
||||
}
|
||||
~AutoFallback() { SetFallback(mOldFallback); }
|
||||
|
||||
private:
|
||||
const JSCallingLocation* mOldFallback;
|
||||
};
|
||||
|
||||
private:
|
||||
static const JSCallingLocation* GetFallback();
|
||||
static void SetFallback(const JSCallingLocation*);
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -59,6 +59,8 @@ static void ProcessViewportToken(ViewportMetaData& aData,
|
|||
aData.mUserScalable.Assign(value);
|
||||
} else if (key_atom == nsGkAtoms::viewport_fit) {
|
||||
aData.mViewportFit.Assign(value);
|
||||
} else if (key_atom == nsGkAtoms::interactive_widget) {
|
||||
aData.mInteractiveWidgetMode.Assign(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ struct ViewportMetaData {
|
|||
nsString mMaximumScale;
|
||||
nsString mUserScalable;
|
||||
nsString mViewportFit;
|
||||
nsString mInteractiveWidgetMode;
|
||||
|
||||
bool operator==(const ViewportMetaData& aOther) const {
|
||||
return mWidth == aOther.mWidth && mHeight == aOther.mHeight &&
|
||||
|
@ -27,7 +28,8 @@ struct ViewportMetaData {
|
|||
mMinimumScale == aOther.mMinimumScale &&
|
||||
mMaximumScale == aOther.mMaximumScale &&
|
||||
mUserScalable == aOther.mUserScalable &&
|
||||
mViewportFit == aOther.mViewportFit;
|
||||
mViewportFit == aOther.mViewportFit &&
|
||||
mInteractiveWidgetMode == aOther.mInteractiveWidgetMode;
|
||||
}
|
||||
bool operator!=(const ViewportMetaData& aOther) const {
|
||||
return !(*this == aOther);
|
||||
|
|
|
@ -157,6 +157,7 @@ EXPORTS.mozilla.dom += [
|
|||
"BodyConsumer.h",
|
||||
"BodyUtil.h",
|
||||
"BorrowedAttrInfo.h",
|
||||
"CacheExpirationTime.h",
|
||||
"CCGCScheduler.h",
|
||||
"CharacterData.h",
|
||||
"ChildIterator.h",
|
||||
|
@ -209,6 +210,7 @@ EXPORTS.mozilla.dom += [
|
|||
"IDTracker.h",
|
||||
"ImageEncoder.h",
|
||||
"ImageTracker.h",
|
||||
"InteractiveWidget.h",
|
||||
"IntlUtils.h",
|
||||
"JSExecutionContext.h",
|
||||
"Link.h",
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
#include "mozilla/dom/BrowserParent.h"
|
||||
#include "mozilla/dom/BrowsingContext.h"
|
||||
#include "mozilla/dom/BrowsingContextGroup.h"
|
||||
#include "mozilla/dom/CacheExpirationTime.h"
|
||||
#include "mozilla/dom/CallbackFunction.h"
|
||||
#include "mozilla/dom/CallbackObject.h"
|
||||
#include "mozilla/dom/ChromeMessageBroadcaster.h"
|
||||
|
@ -11402,7 +11403,9 @@ nsContentUtils::GetSubresourceCacheValidationInfo(nsIRequest* aRequest,
|
|||
if (nsCOMPtr<nsICacheInfoChannel> cache = do_QueryInterface(aRequest)) {
|
||||
uint32_t value = 0;
|
||||
if (NS_SUCCEEDED(cache->GetCacheTokenExpirationTime(&value))) {
|
||||
info.mExpirationTime.emplace(value);
|
||||
// NOTE: If the cache doesn't expire, the value should be
|
||||
// nsICacheEntry::NO_EXPIRATION_TIME.
|
||||
info.mExpirationTime.emplace(CacheExpirationTime::ExpireAt(value));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11439,12 +11442,24 @@ nsContentUtils::GetSubresourceCacheValidationInfo(nsIRequest* aRequest,
|
|||
if (knownCacheable) {
|
||||
MOZ_ASSERT(!info.mExpirationTime);
|
||||
MOZ_ASSERT(!info.mMustRevalidate);
|
||||
info.mExpirationTime = Some(0); // 0 means "doesn't expire".
|
||||
info.mExpirationTime = Some(CacheExpirationTime::Never());
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
CacheExpirationTime nsContentUtils::GetSubresourceCacheExpirationTime(
|
||||
nsIRequest* aRequest, nsIURI* aURI) {
|
||||
auto info = GetSubresourceCacheValidationInfo(aRequest, aURI);
|
||||
|
||||
// For now, we never cache entries that we have to revalidate, or whose
|
||||
// channel don't support caching.
|
||||
if (info.mMustRevalidate || !info.mExpirationTime) {
|
||||
return CacheExpirationTime::AlreadyExpired();
|
||||
}
|
||||
return *info.mExpirationTime;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool nsContentUtils::ShouldBypassSubResourceCache(Document* aDoc) {
|
||||
RefPtr<nsILoadGroup> lg = aDoc->GetDocumentLoadGroup();
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/CacheExpirationTime.h"
|
||||
#include "mozilla/dom/FromParser.h"
|
||||
#include "mozilla/dom/FetchPriority.h"
|
||||
#include "mozilla/fallible.h"
|
||||
|
@ -3440,17 +3441,24 @@ class nsContentUtils {
|
|||
|
||||
struct SubresourceCacheValidationInfo {
|
||||
// The expiration time, in seconds, if known.
|
||||
mozilla::Maybe<uint32_t> mExpirationTime;
|
||||
mozilla::Maybe<CacheExpirationTime> mExpirationTime;
|
||||
bool mMustRevalidate = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets cache validation info for subresources such as images or CSS
|
||||
* stylesheets.
|
||||
* Gets cache validation info for subresources such as images, CSS
|
||||
* stylesheets, or JS.
|
||||
*/
|
||||
static SubresourceCacheValidationInfo GetSubresourceCacheValidationInfo(
|
||||
nsIRequest*, nsIURI*);
|
||||
|
||||
/**
|
||||
* Gets cache expiration time for subresources such as images, CSS
|
||||
* stylesheets, or JS.
|
||||
*/
|
||||
static CacheExpirationTime GetSubresourceCacheExpirationTime(nsIRequest*,
|
||||
nsIURI*);
|
||||
|
||||
/**
|
||||
* Returns true if the request associated with the document should bypass the
|
||||
* shared sub resource cache.
|
||||
|
|
|
@ -488,37 +488,6 @@ void nsDOMNavigationTiming::NotifyLargestContentfulRenderForRootContentDocument(
|
|||
mNavigationStart + TimeDuration::FromMilliseconds(aRenderTime);
|
||||
}
|
||||
|
||||
void nsDOMNavigationTiming::NotifyDOMContentFlushedForRootContentDocument() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!mNavigationStart.IsNull());
|
||||
|
||||
if (!mDOMContentFlushed.IsNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mDOMContentFlushed = TimeStamp::Now();
|
||||
|
||||
if (profiler_thread_is_being_profiled_for_markers() ||
|
||||
PAGELOAD_LOG_ENABLED()) {
|
||||
TimeDuration elapsed = mDOMContentFlushed - mNavigationStart;
|
||||
nsPrintfCString marker(
|
||||
"DOMContentFlushed after %dms for URL %s, %s",
|
||||
int(elapsed.ToMilliseconds()),
|
||||
nsContentUtils::TruncatedURLForDisplay(mLoadedURI).get(),
|
||||
mDocShellHasBeenActiveSinceNavigationStart
|
||||
? "foreground tab"
|
||||
: "this tab was inactive some of the time between navigation start "
|
||||
"and DOMContentFlushed");
|
||||
PAGELOAD_LOG(("%s", marker.get()));
|
||||
PROFILER_MARKER_TEXT(
|
||||
"DOMContentFlushed", DOM,
|
||||
MarkerOptions(
|
||||
MarkerTiming::Interval(mNavigationStart, mDOMContentFlushed),
|
||||
MarkerInnerWindowIdFromDocShell(mDocShell)),
|
||||
marker);
|
||||
}
|
||||
}
|
||||
|
||||
void nsDOMNavigationTiming::NotifyDocShellStateChanged(
|
||||
DocShellState aDocShellState) {
|
||||
mDocShellHasBeenActiveSinceNavigationStart &=
|
||||
|
@ -595,7 +564,6 @@ nsDOMNavigationTiming::nsDOMNavigationTiming(nsDocShell* aDocShell,
|
|||
mNavigationStart(aOther->mNavigationStart),
|
||||
mNonBlankPaint(aOther->mNonBlankPaint),
|
||||
mContentfulComposite(aOther->mContentfulComposite),
|
||||
mDOMContentFlushed(aOther->mDOMContentFlushed),
|
||||
mBeforeUnloadStart(aOther->mBeforeUnloadStart),
|
||||
mUnloadStart(aOther->mUnloadStart),
|
||||
mUnloadEnd(aOther->mUnloadEnd),
|
||||
|
@ -629,7 +597,6 @@ void mozilla::ipc::IPDLParamTraits<nsDOMNavigationTiming*>::Write(
|
|||
WriteIPDLParam(aWriter, aActor, aParam->mNavigationStart);
|
||||
WriteIPDLParam(aWriter, aActor, aParam->mNonBlankPaint);
|
||||
WriteIPDLParam(aWriter, aActor, aParam->mContentfulComposite);
|
||||
WriteIPDLParam(aWriter, aActor, aParam->mDOMContentFlushed);
|
||||
WriteIPDLParam(aWriter, aActor, aParam->mBeforeUnloadStart);
|
||||
WriteIPDLParam(aWriter, aActor, aParam->mUnloadStart);
|
||||
WriteIPDLParam(aWriter, aActor, aParam->mUnloadEnd);
|
||||
|
@ -669,7 +636,6 @@ bool mozilla::ipc::IPDLParamTraits<nsDOMNavigationTiming*>::Read(
|
|||
!ReadIPDLParam(aReader, aActor, &timing->mNavigationStart) ||
|
||||
!ReadIPDLParam(aReader, aActor, &timing->mNonBlankPaint) ||
|
||||
!ReadIPDLParam(aReader, aActor, &timing->mContentfulComposite) ||
|
||||
!ReadIPDLParam(aReader, aActor, &timing->mDOMContentFlushed) ||
|
||||
!ReadIPDLParam(aReader, aActor, &timing->mBeforeUnloadStart) ||
|
||||
!ReadIPDLParam(aReader, aActor, &timing->mUnloadStart) ||
|
||||
!ReadIPDLParam(aReader, aActor, &timing->mUnloadEnd) ||
|
||||
|
|
|
@ -113,9 +113,6 @@ class nsDOMNavigationTiming final : public mozilla::RelativeTimeline {
|
|||
return TimeStampToDOM(mLargestContentfulRender);
|
||||
}
|
||||
DOMTimeMilliSec GetTimeToTTFI() const { return TimeStampToDOM(mTTFI); }
|
||||
DOMTimeMilliSec GetTimeToDOMContentFlushed() const {
|
||||
return TimeStampToDOM(mDOMContentFlushed);
|
||||
}
|
||||
|
||||
DOMHighResTimeStamp GetUnloadEventStartHighRes() {
|
||||
mozilla::TimeStamp stamp = GetUnloadEventStartTimeStamp();
|
||||
|
@ -182,7 +179,6 @@ class nsDOMNavigationTiming final : public mozilla::RelativeTimeline {
|
|||
const mozilla::TimeStamp& aCompositeEndTime);
|
||||
void NotifyLargestContentfulRenderForRootContentDocument(
|
||||
const DOMHighResTimeStamp& aRenderTime);
|
||||
void NotifyDOMContentFlushedForRootContentDocument();
|
||||
void NotifyDocShellStateChanged(DocShellState aDocShellState);
|
||||
|
||||
void MaybeAddLCPProfilerMarker(mozilla::MarkerInnerWindowId aInnerWindowID);
|
||||
|
@ -243,7 +239,6 @@ class nsDOMNavigationTiming final : public mozilla::RelativeTimeline {
|
|||
mozilla::TimeStamp mNonBlankPaint;
|
||||
mozilla::TimeStamp mContentfulComposite;
|
||||
mozilla::TimeStamp mLargestContentfulRender;
|
||||
mozilla::TimeStamp mDOMContentFlushed;
|
||||
|
||||
mozilla::TimeStamp mBeforeUnloadStart;
|
||||
mozilla::TimeStamp mUnloadStart;
|
||||
|
|
|
@ -356,17 +356,16 @@ class IsItemInRangeComparator {
|
|||
nsContentUtils::NodeIndexCache* mCache;
|
||||
};
|
||||
|
||||
bool nsINode::IsSelected(const uint32_t aStartOffset,
|
||||
const uint32_t aEndOffset) const {
|
||||
bool nsINode::IsSelected(const uint32_t aStartOffset, const uint32_t aEndOffset,
|
||||
SelectionNodeCache* aCache) const {
|
||||
MOZ_ASSERT(aStartOffset <= aEndOffset);
|
||||
|
||||
const nsINode* n = GetClosestCommonInclusiveAncestorForRangeInSelection(this);
|
||||
NS_ASSERTION(n || !IsMaybeSelected(),
|
||||
"A node without a common inclusive ancestor for a range in "
|
||||
"Selection is for sure not selected.");
|
||||
|
||||
// Collect the selection objects for potential ranges.
|
||||
nsTHashSet<Selection*> ancestorSelections;
|
||||
AutoTArray<Selection*, 1> ancestorSelections;
|
||||
for (; n; n = GetClosestCommonInclusiveAncestorForRangeInSelection(
|
||||
n->GetParentNode())) {
|
||||
const LinkedList<AbstractRange>* ranges =
|
||||
|
@ -380,13 +379,17 @@ bool nsINode::IsSelected(const uint32_t aStartOffset,
|
|||
// Looks like that IsInSelection() assert fails sometimes...
|
||||
if (range->IsInAnySelection()) {
|
||||
for (const WeakPtr<Selection>& selection : range->GetSelections()) {
|
||||
if (selection) {
|
||||
ancestorSelections.Insert(selection);
|
||||
if (selection && !ancestorSelections.Contains(selection)) {
|
||||
ancestorSelections.AppendElement(selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (aCache && aCache->MaybeCollectNodesAndCheckIfFullySelectedInAnyOf(
|
||||
this, ancestorSelections)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsContentUtils::NodeIndexCache cache;
|
||||
IsItemInRangeComparator comparator{*this, aStartOffset, aEndOffset, &cache};
|
||||
|
|
|
@ -103,6 +103,7 @@ class MutationObservers;
|
|||
template <typename T>
|
||||
class Optional;
|
||||
class OwningNodeOrString;
|
||||
class SelectionNodeCache;
|
||||
template <typename>
|
||||
class Sequence;
|
||||
class ShadowRoot;
|
||||
|
@ -1654,8 +1655,12 @@ class nsINode : public mozilla::dom::EventTarget {
|
|||
* for that nsRange. Collapsed ranges always counts as non-overlapping.
|
||||
*
|
||||
* @param aStartOffset has to be less or equal to aEndOffset.
|
||||
* @param aCache A cache which contains all fully selected nodes for each
|
||||
* selection. If present, this provides a fast path to check if
|
||||
* a node is fully selected.
|
||||
*/
|
||||
bool IsSelected(uint32_t aStartOffset, uint32_t aEndOffset) const;
|
||||
bool IsSelected(uint32_t aStartOffset, uint32_t aEndOffset,
|
||||
mozilla::dom::SelectionNodeCache* aCache = nullptr) const;
|
||||
|
||||
/**
|
||||
* Get the root element of the text editor associated with this node or the
|
||||
|
|
|
@ -105,7 +105,6 @@ nsImageLoadingContent::nsImageLoadingContent()
|
|||
mRequestGeneration(0),
|
||||
mLoadingEnabled(true),
|
||||
mLoading(false),
|
||||
mNewRequestsWillNeedAnimationReset(false),
|
||||
mUseUrgentStartForChannel(false),
|
||||
mLazyLoading(false),
|
||||
mStateChangerDepth(0),
|
||||
|
@ -971,7 +970,6 @@ nsImageLoadingContent::LoadImageWithChannel(nsIChannel* aChannel,
|
|||
if (NS_SUCCEEDED(rv)) {
|
||||
CloneScriptedRequests(req);
|
||||
TrackImage(req);
|
||||
ResetAnimationIfNeeded();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1167,7 +1165,6 @@ nsresult nsImageLoadingContent::LoadImage(nsIURI* aNewURI, bool aForce,
|
|||
|
||||
CloneScriptedRequests(req);
|
||||
TrackImage(req);
|
||||
ResetAnimationIfNeeded();
|
||||
|
||||
// Handle cases when we just ended up with a request but it's already done.
|
||||
// In that situation we have to synchronously switch that request to being
|
||||
|
@ -1483,10 +1480,6 @@ RefPtr<imgRequestProxy>& nsImageLoadingContent::PrepareCurrentRequest(
|
|||
// Get rid of anything that was there previously.
|
||||
ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
|
||||
|
||||
if (mNewRequestsWillNeedAnimationReset) {
|
||||
mCurrentRequestFlags |= REQUEST_NEEDS_ANIMATION_RESET;
|
||||
}
|
||||
|
||||
if (aImageLoadType == eImageLoadType_Imageset) {
|
||||
mCurrentRequestFlags |= REQUEST_IS_IMAGESET;
|
||||
}
|
||||
|
@ -1500,10 +1493,6 @@ RefPtr<imgRequestProxy>& nsImageLoadingContent::PreparePendingRequest(
|
|||
// Get rid of anything that was there previously.
|
||||
ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
|
||||
|
||||
if (mNewRequestsWillNeedAnimationReset) {
|
||||
mPendingRequestFlags |= REQUEST_NEEDS_ANIMATION_RESET;
|
||||
}
|
||||
|
||||
if (aImageLoadType == eImageLoadType_Imageset) {
|
||||
mPendingRequestFlags |= REQUEST_IS_IMAGESET;
|
||||
}
|
||||
|
@ -1562,7 +1551,6 @@ void nsImageLoadingContent::MakePendingRequestCurrent() {
|
|||
mPendingRequestFlags = 0;
|
||||
mCurrentRequestRegistered = mPendingRequestRegistered;
|
||||
mPendingRequestRegistered = false;
|
||||
ResetAnimationIfNeeded();
|
||||
}
|
||||
|
||||
void nsImageLoadingContent::ClearCurrentRequest(
|
||||
|
@ -1606,16 +1594,6 @@ void nsImageLoadingContent::ClearPendingRequest(
|
|||
mPendingRequestFlags = 0;
|
||||
}
|
||||
|
||||
void nsImageLoadingContent::ResetAnimationIfNeeded() {
|
||||
if (mCurrentRequest &&
|
||||
(mCurrentRequestFlags & REQUEST_NEEDS_ANIMATION_RESET)) {
|
||||
nsCOMPtr<imgIContainer> container;
|
||||
mCurrentRequest->GetImage(getter_AddRefs(container));
|
||||
if (container) container->ResetAnimation();
|
||||
mCurrentRequestFlags &= ~REQUEST_NEEDS_ANIMATION_RESET;
|
||||
}
|
||||
}
|
||||
|
||||
bool nsImageLoadingContent::HaveSize(imgIRequest* aImage) {
|
||||
// Handle the null case
|
||||
if (!aImage) return false;
|
||||
|
|
|
@ -424,13 +424,6 @@ class nsImageLoadingContent : public nsIImageLoadingContent {
|
|||
nsresult aReason,
|
||||
const Maybe<OnNonvisible>& aNonvisibleAction = Nothing());
|
||||
|
||||
/**
|
||||
* Reset animation of the current request if
|
||||
* |mNewRequestsWillNeedAnimationReset| was true when the request was
|
||||
* prepared.
|
||||
*/
|
||||
void ResetAnimationIfNeeded();
|
||||
|
||||
/**
|
||||
* Static helper method to tell us if we have the size of a request. The
|
||||
* image may be null.
|
||||
|
@ -467,13 +460,11 @@ class nsImageLoadingContent : public nsIImageLoadingContent {
|
|||
uint8_t mPendingRequestFlags = 0;
|
||||
|
||||
enum {
|
||||
// Set if the request needs ResetAnimation called on it.
|
||||
REQUEST_NEEDS_ANIMATION_RESET = 1 << 0,
|
||||
// Set if the request is currently tracked with the document.
|
||||
REQUEST_IS_TRACKED = 1 << 1,
|
||||
REQUEST_IS_TRACKED = 1 << 0,
|
||||
// Set if this is an imageset request, such as from <img srcset> or
|
||||
// <picture>
|
||||
REQUEST_IS_IMAGESET = 1 << 2,
|
||||
REQUEST_IS_IMAGESET = 1 << 1,
|
||||
};
|
||||
|
||||
// If the image was blocked or if there was an error loading, it's nice to
|
||||
|
@ -565,16 +556,6 @@ class nsImageLoadingContent : public nsIImageLoadingContent {
|
|||
*/
|
||||
bool mLoading : 1;
|
||||
|
||||
/**
|
||||
* A hack to get animations to reset, see bug 594771. On requests
|
||||
* that originate from setting .src, we mark them for needing their animation
|
||||
* reset when they are ready. mNewRequestsWillNeedAnimationReset is set to
|
||||
* true while preparing such requests (as a hack around needing to change an
|
||||
* interface), and the other two booleans store which of the current
|
||||
* and pending requests are of the sort that need their animation restarted.
|
||||
*/
|
||||
bool mNewRequestsWillNeedAnimationReset : 1;
|
||||
|
||||
/**
|
||||
* Flag to indicate whether the channel should be mark as urgent-start.
|
||||
* It should be set in *Element and passed to nsContentUtils::LoadImage.
|
||||
|
|
|
@ -78,63 +78,71 @@ nsLineBreaker::~nsLineBreaker() {
|
|||
"Should have Reset() before destruction!");
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool nsLineBreaker::ShouldCapitalize(uint32_t aChar, bool& aCapitalizeNext) {
|
||||
using mozilla::intl::GeneralCategory;
|
||||
auto category = UnicodeProperties::CharType(aChar);
|
||||
switch (category) {
|
||||
case GeneralCategory::Uppercase_Letter:
|
||||
case GeneralCategory::Lowercase_Letter:
|
||||
case GeneralCategory::Titlecase_Letter:
|
||||
case GeneralCategory::Modifier_Letter:
|
||||
case GeneralCategory::Other_Letter:
|
||||
case GeneralCategory::Decimal_Number:
|
||||
case GeneralCategory::Letter_Number:
|
||||
case GeneralCategory::Other_Number:
|
||||
if (aCapitalizeNext) {
|
||||
aCapitalizeNext = false;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case GeneralCategory::Space_Separator:
|
||||
case GeneralCategory::Line_Separator:
|
||||
case GeneralCategory::Paragraph_Separator:
|
||||
case GeneralCategory::Dash_Punctuation:
|
||||
case GeneralCategory::Initial_Punctuation:
|
||||
/* These punctuation categories are excluded, for examples like
|
||||
* "what colo[u]r" -> "What Colo[u]r?" (rather than "What Colo[U]R?")
|
||||
* and
|
||||
* "snake_case" -> "Snake_case" (to match word selection behavior)
|
||||
case GeneralCategory::Open_Punctuation:
|
||||
case GeneralCategory::Close_Punctuation:
|
||||
case GeneralCategory::Connector_Punctuation:
|
||||
*/
|
||||
aCapitalizeNext = true;
|
||||
break;
|
||||
case GeneralCategory::Final_Punctuation:
|
||||
/* Special-case: exclude Unicode single-close-quote/apostrophe,
|
||||
for examples like "Lowe’s" etc. */
|
||||
if (aChar != 0x2019) {
|
||||
aCapitalizeNext = true;
|
||||
}
|
||||
break;
|
||||
case GeneralCategory::Other_Punctuation:
|
||||
/* Special-case: exclude ASCII apostrophe, for "Lowe's" etc.,
|
||||
and MIDDLE DOT, for Catalan "l·l". */
|
||||
if (aChar != '\'' && aChar != 0x00B7) {
|
||||
aCapitalizeNext = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void SetupCapitalization(const char16_t* aWord, uint32_t aLength,
|
||||
bool* aCapitalization) {
|
||||
// Capitalize the first alphanumeric character after a space or punctuation.
|
||||
using mozilla::intl::GeneralCategory;
|
||||
bool capitalizeNextChar = true;
|
||||
for (uint32_t i = 0; i < aLength; ++i) {
|
||||
uint32_t ch = aWord[i];
|
||||
if (i + 1 < aLength && NS_IS_SURROGATE_PAIR(ch, aWord[i + 1])) {
|
||||
ch = SURROGATE_TO_UCS4(ch, aWord[i + 1]);
|
||||
}
|
||||
auto category = UnicodeProperties::CharType(ch);
|
||||
switch (category) {
|
||||
case GeneralCategory::Uppercase_Letter:
|
||||
case GeneralCategory::Lowercase_Letter:
|
||||
case GeneralCategory::Titlecase_Letter:
|
||||
case GeneralCategory::Modifier_Letter:
|
||||
case GeneralCategory::Other_Letter:
|
||||
case GeneralCategory::Decimal_Number:
|
||||
case GeneralCategory::Letter_Number:
|
||||
case GeneralCategory::Other_Number:
|
||||
if (capitalizeNextChar) {
|
||||
aCapitalization[i] = true;
|
||||
capitalizeNextChar = false;
|
||||
}
|
||||
break;
|
||||
case GeneralCategory::Space_Separator:
|
||||
case GeneralCategory::Line_Separator:
|
||||
case GeneralCategory::Paragraph_Separator:
|
||||
case GeneralCategory::Dash_Punctuation:
|
||||
case GeneralCategory::Initial_Punctuation:
|
||||
/* These punctuation categories are excluded, for examples like
|
||||
* "what colo[u]r" -> "What Colo[u]r?" (rather than "What Colo[U]R?")
|
||||
* and
|
||||
* "snake_case" -> "Snake_case" (to match word selection behavior)
|
||||
case GeneralCategory::Open_Punctuation:
|
||||
case GeneralCategory::Close_Punctuation:
|
||||
case GeneralCategory::Connector_Punctuation:
|
||||
*/
|
||||
capitalizeNextChar = true;
|
||||
break;
|
||||
case GeneralCategory::Final_Punctuation:
|
||||
/* Special-case: exclude Unicode single-close-quote/apostrophe,
|
||||
for examples like "Lowe’s" etc. */
|
||||
if (ch != 0x2019) {
|
||||
capitalizeNextChar = true;
|
||||
}
|
||||
break;
|
||||
case GeneralCategory::Other_Punctuation:
|
||||
/* Special-case: exclude ASCII apostrophe, for "Lowe's" etc.,
|
||||
and MIDDLE DOT, for Catalan "l·l". */
|
||||
if (ch != '\'' && ch != 0x00B7) {
|
||||
capitalizeNextChar = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
aCapitalization[i] =
|
||||
nsLineBreaker::ShouldCapitalize(ch, capitalizeNextChar);
|
||||
|
||||
if (!IS_IN_BMP(ch)) {
|
||||
++i;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue