68.14.3 - everything else

This commit is contained in:
Fedor 2024-07-21 11:54:34 +03:00
parent 2c2b226a2c
commit 48cfaacdb7
1138 changed files with 20235 additions and 35950 deletions

View File

@ -63,10 +63,6 @@ devtools/client/shared/sourceeditor/codemirror/.*
devtools/client/shared/sourceeditor/tern/.*
dom/canvas/test/webgl-conf/checkout/closure-library/.*
dom/media/gmp/rlz/.*
dom/media/gmp/widevine-adapter/content_decryption_module.h
dom/media/gmp/widevine-adapter/content_decryption_module_export.h
dom/media/gmp/widevine-adapter/content_decryption_module_ext.h
dom/media/gmp/widevine-adapter/content_decryption_module_proxy.h
dom/media/platforms/ffmpeg/ffmpeg57/.*
dom/media/platforms/ffmpeg/ffmpeg58/.*
dom/media/platforms/ffmpeg/libav53/.*
@ -111,7 +107,6 @@ js/src/vtune/jitprofiling.c
js/src/vtune/jitprofiling.h
js/src/vtune/legacy/.*
media/ffvpx/.*
media/gmp-clearkey/0.1/openaes/.*
media/kiss_fft/.*
media/libaom/.*
media/libcubeb/.*

View File

@ -210,7 +210,6 @@ layout/mathml/imptests/
# Exclude everything but self-hosted JS
js/ductwork/
js/examples/
js/ipc/
js/public/
js/xpconnect/
js/src/devtools/

View File

@ -71,13 +71,6 @@ module.exports = {
"env": {
"mozilla/browser-window": true
}
}, {
// TODO: Bug 1513639. Temporarily turn off reject-importGlobalProperties
// due to other ESLint enabling happening in DOM.
"files": "dom/**",
"rules": {
"mozilla/reject-importGlobalProperties": "off",
}
}, {
// TODO: Bug 1515949. Enable no-undef for gfx/
"files": "gfx/layers/apz/test/mochitest/**",

View File

@ -1,5 +1,5 @@
python
import sys
sys.path.append('third_party/python/gdbpp/')
sys.path.append('python/gdbpp/')
import gdbpp
end

3
.gitignore vendored
View File

@ -99,6 +99,9 @@ devtools/client/debugger/assets/module-manifest.json
# Ignore node_modules directories in devtools
devtools/**/node_modules
# Ignore browsertime output directory
browsertime-results
# Tag files generated by GNU Global
GTAGS
GRTAGS

28
Cargo.lock generated
View File

@ -1235,6 +1235,7 @@ name = "gkrust"
version = "0.1.0"
dependencies = [
"gkrust-shared",
"mozglue-static",
"mozilla-central-workspace-hack",
"stylo_tests",
]
@ -1245,6 +1246,7 @@ version = "0.1.0"
dependencies = [
"bench-collections-gtest",
"gkrust-shared",
"mozglue-static",
"mp4parse-gtest",
"nsstring-gtest",
"xpcom-gtest",
@ -1555,6 +1557,8 @@ name = "jsrust"
version = "0.1.0"
dependencies = [
"jsrust_shared",
"mozglue-static",
"wat",
]
[[package]]
@ -1624,6 +1628,12 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
[[package]]
name = "leb128"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a"
[[package]]
name = "libc"
version = "0.2.60"
@ -3744,6 +3754,24 @@ version = "0.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a026c1436af49d5537c7561c7474f81f7a754e36445fe52e6e88795a9747291c"
[[package]]
name = "wast"
version = "23.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b080a48623c1b15193eac2e28c7b8d0e6b2e1c6c67ed46ddcd86063e78e504"
dependencies = [
"leb128",
]
[[package]]
name = "wat"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c350d7431aa486488d28cdf75b57d59c02fab9cde20d93c52424510afe18ecc"
dependencies = [
"wast",
]
[[package]]
name = "webdriver"
version = "0.40.1"

View File

@ -150,13 +150,6 @@ endif
recurse_artifact:
$(topsrcdir)/mach --log-no-times artifact install$(if $(MOZ_ARTIFACT_BUILD_SYMBOLS), --symbols$(addprefix =,$(filter full,$(MOZ_ARTIFACT_BUILD_SYMBOLS))))$(if $(MOZ_AUTOMATION), --host-bins)
ifdef MOZ_EME_WIN32_ARTIFACT
recurse_win32-artifact:
rm -rf $(DIST)/i686
$(topsrcdir)/mach --log-no-times artifact install --job $(if $(MOZ_PGO),win32-pgo,win32-opt) --no-tests --distdir $(DIST)/i686
mv $(DIST)/i686/bin/* $(DIST)/i686
endif
ifdef MOZ_ANDROID_FAT_AAR_ARCHITECTURES
recurse_android-fat-aar-artifact:
$(call py_action,fat_aar,$(MOZ_ANDROID_FAT_AAR_ARCHITECTURES) --distdir $(abspath $(DIST)/fat-aar))

View File

@ -31,19 +31,10 @@ function isEventForAutocompleteItem(event) {
* search isn't finished yet.
*/
function waitForSearchFinish() {
if (UrlbarPrefs.get("quantumbar")) {
return Promise.all([
gURLBar.lastQueryContextPromise,
BrowserTestUtils.waitForCondition(() => gURLBar.view.isOpen),
]);
}
return BrowserTestUtils.waitForCondition(
() =>
gURLBar.popupOpen &&
gURLBar.controller.searchStatus >=
Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH,
"Waiting for search to complete"
);
return Promise.all([
gURLBar.lastQueryContextPromise,
BrowserTestUtils.waitForCondition(() => gURLBar.view.isOpen),
]);
}
// Check that the URL bar manages accessibility focus appropriately.
@ -132,9 +123,7 @@ async function runTests() {
EventUtils.synthesizeKey("KEY_ArrowLeft");
await focused;
testStates(textBox, STATE_FOCUSED);
if (UrlbarPrefs.get("quantumbar")) {
gURLBar.view.close();
}
gURLBar.view.close();
// On Mac, down arrow when not at the end of the field moves to the end.
// Move back to the end so the next press of down arrow opens the popup.
EventUtils.synthesizeKey("KEY_ArrowRight");

View File

@ -14,9 +14,7 @@ add_task(async function testAutocompleteRichResult() {
let tab = await openNewTab("data:text/html;charset=utf-8,");
let accService = await initAccessibilityService();
info(
"Opening the URL bar and entering a key to show the PopupAutoCompleteRichResult panel"
);
info("Opening the URL bar and entering a key to show the urlbar panel");
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
waitForFocus,
@ -25,22 +23,10 @@ add_task(async function testAutocompleteRichResult() {
info("Waiting for accessibility to be created for the richlistbox");
let resultsView;
if (UrlbarPrefs.get("quantumbar")) {
resultsView = gURLBar.view.panel.querySelector("#urlbarView-results");
await BrowserTestUtils.waitForCondition(() =>
accService.getAccessibleFor(resultsView)
);
} else {
let urlbarPopup = document.getElementById("PopupAutoCompleteRichResult");
resultsView = document.getAnonymousElementByAttribute(
urlbarPopup,
"anonid",
"richlistbox"
);
await BrowserTestUtils.waitForCondition(() =>
accService.getAccessibleFor(resultsView)
);
}
resultsView = gURLBar.view.panel.querySelector("#urlbarView-results");
await BrowserTestUtils.waitForCondition(() =>
accService.getAccessibleFor(resultsView)
);
info("Confirming that the special case is handled in XULListboxAccessible");
let accessible = accService.getAccessibleFor(resultsView);

View File

@ -27,6 +27,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
InlineSpellCheckerContent:
"resource://gre/modules/InlineSpellCheckerContent.jsm",
ContentDOMReference: "resource://gre/modules/ContentDOMReference.jsm",
});
XPCOMUtils.defineLazyGetter(this, "PageMenuChild", () => {
@ -188,6 +189,8 @@ const messageListeners = {
let ctxDraw = canvas.getContext("2d");
ctxDraw.drawImage(video, 0, 0);
// Note: if changing the content type, don't forget to update
// consumers that also hardcode this content type.
this.mm.sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage:Result", {
dataURL: canvas.toDataURL("image/jpeg", ""),
});
@ -531,7 +534,7 @@ class ContextMenuChild extends ActorChild {
// The same-origin check will be done in nsContextMenu.openLinkInTab.
let parentAllowsMixedContent = !!this.docShell.mixedContentChannel;
let disableSetDesktopBg = null;
let disableSetDesktopBackground = null;
// Media related cache info parent needs for saving
let contentType = null;
@ -541,7 +544,7 @@ class ContextMenuChild extends ActorChild {
aEvent.composedTarget instanceof Ci.nsIImageLoadingContent &&
aEvent.composedTarget.currentURI
) {
disableSetDesktopBg = this._disableSetDesktopBackground(
disableSetDesktopBackground = this._disableSetDesktopBackground(
aEvent.composedTarget
);
@ -587,6 +590,7 @@ class ContextMenuChild extends ActorChild {
Ci.nsIReferrerInfo
);
referrerInfo.initWithElement(aEvent.composedTarget);
referrerInfo = E10SUtils.serializeReferrerInfo(referrerInfo);
// In the case "onLink" we may have to send link referrerInfo to use in
// _openLinkInParameters
@ -598,45 +602,38 @@ class ContextMenuChild extends ActorChild {
linkReferrerInfo.initWithElement(context.link);
}
let targetAsCPOW = context.target;
if (targetAsCPOW) {
let target = context.target;
if (target) {
this._cleanContext();
}
let isRemote =
Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
editFlags = SpellCheckHelper.isEditable(
aEvent.composedTarget,
this.content
);
if (isRemote) {
editFlags = SpellCheckHelper.isEditable(
aEvent.composedTarget,
this.content
if (editFlags & SpellCheckHelper.SPELLCHECKABLE) {
spellInfo = InlineSpellCheckerContent.initContextMenu(
aEvent,
editFlags,
this
);
if (editFlags & SpellCheckHelper.SPELLCHECKABLE) {
spellInfo = InlineSpellCheckerContent.initContextMenu(
aEvent,
editFlags,
this.mm
);
}
// Set the event target first as the copy image command needs it to
// determine what was context-clicked on. Then, update the state of the
// commands on the context menu.
this.docShell.contentViewer
.QueryInterface(Ci.nsIContentViewerEdit)
.setCommandNode(aEvent.composedTarget);
aEvent.composedTarget.ownerGlobal.updateCommands("contentcontextmenu");
customMenuItems = PageMenuChild.build(aEvent.composedTarget);
principal = doc.nodePrincipal;
}
// Set the event target first as the copy image command needs it to
// determine what was context-clicked on. Then, update the state of the
// commands on the context menu.
this.docShell.contentViewer
.QueryInterface(Ci.nsIContentViewerEdit)
.setCommandNode(aEvent.composedTarget);
aEvent.composedTarget.ownerGlobal.updateCommands("contentcontextmenu");
principal = doc.nodePrincipal;
let data = {
context,
charSet,
baseURI,
isRemote,
referrerInfo,
editFlags,
principal,
@ -650,26 +647,22 @@ class ContextMenuChild extends ActorChild {
contentDisposition,
frameOuterWindowID,
popupNodeSelectors,
disableSetDesktopBg,
disableSetDesktopBackground,
parentAllowsMixedContent,
};
if (context.inFrame && !context.inSrcdocFrame) {
if (isRemote) {
data.frameReferrerInfo = E10SUtils.serializeReferrerInfo(
doc.referrerInfo
);
} else {
data.frameReferrerInfo = doc.referrerInfo;
}
data.frameReferrerInfo = E10SUtils.serializeReferrerInfo(
doc.referrerInfo
);
}
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
data.customMenuItems = PageMenuChild.build(aEvent.composedTarget);
}
if (linkReferrerInfo) {
if (isRemote) {
data.linkReferrerInfo = E10SUtils.serializeReferrerInfo(linkReferrerInfo);
} else {
data.linkReferrerInfo = linkReferrerInfo;
}
data.linkReferrerInfo = E10SUtils.serializeReferrerInfo(linkReferrerInfo);
}
Services.obs.notifyObservers(
@ -677,27 +670,14 @@ class ContextMenuChild extends ActorChild {
"on-prepare-contextmenu"
);
if (isRemote) {
data.referrerInfo = E10SUtils.serializeReferrerInfo(data.referrerInfo);
if (data.frameReferrerInfo) {
data.frameReferrerInfo =
E10SUtils.serializeReferrerInfo(data.frameReferrerInfo);
}
// In the event that the content is running in the parent process, we don't
// actually want the contextmenu events to reach the parent - we'll dispatch
// a new contextmenu event after the async message has reached the parent
// instead.
aEvent.preventDefault();
aEvent.stopPropagation();
this.mm.sendAsyncMessage("contextmenu", data, {
targetAsCPOW,
});
} else {
let browser = this.docShell.chromeEventHandler;
let mainWin = browser.ownerGlobal;
data.documentURIObject = doc.documentURIObject;
data.disableSetDesktopBackground = data.disableSetDesktopBg;
delete data.disableSetDesktopBg;
data.context.targetAsCPOW = targetAsCPOW;
mainWin.setContextMenuContentData(data);
}
this.mm.sendAsyncMessage("contextmenu", data);
}
/**
@ -869,6 +849,7 @@ class ContextMenuChild extends ActorChild {
// Remember the node and its owner document that was clicked
// This may be modifed before sending to nsContextMenu
context.target = node;
context.targetIdentifier = ContentDOMReference.get(node);
context.principal = context.target.ownerDocument.nodePrincipal;
context.storagePrincipal =

View File

@ -6,13 +6,6 @@
# * permission is an integer between 1 and 15
# See nsPermissionManager.cpp for more...
# UITour
origin uitour 1 https://www.mozilla.org
origin uitour 1 https://screenshots.firefox.com
origin uitour 1 https://support.mozilla.org
origin uitour 1 about:home
origin uitour 1 about:newtab
# Remote troubleshooting
origin remote-troubleshooting 1 https://support.mozilla.org

View File

@ -147,15 +147,6 @@ pref("lightweightThemes.getMoreURL", "data:text/plain,");
pref("browser.eme.ui.enabled", false);
#endif
// UI tour experience.
pref("browser.uitour.enabled", false);
pref("browser.uitour.loglevel", "Error");
pref("browser.uitour.requireSecure", true);
pref("browser.uitour.themeOrigin", "data:text/plain,");
pref("browser.uitour.url", "data:text/plain,");
// How long to show a Hearbeat survey (two hours, in seconds)
pref("browser.uitour.surveyDuration", 7200);
pref("keyword.enabled", false);
pref("browser.fixup.domainwhitelist.localhost", true);
@ -1612,9 +1603,6 @@ pref("dom.ipc.processPrelaunch.enabled", true);
pref("browser.migrate.chrome.history.limit", 2000);
pref("browser.migrate.chrome.history.maxAgeInDays", 180);
// Enable browser frames for use on desktop. Only exposed to chrome callers.
pref("dom.mozBrowserFramesEnabled", true);
pref("signon.generation.available", true);
pref("signon.generation.enabled", true);
pref("signon.schemeUpgrades", true);

View File

@ -572,10 +572,6 @@
#else
/>
#endif
<menuitem id="menu_openTour"
oncommand="openTourPage();"
label="&helpShowTour2.label;"
accesskey="&helpShowTour2.accesskey;"/>
<menuitem id="menu_keyboardShortcuts"
oncommand="openHelpLink('')"
onclick="checkForMiddleClick(this, event);"

View File

@ -78,7 +78,7 @@
#toolbar-menubar[autohide="true"][inactive="true"]:not([customizing="true"]) {
min-height: 0 !important;
height: 0 !important;
-moz-appearance: none !important;
appearance: none !important;
}
%endif
@ -321,7 +321,8 @@ window:not([chromehidden~="toolbar"]) #nav-bar[nonemptyoverflow] > .overflow-but
}
.titlebar-buttonbox {
-moz-appearance: -moz-window-button-box;
appearance: auto;
-moz-default-appearance: -moz-window-button-box;
position: relative;
}
@ -340,7 +341,8 @@ toolbarpaletteitem {
%ifdef XP_MACOSX
#titlebar-fullscreen-button {
-moz-appearance: -moz-mac-fullscreen-button;
appearance: auto;
-moz-default-appearance: -moz-mac-fullscreen-button;
}
/**
@ -541,9 +543,6 @@ toolbar:not(#TabsToolbar) > #personal-bookmarks {
min-width: 1px;
}
#urlbar[quantumbar="false"] {
-moz-binding: url(chrome://browser/content/urlbarBindings.xml#legacy-urlbar);
}
#urlbar[quantumbar="true"] {
-moz-binding: url(chrome://browser/content/urlbarBindings.xml#urlbar);
@ -624,71 +623,6 @@ html|input.urlbar-input {
}
}
/* Always show URLs LTR. */
.ac-url-text:-moz-locale-dir(rtl),
.ac-title-text[lookslikeurl]:-moz-locale-dir(rtl) {
direction: ltr !important;
}
/* Never show a scrollbar for the Location Bar popup. This overrides the
richlistbox inline overflow: auto style.*/
#PopupAutoCompleteRichResult > richlistbox {
overflow: hidden !important;
}
/* For non-action items, hide the action text; for action items, hide the URL
text. Don't show the separator for keyword results. */
#PopupAutoCompleteRichResult > richlistbox > richlistitem > .ac-type-icon,
#PopupAutoCompleteRichResult > richlistbox > richlistitem > .ac-site-icon,
#PopupAutoCompleteRichResult > richlistbox > richlistitem > .ac-tags:not([empty]),
#PopupAutoCompleteRichResult > richlistbox > richlistitem > .ac-separator:not([type=keyword]),
#PopupAutoCompleteRichResult > richlistbox > richlistitem > .ac-url:not([actiontype]),
#PopupAutoCompleteRichResult > richlistbox > richlistitem > .ac-action[actiontype],
#PopupAutoCompleteRichResult > richlistbox > richlistitem[selected] > .ac-url[actiontype=remotetab],
#PopupAutoCompleteRichResult > richlistbox > richlistitem:hover > .ac-url[actiontype=remotetab]
{
display: -moz-box;
}
#PopupAutoCompleteRichResult > richlistbox > richlistitem[selected] > .ac-action[actiontype=remotetab],
#PopupAutoCompleteRichResult > richlistbox > richlistitem:hover > .ac-action[actiontype=remotetab] {
display: none;
}
/* Normally we want to show the "Search with Engine" label only on hover or
selection, but we also want to show it for "alias offer" results -- that is,
non-heuristic search engine results that have an alias and empty query.
These results tell the user that they can be used to search. */
#PopupAutoCompleteRichResult > richlistbox > richlistitem:not([selected]):not(:hover):not(.aliasOffer) > .ac-action[actiontype=searchengine],
#PopupAutoCompleteRichResult > richlistbox > richlistitem:not([selected]):not(:hover):not(.aliasOffer) > .ac-separator[actiontype=searchengine] {
display: none;
}
/* Hide the em-dash separator between the search query description (.ac-title)
and the "Search with Foo" description (.ac-url) if the search query is the
empty string. Also hide .ac-title itself because it has a margin-inline-end;
alternatively we would need to remove that margin in this case. */
#PopupAutoCompleteRichResult > richlistbox > richlistitem.emptySearchQuery > .ac-separator,
#PopupAutoCompleteRichResult > richlistbox > richlistitem.emptySearchQuery > .ac-title {
display: none;
}
#PopupAutoCompleteRichResult > richlistbox > richlistitem > .ac-site-icon {
margin-inline-start: 0;
}
/* For action items in a noactions popup, show the URL text and hide the action
text and type icon. */
#PopupAutoCompleteRichResult[noactions] > richlistbox > richlistitem.overridable-action > .ac-url {
display: -moz-box;
}
#PopupAutoCompleteRichResult[noactions] > richlistbox > richlistitem.overridable-action > .ac-action {
display: none;
}
#PopupAutoCompleteRichResult[noactions] > richlistbox > richlistitem.overridable-action > .ac-type-icon {
list-style-image: none;
}
#urlbar[actionoverride] > #urlbar-display-box,
#urlbar:not([actiontype="switchtab"]):not([actiontype="extension"]) > #urlbar-display-box,
#urlbar:not([actiontype="switchtab"]) > #urlbar-display-box > #switchtab,
@ -733,31 +667,6 @@ html|input.urlbar-input {
margin-inline-start: 0;
}
#PopupAutoCompleteRichResult {
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#urlbar-rich-result-popup");
}
#PopupAutoCompleteRichResult.showSearchSuggestionsNotification {
transition: height 100ms;
}
#PopupAutoCompleteRichResult > deck[anonid="search-suggestions-notification"] {
display: none;
transition: margin-top 100ms;
}
#PopupAutoCompleteRichResult.showSearchSuggestionsNotification > deck[anonid="search-suggestions-notification"] {
display: -moz-deck;
}
#PopupAutoCompleteRichResult > richlistbox {
transition: height 100ms;
}
#PopupAutoCompleteRichResult.showSearchSuggestionsNotification > richlistbox {
transition: none;
}
#urlbar[pageproxystate=invalid] > #page-action-buttons > .urlbar-page-action,
#identity-box.chromeUI ~ #page-action-buttons > .urlbar-page-action:not(#star-button-box),
.urlbar-history-dropmarker[usertyping],
@ -1076,7 +985,7 @@ toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > #downloads-
/* Give this menupopup an arrow panel styling */
#BMB_bookmarksPopup {
-moz-appearance: none;
appearance: none;
-moz-binding: url("chrome://browser/content/places/menu.xml#places-popup-arrow");
background: transparent;
border: none;
@ -1217,8 +1126,7 @@ toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > #downloads-
}
toolbarpaletteitem[dragover] {
border-left-color: transparent;
border-right-color: transparent;
border-inline-color: transparent;
}
#customization-palette-container {
@ -1278,71 +1186,6 @@ toolbarpaletteitem > toolbaritem {
display: none;
}
/* UI Tour */
@keyframes uitour-wobble {
from {
transform: rotate(0deg) translateX(3px) rotate(0deg);
}
50% {
transform: rotate(360deg) translateX(3px) rotate(-360deg);
}
to {
transform: rotate(720deg) translateX(0px) rotate(-720deg);
}
}
@keyframes uitour-zoom {
from {
transform: scale(0.8);
}
50% {
transform: scale(1.0);
}
to {
transform: scale(0.8);
}
}
@keyframes uitour-color {
from {
border-color: #5B9CD9;
}
50% {
border-color: #FF0000;
}
to {
border-color: #5B9CD9;
}
}
#UITourHighlightContainer,
#UITourHighlight {
pointer-events: none;
}
#UITourHighlight[active] {
animation-delay: 2s;
animation-fill-mode: forwards;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
#UITourHighlight[active="wobble"] {
animation-name: uitour-wobble;
animation-delay: 0s;
animation-duration: 1.5s;
animation-iteration-count: 1;
}
#UITourHighlight[active="zoom"] {
animation-name: uitour-zoom;
animation-duration: 1s;
}
#UITourHighlight[active="color"] {
animation-name: uitour-color;
animation-duration: 2s;
}
/* Combined context-menu items */
#context-navigation > .menuitem-iconic > .menu-iconic-text,
#context-navigation > .menuitem-iconic > .menu-accel-container {
@ -1362,7 +1205,7 @@ toolbarpaletteitem > toolbaritem {
}
.dragfeedback-tab {
-moz-appearance: none;
appearance: none;
opacity: 0.65;
-moz-window-shadow: none;
}

View File

