Update On Tue Sep 3 20:50:13 CEST 2024
This commit is contained in:
parent
8b9e162fce
commit
fc2b848c21
816 changed files with 29842 additions and 5386 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -2323,6 +2323,7 @@ dependencies = [
|
|||
"kvstore",
|
||||
"l10nregistry",
|
||||
"l10nregistry-ffi",
|
||||
"libz-rs-sys",
|
||||
"lmdb-rkv-sys",
|
||||
"localization-ffi",
|
||||
"log",
|
||||
|
@ -3407,6 +3408,14 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-rs-sys"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/memorysafety/zlib-rs?rev=4aa430ccb77537d0d60dab8db993ca51bb1194c5#4aa430ccb77537d0d60dab8db993ca51bb1194c5"
|
||||
dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "line-wrap"
|
||||
version = "0.1.1"
|
||||
|
@ -7240,3 +7249,8 @@ dependencies = [
|
|||
"memchr",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/memorysafety/zlib-rs?rev=4aa430ccb77537d0d60dab8db993ca51bb1194c5#4aa430ccb77537d0d60dab8db993ca51bb1194c5"
|
||||
|
|
|
@ -22,12 +22,7 @@
|
|||
<browser id="sidebar" autoscroll="false" disablehistory="true" disablefullscreen="true" tooltip="aHTMLTooltip"/>
|
||||
</vbox>
|
||||
<splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" resizebefore="sibling" resizeafter="none" hidden="true"/>
|
||||
<vbox id="appcontent" flex="1">
|
||||
<!-- gNotificationBox will be added here lazily. -->
|
||||
<tabbox id="tabbrowser-tabbox"
|
||||
flex="1" tabcontainer="tabbrowser-tabs">
|
||||
<tabpanels id="tabbrowser-tabpanels"
|
||||
flex="1" selectedIndex="0"/>
|
||||
</tabbox>
|
||||
</vbox>
|
||||
<tabbox id="tabbrowser-tabbox" flex="1" tabcontainer="tabbrowser-tabs">
|
||||
<tabpanels id="tabbrowser-tabpanels" flex="1" selectedIndex="0"/>
|
||||
</tabbox>
|
||||
</hbox>
|
||||
|
|
|
@ -1108,12 +1108,13 @@ var PlacesToolbarHelper = {
|
|||
);
|
||||
|
||||
if (toolbar.id == "PersonalToolbar") {
|
||||
if (!toolbar.hasAttribute("initialized")) {
|
||||
toolbar.setAttribute("initialized", "true");
|
||||
}
|
||||
// We just created a new view, thus we must check again the empty toolbar
|
||||
// message, regardless of "initialized".
|
||||
BookmarkingUI.updateEmptyToolbarMessage().catch(console.error);
|
||||
BookmarkingUI.updateEmptyToolbarMessage()
|
||||
.finally(() => {
|
||||
toolbar.toggleAttribute("initialized", true);
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1530,8 +1531,7 @@ var BookmarkingUI = {
|
|||
* We hide it in customize mode, unless there's nothing on the toolbar.
|
||||
*/
|
||||
async updateEmptyToolbarMessage() {
|
||||
let checkNumBookmarksOnToolbar = false;
|
||||
let hasVisibleChildren = (() => {
|
||||
let { initialHiddenState, checkHasBookmarks } = (() => {
|
||||
// Do we have visible kids?
|
||||
if (
|
||||
this.toolbar.querySelector(
|
||||
|
@ -1541,25 +1541,29 @@ var BookmarkingUI = {
|
|||
:scope > toolbaritem:not([hidden], #personal-bookmarks)`
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
return { initialHiddenState: true, checkHasBookmarks: false };
|
||||
}
|
||||
if (!this.toolbar.hasAttribute("initialized") && !this._isCustomizing) {
|
||||
// If the bookmarks are here but it's early in startup, show the
|
||||
// message. It'll get made visibility: hidden early in startup anyway -
|
||||
// it's just to ensure the toolbar has height.
|
||||
return false;
|
||||
|
||||
if (this._isCustomizing) {
|
||||
return { initialHiddenState: true, checkHasBookmarks: false };
|
||||
}
|
||||
// Hmm, apparently not. Check for bookmarks or customize mode:
|
||||
|
||||
// If bookmarks have been moved out of the toolbar, we show the message.
|
||||
let bookmarksToolbarItemsPlacement =
|
||||
CustomizableUI.getPlacementOfWidget("personal-bookmarks");
|
||||
let bookmarksItemInToolbar =
|
||||
bookmarksToolbarItemsPlacement?.area == CustomizableUI.AREA_BOOKMARKS;
|
||||
if (!bookmarksItemInToolbar) {
|
||||
return false;
|
||||
return { initialHiddenState: false, checkHasBookmarks: false };
|
||||
}
|
||||
if (this._isCustomizing) {
|
||||
return true;
|
||||
|
||||
if (!this.toolbar.hasAttribute("initialized")) {
|
||||
// If the toolbar has not been initialized yet, unhide the message, it
|
||||
// will be made 0-width and visibility: hidden anyway, to keep the
|
||||
// toolbar height stable.
|
||||
return { initialHiddenState: false, checkHasBookmarks: true };
|
||||
}
|
||||
|
||||
// Check visible bookmark nodes.
|
||||
if (
|
||||
this.toolbar.querySelector(
|
||||
|
@ -1567,19 +1571,16 @@ var BookmarkingUI = {
|
|||
#PlacesToolbarItems > toolbarbutton`
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
return { initialHiddenState: true, checkHasBookmarks: false };
|
||||
}
|
||||
checkNumBookmarksOnToolbar = true;
|
||||
return false;
|
||||
return { initialHiddenState: true, checkHasBookmarks: true };
|
||||
})();
|
||||
|
||||
if (checkNumBookmarksOnToolbar) {
|
||||
hasVisibleChildren = !(await PlacesToolbarHelper.getIsEmpty());
|
||||
}
|
||||
|
||||
let emptyMsg = document.getElementById("personal-toolbar-empty");
|
||||
emptyMsg.hidden = hasVisibleChildren;
|
||||
emptyMsg.toggleAttribute("nowidth", !hasVisibleChildren);
|
||||
emptyMsg.hidden = initialHiddenState;
|
||||
if (checkHasBookmarks) {
|
||||
emptyMsg.hidden = !(await PlacesToolbarHelper.getIsEmpty());
|
||||
}
|
||||
},
|
||||
|
||||
openLibraryIfLinkClicked(event) {
|
||||
|
|
|
@ -97,8 +97,9 @@ add_task(async function () {
|
|||
AppConstants.DEBUG &&
|
||||
// In the content area
|
||||
r.y1 >=
|
||||
document.getElementById("appcontent").getBoundingClientRect()
|
||||
.top,
|
||||
document
|
||||
.getElementById("tabbrowser-tabbox")
|
||||
.getBoundingClientRect().top,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -82,7 +82,8 @@ add_task(async function () {
|
|||
condition: r =>
|
||||
// In the content area
|
||||
r.y1 >=
|
||||
document.getElementById("appcontent").getBoundingClientRect().top,
|
||||
document.getElementById("tabbrowser-tabbox").getBoundingClientRect()
|
||||
.top,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -115,8 +115,9 @@ add_task(async function () {
|
|||
AppConstants.DEBUG &&
|
||||
// In the content area
|
||||
r.y1 >=
|
||||
document.getElementById("appcontent").getBoundingClientRect()
|
||||
.top,
|
||||
document
|
||||
.getElementById("tabbrowser-tabbox")
|
||||
.getBoundingClientRect().top,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -7,14 +7,14 @@ const EXPECTED_START_ORDINALS = [
|
|||
["sidebar-main", 1],
|
||||
["sidebar-box", 2],
|
||||
["sidebar-splitter", 3],
|
||||
["appcontent", 4],
|
||||
["tabbrowser-tabbox", 4],
|
||||
];
|
||||
|
||||
const EXPECTED_END_ORDINALS = [
|
||||
["sidebar-main", 5],
|
||||
["sidebar-box", 4],
|
||||
["sidebar-splitter", 3],
|
||||
["appcontent", 2],
|
||||
["tabbrowser-tabbox", 2],
|
||||
];
|
||||
|
||||
function getBrowserChildrenWithOrdinals() {
|
||||
|
|
|
@ -540,28 +540,31 @@ async function createAndRemoveDefaultFolder() {
|
|||
}
|
||||
|
||||
async function showLibraryColumn(library, columnName) {
|
||||
await SimpleTest.promiseFocus(library);
|
||||
|
||||
const viewMenu = library.document.getElementById("viewMenu");
|
||||
const viewMenuPopup = library.document.getElementById("viewMenuPopup");
|
||||
const onViewMenuPopup = new Promise(resolve => {
|
||||
viewMenuPopup.addEventListener("popupshown", () => resolve(), {
|
||||
once: true,
|
||||
});
|
||||
info("await viewMenuPopup");
|
||||
await new Promise(resolve => {
|
||||
library.document
|
||||
.getElementById("viewMenuPopup")
|
||||
.addEventListener("popupshown", resolve, {
|
||||
once: true,
|
||||
});
|
||||
viewMenu.click();
|
||||
});
|
||||
EventUtils.synthesizeMouseAtCenter(viewMenu, {}, library);
|
||||
await onViewMenuPopup;
|
||||
|
||||
const viewColumns = library.document.getElementById("viewColumns");
|
||||
const viewColumnsPopup = viewColumns.querySelector("menupopup");
|
||||
const onViewColumnsPopup = new Promise(resolve => {
|
||||
viewColumnsPopup.addEventListener("popupshown", () => resolve(), {
|
||||
once: true,
|
||||
});
|
||||
info("await viewColumnsPopup");
|
||||
await new Promise(resolve => {
|
||||
viewColumns
|
||||
.querySelector("menupopup")
|
||||
.addEventListener("popupshown", () => resolve(), {
|
||||
once: true,
|
||||
});
|
||||
viewColumns.click();
|
||||
});
|
||||
EventUtils.synthesizeMouseAtCenter(viewColumns, {}, library);
|
||||
await onViewColumnsPopup;
|
||||
|
||||
const columnMenu = library.document.getElementById(`menucol_${columnName}`);
|
||||
EventUtils.synthesizeMouseAtCenter(columnMenu, {}, library);
|
||||
library.document.getElementById(`menucol_${columnName}`).click();
|
||||
}
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
|
|
|
@ -103,7 +103,10 @@ add_task(async function test_search_icon() {
|
|||
await SpecialPowers.spawn(tab, [expectedIconURL], async function (iconURL) {
|
||||
let computedStyle = content.window.getComputedStyle(content.document.body);
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => computedStyle.getPropertyValue("--newtab-search-icon") != "null",
|
||||
() =>
|
||||
computedStyle
|
||||
.getPropertyValue("--newtab-search-icon")
|
||||
.startsWith("url"),
|
||||
"Search Icon should get set."
|
||||
);
|
||||
|
||||
|
|
|
@ -366,9 +366,8 @@ add_task(async function test_createRegionWithKeyboard() {
|
|||
} else {
|
||||
const sidebar = document.querySelector("sidebar-main");
|
||||
const sidebarWidth = sidebar.offsetWidth;
|
||||
// Add 1 to account for #appcontent border
|
||||
window100X =
|
||||
(100 + window.mozInnerScreenX + sidebarWidth + 1) *
|
||||
(100 + window.mozInnerScreenX + sidebarWidth) *
|
||||
window.devicePixelRatio;
|
||||
}
|
||||
const contentTop =
|
||||
|
@ -458,9 +457,8 @@ add_task(async function test_createRegionWithKeyboardWithShift() {
|
|||
} else {
|
||||
const sidebar = document.querySelector("sidebar-main");
|
||||
const sidebarWidth = sidebar.offsetWidth;
|
||||
// Add 1 to account for #appcontent border
|
||||
window100X =
|
||||
(100 + window.mozInnerScreenX + sidebarWidth + 1) *
|
||||
(100 + window.mozInnerScreenX + sidebarWidth) *
|
||||
window.devicePixelRatio;
|
||||
}
|
||||
const contentTop =
|
||||
|
|
|
@ -976,6 +976,9 @@ export class SearchOneOffs {
|
|||
}
|
||||
|
||||
if (!this.textbox.value) {
|
||||
if (event.shiftKey) {
|
||||
this.popup.openSearchForm(event, engine);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Select the clicked button so that consumers can easily tell which
|
||||
|
@ -1014,13 +1017,14 @@ export class SearchOneOffs {
|
|||
}
|
||||
|
||||
if (target.classList.contains("search-one-offs-context-open-in-new-tab")) {
|
||||
if (!this.textbox.value) {
|
||||
return;
|
||||
}
|
||||
// Select the context-clicked button so that consumers can easily
|
||||
// tell which button was acted on.
|
||||
this.selectedButton = target.closest("menupopup")._triggerButton;
|
||||
this.handleSearchCommand(event, this.selectedButton.engine, true);
|
||||
if (this.textbox.value) {
|
||||
this.handleSearchCommand(event, this.selectedButton.engine, true);
|
||||
} else {
|
||||
this.popup.openSearchForm(event, this.selectedButton.engine, true);
|
||||
}
|
||||
}
|
||||
|
||||
const isPrivateButton = target.classList.contains(
|
||||
|
|
|
@ -69,11 +69,11 @@
|
|||
if (!engine) {
|
||||
return;
|
||||
}
|
||||
// At this point, the click must have happened on the header.
|
||||
if (!this.searchbar.value) {
|
||||
return;
|
||||
if (this.searchbar.value) {
|
||||
this.oneOffButtons.handleSearchCommand(event, engine);
|
||||
} else if (event.shiftKey) {
|
||||
this.openSearchForm(event, engine);
|
||||
}
|
||||
this.oneOffButtons.handleSearchCommand(event, engine);
|
||||
});
|
||||
|
||||
this._bundle = null;
|
||||
|
@ -262,6 +262,14 @@
|
|||
this.searchbar.handleSearchCommandWhere(event, engine, where, params);
|
||||
}
|
||||
|
||||
openSearchForm(event, engine, forceNewTab = false) {
|
||||
let { where, params } = this.oneOffButtons._whereToOpen(
|
||||
event,
|
||||
forceNewTab
|
||||
);
|
||||
this.searchbar.openSearchFormWhere(event, engine, where, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes DOM events for the popup to the _on_<event type> methods.
|
||||
*
|
||||
|
|
|
@ -306,52 +306,14 @@
|
|||
}
|
||||
|
||||
handleSearchCommand(aEvent, aEngine, aForceNewTab) {
|
||||
let where = "current";
|
||||
let params;
|
||||
const newTabPref = Services.prefs.getBoolPref("browser.search.openintab");
|
||||
|
||||
// Open ctrl/cmd clicks on one-off buttons in a new background tab.
|
||||
if (
|
||||
aEvent &&
|
||||
aEvent.originalTarget.classList.contains("search-go-button")
|
||||
aEvent.originalTarget.classList.contains("search-go-button") &&
|
||||
aEvent.button == 2
|
||||
) {
|
||||
if (aEvent.button == 2) {
|
||||
return;
|
||||
}
|
||||
where = lazy.BrowserUtils.whereToOpenLink(aEvent, false, true);
|
||||
if (
|
||||
newTabPref &&
|
||||
!aEvent.altKey &&
|
||||
!aEvent.getModifierState("AltGraph") &&
|
||||
where == "current" &&
|
||||
!gBrowser.selectedTab.isEmpty
|
||||
) {
|
||||
where = "tab";
|
||||
}
|
||||
} else if (aForceNewTab) {
|
||||
where = "tab";
|
||||
if (Services.prefs.getBoolPref("browser.tabs.loadInBackground")) {
|
||||
where += "-background";
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
(KeyboardEvent.isInstance(aEvent) &&
|
||||
(aEvent.altKey || aEvent.getModifierState("AltGraph"))) ^
|
||||
newTabPref &&
|
||||
!gBrowser.selectedTab.isEmpty
|
||||
) {
|
||||
where = "tab";
|
||||
}
|
||||
if (
|
||||
MouseEvent.isInstance(aEvent) &&
|
||||
(aEvent.button == 1 || aEvent.getModifierState("Accel"))
|
||||
) {
|
||||
where = "tab";
|
||||
params = {
|
||||
inBackground: true,
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
let { where, params } = this._whereToOpen(aEvent, aForceNewTab);
|
||||
this.handleSearchCommandWhere(aEvent, aEngine, where, params);
|
||||
}
|
||||
|
||||
|
@ -449,6 +411,97 @@
|
|||
openTrustedLinkIn(submission.uri.spec, aWhere, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information on where a search results page should be loaded: in the
|
||||
* current tab or a new tab.
|
||||
*
|
||||
* @param {event} aEvent
|
||||
* The event that triggered the page load.
|
||||
* @param {boolean} [aForceNewTab]
|
||||
* True to force the load in a new tab.
|
||||
* @returns {object} An object { where, params }. `where` is a string:
|
||||
* "current" or "tab". `params` is an object further describing how
|
||||
* the page should be loaded.
|
||||
*/
|
||||
_whereToOpen(aEvent, aForceNewTab = false) {
|
||||
let where = "current";
|
||||
let params = {};
|
||||
const newTabPref = Services.prefs.getBoolPref("browser.search.openintab");
|
||||
|
||||
// Open ctrl/cmd clicks on one-off buttons in a new background tab.
|
||||
if (aEvent?.originalTarget.classList.contains("search-go-button")) {
|
||||
where = lazy.BrowserUtils.whereToOpenLink(aEvent, false, true);
|
||||
if (
|
||||
newTabPref &&
|
||||
!aEvent.altKey &&
|
||||
!aEvent.getModifierState("AltGraph") &&
|
||||
where == "current" &&
|
||||
!gBrowser.selectedTab.isEmpty
|
||||
) {
|
||||
where = "tab";
|
||||
}
|
||||
} else if (aForceNewTab) {
|
||||
where = "tab";
|
||||
if (Services.prefs.getBoolPref("browser.tabs.loadInBackground")) {
|
||||
params = {
|
||||
inBackground: true,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
(KeyboardEvent.isInstance(aEvent) &&
|
||||
(aEvent.altKey || aEvent.getModifierState("AltGraph"))) ^
|
||||
newTabPref &&
|
||||
!gBrowser.selectedTab.isEmpty
|
||||
) {
|
||||
where = "tab";
|
||||
}
|
||||
if (
|
||||
MouseEvent.isInstance(aEvent) &&
|
||||
(aEvent.button == 1 || aEvent.getModifierState("Accel"))
|
||||
) {
|
||||
where = "tab";
|
||||
params = {
|
||||
inBackground: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { where, params };
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the search form of the provided engine or the current engine
|
||||
* if no engine was provided.
|
||||
*
|
||||
* @param {event} aEvent
|
||||
* The event causing the searchForm to be opened.
|
||||
* @param {nsISearchEngine} [aEngine]
|
||||
* The search engine or undefined to use the current engine.
|
||||
* @param {string} where
|
||||
* Where the search form should be opened.
|
||||
* @param {object} [params]
|
||||
* Parameters for URILoadingHelper.openLinkIn.
|
||||
*/
|
||||
openSearchFormWhere(aEvent, aEngine, where, params = {}) {
|
||||
let engine = aEngine || this.currentEngine;
|
||||
let searchForm = engine.wrappedJSObject.searchForm;
|
||||
|
||||
if (where === "tab" && !!params.inBackground) {
|
||||
// Keep the focus in the search bar.
|
||||
params.avoidBrowserFocus = true;
|
||||
} else if (
|
||||
where !== "window" &&
|
||||
aEvent.keyCode === KeyEvent.DOM_VK_RETURN
|
||||
) {
|
||||
// Move the focus to the selected browser when keyup the Enter.
|
||||
params.avoidBrowserFocus = true;
|
||||
this._needBrowserFocusAtEnterKeyUp = true;
|
||||
}
|
||||
|
||||
openTrustedLinkIn(searchForm, where, params);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.destroy();
|
||||
while (this.firstChild) {
|
||||
|
@ -807,6 +860,11 @@
|
|||
)
|
||||
)
|
||||
) {
|
||||
if (event.shiftKey) {
|
||||
let engine = this.textbox.selectedButton?.engine;
|
||||
let { where, params } = this._whereToOpen(event);
|
||||
this.openSearchFormWhere(event, engine, where, params);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Otherwise, "call super": do what the autocomplete binding's
|
||||
|
|
|
@ -4,6 +4,8 @@ ChromeUtils.defineESModuleGetters(this, {
|
|||
"resource://testing-common/FormHistoryTestUtils.sys.mjs",
|
||||
});
|
||||
|
||||
const SEARCH_FORM = "http://mochi.test:8888/";
|
||||
|
||||
function expectedURL(aSearchTerms) {
|
||||
const ENGINE_HTML_BASE =
|
||||
"http://mochi.test:8888/browser/browser/components/search/test/browser/test.html";
|
||||
|
@ -40,7 +42,7 @@ function simulateClick(aEvent, aTarget) {
|
|||
|
||||
// modified from toolkit/components/satchel/test/test_form_autocomplete.html
|
||||
function checkMenuEntries(expectedValues) {
|
||||
let actualValues = getMenuEntries();
|
||||
let actualValues = getMenuEntries().toSorted((a, b) => a.localeCompare(b));
|
||||
is(
|
||||
actualValues.length,
|
||||
expectedValues.length,
|
||||
|
@ -61,11 +63,20 @@ function getMenuEntries() {
|
|||
|
||||
var searchBar;
|
||||
var searchButton;
|
||||
var searchEntries = ["test"];
|
||||
var searchEntries = [
|
||||
"testAltReturn",
|
||||
"testAltGrReturn",
|
||||
"testLeftClick",
|
||||
"testMiddleClick",
|
||||
"testReturn",
|
||||
"testShiftMiddleClick",
|
||||
].sort((a, b) => a.localeCompare(b));
|
||||
var preSelectedBrowser;
|
||||
var preTabNo;
|
||||
|
||||
async function prepareTest() {
|
||||
async function prepareTest(searchBarValue = "test") {
|
||||
searchBar.value = searchBarValue;
|
||||
searchBar.updateGoButtonVisibility();
|
||||
preSelectedBrowser = gBrowser.selectedBrowser;
|
||||
preTabNo = gBrowser.tabs.length;
|
||||
|
||||
|
@ -92,7 +103,6 @@ add_setup(async function () {
|
|||
});
|
||||
|
||||
searchBar = BrowserSearch.searchBar;
|
||||
searchBar.value = "test";
|
||||
searchButton = searchBar.querySelector(".search-go-button");
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
|
@ -114,7 +124,7 @@ add_setup(async function () {
|
|||
});
|
||||
|
||||
add_task(async function testReturn() {
|
||||
await prepareTest();
|
||||
await prepareTest("testReturn");
|
||||
EventUtils.synthesizeKey("KEY_Enter");
|
||||
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
|
||||
|
||||
|
@ -126,8 +136,32 @@ add_task(async function testReturn() {
|
|||
);
|
||||
});
|
||||
|
||||
add_task(async function testReturnEmpty() {
|
||||
await prepareTest("");
|
||||
EventUtils.synthesizeKey("KEY_Enter");
|
||||
await TestUtils.waitForTick();
|
||||
|
||||
is(
|
||||
gBrowser.selectedBrowser.ownerDocument.activeElement,
|
||||
searchBar.textbox,
|
||||
"Focus stays in the searchbar"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function testShiftReturn() {
|
||||
await prepareTest("");
|
||||
let promise = BrowserTestUtils.browserLoaded(
|
||||
gBrowser.selectedBrowser,
|
||||
false,
|
||||
SEARCH_FORM
|
||||
);
|
||||
EventUtils.synthesizeKey("KEY_Enter", { shiftKey: true });
|
||||
await promise;
|
||||
info("testShiftReturn opened the search form page.");
|
||||
});
|
||||
|
||||
add_task(async function testAltReturn() {
|
||||
await prepareTest();
|
||||
await prepareTest("testAltReturn");
|
||||
await BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
|
||||
EventUtils.synthesizeKey("KEY_Enter", { altKey: true });
|
||||
});
|
||||
|
@ -140,8 +174,19 @@ add_task(async function testAltReturn() {
|
|||
);
|
||||
});
|
||||
|
||||
add_task(async function testAltReturnEmpty() {
|
||||
await prepareTest("");
|
||||
EventUtils.synthesizeKey("KEY_Enter", { altKey: true });
|
||||
await TestUtils.waitForTick();
|
||||
is(
|
||||
gBrowser.selectedBrowser.ownerDocument.activeElement,
|
||||
searchBar.textbox,
|
||||
"Focus stays in the searchbar"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function testAltGrReturn() {
|
||||
await prepareTest();
|
||||
await prepareTest("testAltGrReturn");
|
||||
await BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
|
||||
EventUtils.synthesizeKey("KEY_Enter", { altGraphKey: true });
|
||||
});
|
||||
|
@ -154,24 +199,37 @@ add_task(async function testAltGrReturn() {
|
|||
);
|
||||
});
|
||||
|
||||
// Shift key has no effect for now, so skip it
|
||||
add_task(async function testShiftAltReturn() {
|
||||
/*
|
||||
yield* prepareTest();
|
||||
add_task(async function testAltGrReturnEmpty() {
|
||||
await prepareTest("");
|
||||
|
||||
let url = expectedURL(searchBar.value);
|
||||
EventUtils.synthesizeKey("KEY_Enter", { altGraphKey: true });
|
||||
await TestUtils.waitForTick();
|
||||
|
||||
let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, url);
|
||||
EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true, altKey: true });
|
||||
yield newTabPromise;
|
||||
is(
|
||||
gBrowser.selectedBrowser.ownerDocument.activeElement,
|
||||
searchBar.textbox,
|
||||
"Focus stays in the searchbar"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function testShiftAltReturnEmpty() {
|
||||
await prepareTest("");
|
||||
|
||||
let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, SEARCH_FORM);
|
||||
EventUtils.synthesizeKey("KEY_Enter", { shiftKey: true, altKey: true });
|
||||
await newTabPromise;
|
||||
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
|
||||
|
||||
is(gBrowser.tabs.length, preTabNo + 1, "Shift+Alt+Return key added new tab");
|
||||
is(gBrowser.currentURI.spec, url, "testShiftAltReturn opened correct search page");
|
||||
*/
|
||||
is(
|
||||
gBrowser.currentURI.spec,
|
||||
SEARCH_FORM,
|
||||
"testShiftAltReturn opened the search form page"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function testLeftClick() {
|
||||
await prepareTest();
|
||||
await prepareTest("testLeftClick");
|
||||
simulateClick({ button: 0 }, searchButton);
|
||||
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
|
||||
is(gBrowser.tabs.length, preTabNo, "LeftClick did not open new tab");
|
||||
|
@ -183,7 +241,7 @@ add_task(async function testLeftClick() {
|
|||
});
|
||||
|
||||
add_task(async function testMiddleClick() {
|
||||
await prepareTest();
|
||||
await prepareTest("testMiddleClick");
|
||||
await BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
|
||||
simulateClick({ button: 1 }, searchButton);
|
||||
});
|
||||
|
@ -196,7 +254,7 @@ add_task(async function testMiddleClick() {
|
|||
});
|
||||
|
||||
add_task(async function testShiftMiddleClick() {
|
||||
await prepareTest();
|
||||
await prepareTest("testShiftMiddleClick");
|
||||
|
||||
let url = expectedURL(searchBar.value);
|
||||
|
||||
|
|
|
@ -53,28 +53,9 @@ add_setup(async function () {
|
|||
});
|
||||
|
||||
add_task(async function nonEmptySearch() {
|
||||
searchBar.focus();
|
||||
searchBar.value = SEARCH_WORD;
|
||||
await openPopup(SEARCH_WORD);
|
||||
|
||||
let shownPromise = promiseEvent(searchPopup, "popupshown");
|
||||
let builtPromise = promiseEvent(oneOffInstance, "rebuild");
|
||||
info("Opening search panel");
|
||||
EventUtils.synthesizeMouseAtCenter(searchIcon, {}, win);
|
||||
await Promise.all([shownPromise, builtPromise]);
|
||||
|
||||
// Get the one-off button for the test engine.
|
||||
let oneOffButton;
|
||||
for (let node of oneOffButtons.children) {
|
||||
if (node.engine && node.engine.name == TEST_ENGINE_NAME) {
|
||||
oneOffButton = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert.notEqual(
|
||||
oneOffButton,
|
||||
undefined,
|
||||
"One-off for test engine should exist"
|
||||
);
|
||||
let oneOffButton = findOneOff(TEST_ENGINE_NAME);
|
||||
|
||||
let promise = BrowserTestUtils.browserLoaded(
|
||||
win.gBrowser.selectedBrowser,
|
||||
|
@ -87,29 +68,8 @@ add_task(async function nonEmptySearch() {
|
|||
});
|
||||
|
||||
add_task(async function emptySearch() {
|
||||
searchBar.focus();
|
||||
searchBar.value = "";
|
||||
|
||||
let shownPromise = promiseEvent(searchPopup, "popupshown");
|
||||
let builtPromise = promiseEvent(oneOffInstance, "rebuild");
|
||||
info("Opening search panel");
|
||||
EventUtils.synthesizeMouseAtCenter(searchIcon, {}, win);
|
||||
await Promise.all([shownPromise, builtPromise]);
|
||||
|
||||
// Get the one-off button for the test engine.
|
||||
let oneOffButton;
|
||||
for (let node of oneOffButtons.children) {
|
||||
if (node.engine && node.engine.name == TEST_ENGINE_NAME) {
|
||||
oneOffButton = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert.notEqual(
|
||||
oneOffButton,
|
||||
undefined,
|
||||
"One-off for test engine should exist"
|
||||
);
|
||||
|
||||
await openPopup("");
|
||||
let oneOffButton = findOneOff(TEST_ENGINE_NAME);
|
||||
EventUtils.synthesizeMouseAtCenter(oneOffButton, {}, win);
|
||||
|
||||
await TestUtils.waitForTick();
|
||||
|
@ -119,3 +79,44 @@ add_task(async function emptySearch() {
|
|||
"Focus stays in the searchbar"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function emptySearchShift() {
|
||||
await openPopup("");
|
||||
let oneOffButton = findOneOff(TEST_ENGINE_NAME);
|
||||
|
||||
let promise = BrowserTestUtils.browserLoaded(
|
||||
win.gBrowser.selectedBrowser,
|
||||
false,
|
||||
`http://mochi.test:8888/`
|
||||
);
|
||||
EventUtils.synthesizeMouseAtCenter(oneOffButton, { shiftKey: true }, win);
|
||||
await promise;
|
||||
info("Opened search form page");
|
||||
});
|
||||
|
||||
function findOneOff(engineName) {
|
||||
let oneOffChildren = [...oneOffButtons.children];
|
||||
let oneOffButton = oneOffChildren.find(
|
||||
node => node.engine?.name == engineName
|
||||
);
|
||||
Assert.notEqual(
|
||||
oneOffButton,
|
||||
undefined,
|
||||
`One-off for ${engineName} should exist`
|
||||
);
|
||||
return oneOffButton;
|
||||
}
|
||||
|
||||
async function openPopup(searchBarValue) {
|
||||
searchBar.focus();
|
||||
searchBar.value = searchBarValue;
|
||||
if (searchBar.textbox.popupOpen) {
|
||||
info("searchPanel is already open");
|
||||
return;
|
||||
}
|
||||
let shownPromise = promiseEvent(searchPopup, "popupshown");
|
||||
let builtPromise = promiseEvent(oneOffInstance, "rebuild");
|
||||
info("Opening search panel");
|
||||
EventUtils.synthesizeMouseAtCenter(searchIcon, {}, win);
|
||||
await Promise.all([shownPromise, builtPromise]);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,9 @@ const TEST_ENGINE_BASENAME = "testEngine.xml";
|
|||
|
||||
let searchbar;
|
||||
let searchIcon;
|
||||
let searchPopup;
|
||||
let oneOffInstance;
|
||||
let oneOffButtons;
|
||||
|
||||
add_setup(async function () {
|
||||
searchbar = await gCUITestUtils.addSearchBar();
|
||||
|
@ -12,6 +15,9 @@ add_setup(async function () {
|
|||
gCUITestUtils.removeSearchBar();
|
||||
});
|
||||
searchIcon = searchbar.querySelector(".searchbar-search-button");
|
||||
searchPopup = document.getElementById("PopupSearchAutoComplete");
|
||||
oneOffInstance = searchPopup.oneOffButtons;
|
||||
oneOffButtons = oneOffInstance.buttons;
|
||||
|
||||
// Set default engine so no external requests are made.
|
||||
await SearchTestUtils.installSearchExtension(
|
||||
|
@ -27,37 +33,66 @@ add_setup(async function () {
|
|||
});
|
||||
});
|
||||
|
||||
add_task(async function telemetry() {
|
||||
searchbar.focus();
|
||||
searchbar.value = "abc";
|
||||
add_task(async function testNewtabEmpty() {
|
||||
await openPopup("abc");
|
||||
let oneOffButton = findOneOff(TEST_ENGINE_NAME);
|
||||
|
||||
let searchPopup = document.getElementById("PopupSearchAutoComplete");
|
||||
let oneOffInstance = searchPopup.oneOffButtons;
|
||||
let promise = BrowserTestUtils.waitForNewTab(gBrowser);
|
||||
await activateContextMenuItem(
|
||||
oneOffButton,
|
||||
".search-one-offs-context-open-in-new-tab"
|
||||
);
|
||||
let tab = await promise;
|
||||
|
||||
let oneOffButtons = oneOffInstance.buttons;
|
||||
// By default the search will open in the background and the popup will stay open
|
||||
await closePopup();
|
||||
|
||||
// Open the popup.
|
||||
let shownPromise = promiseEvent(searchPopup, "popupshown");
|
||||
let builtPromise = promiseEvent(oneOffInstance, "rebuild");
|
||||
info("Opening search panel");
|
||||
EventUtils.synthesizeMouseAtCenter(searchIcon, {});
|
||||
await Promise.all([shownPromise, builtPromise]);
|
||||
Assert.equal(
|
||||
tab.linkedBrowser.currentURI.spec,
|
||||
"http://mochi.test:8888/browser/browser/components/search/test/browser/?search&test=abc",
|
||||
"Expected search tab should have loaded"
|
||||
);
|
||||
|
||||
// Get the one-off button for the test engine.
|
||||
let oneOffButton;
|
||||
for (let node of oneOffButtons.children) {
|
||||
if (node.engine && node.engine.name == TEST_ENGINE_NAME) {
|
||||
oneOffButton = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function testNewtabNonempty() {
|
||||
await openPopup("");
|
||||
let oneOffButton = findOneOff(TEST_ENGINE_NAME);
|
||||
|
||||
let promise = BrowserTestUtils.waitForNewTab(gBrowser);
|
||||
await activateContextMenuItem(
|
||||
oneOffButton,
|
||||
".search-one-offs-context-open-in-new-tab"
|
||||
);
|
||||
let tab = await promise;
|
||||
|
||||
// By default the search form will open in the background and the popup will stay open
|
||||
await closePopup();
|
||||
|
||||
Assert.equal(
|
||||
tab.linkedBrowser.currentURI.spec,
|
||||
"http://mochi.test:8888/",
|
||||
"Search form should have loaded in new tab"
|
||||
);
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
function findOneOff(engineName) {
|
||||
let oneOffChildren = [...oneOffButtons.children];
|
||||
let oneOffButton = oneOffChildren.find(
|
||||
node => node.engine?.name == engineName
|
||||
);
|
||||
Assert.notEqual(
|
||||
oneOffButton,
|
||||
undefined,
|
||||
"One-off for test engine should exist"
|
||||
`One-off for ${engineName} should exist`
|
||||
);
|
||||
return oneOffButton;
|
||||
}
|
||||
|
||||
// Open the context menu on the one-off.
|
||||
async function activateContextMenuItem(oneOffButton, itemID) {
|
||||
let contextMenu = oneOffInstance.querySelector(
|
||||
".search-one-offs-context-menu"
|
||||
);
|
||||
|
@ -68,34 +103,27 @@ add_task(async function telemetry() {
|
|||
});
|
||||
await promise;
|
||||
|
||||
// Click the Search in New Tab menu item.
|
||||
let searchInNewTabMenuItem = contextMenu.querySelector(
|
||||
".search-one-offs-context-open-in-new-tab"
|
||||
);
|
||||
promise = BrowserTestUtils.waitForNewTab(gBrowser);
|
||||
contextMenu.activateItem(searchInNewTabMenuItem);
|
||||
let tab = await promise;
|
||||
let menuItem = contextMenu.querySelector(itemID);
|
||||
contextMenu.activateItem(menuItem);
|
||||
}
|
||||
|
||||
// By default the search will open in the background and the popup will stay open:
|
||||
promise = promiseEvent(searchPopup, "popuphidden");
|
||||
async function openPopup(searchBarValue) {
|
||||
searchbar.focus();
|
||||
searchbar.value = searchBarValue;
|
||||
if (searchbar.textbox.popupOpen) {
|
||||
info("searchPanel is already open");
|
||||
return;
|
||||
}
|
||||
let shownPromise = promiseEvent(searchPopup, "popupshown");
|
||||
let builtPromise = promiseEvent(oneOffInstance, "rebuild");
|
||||
info("Opening search panel");
|
||||
EventUtils.synthesizeMouseAtCenter(searchIcon, {});
|
||||
await Promise.all([shownPromise, builtPromise]);
|
||||
}
|
||||
|
||||
async function closePopup() {
|
||||
let promise = promiseEvent(searchPopup, "popuphidden");
|
||||
info("Closing search panel");
|
||||
EventUtils.synthesizeKey("KEY_Escape");
|
||||
await promise;
|
||||
|
||||
// Check the loaded tab.
|
||||
Assert.equal(
|
||||
tab.linkedBrowser.currentURI.spec,
|
||||
"http://mochi.test:8888/browser/browser/components/search/test/browser/?search&test=abc",
|
||||
"Expected search tab should have loaded"
|
||||
);
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
|
||||
// Move the cursor out of the panel area to avoid messing with other tests.
|
||||
await EventUtils.promiseNativeMouseEvent({
|
||||
type: "mousemove",
|
||||
target: searchbar,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -37,17 +37,11 @@ add_setup(async function () {
|
|||
});
|
||||
|
||||
add_task(async function nonEmptySearch() {
|
||||
searchBar.focus();
|
||||
searchBar.value = SEARCH_WORD;
|
||||
|
||||
let promise = promiseEvent(searchPopup, "popupshown");
|
||||
info("Opening search panel");
|
||||
EventUtils.synthesizeMouseAtCenter(searchIcon, {}, win);
|
||||
await promise;
|
||||
await openPopup(SEARCH_WORD);
|
||||
|
||||
let engineNameBox = searchPopup.querySelector(".searchbar-engine-name");
|
||||
|
||||
promise = BrowserTestUtils.browserLoaded(
|
||||
let promise = BrowserTestUtils.browserLoaded(
|
||||
win.gBrowser.selectedBrowser,
|
||||
false,
|
||||
`http://mochi.test:8888/browser/browser/components/search/test/browser/?search&test=${SEARCH_WORD}`
|
||||
|
@ -58,22 +52,44 @@ add_task(async function nonEmptySearch() {
|
|||
});
|
||||
|
||||
add_task(async function emptySearch() {
|
||||
searchBar.focus();
|
||||
searchBar.value = "";
|
||||
|
||||
let promise = promiseEvent(searchPopup, "popupshown");
|
||||
info("Opening search panel");
|
||||
EventUtils.synthesizeMouseAtCenter(searchIcon, {}, win);
|
||||
await promise;
|
||||
await openPopup("");
|
||||
|
||||
let engineNameBox = searchPopup.querySelector(".searchbar-engine-name");
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(engineNameBox, {}, win);
|
||||
|
||||
await TestUtils.waitForTick();
|
||||
|
||||
Assert.equal(
|
||||
win.gBrowser.selectedBrowser.ownerDocument.activeElement,
|
||||
searchBar.textbox,
|
||||
"Focus stays in the searchbar"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function emptySearchShift() {
|
||||
await openPopup("");
|
||||
|
||||
let engineNameBox = searchPopup.querySelector(".searchbar-engine-name");
|
||||
|
||||
let promise = BrowserTestUtils.browserLoaded(
|
||||
win.gBrowser.selectedBrowser,
|
||||
false,
|
||||
"http://mochi.test:8888/"
|
||||
);
|
||||
EventUtils.synthesizeMouseAtCenter(engineNameBox, { shiftKey: true }, win);
|
||||
await promise;
|
||||
info("Opening search form successful");
|
||||
});
|
||||
|
||||
async function openPopup(searchBarValue) {
|
||||
searchBar.focus();
|
||||
searchBar.value = searchBarValue;
|
||||
if (searchBar.textbox.popupOpen) {
|
||||
info("searchPanel is already open");
|
||||
return;
|
||||
}
|
||||
let shownPromise = promiseEvent(searchPopup, "popupshown");
|
||||
let builtPromise = promiseEvent(searchPopup.oneOffButtons, "rebuild");
|
||||
info("Opening search panel");
|
||||
EventUtils.synthesizeMouseAtCenter(searchIcon, {}, win);
|
||||
await Promise.all([shownPromise, builtPromise]);
|
||||
}
|
||||
|
|
|
@ -184,10 +184,11 @@ add_task(async function openSettingsWithEnter() {
|
|||
const searchButton = searchBar.querySelector(".searchbar-search-button");
|
||||
|
||||
let shownPromise = promiseEvent(searchPopup, "popupshown");
|
||||
info("Clicking icon");
|
||||
let builtPromise = promiseEvent(searchPopup.oneOffButtons, "rebuild");
|
||||
info("Opening search panel");
|
||||
EventUtils.synthesizeMouseAtCenter(searchButton, {}, win);
|
||||
await shownPromise;
|
||||
info("Popup shown");
|
||||
await Promise.all([shownPromise, builtPromise]);
|
||||
info("Search panel ready");
|
||||
|
||||
EventUtils.synthesizeKey("KEY_ArrowUp", {}, win);
|
||||
|
||||
|
|
52
browser/components/sidebar/SidebarManager.sys.mjs
Normal file
52
browser/components/sidebar/SidebarManager.sys.mjs
Normal file
|
@ -0,0 +1,52 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
|
||||
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
|
||||
PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs",
|
||||
});
|
||||
XPCOMUtils.defineLazyPreferenceGetter(lazy, "sidebarNimbus", "sidebar.nimbus");
|
||||
|
||||
export const SidebarManager = {
|
||||
/**
|
||||
* Handle startup tasks like telemetry, adding listeners.
|
||||
*/
|
||||
init() {
|
||||
// Handle nimbus feature pref setting updates on init and enrollment
|
||||
const featureId = "sidebar";
|
||||
lazy.NimbusFeatures[featureId].onUpdate(() => {
|
||||
// Set prefs only if we have an enrollment that's new
|
||||
const feature = { featureId };
|
||||
const enrollment =
|
||||
lazy.ExperimentAPI.getExperimentMetaData(feature) ??
|
||||
lazy.ExperimentAPI.getRolloutMetaData(feature);
|
||||
if (!enrollment) {
|
||||
return;
|
||||
}
|
||||
const slug = enrollment.slug + ":" + enrollment.branch.slug;
|
||||
if (slug == lazy.sidebarNimbus) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set/override user prefs to persist after experiment end
|
||||
const setPref = (pref, value) => {
|
||||
// Only set prefs with a value (so no clearing)
|
||||
if (value != null) {
|
||||
lazy.PrefUtils.setPref("sidebar." + pref, value);
|
||||
}
|
||||
};
|
||||
setPref("nimbus", slug);
|
||||
["main.tools", "revamp", "verticalTabs"].forEach(pref =>
|
||||
setPref(pref, lazy.NimbusFeatures[featureId].getVariable(pref))
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize on first import
|
||||
SidebarManager.init();
|
|
@ -262,6 +262,9 @@ var SidebarController = {
|
|||
},
|
||||
|
||||
async init() {
|
||||
// Initialize with side effects
|
||||
this.SidebarManager;
|
||||
|
||||
this._box = document.getElementById("sidebar-box");
|
||||
this._splitter = document.getElementById("sidebar-splitter");
|
||||
this._reversePositionButton = document.getElementById(
|
||||
|
@ -528,14 +531,14 @@ var SidebarController = {
|
|||
let sidebarContainer = document.getElementById("sidebar-main");
|
||||
let sidebarMain = document.querySelector("sidebar-main");
|
||||
if (!this._positionStart) {
|
||||
// DOM ordering is: sidebar-main | sidebar-box | splitter | appcontent |
|
||||
// Want to display as: | appcontent | splitter | sidebar-box | sidebar-main
|
||||
// So we just swap box and appcontent ordering and move sidebar-main to the end
|
||||
let appcontent = document.getElementById("appcontent");
|
||||
// DOM ordering is: sidebar-main | sidebar-box | splitter | tabbrowser-tabbox |
|
||||
// Want to display as: | tabbrowser-tabbox | splitter | sidebar-box | sidebar-main
|
||||
// So we just swap box and tabbrowser-tabbox ordering and move sidebar-main to the end
|
||||
let tabbox = document.getElementById("tabbrowser-tabbox");
|
||||
let boxOrdinal = this._box.style.order;
|
||||
this._box.style.order = appcontent.style.order;
|
||||
this._box.style.order = tabbox.style.order;
|
||||
|
||||
appcontent.style.order = boxOrdinal;
|
||||
tabbox.style.order = boxOrdinal;
|
||||
// the launcher should be on the right of the sidebar-box
|
||||
sidebarContainer.style.order = parseInt(this._box.style.order) + 1;
|
||||
// Indicate we've switched ordering to the box
|
||||
|
@ -1550,6 +1553,10 @@ var SidebarController = {
|
|||
},
|
||||
};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(SidebarController, {
|
||||
SidebarManager: "resource:///modules/SidebarManager.sys.mjs",
|
||||
});
|
||||
|
||||
// Add getters related to the position here, since we will want them
|
||||
// available for both startDelayedLoad and init.
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
|
|
|
@ -7,3 +7,7 @@
|
|||
JAR_MANIFESTS += ["jar.mn"]
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.toml"]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
"SidebarManager.sys.mjs",
|
||||
]
|
||||
|
|
|
@ -9,13 +9,6 @@
|
|||
--sidebar-text-color: -moz-sidebartext;
|
||||
--sidebar-border-color: -moz-sidebarborder;
|
||||
|
||||
/* stylelint-disable-next-line media-query-no-invalid */
|
||||
@media ((-moz-bool-pref: "browser.theme.macos.native-theme") and (-moz-platform: macOS) and (not (prefers-contrast)) and (prefers-color-scheme: light)) {
|
||||
&:not([lwtheme]) {
|
||||
--toolbar-non-lwt-bgcolor: white;
|
||||
}
|
||||
}
|
||||
|
||||
background-color: transparent;
|
||||
height: 100%;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ run-if = ["os == 'mac'"] # Mac only feature
|
|||
|
||||
["browser_sidebar_max_width.js"]
|
||||
|
||||
["browser_sidebar_nimbus.js"]
|
||||
|
||||
["browser_sidebar_panel_header.js"]
|
||||
|
||||
["browser_sidebar_panel_switcher.js"]
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const { ExperimentFakes } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/NimbusTestUtils.sys.mjs"
|
||||
);
|
||||
|
||||
/**
|
||||
* Check that enrolling into sidebar experiments sets user prefs
|
||||
*/
|
||||
add_task(async function test_nimbus_user_prefs() {
|
||||
const main = "sidebar.main.tools";
|
||||
const nimbus = "sidebar.nimbus";
|
||||
const vertical = "sidebar.verticalTabs";
|
||||
|
||||
Assert.ok(!Services.prefs.prefHasUserValue(main), "No user main pref yet");
|
||||
Assert.ok(
|
||||
!Services.prefs.prefHasUserValue(nimbus),
|
||||
"No user nimbus pref yet"
|
||||
);
|
||||
|
||||
let cleanup = await ExperimentFakes.enrollWithFeatureConfig({
|
||||
featureId: "sidebar",
|
||||
value: {
|
||||
"main.tools": "bar",
|
||||
},
|
||||
});
|
||||
|
||||
Assert.equal(
|
||||
Services.prefs.getStringPref(main),
|
||||
"bar",
|
||||
"Set user pref with experiment"
|
||||
);
|
||||
Assert.ok(Services.prefs.prefHasUserValue(main), "main pref has user value");
|
||||
const nimbusValue = Services.prefs.getStringPref(nimbus);
|
||||
Assert.ok(nimbusValue, "Set some nimbus slug");
|
||||
Assert.ok(
|
||||
Services.prefs.prefHasUserValue(nimbus),
|
||||
"nimbus pref has user value"
|
||||
);
|
||||
|
||||
cleanup();
|
||||
|
||||
Assert.equal(
|
||||
Services.prefs.getStringPref(main),
|
||||
"bar",
|
||||
"main pref still set"
|
||||
);
|
||||
Assert.equal(
|
||||
Services.prefs.getStringPref(nimbus),
|
||||
nimbusValue,
|
||||
"nimbus pref still set"
|
||||
);
|
||||
Assert.ok(!Services.prefs.getBoolPref(vertical), "vertical is default value");
|
||||
Assert.ok(
|
||||
!Services.prefs.prefHasUserValue(vertical),
|
||||
"vertical used default value"
|
||||
);
|
||||
|
||||
cleanup = await ExperimentFakes.enrollWithFeatureConfig({
|
||||
featureId: "sidebar",
|
||||
value: {
|
||||
"main.tools": "aichat,syncedtabs,history",
|
||||
verticalTabs: true,
|
||||
},
|
||||
});
|
||||
|
||||
Assert.ok(!Services.prefs.prefHasUserValue(main), "main pref no longer set");
|
||||
Assert.notEqual(
|
||||
Services.prefs.getStringPref(nimbus),
|
||||
nimbusValue,
|
||||
"nimbus pref changed"
|
||||
);
|
||||
Assert.ok(Services.prefs.getBoolPref(vertical), "vertical set to true");
|
||||
Assert.ok(
|
||||
Services.prefs.prefHasUserValue(vertical),
|
||||
"vertical pref has user value"
|
||||
);
|
||||
|
||||
cleanup();
|
||||
Services.prefs.clearUserPref(nimbus);
|
||||
Services.prefs.clearUserPref(vertical);
|
||||
});
|
||||
|
||||
/**
|
||||
* Check that rollout sets prefs then prefer experiment
|
||||
*/
|
||||
add_task(async function test_nimbus_rollout_experiment() {
|
||||
const revamp = "sidebar.revamp";
|
||||
const nimbus = "sidebar.nimbus";
|
||||
await SpecialPowers.pushPrefEnv({ clear: [[revamp]] });
|
||||
|
||||
const cleanRollout = await ExperimentFakes.enrollWithFeatureConfig(
|
||||
{
|
||||
featureId: "sidebar",
|
||||
value: { revamp: true },
|
||||
},
|
||||
{ isRollout: true }
|
||||
);
|
||||
|
||||
Assert.ok(Services.prefs.getBoolPref(revamp), "Set user pref with rollout");
|
||||
const nimbusValue = Services.prefs.getStringPref(nimbus);
|
||||
Assert.ok(nimbusValue, "Set some nimbus slug");
|
||||
|
||||
const cleanExperiment = await ExperimentFakes.enrollWithFeatureConfig({
|
||||
featureId: "sidebar",
|
||||
value: { revamp: false },
|
||||
});
|
||||
|
||||
Assert.ok(
|
||||
!Services.prefs.getBoolPref(revamp),
|
||||
"revamp pref flipped by experiment"
|
||||
);
|
||||
Assert.notEqual(
|
||||
Services.prefs.getStringPref(nimbus),
|
||||
nimbusValue,
|
||||
"nimbus pref changed by experiment"
|
||||
);
|
||||
|
||||
cleanRollout();
|
||||
cleanExperiment();
|
||||
Services.prefs.clearUserPref(nimbus);
|
||||
});
|
||||
|
||||
/**
|
||||
* Check that multi-feature sidebar and chatbot sets prefs
|
||||
*/
|
||||
add_task(async function test_nimbus_multi_feature() {
|
||||
const chatbot = "browser.ml.chat.enabled";
|
||||
const sidebar = "sidebar.main.tools";
|
||||
Assert.ok(!Services.prefs.prefHasUserValue(chatbot), "chatbot is default");
|
||||
Assert.ok(!Services.prefs.prefHasUserValue(sidebar), "sidebar is default");
|
||||
|
||||
const cleanup = await ExperimentFakes.enrollmentHelper(
|
||||
ExperimentFakes.recipe("foo", {
|
||||
branches: [
|
||||
{
|
||||
slug: "variant",
|
||||
features: [
|
||||
{
|
||||
featureId: "sidebar",
|
||||
value: { "main.tools": "syncedtabs,history" },
|
||||
},
|
||||
{
|
||||
featureId: "chatbot",
|
||||
value: { prefs: { enabled: { value: true } } },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
Assert.ok(Services.prefs.prefHasUserValue(chatbot), "chatbot user pref set");
|
||||
Assert.ok(Services.prefs.prefHasUserValue(sidebar), "sidebar user pref set");
|
||||
|
||||
cleanup();
|
||||
|
||||
Assert.ok(Services.prefs.prefHasUserValue(chatbot), "chatbot pref still set");
|
||||
Assert.ok(Services.prefs.prefHasUserValue(sidebar), "sidebar pref still set");
|
||||
|
||||
Services.prefs.clearUserPref(chatbot);
|
||||
Services.prefs.clearUserPref(sidebar);
|
||||
Services.prefs.clearUserPref("browser.ml.chat.nimbus");
|
||||
Services.prefs.clearUserPref("sidebar.nimbus");
|
||||
});
|
|
@ -3,11 +3,10 @@ Dynamic Result Types
|
|||
|
||||
This document discusses a special category of address bar results called dynamic
|
||||
result types. Dynamic result types allow you to easily add new types of results
|
||||
to the address bar and are especially useful for extensions.
|
||||
to the address bar.
|
||||
|
||||
The intended audience for this document is developers who need to add new kinds
|
||||
of address bar results, either internally in the address bar codebase or through
|
||||
extensions.
|
||||
of address bar results.
|
||||
|
||||
.. contents::
|
||||
:depth: 2
|
||||
|
@ -34,14 +33,6 @@ so that your card is drawn correctly; you may need to update the keyboard
|
|||
selection behavior if your card contains elements that can be independently
|
||||
selected such as different days of the week; and so on.
|
||||
|
||||
If you're implementing your weather card in an extension, as you might in an
|
||||
add-on experiment, then you'd need to land your new result type in
|
||||
mozilla-central so your extension can use it. Your new result type would ship
|
||||
with Firefox even though the vast majority of users would never see it, and your
|
||||
fellow address bar hackers would have to work around your code even though it
|
||||
would remain inactive most of the time, at least until your experiment
|
||||
graduated.
|
||||
|
||||
Dynamic Result Types
|
||||
--------------------
|
||||
|
||||
|
@ -49,52 +40,27 @@ Dynamic Result Types
|
|||
types. Instead of adding a new built-in type along with all that entails, you
|
||||
add a new provider subclass and register a template that describes how the view
|
||||
should draw your result type and indicates which elements are selectable. The
|
||||
address bar takes care of everything else. (Or if you're implementing an
|
||||
extension, you add a few event handlers instead of a provider subclass, although
|
||||
we have a shim_ that abstracts away the differences between internal and
|
||||
extension address bar code.)
|
||||
address bar takes care of everything else.
|
||||
|
||||
Dynamic result types are essentially an abstraction layer: Support for them as a
|
||||
general category of results is built into the address bar, and each
|
||||
implementation of a specific dynamic result type fills in the details.
|
||||
|
||||
In addition, dynamic result types can be added at runtime. This is important for
|
||||
extensions that implement new types of results like the weather forecast example
|
||||
above.
|
||||
|
||||
.. _shim: https://github.com/0c0w3/dynamic-result-type-extension/blob/master/src/shim.js
|
||||
In addition, dynamic result types can be added at runtime.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
To get a feel for how dynamic result types are implemented, you can look at the
|
||||
`example dynamic result type extension <exampleExtension_>`__. The extension
|
||||
uses the recommended shim_ that makes writing address bar extension code very
|
||||
similar to writing internal address bar code, and it's therefore a useful
|
||||
example even if you intend to add a new dynamic result type internally in the
|
||||
address bar codebase in mozilla-central.
|
||||
:searchfox:`UrlbarProviderCalculator <browser/components/urlbar/UrlbarProviderCalculator.sys.mjs>`.
|
||||
|
||||
The next section describes the specific steps you need to take to add a new
|
||||
dynamic result type.
|
||||
|
||||
.. _exampleExtension: https://github.com/0c0w3/dynamic-result-type-extension/blob/master/src/background.js
|
||||
|
||||
Implementation Steps
|
||||
--------------------
|
||||
|
||||
This section describes how to add a new dynamic result type in either of the
|
||||
following cases:
|
||||
|
||||
* You want to add a new dynamic result type in an extension using the
|
||||
recommended shim_.
|
||||
* You want to add a new dynamic result type internal to the address bar codebase
|
||||
in mozilla-central.
|
||||
|
||||
The steps are mostly the same in both cases and are described next.
|
||||
|
||||
If you want to add a new dynamic result type in an extension but don't want to
|
||||
use the shim, then skip ahead to `Appendix B: Using the WebExtensions API
|
||||
Directly`_.
|
||||
This section describes how to add a new dynamic result type.
|
||||
|
||||
1. Register the dynamic result type
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -123,9 +89,7 @@ Next, add the view template for the new type:
|
|||
|
||||
``viewTemplate`` is an object called a view template. It describes in a
|
||||
declarative manner the DOM that should be created in the view for all results of
|
||||
the new type. For providers created in extensions, it also declares the
|
||||
stylesheet that should be applied to results in the view. See `View Templates`_
|
||||
for a description of this object.
|
||||
the new type.
|
||||
|
||||
3. Add the provider
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -161,9 +125,6 @@ For help on implementing providers in general, see the address bar's
|
|||
If you are creating the provider in the internal address bar implementation in
|
||||
mozilla-central, then don't forget to register it in ``UrlbarProvidersManager``.
|
||||
|
||||
If you are creating the provider in an extension, then it's registered
|
||||
automatically, and there's nothing else you need to do.
|
||||
|
||||
__ https://firefox-source-docs.mozilla.org/browser/urlbar/overview.html#urlbarprovider
|
||||
|
||||
4. Implement the provider's getViewUpdate method
|
||||
|
@ -220,11 +181,6 @@ mozilla-central, then add styling `urlbar-dynamic-results.css`_.
|
|||
|
||||
.. _urlbar-dynamic-results.css: https://searchfox.org/mozilla-central/source/browser/themes/shared/urlbar-dynamic-results.css
|
||||
|
||||
If you are creating the provider in an extension, then bundle a CSS file in your
|
||||
extension and declare it in the top-level ``stylesheet`` property of your view
|
||||
template, as described in `View Templates`_. Additionally, if any of your rules
|
||||
override built-in rules, then you'll need to declare them as ``!important``.
|
||||
|
||||
The rest of this section will discuss the CSS rules you need to use to style
|
||||
your results.
|
||||
|
||||
|
@ -276,11 +232,6 @@ build the DOM for a dynamic result type. When a result of a particular dynamic
|
|||
result type is shown in the view, the type's view template is used to construct
|
||||
the part of the view that represents the type in general.
|
||||
|
||||
The need for view templates arises from the fact that extensions run in a
|
||||
separate process from the chrome process and can't directly access the chrome
|
||||
DOM, where the address bar view lives. Since extensions are a primary use case
|
||||
for dynamic result types, this is an important constraint on their design.
|
||||
|
||||
Properties
|
||||
~~~~~~~~~~
|
||||
|
||||
|
@ -333,18 +284,6 @@ structure may include the following properties:
|
|||
An optional list of classes. Each class will be added to the element created
|
||||
for the object by calling ``element.classList.add()``.
|
||||
|
||||
``{string} [stylesheet]``
|
||||
For dynamic result types created in extensions, this property should be set on
|
||||
the root object in the view template structure, and its value should be a
|
||||
stylesheet URL. The stylesheet will be loaded in all browser windows so that
|
||||
the dynamic result type view may be styled. The specified URL will be resolved
|
||||
against the extension's base URI. We recommend specifying a URL relative to
|
||||
your extension's base directory.
|
||||
|
||||
For dynamic result types created internally in the address bar codebase, this
|
||||
value should not be specified and instead styling should be added to
|
||||
`urlbar-dynamic-results.css`_.
|
||||
|
||||
Example
|
||||
~~~~~~~
|
||||
|
||||
|
@ -648,9 +587,6 @@ payload property in the following example:
|
|||
}
|
||||
);
|
||||
|
||||
``UrlbarUtils.HIGHLIGHT`` is defined in the extensions shim_ and is described
|
||||
below.
|
||||
|
||||
Your view template must create an element corresponding to the payload
|
||||
property. That is, it must include an object where the value of the ``name``
|
||||
property is the name of the payload property, like this:
|
||||
|
@ -693,16 +629,7 @@ Appendix A: Examples
|
|||
This section lists some example and real-world consumers of dynamic result
|
||||
types.
|
||||
|
||||
`Example Extension`__
|
||||
This extension demonstrates a simple use of dynamic result types.
|
||||
|
||||
`Weather Quick Suggest Extension`__
|
||||
A real-world Firefox extension experiment that shows weather forecasts and
|
||||
alerts when the user performs relevant searches in the address bar.
|
||||
|
||||
`Tab-to-Search Provider`__
|
||||
This is a built-in provider in mozilla-central that uses dynamic result types.
|
||||
|
||||
__ https://github.com/0c0w3/dynamic-result-type-extension
|
||||
__ https://github.com/mozilla-extensions/firefox-quick-suggest-weather/blob/master/src/background.js
|
||||
__ https://searchfox.org/mozilla-central/source/browser/components/urlbar/UrlbarProviderTabToSearch.sys.mjs
|
||||
|
|
|
@ -84,6 +84,7 @@ add_task(async function test_save_edited_fields() {
|
|||
async function (browser) {
|
||||
info(`Test ${TEST.description}`);
|
||||
|
||||
info(`Wait for save doorhanger shown`);
|
||||
const onSavePopupShown = waitForPopupShown();
|
||||
await focusUpdateSubmitForm(browser, {
|
||||
focusSelector: "#given-name",
|
||||
|
@ -91,10 +92,12 @@ add_task(async function test_save_edited_fields() {
|
|||
});
|
||||
await onSavePopupShown;
|
||||
|
||||
info(`Wait for edit doorhanger shown`);
|
||||
const onEditPopupShown = waitForPopupShown();
|
||||
await clickAddressDoorhangerButton(EDIT_ADDRESS_BUTTON);
|
||||
await onEditPopupShown;
|
||||
|
||||
info(`Fill edit doorhanger`);
|
||||
fillEditDoorhanger(TEST.editedFields);
|
||||
await clickAddressDoorhangerButton(MAIN_BUTTON);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ support-files = [
|
|||
"../fixtures/autocomplete_simple_basic.html",
|
||||
"../fixtures/page_navigation.html",
|
||||
"./empty.html",
|
||||
"../fixtures/**",
|
||||
]
|
||||
|
||||
["browser_autocomplete_footer.js"]
|
||||
|
@ -25,6 +26,10 @@ skip-if = [
|
|||
|
||||
["browser_autofill_address_select_inexact.js"]
|
||||
|
||||
["browser_autofill_creditCard_name.js"]
|
||||
|
||||
["browser_autofill_creditCard_type.js"]
|
||||
|
||||
["browser_autofill_duplicate_fields.js"]
|
||||
|
||||
["browser_autofill_sandboxed_iframe.js"]
|
||||
|
@ -46,6 +51,22 @@ skip-if = [
|
|||
|
||||
["browser_fathom_cc.js"]
|
||||
|
||||
["browser_form_changes.js"]
|
||||
|
||||
["browser_iframe_autofill_cc_number.js"]
|
||||
|
||||
["browser_iframe_autofill_sandbox.js"]
|
||||
|
||||
["browser_iframe_capture.js"]
|
||||
|
||||
["browser_iframe_cross_origin_autofill.js"]
|
||||
|
||||
["browser_iframe_cross_origin_field_detection.js"]
|
||||
|
||||
["browser_iframe_layout_telemetry.js"]
|
||||
|
||||
["browser_iframe_same_origin_field_detection.js"]
|
||||
|
||||
["browser_manageAddressesDialog.js"]
|
||||
|
||||
["browser_page_navigation_in_subtree.js"]
|
||||
|
|
|
@ -6,6 +6,7 @@ http://creativecommons.org/publicdomain/zero/1.0/ */
|
|||
const TEST_PROFILE_US = {
|
||||
email: "address_us@mozilla.org",
|
||||
organization: "Mozilla",
|
||||
"address-level1": "AZ",
|
||||
country: "US",
|
||||
};
|
||||
|
||||
|
@ -30,11 +31,21 @@ const MARKUP_SELECT_COUNTRY = `
|
|||
`;
|
||||
|
||||
// Strip any attributes that could help identify select as country field
|
||||
const MARKUP_SELECT_COUNTRY_WITHOUT_AUTOCOMPLETE =
|
||||
MARKUP_SELECT_COUNTRY.replace(/<select[^>]*>/, "<select>");
|
||||
const MARKUP_SELECT_COUNTRY_WITHOUT_AUTOCOMPLETE = `
|
||||
<html><body>
|
||||
<input id="email">
|
||||
<input id="organization">
|
||||
<select id="country">
|
||||
<option value="">Select a country</option>
|
||||
<option value="Canada">Canada</option>
|
||||
<option value="United States">United States</option>
|
||||
</select>
|
||||
</body></html>
|
||||
`;
|
||||
|
||||
add_autofill_heuristic_tests([
|
||||
{
|
||||
description: "Test autofill select with US profile",
|
||||
fixtureData: MARKUP_SELECT_COUNTRY,
|
||||
profile: TEST_PROFILE_US,
|
||||
expectedResult: [
|
||||
|
@ -51,6 +62,7 @@ add_autofill_heuristic_tests([
|
|||
],
|
||||
},
|
||||
{
|
||||
description: "Test autofill select with CA profile",
|
||||
fixtureData: MARKUP_SELECT_COUNTRY,
|
||||
profile: TEST_PROFILE_CA,
|
||||
expectedResult: [
|
||||
|
@ -67,21 +79,49 @@ add_autofill_heuristic_tests([
|
|||
],
|
||||
},
|
||||
{
|
||||
description: "Test autofill <select> without autocomplete attribute",
|
||||
fixtureData: MARKUP_SELECT_COUNTRY_WITHOUT_AUTOCOMPLETE,
|
||||
profile: TEST_PROFILE_CA,
|
||||
expectedResult: [
|
||||
{
|
||||
default: {
|
||||
reason: "regex-heuristic",
|
||||
},
|
||||
fields: [
|
||||
{ fieldName: "email", autofill: TEST_PROFILE_CA.email },
|
||||
{ fieldName: "organization", autofill: TEST_PROFILE_CA.organization },
|
||||
{ fieldName: "country", autofill: "Canada" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Address form without matching options in select for address-level1 and country",
|
||||
fixtureData: `<form>
|
||||
<input id="email" autocomplete="email">
|
||||
<select id="address-level1" autocomplete="address-level1">
|
||||
<option id=default value=""></option>
|
||||
<option id="option-address-level1-dummy1" value="Dummy">Dummy</option>
|
||||
<option id="option-address-level1-dummy2" value="Dummy 2">Dummy 2</option>
|
||||
</select>
|
||||
<select id="country" autocomplete="country">
|
||||
<option id=default value=""></option>
|
||||
<option id="option-country-dummy1" value="Dummy">Dummy</option>
|
||||
<option id="option-country-dummy2" value="Dummy 2">Dummy 2</option>
|
||||
</select>
|
||||
</form>`,
|
||||
|
||||
profile: TEST_PROFILE_US,
|
||||
expectedResult: [
|
||||
{
|
||||
default: {
|
||||
reason: "autocomplete",
|
||||
},
|
||||
fields: [
|
||||
{ fieldName: "email", autofill: TEST_PROFILE_CA.email },
|
||||
{ fieldName: "organization", autofill: TEST_PROFILE_CA.organization },
|
||||
{
|
||||
fieldName: "country",
|
||||
autofill: "Canada",
|
||||
reason: "regex-heuristic",
|
||||
},
|
||||
{ fieldName: "email", autofill: TEST_PROFILE_US.email },
|
||||
{ fieldName: "address-level1", autofill: "" },
|
||||
{ fieldName: "country", autofill: "" },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
@ -8,13 +8,14 @@ const TEST_PROFILE = {
|
|||
"cc-number": "4111111111111111",
|
||||
// "cc-type" should be remove from proile after fixing Bug 1834768.
|
||||
"cc-type": "visa",
|
||||
"cc-exp-month": 4,
|
||||
"cc-exp-month": "04",
|
||||
"cc-exp-year": new Date().getFullYear(),
|
||||
};
|
||||
|
||||
add_setup(async function () {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["extensions.formautofill.loglevel", "Debug"],
|
||||
["extensions.formautofill.creditCards.supported", "on"],
|
||||
["extensions.formautofill.creditCards.enabled", true],
|
||||
],
|
||||
|
@ -85,6 +86,7 @@ add_autofill_heuristic_tests([
|
|||
<input id="name" placeholder="given-name">
|
||||
</form>`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-number",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
|
@ -96,7 +98,7 @@ add_autofill_heuristic_tests([
|
|||
{
|
||||
fieldName: "cc-exp",
|
||||
reason: "autocomplete",
|
||||
autofill: TEST_PROFILE["cc-exp"],
|
||||
autofill: `${TEST_PROFILE["cc-exp-month"]}/${TEST_PROFILE["cc-exp-year"]}`,
|
||||
},
|
||||
{
|
||||
fieldName: "cc-name",
|
||||
|
@ -184,6 +186,7 @@ add_autofill_heuristic_tests([
|
|||
<input id="country" placeholder="country">
|
||||
</form>`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-number",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
|
@ -0,0 +1,189 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const MOCK_STORAGE = [
|
||||
{
|
||||
name: "John Doe",
|
||||
organization: "Sesame Street",
|
||||
"address-level2": "Austin",
|
||||
tel: "+13453453456",
|
||||
},
|
||||
{
|
||||
name: "Foo Bar",
|
||||
organization: "Mozilla",
|
||||
"address-level2": "San Francisco",
|
||||
tel: "+16509030800",
|
||||
},
|
||||
];
|
||||
|
||||
function makeAddressComment({ primary, secondary, status }) {
|
||||
return JSON.stringify({
|
||||
primary,
|
||||
secondary,
|
||||
status,
|
||||
ariaLabel: primary + " " + secondary + " " + status,
|
||||
});
|
||||
}
|
||||
|
||||
async function removeInputField(browser, selector) {
|
||||
await SpecialPowers.spawn(browser, [{ selector }], async args => {
|
||||
content.document.querySelector(args.selector).remove();
|
||||
});
|
||||
}
|
||||
|
||||
async function addInputField(browser, formId, className) {
|
||||
await SpecialPowers.spawn(browser, [{ formId, className }], async args => {
|
||||
const newElem = content.document.createElement("input");
|
||||
newElem.name = args.className;
|
||||
newElem.autocomplete = args.className;
|
||||
newElem.type = "text";
|
||||
const form = content.document.querySelector(`#${args.formId}`);
|
||||
form.appendChild(newElem);
|
||||
});
|
||||
}
|
||||
|
||||
async function checkFieldsAutofilled(browser, formId, profile) {
|
||||
await SpecialPowers.spawn(browser, [{ formId, profile }], async args => {
|
||||
const elements = content.document.querySelectorAll(`#${args.formId} input`);
|
||||
for (const element of elements) {
|
||||
await ContentTaskUtils.waitForCondition(() => {
|
||||
return element.value == args.profile[element.name];
|
||||
});
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => element.matches(":autofill"),
|
||||
`Checking #${element.id} highlight style`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Compare the comment on the autocomplete menu items to the expected comment.
|
||||
// The profile field is not compared.
|
||||
async function checkMenuEntries(
|
||||
browser,
|
||||
expectedValues,
|
||||
extraRows = 1,
|
||||
{ checkComment = false } = {}
|
||||
) {
|
||||
const expectedLength = expectedValues.length + extraRows;
|
||||
|
||||
let actualValues;
|
||||
await BrowserTestUtils.waitForCondition(() => {
|
||||
actualValues = browser.autoCompletePopup.view.results;
|
||||
return actualValues.length == expectedLength;
|
||||
});
|
||||
is(actualValues.length, expectedLength, " Checking length of expected menu");
|
||||
|
||||
for (let i = 0; i < expectedValues.length; i++) {
|
||||
if (checkComment) {
|
||||
const expectedValue = JSON.parse(expectedValues[i]);
|
||||
const actualValue = JSON.parse(actualValues[i].comment);
|
||||
for (const [key, value] of Object.entries(expectedValue)) {
|
||||
is(
|
||||
actualValue[key],
|
||||
value,
|
||||
`Checking menu entry #${i}, ${key} should be the same`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
is(actualValues[i].label, expectedValues[i], "Checking menu entry #" + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_PAGE = `
|
||||
<form id="form1">
|
||||
<p><label>organization: <input name="organization" autocomplete="organization" type="text"></label></p>
|
||||
<p><label>tel: <input name="tel" autocomplete="tel" type="text"></label></p>
|
||||
<p><label>name: <input name="name" autocomplete="name" type="text"></label></p>
|
||||
</form>
|
||||
<div id="form2">
|
||||
<p><label>organization: <input name="organization" autocomplete="organization" type="text"></label></p>
|
||||
<p><label>tel: <input name="tel" autocomplete="tel" type="text"></label></p>
|
||||
<p><label>name: <input name="name" autocomplete="name" type="text"></label></p>
|
||||
</div>`;
|
||||
|
||||
async function checkFormChangeHappened(formId) {
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: `https://example.com/document-builder.sjs?html=${encodeURIComponent(
|
||||
TEST_PAGE
|
||||
)}`,
|
||||
},
|
||||
async browser => {
|
||||
await openPopupOn(browser, `#${formId} input[name=tel]`);
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
|
||||
|
||||
await checkMenuEntries(
|
||||
browser,
|
||||
MOCK_STORAGE.map(address =>
|
||||
makeAddressComment({
|
||||
primary: address.tel,
|
||||
secondary: address.name,
|
||||
status: "Also autofills name, organization",
|
||||
})
|
||||
),
|
||||
2,
|
||||
{ checkComment: true }
|
||||
);
|
||||
|
||||
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
|
||||
|
||||
// Click the first entry of the autocomplete popup and make sure all fields are autofilled
|
||||
await checkFieldsAutofilled(browser, formId, MOCK_STORAGE[0]);
|
||||
|
||||
// This is for checking the changes of element count.
|
||||
addInputField(browser, formId, "address-level2");
|
||||
await openPopupOn(browser, `#${formId} input[name=name]`);
|
||||
|
||||
// Click on an autofilled field would show an autocomplete popup with "clear form" entry
|
||||
await checkMenuEntries(
|
||||
browser,
|
||||
[
|
||||
"Clear Autofill Form", // Clear Autofill Form
|
||||
"Manage addresses", // FormAutofill Preferemce
|
||||
],
|
||||
0
|
||||
);
|
||||
|
||||
// This is for checking the changes of element removed and added then.
|
||||
removeInputField(browser, `#${formId} input[name=address-level2]`);
|
||||
addInputField(browser, formId, "address-level2");
|
||||
await openPopupOn(browser, `#${formId} input[name=address-level2]`);
|
||||
|
||||
await checkMenuEntries(
|
||||
browser,
|
||||
MOCK_STORAGE.map(address =>
|
||||
makeAddressComment({
|
||||
primary: address["address-level2"],
|
||||
secondary: address.name,
|
||||
status: "Also autofills name, organization, phone",
|
||||
})
|
||||
),
|
||||
2,
|
||||
{ checkComment: true }
|
||||
);
|
||||
|
||||
// Make sure everything is autofilled in the end
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
|
||||
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
|
||||
await checkFieldsAutofilled(browser, formId, MOCK_STORAGE[0]);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
add_setup(async function () {
|
||||
await setStorage(MOCK_STORAGE[0]);
|
||||
await setStorage(MOCK_STORAGE[1]);
|
||||
});
|
||||
|
||||
add_task(async function check_change_happened_in_form() {
|
||||
await checkFormChangeHappened("form1");
|
||||
});
|
||||
|
||||
add_task(async function check_change_happened_in_body() {
|
||||
await checkFormChangeHappened("form2");
|
||||
});
|
|
@ -0,0 +1,285 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global add_autofill_heuristic_tests */
|
||||
|
||||
const TEST_PROFILE = {
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
// "cc-type" should be remove from proile after fixing Bug 1834768.
|
||||
"cc-type": "visa",
|
||||
"cc-exp-month": "04",
|
||||
"cc-exp-year": new Date().getFullYear(),
|
||||
};
|
||||
|
||||
add_setup(async function () {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["extensions.formautofill.creditCards.supported", "on"],
|
||||
["extensions.formautofill.creditCards.enabled", true],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
add_autofill_heuristic_tests([
|
||||
/**
|
||||
* Test credit card number in the main-frame
|
||||
*/
|
||||
{
|
||||
description:
|
||||
"Fill cc-number in a main-frame when the autofill is triggered in a first-party-origin iframe",
|
||||
fixtureData: `
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\"></iframe>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-name",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: TEST_PROFILE["cc-number"] },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Do not fill cc-number in a main-frame when the autofill is triggered in a third-party-origin iframe",
|
||||
fixtureData: `
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NAME}\"></iframe>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-name",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: "" },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
/**
|
||||
* Test credit card number in a first-party-origin iframe
|
||||
*/
|
||||
{
|
||||
description:
|
||||
"Fill cc-number in a first-party-origin iframe when the autofill is triggered in another first-party-origin iframe",
|
||||
fixtureData: `
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\"></iframe>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-name",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: TEST_PROFILE["cc-number"] },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Do not fill cc-number in a first-party-origin iframe when autofill is triggered in a third-party iframe",
|
||||
fixtureData: `
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NAME}\"></iframe>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-name",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: "" },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Fill cc-number in a first-party-origin iframe when the autofill is triggered in the main-frame",
|
||||
fixtureData: `
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<p><label>Card Name: <input id="cc-name" autocomplete="cc-name"></label></p>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-name",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: TEST_PROFILE["cc-number"] },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
/**
|
||||
* Test credit card number is in a third-party iframe
|
||||
*/
|
||||
{
|
||||
description:
|
||||
"Do not fill cc-number in a third-party-origin iframe when the autofill is triggered in a first-party-origin iframe",
|
||||
fixtureData: `
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\"></iframe>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-name",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: "" },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Do not fill cc-number in a third-party-origin iframe when the autofill is triggered in cross-origin third-party-origin iframe",
|
||||
fixtureData: `
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_2_CC_NAME}\"></iframe>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-name",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: "" },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Do not fill cc-number in a third-party-origin iframe when the autofill is triggered in the main-iframe",
|
||||
fixtureData: `
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<p><label>Card Name: <input id="cc-name" autocomplete="cc-name"></label></p>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-name",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: "" },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Fill cc-number in a third-party-origin iframe when the autofill is triggered in a same-origin third-party-origin iframe",
|
||||
fixtureData: `
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NAME}\"></iframe>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-name",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: TEST_PROFILE["cc-number"] },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
/**
|
||||
* Test cases when relaxed restriction is applied
|
||||
*/
|
||||
{
|
||||
description:
|
||||
"Do not fill cc-number in a main-frame when the autofill is triggered in a third-party-origin iframe even when the relaxed restriction is applied",
|
||||
fixtureData: `
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NAME}\"></iframe>
|
||||
`,
|
||||
prefs: [
|
||||
["extensions.formautofill.heuristics.autofillSameOriginWithTop", true],
|
||||
],
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-name",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: "" },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Do not fill cc-number in a first-party-origin iframe when autofill is triggered in a third-party iframe even when the relaxed restriction is applied",
|
||||
fixtureData: `
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NAME}\"></iframe>
|
||||
`,
|
||||
prefs: [
|
||||
["extensions.formautofill.heuristics.autofillSameOriginWithTop", true],
|
||||
],
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-name",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: "" },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Do not fill cc-number in a third-party-origin iframe when the autofill is triggered in cross-origin third-party-origin iframe even when the relaxed restriction is applied",
|
||||
fixtureData: `
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_2_CC_NAME}\"></iframe>
|
||||
`,
|
||||
prefs: [
|
||||
["extensions.formautofill.heuristics.autofillSameOriginWithTop", true],
|
||||
],
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-name",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: "" },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Do not fill cc-number in a third-party-origin iframe when the autofill is triggered in the main-iframe even when the relaxed restriction is applied",
|
||||
fixtureData: `
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<p><label>Card Name: <input id="cc-name" autocomplete="cc-name"></label></p>
|
||||
`,
|
||||
prefs: [
|
||||
["extensions.formautofill.heuristics.autofillSameOriginWithTop", true],
|
||||
],
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-name",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: "" },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
|
@ -0,0 +1,164 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global add_autofill_heuristic_tests */
|
||||
|
||||
const TEST_PROFILE = {
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
// "cc-type" should be remove from proile after fixing Bug 1834768.
|
||||
"cc-type": "visa",
|
||||
"cc-exp-month": "04",
|
||||
"cc-exp-year": new Date().getFullYear(),
|
||||
};
|
||||
|
||||
add_setup(async function () {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["ui.popup.disable_autohide", true],
|
||||
["extensions.formautofill.creditCards.supported", "on"],
|
||||
["extensions.formautofill.creditCards.enabled", true],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
add_autofill_heuristic_tests([
|
||||
{
|
||||
description:
|
||||
"Trigger autofill in the main-frame, do not autofill into sandboxed iframe",
|
||||
fixtureData: `
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<iframe sandbox src=\"${SAME_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe sandbox src=\"${CROSS_ORIGIN_CC_EXP}\"></iframe>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-number",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: TEST_PROFILE["cc-number"] },
|
||||
{ fieldName: "cc-name", autofill: "" },
|
||||
{ fieldName: "cc-exp", autofill: "" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Trigger autofill in a first-party-origin iframe, do not autofill into sandboxed iframe",
|
||||
fixtureData: `
|
||||
<iframe sandbox src=\"${SAME_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<p><label>Card Name: <input id="cc-name" autocomplete="cc-name"></label></p>
|
||||
<iframe sandbox src=\"${SAME_ORIGIN_CC_EXP}\"></iframe>
|
||||
<iframe sandbox src=\"${CROSS_ORIGIN_CC_TYPE}\"></iframe>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-number",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: TEST_PROFILE["cc-number"] },
|
||||
{ fieldName: "cc-name", autofill: "" },
|
||||
{ fieldName: "cc-exp", autofill: "" },
|
||||
{ fieldName: "cc-type", autofill: "" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Trigger autofill in a sandboxed first-party-origin iframe, do not autofill into iframe",
|
||||
fixtureData: `
|
||||
<iframe sandbox src=\"${SAME_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<p><label>Card Name: <input id="cc-name" autocomplete="cc-name"></label></p>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_EXP}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_TYPE}\"></iframe>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-number",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: TEST_PROFILE["cc-number"] },
|
||||
{ fieldName: "cc-name", autofill: "" },
|
||||
{ fieldName: "cc-exp", autofill: "" },
|
||||
{ fieldName: "cc-type", autofill: "" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Trigger autofill in a third-party-origin iframe, do not autofill into sandboxed other iframes",
|
||||
fixtureData: `
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<p><label>Card Name: <input id="cc-name" autocomplete="cc-name"></label></p>
|
||||
<iframe sandbox src=\"${SAME_ORIGIN_CC_EXP}\"></iframe>
|
||||
<iframe sandbox src=\"${CROSS_ORIGIN_CC_TYPE}\"></iframe>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-number",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: TEST_PROFILE["cc-number"] },
|
||||
{ fieldName: "cc-name", autofill: "" },
|
||||
{ fieldName: "cc-exp", autofill: "" },
|
||||
{ fieldName: "cc-type", autofill: "" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Trigger autofill in a sandboxed third-party-origin iframe, do not autofill into other iframes",
|
||||
fixtureData: `
|
||||
<iframe sandbox src=\"${CROSS_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<p><label>Card Name: <input id="cc-name" autocomplete="cc-name"></label></p>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_EXP}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_TYPE}\"></iframe>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-number",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: TEST_PROFILE["cc-number"] },
|
||||
{ fieldName: "cc-name", autofill: "" },
|
||||
{ fieldName: "cc-exp", autofill: "" },
|
||||
{ fieldName: "cc-type", autofill: "" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Relaxed restriction applied - Trigger autofill in a sandboxed third-party-origin iframe, do not autofill into other iframes",
|
||||
fixtureData: `
|
||||
<iframe sandbox src=\"${CROSS_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<p><label>Card Name: <input id="cc-name" autocomplete="cc-name"></label></p>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_EXP}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_TYPE}\"></iframe>
|
||||
`,
|
||||
prefs: [
|
||||
["extensions.formautofill.heuristics.autofillSameOriginWithTop", true],
|
||||
],
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-number",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: TEST_PROFILE["cc-number"] },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
{
|
||||
fieldName: "cc-exp",
|
||||
autofill: `${TEST_PROFILE["cc-exp-month"]}/${TEST_PROFILE["cc-exp-year"]}`,
|
||||
},
|
||||
{ fieldName: "cc-type", autofill: "" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
|
@ -0,0 +1,252 @@
|
|||
/* global add_capture_heuristic_tests */
|
||||
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_setup(async function () {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["extensions.formautofill.creditCards.supported", "on"],
|
||||
["extensions.formautofill.creditCards.enabled", true],
|
||||
["extensions.formautofill.addresses.capture.requiredFields", ""],
|
||||
["extensions.formautofill.loglevel", "Debug"],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
const CAPTURE_FILL_VALUE = {
|
||||
"#cc-number": "4111111111111111",
|
||||
"#cc-name": "John Doe",
|
||||
"#cc-exp": `04/${new Date().getFullYear()}`,
|
||||
};
|
||||
|
||||
const CAPTURE_EXPECTED_RECORD = {
|
||||
"cc-number": "************1111",
|
||||
"cc-name": "John Doe",
|
||||
"cc-exp-month": 4,
|
||||
"cc-exp-year": new Date().getFullYear(),
|
||||
"cc-type": "visa",
|
||||
};
|
||||
|
||||
const CAPTURE_FILL_VALUE_1 = {
|
||||
"#form1 #cc-number": "378282246310005",
|
||||
"#form1 #cc-name": "Timothy Berners-Lee",
|
||||
"#form1 #cc-exp": `07/${new Date().getFullYear() - 1}`,
|
||||
};
|
||||
|
||||
const CAPTURE_EXPECTED_RECORD_1 = {
|
||||
"cc-number": "***********0005",
|
||||
"cc-name": "Timothy Berners-Lee",
|
||||
"cc-exp-month": 7,
|
||||
"cc-exp-year": new Date().getFullYear() - 1,
|
||||
"cc-type": "amex",
|
||||
};
|
||||
|
||||
const CAPTURE_FILL_VALUE_2 = {
|
||||
"#form2 #cc-number": "5555555555554444",
|
||||
"#form2 #cc-name": "Jane Doe",
|
||||
"#form2 #cc-exp": `12/${new Date().getFullYear() + 1}`,
|
||||
};
|
||||
|
||||
const CAPTURE_EXPECTED_RECORD_2 = {
|
||||
"cc-number": "************4444",
|
||||
"cc-name": "Jane Doe",
|
||||
"cc-exp-month": 12,
|
||||
"cc-exp-year": new Date().getFullYear() + 1,
|
||||
"cc-type": "mastercard",
|
||||
};
|
||||
|
||||
add_capture_heuristic_tests([
|
||||
{
|
||||
description: `All fields are in the same same-origin iframe`,
|
||||
fixtureData: `
|
||||
<form id="form1">
|
||||
<iframe src=${SAME_ORIGIN_ALL_FIELDS}></iframe>
|
||||
<input id="submit" type="submit">
|
||||
</form>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
captureFillValue: CAPTURE_FILL_VALUE,
|
||||
captureExpectedRecord: CAPTURE_EXPECTED_RECORD,
|
||||
},
|
||||
{
|
||||
description: `All fields are in the same cross-origin iframe`,
|
||||
fixtureData: `
|
||||
<form id="form1">
|
||||
<iframe src=${CROSS_ORIGIN_ALL_FIELDS}></iframe>
|
||||
<input id="submit" type="submit">
|
||||
</form>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
captureFillValue: CAPTURE_FILL_VALUE,
|
||||
captureExpectedRecord: CAPTURE_EXPECTED_RECORD,
|
||||
},
|
||||
{
|
||||
description:
|
||||
"One main-frame, one same-origin iframe and one cross-origin iframe",
|
||||
fixtureData: `
|
||||
<form id="form1">
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_EXP}\"></iframe>
|
||||
<input id="submit" type="submit">
|
||||
</form>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
captureFillValue: CAPTURE_FILL_VALUE,
|
||||
captureExpectedRecord: CAPTURE_EXPECTED_RECORD,
|
||||
},
|
||||
{
|
||||
description: `Every field is in its own same-origin iframe`,
|
||||
fixtureData: `
|
||||
<form id="form1">
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_EXP}\"></iframe>
|
||||
<input id="submit" type="submit">
|
||||
</form>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
captureFillValue: CAPTURE_FILL_VALUE,
|
||||
captureExpectedRecord: CAPTURE_EXPECTED_RECORD,
|
||||
},
|
||||
{
|
||||
description: `Every field is in its own corss-origin iframe`,
|
||||
fixtureData: `
|
||||
<form id="form1">
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_EXP}\"></iframe>
|
||||
<input id="submit" type="submit">
|
||||
</form>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
captureFillValue: CAPTURE_FILL_VALUE,
|
||||
captureExpectedRecord: CAPTURE_EXPECTED_RECORD,
|
||||
},
|
||||
{
|
||||
description: `Two forms, submit the cross-origin form`,
|
||||
fixtureData: `
|
||||
<form>
|
||||
<iframe src=${SAME_ORIGIN_ALL_FIELDS}?formId=form1></iframe>
|
||||
</form>
|
||||
<form>
|
||||
<iframe src=${CROSS_ORIGIN_ALL_FIELDS}?formId=form2></iframe>
|
||||
</form>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
submitButtonSelector: "#form2 input[type=submit]",
|
||||
captureFillValue: { ...CAPTURE_FILL_VALUE_1, ...CAPTURE_FILL_VALUE_2 },
|
||||
captureExpectedRecord: CAPTURE_EXPECTED_RECORD_2,
|
||||
},
|
||||
{
|
||||
description: `Two forms, submit the same-origin form`,
|
||||
fixtureData: `
|
||||
<form>
|
||||
<iframe src=${SAME_ORIGIN_ALL_FIELDS}?formId=form1></iframe>
|
||||
</form>
|
||||
<form>
|
||||
<iframe src=${CROSS_ORIGIN_ALL_FIELDS}?formId=form2></iframe>
|
||||
</form>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
submitButtonSelector: "#form1 input[type=submit]",
|
||||
captureFillValue: { ...CAPTURE_FILL_VALUE_1, ...CAPTURE_FILL_VALUE_2 },
|
||||
captureExpectedRecord: CAPTURE_EXPECTED_RECORD_1,
|
||||
},
|
||||
{
|
||||
description:
|
||||
"One main-frame, one same-origin sandbox iframe and one cross-origin sandbox iframe",
|
||||
fixtureData: `
|
||||
<form id="form1">
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\" sandbox></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_EXP}\" sandbox></iframe>
|
||||
<input id="submit" type="submit">
|
||||
</form>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
captureFillValue: CAPTURE_FILL_VALUE,
|
||||
captureExpectedRecord: CAPTURE_EXPECTED_RECORD,
|
||||
},
|
||||
]);
|
|
@ -0,0 +1,204 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global add_heuristic_tests */
|
||||
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_PROFILE = {
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
// "cc-type" should be remove from proile after fixing Bug 1834768.
|
||||
"cc-type": "visa",
|
||||
"cc-exp-month": "04",
|
||||
"cc-exp-year": new Date().getFullYear(),
|
||||
};
|
||||
|
||||
add_setup(async function () {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["extensions.formautofill.creditCards.supported", "on"],
|
||||
["extensions.formautofill.creditCards.enabled", true],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
add_autofill_heuristic_tests([
|
||||
{
|
||||
description: `Trigger autofill in the main-frame`,
|
||||
fixtureData: `
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_EXP}\"></iframe>
|
||||
<p><label>Card Type: <select id="cc-type" autocomplete="cc-type">
|
||||
<option></option>
|
||||
<option value="discover">Discover</option>
|
||||
<option value="jcb">JCB</option>
|
||||
<option value="visa">Visa</option>
|
||||
<option value="mastercard">MasterCard</option>
|
||||
<option value="gringotts">Unknown card network</option>
|
||||
</select></label></p>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-number",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: TEST_PROFILE["cc-number"] },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
{ fieldName: "cc-exp", autofill: "" },
|
||||
{ fieldName: "cc-type", autofill: "visa" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Trigger autofill in a fist-party-origin iframe`,
|
||||
fixtureData: `
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_EXP}\"></iframe>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_TYPE}\"></iframe>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-name",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: TEST_PROFILE["cc-number"] },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
{ fieldName: "cc-exp", autofill: "" },
|
||||
{ fieldName: "cc-type", autofill: "visa" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Trigger autofill in a third-party-origin iframe`,
|
||||
fixtureData: `
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_EXP}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_TYPE}\"></iframe>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-exp",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: "" },
|
||||
{ fieldName: "cc-name", autofill: "" },
|
||||
{
|
||||
fieldName: "cc-exp",
|
||||
autofill: `${TEST_PROFILE["cc-exp-month"]}/${TEST_PROFILE["cc-exp-year"]}`,
|
||||
},
|
||||
{ fieldName: "cc-type", autofill: "visa" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Trigger autofill in a third-party-origin iframe, cc-type is in another third-party-origin iframe`,
|
||||
fixtureData: `
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_EXP}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_2_CC_TYPE}\"></iframe>
|
||||
`,
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-exp",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: "" },
|
||||
{ fieldName: "cc-name", autofill: "" },
|
||||
{
|
||||
fieldName: "cc-exp",
|
||||
autofill: `${TEST_PROFILE["cc-exp-month"]}/${TEST_PROFILE["cc-exp-year"]}`,
|
||||
},
|
||||
{ fieldName: "cc-type", autofill: "" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Relaxed autofill restriction - trigger autofill in a third-party-origin iframe`,
|
||||
fixtureData: `
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_EXP}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_TYPE}\"></iframe>
|
||||
`,
|
||||
prefs: [
|
||||
["extensions.formautofill.heuristics.autofillSameOriginWithTop", true],
|
||||
],
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-exp",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: "" },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
{
|
||||
fieldName: "cc-exp",
|
||||
autofill: `${TEST_PROFILE["cc-exp-month"]}/${TEST_PROFILE["cc-exp-year"]}`,
|
||||
},
|
||||
{ fieldName: "cc-type", autofill: "visa" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Relaxed autofill restriction - Do not apply autofill to same-site iframes when autofill is triggered in a main frame`,
|
||||
fixtureData: `
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe src=\"${SAME_SITE_CC_EXP}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_TYPE}\"></iframe>
|
||||
`,
|
||||
prefs: [
|
||||
["extensions.formautofill.heuristics.autofillSameOriginWithTop", true],
|
||||
],
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-number",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: TEST_PROFILE["cc-number"] },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
{ fieldName: "cc-exp", autofill: "" },
|
||||
{ fieldName: "cc-type", autofill: "" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Relaxed autofill restriction - Do not apply autofill to same-site iframes when autofill is triggered in a same-origin iframe`,
|
||||
fixtureData: `
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe src=\"${SAME_SITE_CC_EXP}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_TYPE}\"></iframe>
|
||||
`,
|
||||
prefs: [
|
||||
["extensions.formautofill.heuristics.autofillSameOriginWithTop", true],
|
||||
],
|
||||
profile: TEST_PROFILE,
|
||||
autofillTrigger: "#cc-number",
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number", autofill: TEST_PROFILE["cc-number"] },
|
||||
{ fieldName: "cc-name", autofill: TEST_PROFILE["cc-name"] },
|
||||
{ fieldName: "cc-exp", autofill: "" },
|
||||
{ fieldName: "cc-type", autofill: "" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
|
@ -0,0 +1,184 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global add_heuristic_tests */
|
||||
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_setup(async function () {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["extensions.formautofill.creditCards.supported", "on"],
|
||||
["extensions.formautofill.creditCards.enabled", true],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
add_heuristic_tests([
|
||||
{
|
||||
description: `All fields are in the same cross-origin iframe`,
|
||||
fixtureData: `<iframe src=${CROSS_ORIGIN_ALL_FIELDS}></iframe>`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Mix cross-origin and same-origin iframe`,
|
||||
fixtureData: `
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_EXP}\"></iframe>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Mix main-frame and cross-origin iframe`,
|
||||
fixtureData: `
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<p><label>Card Name: <input id="cc-name" autocomplete="cc-name"></label></p>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_EXP}\"></iframe>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Mix main-frame, same-origin iframe, and cross-origin iframe`,
|
||||
fixtureData: `
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_EXP}\"></iframe>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Every fields is in its own cross-origin iframe`,
|
||||
fixtureData: `
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_EXP}\"></iframe>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `One same-origin iframe and one cross-origin iframe`,
|
||||
fixtureData: `
|
||||
<iframe src=${SAME_ORIGIN_ALL_FIELDS}></iframe>
|
||||
<iframe src=${CROSS_ORIGIN_ALL_FIELDS}></iframe>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Mutliple cross-origin iframes`,
|
||||
fixtureData: `
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_2_CC_EXP}\"></iframe>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Two cross-origin iframes, one of the iframe is sandboxed`,
|
||||
fixtureData: `
|
||||
<iframe src=${CROSS_ORIGIN_ALL_FIELDS}></iframe>
|
||||
<iframe src=${CROSS_ORIGIN_ALL_FIELDS} sandbox></iframe>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Every field is in its own sandboxed cross-origin iframe`,
|
||||
fixtureData: `
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NUMBER}\" sandbox></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_NAME}\" sandbox></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_EXP}\" sandbox></iframe>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
|
@ -0,0 +1,206 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
function assertGleanTelemetry(events, expected_number_of_flowid = 1) {
|
||||
let flow_ids = new Set();
|
||||
events.forEach(({ event_name, expected_extra, event_count = 1 }) => {
|
||||
const actual_events = Glean.formautofill[event_name].testGetValue() ?? [];
|
||||
|
||||
Assert.equal(
|
||||
actual_events.length,
|
||||
event_count,
|
||||
`Expected to have ${event_count} event/s with the name "${event_name}"`
|
||||
);
|
||||
|
||||
expected_extra = Array.isArray(expected_extra)
|
||||
? expected_extra
|
||||
: [expected_extra];
|
||||
for (let idx = 0; idx < actual_events.length; idx++) {
|
||||
const actual = actual_events[idx];
|
||||
const expected = expected_extra[idx];
|
||||
if (expected) {
|
||||
flow_ids.add(actual.extra.flow_id);
|
||||
delete actual.extra.flow_id; // We don't want to test the specific flow_id value yet
|
||||
|
||||
Assert.deepEqual(actual.extra, expected);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Assert.equal(
|
||||
flow_ids.size,
|
||||
expected_number_of_flowid,
|
||||
`All events from the same user interaction session have the same flow id`
|
||||
);
|
||||
}
|
||||
|
||||
add_setup(async function () {
|
||||
Services.telemetry.setEventRecordingEnabled("creditcard", true);
|
||||
registerCleanupFunction(async function () {
|
||||
Services.telemetry.setEventRecordingEnabled("creditcard", false);
|
||||
});
|
||||
await clearGleanTelemetry();
|
||||
});
|
||||
|
||||
add_heuristic_tests([
|
||||
{
|
||||
description: `All fields are in the main frame`,
|
||||
fixtureData: `
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<p><label>Card Name: <input id="cc-name" autocomplete="cc-name"></label></p>
|
||||
<p><label>Card Expiry: <input id="cc-exp" autocomplete="cc-exp"></label></p>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
async onTestComplete() {
|
||||
assertGleanTelemetry([
|
||||
{
|
||||
event_name: "iframeLayoutDetection",
|
||||
expected_extra: {
|
||||
category: "creditcard",
|
||||
iframe_count: 0,
|
||||
main_frame: "cc-number,cc-name,cc-exp",
|
||||
iframe: "",
|
||||
cross_origin: "",
|
||||
sandboxed: "",
|
||||
},
|
||||
},
|
||||
]);
|
||||
await clearGleanTelemetry();
|
||||
},
|
||||
},
|
||||
{
|
||||
description: `All fields are in the same same-origin iframe`,
|
||||
fixtureData: `<iframe src=${SAME_ORIGIN_ALL_FIELDS}></iframe>`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
async onTestComplete() {
|
||||
assertGleanTelemetry([
|
||||
{
|
||||
event_name: "iframeLayoutDetection",
|
||||
expected_extra: {
|
||||
category: "creditcard",
|
||||
iframe_count: 1,
|
||||
main_frame: "",
|
||||
iframe: "cc-number,cc-name,cc-exp",
|
||||
cross_origin: "",
|
||||
sandboxed: "",
|
||||
},
|
||||
},
|
||||
]);
|
||||
await clearGleanTelemetry();
|
||||
},
|
||||
},
|
||||
{
|
||||
description: `Mix main-frame, same-origin iframe, and cross-origin iframe`,
|
||||
fixtureData: `
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe src=\"${CROSS_ORIGIN_CC_EXP}\"></iframe>
|
||||
<iframe sandbox src=\"${CROSS_ORIGIN_CC_TYPE}\"></iframe>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
{ fieldName: "cc-type" },
|
||||
],
|
||||
},
|
||||
],
|
||||
async onTestComplete() {
|
||||
assertGleanTelemetry([
|
||||
{
|
||||
event_name: "iframeLayoutDetection",
|
||||
expected_extra: {
|
||||
category: "creditcard",
|
||||
iframe_count: 3,
|
||||
main_frame: "cc-number",
|
||||
iframe: "cc-name,cc-exp,cc-type",
|
||||
cross_origin: "cc-exp,cc-type",
|
||||
sandboxed: "cc-type",
|
||||
},
|
||||
},
|
||||
]);
|
||||
await clearGleanTelemetry();
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Test category",
|
||||
fixtureData: `
|
||||
<form>
|
||||
<input id="name" autocomplete="name" />
|
||||
<input id="country" autocomplete="country"/>
|
||||
<input id="street-address" autocomplete="street-address" />
|
||||
</form>
|
||||
<form>
|
||||
<input id="cc-name" autocomplete="cc-name" />
|
||||
<input id="cc-number" autocomplete="cc-number"/>
|
||||
<input id="cc-exp" autocomplete="cc-exp" />
|
||||
</form>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "name" },
|
||||
{ fieldName: "country" },
|
||||
{ fieldName: "street-address" },
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
async onTestComplete() {
|
||||
assertGleanTelemetry(
|
||||
[
|
||||
{
|
||||
event_name: "iframeLayoutDetection",
|
||||
event_count: 2,
|
||||
expected_extra: [
|
||||
{
|
||||
category: "address",
|
||||
iframe_count: 0,
|
||||
main_frame: "name,country,street-address",
|
||||
iframe: "",
|
||||
cross_origin: "",
|
||||
sandboxed: "",
|
||||
},
|
||||
{
|
||||
category: "creditcard",
|
||||
iframe_count: 0,
|
||||
main_frame: "cc-name,cc-number,cc-exp",
|
||||
iframe: "",
|
||||
cross_origin: "",
|
||||
sandboxed: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
2
|
||||
);
|
||||
await clearGleanTelemetry();
|
||||
},
|
||||
},
|
||||
]);
|
|
@ -0,0 +1,181 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global add_heuristic_tests */
|
||||
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_setup(async function () {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["extensions.formautofill.creditCards.supported", "on"],
|
||||
["extensions.formautofill.creditCards.enabled", true],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
add_heuristic_tests([
|
||||
{
|
||||
description: `All fields are in the same same-origin iframe`,
|
||||
fixtureData: `<iframe src=${SAME_ORIGIN_ALL_FIELDS}></iframe>`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `cc-name in the main frame, others in its own iframes`,
|
||||
fixtureData: `
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<p><label>Card Name: <input id="cc-name" autocomplete="cc-name"></label></p>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_EXP}\"></iframe>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `cc-name & cc-exp in the main frame, cc-number in an iframe`,
|
||||
fixtureData: `
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<p><label>Card Name: <input id="cc-name" autocomplete="cc-name"></label></p>
|
||||
<p><label>Card Expiration Date: <input id="cc-exp" autocomplete="cc-exp"></label></p>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Every field is in its own same-origin iframe`,
|
||||
fixtureData: `
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NUMBER}\"></iframe>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\"></iframe>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_EXP}\"></iframe>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Two same-origin iframes`,
|
||||
fixtureData: `
|
||||
<iframe src=${SAME_ORIGIN_ALL_FIELDS}></iframe>
|
||||
<iframe src=${SAME_ORIGIN_ALL_FIELDS}></iframe>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Two same-origin iframes, one of the iframe is sandboxed`,
|
||||
fixtureData: `
|
||||
<iframe src=${SAME_ORIGIN_ALL_FIELDS}></iframe>
|
||||
<iframe src=${SAME_ORIGIN_ALL_FIELDS} sandbox></iframe>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Every field is in its own sandboxed same-origin iframe`,
|
||||
fixtureData: `
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NUMBER}\" sandbox></iframe>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_NAME}\" sandbox></iframe>
|
||||
<iframe src=\"${SAME_ORIGIN_CC_EXP}\" sandbox></iframe>
|
||||
`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: `Every field is in its own sandboxed same-origin iframe`,
|
||||
fixtureData: `
|
||||
<iframe src="https://example.com/document-builder.sjs?html=
|
||||
${encodeURIComponent(`
|
||||
<input id="cc-number" autocomplete="cc-number">
|
||||
<input id="cc-name" autocomplete="cc-name">
|
||||
<input id="cc-exp" autocomplete="cc-exp">
|
||||
<input id="cc-number" autocomplete="cc-number">
|
||||
<input id="cc-name" autocomplete="cc-name">
|
||||
<input id="cc-exp-month" autocomplete="cc-exp-month">
|
||||
<input id="cc-exp-year" autocomplete="cc-exp-year">
|
||||
`)}"></iframe>`,
|
||||
expectedResult: [
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp" },
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{ fieldName: "cc-number" },
|
||||
{ fieldName: "cc-name" },
|
||||
{ fieldName: "cc-exp-month" },
|
||||
{ fieldName: "cc-exp-year" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
|
@ -18,6 +18,7 @@ const ADDRESS_VALUES = {
|
|||
add_setup(async function () {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["extensions.formautofill.loglevel", "Debug"],
|
||||
["extensions.formautofill.addresses.capture.enabled", true],
|
||||
["extensions.formautofill.addresses.supported", "on"],
|
||||
["extensions.formautofill.heuristics.captureOnPageNavigation", true],
|
||||
|
|
|
@ -96,12 +96,6 @@ skip-if = [
|
|||
["browser_creditCard_heuristics.js"]
|
||||
skip-if = ["apple_silicon && !debug"] # Bug 1714221
|
||||
|
||||
["browser_creditCard_heuristics_autofill_name.js"]
|
||||
skip-if = ["apple_silicon && !debug"] # Bug 1714221
|
||||
|
||||
["browser_creditCard_heuristics_cc_type.js"]
|
||||
skip-if = ["apple_silicon && !debug"] # Bug 1714221
|
||||
|
||||
["browser_creditCard_osAuth.js"]
|
||||
skip-if = ["os == 'linux'"]
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ add_task(async function test_active_delay() {
|
|||
// gets opened and listen for it in this test before we check if the item
|
||||
// is disabled.
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["security.notification_enable_delay", 1000]],
|
||||
set: [["security.notification_enable_delay", 2000]],
|
||||
});
|
||||
|
||||
await disableOSAuthForThisTest();
|
||||
|
@ -86,17 +86,19 @@ add_task(async function test_active_delay() {
|
|||
info(`Popup was disabled for ${delta} ms`);
|
||||
Assert.greaterOrEqual(
|
||||
delta,
|
||||
1000,
|
||||
"Popup was disabled for at least 1000 ms"
|
||||
2000,
|
||||
"Popup was disabled for at least 2000 ms"
|
||||
);
|
||||
|
||||
// Check the clicking on the menu works now
|
||||
const promise = TestUtils.topicObserved("formautofill-autofill-complete");
|
||||
firstItem.click();
|
||||
is(
|
||||
browser.autoCompletePopup.selectedIndex,
|
||||
0,
|
||||
"First item selected after clicking on enabled item"
|
||||
);
|
||||
await promise;
|
||||
|
||||
// Clean up
|
||||
await closePopup(browser);
|
||||
|
@ -106,7 +108,7 @@ add_task(async function test_active_delay() {
|
|||
|
||||
add_task(async function test_no_delay() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["security.notification_enable_delay", 1000]],
|
||||
set: [["security.notification_enable_delay", 2000]],
|
||||
});
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{ gBrowser, url: ADDRESS_URL },
|
||||
|
|
|
@ -24,6 +24,11 @@ const { FormAutofillNameUtils } = ChromeUtils.importESModule(
|
|||
"resource://gre/modules/shared/FormAutofillNameUtils.sys.mjs"
|
||||
);
|
||||
|
||||
const { VALID_ADDRESS_FIELDS, VALID_CREDIT_CARD_FIELDS } =
|
||||
ChromeUtils.importESModule(
|
||||
"resource://autofill/FormAutofillStorageBase.sys.mjs"
|
||||
);
|
||||
|
||||
const { FormAutofillUtils } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/shared/FormAutofillUtils.sys.mjs"
|
||||
);
|
||||
|
@ -57,6 +62,9 @@ const PRIVACY_PREF_URL = "about:preferences#privacy";
|
|||
|
||||
const HTTP_TEST_PATH = "/browser/browser/extensions/formautofill/test/browser/";
|
||||
const BASE_URL = "http://mochi.test:8888" + HTTP_TEST_PATH;
|
||||
const BASE_URL_HTTPS = "https://mochi.test" + HTTP_TEST_PATH;
|
||||
const CROSS_ORIGIN_BASE_URL = "https://example.org" + HTTP_TEST_PATH;
|
||||
const CROSS_ORIGIN_2_BASE_URL = "https://example.com" + HTTP_TEST_PATH;
|
||||
const FORM_URL = BASE_URL + "autocomplete_basic.html";
|
||||
const ADDRESS_FORM_URL =
|
||||
"https://example.org" +
|
||||
|
@ -94,6 +102,12 @@ const CREDITCARD_FORM_WITH_PAGE_NAVIGATION_BUTTONS =
|
|||
"creditCard/capture_creditCard_on_page_navigation.html";
|
||||
const EMPTY_URL = "https://example.org" + HTTP_TEST_PATH + "empty.html";
|
||||
|
||||
const TOP_LEVEL_HOST = "https://example.com";
|
||||
const TOP_LEVEL_URL = TOP_LEVEL_HOST + HTTP_TEST_PATH;
|
||||
const SAME_SITE_URL = "https://test1.example.com" + HTTP_TEST_PATH;
|
||||
const CROSS_ORIGIN_URL = "https://example.net" + HTTP_TEST_PATH;
|
||||
const CROSS_ORIGIN_2_URL = "https://example.org" + HTTP_TEST_PATH;
|
||||
|
||||
const ENABLED_AUTOFILL_ADDRESSES_PREF =
|
||||
"extensions.formautofill.addresses.enabled";
|
||||
const ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF =
|
||||
|
@ -113,6 +127,54 @@ const SYNC_CREDITCARDS_PREF = "services.sync.engine.creditcards";
|
|||
const SYNC_CREDITCARDS_AVAILABLE_PREF =
|
||||
"services.sync.engine.creditcards.available";
|
||||
|
||||
// For iframe autofill tests
|
||||
const SAME_ORIGIN_ALL_FIELDS =
|
||||
TOP_LEVEL_URL + "../fixtures/autocomplete_cc_mandatory_embeded.html";
|
||||
const SAME_ORIGIN_CC_NUMBER =
|
||||
TOP_LEVEL_URL + "../fixtures/autocomplete_cc_number_embeded.html";
|
||||
const SAME_ORIGIN_CC_NAME =
|
||||
TOP_LEVEL_URL + "../fixtures/autocomplete_cc_name_embeded.html";
|
||||
const SAME_ORIGIN_CC_EXP =
|
||||
TOP_LEVEL_URL + "../fixtures/autocomplete_cc_exp_embeded.html";
|
||||
const SAME_ORIGIN_CC_TYPE =
|
||||
TOP_LEVEL_URL + "../fixtures/autocomplete_cc_type_embeded.html";
|
||||
|
||||
const SAME_SITE_ALL_FIELDS =
|
||||
SAME_SITE_URL + "../fixtures/autocomplete_cc_mandatory_embeded.html";
|
||||
const SAME_SITE_CC_NUMBER =
|
||||
SAME_SITE_URL + "../fixtures/autocomplete_cc_number_embeded.html";
|
||||
const SAME_SITE_CC_NAME =
|
||||
SAME_SITE_URL + "../fixtures/autocomplete_cc_name_embeded.html";
|
||||
const SAME_SITE_CC_EXP =
|
||||
SAME_SITE_URL + "../fixtures/autocomplete_cc_exp_embeded.html";
|
||||
const SAME_SITE_CC_TYPE =
|
||||
SAME_SITE_URL + "../fixtures/autocomplete_cc_type_embeded.html";
|
||||
|
||||
const CROSS_ORIGIN_ALL_FIELDS =
|
||||
CROSS_ORIGIN_URL + "../fixtures/autocomplete_cc_mandatory_embeded.html";
|
||||
const CROSS_ORIGIN_CC_NUMBER =
|
||||
CROSS_ORIGIN_URL + "../fixtures/autocomplete_cc_number_embeded.html";
|
||||
const CROSS_ORIGIN_CC_NAME =
|
||||
CROSS_ORIGIN_URL + "../fixtures/autocomplete_cc_name_embeded.html";
|
||||
const CROSS_ORIGIN_CC_EXP =
|
||||
CROSS_ORIGIN_URL + "../fixtures/autocomplete_cc_exp_embeded.html";
|
||||
const CROSS_ORIGIN_CC_TYPE =
|
||||
CROSS_ORIGIN_URL + "../fixtures/autocomplete_cc_type_embeded.html";
|
||||
|
||||
const CROSS_ORIGIN_2_ALL_FIELDS =
|
||||
CROSS_ORIGIN_2_URL +
|
||||
"../fixtures/" +
|
||||
"autocomplete_cc_mandatory_embeded.html";
|
||||
const CROSS_ORIGIN_2_CC_NUMBER =
|
||||
CROSS_ORIGIN_2_URL + "../fixtures/autocomplete_cc_number_embeded.html";
|
||||
const CROSS_ORIGIN_2_CC_NAME =
|
||||
CROSS_ORIGIN_2_URL + "../fixtures/autocomplete_cc_name_embeded.html";
|
||||
const CROSS_ORIGIN_2_CC_EXP =
|
||||
CROSS_ORIGIN_2_URL + "../fixtures/autocomplete_cc_exp_embeded.html";
|
||||
const CROSS_ORIGIN_2_CC_TYPE =
|
||||
CROSS_ORIGIN_2_URL + "../fixtures/autocomplete_cc_type_embeded.html";
|
||||
|
||||
// Test profiles
|
||||
const TEST_ADDRESS_1 = {
|
||||
"given-name": "John",
|
||||
"additional-name": "R.",
|
||||
|
@ -410,6 +472,8 @@ async function focusUpdateSubmitForm(target, args, submit = true) {
|
|||
} else {
|
||||
form = content.document.getElementById(obj.formId ?? "form");
|
||||
}
|
||||
form ||= content.document;
|
||||
|
||||
let element = form.querySelector(obj.focusSelector);
|
||||
if (element != content.document.activeElement) {
|
||||
info(`focus on element (id=${element.id})`);
|
||||
|
@ -455,6 +519,7 @@ async function focusUpdateSubmitForm(target, args, submit = true) {
|
|||
} else {
|
||||
form = content.document.getElementById(obj.formId ?? "form");
|
||||
}
|
||||
form ||= content.document;
|
||||
info(`submit form (id=${form.id})`);
|
||||
form.querySelector("input[type=submit]").click();
|
||||
});
|
||||
|
@ -524,7 +589,7 @@ async function focusAndWaitForFieldsIdentified(browserOrContext, selector) {
|
|||
if (previouslyIdentified) {
|
||||
info("previouslyIdentified");
|
||||
FormAutofillParent.removeMessageObserver(fieldsIdentifiedObserver);
|
||||
return;
|
||||
return previouslyFocused;
|
||||
}
|
||||
|
||||
// Wait 500ms to ensure that "markAsAutofillField" is completely finished.
|
||||
|
@ -541,6 +606,8 @@ async function focusAndWaitForFieldsIdentified(browserOrContext, selector) {
|
|||
content.document.activeElement
|
||||
).setAttribute("test-formautofill-identified", "true");
|
||||
});
|
||||
|
||||
return previouslyFocused;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -589,34 +656,37 @@ async function waitForAutoCompletePopupOpen(browser, taskFunction) {
|
|||
}
|
||||
|
||||
async function openPopupOn(browser, selector) {
|
||||
const popupOpenPromise = waitForAutoCompletePopupOpen(browser);
|
||||
await SimpleTest.promiseFocus(browser);
|
||||
|
||||
await runAndWaitForAutocompletePopupOpen(browser, async () => {
|
||||
await focusAndWaitForFieldsIdentified(browser, selector);
|
||||
if (!selector.includes("cc-")) {
|
||||
const previouslyFocused = await focusAndWaitForFieldsIdentified(
|
||||
browser,
|
||||
selector
|
||||
);
|
||||
// If the field is already focused, we need to send a key event to
|
||||
// open the popup
|
||||
if (previouslyFocused || !selector.includes("cc-")) {
|
||||
info(`openPopupOn: before VK_DOWN on ${selector}`);
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
|
||||
}
|
||||
});
|
||||
|
||||
await popupOpenPromise;
|
||||
}
|
||||
|
||||
async function openPopupOnSubframe(browser, frameBrowsingContext, selector) {
|
||||
const popupOpenPromise = waitForAutoCompletePopupOpen(browser);
|
||||
|
||||
await SimpleTest.promiseFocus(browser);
|
||||
|
||||
await runAndWaitForAutocompletePopupOpen(browser, async () => {
|
||||
await focusAndWaitForFieldsIdentified(frameBrowsingContext, selector);
|
||||
if (!selector.includes("cc-")) {
|
||||
const previouslyFocused = await focusAndWaitForFieldsIdentified(
|
||||
frameBrowsingContext,
|
||||
selector
|
||||
);
|
||||
// If the field is already focused, we need to send a key event to
|
||||
// open the popup
|
||||
if (previouslyFocused || !selector.includes("cc-")) {
|
||||
info(`openPopupOnSubframe: before VK_DOWN on ${selector}`);
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, frameBrowsingContext);
|
||||
}
|
||||
});
|
||||
|
||||
await popupOpenPromise;
|
||||
}
|
||||
|
||||
async function closePopup(browser) {
|
||||
|
@ -638,6 +708,11 @@ async function closePopup(browser) {
|
|||
}
|
||||
|
||||
async function closePopupForSubframe(browser, frameBrowsingContext) {
|
||||
// Return if the popup isn't open.
|
||||
if (!browser.autoCompletePopup.popupOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
const popupClosePromise = BrowserTestUtils.waitForPopupEvent(
|
||||
browser.autoCompletePopup,
|
||||
"hidden"
|
||||
|
@ -893,6 +968,228 @@ function verifySectionAutofillResult(section, result, expectedSection) {
|
|||
});
|
||||
}
|
||||
|
||||
function getSelectorFromFieldDetail(fieldDetail) {
|
||||
// identifier is set with `${element.id}/${element.name}`;
|
||||
return `#${fieldDetail.identifier.split("/")[0]}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards all recorded Glean telemetry in parent and child processes
|
||||
* and resets FOG and the Glean SDK.
|
||||
*
|
||||
* @param {boolean} onlyInParent Whether we only discard the metric data in the parent process
|
||||
*
|
||||
* Since the current method Services.fog.testResetFOG only discards metrics recorded in the parent process,
|
||||
* we would like to keep this option in our method as well.
|
||||
*/
|
||||
async function clearGleanTelemetry(onlyInParent = false) {
|
||||
if (!onlyInParent) {
|
||||
await Services.fog.testFlushAllChildren();
|
||||
}
|
||||
Services.fog.testResetFOG();
|
||||
}
|
||||
|
||||
function fillEditDoorhanger(record) {
|
||||
const notification = getNotification();
|
||||
|
||||
for (const [key, value] of Object.entries(record)) {
|
||||
const id = AddressEditDoorhanger.getInputId(key);
|
||||
const element = notification.querySelector(`#${id}`);
|
||||
element.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This function should be removed. We should make normalizeFields in
|
||||
// FormAutofillStorageBase.sys.mjs static and using it directly
|
||||
function normalizeAddressFields(record) {
|
||||
let normalized = { ...record };
|
||||
|
||||
if (normalized.name != undefined) {
|
||||
let nameParts = FormAutofillNameUtils.splitName(normalized.name);
|
||||
normalized["given-name"] = nameParts.given;
|
||||
normalized["additional-name"] = nameParts.middle;
|
||||
normalized["family-name"] = nameParts.family;
|
||||
delete normalized.name;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
async function verifyConfirmationHint(
|
||||
browser,
|
||||
forceClose,
|
||||
anchorID = "identity-icon-box"
|
||||
) {
|
||||
let hintElem = browser.ownerGlobal.ConfirmationHint._panel;
|
||||
let popupshown = BrowserTestUtils.waitForPopupEvent(hintElem, "shown");
|
||||
let popuphidden;
|
||||
|
||||
if (!forceClose) {
|
||||
popuphidden = BrowserTestUtils.waitForPopupEvent(hintElem, "hidden");
|
||||
}
|
||||
|
||||
await popupshown;
|
||||
try {
|
||||
Assert.equal(hintElem.state, "open", "hint popup is open");
|
||||
Assert.ok(
|
||||
BrowserTestUtils.isVisible(hintElem.anchorNode),
|
||||
"hint anchorNode is visible"
|
||||
);
|
||||
Assert.equal(
|
||||
hintElem.anchorNode.id,
|
||||
anchorID,
|
||||
"Hint should be anchored on the expected notification icon"
|
||||
);
|
||||
info("verifyConfirmationHint, hint is shown and has its anchorNode");
|
||||
if (forceClose) {
|
||||
await closePopup(hintElem);
|
||||
} else {
|
||||
info("verifyConfirmationHint, assertion ok, wait for poopuphidden");
|
||||
await popuphidden;
|
||||
info("verifyConfirmationHint, hintElem popup is hidden");
|
||||
}
|
||||
} catch (ex) {
|
||||
Assert.ok(false, "Confirmation hint not shown: " + ex.message);
|
||||
} finally {
|
||||
info("verifyConfirmationHint promise finalized");
|
||||
}
|
||||
}
|
||||
|
||||
async function showAddressDoorhanger(browser, values = null) {
|
||||
const defaultValues = {
|
||||
"#given-name": "John",
|
||||
"#family-name": "Doe",
|
||||
"#organization": "Mozilla",
|
||||
"#street-address": "123 Sesame Street",
|
||||
};
|
||||
|
||||
const onPopupShown = waitForPopupShown();
|
||||
const promise = BrowserTestUtils.browserLoaded(browser);
|
||||
await focusUpdateSubmitForm(browser, {
|
||||
focusSelector: "#given-name",
|
||||
newValues: values ?? defaultValues,
|
||||
});
|
||||
await promise;
|
||||
await onPopupShown;
|
||||
}
|
||||
|
||||
async function findContext(browser, selector) {
|
||||
const contexts =
|
||||
browser.browsingContext.top.getAllBrowsingContextsInSubtree();
|
||||
for (const context of contexts) {
|
||||
const find = await SpecialPowers.spawn(
|
||||
context,
|
||||
[selector],
|
||||
async selector => !!content.document.querySelector(selector)
|
||||
);
|
||||
if (find) {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function verifyCaptureRecord(guid, expectedRecord) {
|
||||
let fields;
|
||||
let record = (await getAddresses()).find(addr => addr.guid == guid);
|
||||
if (record) {
|
||||
fields = VALID_ADDRESS_FIELDS;
|
||||
} else {
|
||||
record = (await getCreditCards()).find(cc => cc.guid == guid);
|
||||
if (record) {
|
||||
fields = VALID_CREDIT_CARD_FIELDS;
|
||||
} else {
|
||||
Assert.ok(false, "Cannot find record by guid");
|
||||
}
|
||||
}
|
||||
|
||||
for (const field of fields) {
|
||||
Assert.equal(record[field], expectedRecord[field], `${field} is the same`);
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyPreviewResult(browser, section, expectedSection) {
|
||||
info(`Verify preview result`);
|
||||
const fieldDetails = section.fieldDetails;
|
||||
const expectedFieldDetails = expectedSection.fields;
|
||||
|
||||
for (let i = 0; i < fieldDetails.length; i++) {
|
||||
const selector = getSelectorFromFieldDetail(fieldDetails[i]);
|
||||
const context = await findContext(browser, selector);
|
||||
let expected = expectedFieldDetails[i].autofill ?? "";
|
||||
if (fieldDetails[i].fieldName == "cc-number" && expected.length) {
|
||||
expected = "•".repeat(expected.length - 4) + expected.slice(-4);
|
||||
}
|
||||
|
||||
await SpecialPowers.spawn(context, [{ expected, selector }], async obj => {
|
||||
const element = content.document.querySelector(obj.selector);
|
||||
if (content.HTMLSelectElement.isInstance(element)) {
|
||||
if (obj.expected) {
|
||||
for (let idx = 0; idx < element.options.length; idx++) {
|
||||
if (element.options[idx].value == obj.expected) {
|
||||
obj.expected = element.options[idx].text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
obj.expected = "";
|
||||
}
|
||||
}
|
||||
Assert.equal(
|
||||
element.previewValue,
|
||||
obj.expected,
|
||||
`element ${obj.selector} previewValue is the same ${element.previewValue}`
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyAutofillResult(browser, section, expectedSection) {
|
||||
info(`Verify autofill result`);
|
||||
const fieldDetails = section.fieldDetails;
|
||||
const expectedFieldDetails = expectedSection.fields;
|
||||
|
||||
for (let i = 0; i < fieldDetails.length; i++) {
|
||||
const selector = getSelectorFromFieldDetail(fieldDetails[i]);
|
||||
const context = await findContext(browser, selector);
|
||||
const expected = expectedFieldDetails[i].autofill ?? "";
|
||||
await SpecialPowers.spawn(context, [{ expected, selector }], async obj => {
|
||||
const element = content.document.querySelector(obj.selector);
|
||||
if (content.HTMLSelectElement.isInstance(element)) {
|
||||
if (!obj.expected) {
|
||||
obj.expected = element.options[0].value;
|
||||
}
|
||||
}
|
||||
Assert.equal(
|
||||
element.value,
|
||||
obj.expected,
|
||||
`element ${obj.selector} value is the same ${element.value}`
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyClearResult(browser, section) {
|
||||
info(`Verify clear form result`);
|
||||
const fieldDetails = section.fieldDetails;
|
||||
|
||||
for (let i = 0; i < fieldDetails.length; i++) {
|
||||
const selector = getSelectorFromFieldDetail(fieldDetails[i]);
|
||||
const context = await findContext(browser, selector);
|
||||
const expected = "";
|
||||
await SpecialPowers.spawn(context, [{ expected, selector }], async obj => {
|
||||
const element = content.document.querySelector(obj.selector);
|
||||
if (content.HTMLSelectElement.isInstance(element)) {
|
||||
obj.expected = element.options[0].value;
|
||||
}
|
||||
Assert.equal(
|
||||
element.value,
|
||||
obj.expected,
|
||||
`element ${obj.selector} value is the same ${element.value}`
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function verifySectionFieldDetails(sections, expectedSectionsInfo) {
|
||||
sections.forEach((section, index) => {
|
||||
const expectedSection = expectedSectionsInfo[index];
|
||||
|
@ -956,35 +1253,149 @@ function verifySectionFieldDetails(sections, expectedSectionsInfo) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards all recorded Glean telemetry in parent and child processes
|
||||
* and resets FOG and the Glean SDK.
|
||||
*
|
||||
* @param {boolean} onlyInParent Whether we only discard the metric data in the parent process
|
||||
*
|
||||
* Since the current method Services.fog.testResetFOG only discards metrics recorded in the parent process,
|
||||
* we would like to keep this option in our method as well.
|
||||
*/
|
||||
async function clearGleanTelemetry(onlyInParent = false) {
|
||||
if (!onlyInParent) {
|
||||
await Services.fog.testFlushAllChildren();
|
||||
async function triggerAutofillAndPreview(
|
||||
browser,
|
||||
selector,
|
||||
previewCallback,
|
||||
autofillCallback,
|
||||
clearCallback
|
||||
) {
|
||||
const focusedContext = await findContext(browser, selector);
|
||||
|
||||
if (focusedContext == focusedContext.top) {
|
||||
info(`Open the popup`);
|
||||
await openPopupOn(browser, selector);
|
||||
} else {
|
||||
info(`Open the popup on subframe`);
|
||||
await openPopupOnSubframe(browser, focusedContext, selector);
|
||||
}
|
||||
Services.fog.testResetFOG();
|
||||
|
||||
// Preview
|
||||
info(`Send key down to trigger preview`);
|
||||
let promise = TestUtils.topicObserved("formautofill-preview-complete");
|
||||
const firstItem = getDisplayedPopupItems(browser)[0];
|
||||
if (!firstItem.selected) {
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, focusedContext);
|
||||
}
|
||||
await promise;
|
||||
await previewCallback();
|
||||
|
||||
// Autofill
|
||||
info(`Send key return to trigger autofill`);
|
||||
|
||||
promise = TestUtils.topicObserved("formautofill-autofill-complete");
|
||||
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, focusedContext);
|
||||
|
||||
await promise;
|
||||
await autofillCallback();
|
||||
|
||||
// Clear Form
|
||||
if (focusedContext == focusedContext.top) {
|
||||
info(`Open the popup again for clearing form`);
|
||||
await openPopupOn(browser, selector);
|
||||
} else {
|
||||
info(`Open the popup on subframe again for clearing form`);
|
||||
await openPopupOnSubframe(browser, focusedContext, selector);
|
||||
}
|
||||
|
||||
info(`Send key down and return to clear form`);
|
||||
promise = TestUtils.topicObserved("formautofill-clear-form-complete");
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, focusedContext);
|
||||
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, focusedContext);
|
||||
await promise;
|
||||
await clearCallback();
|
||||
}
|
||||
|
||||
async function triggerCapture(browser, submitButtonSelector, fillSelectors) {
|
||||
for (const [selector, value] of Object.entries(fillSelectors)) {
|
||||
const context = await findContext(browser, selector);
|
||||
await SpecialPowers.spawn(context, [{ selector, value }], obj => {
|
||||
const element = content.document.querySelector(obj.selector);
|
||||
if (content.HTMLInputElement.isInstance(element)) {
|
||||
element.setUserInput(obj.value);
|
||||
} else if (
|
||||
content.HTMLSelectElement.isInstance(element) &&
|
||||
Array.isArray(obj.value)
|
||||
) {
|
||||
element.multiple = true;
|
||||
[...element.options].forEach(option => {
|
||||
option.selected = obj.value.includes(option.value);
|
||||
});
|
||||
} else {
|
||||
element.value = obj.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const onAdded = waitForStorageChangedEvents("add");
|
||||
const onPopupShown = waitForPopupShown();
|
||||
submitButtonSelector ||= "input[type=submit]";
|
||||
const context = await findContext(browser, submitButtonSelector);
|
||||
await SpecialPowers.spawn(context, [submitButtonSelector], selector => {
|
||||
content.document.querySelector(selector).click();
|
||||
});
|
||||
await onPopupShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
|
||||
const [subject] = (await onAdded)[0];
|
||||
return subject.wrappedJSObject.guid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs heuristics test for form autofill on given patterns.
|
||||
*
|
||||
* @param {Array<object>} patterns - An array of test patterns to run the heuristics test on.
|
||||
* @param {string} pattern.description - Description of this heuristic test
|
||||
* @param {string} pattern.fixurePath - The path of the test document
|
||||
* @param {string} pattern.fixureData - Test document by string. Use either fixurePath or fixtureData.
|
||||
* @param {object} pattern.profile - The profile to autofill. This is required only when running autofill test
|
||||
* @param {Array} pattern.expectedResult - The expected result of this heuristic test. See below for detailed explanation
|
||||
* @param {Array<object>} patterns
|
||||
* An array of test patterns to run the heuristics test on.
|
||||
* @param {string} patterns.description
|
||||
* Description of this heuristic test
|
||||
* @param {string} patterns.fixurePath
|
||||
* The path of the test document
|
||||
* @param {string} patterns.fixureData
|
||||
* Test document by string. Use either fixurePath or fixtureData.
|
||||
* @param {Array} patterns.prefs
|
||||
* Array of preferences to be set before running the test.
|
||||
* @param {object} patterns.profile
|
||||
* The profile to autofill. This is required only when running autofill test
|
||||
* @param {Array} patterns.expectedResult
|
||||
* The expected result of this heuristic test. See below for detailed explanation
|
||||
* @param {Function} patterns.onTestComplete
|
||||
* Function that is executed when the test is complete. This can be used by the test
|
||||
* to verify the status after running the test.
|
||||
*
|
||||
* @param {string} patterns.autofillTrigger
|
||||
* The selector to find the element to trigger the autocomplete popup.
|
||||
* Currently we only supports id selector so the value must start with `#`.
|
||||
* This parameter is only used when `options.testAutofill` is set.
|
||||
*
|
||||
* @param {string} patterns.submitButtonSelector
|
||||
* The selector to find the submit button for capture test. This parameter
|
||||
* is only used when `options.testCapture` is set.
|
||||
* @param {object} patterns.captureFillValue
|
||||
* An object that is keyed by selector, and the value to be set for the element
|
||||
* that is found by matching selector before submitting the form. This parameter
|
||||
* is only used when `options.testCapture` is set.
|
||||
* @param {object} patterns.captureExpectedRecord
|
||||
* The expected saved record after capturing the form. Keyed by field name. This
|
||||
* parameter is only used when `options.testCapture` is set.
|
||||
* @param {object} patterns.only
|
||||
* This parameter is used solely for debugging purposes. When set to true,
|
||||
* it restricts the execution to only the specified testcase.
|
||||
*
|
||||
* @param {string} [fixturePathPrefix=""]
|
||||
* The prefix to the path of fixture files.
|
||||
* @param {object} [options={ testAutofill: false, testCapture: false }]
|
||||
* An options object containing additional configuration for running the test.
|
||||
* @param {boolean} [options.testAutofill]
|
||||
* When set to true, the following tests will be run:
|
||||
* 1. Trigger preview and verify the preview result
|
||||
* 2. Trigger autofill and verify the autofill result
|
||||
* 3. Trigger clear form and verify the clear result
|
||||
* @param {boolean} [options.testCapture]
|
||||
* When set to true, the test submits the form after autofilling test finishes.
|
||||
* Before submitting the form, the test first filles value if `captureFillValue`
|
||||
* is set then submits the form. This test then verifies that the capture
|
||||
* doorhanger appears, and the doorhanger captures the expected value (captureExpectedRecord).
|
||||
*
|
||||
* @param {string} [fixturePathPrefix=""] - The prefix to the path of fixture files.
|
||||
* @param {object} [options={ testAutofill: false }] - An options object containing additional configuration for running the test.
|
||||
* @param {boolean} [options.testAutofill=false] - A boolean indicating whether to run the test for autofill or not.
|
||||
* @returns {Promise} A promise that resolves when all the tests are completed.
|
||||
*
|
||||
* The `patterns.expectedResult` array contains test data for different address or credit card sections.
|
||||
|
@ -1037,29 +1448,26 @@ async function clearGleanTelemetry(onlyInParent = false) {
|
|||
* }
|
||||
* ],
|
||||
* "/fixturepath",
|
||||
* {testAutofill: true} // test options
|
||||
* {
|
||||
* testAutofill: true,
|
||||
* testCapture: true,
|
||||
* } // test options
|
||||
* )
|
||||
*/
|
||||
|
||||
async function add_heuristic_tests(
|
||||
patterns,
|
||||
fixturePathPrefix = "",
|
||||
options = { testAutofill: false }
|
||||
options = { testAutofill: false, testCapture: false }
|
||||
) {
|
||||
async function runTest(testPattern) {
|
||||
const TEST_URL = testPattern.fixtureData
|
||||
? `data:text/html,${testPattern.fixtureData}`
|
||||
? TOP_LEVEL_HOST +
|
||||
`/document-builder.sjs?html=${encodeURIComponent(
|
||||
testPattern.fixtureData
|
||||
)}`
|
||||
: `${BASE_URL}../${fixturePathPrefix}${testPattern.fixturePath}`;
|
||||
|
||||
if (testPattern.fixtureData) {
|
||||
info(`Starting test with fixture data`);
|
||||
} else {
|
||||
info(`Starting test fixture: ${testPattern.fixturePath ?? ""}`);
|
||||
}
|
||||
|
||||
if (testPattern.description) {
|
||||
info(`Test "${testPattern.description}"`);
|
||||
}
|
||||
info(`Test "${testPattern.description}"`);
|
||||
|
||||
if (testPattern.prefs) {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
|
@ -1067,139 +1475,154 @@ async function add_heuristic_tests(
|
|||
});
|
||||
}
|
||||
|
||||
await BrowserTestUtils.withNewTab(TEST_URL, async browser => {
|
||||
await SpecialPowers.spawn(browser, [], async function () {
|
||||
const elements = Array.from(
|
||||
content.document.querySelectorAll("input, select")
|
||||
);
|
||||
// Focus on each field in the test document to trigger autofill field detection
|
||||
// on all the fields.
|
||||
elements.forEach(element => element.focus());
|
||||
});
|
||||
if (testPattern.profile) {
|
||||
await setStorage(testPattern.profile);
|
||||
}
|
||||
|
||||
await BrowserTestUtils.withNewTab(TEST_URL, async browser => {
|
||||
await SimpleTest.promiseFocus(browser);
|
||||
|
||||
info(`Focus on each field in the test document`);
|
||||
const contexts =
|
||||
browser.browsingContext.getAllBrowsingContextsInSubtree();
|
||||
for (const context of contexts) {
|
||||
await SpecialPowers.spawn(context, [], async function () {
|
||||
const elements = Array.from(
|
||||
content.document.querySelectorAll("input, select")
|
||||
);
|
||||
// Focus on each field in the test document to trigger autofill field detection
|
||||
// on all the fields.
|
||||
elements.forEach(element => {
|
||||
element.focus();
|
||||
});
|
||||
});
|
||||
await BrowserTestUtils.synthesizeKey("VK_ESCAPE", {}, context);
|
||||
}
|
||||
|
||||
// This is a workaround for when we set focus on elements across iframes (in the previous step).
|
||||
// The popup is not refreshed, and consequently, it does not receive key events needed to trigger
|
||||
// the autocomplete popup.
|
||||
if (contexts.length > 1) {
|
||||
await sleep();
|
||||
}
|
||||
|
||||
info(`Waiting for expected section count`);
|
||||
const actor =
|
||||
browser.browsingContext.currentWindowGlobal.getActor("FormAutofill");
|
||||
await BrowserTestUtils.waitForCondition(() => {
|
||||
return actor.getSections().length == testPattern.expectedResult.length;
|
||||
const sections = Array.from(actor.sectionsByRootId.values()).flat();
|
||||
return sections.length == testPattern.expectedResult.length;
|
||||
}, "Expected section count.");
|
||||
|
||||
// Verify the identified fields in each section.
|
||||
const sections = actor.getSections();
|
||||
info(`Verify the identified fields in each section`);
|
||||
const sections = Array.from(actor.sectionsByRootId.values()).flat();
|
||||
verifySectionFieldDetails(sections, testPattern.expectedResult);
|
||||
|
||||
// Verify the autofilled value.
|
||||
if (options.testAutofill) {
|
||||
for (let index = 0; index < sections.length; index++) {
|
||||
const section = sections[index];
|
||||
// We do not autofill for sections that are invalid.
|
||||
if (!section.isValidSection()) {
|
||||
continue;
|
||||
info(`test preview, autofill, and clear form`);
|
||||
let section;
|
||||
let autofillTrigger = testPattern.autofillTrigger;
|
||||
if (autofillTrigger) {
|
||||
if (!autofillTrigger.startsWith("#")) {
|
||||
Assert.ok(false, `autofillTrigger must start with #`);
|
||||
}
|
||||
|
||||
// Trigger autofill from the first element of this section
|
||||
const elementId = section.fieldDetails[0].elementId;
|
||||
const result = await actor.autofillFields(
|
||||
elementId,
|
||||
testPattern.profile
|
||||
);
|
||||
|
||||
verifySectionAutofillResult(
|
||||
section,
|
||||
result,
|
||||
testPattern.expectedResult[index]
|
||||
section = sections.find(s =>
|
||||
s.fieldDetails.some(f =>
|
||||
f.identifier.startsWith(autofillTrigger.substr(1))
|
||||
)
|
||||
);
|
||||
} else {
|
||||
section = sections[0];
|
||||
autofillTrigger = getSelectorFromFieldDetail(section.fieldDetails[0]);
|
||||
}
|
||||
|
||||
const expected = testPattern.expectedResult[sections.indexOf(section)];
|
||||
|
||||
await triggerAutofillAndPreview(
|
||||
browser,
|
||||
autofillTrigger,
|
||||
async () => verifyPreviewResult(browser, section, expected),
|
||||
async () => verifyAutofillResult(browser, section, expected),
|
||||
async () => verifyClearResult(browser, section)
|
||||
);
|
||||
}
|
||||
|
||||
if (options.testCapture) {
|
||||
info(`test capture`);
|
||||
const guid = await triggerCapture(
|
||||
browser,
|
||||
testPattern.submitButtonSelector,
|
||||
testPattern.captureFillValue
|
||||
);
|
||||
verifyCaptureRecord(guid, testPattern.captureExpectedRecord);
|
||||
await removeAllRecords();
|
||||
}
|
||||
});
|
||||
|
||||
if (testPattern.onTestComplete) {
|
||||
await testPattern.onTestComplete();
|
||||
}
|
||||
|
||||
if (testPattern.profile) {
|
||||
await removeAllRecords();
|
||||
}
|
||||
|
||||
if (testPattern.prefs) {
|
||||
await SpecialPowers.popPrefEnv();
|
||||
}
|
||||
}
|
||||
|
||||
const only = patterns.find(pattern => !!pattern.only);
|
||||
if (only) {
|
||||
add_task(() => runTest(only));
|
||||
return;
|
||||
}
|
||||
|
||||
patterns.forEach(testPattern => {
|
||||
add_task(() => runTest(testPattern));
|
||||
});
|
||||
}
|
||||
|
||||
async function add_autofill_heuristic_tests(patterns, fixturePathPrefix = "") {
|
||||
add_heuristic_tests(patterns, fixturePathPrefix, { testAutofill: true });
|
||||
}
|
||||
async function add_capture_heuristic_tests(patterns, fixturePathPrefix = "") {
|
||||
const oldValue = FormAutofillUtils.getOSAuthEnabled(
|
||||
FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF
|
||||
);
|
||||
|
||||
function fillEditDoorhanger(record) {
|
||||
const notification = getNotification();
|
||||
FormAutofillUtils.setOSAuthEnabled(
|
||||
FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF,
|
||||
false
|
||||
);
|
||||
|
||||
for (const [key, value] of Object.entries(record)) {
|
||||
const id = AddressEditDoorhanger.getInputId(key);
|
||||
const element = notification.querySelector(`#${id}`);
|
||||
element.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This function should be removed. We should make normalizeFields in
|
||||
// FormAutofillStorageBase.sys.mjs static and using it directly
|
||||
function normalizeAddressFields(record) {
|
||||
let normalized = { ...record };
|
||||
|
||||
if (normalized.name != undefined) {
|
||||
let nameParts = FormAutofillNameUtils.splitName(normalized.name);
|
||||
normalized["given-name"] = nameParts.given;
|
||||
normalized["additional-name"] = nameParts.middle;
|
||||
normalized["family-name"] = nameParts.family;
|
||||
delete normalized.name;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
async function verifyConfirmationHint(
|
||||
browser,
|
||||
forceClose,
|
||||
anchorID = "identity-icon-box"
|
||||
) {
|
||||
let hintElem = browser.ownerGlobal.ConfirmationHint._panel;
|
||||
await BrowserTestUtils.waitForPopupEvent(hintElem, "shown");
|
||||
try {
|
||||
Assert.equal(hintElem.state, "open", "hint popup is open");
|
||||
Assert.ok(
|
||||
BrowserTestUtils.isVisible(hintElem.anchorNode),
|
||||
"hint anchorNode is visible"
|
||||
registerCleanupFunction(() => {
|
||||
FormAutofillUtils.setOSAuthEnabled(
|
||||
FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF,
|
||||
oldValue
|
||||
);
|
||||
Assert.equal(
|
||||
hintElem.anchorNode.id,
|
||||
anchorID,
|
||||
"Hint should be anchored on the expected notification icon"
|
||||
);
|
||||
info("verifyConfirmationHint, hint is shown and has its anchorNode");
|
||||
if (forceClose) {
|
||||
await closePopup(hintElem);
|
||||
} else {
|
||||
info("verifyConfirmationHint, assertion ok, wait for poopuphidden");
|
||||
await BrowserTestUtils.waitForPopupEvent(hintElem, "hidden");
|
||||
info("verifyConfirmationHint, hintElem popup is hidden");
|
||||
}
|
||||
} catch (ex) {
|
||||
Assert.ok(false, "Confirmation hint not shown: " + ex.message);
|
||||
} finally {
|
||||
info("verifyConfirmationHint promise finalized");
|
||||
}
|
||||
}
|
||||
|
||||
async function showAddressDoorhanger(browser, values = null) {
|
||||
const defaultValues = {
|
||||
"#given-name": "John",
|
||||
"#family-name": "Doe",
|
||||
"#organization": "Mozilla",
|
||||
"#street-address": "123 Sesame Street",
|
||||
};
|
||||
|
||||
const onPopupShown = waitForPopupShown();
|
||||
const promise = BrowserTestUtils.browserLoaded(browser);
|
||||
await focusUpdateSubmitForm(browser, {
|
||||
focusSelector: "#given-name",
|
||||
newValues: values ?? defaultValues,
|
||||
});
|
||||
await promise;
|
||||
await onPopupShown;
|
||||
|
||||
add_heuristic_tests(patterns, fixturePathPrefix, { testCapture: true });
|
||||
}
|
||||
|
||||
async function add_autofill_heuristic_tests(patterns, fixturePathPrefix = "") {
|
||||
const oldValue = FormAutofillUtils.getOSAuthEnabled(
|
||||
FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF
|
||||
);
|
||||
|
||||
FormAutofillUtils.setOSAuthEnabled(
|
||||
FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF,
|
||||
false
|
||||
);
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
FormAutofillUtils.setOSAuthEnabled(
|
||||
FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF,
|
||||
oldValue
|
||||
);
|
||||
});
|
||||
|
||||
add_heuristic_tests(patterns, fixturePathPrefix, { testAutofill: true });
|
||||
}
|
||||
|
||||
add_setup(function () {
|
||||
|
|
|
@ -132,7 +132,6 @@ add_heuristic_tests(
|
|||
},
|
||||
{
|
||||
fieldName: "address-level1",
|
||||
autofill: "--",
|
||||
reason: "autocomplete",
|
||||
},
|
||||
{
|
||||
|
|
18
browser/extensions/formautofill/test/fixtures/autocomplete_cc_exp_embeded.html
vendored
Normal file
18
browser/extensions/formautofill/test/fixtures/autocomplete_cc_exp_embeded.html
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script>
|
||||
const params = new URLSearchParams(document.location.search);
|
||||
const formId = params.get('formId');
|
||||
if (formId) {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const form = document.querySelector('form');
|
||||
form.id = formId;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<body>
|
||||
<form id="form">
|
||||
<p><label>Expiration Date: <input id="cc-exp" autocomplete="cc-exp"></label></p>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
21
browser/extensions/formautofill/test/fixtures/autocomplete_cc_mandatory_embeded.html
vendored
Normal file
21
browser/extensions/formautofill/test/fixtures/autocomplete_cc_mandatory_embeded.html
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script>
|
||||
const params = new URLSearchParams(document.location.search);
|
||||
const formId = params.get('formId');
|
||||
if (formId) {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const form = document.querySelector('form');
|
||||
form.id = formId;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<body>
|
||||
<form id="form">
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
<p><label>Name: <input id="cc-name" autocomplete="cc-name"></label></p>
|
||||
<p><label>Expiration Data: <input id="cc-exp" autocomplete="cc-exp"></label></p>
|
||||
<input id="submit" type="submit">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
8
browser/extensions/formautofill/test/fixtures/autocomplete_cc_name_embeded.html
vendored
Normal file
8
browser/extensions/formautofill/test/fixtures/autocomplete_cc_name_embeded.html
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<form id="form">
|
||||
<p><label>Name: <input id="cc-name" autocomplete="cc-name"></label></p>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
8
browser/extensions/formautofill/test/fixtures/autocomplete_cc_number_embeded.html
vendored
Normal file
8
browser/extensions/formautofill/test/fixtures/autocomplete_cc_number_embeded.html
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<form id="form">
|
||||
<p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
17
browser/extensions/formautofill/test/fixtures/autocomplete_cc_type_embeded.html
vendored
Normal file
17
browser/extensions/formautofill/test/fixtures/autocomplete_cc_type_embeded.html
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<form id="form">
|
||||
<p><label>Card Type:
|
||||
<select id="cc-type" autocomplete="cc-type">
|
||||
<option></option>
|
||||
<option value="discover">Discover</option>
|
||||
<option value="jcb">JCB</option>
|
||||
<option value="visa">Visa</option>
|
||||
<option value="mastercard">MasterCard</option>
|
||||
<option value="gringotts">Unknown card network</option>
|
||||
</select>
|
||||
</label></p>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
|
@ -493,9 +493,10 @@ function initPopupListener() {
|
|||
}
|
||||
|
||||
async function triggerPopupAndHoverItem(fieldSelector, selectIndex) {
|
||||
const promise = expectPopup();
|
||||
await focusAndWaitForFieldsIdentified(fieldSelector);
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
await expectPopup();
|
||||
await await promise;
|
||||
for (let i = 0; i <= selectIndex; i++) {
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
}
|
||||
|
|
|
@ -20,8 +20,6 @@ support-files = [
|
|||
["test_basic_autocomplete_form.html"]
|
||||
skip-if = [ "apple_catalina && !debug" ] # Bug 1789194
|
||||
|
||||
["test_form_changes.html"]
|
||||
|
||||
["test_formautofill_preview_highlight.html"]
|
||||
skip-if = ["verify"]
|
||||
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test basic autofill</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="text/javascript" src="formautofill_common.js"></script>
|
||||
<script type="text/javascript" src="satchel_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
Form autofill test: autocomplete on an autofocus form
|
||||
|
||||
<script>
|
||||
/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
let MOCK_STORAGE = [{
|
||||
name: "John Doe",
|
||||
organization: "Sesame Street",
|
||||
"address-level2": "Austin",
|
||||
tel: "+13453453456",
|
||||
}, {
|
||||
name: "Foo Bar",
|
||||
organization: "Mozilla",
|
||||
"address-level2": "San Francisco",
|
||||
tel: "+16509030800",
|
||||
}];
|
||||
|
||||
initPopupListener();
|
||||
|
||||
async function setupAddressStorage() {
|
||||
await addAddress(MOCK_STORAGE[0]);
|
||||
await addAddress(MOCK_STORAGE[1]);
|
||||
}
|
||||
|
||||
function addInputField(form, className) {
|
||||
let newElem = document.createElement("input");
|
||||
newElem.name = className;
|
||||
newElem.autocomplete = className;
|
||||
newElem.type = "text";
|
||||
form.appendChild(newElem);
|
||||
}
|
||||
|
||||
async function checkFieldsAutofilled(formId, profile) {
|
||||
const elements = document.querySelectorAll(`#${formId} input`);
|
||||
for (const element of elements) {
|
||||
await SimpleTest.promiseWaitForCondition(() => {
|
||||
return element.value == profile[element.name];
|
||||
});
|
||||
await checkFieldHighlighted(element, true);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkFormChangeHappened(formId) {
|
||||
info("expecting form changed");
|
||||
await focusAndWaitForFieldsIdentified(`#${formId} input[name=tel]`);
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
await expectPopup();
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
checkMenuEntriesComment(MOCK_STORAGE.map(address =>
|
||||
makeAddressComment({
|
||||
primary: address.tel,
|
||||
secondary: address.name,
|
||||
status: "Also autofills name, organization"
|
||||
})
|
||||
), 2);
|
||||
|
||||
// Click the first entry of the autocomplete popup and make sure all fields are autofilled
|
||||
synthesizeKey("KEY_Enter");
|
||||
await checkFieldsAutofilled(formId, MOCK_STORAGE[0]);
|
||||
// This is for checking the changes of element count.
|
||||
addInputField(document.querySelector(`#${formId}`), "address-level2");
|
||||
|
||||
await focusAndWaitForFieldsIdentified(`#${formId} input[name=name]`);
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
await expectPopup();
|
||||
|
||||
// Click on an autofilled field would show an autocomplete popup with "clear form" entry
|
||||
checkMenuEntries([
|
||||
"Clear Autofill Form", // Clear Autofill Form
|
||||
"Manage addresses" // FormAutofill Preferemce
|
||||
], 0);
|
||||
|
||||
// This is for checking the changes of element removed and added then.
|
||||
document.querySelector(`#${formId} input[name=address-level2]`).remove();
|
||||
addInputField(document.querySelector(`#${formId}`), "address-level2");
|
||||
|
||||
await focusAndWaitForFieldsIdentified(`#${formId} input[name=address-level2]`, true);
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
await expectPopup();
|
||||
checkMenuEntriesComment(MOCK_STORAGE.map(address =>
|
||||
makeAddressComment({
|
||||
primary: address["address-level2"],
|
||||
secondary: address.name,
|
||||
status: "Also autofills name, organization, phone"
|
||||
})
|
||||
), 2);
|
||||
|
||||
// Make sure everything is autofilled in the end
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
synthesizeKey("KEY_Enter");
|
||||
await checkFieldsAutofilled(formId, MOCK_STORAGE[0]);
|
||||
}
|
||||
|
||||
add_task(async function init_storage() {
|
||||
await setupAddressStorage();
|
||||
});
|
||||
|
||||
add_task(async function check_change_happened_in_form() {
|
||||
await checkFormChangeHappened("form1");
|
||||
});
|
||||
|
||||
add_task(async function check_change_happened_in_body() {
|
||||
await checkFormChangeHappened("form2");
|
||||
});
|
||||
</script>
|
||||
|
||||
<p id="display"></p>
|
||||
<div id="content">
|
||||
<form id="form1">
|
||||
<p><label>organization: <input name="organization" autocomplete="organization" type="text"></label></p>
|
||||
<p><label>tel: <input name="tel" autocomplete="tel" type="text"></label></p>
|
||||
<p><label>name: <input name="name" autocomplete="name" type="text"></label></p>
|
||||
</form>
|
||||
<div id="form2">
|
||||
<p><label>organization: <input name="organization" autocomplete="organization" type="text"></label></p>
|
||||
<p><label>tel: <input name="tel" autocomplete="tel" type="text"></label></p>
|
||||
<p><label>name: <input name="name" autocomplete="name" type="text"></label></p>
|
||||
</div>
|
||||
</div>
|
||||
<pre id="test"></pre>
|
||||
</body>
|
||||
</html>
|
|
@ -348,34 +348,6 @@ const TESTCASES = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Address form without matching options in select for address-level1 and country",
|
||||
document: `<form>
|
||||
<input autocomplete="given-name">
|
||||
<select autocomplete="address-level1">
|
||||
<option id="option-address-level1-dummy1" value="">Dummy</option>
|
||||
<option id="option-address-level1-dummy2" value="">Dummy 2</option>
|
||||
</select>
|
||||
<select autocomplete="country">
|
||||
<option id="option-country-dummy1" value="">Dummy</option>
|
||||
<option id="option-country-dummy2" value="">Dummy 2</option>
|
||||
</select>
|
||||
</form>`,
|
||||
profileData: [{ ...DEFAULT_ADDRESS_RECORD }],
|
||||
expectedResult: [
|
||||
{
|
||||
guid: "123",
|
||||
"street-address": "2 Harrison St\nline2\nline3",
|
||||
"-moz-street-address-one-line": "2 Harrison St line2 line3",
|
||||
"address-line1": "2 Harrison St",
|
||||
"address-line2": "line2",
|
||||
"address-line3": "line3",
|
||||
tel: "+19876543210",
|
||||
"tel-national": "9876543210",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Change the tel value of a profile to tel-national for a field without pattern and maxlength.",
|
||||
|
@ -1311,9 +1283,11 @@ for (let testcase of TESTCASES) {
|
|||
let expectedOption = doc.getElementById(expectedOptionElement[field]);
|
||||
Assert.notEqual(expectedOption, null);
|
||||
|
||||
let value = testcase.profileData[i][field];
|
||||
let cache = handler.matchingSelectOption.get(select);
|
||||
let targetOption = cache[value] && cache[value].deref();
|
||||
let targetOption =
|
||||
handler.matchSelectOptions(
|
||||
{ element: select, fieldName: field },
|
||||
testcase.profileData[i]
|
||||
) ?? null;
|
||||
Assert.notEqual(targetOption, null);
|
||||
|
||||
Assert.equal(targetOption, expectedOption);
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { FormAutofillChild } = ChromeUtils.importESModule(
|
||||
"resource://autofill/FormAutofillChild.sys.mjs"
|
||||
);
|
||||
|
||||
add_task(async function test_inspectFields() {
|
||||
const doc = MockDocument.createTestDocument(
|
||||
"http://localhost:8080/test/",
|
||||
`<form>
|
||||
<input id="cc-number" autocomplete="cc-number">
|
||||
<input id="cc-name" autocomplete="cc-name">
|
||||
<input id="cc-exp" autocomplete="cc-exp">
|
||||
</form>
|
||||
<select id="cc-type" autocomplete="cc-type">
|
||||
<option/>
|
||||
<option value="visa">VISA</option>
|
||||
</select>
|
||||
<form>
|
||||
<input id="name" autocomplete="name">
|
||||
<select id="country" autocomplete="country">
|
||||
<option/>
|
||||
<option value="US">United States</option>
|
||||
</select>
|
||||
</form>
|
||||
<input id="email" autocomplete="email">
|
||||
`
|
||||
);
|
||||
const fac = new FormAutofillChild();
|
||||
Object.defineProperty(fac, "document", {
|
||||
value: doc,
|
||||
});
|
||||
|
||||
const fields = fac.inspectFields();
|
||||
|
||||
const expectedElements = Array.from(doc.querySelectorAll("input, select"));
|
||||
const inspectedElements = fields.map(field => field.element);
|
||||
Assert.deepEqual(
|
||||
expectedElements,
|
||||
inspectedElements,
|
||||
"inspectedElements should return all the eligible fields"
|
||||
);
|
||||
});
|
|
@ -84,6 +84,10 @@ let addressTestCases = [
|
|||
description: "Focus on an `organization` field",
|
||||
options: {},
|
||||
matchingProfiles,
|
||||
filledCategories: [
|
||||
["address", "name", "tel"],
|
||||
["address", "name", "tel"],
|
||||
],
|
||||
allFieldNames,
|
||||
searchString: "",
|
||||
fieldDetail: { fieldName: "organization" },
|
||||
|
@ -122,6 +126,11 @@ let addressTestCases = [
|
|||
description: "Focus on an `tel` field",
|
||||
options: {},
|
||||
matchingProfiles,
|
||||
filledCategories: [
|
||||
["address", "name", "tel", "organization"],
|
||||
["address", "name", "tel", "organization"],
|
||||
["address", "tel"],
|
||||
],
|
||||
allFieldNames,
|
||||
searchString: "",
|
||||
fieldDetail: { fieldName: "tel" },
|
||||
|
@ -172,6 +181,11 @@ let addressTestCases = [
|
|||
description: "Focus on an `street-address` field",
|
||||
options: {},
|
||||
matchingProfiles,
|
||||
filledCategories: [
|
||||
["address", "name", "tel", "organization"],
|
||||
["address", "name", "tel", "organization"],
|
||||
["address", "tel"],
|
||||
],
|
||||
allFieldNames,
|
||||
searchString: "",
|
||||
fieldDetail: { fieldName: "street-address" },
|
||||
|
@ -222,6 +236,11 @@ let addressTestCases = [
|
|||
description: "Focus on an `address-line1` field",
|
||||
options: {},
|
||||
matchingProfiles,
|
||||
filledCategories: [
|
||||
["address", "name", "tel", "organization"],
|
||||
["address", "name", "tel", "organization"],
|
||||
["address", "tel"],
|
||||
],
|
||||
allFieldNames,
|
||||
searchString: "",
|
||||
fieldDetail: { fieldName: "address-line1" },
|
||||
|
@ -465,6 +484,7 @@ add_task(async function test_all_patterns() {
|
|||
testCase.fieldDetail,
|
||||
testCase.allFieldNames,
|
||||
testCase.matchingProfiles,
|
||||
testCase.filledCategories,
|
||||
testCase.options
|
||||
);
|
||||
let expectedValue = testCase.expected;
|
||||
|
|
|
@ -84,6 +84,8 @@ skip-if = [
|
|||
"apple_silicon", # bug 1729554
|
||||
]
|
||||
|
||||
["test_inspectFields.js"]
|
||||
|
||||
["test_isAddressAutofillAvailable.js"]
|
||||
|
||||
["test_isCJKName.js"]
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"af": {
|
||||
"pin": false,
|
||||
|
@ -35,7 +35,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"an": {
|
||||
"pin": false,
|
||||
|
@ -54,7 +54,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ar": {
|
||||
"pin": false,
|
||||
|
@ -73,7 +73,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ast": {
|
||||
"pin": false,
|
||||
|
@ -92,7 +92,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"az": {
|
||||
"pin": false,
|
||||
|
@ -111,7 +111,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"be": {
|
||||
"pin": false,
|
||||
|
@ -130,7 +130,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"bg": {
|
||||
"pin": false,
|
||||
|
@ -149,7 +149,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"bn": {
|
||||
"pin": false,
|
||||
|
@ -168,7 +168,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"bo": {
|
||||
"pin": false,
|
||||
|
@ -187,7 +187,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"br": {
|
||||
"pin": false,
|
||||
|
@ -206,7 +206,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"brx": {
|
||||
"pin": false,
|
||||
|
@ -225,7 +225,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"bs": {
|
||||
"pin": false,
|
||||
|
@ -244,7 +244,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ca": {
|
||||
"pin": false,
|
||||
|
@ -263,7 +263,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ca-valencia": {
|
||||
"pin": false,
|
||||
|
@ -282,7 +282,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"cak": {
|
||||
"pin": false,
|
||||
|
@ -301,7 +301,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ckb": {
|
||||
"pin": false,
|
||||
|
@ -320,7 +320,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"cs": {
|
||||
"pin": false,
|
||||
|
@ -339,7 +339,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"cy": {
|
||||
"pin": false,
|
||||
|
@ -358,7 +358,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"da": {
|
||||
"pin": false,
|
||||
|
@ -377,7 +377,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"de": {
|
||||
"pin": false,
|
||||
|
@ -396,7 +396,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"dsb": {
|
||||
"pin": false,
|
||||
|
@ -415,7 +415,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"el": {
|
||||
"pin": false,
|
||||
|
@ -434,7 +434,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"en-CA": {
|
||||
"pin": false,
|
||||
|
@ -453,7 +453,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"en-GB": {
|
||||
"pin": false,
|
||||
|
@ -472,7 +472,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"eo": {
|
||||
"pin": false,
|
||||
|
@ -491,7 +491,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"es-AR": {
|
||||
"pin": false,
|
||||
|
@ -510,7 +510,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"es-CL": {
|
||||
"pin": false,
|
||||
|
@ -529,7 +529,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"es-ES": {
|
||||
"pin": false,
|
||||
|
@ -548,7 +548,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"es-MX": {
|
||||
"pin": false,
|
||||
|
@ -567,7 +567,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"et": {
|
||||
"pin": false,
|
||||
|
@ -586,7 +586,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"eu": {
|
||||
"pin": false,
|
||||
|
@ -605,7 +605,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"fa": {
|
||||
"pin": false,
|
||||
|
@ -624,7 +624,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ff": {
|
||||
"pin": false,
|
||||
|
@ -643,7 +643,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"fi": {
|
||||
"pin": false,
|
||||
|
@ -662,7 +662,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"fr": {
|
||||
"pin": false,
|
||||
|
@ -681,7 +681,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"fur": {
|
||||
"pin": false,
|
||||
|
@ -700,7 +700,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"fy-NL": {
|
||||
"pin": false,
|
||||
|
@ -719,7 +719,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ga-IE": {
|
||||
"pin": false,
|
||||
|
@ -738,7 +738,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"gd": {
|
||||
"pin": false,
|
||||
|
@ -757,7 +757,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"gl": {
|
||||
"pin": false,
|
||||
|
@ -776,7 +776,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"gn": {
|
||||
"pin": false,
|
||||
|
@ -795,7 +795,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"gu-IN": {
|
||||
"pin": false,
|
||||
|
@ -814,7 +814,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"he": {
|
||||
"pin": false,
|
||||
|
@ -833,7 +833,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"hi-IN": {
|
||||
"pin": false,
|
||||
|
@ -852,7 +852,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"hr": {
|
||||
"pin": false,
|
||||
|
@ -871,7 +871,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"hsb": {
|
||||
"pin": false,
|
||||
|
@ -890,7 +890,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"hu": {
|
||||
"pin": false,
|
||||
|
@ -909,7 +909,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"hy-AM": {
|
||||
"pin": false,
|
||||
|
@ -928,7 +928,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"hye": {
|
||||
"pin": false,
|
||||
|
@ -947,7 +947,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ia": {
|
||||
"pin": false,
|
||||
|
@ -966,7 +966,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"id": {
|
||||
"pin": false,
|
||||
|
@ -985,7 +985,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"is": {
|
||||
"pin": false,
|
||||
|
@ -1004,7 +1004,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"it": {
|
||||
"pin": false,
|
||||
|
@ -1023,7 +1023,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ja": {
|
||||
"pin": false,
|
||||
|
@ -1040,7 +1040,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ja-JP-mac": {
|
||||
"pin": false,
|
||||
|
@ -1048,7 +1048,7 @@
|
|||
"macosx64",
|
||||
"macosx64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ka": {
|
||||
"pin": false,
|
||||
|
@ -1067,7 +1067,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"kab": {
|
||||
"pin": false,
|
||||
|
@ -1086,7 +1086,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"kk": {
|
||||
"pin": false,
|
||||
|
@ -1105,7 +1105,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"km": {
|
||||
"pin": false,
|
||||
|
@ -1124,7 +1124,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"kn": {
|
||||
"pin": false,
|
||||
|
@ -1143,7 +1143,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ko": {
|
||||
"pin": false,
|
||||
|
@ -1162,7 +1162,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"lij": {
|
||||
"pin": false,
|
||||
|
@ -1181,7 +1181,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"lo": {
|
||||
"pin": false,
|
||||
|
@ -1200,7 +1200,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"lt": {
|
||||
"pin": false,
|
||||
|
@ -1219,7 +1219,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ltg": {
|
||||
"pin": false,
|
||||
|
@ -1238,7 +1238,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"lv": {
|
||||
"pin": false,
|
||||
|
@ -1257,7 +1257,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"meh": {
|
||||
"pin": false,
|
||||
|
@ -1276,7 +1276,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"mk": {
|
||||
"pin": false,
|
||||
|
@ -1295,7 +1295,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"mr": {
|
||||
"pin": false,
|
||||
|
@ -1314,7 +1314,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ms": {
|
||||
"pin": false,
|
||||
|
@ -1333,7 +1333,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"my": {
|
||||
"pin": false,
|
||||
|
@ -1352,7 +1352,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"nb-NO": {
|
||||
"pin": false,
|
||||
|
@ -1371,7 +1371,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ne-NP": {
|
||||
"pin": false,
|
||||
|
@ -1390,7 +1390,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"nl": {
|
||||
"pin": false,
|
||||
|
@ -1409,7 +1409,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"nn-NO": {
|
||||
"pin": false,
|
||||
|
@ -1428,7 +1428,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"oc": {
|
||||
"pin": false,
|
||||
|
@ -1447,7 +1447,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"pa-IN": {
|
||||
"pin": false,
|
||||
|
@ -1466,7 +1466,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"pl": {
|
||||
"pin": false,
|
||||
|
@ -1485,7 +1485,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"pt-BR": {
|
||||
"pin": false,
|
||||
|
@ -1504,7 +1504,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"pt-PT": {
|
||||
"pin": false,
|
||||
|
@ -1523,7 +1523,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"rm": {
|
||||
"pin": false,
|
||||
|
@ -1542,7 +1542,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ro": {
|
||||
"pin": false,
|
||||
|
@ -1561,7 +1561,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ru": {
|
||||
"pin": false,
|
||||
|
@ -1580,7 +1580,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"sat": {
|
||||
"pin": false,
|
||||
|
@ -1599,7 +1599,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"sc": {
|
||||
"pin": false,
|
||||
|
@ -1618,7 +1618,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"scn": {
|
||||
"pin": false,
|
||||
|
@ -1637,7 +1637,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"sco": {
|
||||
"pin": false,
|
||||
|
@ -1656,7 +1656,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"si": {
|
||||
"pin": false,
|
||||
|
@ -1675,7 +1675,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"sk": {
|
||||
"pin": false,
|
||||
|
@ -1694,7 +1694,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"skr": {
|
||||
"pin": false,
|
||||
|
@ -1713,7 +1713,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"sl": {
|
||||
"pin": false,
|
||||
|
@ -1732,7 +1732,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"son": {
|
||||
"pin": false,
|
||||
|
@ -1751,7 +1751,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"sq": {
|
||||
"pin": false,
|
||||
|
@ -1770,7 +1770,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"sr": {
|
||||
"pin": false,
|
||||
|
@ -1789,7 +1789,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"sv-SE": {
|
||||
"pin": false,
|
||||
|
@ -1808,7 +1808,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"szl": {
|
||||
"pin": false,
|
||||
|
@ -1827,7 +1827,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ta": {
|
||||
"pin": false,
|
||||
|
@ -1846,7 +1846,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"te": {
|
||||
"pin": false,
|
||||
|
@ -1865,7 +1865,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"tg": {
|
||||
"pin": false,
|
||||
|
@ -1884,7 +1884,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"th": {
|
||||
"pin": false,
|
||||
|
@ -1903,7 +1903,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"tl": {
|
||||
"pin": false,
|
||||
|
@ -1922,7 +1922,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"tr": {
|
||||
"pin": false,
|
||||
|
@ -1941,7 +1941,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"trs": {
|
||||
"pin": false,
|
||||
|
@ -1960,7 +1960,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"uk": {
|
||||
"pin": false,
|
||||
|
@ -1979,7 +1979,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"ur": {
|
||||
"pin": false,
|
||||
|
@ -1998,7 +1998,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"uz": {
|
||||
"pin": false,
|
||||
|
@ -2017,7 +2017,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"vi": {
|
||||
"pin": false,
|
||||
|
@ -2036,7 +2036,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"wo": {
|
||||
"pin": false,
|
||||
|
@ -2055,7 +2055,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"xh": {
|
||||
"pin": false,
|
||||
|
@ -2074,7 +2074,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"zh-CN": {
|
||||
"pin": false,
|
||||
|
@ -2093,7 +2093,7 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
},
|
||||
"zh-TW": {
|
||||
"pin": false,
|
||||
|
@ -2112,6 +2112,6 @@
|
|||
"win64-aarch64-devedition",
|
||||
"win64-devedition"
|
||||
],
|
||||
"revision": "b05219d455a438c501765992929bf2c3e83996de"
|
||||
"revision": "c628e0bed7361ec547babde7cc6bd68e620f08cb"
|
||||
}
|
||||
}
|
|
@ -143,7 +143,7 @@ add_task(async function toolbarButtons() {
|
|||
() => {
|
||||
return (
|
||||
bookmarksToolbar.getAttribute("collapsed") != "true" &&
|
||||
bookmarksToolbar.getAttribute("initialized") == "true"
|
||||
bookmarksToolbar.hasAttribute("initialized")
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
@import url("chrome://browser/skin/browser-shared.css");
|
||||
@import url("chrome://browser/skin/contextmenu.css");
|
||||
|
||||
@namespace html url("http://www.w3.org/1999/xhtml");
|
||||
|
||||
/**
|
||||
* We intentionally do not include browser-custom-colors.css, instead choosing to
|
||||
* fall back to system colors and transparencies in order to accommodate the
|
||||
|
@ -17,24 +15,6 @@
|
|||
*/
|
||||
@media not (prefers-contrast) {
|
||||
:root:not([lwtheme]) {
|
||||
--toolbar-field-border-color: transparent;
|
||||
|
||||
/* These colors don't exactly match the default dark color that buttons and
|
||||
* textfields have in Adwaita[1][2], which would end up computing to
|
||||
* rgba(0, 0, 0, .8 * .1) and rgba(255, 255, 255, 1 * .1) for light and
|
||||
* dark, respectively.
|
||||
*
|
||||
* Instead, for the light theme we use .05 alpha, to increase the contrast
|
||||
* of the text. For the dark theme, we use a darker background, which works
|
||||
* because the toolbar background applies some white unconditionally, and
|
||||
* matches what our default themes do in other platforms too.
|
||||
*
|
||||
* [1]: https://gitlab.gnome.org/GNOME/libadwaita/-/blob/42c04e038f19b2123560da662692d65480a67931/src/stylesheet/widgets/_buttons.scss#L1
|
||||
* [2]: https://gitlab.gnome.org/GNOME/libadwaita/-/blob/42c04e038f19b2123560da662692d65480a67931/src/stylesheet/widgets/_entries.scss#L9
|
||||
*/
|
||||
--toolbar-field-background-color: light-dark(rgba(0, 0, 0, .05), rgba(0, 0, 0, .3));
|
||||
--toolbar-field-color: inherit;
|
||||
|
||||
@media (-moz-gtk-theme-family) {
|
||||
--tabs-navbar-separator-style: none;
|
||||
@media (prefers-color-scheme: light) {
|
||||
|
|
|
@ -12,14 +12,9 @@
|
|||
|
||||
/* stylelint-disable-next-line media-query-no-invalid */
|
||||
@media (-moz-bool-pref: "browser.theme.macos.native-theme") {
|
||||
/* TODO: Share this with Linux, which effectively does ~the same */
|
||||
@media not (prefers-contrast) {
|
||||
:root:not([lwtheme]) {
|
||||
--toolbar-field-border-color: transparent;
|
||||
--toolbar-field-background-color: light-dark(rgba(0, 0, 0, .05), rgba(0, 0, 0, .3));
|
||||
--toolbar-field-color: inherit;
|
||||
@media (prefers-color-scheme: light) {
|
||||
--toolbar-non-lwt-bgcolor: white;
|
||||
--urlbar-box-bgcolor: #fafafa;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
@namespace html url("http://www.w3.org/1999/xhtml");
|
||||
|
||||
@media not (prefers-contrast) {
|
||||
:root:not([lwtheme]) {
|
||||
--button-primary-bgcolor: light-dark(rgb(0, 97, 224), rgb(0, 221, 255));
|
||||
|
|
|
@ -192,9 +192,6 @@ body {
|
|||
#navigator-toolbox {
|
||||
appearance: none;
|
||||
|
||||
/* Toolbar / content area border */
|
||||
border-bottom: 0.01px solid var(--chrome-content-separator-color);
|
||||
|
||||
background-color: var(--toolbox-non-lwt-bgcolor);
|
||||
color: var(--toolbox-non-lwt-textcolor);
|
||||
|
||||
|
@ -206,11 +203,6 @@ body {
|
|||
color: var(--toolbox-non-lwt-textcolor-inactive);
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line media-query-no-invalid */
|
||||
@media (-moz-bool-pref: "sidebar.revamp") {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&[fullscreenShouldAnimate] {
|
||||
transition: 0.8s margin-top ease-out;
|
||||
}
|
||||
|
@ -318,6 +310,23 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
/* Chrome/content separation */
|
||||
|
||||
#navigator-toolbox {
|
||||
/* This reserves space for the tabbox shadow */
|
||||
border-bottom: 0.5px solid var(--toolbar-bgcolor);
|
||||
}
|
||||
|
||||
#tabbrowser-tabbox {
|
||||
position: relative;
|
||||
box-shadow: 0 0 0 0.5px var(--chrome-content-separator-color);
|
||||
|
||||
/* stylelint-disable-next-line media-query-no-invalid */
|
||||
@media (-moz-bool-pref: "sidebar.revamp") {
|
||||
box-shadow: 0 0 0 0.5px var(--chrome-content-separator-color), 0 2px 6px 0 light-dark(rgb(0, 0, 0, 0.2), rgb(0, 0, 0, 0.8));
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide the TabsToolbar titlebar controls if the menubar is permanently shown.
|
||||
* (That is, if the menu bar doesn't autohide, and we're not in a fullscreen or
|
||||
* popup window.) */
|
||||
|
@ -437,28 +446,11 @@ body {
|
|||
|
||||
/* Bookmarks toolbar empty message */
|
||||
|
||||
/* If the toolbar is not initialized set a zero width, but retain height. */
|
||||
&[collapsed=false]:not([initialized]) > #personal-toolbar-empty {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make the empty bookmarks toolbar message take up no horizontal space.
|
||||
* This avoids two issues:
|
||||
* 1) drag/drop of urls/bookmarks to the toolbar not working, because they
|
||||
* land on the personal-toolbar-empty element.
|
||||
* 2) buttons in the toolbar moving horizontally while the window opens,
|
||||
* because the element is first shown at full width and then completely
|
||||
* hidden.
|
||||
* TODO(emilio): The comment above was never quite true (the message did take
|
||||
* horizontal space, see bug 1812636). Figure out how much of this rule is
|
||||
* really needed.
|
||||
*/
|
||||
&[collapsed=false] > #personal-toolbar-empty[nowidth] > #personal-toolbar-empty-description {
|
||||
margin-inline: 0;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Bookmarks toolbar in customize mode */
|
||||
|
@ -471,6 +463,10 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
#personal-toolbar-empty-description {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#personal-bookmarks {
|
||||
position: relative;
|
||||
-moz-window-dragging: inherit;
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
#appcontent,
|
||||
#browser,
|
||||
#tabbrowser-tabbox,
|
||||
#tabbrowser-tabpanels,
|
||||
|
@ -35,22 +34,6 @@
|
|||
min-height: 0;
|
||||
}
|
||||
|
||||
#appcontent {
|
||||
/* stylelint-disable-next-line media-query-no-invalid */
|
||||
@media (-moz-bool-pref: "sidebar.revamp") {
|
||||
border-top: 0.5px solid var(--chrome-content-separator-color);
|
||||
border-inline-start: 0.5px solid var(--chrome-content-separator-color);
|
||||
box-shadow: 0 2px 6px 0 light-dark(rgb(0, 0, 0, 0.2), rgb(0, 0, 0, 0.8));
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line media-query-no-invalid */
|
||||
@media (not (-moz-bool-pref: "sidebar.position_start")) and (-moz-bool-pref: "sidebar.revamp") {
|
||||
border-inline-end: 0.5px solid var(--chrome-content-separator-color);
|
||||
border-inline-start: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* We set large flex on both containers to allow the devtools toolbox to
|
||||
* set a flex value itself. We don't want the toolbox to actually take up free
|
||||
* space, but we do want it to collapse when the window shrinks, and with
|
||||
|
|
|
@ -174,6 +174,17 @@
|
|||
fun:_ZN5style5bloom19StyleBloom$LT$E$GT$3new*
|
||||
...
|
||||
}
|
||||
{
|
||||
Same as above, but for rustc >= 1.80
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: definite
|
||||
fun:calloc
|
||||
...
|
||||
fun:_ZN3std3sys12thread_local10fast_local4lazy20Storage*10initialize*
|
||||
...
|
||||
fun:_ZN5style5bloom19StyleBloom$LT$E$GT$3new*
|
||||
...
|
||||
}
|
||||
{
|
||||
We intentionally leak sharing cache TLS data in the global servo thread-pool until we can free it consistently (https://github.com/rayon-rs/rayon/issues/688)
|
||||
Memcheck:Leak
|
||||
|
@ -185,6 +196,17 @@
|
|||
fun:_ZN5style7sharing26StyleSharingCache$LT$E$GT$3new*
|
||||
...
|
||||
}
|
||||
{
|
||||
Same as above, but for rustc >= 1.80
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: definite
|
||||
fun:malloc
|
||||
...
|
||||
fun:_ZN3std3sys12thread_local10fast_local4lazy20Storage*10initialize*
|
||||
...
|
||||
fun:_ZN5style7sharing26StyleSharingCache$LT$E$GT$3new*
|
||||
...
|
||||
}
|
||||
{
|
||||
Leak in libfontconfig1 in Debian 8 and 9. See bug 1636003.
|
||||
Memcheck:Leak
|
||||
|
|
|
@ -261,6 +261,9 @@ export IPHONEOS_SDK_DIR
|
|||
PATH := $(topsrcdir)/build/macosx:$(PATH)
|
||||
endif
|
||||
endif
|
||||
# Use the same prefix as set through modules/zlib/src/mozzconf.h
|
||||
# for libz-rs-sys, since we still use the headers from there.
|
||||
export LIBZ_RS_SYS_PREFIX=MOZ_Z_
|
||||
|
||||
ifndef RUSTC_BOOTSTRAP
|
||||
RUSTC_BOOTSTRAP := mozglue_static,qcms
|
||||
|
|
|
@ -13,9 +13,14 @@ import {
|
|||
getSelectedFrame,
|
||||
getCurrentThread,
|
||||
getSelectedException,
|
||||
getSelectedTraceIndex,
|
||||
getAllTraces,
|
||||
} from "../selectors/index";
|
||||
|
||||
import { getMappedExpression } from "./expressions";
|
||||
const {
|
||||
TRACER_FIELDS_INDEXES,
|
||||
} = require("resource://devtools/server/actors/tracer.js");
|
||||
|
||||
async function findExpressionMatch(state, parserWorker, editor, tokenPos) {
|
||||
const location = getSelectedLocation(state);
|
||||
|
@ -35,7 +40,97 @@ async function findExpressionMatch(state, parserWorker, editor, tokenPos) {
|
|||
return editor.getExpressionFromCoords(tokenPos);
|
||||
}
|
||||
|
||||
export function getPreview(target, tokenPos, editor) {
|
||||
/**
|
||||
* Get a preview object for the currently selected frame in the JS Tracer.
|
||||
*
|
||||
* @param {Object} target
|
||||
* The hovered DOM Element within CodeMirror rendering.
|
||||
* @param {Object} tokenPos
|
||||
* The CodeMirror position object for the hovered token.
|
||||
* @param {Object} editor
|
||||
* The CodeMirror editor object.
|
||||
*/
|
||||
export function getTracerPreview(target, tokenPos, editor) {
|
||||
return async thunkArgs => {
|
||||
const { getState, parserWorker } = thunkArgs;
|
||||
const selectedTraceIndex = getSelectedTraceIndex(getState());
|
||||
if (selectedTraceIndex == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const trace = getAllTraces(getState())[selectedTraceIndex];
|
||||
|
||||
// We may be selecting a mutation trace, which doesn't expose any value,
|
||||
// so only consider method calls.
|
||||
if (trace[TRACER_FIELDS_INDEXES.TYPE] != "enter") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const match = await findExpressionMatch(
|
||||
getState(),
|
||||
parserWorker,
|
||||
editor,
|
||||
tokenPos
|
||||
);
|
||||
let { expression, location } = match;
|
||||
const source = getSelectedSource(getState());
|
||||
if (location && source.isOriginal) {
|
||||
const thread = getCurrentThread(getState());
|
||||
const mapResult = await getMappedExpression(
|
||||
expression,
|
||||
thread,
|
||||
thunkArgs
|
||||
);
|
||||
if (mapResult) {
|
||||
expression = mapResult.expression;
|
||||
}
|
||||
}
|
||||
|
||||
const argumentValues = trace[TRACER_FIELDS_INDEXES.ENTER_ARGS];
|
||||
const argumentNames = trace[TRACER_FIELDS_INDEXES.ENTER_ARG_NAMES];
|
||||
if (!argumentNames || !argumentValues) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const argumentIndex = argumentNames.indexOf(expression);
|
||||
if (argumentIndex == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = argumentValues[argumentIndex];
|
||||
// Values are either primitives, or an Object Front
|
||||
const resultGrip = result?.getGrip ? result?.getGrip() : result;
|
||||
|
||||
const root = {
|
||||
path: expression,
|
||||
contents: {
|
||||
value: resultGrip,
|
||||
front: getFront(result),
|
||||
},
|
||||
};
|
||||
return {
|
||||
previewType: "tracer",
|
||||
target,
|
||||
tokenPos,
|
||||
cursorPos: target.getBoundingClientRect(),
|
||||
expression,
|
||||
root,
|
||||
resultGrip,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a preview object for the currently paused frame, if paused.
|
||||
*
|
||||
* @param {Object} target
|
||||
* The hovered DOM Element within CodeMirror rendering.
|
||||
* @param {Object} tokenPos
|
||||
* The CodeMirror position object for the hovered token.
|
||||
* @param {Object} editor
|
||||
* The CodeMirror editor object.
|
||||
*/
|
||||
export function getPausedPreview(target, tokenPos, editor) {
|
||||
return async thunkArgs => {
|
||||
const { getState, client, parserWorker } = thunkArgs;
|
||||
if (
|
||||
|
@ -127,6 +222,7 @@ export function getPreview(target, tokenPos, editor) {
|
|||
};
|
||||
|
||||
return {
|
||||
previewType: "pause",
|
||||
target,
|
||||
tokenPos,
|
||||
cursorPos: target.getBoundingClientRect(),
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
getAllTraces,
|
||||
getTraceFrames,
|
||||
getIsCurrentlyTracing,
|
||||
getCurrentThread,
|
||||
} from "../selectors/index";
|
||||
import { selectSourceBySourceActorID } from "./sources/select.js";
|
||||
const {
|
||||
|
@ -44,9 +45,13 @@ export function addTraces(traces) {
|
|||
|
||||
export function selectTrace(traceIndex) {
|
||||
return async function ({ dispatch, getState }) {
|
||||
// For now, the tracer only consider the top level thread
|
||||
const thread = getCurrentThread(getState());
|
||||
|
||||
dispatch({
|
||||
type: "SELECT_TRACE",
|
||||
traceIndex,
|
||||
thread,
|
||||
});
|
||||
const traces = getAllTraces(getState());
|
||||
const trace = traces[traceIndex];
|
||||
|
|
|
@ -41,7 +41,7 @@ breakpointButton.appendChild(svg);
|
|||
class ColumnBreakpoints extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
columnBreakpoints: PropTypes.array.isRequired,
|
||||
columnBreakpoints: PropTypes.array,
|
||||
editor: PropTypes.object.isRequired,
|
||||
selectedSource: PropTypes.object,
|
||||
addBreakpoint: PropTypes.func,
|
||||
|
|
|
@ -17,6 +17,12 @@
|
|||
border-bottom: 1px solid var(--theme-splitter-color);
|
||||
}
|
||||
|
||||
.conditional-breakpoint-panel:focus-within {
|
||||
outline: var(--theme-focus-outline);
|
||||
outline-offset: -2px;
|
||||
box-shadow: var(--theme-outline-box-shadow);
|
||||
}
|
||||
|
||||
.conditional-breakpoint-panel .prompt {
|
||||
font-size: 1.8em;
|
||||
color: var(--theme-graphs-orange);
|
||||
|
|
|
@ -24,12 +24,6 @@ import {
|
|||
|
||||
const classnames = require("resource://devtools/client/shared/classnames.js");
|
||||
|
||||
function addNewLine(doc) {
|
||||
const cursor = doc.getCursor();
|
||||
const pos = { line: cursor.line, ch: cursor.ch };
|
||||
doc.replaceRange("\n", pos);
|
||||
}
|
||||
|
||||
export class ConditionalPanel extends PureComponent {
|
||||
cbPanel;
|
||||
input;
|
||||
|
@ -70,12 +64,8 @@ export class ConditionalPanel extends PureComponent {
|
|||
};
|
||||
|
||||
onKey = e => {
|
||||
if (e.key === "Enter") {
|
||||
if (this.codeMirror && e.altKey) {
|
||||
addNewLine(this.codeMirror.doc);
|
||||
} else {
|
||||
this.saveAndClose();
|
||||
}
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
this.saveAndClose();
|
||||
} else if (e.key === "Escape") {
|
||||
this.props.closeConditionalPanel();
|
||||
}
|
||||
|
|
|
@ -46,14 +46,14 @@ class SourceFooter extends PureComponent {
|
|||
static get propTypes() {
|
||||
return {
|
||||
canPrettyPrint: PropTypes.bool.isRequired,
|
||||
prettyPrintMessage: PropTypes.string.isRequired,
|
||||
prettyPrintMessage: PropTypes.string,
|
||||
endPanelCollapsed: PropTypes.bool.isRequired,
|
||||
horizontal: PropTypes.bool.isRequired,
|
||||
jumpToMappedLocation: PropTypes.func.isRequired,
|
||||
mappedSource: PropTypes.object,
|
||||
selectedSource: PropTypes.object,
|
||||
selectedLocation: PropTypes.object,
|
||||
isSelectedSourceBlackBoxed: PropTypes.bool.isRequired,
|
||||
isSelectedSourceBlackBoxed: PropTypes.bool,
|
||||
sourceLoaded: PropTypes.bool.isRequired,
|
||||
toggleBlackBox: PropTypes.func.isRequired,
|
||||
togglePaneCollapse: PropTypes.func.isRequired,
|
||||
|
@ -277,6 +277,7 @@ class SourceFooter extends PureComponent {
|
|||
MenuButton,
|
||||
{
|
||||
menuId: "debugger-source-map-button",
|
||||
key: "debugger-source-map-button",
|
||||
toolboxDoc,
|
||||
className: classnames("devtools-button", "debugger-source-map-button", {
|
||||
error: !!this.props.sourceMapError,
|
||||
|
|
|
@ -59,7 +59,7 @@ export class HighlightLine extends Component {
|
|||
selectedFrame: PropTypes.object,
|
||||
selectedLocation: PropTypes.object.isRequired,
|
||||
selectedSourceTextContent: PropTypes.object.isRequired,
|
||||
shouldHighlightSelectedLocation: PropTypes.func.isRequired,
|
||||
shouldHighlightSelectedLocation: PropTypes.bool.isRequired,
|
||||
editor: PropTypes.object,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -69,6 +69,7 @@ class InlinePreviewRow extends PureComponent {
|
|||
React.createElement(InlinePreview, {
|
||||
line,
|
||||
key: `${line}-${preview.name}`,
|
||||
type: preview.type,
|
||||
variable: preview.name,
|
||||
value: preview.value,
|
||||
openElementInInspector,
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
|
||||
|
||||
.popover .preview {
|
||||
background: var(--theme-body-background);
|
||||
width: 350px;
|
||||
border: 1px solid var(--theme-splitter-color);
|
||||
padding: 10px;
|
||||
height: auto;
|
||||
min-height: inherit;
|
||||
max-height: 200px;
|
||||
overflow: auto;
|
||||
box-shadow: 1px 2px 3px var(--popup-shadow-color);
|
||||
}
|
||||
|
||||
.theme-dark .popover .preview {
|
||||
box-shadow: 1px 2px 3px var(--popup-shadow-color);
|
||||
}
|
||||
|
||||
.popover .preview .header {
|
||||
width: 100%;
|
||||
line-height: 20px;
|
||||
border-bottom: 1px solid #cccccc;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.popover .preview .header .link {
|
||||
align-self: flex-end;
|
||||
color: var(--theme-link-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.selection,
|
||||
.debug-expression.selection {
|
||||
background-color: var(--theme-highlight-yellow);
|
||||
}
|
||||
|
||||
.theme-dark .selection,
|
||||
.theme-dark .debug-expression.selection {
|
||||
background-color: #743884;
|
||||
}
|
||||
|
||||
.theme-dark .cm-s-mozilla .selection,
|
||||
.theme-dark .cm-s-mozilla .debug-expression.selection {
|
||||
color: #e7ebee;
|
||||
}
|
||||
|
||||
.popover .preview .function-signature {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.theme-dark .popover .preview {
|
||||
border-color: var(--theme-body-color);
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.tooltip .preview {
|
||||
background: var(--theme-toolbar-background);
|
||||
max-width: inherit;
|
||||
border: 1px solid var(--theme-splitter-color);
|
||||
box-shadow: 1px 2px 4px 1px var(--theme-toolbar-background-alt);
|
||||
padding: 5px;
|
||||
height: auto;
|
||||
min-height: inherit;
|
||||
max-height: 200px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.theme-dark .tooltip .preview {
|
||||
border-color: var(--theme-body-color);
|
||||
}
|
||||
|
||||
.tooltip .gap {
|
||||
height: 4px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.add-to-expression-bar {
|
||||
border: 1px solid var(--theme-splitter-color);
|
||||
border-top: none;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
line-height: 30px;
|
||||
background: var(--theme-toolbar-background);
|
||||
color: var(--theme-text-color-inactive);
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.add-to-expression-bar .prompt {
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.add-to-expression-bar .expression-to-save-label {
|
||||
width: calc(100% - 4em);
|
||||
}
|
||||
|
||||
.add-to-expression-bar .expression-to-save-button {
|
||||
font-size: 14px;
|
||||
color: var(--theme-comment);
|
||||
}
|
|
@ -10,17 +10,91 @@
|
|||
box-shadow: 1px 1px 3px var(--popup-shadow-color);
|
||||
}
|
||||
|
||||
/* Popover is used when previewing objects */
|
||||
.popover .preview-popup {
|
||||
padding: 5px 10px;
|
||||
max-width: 450px;
|
||||
min-width: 200px;
|
||||
|
||||
&.preview-type-pause {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
/* Because of tracer header, we can't put a padding on the popup */
|
||||
/* Nor can we put a margin on the .tree or .objectBox which causes overflows */
|
||||
&.preview-type-tracer {
|
||||
padding-bottom: 5px;
|
||||
.tree {
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Tooltip is used when previewing primitives */
|
||||
.tooltip .preview-popup {
|
||||
max-width: inherit;
|
||||
padding: 5px;
|
||||
min-height: inherit;
|
||||
max-height: 200px;
|
||||
|
||||
&.preview-type-pause {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
&.preview-type-tracer {
|
||||
padding-bottom: 5px;
|
||||
|
||||
.preview-tracer-header {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.objectBox {
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview-tracer-header {
|
||||
--icon-url: url("chrome://devtools/content/debugger/images/trace.svg");
|
||||
--icon-color: var(--theme-inline-preview-label-trace-color);
|
||||
color: var(--theme-inline-preview-label-trace-color);
|
||||
background-color: var(--theme-inline-preview-label-trace-background);
|
||||
border-block-end: 1px solid oklch(from var(--theme-inline-preview-label-trace-color) l c h / 0.25);
|
||||
|
||||
/* Make sure the header is always visible */
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
/* Add a bit more padding on the end to balance the icon, especially when have a small primitive value in preview */
|
||||
padding-inline-end: 10px !important;
|
||||
}
|
||||
|
||||
.preview-tracer-warning {
|
||||
--icon-url: url("chrome://devtools/skin/images/info.svg");
|
||||
--icon-color: currentColor;
|
||||
background-color: var(--theme-body-alternate-emphasized-background);
|
||||
border-bottom: 1px solid var(--theme-splitter-color);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.preview-tracer-header, .preview-tracer-warning {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
padding: 5px;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
flex-shrink: 0;
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
||||
background-image: var(--icon-url);
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
|
||||
-moz-context-properties: fill;
|
||||
fill: var(--icon-color);
|
||||
}
|
||||
}
|
||||
|
||||
.preview-popup .tree {
|
||||
|
@ -80,35 +154,6 @@
|
|||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.add-to-expression-bar {
|
||||
border: 1px solid var(--theme-splitter-color);
|
||||
border-top: none;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
line-height: 30px;
|
||||
background: var(--theme-toolbar-background);
|
||||
color: var(--theme-text-color-inactive);
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.add-to-expression-bar .prompt {
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.add-to-expression-bar .expression-to-save-label {
|
||||
width: calc(100% - 4em);
|
||||
}
|
||||
|
||||
.add-to-expression-bar .expression-to-save-button {
|
||||
font-size: 14px;
|
||||
color: var(--theme-comment);
|
||||
}
|
||||
|
||||
/* Exception popup */
|
||||
.exception-popup .exception-text {
|
||||
color: var(--red-70);
|
||||
|
|
|
@ -107,7 +107,7 @@ export class Popup extends Component {
|
|||
|
||||
renderPreview() {
|
||||
const {
|
||||
preview: { root, exception, resultGrip },
|
||||
preview: { root, exception, resultGrip, previewType },
|
||||
} = this.props;
|
||||
|
||||
const usesCustomFormatter =
|
||||
|
@ -119,11 +119,21 @@ export class Popup extends Component {
|
|||
|
||||
return div(
|
||||
{
|
||||
className: "preview-popup",
|
||||
className: `preview-popup preview-type-${previewType}`,
|
||||
style: {
|
||||
maxHeight: this.calculateMaxHeight(),
|
||||
},
|
||||
},
|
||||
// Bug 1915610 - JS Tracer isn't localized yet
|
||||
previewType == "tracer"
|
||||
? div({ className: "preview-tracer-header" }, "Tracer preview")
|
||||
: null,
|
||||
previewType == "tracer" && !nodeIsPrimitive(root)
|
||||
? div(
|
||||
{ className: "preview-tracer-warning" },
|
||||
"Attribute previews on traced objects are showing the current values and not the value at execution of the selected frame."
|
||||
)
|
||||
: null,
|
||||
React.createElement(ObjectInspector, {
|
||||
roots: [root],
|
||||
autoExpandDepth: 1,
|
||||
|
|
|
@ -8,7 +8,10 @@ import { connect } from "devtools/client/shared/vendor/react-redux";
|
|||
|
||||
import Popup from "./Popup";
|
||||
|
||||
import { getIsCurrentThreadPaused } from "../../../selectors/index";
|
||||
import {
|
||||
getIsCurrentThreadPaused,
|
||||
getSelectedTraceIndex,
|
||||
} from "../../../selectors/index";
|
||||
import actions from "../../../actions/index";
|
||||
import { features } from "../../../utils/prefs";
|
||||
|
||||
|
@ -26,6 +29,7 @@ class Preview extends PureComponent {
|
|||
editor: PropTypes.object.isRequired,
|
||||
editorRef: PropTypes.object.isRequired,
|
||||
isPaused: PropTypes.bool.isRequired,
|
||||
hasSelectedTrace: PropTypes.bool.isRequired,
|
||||
getExceptionPreview: PropTypes.func.isRequired,
|
||||
getPreview: PropTypes.func,
|
||||
};
|
||||
|
@ -75,7 +79,14 @@ class Preview extends PureComponent {
|
|||
const tokenId = {};
|
||||
this.currentTokenId = tokenId;
|
||||
|
||||
const { editor, getPreview, getExceptionPreview } = this.props;
|
||||
const {
|
||||
editor,
|
||||
getPausedPreview,
|
||||
getTracerPreview,
|
||||
getExceptionPreview,
|
||||
isPaused,
|
||||
hasSelectedTrace,
|
||||
} = this.props;
|
||||
const isTargetException = target.closest(`.${EXCEPTION_MARKER}`);
|
||||
|
||||
let preview;
|
||||
|
@ -83,8 +94,13 @@ class Preview extends PureComponent {
|
|||
preview = await getExceptionPreview(target, tokenPos, editor);
|
||||
}
|
||||
|
||||
if (!preview && this.props.isPaused && !this.state.selecting) {
|
||||
preview = await getPreview(target, tokenPos, editor);
|
||||
if (!preview && (hasSelectedTrace || isPaused) && !this.state.selecting) {
|
||||
if (hasSelectedTrace) {
|
||||
preview = await getTracerPreview(target, tokenPos, editor);
|
||||
}
|
||||
if (!preview && isPaused) {
|
||||
preview = await getPausedPreview(target, tokenPos, editor);
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent modifying state and showing this preview if we started hovering another token
|
||||
|
@ -95,19 +111,19 @@ class Preview extends PureComponent {
|
|||
};
|
||||
|
||||
onMouseUp = () => {
|
||||
if (this.props.isPaused) {
|
||||
if (this.props.isPaused || this.props.hasSelectedTrace) {
|
||||
this.setState({ selecting: false });
|
||||
}
|
||||
};
|
||||
|
||||
onMouseDown = () => {
|
||||
if (this.props.isPaused) {
|
||||
if (this.props.isPaused || this.props.hasSelectedTrace) {
|
||||
this.setState({ selecting: true });
|
||||
}
|
||||
};
|
||||
|
||||
onScroll = () => {
|
||||
if (this.props.isPaused) {
|
||||
if (this.props.isPaused || this.props.hasSelectedTrace) {
|
||||
this.clearPreview();
|
||||
}
|
||||
};
|
||||
|
@ -133,11 +149,13 @@ class Preview extends PureComponent {
|
|||
const mapStateToProps = state => {
|
||||
return {
|
||||
isPaused: getIsCurrentThreadPaused(state),
|
||||
hasSelectedTrace: getSelectedTraceIndex(state) != null,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
addExpression: actions.addExpression,
|
||||
getPreview: actions.getPreview,
|
||||
getPausedPreview: actions.getPausedPreview,
|
||||
getTracerPreview: actions.getTracerPreview,
|
||||
getExceptionPreview: actions.getExceptionPreview,
|
||||
})(Preview);
|
||||
|
|
|
@ -58,7 +58,7 @@ class SearchInFileBar extends Component {
|
|||
editor: PropTypes.object,
|
||||
modifiers: PropTypes.object.isRequired,
|
||||
searchInFileEnabled: PropTypes.bool.isRequired,
|
||||
selectedSourceTextContent: PropTypes.bool.isRequired,
|
||||
selectedSourceTextContent: PropTypes.object,
|
||||
selectedSource: PropTypes.object.isRequired,
|
||||
setActiveSearch: PropTypes.func.isRequired,
|
||||
querySearchWorker: PropTypes.func.isRequired,
|
||||
|
|
|
@ -876,7 +876,7 @@ class Editor extends PureComponent {
|
|||
React.Fragment,
|
||||
null,
|
||||
React.createElement(Breakpoints, { editor }),
|
||||
isPaused &&
|
||||
(isPaused || isTraceSelected) &&
|
||||
selectedSource.isOriginal &&
|
||||
!selectedSource.isPrettyPrinted &&
|
||||
!mapScopesEnabled
|
||||
|
@ -929,7 +929,7 @@ class Editor extends PureComponent {
|
|||
React.createElement(Breakpoints, {
|
||||
editor,
|
||||
}),
|
||||
isPaused &&
|
||||
(isPaused || isTraceSelected) &&
|
||||
selectedSource.isOriginal &&
|
||||
!selectedSource.isPrettyPrinted &&
|
||||
!mapScopesEnabled
|
||||
|
|
|
@ -34,6 +34,7 @@ exports[`SourceFooter Component default case should render 1`] = `
|
|||
<MenuButton
|
||||
className="devtools-button debugger-source-map-button disabled not-mapped"
|
||||
icon={true}
|
||||
key="debugger-source-map-button"
|
||||
menuId="debugger-source-map-button"
|
||||
menuOffset={-5}
|
||||
menuPosition="bottom"
|
||||
|
@ -90,6 +91,7 @@ exports[`SourceFooter Component move cursor should render new cursor position 1`
|
|||
<MenuButton
|
||||
className="devtools-button debugger-source-map-button disabled not-mapped"
|
||||
icon={true}
|
||||
key="debugger-source-map-button"
|
||||
menuId="debugger-source-map-button"
|
||||
menuOffset={-5}
|
||||
menuPosition="bottom"
|
||||
|
|
|
@ -76,10 +76,9 @@ export class Outline extends Component {
|
|||
return {
|
||||
alphabetizeOutline: PropTypes.bool.isRequired,
|
||||
cursorPosition: PropTypes.object,
|
||||
flashLineRange: PropTypes.func.isRequired,
|
||||
onAlphabetizeClick: PropTypes.func.isRequired,
|
||||
selectLocation: PropTypes.func.isRequired,
|
||||
selectedLocation: PropTypes.object.isRequired,
|
||||
selectedLocation: PropTypes.object,
|
||||
getFunctionSymbols: PropTypes.func.isRequired,
|
||||
getClassSymbols: PropTypes.func.isRequired,
|
||||
selectedSourceTextContent: PropTypes.object,
|
||||
|
|
|
@ -77,18 +77,8 @@ export class ProjectSearch extends Component {
|
|||
return {
|
||||
doSearchForHighlight: PropTypes.func.isRequired,
|
||||
query: PropTypes.string.isRequired,
|
||||
results: PropTypes.array.isRequired,
|
||||
searchSources: PropTypes.func.isRequired,
|
||||
selectSpecificLocationOrSameUrl: PropTypes.func.isRequired,
|
||||
status: PropTypes.oneOf([
|
||||
"INITIAL",
|
||||
"FETCHING",
|
||||
"CANCELED",
|
||||
"DONE",
|
||||
"ERROR",
|
||||
]).isRequired,
|
||||
modifiers: PropTypes.object,
|
||||
toggleProjectSearchModifier: PropTypes.func,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -52,14 +52,14 @@ class SourcesTree extends Component {
|
|||
|
||||
static get propTypes() {
|
||||
return {
|
||||
mainThreadHost: PropTypes.string.isRequired,
|
||||
mainThreadHost: PropTypes.string,
|
||||
expanded: PropTypes.object.isRequired,
|
||||
focusItem: PropTypes.func.isRequired,
|
||||
focused: PropTypes.object,
|
||||
projectRoot: PropTypes.string.isRequired,
|
||||
selectSource: PropTypes.func.isRequired,
|
||||
setExpandedState: PropTypes.func.isRequired,
|
||||
rootItems: PropTypes.object.isRequired,
|
||||
rootItems: PropTypes.array.isRequired,
|
||||
clearProjectDirectoryRoot: PropTypes.func.isRequired,
|
||||
projectRootName: PropTypes.string.isRequired,
|
||||
setHideOrShowIgnoredSources: PropTypes.func.isRequired,
|
||||
|
|
|
@ -26,11 +26,11 @@ class SourceTreeItemContents extends Component {
|
|||
static get propTypes() {
|
||||
return {
|
||||
autoExpand: PropTypes.bool.isRequired,
|
||||
depth: PropTypes.bool.isRequired,
|
||||
depth: PropTypes.number.isRequired,
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
focusItem: PropTypes.func.isRequired,
|
||||
focused: PropTypes.bool.isRequired,
|
||||
hasMatchingGeneratedSource: PropTypes.bool.isRequired,
|
||||
hasMatchingGeneratedSource: PropTypes.bool,
|
||||
item: PropTypes.object.isRequired,
|
||||
selectSourceItem: PropTypes.func.isRequired,
|
||||
setExpanded: PropTypes.func.isRequired,
|
||||
|
|
|
@ -400,9 +400,11 @@ export class Tracer extends Component {
|
|||
const yInSlider = event.clientY - top;
|
||||
const mousePositionRatio = yInSlider / height;
|
||||
|
||||
const index =
|
||||
this.state.startIndex +
|
||||
Math.floor(mousePositionRatio * this.state.renderedTraceCount);
|
||||
// Indexes and ratios are floating number whereas
|
||||
// we expect to pass an array index to `selectTrace`.
|
||||
const index = Math.round(
|
||||
this.state.startIndex + mousePositionRatio * this.state.renderedTraceCount
|
||||
);
|
||||
|
||||
this.props.selectTrace(index);
|
||||
}
|
||||
|
|
|
@ -43,7 +43,6 @@ class PrimaryPanes extends Component {
|
|||
|
||||
static get propTypes() {
|
||||
return {
|
||||
projectRootName: PropTypes.string.isRequired,
|
||||
selectedTab: PropTypes.oneOf(tabs).isRequired,
|
||||
setPrimaryPaneTab: PropTypes.func.isRequired,
|
||||
setActiveSearch: PropTypes.func.isRequired,
|
||||
|
|
|
@ -93,7 +93,6 @@ class CommandBar extends Component {
|
|||
isPaused: PropTypes.bool.isRequired,
|
||||
isWaitingOnBreak: PropTypes.bool.isRequired,
|
||||
javascriptEnabled: PropTypes.bool.isRequired,
|
||||
trace: PropTypes.func.isRequired,
|
||||
resume: PropTypes.func.isRequired,
|
||||
skipPausing: PropTypes.bool.isRequired,
|
||||
stepIn: PropTypes.func.isRequired,
|
||||
|
@ -105,9 +104,6 @@ class CommandBar extends Component {
|
|||
toggleSkipPausing: PropTypes.any.isRequired,
|
||||
toggleSourceMapsEnabled: PropTypes.func.isRequired,
|
||||
topFrameSelected: PropTypes.bool.isRequired,
|
||||
logMethod: PropTypes.string.isRequired,
|
||||
logValues: PropTypes.bool.isRequired,
|
||||
traceOnNextInteraction: PropTypes.bool.isRequired,
|
||||
setHideOrShowIgnoredSources: PropTypes.func.isRequired,
|
||||
toggleSourceMapIgnoreList: PropTypes.func.isRequired,
|
||||
};
|
||||
|
|
|
@ -26,7 +26,6 @@ FrameTitle.propTypes = {
|
|||
frame: PropTypes.object.isRequired,
|
||||
options: PropTypes.object.isRequired,
|
||||
l10n: PropTypes.object.isRequired,
|
||||
showFrameContextMenu: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
function getFrameLocation(frame, shouldDisplayOriginalLocation) {
|
||||
|
@ -102,6 +101,7 @@ export default class FrameComponent extends Component {
|
|||
panel: PropTypes.oneOf(["debugger", "webconsole"]).isRequired,
|
||||
selectFrame: PropTypes.func.isRequired,
|
||||
selectedFrame: PropTypes.object,
|
||||
isTracerFrameSelected: PropTypes.bool.isRequired,
|
||||
shouldMapDisplayName: PropTypes.bool.isRequired,
|
||||
shouldDisplayOriginalLocation: PropTypes.bool.isRequired,
|
||||
showFrameContextMenu: PropTypes.func.isRequired,
|
||||
|
@ -144,6 +144,7 @@ export default class FrameComponent extends Component {
|
|||
const {
|
||||
frame,
|
||||
selectedFrame,
|
||||
isTracerFrameSelected,
|
||||
hideLocation,
|
||||
shouldMapDisplayName,
|
||||
displayFullUrl,
|
||||
|
@ -155,7 +156,15 @@ export default class FrameComponent extends Component {
|
|||
const { l10n } = this.context;
|
||||
|
||||
const className = classnames("frame", {
|
||||
selected: selectedFrame && selectedFrame.id === frame.id,
|
||||
selected:
|
||||
!isTracerFrameSelected &&
|
||||
selectedFrame &&
|
||||
selectedFrame.id === frame.id,
|
||||
// When a JS Tracer frame is selected, the frame will still be considered as selected,
|
||||
// and switch from a blue to a grey background. It will still be considered as selected
|
||||
// from the point of view of stepping buttons.
|
||||
inactive:
|
||||
isTracerFrameSelected && selectedFrame && selectedFrame.id === frame.id,
|
||||
});
|
||||
|
||||
const location = getFrameLocation(frame, shouldDisplayOriginalLocation);
|
||||
|
|
|
@ -99,6 +99,12 @@
|
|||
color: white;
|
||||
}
|
||||
|
||||
.frames [role="list"] [role="listitem"].inactive,
|
||||
.frames [role="list"] [role="listitem"].inactive.async-label {
|
||||
background-color: light-dark(var(--theme-toolbar-background-alt), var(--theme-body-alternate-emphasized-background));
|
||||
}
|
||||
|
||||
|
||||
.frames [role="list"] [role="listitem"].selected i.annotation-logo svg path {
|
||||
fill: white;
|
||||
}
|
||||
|
|
|
@ -66,6 +66,7 @@ export default class Group extends Component {
|
|||
selectFrame: PropTypes.func.isRequired,
|
||||
selectLocation: PropTypes.func,
|
||||
selectedFrame: PropTypes.object,
|
||||
isTracerFrameSelected: PropTypes.bool.isRequired,
|
||||
showFrameContextMenu: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
@ -91,6 +92,7 @@ export default class Group extends Component {
|
|||
selectFrame,
|
||||
selectLocation,
|
||||
selectedFrame,
|
||||
isTracerFrameSelected,
|
||||
displayFullUrl,
|
||||
getFrameTitle,
|
||||
disableContextMenu,
|
||||
|
@ -115,6 +117,7 @@ export default class Group extends Component {
|
|||
hideLocation: true,
|
||||
key: frame.id,
|
||||
selectedFrame,
|
||||
isTracerFrameSelected,
|
||||
selectFrame,
|
||||
selectLocation,
|
||||
shouldMapDisplayName: false,
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
getCurrentThreadFrames,
|
||||
getCurrentThread,
|
||||
getShouldSelectOriginalLocation,
|
||||
getSelectedTraceIndex,
|
||||
} from "../../../selectors/index";
|
||||
|
||||
const NUM_FRAMES_SHOWN = 7;
|
||||
|
@ -43,6 +44,7 @@ class Frames extends Component {
|
|||
selectFrame: PropTypes.func.isRequired,
|
||||
selectLocation: PropTypes.func,
|
||||
selectedFrame: PropTypes.object,
|
||||
isTracerFrameSelected: PropTypes.bool.isRequired,
|
||||
showFrameContextMenu: PropTypes.func,
|
||||
shouldDisplayOriginalLocation: PropTypes.bool,
|
||||
};
|
||||
|
@ -52,6 +54,7 @@ class Frames extends Component {
|
|||
const {
|
||||
frames,
|
||||
selectedFrame,
|
||||
isTracerFrameSelected,
|
||||
frameworkGroupingOn,
|
||||
shouldDisplayOriginalLocation,
|
||||
} = this.props;
|
||||
|
@ -59,6 +62,7 @@ class Frames extends Component {
|
|||
return (
|
||||
frames !== nextProps.frames ||
|
||||
selectedFrame !== nextProps.selectedFrame ||
|
||||
isTracerFrameSelected !== nextProps.isTracerFrameSelected ||
|
||||
showAllFrames !== nextState.showAllFrames ||
|
||||
frameworkGroupingOn !== nextProps.frameworkGroupingOn ||
|
||||
shouldDisplayOriginalLocation !== nextProps.shouldDisplayOriginalLocation
|
||||
|
@ -93,6 +97,7 @@ class Frames extends Component {
|
|||
selectFrame,
|
||||
selectLocation,
|
||||
selectedFrame,
|
||||
isTracerFrameSelected,
|
||||
displayFullUrl,
|
||||
getFrameTitle,
|
||||
disableContextMenu,
|
||||
|
@ -119,6 +124,7 @@ class Frames extends Component {
|
|||
selectFrame,
|
||||
selectLocation,
|
||||
selectedFrame,
|
||||
isTracerFrameSelected,
|
||||
shouldDisplayOriginalLocation,
|
||||
key: String(frameOrGroup.id),
|
||||
displayFullUrl,
|
||||
|
@ -132,6 +138,7 @@ class Frames extends Component {
|
|||
selectFrame,
|
||||
selectLocation,
|
||||
selectedFrame,
|
||||
isTracerFrameSelected,
|
||||
key: frameOrGroup[0].id,
|
||||
displayFullUrl,
|
||||
getFrameTitle,
|
||||
|
@ -203,6 +210,7 @@ const mapStateToProps = state => ({
|
|||
frames: getCurrentThreadFrames(state),
|
||||
frameworkGroupingOn: getFrameworkGroupingState(state),
|
||||
selectedFrame: getSelectedFrame(state, getCurrentThread(state)),
|
||||
isTracerFrameSelected: getSelectedTraceIndex(state) != null,
|
||||
shouldDisplayOriginalLocation: getShouldSelectOriginalLocation(state),
|
||||
disableFrameTruncate: false,
|
||||
disableContextMenu: false,
|
||||
|
|
|
@ -360,7 +360,7 @@ const mapStateToProps = state => {
|
|||
let originalFrameScopes;
|
||||
let generatedFrameScopes;
|
||||
let isLoading;
|
||||
let mapScopesEnabled;
|
||||
let mapScopesEnabled = false;
|
||||
|
||||
if (
|
||||
selectedSource?.isOriginal &&
|
||||
|
|
|
@ -88,7 +88,7 @@ class SecondaryPanes extends Component {
|
|||
mapScopesEnabled: PropTypes.bool.isRequired,
|
||||
pauseReason: PropTypes.string.isRequired,
|
||||
shouldBreakpointsPaneOpenOnPause: PropTypes.bool.isRequired,
|
||||
thread: PropTypes.string.isRequired,
|
||||
thread: PropTypes.string,
|
||||
renderWhyPauseDelay: PropTypes.number.isRequired,
|
||||
selectedFrame: PropTypes.object,
|
||||
skipPausing: PropTypes.bool.isRequired,
|
||||
|
@ -184,6 +184,7 @@ class SecondaryPanes extends Component {
|
|||
return {
|
||||
header: L10N.getStr("scopes.header"),
|
||||
className: "scopes-pane",
|
||||
id: "scopes-pane",
|
||||
component: React.createElement(Scopes, null),
|
||||
opened: prefs.scopesVisible,
|
||||
buttons: this.getScopesButtons(),
|
||||
|
|
|
@ -26,7 +26,3 @@
|
|||
.popover:not(.orientation-right) .preview-popup {
|
||||
margin-left: var(--left-offset);
|
||||
}
|
||||
|
||||
.popover .add-to-expression-bar {
|
||||
margin-left: var(--left-offset);
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
@import url("chrome://devtools/content/debugger/src/components/Editor/Editor.css");
|
||||
@import url("chrome://devtools/content/debugger/src/components/Editor/Footer.css");
|
||||
@import url("chrome://devtools/content/debugger/src/components/Editor/InlinePreview.css");
|
||||
@import url("chrome://devtools/content/debugger/src/components/Editor/Preview.css");
|
||||
@import url("chrome://devtools/content/debugger/src/components/Editor/Preview/Popup.css");
|
||||
@import url("chrome://devtools/content/debugger/src/components/Editor/SearchInFileBar.css");
|
||||
@import url("chrome://devtools/content/debugger/src/components/Editor/Tabs.css");
|
||||
|
|
|
@ -58,6 +58,7 @@ const createInitialPauseState = () => ({
|
|||
previousLocation: null,
|
||||
expandedScopes: new Set(),
|
||||
lastExpandedScopes: [],
|
||||
shouldBreakpointsPaneOpenOnPause: false,
|
||||
});
|
||||
|
||||
export function getThreadPauseState(state, thread) {
|
||||
|
|
|
@ -238,6 +238,11 @@ export function getSelectedFrameId(state, thread) {
|
|||
|
||||
export function isTopFrameSelected(state, thread) {
|
||||
const selectedFrameId = getSelectedFrameId(state, thread);
|
||||
// Consider that the top frame is selected when none is specified,
|
||||
// which happens when a JS Tracer frame is selected.
|
||||
if (!selectedFrameId) {
|
||||
return true;
|
||||
}
|
||||
const topFrame = getTopFrame(state, thread);
|
||||
return selectedFrameId == topFrame?.id;
|
||||
}
|
||||
|
|
|
@ -40,18 +40,21 @@ add_task(async function () {
|
|||
bp = findBreakpoint(dbg, "simple2.js", 5);
|
||||
is(bp.options.condition, "12", "Hit 'Enter' doesn't add a new line");
|
||||
|
||||
info("Hit 'Alt+Enter' when the cursor is in the conditional statement");
|
||||
info("Hit 'Shift+Enter' when the cursor is in the conditional statement");
|
||||
rightClickElement(dbg, "gutter", 5);
|
||||
await waitForContextMenu(dbg);
|
||||
selectContextMenuItem(dbg, `${selectors.editConditionItem}`);
|
||||
await waitForConditionalPanelFocus(dbg);
|
||||
// Move one char left to put the cursor between 1 and 2
|
||||
pressKey(dbg, "Left");
|
||||
pressKey(dbg, "AltEnter");
|
||||
// Insert a new line
|
||||
pressKey(dbg, "ShiftEnter");
|
||||
// And validate
|
||||
pressKey(dbg, "Enter");
|
||||
await waitForCondition(dbg, "1\n2");
|
||||
|
||||
bp = findBreakpoint(dbg, "simple2.js", 5);
|
||||
is(bp.options.condition, "1\n2", "Hit 'Alt+Enter' adds a new line");
|
||||
is(bp.options.condition, "1\n2", "Hit 'Shift+Enter' adds a new line");
|
||||
|
||||
clickElement(dbg, "gutter", 5);
|
||||
await waitForDispatch(dbg.store, "REMOVE_BREAKPOINT");
|
||||
|
|
|
@ -42,11 +42,14 @@ add_task(async function () {
|
|||
invokeInTab("main");
|
||||
|
||||
info("Wait for the call tree to appear in the tracer panel");
|
||||
const tree = await waitForElementWithSelector(dbg, "#tracer-tab-panel .tree");
|
||||
const tracerTree = await waitForElementWithSelector(
|
||||
dbg,
|
||||
"#tracer-tab-panel .tree"
|
||||
);
|
||||
|
||||
info("Wait for the expected traces to appear in the call tree");
|
||||
const traces = await waitFor(() => {
|
||||
const elements = tree.querySelectorAll(".trace-line");
|
||||
let traces = await waitFor(() => {
|
||||
const elements = tracerTree.querySelectorAll(".trace-line");
|
||||
if (elements.length == 3) {
|
||||
return elements;
|
||||
}
|
||||
|
@ -59,6 +62,10 @@ add_task(async function () {
|
|||
info("Select the trace for the call to `foo`");
|
||||
EventUtils.synthesizeMouseAtCenter(traces[1], {}, dbg.win);
|
||||
|
||||
let focusedTrace = tracerTree.querySelector(".tree-node.focused .trace-line");
|
||||
is(focusedTrace, traces[1], "The clicked trace is now focused");
|
||||
|
||||
// Naive sanity checks for inlines previews
|
||||
const inlinePreviews = [
|
||||
{
|
||||
identifier: "x:",
|
||||
|
@ -89,6 +96,76 @@ add_task(async function () {
|
|||
index++;
|
||||
}
|
||||
|
||||
// Naive sanity checks for popup previews on hovering
|
||||
{
|
||||
const { element: popupEl, tokenEl } = await tryHovering(
|
||||
dbg,
|
||||
1,
|
||||
14,
|
||||
"previewPopup"
|
||||
);
|
||||
is(popupEl.querySelector(".objectBox")?.textContent, "1");
|
||||
await closePreviewForToken(dbg, tokenEl, "previewPopup");
|
||||
}
|
||||
|
||||
{
|
||||
const { element: popupEl, tokenEl } = await tryHovering(
|
||||
dbg,
|
||||
1,
|
||||
17,
|
||||
"previewPopup"
|
||||
);
|
||||
is(popupEl.querySelector(".objectBox")?.textContent, "2");
|
||||
await closePreviewForToken(dbg, tokenEl, "previewPopup");
|
||||
}
|
||||
|
||||
let focusedPausedFrame = findElementWithSelector(
|
||||
dbg,
|
||||
".frames .frame.selected"
|
||||
);
|
||||
ok(!focusedPausedFrame, "Before pausing, there is no selected paused frame");
|
||||
|
||||
info("Trigger a breakpoint");
|
||||
const onResumed = SpecialPowers.spawn(
|
||||
gBrowser.selectedBrowser,
|
||||
[],
|
||||
async function () {
|
||||
content.eval("debugger;");
|
||||
}
|
||||
);
|
||||
await waitForPaused(dbg);
|
||||
await waitForSelectedLocation(dbg, 1, 0);
|
||||
|
||||
focusedPausedFrame = findElementWithSelector(dbg, ".frames .frame.selected");
|
||||
ok(
|
||||
!!focusedPausedFrame,
|
||||
"When paused, a frame is selected in the call stack panel"
|
||||
);
|
||||
|
||||
focusedTrace = tracerTree.querySelector(".tree-node.focused .trace-line");
|
||||
is(focusedTrace, null, "When pausing, there is no trace selected anymore");
|
||||
|
||||
info("Re select the tracer frame while being paused");
|
||||
EventUtils.synthesizeMouseAtCenter(traces[1], {}, dbg.win);
|
||||
|
||||
await waitForSelectedLocation(dbg, 1, 12);
|
||||
focusedPausedFrame = findElementWithSelector(dbg, ".frames .frame.selected");
|
||||
ok(
|
||||
!focusedPausedFrame,
|
||||
"While paused, if we select a tracer frame, the paused frame is no longer highlighted in the call stack panel"
|
||||
);
|
||||
const highlightedPausedFrame = findElementWithSelector(
|
||||
dbg,
|
||||
".frames .frame.inactive"
|
||||
);
|
||||
ok(
|
||||
!!highlightedPausedFrame,
|
||||
"But it is still highlighted as inactive with a grey background"
|
||||
);
|
||||
|
||||
await resume(dbg);
|
||||
await onResumed;
|
||||
|
||||
// Trigger a click in the content page to verify we do trace DOM events
|
||||
BrowserTestUtils.synthesizeMouseAtCenter(
|
||||
"button",
|
||||
|
@ -97,13 +174,18 @@ add_task(async function () {
|
|||
);
|
||||
|
||||
const clickTrace = await waitFor(() =>
|
||||
tree.querySelector(".tracer-dom-event")
|
||||
tracerTree.querySelector(".tracer-dom-event")
|
||||
);
|
||||
is(clickTrace.textContent, "DOM | click");
|
||||
is(
|
||||
tracerTree.querySelectorAll(".trace-line").length,
|
||||
6,
|
||||
"The click event adds two elements in the tree. The DOM Event and its top frame"
|
||||
);
|
||||
|
||||
await BrowserTestUtils.synthesizeKey("x", {}, gBrowser.selectedBrowser);
|
||||
const keyTrace = await waitFor(() => {
|
||||
const elts = tree.querySelectorAll(".tracer-dom-event");
|
||||
const elts = tracerTree.querySelectorAll(".tracer-dom-event");
|
||||
if (elts.length == 2) {
|
||||
return elts[1];
|
||||
}
|
||||
|
@ -111,9 +193,52 @@ add_task(async function () {
|
|||
});
|
||||
is(keyTrace.textContent, "DOM | keypress");
|
||||
|
||||
// Assert the final content of the tree before stopping
|
||||
const finalTreeSize = 7;
|
||||
is(tree.querySelectorAll(".trace-line").length, finalTreeSize);
|
||||
is(
|
||||
tracerTree.querySelectorAll(".trace-line").length,
|
||||
8,
|
||||
"The key event adds two elements in the tree. The DOM Event and its top frame"
|
||||
);
|
||||
|
||||
info("Trigger a DOM Mutation");
|
||||
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
|
||||
content.eval(`
|
||||
window.doMutation = () => {
|
||||
const div = document.createElement("div");
|
||||
document.body.appendChild(div);
|
||||
//# sourceURL=foo.js
|
||||
};
|
||||
`);
|
||||
content.wrappedJSObject.doMutation();
|
||||
});
|
||||
|
||||
// Wait for the `eval` and the `doMutation` calls to be rendered
|
||||
traces = await waitFor(() => {
|
||||
// Scroll to bottom to ensure rendering the last elements (otherwise they are not because of VirtualizedTree)
|
||||
tracerTree.scrollTop = tracerTree.scrollHeight;
|
||||
const elements = tracerTree.querySelectorAll(".trace-line");
|
||||
// Wait for the expected element to be rendered
|
||||
if (
|
||||
elements[elements.length - 1].textContent.includes("window.doMutation")
|
||||
) {
|
||||
return elements;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const doMutationTrace = traces[traces.length - 1];
|
||||
is(doMutationTrace.textContent, "λ window.doMutation eval:2:32");
|
||||
|
||||
// Expand the call to doMutation in order to show the DOM Mutation in the tree
|
||||
doMutationTrace.querySelector(".arrow").click();
|
||||
|
||||
const mutationTrace = await waitFor(() =>
|
||||
tracerTree.querySelector(".tracer-dom-mutation")
|
||||
);
|
||||
is(mutationTrace.textContent, "DOM Mutation | add");
|
||||
|
||||
// Click on the mutation trace to open its source
|
||||
mutationTrace.click();
|
||||
await waitForSelectedSource(dbg, "foo.js");
|
||||
|
||||
// Test Disabling tracing
|
||||
info("Disable the tracing");
|
||||
|
|
|
@ -136,9 +136,9 @@ var gDevToolsBrowser = (exports.gDevToolsBrowser = {
|
|||
},
|
||||
|
||||
/**
|
||||
* This function makes sure that the "devtoolstheme" attribute is set on the browser
|
||||
* window to make it possible to change colors on elements in the browser (like the
|
||||
* splitter between the toolbox and web content).
|
||||
* This function makes sure that the "devtoolstheme" attribute is set on the
|
||||
* browser window to make it possible to change colors on elements in the
|
||||
* browser (like the splitter between the toolbox and web content).
|
||||
*/
|
||||
updateDevtoolsThemeAttribute(win) {
|
||||
// Set an attribute on root element of each window to make it possible
|
||||
|
@ -147,13 +147,7 @@ var gDevToolsBrowser = (exports.gDevToolsBrowser = {
|
|||
if (devtoolsTheme != "dark") {
|
||||
devtoolsTheme = "light";
|
||||
}
|
||||
|
||||
// Style the splitter between the toolbox and page content. This used to
|
||||
// set the attribute on the browser's root node but that regressed tpaint:
|
||||
// bug 1331449.
|
||||
win.document
|
||||
.getElementById("appcontent")
|
||||
.setAttribute("devtoolstheme", devtoolsTheme);
|
||||
win.document.documentElement.setAttribute("devtoolstheme", devtoolsTheme);
|
||||
},
|
||||
|
||||
observe(subject, topic, prefName) {
|
||||
|
|
|
@ -12,21 +12,21 @@ add_task(async function testDevtoolsTheme() {
|
|||
info("Checking stylesheet and :root attributes based on devtools theme.");
|
||||
Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "light");
|
||||
is(
|
||||
document.getElementById("appcontent").getAttribute("devtoolstheme"),
|
||||
document.documentElement.getAttribute("devtoolstheme"),
|
||||
"light",
|
||||
"The element has an attribute based on devtools theme."
|
||||
);
|
||||
|
||||
Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "dark");
|
||||
is(
|
||||
document.getElementById("appcontent").getAttribute("devtoolstheme"),
|
||||
document.documentElement.getAttribute("devtoolstheme"),
|
||||
"dark",
|
||||
"The element has an attribute based on devtools theme."
|
||||
);
|
||||
|
||||
Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "unknown");
|
||||
is(
|
||||
document.getElementById("appcontent").getAttribute("devtoolstheme"),
|
||||
document.documentElement.getAttribute("devtoolstheme"),
|
||||
"light",
|
||||
"The element has 'light' as a default for the devtoolstheme attribute."
|
||||
);
|
||||
|
|
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