@ -35,6 +35,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
LightweightThemeConsumer:
"resource://gre/modules/LightweightThemeConsumer.jsm",
Log: "resource://gre/modules/Log.jsm",
LoginHelper: "resource://gre/modules/LoginHelper.jsm",
LoginManagerParent: "resource://gre/modules/LoginManagerParent.jsm",
MigrationUtils: "resource:///modules/MigrationUtils.jsm",
NetUtil: "resource://gre/modules/NetUtil.jsm",
@ -68,7 +69,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
Translation: "resource:///modules/translation/Translation.jsm",
UITour: "resource:///modules/UITour.jsm",
UpdateUtils: "resource://gre/modules/UpdateUtils.jsm",
UrlbarInput: "resource:///modules/UrlbarInput.jsm",
UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
@ -182,7 +182,7 @@ XPCOMUtils.defineLazyScriptGetter(
);
XPCOMUtils.defineLazyScriptGetter(
this,
["setContextMenuContentData", "openContextMenu", "nsContextMenu"],
["openContextMenu", "nsContextMenu"],
"chrome://browser/content/nsContextMenu.js"
);
XPCOMUtils.defineLazyScriptGetter(
@ -290,60 +290,30 @@ XPCOMUtils.defineLazyGetter(this, "gURLBar", () => gURLBarHandler.urlbar);
/**
* Tracks the urlbar object, allowing to reinitiate it when necessary, e.g. on
* customization or when the quantumbar pref changes.
* customization.
*/
var gURLBarHandler = {
toggleQuantumBarAttribute() {
this.textbox = document.getElementById("urlbar");
this.textbox.setAttribute("quantumbar", this.quantumbar);
},
/**
* The urlbar binding or object.
*/
get urlbar() {
if (!this._urlbar) {
if (this.quantumbar) {
this._urlbar = new UrlbarInput({ textbox: this.textbox });
if (this._lastValue) {
this._urlbar.value = this._lastValue;
delete this._lastValue;
}
} else {
this._urlbar = this.textbox;
let textbox = document.getElementById("urlbar");
this._urlbar = new UrlbarInput({ textbox });
if (this._lastValue) {
this._urlbar.value = this._lastValue;
delete this._lastValue;
}
gBrowser.tabContainer.addEventListener("TabSelect", this._urlbar);
}
return this._urlbar;
},
/**
* Forwards to gURLBar.formatValue(), if the binding has been applied already.
* This is necessary until the Quantum Bar is not the default and we allow
* to dynamically switch between it and the legacy implementation, because the
* binding is only applied before the initial xul layout.
*/
formatValue() {
if (this.quantumbar) {
this.urlbar.formatValue();
} else if (typeof this.textbox.formatValue == "function") {
this.textbox.formatValue();
}
},
/**
* Invoked when the quantumbar pref changes.
*/
handlePrefChange() {
this._updateBinding();
this._reset();
},
/**
* Invoked by CustomizationHandler when a customization starts.
*/
customizeStart() {
if (this._urlbar && this._urlbar.constructor.name == "UrlbarInput") {
if (this._urlbar) {
this._urlbar.removeCopyCutController();
}
},
@ -355,44 +325,20 @@ var gURLBarHandler = {
this._reset();
},
/**
* Rebuilds the textbox binding by detaching the element when necessary.
*/
_updateBinding() {
let quantumbarApplied = this.textbox.getAttribute("quantumbar") == "true";
if (quantumbarApplied != this.quantumbar) {
let placeholder = document.createXULElement("toolbarpaletteitem");
let parent = this.textbox.parentNode;
parent.replaceChild(placeholder, this.textbox);
this.textbox.setAttribute("quantumbar", this.quantumbar);
parent.replaceChild(this.textbox, placeholder);
}
},
/**
* Used to reset the gURLBar value.
*/
_reset() {
if (this._urlbar) {
gBrowser.tabContainer.removeEventListener("TabSelect", this._urlbar);
if (this._urlbar.constructor.name == "UrlbarInput") {
this._lastValue = this._urlbar.value;
this._urlbar.uninit();
}
this._lastValue = this._urlbar.value;
this._urlbar.uninit();
delete this._urlbar;
gURLBar = this.urlbar;
}
},
};
XPCOMUtils.defineLazyPreferenceGetter(
gURLBarHandler,
"quantumbar",
"browser.urlbar.quantumbar",
false,
gURLBarHandler.handlePrefChange.bind(gURLBarHandler)
);
XPCOMUtils.defineLazyGetter(this, "ReferrerInfo", () =>
Components.Constructor(
"@mozilla.org/referrer-info;1",
@ -1666,8 +1612,9 @@ var gBrowserInit = {
},
onBeforeInitialXULLayout() {
// Dynamically switch on-off the Quantum Bar based on prefs.
gURLBarHandler.toggleQuantumBarAttribute();
// Turn on QuantumBar. This can be removed once the quantumbar attribute is gone.
let urlbar = document.getElementById("urlbar");
urlbar.setAttribute("quantumbar", true);
// Set a sane starting width/height for all resolutions on new profiles.
if (Services.prefs.getBoolPref("privacy.resistFingerprinting")) {
@ -2807,29 +2754,17 @@ function loadOneOrMoreURIs(aURIString, aTriggeringPrincipal, aCsp) {
/**
* Focuses the location bar input field and selects its contents.
*
* @param [optional] userInitiatedFocus
* Whether this focus is caused by an user interaction whose intention
* was to use the location bar. For example, using a shortcut to go to
* the location bar, or a contextual menu to search from it.
* The default is false and should be used in all those cases where the
* code focuses the location bar but that's not the primary user
* intention, like when opening a new tab.
*/
function focusAndSelectUrlBar(userInitiatedFocus = false) {
function focusAndSelectUrlBar() {
// In customize mode, the url bar is disabled. If a new tab is opened or the
// user switches to a different tab, this function gets called before we've
// finished leaving customize mode, and the url bar will still be disabled.
// We can't focus it when it's disabled, so we need to re-run ourselves when
// we've finished leaving customize mode.
if (CustomizationHandler.isExitingCustomizeMode) {
gNavToolbox.addEventListener(
"aftercustomization",
function() {
focusAndSelectUrlBar(userInitiatedFocus);
},
{ once: true }
);
if (CustomizationHandler.isCustomizing()) {
gNavToolbox.addEventListener("aftercustomization", focusAndSelectUrlBar, {
once: true,
});
return;
}
@ -2837,14 +2772,15 @@ function focusAndSelectUrlBar(userInitiatedFocus = false) {
FullScreen.showNavToolbox();
}
gURLBar.userInitiatedFocus = userInitiatedFocus;
gURLBar.select();
gURLBar.userInitiatedFocus = false;
}
function openLocation() {
if (window.location.href == AppConstants.BROWSER_CHROME_URL) {
focusAndSelectUrlBar(true);
focusAndSelectUrlBar();
if (gURLBar.openViewOnFocus && !gURLBar.view.isOpen) {
gURLBar.startQuery();
}
return;
}
@ -3090,13 +3026,8 @@ function readFromClipboard() {
/**
* Open the View Source dialog.
*
* @param aArgsOrDocument
* Either an object or a Document. Passing a Document is deprecated,
* and is not supported with e10s. This function will throw if
* aArgsOrDocument is a CPOW.
*
* If aArgsOrDocument is an object, that object can take the
* following properties:
* @param args
* An object with the following properties:
*
* URL (required):
* A string URL for the page we'd like to view the source of.
@ -3111,27 +3042,7 @@ function readFromClipboard() {
* lineNumber (optional):
* The line number to focus on once the source is loaded.
*/
async function BrowserViewSourceOfDocument(aArgsOrDocument) {
let args;
if (aArgsOrDocument instanceof Document) {
let doc = aArgsOrDocument;
// Deprecated API - callers should pass args object instead.
if (Cu.isCrossProcessWrapper(doc)) {
throw new Error(
"BrowserViewSourceOfDocument cannot accept a CPOW as a document."
);
}
let win = doc.defaultView;
let browser = win.docShell.chromeEventHandler;
let outerWindowID = win.windowUtils.outerWindowID;
let URL = browser.currentURI.spec;
args = { browser, outerWindowID, URL };
} else {
args = aArgsOrDocument;
}
async function BrowserViewSourceOfDocument(args) {
// Check if external view source is enabled. If so, try it. If it fails,
// fallback to internal view source.
if (Services.prefs.getBoolPref("view_source.editor.external")) {
@ -4089,9 +4000,7 @@ function getDetailedCertErrorInfo(location, securityInfo) {
hasHPKP,
]);
certErrorDetails +=
"\r\n\r\n" +
gNavigatorBundle.getString("certErrorDetailsCertChain.label");
certErrorDetails;
return certErrorDetails;
}
@ -5975,7 +5884,7 @@ var XULBrowserWindow = {
// Make sure the "https" part of the URL is striked out or not,
// depending on the current mixed active content blocking state.
gURLBarHandler.formatValue();
gURLBar.formatValue();
try {
uri = Services.io.createExposableURI(uri);
@ -8917,58 +8826,39 @@ var gPrivateBrowsingUI = {
// temporary fix until bug 463607 is fixed
document.getElementById("Tools:Sanitize").setAttribute("disabled", "true");
if (window.location.href == AppConstants.BROWSER_CHROME_URL) {
// Adjust the window's title
let docElement = document.documentElement;
if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
docElement.setAttribute(
"title",
docElement.getAttribute("title_privatebrowsing")
);
docElement.setAttribute(
"titlemodifier",
docElement.getAttribute("titlemodifier_privatebrowsing")
);
}
if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
return;
}
// Adjust the window's title
let docElement = document.documentElement;
if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
docElement.setAttribute(
"privatebrowsingmode",
PrivateBrowsingUtils.permanentPrivateBrowsing
? "permanent"
: "temporary"
"title",
docElement.getAttribute("title_privatebrowsing")
);
gBrowser.updateTitlebar();
docElement.setAttribute(
"titlemodifier",
docElement.getAttribute("titlemodifier_privatebrowsing")
);
}
docElement.setAttribute(
"privatebrowsingmode",
PrivateBrowsingUtils.permanentPrivateBrowsing ? "permanent" : "temporary"
);
gBrowser.updateTitlebar();
if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
// Adjust the New Window menu entries
[
{ normal: "menu_newNavigator", private: "menu_newPrivateWindow" },
].forEach(function(menu) {
let newWindow = document.getElementById(menu.normal);
let newPrivateWindow = document.getElementById(menu.private);
if (newWindow && newPrivateWindow) {
newPrivateWindow.hidden = true;
newWindow.label = newPrivateWindow.label;
newWindow.accessKey = newPrivateWindow.accessKey;
newWindow.command = newPrivateWindow.command;
}
});
if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
// Adjust the New Window menu entries
let newWindow = document.getElementById("menu_newNavigator");
let newPrivateWindow = document.getElementById("menu_newPrivateWindow");
if (newWindow && newPrivateWindow) {
newPrivateWindow.hidden = true;
newWindow.label = newPrivateWindow.label;
newWindow.accessKey = newPrivateWindow.accessKey;
newWindow.command = newPrivateWindow.command;
}
}
let urlBarSearchParam =
gURLBar.getAttribute("autocompletesearchparam") || "";
if (
!PrivateBrowsingUtils.permanentPrivateBrowsing &&
!urlBarSearchParam.includes("disable-private-actions")
) {
// Disable switch to tab autocompletion for private windows.
// We leave it enabled for permanent private browsing mode though.
urlBarSearchParam += " disable-private-actions";
}
if (!urlBarSearchParam.includes("private-window")) {
urlBarSearchParam += " private-window";
}
gURLBar.setAttribute("autocompletesearchparam", urlBarSearchParam);
},
};

View File

@ -221,16 +221,6 @@
noautofocus="true"
hidden="true" />
<!-- for url bar autocomplete -->
<panel type="autocomplete-richlistbox"
id="PopupAutoCompleteRichResult"
role="group"
noautofocus="true"
hidden="true"
flip="none"
level="parent"
overflowpadding="15" />
<!-- for date/time picker. consumeoutsideclicks is set to never, so that
clicks on the anchored input box are never consumed. -->
<panel id="DateTimePickerPanel"
@ -313,42 +303,6 @@
</hbox>
</panel>
<!-- UI tour experience -->
<panel id="UITourTooltip"
type="arrow"
hidden="true"
noautofocus="true"
noautohide="true"
align="start"
orient="vertical"
role="alert">
<vbox>
<hbox id="UITourTooltipBody">
<image id="UITourTooltipIcon"/>
<vbox flex="1">
<hbox id="UITourTooltipTitleContainer">
<label id="UITourTooltipTitle" flex="1"/>
<toolbarbutton id="UITourTooltipClose" class="close-icon"
tooltiptext="&uiTour.infoPanel.close;"/>
</hbox>
<description id="UITourTooltipDescription" flex="1"/>
</vbox>
</hbox>
<hbox id="UITourTooltipButtons" flex="1" align="center"/>
</vbox>
</panel>
<!-- type="default" forces frames to be created so that the panel's size can be determined -->
<panel id="UITourHighlightContainer"
type="default"
hidden="true"
noautofocus="true"
noautohide="true"
flip="none"
consumeoutsideclicks="false"
mousethrough="always">
<box id="UITourHighlight"></box>
</panel>
<panel id="sidebarMenu-popup"
class="cui-widget-panel"
role="group"

View File

@ -19,7 +19,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
ContentMetaHandler: "resource:///modules/ContentMetaHandler.jsm",
LoginFormFactory: "resource://gre/modules/LoginFormFactory.jsm",
InsecurePasswordUtils: "resource://gre/modules/InsecurePasswordUtils.jsm",
ContextMenuChild: "resource:///actors/ContextMenuChild.jsm",
});
XPCOMUtils.defineLazyGetter(this, "LoginManagerContent", () => {
@ -32,11 +31,6 @@ XPCOMUtils.defineLazyGetter(this, "LoginManagerContent", () => {
// NOTE: Much of this logic is duplicated in BrowserCLH.js for Android.
addMessageListener("PasswordManager:fillForm", function(message) {
// intercept if ContextMenu.jsm had sent a plain object for remote targets
message.objects.inputElement = ContextMenuChild.getTarget(
global,
message,
"inputElement"
);
LoginManagerContent.receiveMessage(message, content);
});
addMessageListener("PasswordManager:fillGeneratedPassword", function(message) {

View File

@ -8,17 +8,12 @@
border-top: none;
box-shadow: 0 5px 10px hsla(0, 0%, 0%, .1);
position: absolute;
left: 0;
inset-inline-start: 0;
z-index: 1001;
-moz-user-select: none;
cursor: default;
}
.contentSearchSuggestionTable:dir(rtl) {
left: auto;
right: 0;
}
.contentSearchSuggestionsList {
border-bottom: 1px solid hsl(0, 0%, 92%);
width: 100%;
@ -106,7 +101,7 @@
}
.contentSearchOneOffItem {
-moz-appearance: none;
appearance: none;
height: 32px;
margin: 0;
padding: 0;
@ -118,7 +113,7 @@
}
.contentSearchOneOffItem:dir(rtl) {
background-position: left center;
background-position-x: left;
}
.contentSearchOneOffItem > img {

File diff suppressed because it is too large Load Diff

View File

@ -17,3 +17,55 @@
/* This following entry can be removed when Bug 522850 is fixed. */
min-width: 1px;
}
table {
border-spacing: 0;
}
.tableSeparator {
height: 6px;
}
th, td {
padding: 0;
}
th {
font: inherit;
text-align: start;
padding-inline-end: .5em;
}
/*
Make the first column shrink to its min-content, except for #securityTable
which has full sentences in its first column.
*/
table:not(#securityTable) th {
width: 0;
}
th > label,
td > input,
.table-split-column {
width: 100%;
margin-block: 1px 4px;
}
.table-split-column {
display: flex;
align-items: center;
}
.table-split-column > label,
.table-split-column > input {
flex-grow: 1;
}
.table-split-column > button {
flex-shrink: 0;
}
#hostText {
-moz-box-flex: 1;
margin-top: 1px; /* same margin as adjacent label */
}

View File

@ -149,7 +149,10 @@ pageInfoTreeView.prototype = {
return 0;
},
getImageSrc(row, column) {},
getCellValue(row, column) {},
getCellValue(row, column) {
let col = column != null ? column : this.copycol;
return row < 0 || col < 0 ? "" : this.data[row][col] || "";
},
toggleOpenState(index) {},
cycleHeader(col) {},
selectionChanged() {},
@ -269,6 +272,19 @@ const nsIPermissionManager = Ci.nsIPermissionManager;
const nsICertificateDialogs = Ci.nsICertificateDialogs;
const CERTIFICATEDIALOGS_CONTRACTID = "@mozilla.org/nsCertificateDialogs;1";
// clipboard helper
function getClipboardHelper() {
try {
return Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
Ci.nsIClipboardHelper
);
} catch (e) {
// do nothing, later code will handle the error
return null;
}
}
const gClipboardHelper = getClipboardHelper();
// namespaces, don't need all of these yet...
const XLinkNS = "http://www.w3.org/1999/xlink";
const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
@ -1150,11 +1166,9 @@ function getContentTypeFromHeaders(cacheEntryDescriptor) {
function setItemValue(id, value) {
var item = document.getElementById(id);
item.closest("tr").hidden = !value;
if (value) {
item.parentNode.collapsed = false;
item.value = value;
} else {
item.parentNode.collapsed = true;
}
}
@ -1175,6 +1189,37 @@ function formatDate(datestr, unknown) {
return dateTimeFormatter.format(date);
}
function doCopy() {
if (!gClipboardHelper) {
return;
}
var elem = document.commandDispatcher.focusedElement;
if (elem && elem.localName == "tree") {
var view = elem.view;
var selection = view.selection;
var text = [],
tmp = "";
var min = {},
max = {};
var count = selection.getRangeCount();
for (var i = 0; i < count; i++) {
selection.getRangeAt(i, min, max);
for (var row = min.value; row <= max.value; row++) {
tmp = view.getCellValue(row, null);
if (tmp) {
text.push(tmp);
}
}
}
gClipboardHelper.copyString(text.join("\n"));
}
}
function doSelectAllMedia() {
var tree = document.getElementById("imagetree");

View File

@ -15,6 +15,7 @@
<window id="main-window"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
data-l10n-id="page-info-window"
data-l10n-attrs="style"
windowtype="Browser:page-info"
@ -24,20 +25,21 @@
screenX="10" screenY="10"
persist="screenX screenY width height sizemode">
#ifdef XP_MACOSX
#include ../macWindow.inc.xul
#endif
<linkset>
<html:link rel="localization" href="browser/pageInfo.ftl"/>
</linkset>
#ifdef XP_MACOSX
#include ../macWindow.inc.xul
#else
<script src="chrome://global/content/globalOverlay.js"/>
<script src="chrome://global/content/editMenuOverlay.js"/>
<script src="chrome://browser/content/utilityOverlay.js"/>
#endif
<script src="chrome://global/content/contentAreaUtils.js"/>
<script src="chrome://global/content/treeUtils.js"/>
<script src="chrome://browser/content/pageinfo/pageInfo.js"/>
<script src="chrome://browser/content/pageinfo/permissions.js"/>
<script src="chrome://browser/content/pageinfo/security.js"/>
<script src="chrome://browser/content/utilityOverlay.js"/>
<stringbundleset id="pageinfobundleset">
<stringbundle id="pkiBundle" src="chrome://pippki/locale/pippki.properties"/>
@ -47,7 +49,8 @@
<commandset id="pageInfoCommandSet">
<command id="cmd_close" oncommand="window.close();"/>
<command id="cmd_help" oncommand="doHelpButton();"/>
<command id="cmd_selectall" oncommand="doSelectAll();"/>
<command id="cmd_copy_tree" oncommand="doCopy();"/>
<command id="cmd_selectall_tree" oncommand="doSelectAll();"/>
</commandset>
<keyset id="pageInfoKeySet">
@ -58,12 +61,14 @@
#else
<key keycode="VK_F1" command="cmd_help"/>
#endif
<key data-l10n-id="select-all" data-l10n-attrs="key" modifiers="accel" command="cmd_selectall"/>
<key data-l10n-id="select-all" data-l10n-attrs="key" modifiers="alt" command="cmd_selectall"/>
<key data-l10n-id="copy" data-l10n-attrs="key" modifiers="accel" command="cmd_copy_tree"/>
<key data-l10n-id="select-all" data-l10n-attrs="key" modifiers="accel" command="cmd_selectall_tree"/>
<key data-l10n-id="select-all" data-l10n-attrs="key" modifiers="alt" command="cmd_selectall_tree"/>
</keyset>
<menupopup id="picontext">
<menuitem id="menu_selectall" data-l10n-id="menu-select-all" command="cmd_selectall"/>
<menuitem id="menu_selectall" data-l10n-id="menu-select-all" command="cmd_selectall_tree"/>
<menuitem id="menu_copy" data-l10n-id="menu-copy" command="cmd_copy_tree"/>
</menupopup>
<vbox id="topBar">
@ -82,61 +87,74 @@
<deck id="mainDeck" flex="1">
<!-- General page information -->
<vbox id="generalPanel">
<grid id="generalGrid">
<columns>
<column/>
<column class="gridSeparator"/>
<column flex="1"/>
</columns>
<rows id="generalRows">
<row id="generalTitle">
<label control="titletext" data-l10n-id="general-title"/>
<separator/>
<textbox readonly="true" id="titletext"/>
</row>
<row id="generalURLRow">
<label control="urltext" data-l10n-id="general-url"/>
<separator/>
<textbox readonly="true" id="urltext"/>
</row>
<row id="generalSeparatorRow1">
<separator class="thin"/>
</row>
<row id="generalTypeRow">
<label control="typetext" data-l10n-id="general-type"/>
<separator/>
<textbox readonly="true" id="typetext"/>
</row>
<row id="generalModeRow">
<label control="modetext" data-l10n-id="general-mode"/>
<separator/>
<textbox readonly="true" crop="end" id="modetext"/>
</row>
<row id="generalEncodingRow">
<label control="encodingtext" data-l10n-id="general-encoding"/>
<separator/>
<textbox readonly="true" id="encodingtext"/>
</row>
<row id="generalSizeRow">
<label control="sizetext" data-l10n-id="general-size"/>
<separator/>
<textbox readonly="true" id="sizetext"/>
</row>
<row id="generalReferrerRow">
<label control="refertext" data-l10n-id="general-referrer"/>
<separator/>
<textbox readonly="true" id="refertext"/>
</row>
<row id="generalSeparatorRow2">
<separator class="thin"/>
</row>
<row id="generalModifiedRow">
<label control="modifiedtext" data-l10n-id="general-modified"/>
<separator/>
<textbox readonly="true" id="modifiedtext"/>
</row>
</rows>
</grid>
<table id="generalTable" xmlns="http://www.w3.org/1999/xhtml">
<tr id="generalTitle">
<th>
<xul:label control="titletext" data-l10n-id="general-title"/>
</th>
<td>
<input readonly="readonly" id="titletext" data-l10n-attrs="value"/>
</td>
</tr>
<tr id="generalURLRow">
<th>
<xul:label control="urltext" data-l10n-id="general-url"/>
</th>
<td>
<input readonly="readonly" id="urltext"/>
</td>
</tr>
<tr class="tableSeparator"/>
<tr id="generalTypeRow">
<th>
<xul:label control="typetext" data-l10n-id="general-type"/>
</th>
<td>
<input readonly="readonly" id="typetext"/>
</td>
</tr>
<tr id="generalModeRow">
<th>
<xul:label control="modetext" data-l10n-id="general-mode"/>
</th>
<td>
<input readonly="readonly" id="modetext" data-l10n-attrs="value"/>
</td>
</tr>
<tr id="generalEncodingRow">
<th>
<xul:label control="encodingtext" data-l10n-id="general-encoding"/>
</th>
<td>
<input readonly="readonly" id="encodingtext"/>
</td>
</tr>
<tr id="generalSizeRow">
<th>
<xul:label control="sizetext" data-l10n-id="general-size"/>
</th>
<td>
<input readonly="readonly" id="sizetext" data-l10n-attrs="value"/>
</td>
</tr>
<tr id="generalReferrerRow">
<th>
<xul:label control="refertext" data-l10n-id="general-referrer"/>
</th>
<td>
<input readonly="readonly" id="refertext"/>
</td>
</tr>
<tr class="tableSeparator"/>
<tr id="generalModifiedRow">
<th>
<xul:label control="modifiedtext" data-l10n-id="general-modified"/>
</th>
<td>
<input readonly="readonly" id="modifiedtext"/>
</td>
</tr>
</table>
<separator class="thin"/>
<vbox id="metaTags" flex="1">
<label control="metatree" id="metaTagsCaption" class="header"/>
@ -187,45 +205,56 @@
</tree>
<splitter orient="vertical" id="mediaSplitter"/>
<vbox flex="1" id="mediaPreviewBox" collapsed="true">
<grid id="mediaGrid">
<columns>
<column id="mediaLabelColumn"/>
<column class="gridSeparator"/>
<column flex="1"/>
</columns>
<rows id="mediaRows">
<row id="mediaLocationRow">
<label control="imageurltext" data-l10n-id="media-location"/>
<separator/>
<textbox readonly="true" id="imageurltext"/>
</row>
<row id="mediaTypeRow">
<label control="imagetypetext" data-l10n-id="general-type"/>
<separator/>
<textbox id="imagetypetext"/>
</row>
<row id="mediaSizeRow">
<label control="imagesizetext" data-l10n-id="general-size"/>
<separator/>
<textbox readonly="true" id="imagesizetext"/>
</row>
<row id="mediaDimensionRow">
<label control="imagedimensiontext" data-l10n-id="media-dimension"/>
<separator/>
<textbox readonly="true" id="imagedimensiontext"/>
</row>
<row id="mediaTextRow">
<label control="imagetext" data-l10n-id="media-text"/>
<separator/>
<textbox readonly="true" id="imagetext"/>
</row>
<row id="mediaLongdescRow">
<label control="imagelongdesctext" data-l10n-id="media-long-desc"/>
<separator/>
<textbox readonly="true" id="imagelongdesctext"/>
</row>
</rows>
</grid>
<table id="mediaTable" xmlns="http://www.w3.org/1999/xhtml">
<tr id="mediaLocationRow">
<th>
<xul:label control="imageurltext" data-l10n-id="media-location"/>
</th>
<td>
<input readonly="readonly" id="imageurltext"/>
</td>
</tr>
<tr id="mediaTypeRow">
<th>
<xul:label control="imagetypetext" data-l10n-id="general-type"/>
</th>
<td>
<input id="imagetypetext" data-l10n-attrs="value"/>
</td>
</tr>
<tr id="mediaSizeRow">
<th>
<xul:label control="imagesizetext" data-l10n-id="general-size"/>
</th>
<td>
<input readonly="readonly" id="imagesizetext" data-l10n-attrs="value"/>
</td>
</tr>
<tr id="mediaDimensionRow">
<th>
<xul:label control="imagedimensiontext" data-l10n-id="media-dimension"/>
</th>
<td>
<input readonly="readonly" id="imagedimensiontext" data-l10n-attrs="value"/>
</td>
</tr>
<tr id="mediaTextRow">
<th>
<xul:label control="imagetext" data-l10n-id="media-text"/>
</th>
<td>
<input readonly="readonly" id="imagetext"/>
</td>
</tr>
<tr id="mediaLongdescRow">
<th>
<xul:label control="imagelongdesctext" data-l10n-id="media-long-desc"/>
</th>
<td>
<input readonly="readonly" id="imagelongdesctext"/>
</td>
</tr>
</table>
<hbox id="imageSaveBox" align="end">
<vbox id="blockImageBox">
<checkbox id="blockImage" hidden="true" oncommand="onBlockImage()"
@ -264,8 +293,7 @@
<vbox id="permPanel">
<hbox id="permHostBox">
<label data-l10n-id="permissions-for" control="hostText" />
<textbox id="hostText" class="header" readonly="true"
crop="end" flex="1"/>
<html:input id="hostText" class="header" readonly="readonly"/>
</hbox>
<vbox id="permList" flex="1"/>
@ -279,88 +307,102 @@
<!-- Identity Section -->
<groupbox>
<label class="header" data-l10n-id="security-view-identity"/>
<grid>
<columns>
<column/>
<column flex="1"/>
</columns>
<rows>
<!-- Domain -->
<row>
<label data-l10n-id="security-view-identity-domain"
control="security-identity-domain-value"/>
<textbox id="security-identity-domain-value" readonly="true"/>
</row>
<!-- Owner -->
<row>
<label id="security-identity-owner-label"
class="fieldLabel"
data-l10n-id="security-view-identity-owner"
control="security-identity-owner-value"/>
<textbox id="security-identity-owner-value" readonly="true"/>
</row>
<!-- Verifier -->
<row>
<label data-l10n-id="security-view-identity-verifier"
control="security-identity-verifier-value"/>
<hbox align="center">
<textbox id="security-identity-verifier-value" readonly="true"
flex="1"/>
<button id="security-view-cert" data-l10n-id="security-view"
oncommand="security.viewCert();"/>
</hbox>
</row>
<!-- Certificate Validity -->
<row id="security-identity-validity-row">
<label data-l10n-id="security-view-identity-validity"
control="security-identity-validity-value"/>
<textbox id="security-identity-validity-value" readonly="true"/>
</row>
</rows>
</grid>
<table xmlns="http://www.w3.org/1999/xhtml">
<!-- Domain -->
<tr>
<th>
<xul:label data-l10n-id="security-view-identity-domain"
control="security-identity-domain-value"/>
</th>
<td>
<input id="security-identity-domain-value" readonly="readonly"/>
</td>
</tr>
<!-- Owner -->
<tr>
<th>
<xul:label id="security-identity-owner-label"
class="fieldLabel"
data-l10n-id="security-view-identity-owner"
control="security-identity-owner-value"/>
</th>
<td>
<input id="security-identity-owner-value" readonly="readonly" data-l10n-attrs="value"/>
</td>
</tr>
<!-- Verifier -->
<tr>
<th>
<xul:label data-l10n-id="security-view-identity-verifier"
control="security-identity-verifier-value"/>
</th>
<td>
<div class="table-split-column">
<input id="security-identity-verifier-value" readonly="readonly"
data-l10n-attrs="value"/>
<xul:button id="security-view-cert" data-l10n-id="security-view"
oncommand="security.viewCert();"/>
</div>
</td>
</tr>
<!-- Certificate Validity -->
<tr id="security-identity-validity-row">
<th>
<xul:label data-l10n-id="security-view-identity-validity"
control="security-identity-validity-value"/>
</th>
<td>
<input id="security-identity-validity-value" readonly="readonly"/>
</td>
</tr>
</table>
</groupbox>
<!-- Privacy & History section -->
<groupbox>
<label class="header" data-l10n-id="security-view-privacy"/>
<grid>
<columns>
<column flex="1"/>
<column flex="1"/>
</columns>
<rows>
<!-- History -->
<row>
<label control="security-privacy-history-value" data-l10n-id="security-view-privacy-history-value"/>
<label id="security-privacy-history-value"
<table id="securityTable" xmlns="http://www.w3.org/1999/xhtml">
<!-- History -->
<tr>
<th>
<xul:label control="security-privacy-history-value" data-l10n-id="security-view-privacy-history-value"/>
</th>
<td>
<xul:label id="security-privacy-history-value"
data-l10n-id="security-view-unknown"/>
</row>
<!-- Site Data & Cookies -->
<row id="security-privacy-sitedata-row">
<label control="security-privacy-sitedata-value" data-l10n-id="security-view-privacy-sitedata-value"/>
<hbox id="security-privacy-sitedata-box" align="center">
<label id="security-privacy-sitedata-value" data-l10n-id="security-view-unknown"
flex="1"/>
<button id="security-clear-sitedata"
disabled="true"
data-l10n-id="security-view-privacy-clearsitedata"
oncommand="security.clearSiteData();"/>
</hbox>
</row>
<!-- Passwords -->
<row>
<label control="security-privacy-passwords-value" data-l10n-id="security-view-privacy-passwords-value"/>
<hbox id="security-privacy-passwords-box" align="center">
<label id="security-privacy-passwords-value"
data-l10n-id="security-view-unknown"
flex="1"/>
<button id="security-view-password"
data-l10n-id="security-view-privacy-viewpasswords"
oncommand="security.viewPasswords();"/>
</hbox>
</row>
</rows>
</grid>
</td>
</tr>
<!-- Site Data & Cookies -->
<tr id="security-privacy-sitedata-row">
<th>
<xul:label control="security-privacy-sitedata-value" data-l10n-id="security-view-privacy-sitedata-value"/>
</th>
<td>
<div class="table-split-column">
<xul:label id="security-privacy-sitedata-value" data-l10n-id="security-view-unknown"/>
<xul:button id="security-clear-sitedata"
disabled="true"
data-l10n-id="security-view-privacy-clearsitedata"
oncommand="security.clearSiteData();"/>
</div>
</td>
</tr>
<!-- Passwords -->
<tr>
<th>
<xul:label control="security-privacy-passwords-value" data-l10n-id="security-view-privacy-passwords-value"/>
</th>
<td>
<div class="table-split-column">
<xul:label id="security-privacy-passwords-value"
data-l10n-id="security-view-unknown"/>
<xul:button id="security-view-password"
data-l10n-id="security-view-privacy-viewpasswords"
oncommand="security.viewPasswords();"/>
</div>
</td>
</tr>
</table>
</groupbox>
<!-- Technical Details section -->

View File

@ -247,7 +247,7 @@ function securityOnLoad(uri, windowInfo) {
// treating these certs as domain-validated only.
document.l10n.setAttributes(
document.getElementById("security-identity-owner-value"),
"security-no-owner"
"page-info-security-no-owner"
);
setText(
"security-identity-verifier-value",
@ -258,11 +258,11 @@ function securityOnLoad(uri, windowInfo) {
// We don't have valid identity credentials.
document.l10n.setAttributes(
document.getElementById("security-identity-owner-value"),
"security-no-owner"
"page-info-security-no-owner"
);
document.l10n.setAttributes(
document.getElementById("security-identity-verifier-value"),
"not-set-verified-by"
"page-info-not-specified"
);
}
@ -368,7 +368,7 @@ function setText(id, value) {
if (!element) {
return;
}
if (element.localName == "textbox" || element.localName == "label") {
if (element.localName == "input" || element.localName == "label") {
element.value = value;
} else {
element.textContent = value;

View File

@ -52,10 +52,12 @@
}
let messageManager = window.getGroupMessageManager("browsers");
window.messageManager.addMessageListener("contextmenu", this);
if (gMultiProcessBrowser) {
messageManager.addMessageListener("DOMTitleChanged", this);
messageManager.addMessageListener("DOMWindowClose", this);
window.messageManager.addMessageListener("contextmenu", this);
messageManager.addMessageListener("DOMWindowClose", this);
messageManager.addMessageListener("Browser:Init", this);
} else {
this._outerWindowIDBrowserMap.set(
@ -1338,9 +1340,7 @@
// if the tab is a blank one.
if (newBrowser._urlbarFocused && gURLBar) {
// Explicitly close the popup if the URL bar retains focus
if (!gURLBar.openViewOnFocus) {
gURLBar.closePopup();
}
gURLBar.view.close();
// If the user happened to type into the URL bar for this browser
// by the time we got here, focusing will cause the text to be

View File

@ -57,7 +57,7 @@
frame1.docShell.chromeEventHandler.removeAttribute("crashedPageTitle");
SimpleTest.is(frame1.contentDocument.documentURI,
"about:tabcrashed?e=tabcrashed&u=http%3A//www.example.com/1&c=UTF-8&f=regular&d=pageTitle",
"about:tabcrashed?e=tabcrashed&u=http%3A//www.example.com/1&c=UTF-8&d=pageTitle",
"Correct about:tabcrashed displayed for page with title.");
errorPageReady = waitForErrorPage(frame2);
@ -66,7 +66,7 @@
await errorPageReady;
SimpleTest.is(frame2.contentDocument.documentURI,
"about:tabcrashed?e=tabcrashed&u=http%3A//www.example.com/2&c=UTF-8&f=regular&d=%20",
"about:tabcrashed?e=tabcrashed&u=http%3A//www.example.com/2&c=UTF-8&d=%20",
"Correct about:tabcrashed displayed for page with no title.");
SimpleTest.finish();

View File

@ -55,7 +55,7 @@
frame1.docShell.chromeEventHandler.removeAttribute("crashedPageTitle");
SimpleTest.is(frame1.contentDocument.documentURI,
"about:restartrequired?e=restartrequired&u=http%3A//www.example.com/1&c=UTF-8&f=regular&d=%20",
"about:restartrequired?e=restartrequired&u=http%3A//www.example.com/1&c=UTF-8&d=%20",
"Correct about:restartrequired displayed for page with title.");
errorPageReady = waitForErrorPage(frame2);
@ -64,7 +64,7 @@
await errorPageReady;
SimpleTest.is(frame2.contentDocument.documentURI,
"about:restartrequired?e=restartrequired&u=http%3A//www.example.com/2&c=UTF-8&f=regular&d=%20",
"about:restartrequired?e=restartrequired&u=http%3A//www.example.com/2&c=UTF-8&d=%20",
"Correct about:restartrequired displayed for page with no title.");
SimpleTest.finish();

View File

@ -182,6 +182,15 @@ const SELECT_INHERITED_COLORS_ON_OPTIONS_DONT_GET_UNIQUE_RULES_IF_RULE_SET_ON_SE
</select></body></html>
`;
const SELECT_FONT_INHERITS_TO_OPTION = `
<html><head><style>
select { font-family: monospace }
</style></head><body><select id='one'>
<option>One</option>
<option style="font-family: sans-serif">Two</option>
</select></body></html>
`;
function getSystemColor(color) {
// Need to convert system color to RGB color.
let textarea = document.createElementNS(
@ -246,7 +255,7 @@ function testOptionColors(index, item, menulist) {
}
}
async function testSelectColors(select, itemCount, options) {
async function openSelectPopup(select) {
const pageUrl = "data:text/html," + escape(select);
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
@ -263,7 +272,11 @@ async function testSelectColors(select, itemCount, options) {
gBrowser.selectedBrowser
);
await popupShownPromise;
return { tab, menulist, selectPopup };
}
async function testSelectColors(select, itemCount, options) {
let { tab, menulist, selectPopup } = await openSelectPopup(select);
if (options.waitForComputedStyle) {
let property = options.waitForComputedStyle.property;
let value = options.waitForComputedStyle.value;
@ -624,3 +637,29 @@ add_task(
BrowserTestUtils.removeTab(gBrowser.selectedTab);
}
);
add_task(async function test_select_font_inherits_to_option() {
let { tab, menulist, selectPopup } = await openSelectPopup(
SELECT_FONT_INHERITS_TO_OPTION
);
let popupFont = getComputedStyle(selectPopup).fontFamily;
let items = menulist.querySelectorAll("menuitem");
is(items.length, 2, "Should have two options");
let firstItemFont = getComputedStyle(items[0]).fontFamily;
let secondItemFont = getComputedStyle(items[1]).fontFamily;
is(
popupFont,
firstItemFont,
"First menuitem's font should be inherited from the select"
);
isnot(
popupFont,
secondItemFont,
"Second menuitem's font should be the author specified one"
);
await hideSelectPopup(selectPopup, "escape");
BrowserTestUtils.removeTab(tab);
});

View File

@ -69,7 +69,6 @@ support-files =
[browser_blob-channelname.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_bug321000.js]
tags = clipboard
skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_bug356571.js]
@ -130,7 +129,6 @@ skip-if = true # Bug 1478159
[browser_bug533232.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_bug537013.js]
tags = clipboard
skip-if = true # bug 1393813
# skip-if = e10s # Bug 1134458 - Find bar doesn't work correctly in a detached tab
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
@ -145,10 +143,8 @@ skip-if = true # bug 1393813
[browser_bug565575.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_bug567306.js]
tags = clipboard
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_bug1261299.js]
tags = clipboard
skip-if = toolkit != "cocoa" # Because of tests for supporting Service Menu of macOS, bug 1261299
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_bug1297539.js]
@ -205,9 +201,6 @@ skip-if = os == "win" && debug && e10s || (verify && debug && (os == 'linux')) #
[browser_bug734076.js]
skip-if = (verify && debug && (os == 'linux'))
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_bug735471.js]
uses-unsafe-cpows = true
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_bug749738.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_bug763468_perwindowpb.js]
@ -230,8 +223,6 @@ skip-if = os == 'win' || (verify && debug && (os == 'linux'))
[browser_accesskeys.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_clipboard.js]
uses-unsafe-cpows = true
tags = clipboard
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_clipboard_pastefile.js]
skip-if = true # Disabled due to the clipboard not supporting real file types yet (bug 1288773)
@ -283,7 +274,6 @@ skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (b
[browser_menuButtonFitts.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_middleMouse_noJSPaste.js]
tags = clipboard
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_minimize.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
@ -421,7 +411,6 @@ skip-if = debug # Bug 1444565, Bug 1457887
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_blockHPKP.js]
skip-if = verify && !debug
uses-unsafe-cpows = true
tags = psm
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_windowactivation.js]

View File

@ -27,7 +27,7 @@ add_task(async function() {
let contextMenuPromise = BrowserTestUtils.waitForEvent(
contextMenu,
"popupshown"
).then(() => gContextMenuContentData.target);
);
await ContentTask.spawn(
tab.linkedBrowser,

View File

@ -1,26 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/.
*/
function test() {
waitForExplicitFinish();
// Open a new tab.
whenNewTabLoaded(window, testPreferences);
}
function testPreferences() {
whenTabLoaded(gBrowser.selectedTab, function() {
is(
content.location.href,
"about:preferences",
"Checking if the preferences tab was opened"
);
gBrowser.removeCurrentTab();
finish();
});
openPreferences();
}

View File

@ -89,7 +89,7 @@ function createPanel(attrs)
button.label = "OK";
button.width = 120;
button.height = 40;
button.setAttribute("style", "-moz-appearance: none; border: 0; margin: 0;");
button.setAttribute("style", "appearance: none; border: 0; margin: 0;");
panel.setAttribute("style", "-moz-appearance: none; border: 0; margin: 0;");
return document.documentElement.appendChild(panel);
}

View File

@ -10,6 +10,7 @@ ChromeUtils.defineModuleGetter(
);
const TEST_ORIGIN = "https://example.com";
const TEST_HTTP_ORIGIN = "http://example.com";
const TEST_SUB_ORIGIN = "https://test1.example.com";
const REMOVE_DIALOG_URL =
"chrome://browser/content/preferences/siteDataRemoveSelected.xul";
@ -36,30 +37,67 @@ add_task(async function test_CertificateError() {
let pageInfo = BrowserPageInfo(TEST_ORIGIN_CERT_ERROR, "securityTab");
await BrowserTestUtils.waitForEvent(pageInfo, "load");
let securityTab = pageInfo.document.getElementById("securityTab");
let pageInfoDoc = pageInfo.document;
let securityTab = pageInfoDoc.getElementById("securityTab");
await TestUtils.waitForCondition(
() => BrowserTestUtils.is_visible(securityTab),
"Security tab should be visible."
);
let owner = pageInfo.document.getElementById("security-identity-owner-value");
let verifier = pageInfo.document.getElementById(
"security-identity-verifier-value"
);
let domain = pageInfo.document.getElementById(
"security-identity-domain-value"
let owner = pageInfoDoc.getElementById("security-identity-owner-value");
let verifier = pageInfoDoc.getElementById("security-identity-verifier-value");
let domain = pageInfoDoc.getElementById("security-identity-domain-value");
await TestUtils.waitForCondition(
() => owner.value === "This website does not supply ownership information.",
`Value of owner should be should be "This website does not supply ownership information." instead got "${
verifier.value
}".`
);
await TestUtils.waitForCondition(
() =>
owner.textContent ===
"This website does not supply ownership information.",
`Value of owner should be should be "This website does not supply ownership information." instead got "${
() => verifier.textContent === "Not specified",
`Value of verifier should be "Mozilla Testing", instead got "${
verifier.textContent
}".`
);
await TestUtils.waitForCondition(
() => domain.value === browser.currentURI.displayHost,
`Value of domain should be ${
browser.currentURI.displayHost
}, instead got "${domain.value}".`
);
pageInfo.close();
BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
// Test displaying website identity information on http pages.
add_task(async function test_SecurityHTTP() {
await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_HTTP_ORIGIN);
let pageInfo = BrowserPageInfo(TEST_HTTP_ORIGIN, "securityTab");
await BrowserTestUtils.waitForEvent(pageInfo, "load");
let pageInfoDoc = pageInfo.document;
let securityTab = pageInfoDoc.getElementById("securityTab");
await TestUtils.waitForCondition(
() => BrowserTestUtils.is_visible(securityTab),
"Security tab should be visible."
);
let owner = pageInfoDoc.getElementById("security-identity-owner-value");
let verifier = pageInfoDoc.getElementById("security-identity-verifier-value");
let domain = pageInfoDoc.getElementById("security-identity-domain-value");
await TestUtils.waitForCondition(
() => owner.value === "This website does not supply ownership information.",
`Value of owner should be should be "This website does not supply ownership information." instead got "${
verifier.value
}".`
);
await TestUtils.waitForCondition(
() => verifier.textContent === "Not specified",
`Value of verifier should be "Not specified", instead got "${
@ -68,9 +106,9 @@ add_task(async function test_CertificateError() {
);
await TestUtils.waitForCondition(
() => domain.value === browser.currentURI.displayHost,
() => domain.value === gBrowser.selectedBrowser.currentURI.displayHost,
`Value of domain should be ${
browser.currentURI.displayHost
gBrowser.selectedBrowser.currentURI.displayHost
}, instead got "${domain.value}".`
);
@ -88,13 +126,10 @@ add_task(async function test_SiteData() {
let pageInfo = BrowserPageInfo(TEST_ORIGIN, "securityTab");
await BrowserTestUtils.waitForEvent(pageInfo, "load");
let pageInfoDoc = pageInfo.document;
let label = pageInfo.document.getElementById(
"security-privacy-sitedata-value"
);
let clearButton = pageInfo.document.getElementById(
"security-clear-sitedata"
);
let label = pageInfoDoc.getElementById("security-privacy-sitedata-value");
let clearButton = pageInfoDoc.getElementById("security-clear-sitedata");
let size = DownloadUtils.convertByteUnits(totalUsage);
@ -142,12 +177,10 @@ add_task(async function test_Cookies() {
let pageInfo = BrowserPageInfo(TEST_ORIGIN, "securityTab");
await BrowserTestUtils.waitForEvent(pageInfo, "load");
let label = pageInfo.document.getElementById(
"security-privacy-sitedata-value"
);
let clearButton = pageInfo.document.getElementById(
"security-clear-sitedata"
);
let pageInfoDoc = pageInfo.document;
let label = pageInfoDoc.getElementById("security-privacy-sitedata-value");
let clearButton = pageInfoDoc.getElementById("security-clear-sitedata");
// The usage details are filled asynchronously, so we assert that they're present by
// waiting for them to be filled in.

View File

@ -1,116 +0,0 @@
"use strict";
// This tests searching in the legacy urlbar implementation (a.k.a. the
// awesomebar).
// There are a _lot_ of reflows in this test, and processing them takes
// time. On slower builds, we need to boost our allowed test time.
requestLongerTimeout(5);
/**
* WHOA THERE: We should never be adding new things to EXPECTED_REFLOWS. This
* is a whitelist that should slowly go away as we improve the performance of
* the front-end. Instead of adding more reflows to the whitelist, you should
* be modifying your code to avoid the reflow.
*
* See https://developer.mozilla.org/en-US/Firefox/Performance_best_practices_for_Firefox_fe_engineers
* for tips on how to do that.
*/
/* These reflows happen only the first time the awesomebar panel opens. */
const EXPECTED_REFLOWS_FIRST_OPEN = [];
if (
AppConstants.platform == "linux" ||
AppConstants.platform == "win" ||
// macOS 10.14 Mojave (Darwin version 18)
AppConstants.isPlatformAndVersionAtLeast("macosx", "18")
) {
EXPECTED_REFLOWS_FIRST_OPEN.push({
stack: [
"__rebuild@chrome://browser/content/search/search-one-offs.js",
/* This is limited to a one-line stack, because the next item is an async
function and as such not supported on all trees, according to bug 1501761.
"async*set popup@chrome://browser/content/search/search-one-offs.js",
"_syncOneOffSearchesEnabled@chrome://browser/content/urlbarBindings.xml",
"toggleOneOffSearches@chrome://browser/content/urlbarBindings.xml",
"_enableOrDisableOneOffSearches@chrome://browser/content/urlbarBindings.xml",
"@chrome://browser/content/urlbarBindings.xml",
"_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
"openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
"openPopup@chrome://global/content/bindings/autocomplete.xml",
"set_popupOpen@chrome://global/content/bindings/autocomplete.xml",*/
],
});
}
EXPECTED_REFLOWS_FIRST_OPEN.push(
{
stack: [
"_handleOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
"handleOverUnderflow@chrome://global/content/elements/autocomplete-richlistitem.js",
"_reuseAcItem@chrome://global/content/elements/autocomplete-richlistitem.js",
"_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
"_invalidate@chrome://global/content/bindings/autocomplete.xml",
"invalidate@chrome://global/content/bindings/autocomplete.xml",
],
maxCount: 60, // This number should only ever go down - never up.
},
{
stack: [
"_handleOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
"handleOverUnderflow@chrome://global/content/elements/autocomplete-richlistitem.js",
"_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
"openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
"openPopup@chrome://global/content/bindings/autocomplete.xml",
"set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
],
maxCount: 6, // This number should only ever go down - never up.
},
// Bug 1359989
{
stack: [
"_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
"openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
"openPopup@chrome://global/content/bindings/autocomplete.xml",
"set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
],
}
);
// These extra reflows happen on beta/release as one of the default bookmarks in
// bookmarks.html.in has a long URL.
if (AppConstants.RELEASE_OR_BETA) {
EXPECTED_REFLOWS_FIRST_OPEN.push(
{
stack: [
"_handleOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
"_onUnderflow@chrome://global/content/elements/autocomplete-richlistitem.js",
"MozAutocompleteRichlistitem/<@chrome://global/content/elements/autocomplete-richlistitem.js",
],
maxCount: 6,
},
{
stack: [
"_handleOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
"_onOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
"MozAutocompleteRichlistitem/<@chrome://global/content/elements/autocomplete-richlistitem.js",
],
maxCount: 6,
},
{
stack: [
"_handleOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
"_adjustAcItem@chrome://global/content/elements/autocomplete-richlistitem.js",
"_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
"_invalidate@chrome://global/content/bindings/autocomplete.xml",
"invalidate@chrome://global/content/bindings/autocomplete.xml",
],
maxCount: 12,
}
);
}
add_task(async function awesomebar() {
await runUrlbarTest(true, true, EXPECTED_REFLOWS_FIRST_OPEN);
});

View File

@ -1,113 +0,0 @@
"use strict";
// This tests searching in the legacy urlbar implementation (a.k.a. the
// awesomebar).
// There are a _lot_ of reflows in this test, and processing them takes
// time. On slower builds, we need to boost our allowed test time.
requestLongerTimeout(5);
/**
* WHOA THERE: We should never be adding new things to EXPECTED_REFLOWS. This
* is a whitelist that should slowly go away as we improve the performance of
* the front-end. Instead of adding more reflows to the whitelist, you should
* be modifying your code to avoid the reflow.
*
* See https://developer.mozilla.org/en-US/Firefox/Performance_best_practices_for_Firefox_fe_engineers
* for tips on how to do that.
*/
/* These reflows happen only the first time the awesomebar panel opens. */
const EXPECTED_REFLOWS_FIRST_OPEN = [];
if (
AppConstants.platform == "linux" ||
AppConstants.platform == "win" ||
// macOS 10.14 Mojave (Darwin version 18)
AppConstants.isPlatformAndVersionAtLeast("macosx", "18")
) {
EXPECTED_REFLOWS_FIRST_OPEN.push({
stack: [
"__rebuild@chrome://browser/content/search/search-one-offs.js",
/* This is limited to a one-line stack, because the next item is an async
function and as such not supported on all trees, according to bug 1501761.
"async*set popup@chrome://browser/content/search/search-one-offs.js",
"_syncOneOffSearchesEnabled@chrome://browser/content/urlbarBindings.xml",
"toggleOneOffSearches@chrome://browser/content/urlbarBindings.xml",
"_enableOrDisableOneOffSearches@chrome://browser/content/urlbarBindings.xml",
"@chrome://browser/content/urlbarBindings.xml",
"_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
"openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
"openPopup@chrome://global/content/bindings/autocomplete.xml",
"set_popupOpen@chrome://global/content/bindings/autocomplete.xml",*/
],
});
}
EXPECTED_REFLOWS_FIRST_OPEN.push(
{
stack: [
"_handleOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
"handleOverUnderflow@chrome://global/content/elements/autocomplete-richlistitem.js",
"_reuseAcItem@chrome://global/content/elements/autocomplete-richlistitem.js",
"_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
"_invalidate@chrome://global/content/bindings/autocomplete.xml",
"invalidate@chrome://global/content/bindings/autocomplete.xml",
],
maxCount: 36, // This number should only ever go down - never up.
},
{
stack: [
"_handleOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
"handleOverUnderflow@chrome://global/content/elements/autocomplete-richlistitem.js",
"_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
"openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
"openPopup@chrome://global/content/bindings/autocomplete.xml",
"set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
],
maxCount: 6, // This number should only ever go down - never up.
},
// Bug 1359989
{
stack: [
"_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
"openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
"openPopup@chrome://global/content/bindings/autocomplete.xml",
"set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
],
}
);
/* These reflows happen everytime the awesomebar panel opens. */
const EXPECTED_REFLOWS_SECOND_OPEN = [
{
stack: [
"_handleOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
"handleOverUnderflow@chrome://global/content/elements/autocomplete-richlistitem.js",
"_reuseAcItem@chrome://global/content/elements/autocomplete-richlistitem.js",
"_appendCurrentResult@chrome://global/content/bindings/autocomplete.xml",
"_invalidate@chrome://global/content/bindings/autocomplete.xml",
"invalidate@chrome://global/content/bindings/autocomplete.xml",
],
maxCount: 24, // This number should only ever go down - never up.
},
// Bug 1359989
{
stack: [
"_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
"openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
"openPopup@chrome://global/content/bindings/autocomplete.xml",
"set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
],
},
];
add_task(async function awesomebar() {
await runUrlbarTest(
true,
false,
EXPECTED_REFLOWS_FIRST_OPEN,
EXPECTED_REFLOWS_SECOND_OPEN
);
});

View File

@ -1,8 +0,0 @@
[DEFAULT]
prefs =
browser.urlbar.quantumbar=false
[../browser_urlbar_keyed_search_legacy.js]
skip-if = (os == 'linux') || (os == 'win' && debug) || (os == 'win' && bits == 32) # Disabled on Linux and Windows debug due to perma failures. Bug 1392320. Disabled on Win32 because of intermittent OOM failures (bug 1448241).
[../browser_urlbar_search_legacy.js]
skip-if = (debug || ccov) && (os == 'linux' || os == 'win') || (os == 'win' && bits == 32) # Disabled on Linux and Windows debug and ccov due to intermittent timeouts. Bug 1414126, bug 1426611. Disabled on Win32 because of intermittent OOM failures (bug 1448241)

View File

@ -43,7 +43,6 @@ add_task(async function() {
"plugin should not have been found."
);
// simple cpows
await ContentTask.spawn(gTestBrowser, null, function() {
let plugin = content.document.getElementById("plugin");
ok(plugin, "plugin should be in the page");

View File

@ -235,9 +235,6 @@ const ignorableWhitelist = new Set([
// toolkit/mozapps/extensions/nsBlocklistService.js
"resource://app/blocklist.xml",
// dom/media/gmp/GMPParent.cpp
"resource://gre/gmp-clearkey/0.1/manifest.json",
// Bug 1351669 - obsolete test file
"resource://gre/res/test.properties",

View File

@ -35,7 +35,7 @@ let whitelist = [
isFromDevTools: false,
},
{
sourceName: /\b(minimal-xul|html|mathml|ua)\.css$/i,
sourceName: /\b(minimal-xul|html|mathml|ua|forms|svg)\.css$/i,
errorMessage: /Unknown property.*-moz-/i,
isFromDevTools: false,
},
@ -57,14 +57,6 @@ let whitelist = [
platforms: ["linux"],
isFromDevTools: false,
},
// The '-moz-menulist-arrow-button' value is only supported in chrome and UA sheets
// but forms.css is loaded as a document sheet by this test.
// Maybe bug 1261237 will fix this?
{
sourceName: /(?:res|gre-resources)\/forms\.css$/i,
errorMessage: /Error in parsing value for \u2018-moz-appearance\u2019/iu,
isFromDevTools: false,
},
// These variables are declared somewhere else, and error when we load the
// files directly. They're all marked intermittent because their appearance
// in the error console seems to not be consistent.
@ -101,14 +93,6 @@ if (
});
}
if (!Services.prefs.getBoolPref("layout.css.scrollbar-width.enabled")) {
whitelist.push({
sourceName: /(?:res|gre-resources)\/forms\.css$/i,
errorMessage: /Unknown property .*\bscrollbar-width\b/i,
isFromDevTools: false,
});
}
if (!Services.prefs.getBoolPref("layout.css.file-chooser-button.enabled")) {
// Reserved to UA sheets, behind a pref for content.
whitelist.push({

File diff suppressed because it is too large Load Diff

View File

@ -364,7 +364,7 @@ function openLinkIn(url, where, params) {
var aPostData = params.postData;
var aCharset = params.charset;
var aReferrerInfo = params.referrerInfo
? params.referrerInfo
? E10SUtils.deserializeReferrerInfo(params.referrerInfo)
: new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, null);
var aRelatedToCurrent = params.relatedToCurrent;
var aAllowInheritPrincipal = !!params.allowInheritPrincipal;
@ -390,8 +390,6 @@ function openLinkIn(url, where, params) {
}
if (where == "save") {
// TODO(1073187): propagate referrerPolicy.
// ContentClick.jsm passes isContentWindowPrivate for saveURL instead of passing a CPOW initiatingDoc
if ("isContentWindowPrivate" in params) {
saveURL(
url,
@ -1066,12 +1064,6 @@ function openFeedbackPage() {
openTrustedLinkIn(url, "tab");
}
function openTourPage() {
let scope = {};
ChromeUtils.import("resource:///modules/UITour.jsm", scope);
openTrustedLinkIn(scope.UITour.url, "tab");
}
function buildHelpMenu() {
document.getElementById(
"feedbackPage"

View File

@ -93,7 +93,7 @@ browser.jar:
content/browser/tabbrowser.css (content/tabbrowser.css)
content/browser/tabbrowser.js (content/tabbrowser.js)
content/browser/tabbrowser.xml (content/tabbrowser.xml)
* content/browser/urlbarBindings.xml (content/urlbarBindings.xml)
content/browser/urlbarBindings.xml (content/urlbarBindings.xml)
content/browser/utilityOverlay.js (content/utilityOverlay.js)
content/browser/webext-panels.js (content/webext-panels.js)
* content/browser/webext-panels.xul (content/webext-panels.xul)

View File

@ -33,7 +33,6 @@ BROWSER_CHROME_MANIFESTS += [
'content/test/performance/browser.ini',
'content/test/performance/hidpi/browser.ini',
'content/test/performance/io/browser.ini',
'content/test/performance/legacyurlbar/browser.ini',
'content/test/performance/lowdpi/browser.ini',
'content/test/permissions/browser.ini',
'content/test/plugins/browser-rs-blocklist.ini',

View File

@ -42,6 +42,7 @@ let LEGACY_ACTORS = {
matches: ["about:logins", "about:logins?*"],
module: "resource:///actors/AboutLoginsChild.jsm",
events: {
AboutLoginsCopyLoginDetail: { wantUntrusted: true },
AboutLoginsCreateLogin: { wantUntrusted: true },
AboutLoginsDeleteLogin: { wantUntrusted: true },
AboutLoginsImport: { wantUntrusted: true },
@ -55,6 +56,7 @@ let LEGACY_ACTORS = {
"AboutLogins:LoginAdded",
"AboutLogins:LoginModified",
"AboutLogins:LoginRemoved",
"AboutLogins:MasterPasswordResponse",
],
},
},
@ -284,16 +286,6 @@ let LEGACY_ACTORS = {
},
},
UITour: {
child: {
module: "resource:///modules/UITourChild.jsm",
events: {
mozUITour: { wantUntrusted: true },
},
permissions: ["uitour"],
},
},
URIFixup: {
child: {
module: "resource:///actors/URIFixupChild.jsm",
@ -453,7 +445,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
PluralForm: "resource://gre/modules/PluralForm.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.jsm",
RemoteSettings: "resource://services-settings/remote-settings.js",
RemoteSecuritySettings: "resource://gre/modules/psm/RemoteSecuritySettings.jsm",
RFPHelper: "resource://gre/modules/RFPHelper.jsm",
SafeBrowsing: "resource://gre/modules/SafeBrowsing.jsm",
@ -465,7 +456,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
TabUnloader: "resource:///modules/TabUnloader.jsm",
UIState: "resource://services-sync/UIState.jsm",
UITour: "resource:///modules/UITour.jsm",
WebChannel: "resource://gre/modules/WebChannel.jsm",
WindowsRegistry: "resource://gre/modules/WindowsRegistry.jsm",
});
@ -561,6 +551,7 @@ const listeners = {
"AboutLogins:CreateLogin": ["AboutLoginsParent"],
"AboutLogins:DeleteLogin": ["AboutLoginsParent"],
"AboutLogins:Import": ["AboutLoginsParent"],
"AboutLogins:MasterPasswordRequest": ["AboutLoginsParent"],
"AboutLogins:OpenPreferences": ["AboutLoginsParent"],
"AboutLogins:OpenSite": ["AboutLoginsParent"],
"AboutLogins:Subscribe": ["AboutLoginsParent"],
@ -1959,14 +1950,6 @@ BrowserGlue.prototype = {
// can check the log.
this._gmpInstallManager.simpleCheckAndInstall().catch(() => {});
});
Services.tm.idleDispatchToMainThread(() => {
RemoteSettings.init();
});
Services.tm.idleDispatchToMainThread(() => {
RemoteSecuritySettings.init();
});
},
_onQuitRequest: function BG__onQuitRequest(aCancelQuit, aQuitType) {
@ -2011,10 +1994,6 @@ BrowserGlue.prototype = {
}
}
if (pagecount < 2) {
return;
}
if (!aQuitType) {
aQuitType = "quit";
}
@ -2067,10 +2046,12 @@ BrowserGlue.prototype = {
? "tabs.closeWarningMultipleSessionRestore2"
: "tabs.closeWarningMultiple";
warningMessage = gTabbrowserBundle.GetStringFromName(stringID);
warningMessage = PluralForm.get(pagecount, warningMessage).replace(
"#1",
pagecount
);
if (pagecount > 1) {
warningMessage = PluralForm.get(pagecount, warningMessage).replace(
"#1",
pagecount
);
}
}
let warnOnClose = { value: true };
@ -4063,13 +4044,6 @@ var JawsScreenReaderVersionCheck = {
},
};
// Listen for UITour messages.
// Do it here instead of the UITour module itself so that the UITour module is lazy loaded
// when the first message is received.
Services.mm.addMessageListener("UITour:onPageEvent", function(aMessage) {
UITour.onPageEvent(aMessage, aMessage.data);
});
// Listen for HybridContentTelemetry messages.
// Do it here instead of HybridContentTelemetry.init() so that
// the module can be lazily loaded on the first message.

View File

@ -134,7 +134,7 @@ body.config-warning {
td.cell-value > form > input[type="text"],
td.cell-value > form > input[type="number"] {
-moz-appearance: textfield;
appearance: textfield;
width: 100%;
box-sizing: border-box;
}

View File

@ -12,6 +12,9 @@ const { ActorChild } = ChromeUtils.import(
const { LoginHelper } = ChromeUtils.import(
"resource://gre/modules/LoginHelper.jsm"
);
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
@ -19,11 +22,21 @@ ChromeUtils.defineModuleGetter(
"resource://gre/modules/AppConstants.jsm"
);
XPCOMUtils.defineLazyServiceGetter(
this,
"ClipboardHelper",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper"
);
let masterPasswordPromise;
class AboutLoginsChild extends ActorChild {
handleEvent(event) {
switch (event.type) {
case "AboutLoginsInit": {
this.mm.sendAsyncMessage("AboutLogins:Subscribe");
let messageManager = this.mm;
messageManager.sendAsyncMessage("AboutLogins:Subscribe");
let documentElement = this.content.document.documentElement;
documentElement.classList.toggle(
@ -36,6 +49,15 @@ class AboutLoginsChild extends ActorChild {
doLoginsMatch(loginA, loginB) {
return LoginHelper.doLoginsMatch(loginA, loginB, {});
},
promptForMasterPassword(resolve) {
masterPasswordPromise = {
resolve,
};
messageManager.sendAsyncMessage(
"AboutLogins:MasterPasswordRequest"
);
},
};
waivedContent.AboutLoginsUtils = Cu.cloneInto(
AboutLoginsUtils,
@ -46,6 +68,10 @@ class AboutLoginsChild extends ActorChild {
);
break;
}
case "AboutLoginsCopyLoginDetail": {
ClipboardHelper.copyString(event.detail);
break;
}
case "AboutLoginsCreateLogin": {
this.mm.sendAsyncMessage("AboutLogins:CreateLogin", {
login: event.detail,
@ -95,6 +121,11 @@ class AboutLoginsChild extends ActorChild {
case "AboutLogins:LoginRemoved":
this.sendToContent("LoginRemoved", message.data);
break;
case "AboutLogins:MasterPasswordResponse":
if (masterPasswordPromise) {
masterPasswordPromise.resolve(message.data);
}
break;
}
}

View File

@ -37,18 +37,9 @@ XPCOMUtils.defineLazyGetter(this, "log", () => {
const ABOUT_LOGINS_ORIGIN = "about:logins";
const MASTER_PASSWORD_NOTIFICATION_ID = "master-password-login-required";
const PRIVILEGED_PROCESS_PREF =
"browser.tabs.remote.separatePrivilegedContentProcess";
const PRIVILEGED_PROCESS_ENABLED = Services.prefs.getBoolPref(
PRIVILEGED_PROCESS_PREF,
false
);
// When the privileged content process is enabled, we expect about:logins
// to load in it. Otherwise, it's in a normal web content process.
const EXPECTED_ABOUTLOGINS_REMOTE_TYPE = PRIVILEGED_PROCESS_ENABLED
? E10SUtils.PRIVILEGED_REMOTE_TYPE
: E10SUtils.DEFAULT_REMOTE_TYPE;
// about:logins will always use the privileged content process,
// even if it is disabled for other consumers such as about:newtab.
const EXPECTED_ABOUTLOGINS_REMOTE_TYPE = E10SUtils.PRIVILEGED_REMOTE_TYPE;
const isValidLogin = login => {
return !(login.origin || "").startsWith("chrome://");
@ -148,6 +139,50 @@ var AboutLoginsParent = {
});
break;
}
case "AboutLogins:MasterPasswordRequest": {
// This doesn't harm if passwords are not encrypted
let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"].createInstance(
Ci.nsIPK11TokenDB
);
let token = tokendb.getInternalKeyToken();
let messageManager = message.target.messageManager;
// If there is no master password, return as-if authentication succeeded.
if (token.checkPassword("")) {
messageManager.sendAsyncMessage(
"AboutLogins:MasterPasswordResponse",
true
);
return;
}
// If a master password prompt is already open, just exit early and return false.
// The user can re-trigger it after responding to the already open dialog.
if (Services.logins.uiBusy) {
messageManager.sendAsyncMessage(
"AboutLogins:MasterPasswordResponse",
false
);
return;
}
// So there's a master password. But since checkPassword didn't succeed, we're logged out (per nsIPK11Token.idl).
try {
// Relogin and ask for the master password.
token.login(true); // 'true' means always prompt for token password. User will be prompted until
// clicking 'Cancel' or entering the correct password.
} catch (e) {
// An exception will be thrown if the user cancels the login prompt dialog.
// User is also logged out of Software Security Device.
}
messageManager.sendAsyncMessage(
"AboutLogins:MasterPasswordResponse",
token.isLoggedIn()
);
break;
}
case "AboutLogins:Subscribe": {
if (
!ChromeUtils.nondeterministicGetWeakSetKeys(this._subscribers).length

View File

@ -3,8 +3,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
body {
--sidebar-width: 320px;
display: grid;
grid-template-columns: minmax(320px, max-content) 1fr;
grid-template-columns: var(--sidebar-width) 1fr;
grid-template-rows: 75px 1fr;
grid-template-areas: "header header"
"logins login";
@ -15,13 +16,16 @@ header {
display: flex;
grid-area: header;
align-items: center;
background-color: var(--in-content-box-background);
background-color: var(--in-content-page-background);
border-bottom: 1px solid var(--in-content-box-border-color);
padding: 0 18px;
padding-inline-end: 23px;
}
login-filter {
flex: auto;
min-width: 320px;
max-width: 400px;
margin-inline: 40px auto;
flex-grow: 1;
align-self: center;
}
@ -38,12 +42,3 @@ login-item {
max-width: 800px;
}
#branding-logo {
height: 32px;
margin-inline-end: 18px;
}
:root:not(.official-branding) #branding-logo {
display: none;
}

View File

@ -9,7 +9,7 @@
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; object-src 'none'; script-src resource: chrome:; img-src data: blob:;"/>
<title data-l10n-id="about-logins-page-title"></title>
<link rel="localization" href="preview/aboutLogins.ftl">
<script type="module" src="chrome://browser/content/aboutlogins/components/confirm-delete-dialog.js"></script>
<script type="module" src="chrome://browser/content/aboutlogins/components/confirmation-dialog.js"></script>
<script type="module" src="chrome://browser/content/aboutlogins/components/login-filter.js"></script>
<script type="module" src="chrome://browser/content/aboutlogins/components/login-item.js"></script>
<script type="module" src="chrome://browser/content/aboutlogins/components/login-list.js"></script>
@ -28,24 +28,25 @@
</header>
<login-list></login-list>
<login-item></login-item>
<confirm-delete-dialog hidden></confirm-delete-dialog>
<confirmation-dialog hidden></confirmation-dialog>
<template id="confirm-delete-dialog-template">
<template id="confirmation-dialog-template">
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/common.css">
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/confirm-delete-dialog.css">
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/confirmation-dialog.css">
<div class="overlay">
<div class="container" role="dialog" aria-labelledby="title" aria-describedby="message">
<div class="title-bar">
<h1 class="title" id="title" data-l10n-id="confirm-delete-dialog-title"></h1>
<button class="dismiss-button" data-l10n-id="confirm-delete-dialog-dismiss-button"></button>
</div>
<button class="dismiss-button" data-l10n-id="confirmation-dialog-dismiss-button">
<img class="dismiss-icon" src="chrome://global/skin/icons/close.svg"/>
</button>
<div class="content">
<p class="message" id="message" data-l10n-id="confirm-delete-dialog-message"></p>
<img class="warning-icon" src="chrome://global/skin/icons/warning.svg"/>
<h1 class="title" id="title"></h1>
<p class="message" id="message"></p>
</div>
<div class="buttons">
<button class="cancel-button" data-l10n-id="confirm-delete-dialog-cancel-button"></button>
<button class="confirm-button" data-l10n-id="confirm-delete-dialog-confirm-button"></button>
<button class="confirm-button danger-button"></button>
<button class="cancel-button" data-l10n-id="confirmation-dialog-cancel-button"></button>
</div>
</div>
</div>
@ -72,7 +73,7 @@
</template>
<template id="login-list-item-template">
<li class="login-list-item">
<li class="labels login-list-item" role="option">
<span class="title"></span>
<span class="username"></span>
</li>
@ -87,23 +88,23 @@
<span class="login-item-title"></span>
<span class="new-login-title" data-l10n-id="login-item-new-login-title"></span>
</h2>
<button class="edit-button alternate-button" data-l10n-id="login-item-edit-button"></button>
<button class="delete-button alternate-button" data-l10n-id="login-item-delete-button"></button>
<button class="edit-button ghost-button" data-l10n-id="login-item-edit-button"></button>
<button class="delete-button ghost-button" data-l10n-id="login-item-delete-button"></button>
</div>
<form>
<div class="detail-row">
<label class="detail-cell">
<span class="origin-label field-label" data-l10n-id="login-item-origin-label"></span>
<input type="url" name="origin" class="origin-input" required data-l10n-id="login-item-origin"/>
<input type="url" name="origin" class="origin-input" required data-l10n-id="login-item-origin" dir="auto"/>
</label>
<button class="open-site-button" data-l10n-id="login-item-open-site-button"></button>
<button class="open-site-button" data-l10n-id="login-item-open-site-button" type="button"></button>
</div>
<div class="detail-row">
<label class="detail-cell">
<span class="username-label field-label" data-l10n-id="login-item-username-label"></span>
<input type="text" name="username" data-l10n-id="login-item-username"/>
</label>
<button class="copy-button copy-username-button" data-copy-login-property="username">
<button class="copy-button copy-username-button" data-copy-login-property="username" type="button">
<span class="copied-button-text" data-l10n-id="login-item-copied-username-button-text"></span>
<span class="copy-button-text" data-l10n-id="login-item-copy-username-button-text"></span>
</button>
@ -118,7 +119,7 @@
data-l10n-id="login-item-password-reveal-checkbox"/>
</div>
</label>
<button class="copy-button copy-password-button" data-copy-login-property="password">
<button class="copy-button copy-password-button" data-copy-login-property="password" type="button">
<span class="copied-button-text" data-l10n-id="login-item-copied-password-button-text"></span>
<span class="copy-button-text" data-l10n-id="login-item-copy-password-button-text"></span>
</button>
@ -126,8 +127,8 @@
<p class="time-created meta-info" data-l10n-id="login-item-time-created" data-l10n-args='{"timeCreated": 0}'></p>
<p class="time-changed meta-info" data-l10n-id="login-item-time-changed" data-l10n-args='{"timeChanged": 0}'></p>
<p class="time-used meta-info" data-l10n-id="login-item-time-used" data-l10n-args='{"timeUsed": 0}'></p>
<button class="save-changes-button" data-l10n-id="login-item-save-changes-button"></button>
<button class="cancel-button" data-l10n-id="login-item-cancel-button"></button>
<button class="save-changes-button" type="submit"></button>
<button class="cancel-button" data-l10n-id="login-item-cancel-button" type="button"></button>
</form>
</template>
@ -135,17 +136,17 @@
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/common.css">
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/login-filter.css">
<input data-l10n-id="login-filter" class="filter" type="text"/>
<input data-l10n-id="login-filter" class="filter" type="text" dir="auto"/>
</template>
<template id="menu-button-template">
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/common.css">
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/menu-button.css">
<button class="menu-button alternate-button" data-l10n-id="menu"></button>
<button class="menu-button ghost-button" data-l10n-id="menu"></button>
<ul class="menu" role="menu" hidden>
<button role="menuitem" class="menuitem-button menuitem-import alternate-button" hidden data-supported-platforms="Win32" data-event-name="AboutLoginsImport" data-l10n-id="menu-menuitem-import"></button>
<button role="menuitem" class="menuitem-button menuitem-preferences alternate-button" data-event-name="AboutLoginsOpenPreferences" data-l10n-id="menu-menuitem-preferences"></button>
<button role="menuitem" class="menuitem-button menuitem-import ghost-button" hidden data-supported-platforms="Win32" data-event-name="AboutLoginsImport" data-l10n-id="menu-menuitem-import"></button>
<button role="menuitem" class="menuitem-button menuitem-preferences ghost-button" data-event-name="AboutLoginsOpenPreferences" data-l10n-id="menu-menuitem-preferences"></button>
</ul>
</template>
</body>

View File

@ -7,15 +7,3 @@
[hidden] {
display: none !important;
}
.alternate-button {
background-color: transparent;
}
.alternate-button:hover {
background-color: var(--in-content-button-background-hover);
}
.alternate-button:hover:active {
background-color: var(--in-content-button-background-active);
}

View File

@ -1,88 +1,85 @@
.overlay {
/* 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/. */
.overlay {
position: fixed;
z-index: 1;
top: 0;
bottom: 0;
left: 0;
right: 0;
inset: 0;
/* TODO: this color is used in the about:preferences overlay, but
why isn't it declared as a variable? */
background-color: rgba(0,0,0,0.5);
display: flex;
align-items: center;
}
.container {
z-index: 2;
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
width: 50%;
min-width: 250px;
max-width: 500px;
height: 40%;
min-height: 200px;
margin: auto;
background: var(--in-content-page-background);
color: var(--in-content-page-color);
}
.title-bar {
position: relative;
flex: 0 1 auto;
text-align: center;
background-color: var(--in-content-dialog-header-background);
padding: 5px;
border-bottom: 1px solid var(--in-content-border-color);
box-shadow: var(--shadow-30);
/* show a border in high contrast mode */
outline: 1px solid transparent;
}
.title {
font-size: .9em;
line-height: 1.8em;
font-weight: 600;
font-size: 1.5em;
font-weight: normal;
-moz-user-select: none;
margin: 0;
}
button.dismiss-button {
.message {
color: var(--in-content-deemphasized-text);
margin-bottom: 0;
}
.dismiss-button {
position: absolute;
top: 0;
right: 0;
inset-inline-end: 0;
min-width: 20px;
min-height: 20px;
margin: 8px 16px;
margin: 16px;
padding: 0;
background: url(chrome://global/skin/icons/close.svg) no-repeat center;
line-height: 0;
}
.dismiss-icon {
-moz-context-properties: fill, fill-opacity;
fill: currentColor;
fill-opacity: 0;
}
button.dismiss-button:dir(rtl) {
right: auto;
left: 0;
}
.content {
flex: 1 1 auto;
display: flex;
justify-content: center;
align-items: center;
}
.buttons {
flex: 0 1 auto;
display: flex;
justify-content: space-between;
}
.buttons button {
margin: 0;
.warning-icon {
-moz-context-properties: fill;
fill: currentColor;
width: 40px;
height: 40px;
margin: 16px;
}
.content,
.buttons {
margin: 16px;
text-align: center;
padding: 16px 32px;
}
.buttons {
display: flex;
justify-content: space-between;
}
.buttons.macosx > .confirm-button {
order: 1;
}
.buttons > button {
min-width: 140px;
}

View File

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
export default class ConfirmDeleteDialog extends HTMLElement {
export default class ConfirmationDialog extends HTMLElement {
constructor() {
super();
this._promise = null;
@ -12,22 +12,31 @@ export default class ConfirmDeleteDialog extends HTMLElement {
if (this.shadowRoot) {
return;
}
let template = document.querySelector("#confirm-delete-dialog-template");
let template = document.querySelector("#confirmation-dialog-template");
let shadowRoot = this.attachShadow({ mode: "open" });
document.l10n.connectRoot(shadowRoot);
shadowRoot.appendChild(template.content.cloneNode(true));
this._buttons = this.shadowRoot.querySelector(".buttons");
this._cancelButton = this.shadowRoot.querySelector(".cancel-button");
this._confirmButton = this.shadowRoot.querySelector(".confirm-button");
this._dismissButton = this.shadowRoot.querySelector(".dismiss-button");
this._message = this.shadowRoot.querySelector(".message");
this._overlay = this.shadowRoot.querySelector(".overlay");
this._title = this.shadowRoot.querySelector(".title");
this._buttons.classList.toggle("macosx", navigator.platform == "MacIntel");
}
handleEvent(event) {
switch (event.type) {
case "keydown":
if (event.repeat) {
// Prevent repeat keypresses from accidentally confirming the
// dialog since the confirmation button is focused by default.
event.preventDefault();
return;
}
if (event.key === "Escape" && !event.defaultPrevented) {
this.onCancel();
}
@ -35,7 +44,7 @@ export default class ConfirmDeleteDialog extends HTMLElement {
case "click":
if (
event.target.classList.contains("cancel-button") ||
event.target.classList.contains("dismiss-button") ||
event.currentTarget.classList.contains("dismiss-button") ||
event.target.classList.contains("overlay")
) {
this.onCancel();
@ -45,7 +54,28 @@ export default class ConfirmDeleteDialog extends HTMLElement {
}
}
setKeyboardAccessForElementsExternalToDialog(enableTabbingOutsideDialog) {
const pageElements = document.querySelectorAll(
"login-item, login-list, menu-button, login-filter, fxaccounts-button, [tabindex]"
);
pageElements.forEach(el => {
if (!enableTabbingOutsideDialog) {
if (el.tabIndex > -1) {
el.dataset.oldTabIndex = el.tabIndex;
}
el.tabIndex = "-1";
} else if (el.dataset.oldTabIndex) {
el.tabIndex = el.dataset.oldTabIndex;
delete el.dataset.oldTabIndex;
} else {
el.removeAttribute("tabindex");
}
});
}
hide() {
this.setKeyboardAccessForElementsExternalToDialog(true);
this._cancelButton.removeEventListener("click", this);
this._confirmButton.removeEventListener("click", this);
this._dismissButton.removeEventListener("click", this);
@ -55,18 +85,24 @@ export default class ConfirmDeleteDialog extends HTMLElement {
this.hidden = true;
}
show() {
show({ title, message, confirmButtonLabel }) {
this.setKeyboardAccessForElementsExternalToDialog(false);
this.hidden = false;
document.l10n.setAttributes(this._title, title);
document.l10n.setAttributes(this._message, message);
document.l10n.setAttributes(this._confirmButton, confirmButtonLabel);
this._cancelButton.addEventListener("click", this);
this._confirmButton.addEventListener("click", this);
this._dismissButton.addEventListener("click", this);
this._overlay.addEventListener("click", this);
window.addEventListener("keydown", this);
// For accessibility, focus the least destructive action button when the
// dialog loads.
this._cancelButton.focus();
// For speed-of-use, focus the confirm button when the
// dialog loads. Showing the dialog itself provides enough
// of a buffer for accidental deletions.
this._confirmButton.focus();
this._promise = new Promise((resolve, reject) => {
this._resolve = resolve;
@ -86,4 +122,4 @@ export default class ConfirmDeleteDialog extends HTMLElement {
this.hide();
}
}
customElements.define("confirm-delete-dialog", ConfirmDeleteDialog);
customElements.define("confirmation-dialog", ConfirmationDialog);

View File

@ -2,25 +2,31 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
:host {
display: flex;
}
.filter[type="text"] {
-moz-context-properties: fill, fill-opacity;
fill: currentColor;
fill-opacity: 0.4;
background-image: url("chrome://global/skin/icons/search.svg");
background-position: 4px 50%;
background-position: 8px center;
background-repeat: no-repeat;
background-size: 16px;
flex: auto;
width: 100%;
margin: 0;
padding: 4px 0;
padding-inline-start: 28px;
padding-block: 6px;
/* We use separate RTL rules over logical properties since we want the visual direction
to be independent from the user input direction */
padding-left: 32px;
text-align: left;
box-sizing: border-box;
}
.filter:focus-within {
fill-opacity: 0.9;
:host(:dir(rtl)) .filter {
background-position-x: right 8px;
padding-right: 32px;
text-align: right;
}
.filter:focus {
fill-opacity: 0.8;
}

View File

@ -3,25 +3,30 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
:host {
padding: 18px;
padding: 40px;
--reveal-checkbox-opacity: .8;
--reveal-checkbox-opacity-hover: .6;
--reveal-checkbox-opacity-active: 1;
--success-color: #00c100;
--edit-delete-button-color: #4a4a4f;
}
@media (prefers-color-scheme: dark) {
:host {
--reveal-checkbox-opacity: .8;
--reveal-checkbox-opacity-hover: 1;
--reveal-checkbox-opacity-active: .6;
--success-color: #86DE74;
--edit-delete-button-color: #cfcfd1;
}
}
:host([data-editing]) .edit-button,
:host([data-editing]) .copy-button,
:host([data-editing]) .open-site-button,
:host([data-is-new-login]) .delete-button,
:host([data-is-new-login]) .origin-saved-value,
:host([data-is-new-login]) copy-to-clipboard-button,
:host([data-is-new-login]) .open-site-button,
:host([data-is-new-login]) .meta-info,
:host([data-is-new-login]) .login-item-title,
:host(:not([data-is-new-login])) .new-login-title,
@ -30,28 +35,37 @@
display: none;
}
:host(:not([data-editing])) input[type="password"],
:host(:not([data-editing])) input[type="text"],
:host(:not([data-editing])) input[type="url"] {
input[type="password"][readOnly],
input[type="text"][readOnly],
input[type="url"][readOnly] {
all: unset;
font-size: 1.1em;
display: inline-block;
width: -moz-available;
background-color: transparent !important; /* override common.inc.css */
}
.detail-cell input:not([type="checkbox"]),
.save-changes-button {
margin-inline-start: 0; /* align all elements on the start side */
}
.header {
display: flex;
border-bottom: 1px solid var(--in-content-box-border-color);
margin-bottom: 40px;
}
.title {
margin-top: 0;
margin-bottom: 0;
flex: auto;
margin-block: 0;
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.delete-button,
.edit-button {
color: var(--edit-delete-button-color) !important;
background-repeat: no-repeat;
background-position: 8px;
-moz-context-properties: fill;
@ -61,7 +75,7 @@
.delete-button:dir(rtl),
.edit-button:dir(rtl) {
background-position: right 8px center;
background-position-x: right 8px;
}
.delete-button {
@ -74,36 +88,33 @@
padding-inline-start: 32px; /* 8px on each side, and 16px for icon width */
}
:host(:not([data-editing])) input[type="url"] {
input[type="url"][readOnly] {
color: var(--in-content-link-color) !important;
cursor: pointer;
}
:host(:not([data-editing])) input[type="url"]:hover {
input[type="url"][readOnly]:hover {
color: var(--in-content-link-color-hover) !important;
text-decoration: underline;
}
:host(:not([data-editing])) input[type="url"]:hover:active {
input[type="url"][readOnly]:hover:active {
color: var(--in-content-link-color-active) !important;
}
.detail-row,
.reveal-password-wrapper {
display: flex;
align-items: center;
}
.detail-row {
margin-bottom: 20px;
display: flex;
align-items: end;
margin-bottom: 40px;
}
.detail-cell {
flex: auto;
}
.detail-row > button {
align-self: end;
flex-grow: 1;
}
.field-label {
@ -113,6 +124,10 @@
margin-bottom: 5px;
}
:host([data-editing]) .detail-cell input:not([type="checkbox"]) {
width: 280px;
}
.copy-button:not([data-copied]) .copied-button-text,
.copy-button[data-copied] .copy-button-text {
display: none;
@ -129,8 +144,17 @@
box-shadow: none;
}
.copied-button-text {
background-image: url(chrome://global/skin/icons/check.svg);
background-repeat: no-repeat;
-moz-context-properties: fill;
fill: currentColor;
padding-inline-start: 22px;
}
.meta-info {
font-size: smaller;
color: var(--in-content-deemphasized-text);
}
.meta-info:first-of-type {
@ -178,11 +202,3 @@
-moz-outline-radius: 3px;
box-shadow: 0 0 0 4px var(--in-content-border-active-shadow);
}
@media (prefers-color-scheme: dark) {
:host {
--reveal-checkbox-opacity: .8;
--reveal-checkbox-opacity-hover: 1;
--reveal-checkbox-opacity-active: .6;
}
}

View File

@ -65,16 +65,16 @@ export default class LoginItem extends HTMLElement {
this._copyUsernameButton.addEventListener("click", this);
this._deleteButton.addEventListener("click", this);
this._editButton.addEventListener("click", this);
this._form.addEventListener("submit", this);
this._openSiteButton.addEventListener("click", this);
this._originInput.addEventListener("click", this);
this._revealCheckbox.addEventListener("click", this);
this._saveChangesButton.addEventListener("click", this);
window.addEventListener("AboutLoginsCreateLogin", this);
window.addEventListener("AboutLoginsInitialLoginSelected", this);
window.addEventListener("AboutLoginsLoginSelected", this);
}
render() {
async render() {
document.l10n.setAttributes(this._timeCreated, "login-item-time-created", {
timeCreated: this._login.timeCreated || "",
});
@ -89,10 +89,16 @@ export default class LoginItem extends HTMLElement {
this._originInput.defaultValue = this._login.origin || "";
this._usernameInput.defaultValue = this._login.username || "";
this._passwordInput.defaultValue = this._login.password || "";
this._updatePasswordRevealState();
document.l10n.setAttributes(
this._saveChangesButton,
this.dataset.isNewLogin
? "login-item-save-new-button"
: "login-item-save-changes-button"
);
await this._updatePasswordRevealState();
}
handleEvent(event) {
async handleEvent(event) {
switch (event.type) {
case "AboutLoginsCreateLogin": {
this.setLogin({});
@ -103,7 +109,23 @@ export default class LoginItem extends HTMLElement {
break;
}
case "AboutLoginsLoginSelected": {
this.setLogin(event.detail);
let login = event.detail;
if (this.hasPendingChanges()) {
event.preventDefault();
this.showConfirmationDialog("discard-changes", () => {
// Clear any pending changes
this.setLogin(login);
window.dispatchEvent(
new CustomEvent("AboutLoginsLoginSelected", {
detail: login,
cancelable: true,
})
);
});
} else {
this.setLogin(login);
}
break;
}
case "blur": {
@ -120,18 +142,22 @@ export default class LoginItem extends HTMLElement {
case "click": {
let classList = event.currentTarget.classList;
if (classList.contains("reveal-password-checkbox")) {
this._updatePasswordRevealState();
await this._updatePasswordRevealState();
let method = this._revealCheckbox.checked ? "show" : "hide";
return;
}
// Prevent form submit behavior on the following buttons.
event.preventDefault();
if (classList.contains("cancel-button")) {
let wasExistingLogin = !!this._login.guid;
if (wasExistingLogin) {
this.setLogin(this._login);
if (this.hasPendingChanges()) {
this.showConfirmationDialog("discard-changes", () => {
this.setLogin(this._login);
});
} else {
this.setLogin(this._login);
}
} else {
window.dispatchEvent(new CustomEvent("AboutLoginsClearSelection"));
}
@ -142,18 +168,30 @@ export default class LoginItem extends HTMLElement {
classList.contains("copy-username-button")
) {
let copyButton = event.currentTarget;
if (copyButton.dataset.copyLoginProperty == "password") {
let masterPasswordAuth = await new Promise(resolve => {
window.AboutLoginsUtils.promptForMasterPassword(resolve);
});
if (!masterPasswordAuth) {
return;
}
}
copyButton.disabled = true;
let propertyToCopy = copyButton.dataset.copyLoginProperty;
navigator.clipboard.writeText(this._login[propertyToCopy]).then(
() => {
copyButton.dataset.copied = true;
setTimeout(() => {
copyButton.disabled = false;
delete copyButton.dataset.copied;
}, LoginItem.COPY_BUTTON_RESET_TIMEOUT);
},
() => (copyButton.disabled = false)
copyButton.dataset.copied = true;
let propertyToCopy = this._login[
copyButton.dataset.copyLoginProperty
];
document.dispatchEvent(
new CustomEvent("AboutLoginsCopyLoginDetail", {
bubbles: true,
detail: propertyToCopy,
})
);
setTimeout(() => {
copyButton.disabled = false;
delete copyButton.dataset.copied;
}, LoginItem.COPY_BUTTON_RESET_TIMEOUT);
return;
}
if (classList.contains("delete-button")) {
@ -166,7 +204,7 @@ export default class LoginItem extends HTMLElement {
}
if (
classList.contains("open-site-button") ||
(classList.contains("origin-input") && !this.dataset.editing)
(classList.contains("origin-input") && !this.readOnly)
) {
document.dispatchEvent(
new CustomEvent("AboutLoginsOpenSite", {
@ -174,52 +212,91 @@ export default class LoginItem extends HTMLElement {
detail: this._login,
})
);
return;
}
if (classList.contains("save-changes-button")) {
if (!this._isFormValid({ reportErrors: true })) {
return;
}
let loginUpdates = this._loginFromForm();
if (this._login.guid) {
loginUpdates.guid = this._login.guid;
document.dispatchEvent(
new CustomEvent("AboutLoginsUpdateLogin", {
bubbles: true,
detail: loginUpdates,
})
);
} else {
document.dispatchEvent(
new CustomEvent("AboutLoginsCreateLogin", {
bubbles: true,
detail: loginUpdates,
})
);
}
}
break;
}
case "submit": {
// Prevent page navigation form submit behavior.
event.preventDefault();
if (!this._isFormValid({ reportErrors: true })) {
return;
}
let loginUpdates = this._loginFromForm();
if (this._login.guid) {
loginUpdates.guid = this._login.guid;
document.dispatchEvent(
new CustomEvent("AboutLoginsUpdateLogin", {
bubbles: true,
detail: loginUpdates,
})
);
} else {
document.dispatchEvent(
new CustomEvent("AboutLoginsCreateLogin", {
bubbles: true,
detail: loginUpdates,
})
);
}
}
}
}
/**
* Shows a confirmation dialog.
* @param {string} type The type of confirmation dialog to display.
* @param {boolean} onConfirm Optional, the function to execute when the confirm button is clicked.
*/
showConfirmationDialog(type, onConfirm = () => {}) {
const dialog = document.querySelector("confirmation-dialog");
let options;
switch (type) {
case "delete": {
options = {
title: "confirm-delete-dialog-title",
message: "confirm-delete-dialog-message",
confirmButtonLabel: "confirm-delete-dialog-confirm-button",
};
break;
}
case "discard-changes": {
options = {
title: "confirm-discard-changes-dialog-title",
message: "confirm-discard-changes-dialog-message",
confirmButtonLabel: "confirm-discard-changes-dialog-confirm-button",
};
break;
}
}
let dialogPromise = dialog.show(options);
dialogPromise.then(onConfirm, () => {});
return dialogPromise;
}
/**
* Toggles the confirm delete dialog, completing the deletion if the user
* agrees.
*/
confirmDelete() {
const dialog = document.querySelector("confirm-delete-dialog");
dialog.show().then(
() => {
document.dispatchEvent(
new CustomEvent("AboutLoginsDeleteLogin", {
bubbles: true,
detail: this._login,
})
);
},
() => {}
this.showConfirmationDialog("delete", () => {
document.dispatchEvent(
new CustomEvent("AboutLoginsDeleteLogin", {
bubbles: true,
detail: this._login,
})
);
});
}
hasPendingChanges() {
let { origin = "", username = "", password = "" } = this._login || {};
let valuesChanged = !window.AboutLoginsUtils.doLoginsMatch(
{ origin, username, password },
this._loginFromForm()
);
return this.dataset.editing && valuesChanged;
}
/**
@ -361,7 +438,7 @@ export default class LoginItem extends HTMLElement {
this._deleteButton.disabled = this.dataset.isNewLogin;
this._editButton.disabled = shouldEdit;
let inputTabIndex = !shouldEdit ? -1 : 0;
this._originInput.readOnly = !shouldEdit;
this._originInput.readOnly = !this.dataset.isNewLogin;
this._originInput.tabIndex = inputTabIndex;
this._usernameInput.readOnly = !shouldEdit;
this._usernameInput.tabIndex = inputTabIndex;
@ -375,7 +452,17 @@ export default class LoginItem extends HTMLElement {
}
}
_updatePasswordRevealState() {
async _updatePasswordRevealState() {
if (this._revealCheckbox.checked) {
let masterPasswordAuth = await new Promise(resolve => {
window.AboutLoginsUtils.promptForMasterPassword(resolve);
});
if (!masterPasswordAuth) {
this._revealCheckbox.checked = false;
return;
}
}
let titleId = this._revealCheckbox.checked
? "login-item-password-reveal-checkbox-hide"
: "login-item-password-reveal-checkbox-show";

View File

@ -10,16 +10,18 @@
*/
export default class LoginListItemFactory {
static create(login) {
let loginListItemTemplate = document.querySelector(
"#login-list-item-template"
);
let loginListItem = loginListItemTemplate.content.cloneNode(true);
let listItem = loginListItem.querySelector("li");
let title = loginListItem.querySelector(".title");
let username = loginListItem.querySelector(".username");
let template = document.querySelector("#login-list-item-template");
let fragment = template.content.cloneNode(true);
let listItem = fragment.firstElementChild;
listItem.setAttribute("role", "option");
LoginListItemFactory.update(listItem, login);
return listItem;
}
static update(listItem, login) {
let title = listItem.querySelector(".title");
let username = listItem.querySelector(".username");
if (!login.guid) {
listItem.id = "new-login-list-item";
document.l10n.setAttributes(title, "login-list-item-title-new-login");
@ -27,24 +29,30 @@ export default class LoginListItemFactory {
username,
"login-list-item-subtitle-new-login"
);
return listItem;
return;
}
// Prepend the ID with a string since IDs must not begin with a number.
listItem.id = "lli-" + login.guid;
listItem.dataset.guid = login.guid;
if (!listItem.id) {
listItem.id = "lli-" + login.guid;
listItem.dataset.guid = login.guid;
}
listItem._login = login;
title.textContent = login.title;
if (login.username.trim()) {
username.removeAttribute("data-l10n-id");
username.textContent = login.username.trim();
if (title.textContent != login.title) {
title.textContent = login.title;
}
let trimmedUsernameValue = login.username.trim();
if (trimmedUsernameValue) {
if (username.textContent != trimmedUsernameValue) {
username.removeAttribute("data-l10n-id");
username.textContent = trimmedUsernameValue;
}
} else {
document.l10n.setAttributes(
username,
"login-list-item-subtitle-missing-username"
);
}
return listItem;
}
}

View File

@ -13,13 +13,26 @@
.meta {
display: flex;
align-items: center;
padding: 10px 18px;
padding: 5px 18px;
border-bottom: 1px solid var(--in-content-box-border-color);
background-color: var(--in-content-box-info-background);
background-color: var(--in-content-box-background);
color: var(--in-content-deemphasized-text);
}
#login-sort {
min-height: initial;
line-height: 1.1;
font: inherit;
font-weight: 600;
color: var(--in-content-text-color) !important;
}
#login-sort > option {
font-weight: normal;
}
.count {
flex: auto;
flex-grow: 1;
text-align: end;
font-size: smaller;
margin-inline-start: 18px;
@ -35,7 +48,7 @@ ol {
}
.create-login-button {
margin: 18px;
margin: 16px;
}
.login-list-item {
@ -65,14 +78,23 @@ ol {
background-color: var(--in-content-box-background-hover);
}
.title {
font-weight: bold;
.login-list-item.selected .title {
font-weight: 600;
}
.labels {
flex-grow: 1;
overflow: hidden;
}
.title,
.username {
display: block;
max-width: 50ch;
text-overflow: ellipsis;
overflow: hidden;
}
.username {
font-size: 0.85em;
color: var(--in-content-deemphasized-text);
}

View File

@ -14,7 +14,10 @@ const sortFnOptions = {
export default class LoginList extends HTMLElement {
constructor() {
super();
this._logins = [];
// An array of login GUIDs, stored in sorted order.
this._loginGuidsSortedOrder = [];
// A map of login GUID -> {login, listItem}.
this._logins = {};
this._filter = "";
this._selectedGuid = null;
this._blankLoginListItem = LoginListItemFactory.create({});
@ -32,6 +35,7 @@ export default class LoginList extends HTMLElement {
this._count = shadowRoot.querySelector(".count");
this._createLoginButton = shadowRoot.querySelector(".create-login-button");
this._list = shadowRoot.querySelector("ol");
this._list.appendChild(this._blankLoginListItem);
this._sortSelect = shadowRoot.querySelector("#login-sort");
this.render();
@ -48,63 +52,45 @@ export default class LoginList extends HTMLElement {
this._createLoginButton.addEventListener("click", this);
}
/**
*
* @param {object} options optional
* createLogin: When set to true will show and select
* a blank login-list-item.
*/
async render(options = {}) {
this._list.textContent = "";
if (options.createLogin) {
this._blankLoginListItem.classList.add("selected");
this._blankLoginListItem.setAttribute("aria-selected", "true");
this._list.setAttribute(
"aria-activedescendant",
this._blankLoginListItem.id
);
this._list.appendChild(this._blankLoginListItem);
} else {
this._blankLoginListItem.remove();
}
if (!this._logins.length) {
document.l10n.setAttributes(this._count, "login-list-count", {
count: 0,
});
return;
}
let visibleLogins = this._applyFilter();
document.l10n.setAttributes(this._count, "login-list-count", {
count: visibleLogins.length,
});
async render() {
let visibleLoginGuids = this._applyFilter();
this._updateVisibleLoginCount(visibleLoginGuids.size);
// Add all of the logins that are not in the DOM yet.
let fragment = document.createDocumentFragment();
let chunkSize = 5;
for (let i = 0; i < this._logins.length; i++) {
let login = this._logins[i];
for (let guid of this._loginGuidsSortedOrder) {
if (this._logins[guid].listItem) {
continue;
}
let login = this._logins[guid].login;
let listItem = LoginListItemFactory.create(login);
if (login.guid == this._selectedGuid) {
this._logins[login.guid] = Object.assign(this._logins[login.guid], {
listItem,
});
fragment.appendChild(listItem);
}
this._list.appendChild(fragment);
// Show, hide, and update state of the list items per the applied search filter.
for (let guid of this._loginGuidsSortedOrder) {
let { listItem } = this._logins[guid];
if (guid == this._selectedGuid) {
this._setListItemAsSelected(listItem);
}
if (!visibleLogins.includes(login.guid)) {
listItem.hidden = true;
}
fragment.appendChild(listItem);
// Display a first chunk of logins ASAP to improve perceived performance,
// then append progressively larger chunks until complete.
if (i == chunkSize) {
this._list.appendChild(fragment);
await new Promise(resolve => requestAnimationFrame(resolve));
chunkSize *= chunkSize;
}
listItem.hidden = !visibleLoginGuids.has(listItem.dataset.guid);
}
this._blankLoginListItem.hidden = this._selectedGuid != null;
// Re-arrange the login-list-items according to their sort
for (let i = this._loginGuidsSortedOrder.length - 1; i >= 0; i--) {
let guid = this._loginGuidsSortedOrder[i];
let { listItem } = this._logins[guid];
this._list.insertBefore(
listItem,
this._blankLoginListItem.nextElementSibling
);
}
this._list.appendChild(fragment);
}
handleEvent(event) {
@ -115,8 +101,8 @@ export default class LoginList extends HTMLElement {
return;
}
let loginListItem = event.originalTarget.closest(".login-list-item");
if (!loginListItem || !loginListItem.dataset.guid) {
let listItem = event.originalTarget.closest(".login-list-item");
if (!listItem || !listItem.dataset.guid) {
return;
}
@ -124,31 +110,32 @@ export default class LoginList extends HTMLElement {
new CustomEvent("AboutLoginsLoginSelected", {
bubbles: true,
composed: true,
detail: loginListItem._login,
cancelable: true, // allow calling preventDefault() on event
detail: listItem._login,
})
);
break;
}
case "change": {
const sort = this._sortSelect.value;
this._logins = this._logins.sort((a, b) => sortFnOptions[sort](a, b));
this._applySort();
this.render();
break;
}
case "AboutLoginsClearSelection": {
if (!this._logins.length) {
if (!this._loginGuidsSortedOrder.length) {
return;
}
window.dispatchEvent(
new CustomEvent("AboutLoginsLoginSelected", {
detail: this._logins[0],
cancelable: true,
})
);
break;
}
case "AboutLoginsCreateLogin": {
this._selectedGuid = null;
this.render({ createLogin: true });
this._setListItemAsSelected(this._blankLoginListItem);
break;
}
case "AboutLoginsFilterLogins": {
@ -157,7 +144,7 @@ export default class LoginList extends HTMLElement {
break;
}
case "AboutLoginsLoginSelected": {
if (this._selectedGuid == event.detail.guid) {
if (event.defaultPrevented || this._selectedGuid == event.detail.guid) {
return;
}
@ -182,16 +169,18 @@ export default class LoginList extends HTMLElement {
* @param {login[]} logins An array of logins used for displaying in the list.
*/
setLogins(logins) {
this._logins = logins;
const sort = this._sortSelect.value;
this._logins = this._logins.sort((a, b) => sortFnOptions[sort](a, b));
this._loginGuidsSortedOrder = [];
this._logins = logins.reduce((map, login) => {
this._loginGuidsSortedOrder.push(login.guid);
map[login.guid] = { login };
return map;
}, {});
this._applySort();
this._list.textContent = "";
this._list.appendChild(this._blankLoginListItem);
this.render();
if (
!this._selectedGuid ||
!this._logins.findIndex(login => login.guid == this._selectedGuid) != -1
) {
if (!this._selectedGuid || !this._logins[this._selectedGuid]) {
// Select the first visible login after any possible filter is applied.
let firstVisibleListItem = this._list.querySelector(
".login-list-item[data-guid]:not([hidden])"
@ -212,56 +201,106 @@ export default class LoginList extends HTMLElement {
* @param {login} login A login that was added to storage.
*/
loginAdded(login) {
this._logins.push(login);
this._logins[login.guid] = { login };
this._loginGuidsSortedOrder.push(login.guid);
this._applySort();
// Add the list item and update any other related state that may pertain
// to the list item such as breach alerts.
this.render();
}
/**
* @param {login} login A login that was modified in storage. The related login-list-item
* will get updated.
* @param {login} login A login that was modified in storage. The related
* login-list-item will get updated.
*/
loginModified(login) {
for (let i = 0; i < this._logins.length; i++) {
if (this._logins[i].guid == login.guid) {
this._logins[i] = login;
break;
}
}
this._logins[login.guid] = Object.assign(this._logins[login.guid], {
login,
});
this._applySort();
let { listItem } = this._logins[login.guid];
LoginListItemFactory.update(listItem, login);
// Update any other related state that may pertain to the list item
// such as breach alerts that may or may not now apply.
this.render();
}
/**
* @param {login} login A login that was removed from storage. The related login-list-item
* will get removed. The login object is a plain JS object
* representation of nsILoginInfo/nsILoginMetaInfo.
* @param {login} login A login that was removed from storage. The related
* login-list-item will get removed. The login object
* is a plain JS object representation of
* nsILoginInfo/nsILoginMetaInfo.
*/
loginRemoved(login) {
this._logins = this._logins.filter(l => l.guid != login.guid);
this.render();
this._logins[login.guid].listItem.remove();
// Update the selected list item to the previous item in the list
// if one exists, otherwise the next item. If no logins remain
// the login-intro text will be shown instead of the login-list.
if (this._selectedGuid == login.guid) {
let index = this._loginGuidsSortedOrder.indexOf(login.guid);
if (this._loginGuidsSortedOrder.length > 1) {
let newlySelectedIndex = index > 0 ? index - 1 : index + 1;
let newlySelectedListItem = this._logins[
this._loginGuidsSortedOrder[newlySelectedIndex]
].listItem;
this._setListItemAsSelected(newlySelectedListItem);
}
}
delete this._logins[login.guid];
this._loginGuidsSortedOrder = this._loginGuidsSortedOrder.filter(guid => {
return guid != login.guid;
});
let visibleLoginGuids = this._applyFilter();
this._updateVisibleLoginCount(visibleLoginGuids.size);
// Since the login has been removed, we don't need to call render
// as nothing related to the login needs updating.
}
/**
* Filters the displayed logins in the list to only those matching the
* cached filter value.
* @returns {Set} Set of login guids that match the filter.
*/
_applyFilter() {
let matchingLoginGuids;
if (this._filter) {
matchingLoginGuids = this._logins
.filter(login => {
matchingLoginGuids = new Set(
this._loginGuidsSortedOrder.filter(guid => {
let { login } = this._logins[guid];
return (
login.origin.toLocaleLowerCase().includes(this._filter) ||
login.username.toLocaleLowerCase().includes(this._filter)
);
})
.map(login => login.guid);
);
} else {
matchingLoginGuids = this._logins.map(login => login.guid);
matchingLoginGuids = new Set([...this._loginGuidsSortedOrder]);
}
return matchingLoginGuids;
}
_applySort() {
const sort = this._sortSelect.value;
this._loginGuidsSortedOrder = this._loginGuidsSortedOrder.sort((a, b) => {
let loginA = this._logins[a].login;
let loginB = this._logins[b].login;
return sortFnOptions[sort](loginA, loginB);
});
}
_updateVisibleLoginCount(count) {
if (count != document.l10n.getAttributes(this._count).args.count) {
document.l10n.setAttributes(this._count, "login-list-count", {
count,
});
}
}
_handleKeyboardNav(event) {
if (
this._createLoginButton == this.shadowRoot.activeElement &&
@ -349,9 +388,7 @@ export default class LoginList extends HTMLElement {
oldSelectedItem.classList.remove("selected");
oldSelectedItem.removeAttribute("aria-selected");
}
if (listItem.dataset.guid) {
this._blankLoginListItem.remove();
}
this._blankLoginListItem.hidden = !!listItem.dataset.guid;
listItem.classList.add("selected");
listItem.setAttribute("aria-selected", "true");
this._list.setAttribute("aria-activedescendant", listItem.id);

View File

@ -14,49 +14,50 @@
fill: currentColor;
width: 30px;
min-width: 30px;
margin-inline-start: 0;
margin-inline-end: 0;
margin-inline: 0;
}
.menu {
position: absolute;
top: 30px;
right: 0;
inset-inline-end: 0;
margin: 0;
padding: 5px 0;
background-color: var(--in-content-box-background);
border: 1px solid var(--in-content-box-border-color);
border-radius: 2px;
box-shadow: var(--shadow-10);
border-radius: 5px;
box-shadow: var(--shadow-30);
min-width: max-content;
list-style-type: none;
display: flex;
flex-direction: column;
}
.menu:dir(rtl) {
right: auto;
left: 0;
}
.menuitem-button {
padding: 4px 8px;
padding: 2px 8px;
/* 32px = 8px (padding) + 16px (icon) + 8px (padding) */
padding-inline-start: 32px;
background-repeat: no-repeat;
background-position: left 8px center;
background-size: 16px;
margin: 0;
border-radius: 0;
/* Override common.inc.css box-shadow on these buttons */
box-shadow: none !important;
text-align: start;
-moz-context-properties: fill;
fill: currentColor;
/* Override common.inc.css properties */
margin: 0;
border-radius: 0;
text-align: start;
min-height: initial;
}
.menuitem-button:dir(rtl) {
background-position: right 8px center;
background-position-x: right 8px;
}
.menuitem-separator {
border-top-width: 2px;
margin-block: 5px;
width: 100%;
}
.menuitem-import {
@ -66,3 +67,4 @@
.menuitem-preferences {
background-image: url("chrome://browser/skin/settings.svg");
}

View File

@ -3,8 +3,8 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
browser.jar:
content/browser/aboutlogins/components/confirm-delete-dialog.css (content/components/confirm-delete-dialog.css)
content/browser/aboutlogins/components/confirm-delete-dialog.js (content/components/confirm-delete-dialog.js)
content/browser/aboutlogins/components/confirmation-dialog.css (content/components/confirmation-dialog.css)
content/browser/aboutlogins/components/confirmation-dialog.js (content/components/confirmation-dialog.js)
content/browser/aboutlogins/components/login-filter.css (content/components/login-filter.css)
content/browser/aboutlogins/components/login-filter.js (content/components/login-filter.js)
content/browser/aboutlogins/components/login-item.css (content/components/login-item.css)

View File

@ -5,7 +5,7 @@
JAR_MANIFESTS += ['jar.mn']
with Files('**'):
BUG_COMPONENT = ('Toolkit', 'Password Manager')
BUG_COMPONENT = ('Firefox', 'about:logins')
EXTRA_JS_MODULES += [
'AboutLoginsParent.jsm',

View File

@ -11,6 +11,7 @@ support-files =
[browser_deleteLogin.js]
[browser_loginListChanges.js]
[browser_masterPassword.js]
skip-if = (os == 'linux') # bug 1569789
[browser_openFiltered.js]
[browser_openImport.js]
skip-if = (os != "win") # import is only available on Windows

View File

@ -15,10 +15,13 @@ add_task(async function test() {
let browser = gBrowser.selectedBrowser;
await ContentTask.spawn(browser, null, async () => {
let dialog = Cu.waiveXrays(
content.document.querySelector("confirm-delete-dialog")
);
let loginItem = Cu.waiveXrays(content.document.querySelector("login-item"));
let showPromise = loginItem.showConfirmationDialog("delete");
let dialog = Cu.waiveXrays(
content.document.querySelector("confirmation-dialog")
);
let cancelButton = dialog.shadowRoot.querySelector(".cancel-button");
let confirmDeleteButton = dialog.shadowRoot.querySelector(
".confirm-button"
@ -27,14 +30,20 @@ add_task(async function test() {
let message = dialog.shadowRoot.querySelector(".message");
let title = dialog.shadowRoot.querySelector(".title");
await content.document.l10n.translateElements([
title,
message,
confirmDeleteButton,
]);
is(
title.textContent,
"Confirm Deletion",
"Delete this login?",
"Title contents should match l10n attribute set on outer element"
);
is(
message.textContent,
"Are you sure you want to delete this login?",
"This action cannot be undone.",
"Message contents should match l10n attribute set on outer element"
);
is(
@ -44,11 +53,10 @@ add_task(async function test() {
);
is(
confirmDeleteButton.textContent,
"Delete login",
"Delete",
"Delete button contents should match l10n attribute set on outer element"
);
let showPromise = dialog.show();
cancelButton.click();
try {
await showPromise;
@ -68,7 +76,7 @@ add_task(async function test() {
);
ok(dialog.hidden, "Dialog should be hidden after clicking cancel button");
showPromise = dialog.show();
showPromise = loginItem.showConfirmationDialog("delete");
dismissButton.click();
try {
await showPromise;
@ -88,7 +96,7 @@ add_task(async function test() {
);
ok(dialog.hidden, "Dialog should be hidden after clicking dismiss button");
showPromise = dialog.show();
showPromise = loginItem.showConfirmationDialog("delete");
confirmDeleteButton.click();
try {
await showPromise;

View File

@ -81,13 +81,13 @@ add_task(async function test_create_login() {
content.document.querySelector("login-list")
);
let loginFound = await ContentTaskUtils.waitForCondition(() => {
return loginList._logins.length == aExpectedCount;
return loginList._loginGuidsSortedOrder.length == aExpectedCount;
}, "Waiting for login to be displayed");
ok(loginFound, "Expected number of logins found in login-list");
let loginListItem = [
...loginList.shadowRoot.querySelectorAll(".login-list-item"),
].find(l => l._login.origin == aOriginTuple[1]);
].find(l => l._login && l._login.origin == aOriginTuple[1]);
ok(
!!loginListItem,
`Stored login should only include the origin of the URL provided during creation (${
@ -141,7 +141,9 @@ add_task(async function test_create_login() {
let loginList = Cu.waiveXrays(
content.document.querySelector("login-list")
);
let login = loginList._logins.find(l => l.origin == aOriginTuple[1]);
let login = Object.values(loginList._logins).find(
obj => obj.login.origin == aOriginTuple[1]
).login;
ok(
!!login,
"Stored login should only include the origin of the URL provided during creation"

View File

@ -20,9 +20,9 @@ add_task(async function test_show_logins() {
let loginList = Cu.waiveXrays(content.document.querySelector("login-list"));
let loginFound = await ContentTaskUtils.waitForCondition(() => {
return (
loginList._logins.length == 2 &&
loginList._logins[0].guid == logins[0].guid &&
loginList._logins[1].guid == logins[1].guid
loginList._loginGuidsSortedOrder.length == 2 &&
loginList._loginGuidsSortedOrder.includes(logins[0].guid) &&
loginList._loginGuidsSortedOrder.includes(logins[1].guid)
);
}, "Waiting for logins to be displayed");
ok(loginFound, "Newly added logins should be added to the page");
@ -60,7 +60,7 @@ add_task(async function test_login_item() {
deleteButton.click();
let confirmDeleteDialog = Cu.waiveXrays(
content.document.querySelector("confirm-delete-dialog")
content.document.querySelector("confirmation-dialog")
);
let confirmButton = confirmDeleteDialog.shadowRoot.querySelector(
".confirm-button"

View File

@ -25,8 +25,8 @@ add_task(async function test_login_added() {
let loginList = Cu.waiveXrays(content.document.querySelector("login-list"));
let loginFound = await ContentTaskUtils.waitForCondition(() => {
return (
loginList._logins.length == 1 &&
loginList._logins[0].guid == addedLogin.guid
loginList._loginGuidsSortedOrder.length == 1 &&
loginList._loginGuidsSortedOrder[0] == addedLogin.guid
);
}, "Waiting for login to be added");
ok(loginFound, "Newly added logins should be added to the page");
@ -47,9 +47,10 @@ add_task(async function test_login_modified() {
let loginList = Cu.waiveXrays(content.document.querySelector("login-list"));
let loginFound = await ContentTaskUtils.waitForCondition(() => {
return (
loginList._logins.length == 1 &&
loginList._logins[0].guid == modifiedLogin.guid &&
loginList._logins[0].username == modifiedLogin.username
loginList._loginGuidsSortedOrder.length == 1 &&
loginList._loginGuidsSortedOrder[0] == modifiedLogin.guid &&
loginList._logins[loginList._loginGuidsSortedOrder[0]].login.username ==
modifiedLogin.username
);
}, "Waiting for username to get updated");
ok(loginFound, "The login should get updated on the page");
@ -69,7 +70,7 @@ add_task(async function test_login_removed() {
await ContentTask.spawn(browser, login, async removedLogin => {
let loginList = Cu.waiveXrays(content.document.querySelector("login-list"));
let loginRemoved = await ContentTaskUtils.waitForCondition(() => {
return loginList._logins.length == 0;
return loginList._loginGuidsSortedOrder.length == 0;
}, "Waiting for login to get removed");
ok(loginRemoved, "The login should be removed from the page");
});

View File

@ -33,9 +33,9 @@ function waitForLoginCountToReach(browser, loginCount) {
return ContentTask.spawn(browser, loginCount, async expectedLoginCount => {
let loginList = Cu.waiveXrays(content.document.querySelector("login-list"));
await ContentTaskUtils.waitForCondition(() => {
return loginList._logins.length == expectedLoginCount;
return loginList._loginGuidsSortedOrder.length == expectedLoginCount;
});
return loginList._logins.length;
return loginList._loginGuidsSortedOrder.length;
});
}
@ -93,4 +93,79 @@ add_task(async function test() {
logins = await waitForLoginCountToReach(browser, 1);
is(logins, 1, "Logins should be displayed when MP is set and authenticated");
// Show MP dialog when Copy Password button clicked
mpDialogShown = waitForMPDialog("cancel");
await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
let loginItem = content.document.querySelector("login-item");
let copyButton = loginItem.shadowRoot.querySelector(
".copy-password-button"
);
copyButton.click();
});
await mpDialogShown;
info("Master Password dialog shown and canceled");
mpDialogShown = waitForMPDialog("authenticate");
info("Clicking copy password button again");
await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
let loginItem = content.document.querySelector("login-item");
let copyButton = loginItem.shadowRoot.querySelector(
".copy-password-button"
);
copyButton.click();
});
await mpDialogShown;
info("Master Password dialog shown and authenticated");
await ContentTask.spawn(browser, null, async function() {
let loginItem = content.document.querySelector("login-item");
let copyButton = loginItem.shadowRoot.querySelector(
".copy-password-button"
);
await ContentTaskUtils.waitForCondition(() => {
return copyButton.disabled;
}, "Waiting for copy button to be disabled");
info("Password was copied to clipboard");
});
// Show MP dialog when Reveal Password checkbox is checked
mpDialogShown = waitForMPDialog("cancel");
await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
let loginItem = content.document.querySelector("login-item");
let revealCheckbox = loginItem.shadowRoot.querySelector(
".reveal-password-checkbox"
);
revealCheckbox.click();
});
await mpDialogShown;
info("Master Password dialog shown and canceled");
await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
let loginItem = content.document.querySelector("login-item");
let revealCheckbox = loginItem.shadowRoot.querySelector(
".reveal-password-checkbox"
);
ok(
!revealCheckbox.checked,
"reveal checkbox should be unchecked if MP dialog canceled"
);
});
mpDialogShown = waitForMPDialog("authenticate");
await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
let loginItem = content.document.querySelector("login-item");
let revealCheckbox = loginItem.shadowRoot.querySelector(
".reveal-password-checkbox"
);
revealCheckbox.click();
});
await mpDialogShown;
info("Master Password dialog shown and authenticated");
await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
let loginItem = content.document.querySelector("login-item");
let revealCheckbox = loginItem.shadowRoot.querySelector(
".reveal-password-checkbox"
);
ok(
revealCheckbox.checked,
"reveal checkbox should be checked if MP dialog authenticated"
);
});
});

View File

@ -43,7 +43,7 @@ add_task(async function test_query_parameter_filter() {
await ContentTask.spawn(browser, vanillaLogins, async logins => {
let loginList = Cu.waiveXrays(content.document.querySelector("login-list"));
await ContentTaskUtils.waitForCondition(() => {
return loginList._logins.length == 2;
return loginList._loginGuidsSortedOrder.length == 2;
}, "Waiting for logins to be cached");
let loginItem = Cu.waiveXrays(content.document.querySelector("login-item"));
@ -82,9 +82,9 @@ add_task(async function test_query_parameter_filter() {
logins[0].guid,
"TEST_LOGIN1 should be visible"
);
is(hiddenLoginListItems.length, 1, "One login should be hidden");
is(hiddenLoginListItems.length, 2, "One login should be hidden");
is(
hiddenLoginListItems[0].dataset.guid,
hiddenLoginListItems[1].dataset.guid,
logins[1].guid,
"TEST_LOGIN2 should be hidden"
);

View File

@ -19,7 +19,8 @@ add_task(async function test_show_logins() {
let loginList = Cu.waiveXrays(content.document.querySelector("login-list"));
let loginFound = await ContentTaskUtils.waitForCondition(() => {
return (
loginList._logins.length == 1 && loginList._logins[0].guid == loginGuid
loginList._loginGuidsSortedOrder.length == 1 &&
loginList._loginGuidsSortedOrder[0] == loginGuid
);
}, "Waiting for login to be displayed");
ok(loginFound, "Stored logins should be displayed upon loading the page");
@ -63,14 +64,38 @@ add_task(async function test_login_item() {
usernameInput.value += "-undome";
passwordInput.value += "-undome";
let dialog = content.document.querySelector("confirmation-dialog");
ok(dialog.hidden, "Confirm dialog should initially be hidden");
let cancelButton = loginItem.shadowRoot.querySelector(".cancel-button");
cancelButton.click();
ok(!dialog.hidden, "Confirm dialog should be visible");
let confirmDiscardButton = dialog.shadowRoot.querySelector(
".confirm-button"
);
await content.document.l10n.translateElements([
dialog.shadowRoot.querySelector(".title"),
dialog.shadowRoot.querySelector(".message"),
confirmDiscardButton,
]);
confirmDiscardButton.click();
ok(dialog.hidden, "Confirm dialog should be hidden after confirming");
usernameInput = loginItem.shadowRoot.querySelector(
"input[name='username']"
);
passwordInput = loginItem.shadowRoot.querySelector(
"input[name='password']"
);
await ContentTaskUtils.waitForCondition(
() => usernameInput.value == login.username
);
is(
usernameInput.value,
login.username,
@ -103,9 +128,10 @@ add_task(async function test_login_item() {
);
await ContentTaskUtils.waitForCondition(() => {
loginListItem = Cu.waiveXrays(
loginList.shadowRoot.querySelector(".login-list-item")
loginList.shadowRoot.querySelector(".login-list-item[data-guid]")
);
return (
loginListItem._login &&
loginListItem._login.username == usernameInput.value &&
loginListItem._login.password == passwordInput.value
);
@ -123,7 +149,7 @@ add_task(async function test_login_item() {
let deleteButton = loginItem.shadowRoot.querySelector(".delete-button");
deleteButton.click();
let confirmDeleteDialog = Cu.waiveXrays(
content.document.querySelector("confirm-delete-dialog")
content.document.querySelector("confirmation-dialog")
);
let confirmDeleteButton = confirmDeleteDialog.shadowRoot.querySelector(
".confirm-button"
@ -131,8 +157,8 @@ add_task(async function test_login_item() {
confirmDeleteButton.click();
await ContentTaskUtils.waitForCondition(() => {
loginListItem = Cu.waiveXrays(
loginList.shadowRoot.querySelector(".login-list-item")
loginListItem = loginList.shadowRoot.querySelector(
".login-list-item[data-guid]"
);
return !loginListItem;
}, "Waiting for login to be removed from list");

View File

@ -45,3 +45,20 @@ Object.defineProperty(document, "l10n", {
},
},
});
Object.defineProperty(window, "AboutLoginsUtils", {
configurable: true,
writable: true,
value: {
promptForMasterPassword(resolve) {
resolve(true);
},
doLoginsMatch(login1, login2) {
return (
login1.origin == login2.origin &&
login1.username == login2.username &&
login1.password == login2.password
);
},
},
});

View File

@ -1,14 +1,14 @@
<!DOCTYPE HTML>
<html>
<!--
Test the confirm-delete-dialog component
Test the confirmation-dialog component
-->
<head>
<meta charset="utf-8">
<title>Test the confirm-delete-dialog component</title>
<title>Test the confirmation-dialog component</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="module" src="chrome://browser/content/aboutlogins/components/confirm-delete-dialog.js"></script>
<script type="module" src="chrome://browser/content/aboutlogins/components/confirmation-dialog.js"></script>
<script src="aboutlogins_common.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
@ -23,52 +23,70 @@ Test the confirm-delete-dialog component
<pre id="test">
</pre>
<script>
/** Test the confirm-delete-dialog component **/
/** Test the confirmation-dialog component **/
let cancelButton, confirmButton, gConfirmDeleteDialog;
let options = {
title: "confirm-delete-dialog-title",
message: "confirm-delete-dialog-message",
confirmButtonLabel: "confirm-delete-dialog-confirm-button"
};
let cancelButton, confirmButton, gConfirmationDialog;
add_task(async function setup() {
let templateFrame = document.getElementById("templateFrame");
let displayEl = document.getElementById("display");
importDependencies(templateFrame, displayEl);
gConfirmDeleteDialog = document.createElement("confirm-delete-dialog");
displayEl.appendChild(gConfirmDeleteDialog);
ok(gConfirmDeleteDialog, "The dialog should exist");
gConfirmationDialog = document.createElement("confirmation-dialog");
displayEl.appendChild(gConfirmationDialog);
ok(gConfirmationDialog, "The dialog should exist");
cancelButton = gConfirmDeleteDialog.shadowRoot.querySelector(".cancel-button");
confirmButton = gConfirmDeleteDialog.shadowRoot.querySelector(".confirm-button");
cancelButton = gConfirmationDialog.shadowRoot.querySelector(".cancel-button");
confirmButton = gConfirmationDialog.shadowRoot.querySelector(".confirm-button");
ok(cancelButton, "The cancel button should exist");
ok(confirmButton, "The confirm button should exist");
});
add_task(async function test_escape_key_to_cancel() {
gConfirmDeleteDialog.show();
ok(!gConfirmDeleteDialog.hidden, "The dialog should be visible");
gConfirmationDialog.show(options);
ok(!gConfirmationDialog.hidden, "The dialog should be visible");
sendKey("ESCAPE");
ok(gConfirmDeleteDialog.hidden, "The dialog should be hidden after hitting Escape");
gConfirmDeleteDialog.hide();
ok(gConfirmationDialog.hidden, "The dialog should be hidden after hitting Escape");
gConfirmationDialog.hide();
});
add_task(async function test_initial_focus() {
gConfirmDeleteDialog.show();
ok(!gConfirmDeleteDialog.hidden, "The dialog should be visible");
is(gConfirmDeleteDialog.shadowRoot.activeElement, cancelButton,
"After initially opening the dialog, the cancel button should be focused");
gConfirmDeleteDialog.hide();
gConfirmationDialog.show(options);
ok(!gConfirmationDialog.hidden, "The dialog should be visible");
is(gConfirmationDialog.shadowRoot.activeElement, confirmButton,
"After initially opening the dialog, the confirm button should be focused");
gConfirmationDialog.hide();
});
add_task(async function test_tab_focus() {
gConfirmDeleteDialog.show();
ok(!gConfirmDeleteDialog.hidden, "The dialog should be visible");
gConfirmationDialog.show(options);
ok(!gConfirmationDialog.hidden, "The dialog should be visible");
sendKey("TAB");
is(gConfirmDeleteDialog.shadowRoot.activeElement, confirmButton,
"After opening the dialog and tabbing once, the confirm delete button should be focused");
gConfirmDeleteDialog.hide();
is(gConfirmationDialog.shadowRoot.activeElement, cancelButton,
"After opening the dialog and tabbing once, the cancel button should be focused");
gConfirmationDialog.hide();
});
add_task(async function test_enter_key_to_cancel() {
let showPromise = gConfirmDeleteDialog.show();
ok(!gConfirmDeleteDialog.hidden, "The dialog should be visible");
let showPromise = gConfirmationDialog.show(options);
ok(!gConfirmationDialog.hidden, "The dialog should be visible");
sendKey("RETURN");
try {
await showPromise;
ok(true, "The dialog Promise should resolve after hitting Return with the confirm button focused");
} catch (ex) {
ok(false, "The dialog Promise should not reject after hitting Return with the confirm button focused");
}
});
add_task(async function test_enter_key_to_confirm() {
let showPromise = gConfirmationDialog.show(options);
ok(!gConfirmationDialog.hidden, "The dialog should be visible");
sendKey("TAB");
sendKey("RETURN");
try {
await showPromise;
@ -78,18 +96,32 @@ add_task(async function test_enter_key_to_cancel() {
}
});
add_task(async function test_enter_key_to_confirm() {
let showPromise = gConfirmDeleteDialog.show();
ok(!gConfirmDeleteDialog.hidden, "The dialog should be visible");
sendKey("TAB");
sendKey("RETURN");
try {
await showPromise;
ok(true, "The dialog Promise should resolve after hitting Return with the confirm button focused");
} catch (ex) {
ok(false, "The dialog Promise should not reject after hitting Return with the confirm button focused");
add_task(async function test_dialog_focus_trap() {
let displayEl = document.getElementById("display");
let displayElChildSpan = document.createElement("span");
displayElChildSpan.tabIndex = 0;
displayElChildSpan.id = "display-child";
displayEl.appendChild(displayElChildSpan);
gConfirmationDialog.show(options);
ok(!gConfirmationDialog.hidden, "The dialog should be visible");
ok(displayElChildSpan.tabIndex === -1, "The tabIndex value for elements with a hardcoded tabIndex attribute should be reset to '-1'.")
ok(displayElChildSpan.dataset.oldTabIndex === "0", "Existing tabIndex values should be stored in `dataset.oldTabIndex`.")
const isActiveElemDialogOrHTML = (elemTagName) => {
return (["HTML", "CONFIRMATION-DIALOG"].includes(elemTagName));
}
let iterator = 0;
while(iterator < 20) {
sendKey("TAB");
isnot(document.activeElement.id, "display-child", "The display-child element should not gain focus when the dialog is showing");
is(isActiveElemDialogOrHTML(document.activeElement.tagName), true, "The confirmation-dialog should always have focus when the dialog is showing");
iterator++;
}
});
</script>
</body>
</html>

View File

@ -9,6 +9,7 @@ Test the login-item component
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="module" src="chrome://browser/content/aboutlogins/components/login-item.js"></script>
<script type="module" src="chrome://browser/content/aboutlogins/components/confirmation-dialog.js"></script>
<script src="aboutlogins_common.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
@ -25,7 +26,7 @@ Test the login-item component
<script>
/** Test the login-item component **/
let gLoginItem;
let gLoginItem, gConfirmationDialog;
const TEST_LOGIN_1 = {
guid: "123456789",
origin: "https://example.com",
@ -53,6 +54,10 @@ add_task(async function setup() {
gLoginItem = document.createElement("login-item");
displayEl.appendChild(gLoginItem);
gConfirmationDialog = document.createElement("confirmation-dialog");
gConfirmationDialog.hidden = true;
displayEl.appendChild(gConfirmationDialog);
});
add_task(async function test_empty_item() {
@ -77,6 +82,8 @@ add_task(async function test_set_login() {
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-created")).args.timeCreated, TEST_LOGIN_1.timeCreated, "time-created should be populated");
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-changed")).args.timeChanged, TEST_LOGIN_1.timePasswordChanged, "time-changed should be populated");
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-used")).args.timeUsed, TEST_LOGIN_1.timeLastUsed, "time-used should be populated");
let copyButtons = [...gLoginItem.shadowRoot.querySelectorAll(".copy-button")];
ok(copyButtons.every(button => !isHidden(button)), "The copy buttons should be visible when viewing a login");
});
add_task(async function test_edit_login() {
@ -95,6 +102,8 @@ add_task(async function test_edit_login() {
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-created")).args.timeCreated, TEST_LOGIN_1.timeCreated, "time-created should be populated");
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-changed")).args.timeChanged, TEST_LOGIN_1.timePasswordChanged, "time-changed should be populated");
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-used")).args.timeUsed, TEST_LOGIN_1.timeLastUsed, "time-used should be populated");
let copyButtons = [...gLoginItem.shadowRoot.querySelectorAll(".copy-button")];
ok(copyButtons.every(button => isHidden(button)), "The copy buttons should be visible when editing a login");
gLoginItem.shadowRoot.querySelector("input[name='username']").value = "newUsername";
gLoginItem.shadowRoot.querySelector("input[name='password']").value = "newPassword";
@ -120,6 +129,13 @@ add_task(async function test_edit_login_cancel() {
"loginItem should not be in 'isNewLogin' mode");
gLoginItem.shadowRoot.querySelector(".cancel-button").click();
gConfirmationDialog.shadowRoot.querySelector(".confirm-button").click();
await SimpleTest.promiseWaitForCondition(
() => gConfirmationDialog.hidden,
"waiting for confirmation dialog to hide"
);
ok(!gLoginItem.dataset.editing, "loginItem should not be in 'edit' mode");
ok(!gLoginItem.dataset.isNewLogin, "loginItem should not be in 'isNewLogin' mode");
});
@ -133,6 +149,8 @@ add_task(async function test_reveal_password_change_selected_login() {
is(passwordInput.type, "password", "Password should be masked by default");
revealCheckbox.click();
ok(revealCheckbox.checked, "reveal-checkbox should be checked after clicking");
await SimpleTest.promiseWaitForCondition(() => passwordInput.type == "text",
"waiting for password input type to change after checking for master password");
is(passwordInput.type, "text", "Password should be unmasked when checkbox is clicked");
gLoginItem.setLogin(TEST_LOGIN_2);
@ -155,6 +173,8 @@ add_task(async function test_set_login_empty() {
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-created")).args.timeCreated, "", "time-created should be blank when undefined");
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-changed")).args.timeChanged, "", "time-changed should be blank when undefined");
is(document.l10n.getAttributes(gLoginItem.shadowRoot.querySelector(".time-used")).args.timeUsed, "", "time-used should be blank when undefined");
let copyButtons = [...gLoginItem.shadowRoot.querySelectorAll(".copy-button")];
ok(copyButtons.every(button => isHidden(button)), "The copy buttons should be hidden when creating a login");
let createEventDispatched = false;
document.addEventListener("AboutLoginsCreateLogin", event => {

View File

@ -93,21 +93,21 @@ add_task(async function test_keyboard_navigation() {
for (let [keyFwd, keyRev] of [["LEFT", "RIGHT"], ["DOWN", "UP"]]) {
sendKey(keyFwd);
await SimpleTest.promiseWaitForCondition(() => ol.getAttribute("aria-activedescendant") == ol.children[1].id,
await SimpleTest.promiseWaitForCondition(() => ol.getAttribute("aria-activedescendant") == ol.querySelectorAll(".login-list-item:not([hidden])")[1].id,
`waiting for second item in list to get focused (${keyFwd})`);
ok(ol.children[1].classList.contains("keyboard-selected"), `second item should be marked as keyboard-selected (${keyFwd})`);
ok(ol.querySelectorAll(".login-list-item:not([hidden])")[1].classList.contains("keyboard-selected"), `second item should be marked as keyboard-selected (${keyFwd})`);
sendKey(keyRev);
await SimpleTest.promiseWaitForCondition(() => ol.getAttribute("aria-activedescendant") == ol.children[0].id,
await SimpleTest.promiseWaitForCondition(() => ol.getAttribute("aria-activedescendant") == ol.querySelectorAll(".login-list-item:not([hidden])")[0].id,
`waiting for first item in list to get focused (${keyRev})`);
ok(ol.children[0].classList.contains("keyboard-selected"), `first item should be marked as keyboard-selected (${keyRev})`);
ok(ol.querySelectorAll(".login-list-item:not([hidden])")[0].classList.contains("keyboard-selected"), `first item should be marked as keyboard-selected (${keyRev})`);
}
sendKey("DOWN");
await SimpleTest.promiseWaitForCondition(() => ol.getAttribute("aria-activedescendant") == ol.children[1].id,
await SimpleTest.promiseWaitForCondition(() => ol.getAttribute("aria-activedescendant") == ol.querySelectorAll(".login-list-item:not([hidden])")[1].id,
`waiting for second item in list to get focused (DOWN)`);
ok(ol.children[1].classList.contains("keyboard-selected"), `second item should be marked as keyboard-selected (DOWN)`);
let selectedGuid = ol.children[1].dataset.guid;
ok(ol.querySelectorAll(".login-list-item:not([hidden])")[1].classList.contains("keyboard-selected"), `second item should be marked as keyboard-selected (DOWN)`);
let selectedGuid = ol.querySelectorAll(".login-list-item:not([hidden])")[1].dataset.guid;
let loginSelectedEvent = null;
gLoginList.addEventListener("AboutLoginsLoginSelected", event => loginSelectedEvent = event, {once: true});
@ -124,7 +124,7 @@ add_task(async function test_empty_login_username_in_list() {
}));
gLoginList.setLogins([TEST_LOGIN_3]);
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item:not([hidden])");
is(loginListItems.length, 1, "The one stored login should be displayed");
is(loginListItems[0].dataset.guid, TEST_LOGIN_3.guid, "login-list-item should have correct guid attribute");
let loginUsername = loginListItems[0].querySelector(".username");
@ -133,7 +133,7 @@ add_task(async function test_empty_login_username_in_list() {
add_task(async function test_populated_list() {
gLoginList.setLogins([TEST_LOGIN_1, TEST_LOGIN_2]);
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item:not([hidden])");
is(loginListItems.length, 2, "The two stored logins should be displayed");
is(loginListItems[0].dataset.guid, TEST_LOGIN_1.guid, "login-list-item should have correct guid attribute");
is(loginListItems[0].querySelector(".title").textContent, TEST_LOGIN_1.title,
@ -143,7 +143,7 @@ add_task(async function test_populated_list() {
ok(loginListItems[0].classList.contains("selected"), "The first item should be selected by default");
ok(!loginListItems[1].classList.contains("selected"), "The second item should not be selected by default");
loginListItems[0].click();
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item:not([hidden])");
is(loginListItems.length, 2, "After selecting one, only the two stored logins should be displayed");
ok(loginListItems[0].classList.contains("selected"), "The first item should be selected");
ok(!loginListItems[1].classList.contains("selected"), "The second item should still not be selected");
@ -159,7 +159,7 @@ add_task(async function test_filtered_list() {
detail: "user1",
}));
is(JSON.parse(countSpan.getAttribute("data-l10n-args")).count, 1, "Count should match result amount");
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item[data-guid]");
is(loginListItems[0].querySelector(".username").textContent, "user1", "user1 is expected first");
ok(!loginListItems[0].hidden, "user1 should remain visible");
ok(loginListItems[1].hidden, "user2 should be hidden");
@ -169,7 +169,7 @@ add_task(async function test_filtered_list() {
detail: "user2",
}));
is(JSON.parse(countSpan.getAttribute("data-l10n-args")).count, 1, "Count should match result amount");
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item[data-guid]");
ok(loginListItems[0].hidden, "user1 should be hidden");
ok(!loginListItems[1].hidden, "user2 should be visible");
window.dispatchEvent(new CustomEvent("AboutLoginsFilterLogins", {
@ -178,7 +178,7 @@ add_task(async function test_filtered_list() {
detail: "user",
}));
is(JSON.parse(countSpan.getAttribute("data-l10n-args")).count, 2, "Count should match result amount");
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item[data-guid]");
ok(!loginListItems[0].hidden, "user1 should be visible");
ok(!loginListItems[1].hidden, "user2 should be visible");
window.dispatchEvent(new CustomEvent("AboutLoginsFilterLogins", {
@ -187,7 +187,7 @@ add_task(async function test_filtered_list() {
detail: "foo",
}));
is(JSON.parse(countSpan.getAttribute("data-l10n-args")).count, 0, "Count should match result amount");
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item[data-guid]");
ok(loginListItems[0].hidden, "user1 should be hidden");
ok(loginListItems[1].hidden, "user2 should be hidden");
window.dispatchEvent(new CustomEvent("AboutLoginsFilterLogins", {
@ -196,7 +196,7 @@ add_task(async function test_filtered_list() {
detail: "",
}));
is(JSON.parse(countSpan.getAttribute("data-l10n-args")).count, 2, "Count should be reset to full list length");
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item[data-guid]");
ok(!loginListItems[0].hidden, "user1 should be visible");
ok(!loginListItems[1].hidden, "user2 should be visible");
});
@ -205,7 +205,7 @@ add_task(async function test_login_modified() {
let modifiedLogin = Object.assign(TEST_LOGIN_1, {username: "user11"});
gLoginList.loginModified(modifiedLogin);
await asyncElementRendered();
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item[data-guid]:not([hidden])");
is(loginListItems.length, 2, "Both logins should be displayed");
is(loginListItems[0].dataset.guid, TEST_LOGIN_1.guid, "login-list-item should have correct guid attribute");
is(loginListItems[0].querySelector(".title").textContent, TEST_LOGIN_1.title,
@ -217,10 +217,14 @@ add_task(async function test_login_modified() {
});
add_task(async function test_login_added() {
let newLogin = Object.assign({}, TEST_LOGIN_1, {username: "user22", guid: "111222"});
info("selected sort: " + gLoginList.shadowRoot.getElementById("login-sort").selectedIndex);
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item:not([hidden])");
is(loginListItems.length, 2, "Should have two logins at start of test");
let newLogin = Object.assign({}, TEST_LOGIN_1, {title: "example2.example.com", guid: "111222"});
gLoginList.loginAdded(newLogin);
await asyncElementRendered();
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item:not([hidden])");
is(loginListItems.length, 3, "New login should be added to the list");
is(loginListItems[0].dataset.guid, TEST_LOGIN_1.guid, "login-list-item1 should have correct guid attribute");
is(loginListItems[1].dataset.guid, TEST_LOGIN_2.guid, "login-list-item2 should have correct guid attribute");
@ -234,7 +238,7 @@ add_task(async function test_login_added() {
add_task(async function test_login_removed() {
gLoginList.loginRemoved({guid: "111222"});
await asyncElementRendered();
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item:not([hidden])");
is(loginListItems.length, 2, "New login should be removed from the list");
is(loginListItems[0].dataset.guid, TEST_LOGIN_1.guid, "login-list-item1 should have correct guid attribute");
is(loginListItems[1].dataset.guid, TEST_LOGIN_2.guid, "login-list-item2 should have correct guid attribute");
@ -242,18 +246,16 @@ add_task(async function test_login_removed() {
add_task(async function test_login_added_filtered() {
let countSpan = gLoginList.shadowRoot.querySelector(".count");
is(JSON.parse(countSpan.getAttribute("data-l10n-args")).count, 2, "Count should match full list length");
is(document.l10n.getAttributes(countSpan).args.count, 2, "Count should match full list length");
window.dispatchEvent(new CustomEvent("AboutLoginsFilterLogins", {
bubbles: true,
composed: true,
detail: "user1",
}));
is(JSON.parse(countSpan.getAttribute("data-l10n-args")).count, 1, "Count should match result amount");
let newLogin = Object.assign({}, TEST_LOGIN_1, {username: "user22", guid: "111222"});
let newLogin = Object.assign({}, TEST_LOGIN_1, {title: "example2.example.com", username: "user22", guid: "111222"});
gLoginList.loginAdded(newLogin);
await asyncElementRendered();
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item[data-guid]");
is(loginListItems.length, 3, "New login should be added to the list");
is(loginListItems[0].dataset.guid, TEST_LOGIN_1.guid, "login-list-item1 should have correct guid attribute");
is(loginListItems[1].dataset.guid, TEST_LOGIN_2.guid, "login-list-item2 should have correct guid attribute");
@ -265,32 +267,65 @@ add_task(async function test_login_added_filtered() {
});
add_task(async function test_sorted_list() {
function dispatchChangeEvent(target) {
let event = document.createEvent("UIEvent");
event.initEvent("change", true, true);
target.dispatchEvent(event);
}
// Clear the filter
window.dispatchEvent(new CustomEvent("AboutLoginsFilterLogins", {
detail: "",
}));
// Clear the selection so the 'new' login will be in the list too.
window.dispatchEvent(new CustomEvent("AboutLoginsLoginSelected", {
detail: {},
}));
// sort by last used
gLoginList.shadowRoot.getElementById("login-sort").selectedIndex = 1;
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
is(loginListItems.length, 3, "The list should contain the three stored logins");
let timeUsed = loginListItems[0]._login.timeLastUsed;
let timeUsed2 = loginListItems[1]._login.timeLastUsed;
is(timeUsed2 > timeUsed, true, "Last used login should be displayed at top of list");
// make sure that the logins have distinct orderings based on sort order
let [guid1, guid2, guid3] = gLoginList._loginGuidsSortedOrder;
gLoginList._logins[guid1].login.timeLastUsed = 0;
gLoginList._logins[guid2].login.timeLastUsed = 1;
gLoginList._logins[guid3].login.timeLastUsed = 2;
gLoginList._logins[guid1].login.title = "a";
gLoginList._logins[guid2].login.title = "b";
gLoginList._logins[guid3].login.title = "c";
gLoginList._logins[guid1].login.timePasswordChanged = 1;
gLoginList._logins[guid2].login.timePasswordChanged = 2;
gLoginList._logins[guid3].login.timePasswordChanged = 0;
// sort by name
gLoginList.shadowRoot.getElementById("login-sort").selectedIndex = 0;
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
let title = loginListItems[0]._login.title;
// sort by last used
let loginSort = gLoginList.shadowRoot.getElementById("login-sort");
loginSort.selectedIndex = 1;
dispatchChangeEvent(loginSort);
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item:not([hidden])");
is(loginListItems.length, 3, "The list should contain the three stored logins");
let timeUsed1 = loginListItems[0]._login.timeLastUsed;
let timeUsed2 = loginListItems[1]._login.timeLastUsed;
let timeUsed3 = loginListItems[2]._login.timeLastUsed;
is(timeUsed1 > timeUsed2, true, "Logins sorted by timeLastUsed. First: " + timeUsed1 + "; Second: " + timeUsed2);
is(timeUsed2 > timeUsed3, true, "Logins sorted by timeLastUsed. Second: " + timeUsed2 + "; Third: " + timeUsed3);
// sort by title
loginSort.selectedIndex = 0;
dispatchChangeEvent(loginSort);
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item:not([hidden])");
let title1 = loginListItems[0]._login.title;
let title2 = loginListItems[1]._login.title;
is(title.localeCompare(title2), -1, "Logins should be sorted alphabetically by hostname");
let title3 = loginListItems[2]._login.title;
is(title1.localeCompare(title2), -1, "Logins sorted by title. First: " + title1 + "; Second: " + title2);
is(title2.localeCompare(title3), -1, "Logins sorted by title. Second: " + title2 + "; Third: " + title3);
// sort by last changed
gLoginList.shadowRoot.getElementById("login-sort").selectedIndex = 2;
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
let pwChanged = loginListItems[0]._login.timePasswordChanged;
loginSort.selectedIndex = 2;
dispatchChangeEvent(loginSort);
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item:not([hidden])");
let pwChanged1 = loginListItems[0]._login.timePasswordChanged;
let pwChanged2 = loginListItems[1]._login.timePasswordChanged;
is(pwChanged2 > pwChanged, true, "Login with most recently changed password should be displayed at top of list");
let pwChanged3 = loginListItems[2]._login.timePasswordChanged;
is(pwChanged1 > pwChanged2, true, "Logins sorted by timePasswordChanged. First: " + pwChanged1 + "; Second: " + pwChanged2);
is(pwChanged2 > pwChanged3, true, "Logins sorted by timePasswordChanged. Second: " + pwChanged2 + "; Third: " + pwChanged3);
});
</script>

View File

@ -18,57 +18,6 @@ async function openTabInUserContext(uri, userContextId) {
return { tab, browser };
}
// Opens `uri' in a new <iframe mozbrowser> with the provided userContextId.
// Returns the newly opened browser.
async function addBrowserFrameInUserContext(uri, userContextId) {
// Create a browser frame with the user context and uri
const browser = document.createElementNS(
"http://www.w3.org/1999/xhtml",
"iframe"
);
browser.setAttribute("remote", "true");
browser.setAttribute("usercontextid", userContextId);
browser.setAttribute("mozbrowser", "true");
// `noisolation = true` means `OA.mInIsolatedMozBrowser = false` which matches
// the default for a XUL browser. It is indepedent from user contexts.
browser.setAttribute("noisolation", "true");
browser.setAttribute("src", uri);
gBrowser.tabpanels.appendChild(browser);
// Create a XUL browser-like API expected by test helpers
Object.defineProperty(browser, "messageManager", {
get() {
return browser.frameLoader.messageManager;
},
configurable: true,
enumerable: true,
});
await browserFrameLoaded(browser);
return { browser };
}
function browserFrameLoaded(browser) {
const mm = browser.messageManager;
return new Promise(resolve => {
const eventName = "browser-test-utils:loadEvent";
mm.addMessageListener(eventName, function onLoad(msg) {
if (msg.target != browser) {
return;
}
mm.removeMessageListener(eventName, onLoad);
resolve(msg.data.internalURL);
});
});
}
function removeBrowserFrame({ browser }) {
browser.remove();
// Clean up Browser API parent-side data
delete window._browserElementParents;
}
async function runTestForReceiver(receiver) {
let channelName = "contextualidentity-broadcastchannel";
@ -132,13 +81,3 @@ add_task(async function test() {
await runTestForReceiver(receiver);
gBrowser.removeTab(receiver.tab);
});
add_task(async function test() {
info("Checking broadcast channel with <iframe mozbrowser> receiver");
await SpecialPowers.pushPrefEnv({
set: [["dom.mozBrowserFramesEnabled", true]],
});
let receiver = await addBrowserFrameInUserContext(URI, 2);
await runTestForReceiver(receiver);
removeBrowserFrame(receiver);
});

View File

@ -988,14 +988,6 @@ var PanelMultiView = class extends AssociatedToNode {
this._viewStack.style.marginInlineStart = "-" + deltaX + "px";
}
// Set the transition style and listen for its end to clean up and make sure
// the box sizing becomes dynamic again.
// Somehow, putting these properties in PanelUI.css doesn't work for newly
// shown nodes in a XUL parent node.
this._viewStack.style.transition =
"transform var(--animation-easing-function)" +
" var(--panelui-subview-transition-duration)";
this._viewStack.style.willChange = "transform";
// Use an outline instead of a border so that the size is not affected.
deepestNode.style.outline = "1px solid var(--panel-separator-color)";
@ -1022,41 +1014,6 @@ var PanelMultiView = class extends AssociatedToNode {
this._viewStack.style.transform =
"translateX(" + (moveToLeft ? "" : "-") + deltaX + "px)";
await new Promise(resolve => {
details.resolve = resolve;
this._viewContainer.addEventListener(
"transitionend",
(details.listener = ev => {
// It's quite common that `height` on the view container doesn't need
// to transition, so we make sure to do all the work on the transform
// transition-end, because that is guaranteed to happen.
if (ev.target != this._viewStack || ev.propertyName != "transform") {
return;
}
this._viewContainer.removeEventListener(
"transitionend",
details.listener
);
delete details.listener;
resolve();
})
);
this._viewContainer.addEventListener(
"transitioncancel",
(details.cancelListener = ev => {
if (ev.target != this._viewStack) {
return;
}
this._viewContainer.removeEventListener(
"transitioncancel",
details.cancelListener
);
delete details.cancelListener;
resolve();
})
);
});
// Bail out if the panel was closed during the transition.
if (!nextPanelView.isOpenIn(this)) {
return;

View File

@ -42,7 +42,6 @@ skip-if = os == "linux"
skip-if = os == "mac"
[browser_934951_zoom_in_toolbar.js]
uses-unsafe-cpows = true
[browser_938980_navbar_collapsed.js]
[browser_938995_indefaultstate_nonremovable.js]
[browser_940013_registerToolbarNode_calls_registerArea.js]

View File

@ -14,12 +14,6 @@ let { SyncedTabs } = ChromeUtils.import(
);
let { UIState } = ChromeUtils.import("resource://services-sync/UIState.jsm");
ChromeUtils.defineModuleGetter(
this,
"UITour",
"resource:///modules/UITour.jsm"
);
// These are available on the widget implementation, but it seems impossible
// to grab that impl at runtime.
const DECKINDEX_TABS = 0;
@ -97,11 +91,6 @@ async function openPrefsFromMenuPanel(expectedPanelId, entryPoint) {
// check the button's functionality
await document.getElementById("nav-bar").overflowable.show();
if (entryPoint == "uitour") {
UITour.tourBrowsersByWindow.set(window, new Set());
UITour.tourBrowsersByWindow.get(window).add(gBrowser.selectedBrowser);
}
let syncButton = document.getElementById("sync-button");
ok(syncButton, "The Sync button was added to the Panel Menu");
@ -164,7 +153,6 @@ async function asyncCleanup() {
// restore the tabs
BrowserTestUtils.addTab(gBrowser, initialLocation);
gBrowser.removeTab(newTab);
UITour.tourBrowsersByWindow.delete(window);
}
// When Sync is not setup.

View File

@ -15,15 +15,10 @@ body {
}
#sectionTitle {
float: left;
float: inline-start;
padding-inline-start: 1rem;
}
#sectionTitle:dir(rtl) {
float: right;
padding-inline-end: 1rem;
}
/** Categories **/
.category {

View File

@ -22,7 +22,7 @@ body * {
}
.browser-style {
-moz-appearance: none;
appearance: none;
margin-bottom: 6px;
text-align: left;
}
@ -145,7 +145,7 @@ button.browser-style.default.focused {
/* Radio Buttons */
.browser-style > input[type="radio"] {
-moz-appearance: none;
appearance: none;
background-color: #fff;
background-position: center;
border: 1px solid #b1b1b1;
@ -199,7 +199,7 @@ button.browser-style.default.focused {
/* Checkboxes */
.browser-style > input[type="checkbox"] {
-moz-appearance: none;
appearance: none;
background-color: #fff;
background-position: center;
border: 1px solid #b1b1b1;
@ -390,8 +390,7 @@ textarea.browser-style:invalid:not(:focus) {
.panel-list-item:not(.disabled):hover {
background-color: rgba(0, 0, 0, 0.06);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
border-top: 1px solid rgba(0, 0, 0, 0.1);
border-block: 1px solid rgba(0, 0, 0, 0.1);
}
.panel-list-item:not(.disabled):hover:active {

View File

@ -264,11 +264,6 @@ add_task(async function() {
await extension.awaitMessage("set-synchronous-set");
let text = await startInputSession();
if (!UrlbarPrefs.get("quantumbar")) {
// TODO Bug 1530338: We can't yet wait for a specific result for the
// quantumbar. Therefore we just skip this for now.
await waitForResult(0);
}
extension.sendMessage(info.test);
await extension.awaitMessage("test-ready");

View File

@ -48,22 +48,11 @@ add_task(async function test_tab_options_modals() {
aboutAddonsBrowser.addEventListener(
"DOMWillOpenModalDialog",
function onModalDialog(event) {
if (Cu.isCrossProcessWrapper(event.target)) {
// This event fires in both the content and chrome processes. We
// want to ignore the one in the content process.
return;
}
aboutAddonsBrowser.removeEventListener(
"DOMWillOpenModalDialog",
onModalDialog,
true
);
// Wait for the next event tick to make sure the remaining part of the
// testcase runs after the dialog gets opened.
SimpleTest.executeSoon(resolve);
},
true
{ once: true, capture: true }
);
});

View File

@ -69,7 +69,7 @@
oncommand="MigrationWizard.onImportItemCommand();">
<description control="dataSources">&importItems.label;</description>
<vbox id="dataSources" style="overflow: auto; -moz-appearance: listbox" align="left" flex="1" role="group"/>
<vbox id="dataSources" style="overflow: auto; appearance: auto; -moz-default-appearance: listbox" align="left" flex="1" role="group"/>
</wizardpage>
<wizardpage id="migrating" pageid="migrating" label="&migrating.title;"

View File

@ -9,9 +9,6 @@ const { actionTypes: at } = ChromeUtils.import(
const { getDomain } = ChromeUtils.import(
"resource://activity-stream/lib/TippyTopProvider.jsm"
);
const { RemoteSettings } = ChromeUtils.import(
"resource://services-settings/remote-settings.js"
);
ChromeUtils.defineModuleGetter(
this,
@ -148,50 +145,10 @@ this.FaviconFeed = class FaviconFeed {
return;
}
const site = await this.getSite(getDomain(url));
if (!site) {
if (!this._queryForRedirects.has(url)) {
this._queryForRedirects.add(url);
Services.tm.idleDispatchToMainThread(() => fetchIconFromRedirects(url));
}
return;
if (!this._queryForRedirects.has(url)) {
this._queryForRedirects.add(url);
Services.tm.idleDispatchToMainThread(() => fetchIconFromRedirects(url));
}
let iconUri = Services.io.newURI(site.image_url);
// The #tippytop is to be able to identify them for telemetry.
iconUri = iconUri
.mutate()
.setRef("tippytop")
.finalize();
PlacesUtils.favicons.setAndFetchFaviconForPage(
Services.io.newURI(url),
iconUri,
false,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
null,
Services.scriptSecurityManager.getSystemPrincipal()
);
}
/**
* Get the site tippy top data from Remote Settings.
*/
async getSite(domain) {
const sites = await this.tippyTop.get({
filters: { domain },
syncIfEmpty: false,
});
return sites.length ? sites[0] : null;
}
/**
* Get the tippy top collection from Remote Settings.
*/
get tippyTop() {
if (!this._tippyTop) {
this._tippyTop = RemoteSettings("tippytop");
}
return this._tippyTop;
}
/**

View File

@ -1,463 +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/. */
"use strict";
const { RemoteSettings } = ChromeUtils.import(
"resource://services-settings/remote-settings.js"
);
const { actionCreators: ac } = ChromeUtils.import(
"resource://activity-stream/common/Actions.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"perfService",
"resource://activity-stream/common/PerfService.jsm"
);
const { NaiveBayesTextTagger } = ChromeUtils.import(
"resource://activity-stream/lib/NaiveBayesTextTagger.jsm"
);
const { NmfTextTagger } = ChromeUtils.import(
"resource://activity-stream/lib/NmfTextTagger.jsm"
);
const { RecipeExecutor } = ChromeUtils.import(
"resource://activity-stream/lib/RecipeExecutor.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"NewTabUtils",
"resource://gre/modules/NewTabUtils.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => new TextDecoder());
XPCOMUtils.defineLazyGetter(this, "baseAttachmentsURL", async () => {
const server = Services.prefs.getCharPref("services.settings.server");
const serverInfo = await (await fetch(`${server}/`, {
credentials: "omit",
})).json();
const {
capabilities: {
attachments: { base_url },
},
} = serverInfo;
return base_url;
});
const PERSONALITY_PROVIDER_DIR = OS.Path.join(
OS.Constants.Path.localProfileDir,
"personality-provider"
);
const RECIPE_NAME = "personality-provider-recipe";
const MODELS_NAME = "personality-provider-models";
function getHash(aStr) {
// return the two-digit hexadecimal code for a byte
let toHexString = charCode => `0${charCode.toString(16)}`.slice(-2);
let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
Ci.nsICryptoHash
);
hasher.init(Ci.nsICryptoHash.SHA256);
let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
Ci.nsIStringInputStream
);
stringStream.data = aStr;
hasher.updateFromStream(stringStream, -1);
// convert the binary hash data to a hex string.
let binary = hasher.finish(false);
return Array.from(binary, (c, i) => toHexString(binary.charCodeAt(i)))
.join("")
.toLowerCase();
}
/**
* V2 provider builds and ranks an interest profile (also called an interest vector) off the browse history.
* This allows Firefox to classify pages into topics, by examining the text found on the page.
* It does this by looking at the history text content, title, and description.
*/
this.PersonalityProvider = class PersonalityProvider {
constructor(
timeSegments,
parameterSets,
maxHistoryQueryResults,
version,
scores,
v2Params
) {
this.v2Params = v2Params || {};
this.dispatch = this.v2Params.dispatch || (() => {});
this.modelKeys = this.v2Params.modelKeys;
this.timeSegments = timeSegments;
this.parameterSets = parameterSets;
this.maxHistoryQueryResults = maxHistoryQueryResults;
this.version = version;
this.scores = scores || {};
this.interestConfig = this.scores.interestConfig;
this.interestVector = this.scores.interestVector;
this.onSync = this.onSync.bind(this);
this.setupSyncAttachment(RECIPE_NAME);
this.setupSyncAttachment(MODELS_NAME);
}
async onSync(event) {
const {
data: { created, updated, deleted },
} = event;
// Remove every removed attachment.
const toRemove = deleted.concat(updated.map(u => u.old));
await Promise.all(toRemove.map(record => this.deleteAttachment(record)));
// Download every new/updated attachment.
const toDownload = created.concat(updated.map(u => u.new));
await Promise.all(
toDownload.map(record => this.maybeDownloadAttachment(record))
);
}
setupSyncAttachment(collection) {
RemoteSettings(collection).on("sync", this.onSync);
}
/**
* Downloads the attachment to disk assuming the dir already exists
* and any existing files matching the filename are clobbered.
*/
async _downloadAttachment(record) {
const {
attachment: { location, filename },
} = record;
const remoteFilePath = (await baseAttachmentsURL) + location;
const localFilePath = OS.Path.join(PERSONALITY_PROVIDER_DIR, filename);
const headers = new Headers();
headers.set("Accept-Encoding", "gzip");
const resp = await fetch(remoteFilePath, { headers, credentials: "omit" });
if (!resp.ok) {
Cu.reportError(`Failed to fetch ${remoteFilePath}: ${resp.status}`);
return;
}
const buffer = await resp.arrayBuffer();
const bytes = new Uint8Array(buffer);
await OS.File.writeAtomic(localFilePath, bytes, {
tmpPath: `${localFilePath}.tmp`,
});
}
/**
* Attempts to download the attachment, but only if it doesn't already exist.
*/
async maybeDownloadAttachment(record, retries = 3) {
const {
attachment: { filename, hash, size },
} = record;
await OS.File.makeDir(PERSONALITY_PROVIDER_DIR);
const localFilePath = OS.Path.join(PERSONALITY_PROVIDER_DIR, filename);
let retry = 0;
while (
retry++ < retries &&
(!(await OS.File.exists(localFilePath)) ||
(await OS.File.stat(localFilePath)).size !== size ||
getHash(await this._getFileStr(localFilePath)) !== hash)
) {
await this._downloadAttachment(record);
}
}
async deleteAttachment(record) {
const {
attachment: { filename },
} = record;
await OS.File.makeDir(PERSONALITY_PROVIDER_DIR);
const path = OS.Path.join(PERSONALITY_PROVIDER_DIR, filename);
await OS.File.remove(path, { ignoreAbsent: true });
return OS.File.removeEmptyDir(PERSONALITY_PROVIDER_DIR, {
ignoreAbsent: true,
});
}
/**
* Gets contents of the attachment if it already exists on file,
* and if not attempts to download it.
*/
async getAttachment(record) {
const {
attachment: { filename },
} = record;
const filepath = OS.Path.join(PERSONALITY_PROVIDER_DIR, filename);
try {
await this.maybeDownloadAttachment(record);
return JSON.parse(await this._getFileStr(filepath));
} catch (error) {
Cu.reportError(`Failed to load ${filepath}: ${error.message}`);
}
return {};
}
// A helper function to read and decode a file, it isn't a stand alone function.
// If you use this, ensure you check the file exists and you have a try catch.
async _getFileStr(filepath) {
const binaryData = await OS.File.read(filepath);
return gTextDecoder.decode(binaryData);
}
async init(callback) {
const perfStart = perfService.absNow();
this.interestConfig = this.interestConfig || (await this.getRecipe());
if (!this.interestConfig) {
this.dispatch(
ac.PerfEvent({ event: "PERSONALIZATION_V2_GET_RECIPE_ERROR" })
);
return;
}
this.recipeExecutor = await this.generateRecipeExecutor();
if (!this.recipeExecutor) {
this.dispatch(
ac.PerfEvent({
event: "PERSONALIZATION_V2_GENERATE_RECIPE_EXECUTOR_ERROR",
})
);
return;
}
this.interestVector =
this.interestVector || (await this.createInterestVector());
if (!this.interestVector) {
this.dispatch(
ac.PerfEvent({
event: "PERSONALIZATION_V2_CREATE_INTEREST_VECTOR_ERROR",
})
);
return;
}
this.dispatch(
ac.PerfEvent({
event: "PERSONALIZATION_V2_TOTAL_DURATION",
value: Math.round(perfService.absNow() - perfStart),
})
);
this.initialized = true;
if (callback) {
callback();
}
}
async getFromRemoteSettings(name) {
const result = await RemoteSettings(name).get();
return Promise.all(
result.map(async record => ({
...(await this.getAttachment(record)),
recordKey: record.key,
}))
);
}
/**
* Returns a Recipe from remote settings to be consumed by a RecipeExecutor.
* A Recipe is a set of instructions on how to processes a RecipeExecutor.
*/
async getRecipe() {
if (!this.recipes || !this.recipes.length) {
const start = perfService.absNow();
this.recipes = await this.getFromRemoteSettings(RECIPE_NAME);
this.dispatch(
ac.PerfEvent({
event: "PERSONALIZATION_V2_GET_RECIPE_DURATION",
value: Math.round(perfService.absNow() - start),
})
);
}
return this.recipes[0];
}
/**
* Returns a Recipe Executor.
* A Recipe Executor is a set of actions that can be consumed by a Recipe.
* The Recipe determines the order and specifics of which the actions are called.
*/
async generateRecipeExecutor() {
if (!this.taggers) {
const startTaggers = perfService.absNow();
let nbTaggers = [];
let nmfTaggers = {};
const models = await this.getFromRemoteSettings(MODELS_NAME);
if (models.length === 0) {
return null;
}
for (let model of models) {
if (!this.modelKeys.includes(model.recordKey)) {
continue;
}
if (model.model_type === "nb") {
nbTaggers.push(new NaiveBayesTextTagger(model));
} else if (model.model_type === "nmf") {
nmfTaggers[model.parent_tag] = new NmfTextTagger(model);
}
}
this.dispatch(
ac.PerfEvent({
event: "PERSONALIZATION_V2_TAGGERS_DURATION",
value: Math.round(perfService.absNow() - startTaggers),
})
);
this.taggers = { nbTaggers, nmfTaggers };
}
const startRecipeExecutor = perfService.absNow();
const recipeExecutor = new RecipeExecutor(
this.taggers.nbTaggers,
this.taggers.nmfTaggers
);
this.dispatch(
ac.PerfEvent({
event: "PERSONALIZATION_V2_RECIPE_EXECUTOR_DURATION",
value: Math.round(perfService.absNow() - startRecipeExecutor),
})
);
return recipeExecutor;
}
/**
* Grabs a slice of browse history for building a interest vector
*/
async fetchHistory(columns, beginTimeSecs, endTimeSecs) {
let sql = `SELECT url, title, visit_count, frecency, last_visit_date, description
FROM moz_places
WHERE last_visit_date >= ${beginTimeSecs * 1000000}
AND last_visit_date < ${endTimeSecs * 1000000}`;
columns.forEach(requiredColumn => {
sql += ` AND IFNULL(${requiredColumn}, "") <> ""`;
});
sql += " LIMIT 30000";
const { activityStreamProvider } = NewTabUtils;
const history = await activityStreamProvider.executePlacesQuery(sql, {
columns,
params: {},
});
return history;
}
/**
* Examines the user's browse history and returns an interest vector that
* describes the topics the user frequently browses.
*/
async createInterestVector() {
let interestVector = {};
let endTimeSecs = new Date().getTime() / 1000;
let beginTimeSecs = endTimeSecs - this.interestConfig.history_limit_secs;
let history = await this.fetchHistory(
this.interestConfig.history_required_fields,
beginTimeSecs,
endTimeSecs
);
this.dispatch(
ac.PerfEvent({
event: "PERSONALIZATION_V2_HISTORY_SIZE",
value: history.length,
})
);
const start = perfService.absNow();
for (let historyRec of history) {
let ivItem = this.recipeExecutor.executeRecipe(
historyRec,
this.interestConfig.history_item_builder
);
if (ivItem === null) {
continue;
}
interestVector = this.recipeExecutor.executeCombinerRecipe(
interestVector,
ivItem,
this.interestConfig.interest_combiner
);
if (interestVector === null) {
return null;
}
}
const finalResult = this.recipeExecutor.executeRecipe(
interestVector,
this.interestConfig.interest_finalizer
);
this.dispatch(
ac.PerfEvent({
event: "PERSONALIZATION_V2_CREATE_INTEREST_VECTOR_DURATION",
value: Math.round(perfService.absNow() - start),
})
);
return finalResult;
}
/**
* Calculates a score of a Pocket item when compared to the user's interest
* vector. Returns the score. Higher scores are better. Assumes this.interestVector
* is populated.
*/
calculateItemRelevanceScore(pocketItem) {
if (!this.initialized) {
return pocketItem.item_score || 1;
}
let scorableItem = this.recipeExecutor.executeRecipe(
pocketItem,
this.interestConfig.item_to_rank_builder
);
if (scorableItem === null) {
return -1;
}
let rankingVector = JSON.parse(JSON.stringify(this.interestVector));
Object.keys(scorableItem).forEach(key => {
rankingVector[key] = scorableItem[key];
});
rankingVector = this.recipeExecutor.executeRecipe(
rankingVector,
this.interestConfig.item_ranker
);
if (rankingVector === null) {
return -1;
}
return rankingVector.score;
}
/**
* Returns an object holding the settings and affinity scores of this provider instance.
*/
getAffinities() {
return {
timeSegments: this.timeSegments,
parameterSets: this.parameterSets,
maxHistoryQueryResults: this.maxHistoryQueryResults,
version: this.version,
scores: {
interestConfig: this.interestConfig,
interestVector: this.interestVector,
taggers: this.taggers,
},
};
}
};
const EXPORTED_SYMBOLS = ["PersonalityProvider"];

View File

@ -1,99 +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/. */
"use strict";
const { RemoteSettings } = ChromeUtils.import(
"resource://services-settings/remote-settings.js"
);
// Returns whether the passed in params match the criteria.
// To match, they must contain all the params specified in criteria and the values
// must match if a value is provided in criteria.
function _hasParams(criteria, params) {
for (let param of criteria) {
const val = params.get(param.key);
if (
val === null ||
(param.value && param.value !== val) ||
(param.prefix && !val.startsWith(param.prefix))
) {
return false;
}
}
return true;
}
/**
* classifySite
* Classifies a given URL into a category based on classification data from RemoteSettings.
* The data from remote settings can match a category by one of the following:
* - match the exact URL
* - match the hostname or second level domain (sld)
* - match query parameter(s), and optionally their values or prefixes
* - match both (hostname or sld) and query parameter(s)
*
* The data looks like:
* [{
* "type": "hostname-and-params-match",
* "criteria": [
* {
* "url": "https://matchurl.com",
* "hostname": "matchhostname.com",
* "sld": "secondleveldomain",
* "params": [
* {
* "key": "matchparam",
* "value": "matchvalue",
* "prefix": "matchpPrefix",
* },
* ],
* },
* ],
* "weight": 300,
* },...]
*/
async function classifySite(url, RS = RemoteSettings) {
let category = "other";
let parsedURL;
// Try to parse the url.
for (let _url of [url, `https://${url}`]) {
try {
parsedURL = new URL(_url);
break;
} catch (e) {}
}
if (parsedURL) {
// If we parsed successfully, find a match.
const hostname = parsedURL.hostname.replace(/^www\./i, "");
const params = parsedURL.searchParams;
// NOTE: there will be an initial/default local copy of the data in m-c.
// Therefore, this should never return an empty list [].
const siteTypes = await RS("sites-classification").get();
const sortedSiteTypes = siteTypes.sort(
(x, y) => (y.weight || 0) - (x.weight || 0)
);
for (let type of sortedSiteTypes) {
for (let criteria of type.criteria) {
if (criteria.url && criteria.url !== url) {
continue;
}
if (criteria.hostname && criteria.hostname !== hostname) {
continue;
}
if (criteria.sld && criteria.sld !== hostname.split(".")[0]) {
continue;
}
if (criteria.params && !_hasParams(criteria.params, params)) {
continue;
}
return type.type;
}
}
}
return category;
}
const EXPORTED_SYMBOLS = ["classifySite"];

View File

@ -377,6 +377,10 @@ var PlacesUIUtils = {
getViewForNode: function PUIU_getViewForNode(aNode) {
let node = aNode;
if (Cu.isDeadWrapper(node)) {
return null;
}
if (node.localName == "panelview" && node._placesView) {
return node._placesView;
}

View File

@ -14,11 +14,6 @@ ChromeUtils.defineModuleGetter(
"AddonManager",
"resource://gre/modules/AddonManager.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"RemoteSettings",
"resource://services-settings/remote-settings.js"
);
ChromeUtils.defineModuleGetter(
this,
"SelectionChangedMenulist",
@ -57,16 +52,6 @@ async function installFromUrl(url, hash, callback) {
return install.addon;
}
async function dictionaryIdsForLocale(locale) {
let entries = await RemoteSettings("language-dictionaries").get({
filters: { id: locale },
});
if (entries.length > 0) {
return entries[0].dictionaries;
}
return [];
}
class OrderedListBox {
constructor({
richlistbox,

View File

@ -15,6 +15,6 @@
}
.option-description {
color: #737373;
color: var(--in-content-deemphasized-text);
margin-top: -0.5em !important;
}

View File

@ -29,7 +29,7 @@
}
#permissionsDisableDescription {
color: #737373;
color: var(--in-content-deemphasized-text);
line-height: 110%;
}

View File

@ -147,11 +147,9 @@ add_task(async function test_search_handoff_on_paste() {
await new Promise(r =>
EventUtils.synthesizeKey("v", { accelKey: true }, win, r)
);
// TODO: Bug 1539199 We should be able to wait for search complete for AwesomeBar
// as well.
if (UrlbarPrefs.get("quantumbar")) {
await UrlbarTestUtils.promiseSearchComplete(win);
}
await UrlbarTestUtils.promiseSearchComplete(win);
ok(urlBarHasNormalFocus(win), "url bar has normal focused");
is(win.gURLBar.value, "@google words", "url bar has search text");

View File

@ -714,17 +714,6 @@ class SearchOneOffs {
}
_buttonForEngine(engine) {
if (this.telemetryOrigin == "urlbar" && !UrlbarPrefs.get("quantumbar")) {
return (
this._popup &&
document.getAnonymousElementByAttribute(
this._popup,
"id",
this._buttonIDForEngine(engine)
)
);
}
let id = this._buttonIDForEngine(engine);
return this._popup && document.getElementById(id);
}

View File

@ -12,7 +12,6 @@ const ONEOFF_URLBAR_PREF = "browser.urlbar.oneOffSearches";
const urlbar = document.getElementById("urlbar");
const searchPopup = document.getElementById("PopupSearchAutoComplete");
const urlbarPopup = document.getElementById("PopupAutoCompleteRichResult");
const searchOneOff = searchPopup.oneOffButtons;
const urlBarOneOff = UrlbarTestUtils.getOneOffSearchButtons(window);
@ -88,7 +87,7 @@ add_task(async function test_urlBarChangeEngine() {
let oneOffButton = await openPopupAndGetEngineButton(
false,
urlbarPopup,
null,
urlBarOneOff,
URLBAR_BASE_ID
);
@ -126,7 +125,7 @@ add_task(async function test_urlBarChangeEngine() {
await UrlbarTestUtils.promisePopupClose(window);
// Move the cursor out of the panel area to avoid messing with other tests.
await EventUtils.synthesizeNativeMouseMove(urlbarPopup);
await EventUtils.synthesizeNativeMouseMove(urlbar);
});
/**

View File

@ -111,7 +111,6 @@ add_task(async function test_unrestored_tabs_listed() {
});
const total = UrlbarTestUtils.getResultCount(window);
info(`Found ${total} matches`);
const quantumbar = UrlbarPrefs.get("quantumbar");
// Check to see the expected uris and titles match up (in any order)
for (let i = 0; i < total; i++) {
@ -120,9 +119,7 @@ add_task(async function test_unrestored_tabs_listed() {
info("Skip heuristic match");
continue;
}
const url = quantumbar
? result.url
: PlacesUtils.parseActionUrl(result.url).params.url;
const url = result.url;
Assert.ok(
url in tabsForEnsure,
`Should have the found result '${url}' in the expected list of entries`

View File

@ -150,7 +150,8 @@ nsMacShellService::SetDesktopBackground(Element* aElement, int32_t aPosition,
auto referrerInfo = mozilla::MakeRefPtr<mozilla::dom::ReferrerInfo>(*aElement);
return wbp->SaveURI(imageURI, aElement->NodePrincipal(), 0, referrerInfo,
nullptr, nullptr, mBackgroundFile, loadContext);
nullptr, nullptr, mBackgroundFile,
nsIContentPolicy::TYPE_IMAGE, loadContext);
}
NS_IMETHODIMP

View File

@ -97,7 +97,6 @@ class UrlbarInput {
this.controller.setInput(this);
this.view = new UrlbarView(this);
this.valueIsTyped = false;
this.userInitiatedFocus = false;
this.isPrivate = PrivateBrowsingUtils.isWindowPrivate(this.window);
this.lastQueryContextPromise = Promise.resolve();
this._actionOverrideKeyCount = 0;
@ -176,28 +175,32 @@ class UrlbarInput {
this.eventBufferer = new UrlbarEventBufferer(this);
this._inputFieldEvents = [
"blur",
"compositionstart",
"compositionend",
"dragover",
"dragstart",
"drop",
"focus",
"blur",
"input",
"keydown",
"keyup",
"mousedown",
"mouseover",
"overflow",
"underflow",
"paste",
"scrollend",
"select",
"overflow",
"underflow",
"dragstart",
"dragover",
"drop",
"compositionstart",
"compositionend",
];
for (let name of this._inputFieldEvents) {
this.inputField.addEventListener(name, this);
}
// This is needed for the dropmarker. Once we remove that (i.e. make
// openViewOnFocus = true the default), this won't be needed anymore.
this.addEventListener("mousedown", this);
this.view.panel.addEventListener("popupshowing", this);
this.view.panel.addEventListener("popuphidden", this);
@ -291,15 +294,6 @@ class UrlbarInput {
}
}
/**
* This exists for legacy compatibility, and can be removed once the old
* urlbar code goes away, by changing callers. Internal consumers should use
* view.close().
*/
closePopup() {
this.view.close();
}
focus() {
this.inputField.focus();
}
@ -1448,10 +1442,6 @@ class UrlbarInput {
if (this.getAttribute("pageproxystate") != "valid") {
this.window.UpdatePopupNotificationsVisibility();
}
if (this.openViewOnFocus) {
this.startQuery();
}
}
_on_mouseover(event) {
@ -1459,23 +1449,23 @@ class UrlbarInput {
}
_on_mousedown(event) {
if (
(event.target == this.inputField ||
// Can be removed after bug 1513337:
event.originalTarget.classList.contains("anonymous-div")) &&
event.button == 0 &&
event.detail == 2 &&
UrlbarPrefs.get("doubleClickSelectsAll")
) {
this.editor.selectAll();
event.preventDefault();
// We only care about left clicks here.
if (event.button != 0) {
return;
}
if (
event.originalTarget.classList.contains("urlbar-history-dropmarker") &&
event.button == 0
) {
if (event.currentTarget == this.inputField) {
if (event.detail == 2 &&
UrlbarPrefs.get("doubleClickSelectsAll")) {
this.editor.selectAll();
event.preventDefault();
} else if (this.openViewOnFocus && !this.view.isOpen) {
this.startQuery();
}
return;
}
if (event.originalTarget.classList.contains("urlbar-history-dropmarker")) {
if (this.view.isOpen) {
this.view.close();
} else {
@ -1631,9 +1621,6 @@ class UrlbarInput {
_on_TabSelect(event) {
this._resetSearchState();
this.controller.viewContextChanged();
if (this.focused && this.openViewOnFocus) {
this.startQuery();
}
}
_on_keydown(event) {

View File

@ -341,9 +341,7 @@ class UrlbarValueFormatter {
return false;
}
let alias = UrlbarPrefs.get("quantumbar")
? this._getSearchAlias()
: this._getSearchAliasAwesomebar();
let alias = this._getSearchAlias();
if (!alias) {
return false;
}
@ -419,42 +417,6 @@ class UrlbarValueFormatter {
return null;
}
_getSearchAliasAwesomebar() {
let popup = this.urlbarInput.popup;
// To determine whether the input contains a valid alias, check the value of
// the selected result -- whether it's a search engine result with an alias.
// Actually, check the selected listbox item, not the result in the
// controller, because we want to continue highlighting the alias when the
// popup is closed and the search has stopped. The selected index when the
// popup is closed is zero, however, which is why we also check the previous
// selected index.
let itemIndex =
popup.selectedIndex < 0
? popup._previousSelectedIndex
: popup.selectedIndex;
if (itemIndex < 0) {
return null;
}
let item = popup.richlistbox.children[itemIndex] || null;
// This actiontype check isn't necessary because we call _parseActionUrl
// below and we could check action.type instead. But since this method is
// called very often, as an optimization, first do a simple string
// comparison on actiontype before continuing with the more expensive regexp
// that _parseActionUrl uses.
if (!item || item.getAttribute("actiontype") != "searchengine") {
return null;
}
let url = item.getAttribute("url");
let action = this.urlbarInput._parseActionUrl(url);
if (!action) {
return null;
}
return action.params.alias || null;
}
/**
* Passes DOM events to the _on_<event type> methods.
* @param {Event} event

View File

@ -0,0 +1,9 @@
Getting in Touch
================
For any questions regarding the Address Bar, the team is available through
the #fx-search channel on irc.mozilla.org and the fx-search@mozilla.com mailing
list.
Issues can be `filed in Bugzilla <https://bugzilla.mozilla.org/enter_bug.cgi?product=Firefox&component=Address%20Bar>`_
under the Firefox / Address Bar component.

View File

@ -0,0 +1,4 @@
Debugging & Logging
===================
*Content to be written*

View File

@ -0,0 +1,4 @@
Experiments & Extensions
========================
*Content to be written*

View File

@ -0,0 +1,26 @@
Address Bar
===========
This document describes the implementation of Firefox's address bar, also known
as the quantumbar or urlbar. The address bar was also called the awesomebar
until Firefox 68, when it was substantially rewritten.
The address bar is a specialized search access point that aggregates data from
several different sources, including:
* Places (Firefox's history and bookmarks system)
* Search engines (including search suggestions)
* WebExtensions
* Open tabs
Most of the address bar code lives in `browser/components/urlbar <https://dxr.mozilla.org/mozilla-central/source/browser/components/urlbar/>`_.
A separate and important back-end piece currently is `toolkit/components/places/UnifiedComplete.jsm <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/places/UnifiedComplete.jsm>`_, which was carried over from awesomebar and has not yet been rewritten for quantumbar.
.. toctree::
overview
utilities
telemetry
debugging
experiments
contact

View File

@ -1,36 +1,7 @@
.. _addressbar:
Architecture Overview
=====================
===========
Address Bar
===========
The *Address Bar* component drives the browser's url bar, a specialized search
access point (SAP) including different sources of information:
* Places: the History, Bookmarks, Favicons and Tags component
* Search Engines and Suggestions
* WebExtensions
* Open Tabs
* Keywords and aliases
The *Address Bar* component lives in the
`browser/components/urlbar/ <https://dxr.mozilla.org/mozilla-central/source/browser/components/urlbar/>`_ folder.
.. note::
The current *Address Bar* is driven by the legacy
`toolkit autocomplete <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/autocomplete>`_
binding, along with the *Places UnifiedComplete* component. This documentation
describes a new rewrite of the feature, also known as the **Quantum Bar**.
.. caution::
This code is under heavy development and may change abruptly.
Global Architecture Overview
============================
The *Address Bar* is implemented as a *Model-View-Controller* (MVC) system. One of
The address bar is implemented as a *model-view-controller* (MVC) system. One of
the scopes of this architecture is to allow easy replacement of its components,
for easier experimentation.
@ -46,7 +17,7 @@ and responsibilities.
The UrlbarQueryContext
======================
----------------------
The *UrlbarQueryContext* object describes a single instance of a search.
It is augmented as it progresses through the system, with various information:
@ -85,7 +56,7 @@ It is augmented as it progresses through the system, with various information:
The Model
=========
---------
The *Model* is the component responsible for retrieving search results based on
the user's input, and sorting them accordingly to their importance.
@ -134,7 +105,7 @@ used by the user to restrict the search to specific result type (See the
}
UrlbarProvider
--------------
~~~~~~~~~~~~~~
A provider is specialized into searching and returning results from different
information sources. Internal providers are usually implemented in separate
@ -218,7 +189,7 @@ implementation details may vary deeply among different providers.
}
UrlbarMuxer
-----------
~~~~~~~~~~~
The *Muxer* is responsible for sorting results based on their importance and
additional rules that depend on the UrlbarQueryContext. The muxer to use is
@ -253,7 +224,7 @@ indicated by the UrlbarQueryContext.muxer property.
The Controller
==============
--------------
`UrlbarController <https://dxr.mozilla.org/mozilla-central/source/browser/components/urlbar/UrlbarController.jsm>`_
is the component responsible for reacting to user's input, by communicating
@ -282,7 +253,7 @@ View (e.g. showing/hiding a panel). It is also responsible for reporting Telemet
The View
=========
--------
The View is the component responsible for presenting search results to the
user and handling their input.
@ -293,7 +264,7 @@ user and handling their input.
reference for the default View, but may not be valid for other implementations.
`UrlbarInput.jsm <https://dxr.mozilla.org/mozilla-central/source/browser/components/urlbar/UrlbarInput.jsm>`_
-------------------------------------------------------------------------------------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Implements an input box *View*, owns an *UrlbarView*.
@ -307,8 +278,6 @@ Implements an input box *View*, owns an *UrlbarView*.
// Uses UrlbarValueFormatter to highlight the base host, search aliases
// and to keep the host visible on overflow.
formatValue(val);
// Manage view visibility.
closePopup();
openResults();
// Converts an internal URI (e.g. a URI with a username or password) into
// one which we can expose to the user.
@ -331,8 +300,6 @@ Implements an input box *View*, owns an *UrlbarView*.
view;
// Whether the current value was typed by the user.
valueIsTyped;
// Whether the input box has been focused by a user action.
userInitiatedFocus;
// Whether the context is in Private Browsing mode.
isPrivate;
// Whether the input box is focused.
@ -344,7 +311,7 @@ Implements an input box *View*, owns an *UrlbarView*.
}
`UrlbarView.jsm <https://dxr.mozilla.org/mozilla-central/source/browser/components/urlbar/UrlbarView.jsm>`_
-----------------------------------------------------------------------------------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Represents the base *View* implementation, communicates with the *Controller*.
@ -371,7 +338,7 @@ Represents the base *View* implementation, communicates with the *Controller*.
UrlbarResult
============
------------
An `UrlbarResult <https://dxr.mozilla.org/mozilla-central/source/browser/components/urlbar/UrlbarResult.jsm>`_
instance represents a single search result with a result type, that
@ -419,54 +386,3 @@ The following RESULT_TYPEs are supported:
OMNIBOX: 5,
// Payload: { icon, url, device, title }
REMOTE_TAB: 6,
Shared Modules
==============
Various modules provide shared utilities to the other components:
`UrlbarPrefs.jsm <https://dxr.mozilla.org/mozilla-central/source/browser/components/urlbar/UrlbarPrefs.jsm>`_
-------------------------------------------------------------------------------------------------------------
Implements a Map-like storage or urlbar related preferences. The values are kept
up-to-date.
.. highlight:: JavaScript
.. code::
// Always use browser.urlbar. relative branch, except for the preferences in
// PREF_OTHER_DEFAULTS.
UrlbarPrefs.get("delay"); // Gets value of browser.urlbar.delay.
.. note::
Newly added preferences should always be properly documented in UrlbarPrefs.
`UrlbarUtils.jsm <https://dxr.mozilla.org/mozilla-central/source/browser/components/urlbar/UrlbarUtils.jsm>`_
-------------------------------------------------------------------------------------------------------------
Includes shared utils and constants shared across all the components.
Telemetry Probes
================
*Content to be written*
Debugging & Logging
===================
*Content to be written*
Getting in Touch
================
For any questions regarding the Address Bar, the team is available through
the #fx-search channel on irc.mozilla.org and the fx-search@mozilla.com mailing
list.
Issues can be `filed in Bugzilla <https://bugzilla.mozilla.org/enter_bug.cgi?product=Firefox&component=Address%20Bar>`_
under the Firefox / Address Bar component.

Some files were not shown because too many files have changed in this diff Show More