Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE

This commit is contained in:
Razvan Maries 2019-08-02 01:52:58 +03:00
commit 518b15a876
257 changed files with 4516 additions and 2274 deletions

View File

@ -193,3 +193,20 @@ jobs:
# No default branch
mozilla-central:
- {weekday: 'Monday', hour: 10, minute: 0}
- name: raptor-tp6m
job:
type: decision-task
treeherder-symbol: tp6m
target-tasks-method: raptor_tp6m
include-push-tasks: true
run-on-projects:
- mozilla-central
when:
- {weekday: 'Monday', hour: 3, minute: 0}
- {weekday: 'Tuesday', hour: 3, minute: 0}
- {weekday: 'Wednesday', hour: 3, minute: 0}
- {weekday: 'Thursday', hour: 3, minute: 0}
- {weekday: 'Friday', hour: 3, minute: 0}
- {weekday: 'Saturday', hour: 3, minute: 0}
- {weekday: 'Sunday', hour: 3, minute: 0}

View File

@ -1320,6 +1320,8 @@ pref("trailhead.firstrun.branches", "join-privacy");
// The pref that controls if the What's New panel is enabled.
pref("browser.messaging-system.whatsNewPanel.enabled", false);
// Whether to use Messaging System to add a badge to the FxA toolbar button
pref("browser.messaging-system.fxatoolbarbadge.enabled", true);
// Enable the DOM fullscreen API.
pref("full-screen-api.enabled", true);

View File

@ -10,6 +10,13 @@ ChromeUtils.defineModuleGetter(
"resource://gre/modules/ContentBlockingAllowList.jsm"
);
XPCOMUtils.defineLazyServiceGetter(
this,
"TrackingDBService",
"@mozilla.org/tracking-db-service;1",
"nsITrackingDBService"
);
var Fingerprinting = {
PREF_ENABLED: "privacy.trackingprotection.fingerprinting.enabled",
reportBreakageLabel: "fingerprinting",
@ -1078,21 +1085,27 @@ var gProtectionsHandler = {
"protections-popup-tp-switch"
));
},
get _protectionPopupSettingsButton() {
delete this._protectionPopupSettingsButton;
return (this._protectionPopupSettingsButton = document.getElementById(
get _protectionsPopupSettingsButton() {
delete this._protectionsPopupSettingsButton;
return (this._protectionsPopupSettingsButton = document.getElementById(
"protections-popup-settings-button"
));
},
get _protectionPopupFooter() {
delete this._protectionPopupFooter;
return (this._protectionPopupFooter = document.getElementById(
get _protectionsPopupFooter() {
delete this._protectionsPopupFooter;
return (this._protectionsPopupFooter = document.getElementById(
"protections-popup-footer"
));
},
get _protectionPopupTrackersCounterDescription() {
delete this._protectionPopupTrackersCounterDescription;
return (this._protectionPopupTrackersCounterDescription = document.getElementById(
get _protectionsPopupTrackersCounterBox() {
delete this._protectionsPopupTrackersCounterBox;
return (this._protectionsPopupTrackersCounterBox = document.getElementById(
"protections-popup-trackers-blocked-counter-box"
));
},
get _protectionsPopupTrackersCounterDescription() {
delete this._protectionsPopupTrackersCounterDescription;
return (this._protectionsPopupTrackersCounterDescription = document.getElementById(
"protections-popup-trackers-blocked-counter-description"
));
},
@ -1223,6 +1236,9 @@ var gProtectionsHandler = {
this.appMenuLabel.setAttribute("value", this.strings.appMenuTitle);
this.appMenuLabel.setAttribute("tooltiptext", this.strings.appMenuTooltip);
// Add an observer to observe that the history has been cleared.
Services.obs.addObserver(this, "browser:purge-session-history");
},
uninit() {
@ -1236,15 +1252,18 @@ var gProtectionsHandler = {
this.PREF_ANIMATIONS_ENABLED,
this.updateAnimationsEnabled
);
Services.obs.removeObserver(this, "browser:purge-session-history");
},
openPreferences(origin) {
openPreferences("privacy-trackingprotection", { origin });
},
openProtections() {
openProtections(relatedToCurrent = false) {
switchToTabHavingURI("about:protections", true, {
replaceQueryString: true,
relatedToCurrent,
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
});
},
@ -1329,6 +1348,12 @@ var gProtectionsHandler = {
if (event.target == this._protectionsPopup) {
window.removeEventListener("focus", this, true);
gIdentityHandler._trackingProtectionIconContainer.removeAttribute("open");
// Hide the tracker counter when the popup get hidden.
this._protectionsPopupTrackersCounterBox.toggleAttribute(
"showing",
false
);
}
},
@ -1343,6 +1368,25 @@ var gProtectionsHandler = {
}
},
async onTrackingProtectionIconHoveredOrFocused() {
// We would try to pre-fetch the data whenever the shield icon is hovered or
// focused. We check focus event here due to the keyboard navigation.
if (this._updatingFooter) {
return;
}
this._updatingFooter = true;
// Get the tracker count and set it to the counter in the footer.
const trackerCount = await TrackingDBService.sumAllEvents();
this.setTrackersBlockedCounter(trackerCount);
// Try to get the earliest recorded date in case that there was no record
// during the initiation but new records come after that.
await this.maybeUpdateEarliestRecordedDateTooltip();
this._updatingFooter = false;
},
// This triggers from top level location changes.
onLocationChange() {
if (this._showToastAfterRefresh) {
@ -1497,6 +1541,17 @@ var gProtectionsHandler = {
}
},
observe(subject, topic, data) {
switch (topic) {
case "browser:purge-session-history":
// We need to update the earliest recorded date if history has been
// cleared.
this._hasEarliestRecord = false;
this.maybeUpdateEarliestRecordedDateTooltip();
break;
}
},
refreshProtectionsPopup() {
let host = gIdentityHandler.getHostForDisplay();
@ -1529,11 +1584,8 @@ var gProtectionsHandler = {
!currentlyEnabled
);
// Set the counter of the 'Trackers blocked This Week'.
// We need to get the statistics of trackers. So far, we haven't implemented
// this yet. So we use a fake number here. Should be resolved in
// Bug 1555231.
this.setTrackersBlockedCounter(244051);
// Update the tooltip of the blocked tracker counter.
this.maybeUpdateEarliestRecordedDateTooltip();
},
disableForCurrentPage() {
@ -1593,10 +1645,19 @@ var gProtectionsHandler = {
},
setTrackersBlockedCounter(trackerCount) {
this._protectionPopupTrackersCounterDescription.textContent =
// gNavigatorBundle.getFormattedString(
// "protections.trackers_counter", [cnt]);
`Trackers blocked this week: ${trackerCount.toLocaleString()}`;
let forms = gNavigatorBundle.getString(
"protections.footer.blockedTrackerCounter.description"
);
this._protectionsPopupTrackersCounterDescription.textContent = PluralForm.get(
trackerCount,
forms
).replace("#1", trackerCount);
// Show the counter if the number of tracker is not zero.
this._protectionsPopupTrackersCounterBox.toggleAttribute(
"showing",
trackerCount != 0
);
},
/**
@ -1800,4 +1861,35 @@ var gProtectionsHandler = {
this._protectionsPopup.hidePopup();
this.submitBreakageReport(this.reportURI);
},
async maybeUpdateEarliestRecordedDateTooltip() {
if (this._hasEarliestRecord) {
return;
}
let date = await TrackingDBService.getEarliestRecordedDate();
// If there is no record for any blocked tracker, we don't have to do anything
// since the tracker counter won't be shown.
if (!date) {
return;
}
this._hasEarliestRecord = true;
const dateLocaleStr = new Date(date).toLocaleDateString("default", {
month: "long",
day: "numeric",
year: "numeric",
});
const tooltipStr = gNavigatorBundle.getFormattedString(
"protections.footer.blockedTrackerCounter.tooltip",
[dateLocaleStr]
);
this._protectionsPopupTrackersCounterDescription.setAttribute(
"tooltiptext",
tooltipStr
);
},
};

View File

@ -489,6 +489,16 @@ XPCOMUtils.defineLazyPreferenceGetter(
}
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"gMsgingSystemFxABadge",
"browser.messaging-system.fxatoolbarbadge.enabled",
true,
(aPref, aOldVal, aNewVal) => {
showFxaToolbarMenu(gFxaToolbarEnabled);
}
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"gHtmlAboutAddonsEnabled",
@ -631,7 +641,9 @@ function showFxaToolbarMenu(enable) {
// We set an attribute here so that we can toggle the custom
// badge depending on whether the FxA menu was ever accessed.
if (!gFxaToolbarAccessed) {
// If badging is handled by Messaging System we shouldn't set
// the attribute.
if (!gFxaToolbarAccessed && !gMsgingSystemFxABadge) {
mainWindowEl.setAttribute("fxa_avatar_badged", "badged");
} else {
mainWindowEl.removeAttribute("fxa_avatar_badged");

View File

@ -849,7 +849,9 @@
<box id="tracking-protection-icon-container" align="center"
role="button"
onclick="gProtectionsHandler.handleProtectionsButtonEvent(event);"
onkeypress="gProtectionsHandler.handleProtectionsButtonEvent(event);">
onkeypress="gProtectionsHandler.handleProtectionsButtonEvent(event);"
onmouseover="gProtectionsHandler.onTrackingProtectionIconHoveredOrFocused();"
onfocus="gProtectionsHandler.onTrackingProtectionIconHoveredOrFocused();">
<box id="tracking-protection-icon-box" animationsenabled="true">
<image id="tracking-protection-icon"/>
<box id="tracking-protection-icon-animatable-box" flex="1">

View File

@ -1021,15 +1021,18 @@ nsContextMenu.prototype = {
}
let documentURI = gContextMenuContentData.documentURIObject;
let formOrigin = LoginHelper.getLoginOrigin(documentURI.spec);
let fragment = LoginManagerContextMenu.addLoginsToMenu(
this.targetIdentifier,
this.browser,
documentURI
formOrigin
);
let isGeneratedPasswordEnabled =
LoginHelper.generationAvailable && LoginHelper.generationEnabled;
let canFillGeneratedPassword =
this.onPassword && isGeneratedPasswordEnabled;
this.onPassword &&
isGeneratedPasswordEnabled &&
Services.logins.getLoginSavingEnabled(formOrigin);
this.showItem("fill-login-no-logins", !fragment);
this.showItem("fill-login-generated-password", canFillGeneratedPassword);

View File

@ -7,6 +7,7 @@
prefs =
# Skip migration work in BG__migrateUI for browser_startup.js since it isn't
# representative of common startup.
browser.messaging-system.fxatoolbarbadge.enabled=false # Bug 1570336
browser.migration.version=9999999
browser.urlbar.quantumbar=true
browser.startup.record=true

View File

@ -130,7 +130,7 @@ add_task(async function testSettingsButton() {
gBrowser,
"about:preferences#privacy"
);
gProtectionsHandler._protectionPopupSettingsButton.click();
gProtectionsHandler._protectionsPopupSettingsButton.click();
// The protection popup should be hidden after clicking settings button.
await popuphiddenPromise;
@ -144,9 +144,9 @@ add_task(async function testSettingsButton() {
});
/**
* A test for the 'Show Full Report' button in the footer seciton.
* A test for the 'Show Full Report' button in the footer section.
*/
add_task(async function testShowFullReportLink() {
add_task(async function testShowFullReportButton() {
// Open a tab and its protection panel.
let tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
@ -162,11 +162,11 @@ add_task(async function testShowFullReportLink() {
gBrowser,
"about:protections"
);
let showFullReportLink = document.getElementById(
"protections-popup-show-full-report-link"
let showFullReportButton = document.getElementById(
"protections-popup-show-report-button"
);
showFullReportLink.click();
showFullReportButton.click();
// The protection popup should be hidden after clicking the link.
await popuphiddenPromise;

View File

@ -17,6 +17,10 @@ async function openProtectionsPanel(toast) {
let shieldIconContainer = document.getElementById(
"tracking-protection-icon-container"
);
// Focus to the icon container in order to fetch tracker count.
shieldIconContainer.focus();
if (!toast) {
EventUtils.synthesizeMouseAtCenter(shieldIconContainer, {});
} else {

View File

@ -22,6 +22,9 @@ add_task(async function test_ui_state_notification_calls_updateAllUI() {
});
add_task(async function test_ui_state_signedin() {
const msBadgeEnabled = Services.prefs.getBoolPref(
"browser.messaging-system.fxatoolbarbadge.enabled"
);
const relativeDateAnchor = new Date();
let state = {
status: UIState.STATUS_SIGNED_IN,
@ -52,7 +55,9 @@ add_task(async function test_ui_state_signedin() {
checkRemoteTabsPanel("PanelUI-remotetabs-main", false);
checkMenuBarItem("sync-syncnowitem");
checkFxaToolbarButtonPanel("PanelUI-fxa-menu");
checkFxAAvatar("signedin");
if (!msBadgeEnabled) {
checkFxAAvatar("signedin");
}
gSync.relativeTimeFormat = origRelativeTimeFormat;
});
@ -82,6 +87,9 @@ add_task(async function test_ui_state_syncing() {
});
add_task(async function test_ui_state_unconfigured() {
const msBadgeEnabled = Services.prefs.getBoolPref(
"browser.messaging-system.fxatoolbarbadge.enabled"
);
let state = {
status: UIState.STATUS_NOT_CONFIGURED,
};
@ -95,7 +103,9 @@ add_task(async function test_ui_state_unconfigured() {
checkRemoteTabsPanel("PanelUI-remotetabs-setupsync");
checkMenuBarItem("sync-setup");
checkFxaToolbarButtonPanel("PanelUI-fxa-signin");
checkFxAAvatar("not_configured");
if (!msBadgeEnabled) {
checkFxAAvatar("not_configured");
}
});
add_task(async function test_ui_state_unverified() {
@ -259,6 +269,16 @@ async function checkFxaToolbarButtonPanel(expectedShownItemId) {
);
}
async function checkFxABadged() {
const button = document.getElementById("fxa-toolbar-menu-button");
await BrowserTestUtils.waitForCondition(() => {
return button.querySelector("label.feature-callout");
});
const badge = button.querySelector("label.feature-callout");
ok(badge, "expected feature-callout style badge");
ok(BrowserTestUtils.is_visible(badge), "expected the badge to be visible");
}
// fxaStatus is one of 'not_configured', 'unverified', or 'signedin'.
function checkFxAAvatar(fxaStatus) {
const avatarContainers = [

View File

@ -316,10 +316,27 @@ var AboutProtectionsHandler = {
if (largest < dataToSend[timestamp].total) {
largest = dataToSend[timestamp].total;
}
dataToSend.largest = largest;
}
dataToSend.largest = largest;
dataToSend.earliestDate = earliestDate;
dataToSend.sumEvents = sumEvents;
let weekdays = Services.intl.getDisplayNames(undefined, {
style: "short",
keys: [
"dates/gregorian/weekdays/sunday",
"dates/gregorian/weekdays/monday",
"dates/gregorian/weekdays/tuesday",
"dates/gregorian/weekdays/wednesday",
"dates/gregorian/weekdays/thursday",
"dates/gregorian/weekdays/friday",
"dates/gregorian/weekdays/saturday",
"dates/gregorian/weekdays/sunday",
],
});
weekdays = Object.values(weekdays.values);
dataToSend.weekdays = weekdays;
this.sendMessage(
aMessage.target,
"SendContentBlockingRecords",

View File

@ -89,7 +89,7 @@
</template>
<template id="login-list-item-template">
<li class="login-list-item">
<li class="login-list-item" role="option">
<span class="title"></span>
<span class="username"></span>
</li>

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

@ -15,7 +15,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({});
@ -33,6 +36,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();
@ -49,68 +53,51 @@ 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 (
this._breachesByLoginGUID &&
this._breachesByLoginGUID.has(login.guid)
this._breachesByLoginGUID.has(listItem.dataset.guid)
) {
listItem.classList.add("breached");
}
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) {
@ -122,8 +109,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;
}
@ -131,7 +118,7 @@ export default class LoginList extends HTMLElement {
new CustomEvent("AboutLoginsLoginSelected", {
bubbles: true,
composed: true,
detail: loginListItem._login,
detail: listItem._login,
})
);
@ -139,13 +126,12 @@ export default class LoginList extends HTMLElement {
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(
@ -157,7 +143,7 @@ export default class LoginList extends HTMLElement {
}
case "AboutLoginsCreateLogin": {
this._selectedGuid = null;
this.render({ createLogin: true });
this._setListItemAsSelected(this._blankLoginListItem);
break;
}
case "AboutLoginsFilterLogins": {
@ -191,16 +177,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])"
@ -218,7 +206,8 @@ export default class LoginList extends HTMLElement {
}
/**
* @param {Map} breachesByLoginGUID A Map of breaches by login GUIDs used for displaying breached login indicators.
* @param {Map} breachesByLoginGUID A Map of breaches by login GUIDs used
* for displaying breached login indicators.
*/
updateBreaches(breachesByLoginGUID) {
this._breachesByLoginGUID = breachesByLoginGUID;
@ -229,56 +218,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 &&
@ -366,9 +405,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

@ -6,6 +6,7 @@ requestLongerTimeout(2);
ChromeUtils.import("resource://testing-common/TelemetryTestUtils.jsm", this);
function waitForTelemetryEventCount(count) {
info("waiting for telemetry event count of " + count);
return TestUtils.waitForCondition(() => {
let events = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
@ -77,7 +78,7 @@ add_task(async function test_telemetry_events() {
let promiseNewTab = BrowserTestUtils.waitForNewTab(
gBrowser,
TEST_LOGIN1.origin
TEST_LOGIN2.origin
);
await ContentTask.spawn(gBrowser.selectedBrowser, null, async function() {
let loginItem = content.document.querySelector("login-item");
@ -87,7 +88,7 @@ add_task(async function test_telemetry_events() {
openSiteButton.click();
});
let newTab = await promiseNewTab;
ok(true, "New tab opened to " + TEST_LOGIN1.origin);
ok(true, "New tab opened to " + TEST_LOGIN2.origin);
BrowserTestUtils.removeTab(newTab);
await waitForTelemetryEventCount(4);

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");

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;
});
}

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");
@ -103,9 +104,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
);
@ -131,8 +133,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

@ -101,21 +101,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});
@ -132,7 +132,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");
@ -141,7 +141,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,
@ -151,7 +151,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_populated_list() {
add_task(async function test_breach_indicator() {
gLoginList.updateBreaches(TEST_BREACHES_MAP);
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item:not([hidden])");
ok(loginListItems[0].classList.contains("breached"), "The first login should have the .breached class.");
ok(!loginListItems[1].classList.contains("breached"), "The second login should not have the .breached class");
});
@ -173,7 +173,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");
@ -182,7 +182,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", {
@ -190,7 +190,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", {
@ -198,7 +198,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", {
@ -206,7 +206,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");
});
@ -215,7 +215,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,
@ -227,10 +227,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");
@ -244,7 +248,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");
@ -252,18 +256,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");
@ -275,32 +277,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

@ -22,6 +22,7 @@ const TEST_BREACHES = [
Domain: "breached.com",
Name: "Breached",
PwnCount: 1643100,
DataClasses: ["Email addresses", "Usernames", "Passwords", "IP addresses"],
_status: "synced",
id: "047940fe-d2fd-4314-b636-b4a952ee0043",
last_modified: "1541615610052",
@ -33,11 +34,24 @@ const TEST_BREACHES = [
Domain: "breached-subdomain.host.com",
Name: "Only a Sub-Domain was Breached",
PwnCount: 2754200,
DataClasses: ["Email addresses", "Usernames", "Passwords", "IP addresses"],
_status: "synced",
id: "047940fe-d2fd-4314-b636-b4a952ee0044",
last_modified: "1541615610052",
schema: "1541615609018",
},
{
AddedDate: "2018-12-20T23:56:26Z",
BreachDate: "2018-12-16",
Domain: "breached-site-without-passwords.com",
Name: "Breached Site without passwords",
PwnCount: 987654,
DataClasses: ["Email addresses", "Usernames", "IP addresses"],
_status: "synced",
id: "047940fe-d2fd-4314-b636-b4a952ee0045",
last_modified: "1541615610052",
schema: "1541615609018",
},
];
const NOT_BREACHED_LOGIN = LoginTestUtils.testData.formLogin({
@ -67,6 +81,15 @@ const BREACHED_SUBDOMAIN_LOGIN = LoginTestUtils.testData.formLogin({
password: "password",
timePasswordChanged: new Date("2018-12-15").getTime(),
});
const LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS = LoginTestUtils.testData.formLogin(
{
origin: "https://breached-site-without-passwords.com",
formActionOrigin: "https://breached-site-without-passwords.com",
username: "username",
password: "password",
timePasswordChanged: new Date("2018-12-15").getTime(),
}
);
add_task(async function test_getBreachesForLogins_notBreachedLogin() {
Services.logins.addLogin(NOT_BREACHED_LOGIN);
@ -123,3 +146,20 @@ add_task(async function test_getBreachesForLogins_breachedSubdomain() {
"Should be 1 breached login: " + BREACHED_SUBDOMAIN_LOGIN.origin
);
});
add_task(
async function test_getBreachesForLogins_breachedSiteWithoutPasswords() {
Services.logins.addLogin(LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS);
const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
[LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS],
TEST_BREACHES
);
Assert.strictEqual(
breachesByLoginGUID.size,
0,
"Should be 0 breached login: " +
LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS.origin
);
}
);

View File

@ -49,7 +49,8 @@
<vbox id="protections-popup-category-list">
<toolbarbutton id="protections-popup-category-tracking-protection"
onclick="gProtectionsHandler.showTrackersSubview();"
class="protections-popup-category" align="center">
class="protections-popup-category" align="center"
wrap="true">
<image class="protections-popup-category-icon tracking-protection-icon"/>
<label flex="1" class="protections-popup-category-label">&contentBlocking.trackingProtection3.label;</label>
<label flex="1" id="protections-popup-tracking-protection-state-label" class="protections-popup-category-state-label"/>
@ -64,7 +65,8 @@
</toolbarbutton>
<toolbarbutton id="protections-popup-category-cookies"
onclick="gProtectionsHandler.showCookiesSubview();"
class="protections-popup-category" align="center">
class="protections-popup-category" align="center"
wrap="true">
<image class="protections-popup-category-icon thirdpartycookies-icon"/>
<label flex="1" id="protections-popup-category-label-default"
class="protections-popup-category-label">&contentBlocking.cookies.label;</label>
@ -72,14 +74,16 @@
</toolbarbutton>
<toolbarbutton id="protections-popup-category-cryptominers"
onclick="gProtectionsHandler.showCryptominersSubview();"
class="protections-popup-category" align="center">
class="protections-popup-category" align="center"
wrap="true">
<image class="protections-popup-category-icon cryptominers-icon"/>
<label flex="1" class="protections-popup-category-label">&contentBlocking.cryptominers.label;</label>
<label flex="1" id="protections-popup-cryptominers-state-label" class="protections-popup-category-state-label"/>
</toolbarbutton>
<toolbarbutton id="protections-popup-category-fingerprinters"
onclick="gProtectionsHandler.showFingerprintersSubview();"
class="protections-popup-category" align="center">
class="protections-popup-category" align="center"
wrap="true">
<image class="protections-popup-category-icon fingerprinters-icon"/>
<label flex="1" class="protections-popup-category-label">&contentBlocking.fingerprinters.label;</label>
<label flex="1" id="protections-popup-fingerprinters-state-label" class="protections-popup-category-state-label"/>
@ -88,24 +92,28 @@
</vbox>
</hbox>
<vbox id="protections-popup-settings-section"
class="protections-popup-section">
<vbox id="protections-popup-footer" class="protections-popup-section">
<toolbarbutton id="protections-popup-settings-button"
class="protections-popup-footer-button"
oncommand="gProtectionsHandler.openPreferences();">
<image class="protection-settings-icon"/>
<label class="protections-popup-settings-label" flex="1">&protections.settings.label;</label>
<image class="protection-popup-footer-icon protections-popup-settings-icon"/>
<label class="protections-popup-footer-button-label" flex="1">&protections.settings.label;</label>
</toolbarbutton>
<stack id="protections-popup-show-report-stack">
<toolbarbutton id="protections-popup-show-report-button"
class="protections-popup-footer-button"
oncommand="gProtectionsHandler.openProtections(true);">
<image class="protection-popup-footer-icon protections-popup-show-report-icon"/>
<label class="protections-popup-footer-button-label" flex="1">&protections.report.label;</label>
</toolbarbutton>
<hbox id="protections-popup-trackers-blocked-counter-box"
align="center"
right="0">
<description id="protections-popup-trackers-blocked-counter-description"
onclick="gProtectionsHandler.openProtections(true);"/>
</hbox>
</stack>
</vbox>
<hbox id="protections-popup-footer">
<description id="protections-popup-trackers-blocked-counter-description"
flex="1"/>
<label id="protections-popup-show-full-report-link"
is="text-link"
useoriginprincipal="true"
href="about:protections">&protections.report.label;</label>
</hbox>
</panelview>
<!-- Site Not Working? SubView -->

View File

@ -194,7 +194,6 @@ skip-if = !e10s
fail-if = fission
[browser_ext_tabs_duplicate.js]
[browser_ext_tabs_events.js]
skip-if = true # Bug 1521363
[browser_ext_tabs_events_order.js]
[browser_ext_tabs_executeScript.js]
skip-if = (verify && !debug && (os == 'mac'))

View File

@ -77,13 +77,17 @@ add_task(async function test_tab_events_incognito_monitored() {
}
try {
let windows = await Promise.all([
browser.windows.create({ url: "about:blank", incognito }),
browser.windows.create({ url: "about:blank", incognito }),
]);
let firstWindow = await browser.windows.create({
url: "about:blank",
incognito,
});
let otherWindow = await browser.windows.create({
url: "about:blank",
incognito,
});
let windowId = windows[0].id;
let otherWindowId = windows[1].id;
let windowId = firstWindow.id;
let otherWindowId = otherWindow.id;
let created = await expectEvents(["onCreated", "onCreated"]);
let initialTab = created[1].tab;

View File

@ -36,6 +36,7 @@ add_task(async function startup() {
// engine from over the network.
let engine = await Services.search.addEngineWithDetails("Test engine", {
template: "http://example.com/?s=%S",
alias: "@testengine",
});
Services.search.defaultEngine = engine;
@ -298,6 +299,186 @@ add_task(async function test_onProviderResultsRequested() {
await ext.unload();
});
// Extensions can specify search engines using engine names, aliases, and URLs.
add_task(async function test_onProviderResultsRequested_searchEngines() {
let ext = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["urlbar"],
},
isPrivileged: true,
incognitoOverride: "spanning",
background() {
browser.urlbar.onBehaviorRequested.addListener(query => {
return "restricting";
}, "test");
browser.urlbar.onResultsRequested.addListener(query => {
return [
{
type: "search",
source: "search",
payload: {
engine: "Test engine",
suggestion: "engine specified",
},
},
{
type: "search",
source: "search",
payload: {
keyword: "@testengine",
suggestion: "keyword specified",
},
},
{
type: "search",
source: "search",
payload: {
url: "http://example.com/?s",
suggestion: "url specified",
},
},
{
type: "search",
source: "search",
payload: {
engine: "Test engine",
keyword: "@testengine",
url: "http://example.com/?s",
suggestion: "engine, keyword, and url specified",
},
},
{
type: "search",
source: "search",
payload: {
keyword: "@testengine",
url: "http://example.com/?s",
suggestion: "keyword and url specified",
},
},
{
type: "search",
source: "search",
payload: {
suggestion: "no engine",
},
},
{
type: "search",
source: "search",
payload: {
engine: "bogus",
suggestion: "no matching engine",
},
},
{
type: "search",
source: "search",
payload: {
keyword: "@bogus",
suggestion: "no matching keyword",
},
},
{
type: "search",
source: "search",
payload: {
url: "http://bogus-no-search-engine.com/",
suggestion: "no matching url",
},
},
{
type: "search",
source: "search",
payload: {
url: "bogus",
suggestion: "invalid url",
},
},
{
type: "search",
source: "search",
payload: {
url: "foo:bar",
suggestion: "url with no hostname",
},
},
];
}, "test");
},
});
await ext.startup();
// Run a query.
let context = new UrlbarQueryContext({
allowAutofill: false,
isPrivate: false,
maxResults: 10,
searchString: "test",
});
let controller = new UrlbarController({
browserWindow: {
location: {
href: AppConstants.BROWSER_CHROME_URL,
},
},
});
await controller.startQuery(context);
// Check the results. The first several are valid and should include "Test
// engine" as the engine. The others don't specify an engine and are
// therefore invalid, so they should be ignored.
let expectedResults = [
{
type: UrlbarUtils.RESULT_TYPE.SEARCH,
source: UrlbarUtils.RESULT_SOURCE.SEARCH,
engine: "Test engine",
title: "engine specified",
heuristic: false,
},
{
type: UrlbarUtils.RESULT_TYPE.SEARCH,
source: UrlbarUtils.RESULT_SOURCE.SEARCH,
engine: "Test engine",
title: "keyword specified",
heuristic: false,
},
{
type: UrlbarUtils.RESULT_TYPE.SEARCH,
source: UrlbarUtils.RESULT_SOURCE.SEARCH,
engine: "Test engine",
title: "url specified",
heuristic: false,
},
{
type: UrlbarUtils.RESULT_TYPE.SEARCH,
source: UrlbarUtils.RESULT_SOURCE.SEARCH,
engine: "Test engine",
title: "engine, keyword, and url specified",
heuristic: false,
},
{
type: UrlbarUtils.RESULT_TYPE.SEARCH,
source: UrlbarUtils.RESULT_SOURCE.SEARCH,
engine: "Test engine",
title: "keyword and url specified",
heuristic: false,
},
];
let actualResults = context.results.map(r => ({
type: r.type,
source: r.source,
engine: r.payload.engine || null,
title: r.title,
heuristic: r.heuristic,
}));
Assert.deepEqual(actualResults, expectedResults);
await ext.unload();
});
// Adds two providers, one active and one inactive. Only the active provider
// should be asked to return results.
add_task(async function test_activeAndInactiveProviders() {

View File

@ -37,6 +37,7 @@ Please note that some targeting attributes require stricter controls on the tele
* [hasAccessedFxAPanel](#hasaccessedfxapanel)
* [isWhatsNewPanelEnabled](#iswhatsnewpanelenabled)
* [earliestFirefoxVersion](#earliestfirefoxversion)
* [isFxABadgeEnabled](#isfxabadgeenabled)
## Detailed usage
@ -507,3 +508,13 @@ Integer value of the first Firefox version the profile ran on
```ts
declare const earliestFirefoxVersion: boolean;
```
### `isFxABadgeEnabled`
Boolean pref that controls if the FxA toolbar button is badged by Messaging System.
#### Definition
```ts
declare const isFxABadgeEnabled: boolean;
```

View File

@ -416,6 +416,12 @@ const TargetingGetters = {
return null;
},
get isFxABadgeEnabled() {
return Services.prefs.getBoolPref(
"browser.messaging-system.fxatoolbarbadge.enabled",
false
);
},
};
this.ASRouterTargeting = {

View File

@ -366,6 +366,17 @@ const ONBOARDING_MESSAGES = () => [
"attributionData.campaign == 'non-fx-button' && attributionData.source == 'addons.mozilla.org'",
trigger: { id: "firstRun" },
},
{
id: "FXA_ACCOUNTS_BADGE",
template: "toolbar_badge",
content: {
delay: 10000, // delay for 10 seconds
target: "fxa-toolbar-menu-button",
},
// Never accessed the FxA panel && doesn't use Firefox sync & has FxA enabled
targeting: `isFxABadgeEnabled && !hasAccessedFxAPanel && !usesFirefoxSync && isFxAEnabled == true`,
trigger: { id: "toolbarBadgeUpdate" },
},
];
const OnboardingMessageProvider = {

View File

@ -50,16 +50,6 @@ const MESSAGES = () => [
},
trigger: { id: "bookmark-panel" },
},
{
id: "FXA_ACCOUNTS_BADGE",
template: "toolbar_badge",
content: {
target: "fxa-toolbar-menu-button",
},
// Never accessed the FxA panel && doesn't use Firefox sync & has FxA enabled
targeting: `!hasAccessedFxAPanel && !usesFirefoxSync && isFxAEnabled == true`,
trigger: { id: "toolbarBadgeUpdate" },
},
{
id: `WHATS_NEW_BADGE_${FIREFOX_VERSION}`,
template: "toolbar_badge",

View File

@ -4,7 +4,7 @@ const messages = PanelTestProvider.getMessages();
describe("PanelTestProvider", () => {
it("should have a message", () => {
assert.lengthOf(messages, 7);
assert.lengthOf(messages, 6);
});
it("should be a valid message", () => {
assert.jsonSchema(messages[0].content, schema);

View File

@ -1,6 +1,7 @@
import { _ToolbarBadgeHub } from "lib/ToolbarBadgeHub.jsm";
import { GlobalOverrider } from "test/unit/utils";
import { PanelTestProvider } from "lib/PanelTestProvider.jsm";
import { OnboardingMessageProvider } from "lib/OnboardingMessageProvider.jsm";
import { _ToolbarPanelHub } from "lib/ToolbarPanelHub.jsm";
describe("ToolbarBadgeHub", () => {
@ -25,9 +26,12 @@ describe("ToolbarBadgeHub", () => {
fakeAddImpression = sandbox.stub();
fakeDispatch = sandbox.stub();
isBrowserPrivateStub = sandbox.stub();
const msgs = await PanelTestProvider.getMessages();
fxaMessage = msgs.find(({ id }) => id === "FXA_ACCOUNTS_BADGE");
whatsnewMessage = msgs.find(({ id }) => id.includes("WHATS_NEW_BADGE_"));
const panelTestMsgs = await PanelTestProvider.getMessages();
const onboardingMsgs = await OnboardingMessageProvider.getUntranslatedMessages();
fxaMessage = onboardingMsgs.find(({ id }) => id === "FXA_ACCOUNTS_BADGE");
whatsnewMessage = panelTestMsgs.find(({ id }) =>
id.includes("WHATS_NEW_BADGE_")
);
fakeElement = {
classList: {
add: sandbox.stub(),

View File

@ -53,14 +53,15 @@ export default class LockwiseCard {
container.classList.remove("hidden");
if (isLoggedIn) {
title.textContent = "Firefox Lockwise";
headerContent.textContent =
"Securely store and sync your passwords to all your devices.";
title.setAttribute("data-l10n-id", "lockwise-title-logged-in");
headerContent.setAttribute(
"data-l10n-id",
"lockwise-header-content-logged-in"
);
this.renderContentForLoggedInUser(container, numLogins, numSyncedDevices);
} else {
title.textContent = "Never forget a password again";
headerContent.textContent =
"Firefox Lockwise securely stores your passwords in your browser.";
title.setAttribute("data-l10n-id", "lockwise-title");
headerContent.setAttribute("data-l10n-id", "lockwise-header-content");
}
}
@ -81,6 +82,18 @@ export default class LockwiseCard {
);
numberOfLoginsBlock.textContent = storedLogins;
const lockwisePasswordsStored = this.doc.getElementById(
"lockwise-passwords-stored"
);
lockwisePasswordsStored.setAttribute(
"data-l10n-args",
JSON.stringify({ count: storedLogins })
);
lockwisePasswordsStored.setAttribute(
"data-l10n-id",
"lockwise-passwords-stored"
);
// Set the text for the number of synced devices.
const syncedDevicesBlock = container.querySelector(
".number-of-synced-devices.block"
@ -89,11 +102,15 @@ export default class LockwiseCard {
const syncedDevicesText = container.querySelector(".synced-devices-text");
const textEl = syncedDevicesText.querySelector("span");
textEl.textContent =
syncedDevices > 0
? `Syncing to ${syncedDevices} other devices.`
: "Not syncing to other devices.";
if (syncedDevices) {
textEl.setAttribute(
"data-l10n-args",
JSON.stringify({ count: syncedDevices })
);
textEl.setAttribute("data-l10n-id", "lockwise-sync-status");
} else {
textEl.setAttribute("data-l10n-id", "lockwise-sync-not-syncing");
}
// Display the link for enabling sync if no synced devices are detected.
if (syncedDevices === 0) {
const syncLink = syncedDevicesText.querySelector("a");

View File

@ -38,28 +38,26 @@ export default class MonitorClass {
}
buildContent(loginData, monitorData) {
const { hasFxa, numLogins } = loginData;
const isLoggedIn = numLogins > 0 || hasFxa;
const { numLogins } = loginData;
const headerContent = this.doc.querySelector(
"#monitor-header-content span"
);
const monitorCard = this.doc.querySelector(".card.monitor-card");
if (isLoggedIn && !monitorData.error) {
if (numLogins > 0 && !monitorData.error) {
monitorCard.classList.add("has-logins");
headerContent.textContent =
"Firefox Monitor warns you if your info has appeared in a known data breach";
headerContent.setAttribute(
"data-l10n-id",
"monitor-header-content-logged-in"
);
this.renderContentForUserWithLogins(monitorData);
} else {
monitorCard.classList.add("no-logins");
const signUpForMonitorLink = this.doc.getElementById(
"sign-up-for-monitor-link"
);
signUpForMonitorLink.textContent = hasFxa
? "Turn on Monitor"
: "Sign up for Monitor";
signUpForMonitorLink.href = this.buildMonitorUrl(monitorData.userEmail);
headerContent.textContent =
"Check Firefox Monitor to see if you've been part of a data breach and get alerts about new breaches.";
signUpForMonitorLink.setAttribute("data-l10n-id", "monitor-sign-up");
headerContent.setAttribute("data-l10n-id", "monitor-header-content");
}
}
@ -101,6 +99,34 @@ export default class MonitorClass {
knownBreaches.textContent = monitorData.numBreaches;
exposedPasswords.textContent = monitorData.passwords;
const infoMonitoredAddresses = this.doc.getElementById(
"info-monitored-addresses"
);
infoMonitoredAddresses.setAttribute(
"data-l10n-args",
JSON.stringify({ count: monitorData.monitoredEmails })
);
infoMonitoredAddresses.setAttribute(
"data-l10n-id",
"info-monitored-addresses"
);
const infoKnownBreaches = this.doc.getElementById("info-known-breaches");
infoKnownBreaches.setAttribute(
"data-l10n-args",
JSON.stringify({ count: monitorData.numBreaches })
);
infoKnownBreaches.setAttribute("data-l10n-id", "info-known-breaches");
const infoExposedPasswords = this.doc.getElementById(
"info-exposed-passwords"
);
infoExposedPasswords.setAttribute(
"data-l10n-args",
JSON.stringify({ count: monitorData.passwords })
);
infoExposedPasswords.setAttribute("data-l10n-id", "info-exposed-passwords");
// Display Lockwise section if there are any potential breached logins to report.
if (monitorData.potentiallyBreachedLogins > 0) {
const lockwiseSection = this.doc.querySelector(
@ -113,6 +139,14 @@ export default class MonitorClass {
exposedLockwisePasswords.textContent =
monitorData.potentiallyBreachedLogins;
let breachedPasswordWarning = this.doc.getElementById("password-warning");
breachedPasswordWarning.setAttribute(
"data-l10n-args",
JSON.stringify({ count: monitorData.potentiallyBreachedLogins })
);
breachedPasswordWarning.setAttribute("data-l10n-id", "password-warning");
lockwiseSection.classList.remove("hidden");
}
}

View File

@ -25,8 +25,105 @@ graph-total-summary =
}
# The terminology used to refer to categories of Content Blocking is also used in chrome/browser/browser.properties and should be translated consistently.
# The category name in the <b> tag will be bold.
# "Standard" in this case is an adjective, meaning "default" or "normal".
# The category name in the <b> tag will be bold.
protection-header-details-standard = Protection Level is set to <b>Standard</b>
protection-header-details-strict = Protection Level is set to <b>Strict</b>
protection-header-details-custom = Protection Level is set to <b>Custom</b>
protection-report-page-title = Privacy Protections
protection-report-content-title = Privacy Protections
etp-card-title = Enhanced Tracking Protection
etp-card-content = Trackers follow you around online to collect information about your browsing habits and interests. { -brand-short-name } blocks many of these trackers and other malicious scripts.
# This string is used to label the X axis of a graph. Other days of the week are generated via Intl.DateTimeFormat,
# capitalization for this string should match the output for your locale.
graph-today = Today
social-tab-title = Social Media Trackers
social-tab-contant = Social media like, post, and comment buttons on other websites can track you — even if you dont use them. Logging in to sites using your Facebook or Twitter account is another way they can track what you do on those sites. We remove these trackers so Facebook and Twitter see less of what you do online.
cookie-tab-title = Cross-Site Tracking Cookies
cookie-tab-content = Cross-site tracking cookies follow you from site to site to collect data about your browsing habits. Advertisers and analytics companies gather this data to create a profile of your interests across many sites. Blocking them reduces the number of personalized ads that follow you around.
tracker-tab-title = Tracking Content
tracker-tab-content = Websites may load outside ads, videos, and other content that contain hidden trackers. Blocking tracking content can make websites load faster, but some buttons, forms, and login fields might not work.
fingerprinter-tab-title = Fingerprinters
fingerprinter-tab-content = Fingerprinting is a form of online tracking thats different from your real fingerprints. Companies use it to create a unique profile of you using data about your browser, device, and other settings. We block ad trackers from fingerprinting your device.
cryptominer-tab-title = Cryptominers
cryptominer-tab-content = Some websites host hidden malware that secretly uses your systems computing power to mine cryptocurrency, or digital money. It drains your battery, slows down your computer, and increases your energy bill. We block known cryptominers from using your computing resources to make money.
lockwise-title = Never forget a password again
lockwise-title-logged-in = { -lockwise-brand-name }
lockwise-header-content = { -lockwise-brand-name } securely stores your passwords in your browser.
lockwise-header-content-logged-in = Securely store and sync your passwords to all your devices.
open-about-logins-button = Open in { -brand-short-name }
lockwise-no-logins-content = Get the <a data-l10n-name="lockwise-inline-link">{ -lockwise-brand-name }</a> app to take your passwords everywhere.
# This string is displayed after a large numeral that indicates the total number
# of email addresses being monitored. Dont add $count to
# your localization, because it would result in the number showing twice.
lockwise-passwords-stored =
{ $count ->
[one] Password stored securely. <a data-l10n-name="lockwise-how-it-works">How it works</a>
*[other] Passwords stored securely. <a data-l10n-name="lockwise-how-it-works">How it works</a>
}
turn-on-sync = Turn on { -sync-brand-short-name }…
.title = Go to sync preferences
# Variables:
# $count (Number) - Number of devices connected with sync.
lockwise-sync-status =
{ $count ->
[one] Syncing to { $count } other device.
*[other] Syncing to { $count } other devices.
}
lockwise-sync-not-syncing = Not syncing to other devices.
monitor-title = Look out for data breaches
monitor-link = How it works
monitor-header-content = Check { -monitor-brand-name } to see if youve been part of a data breach and get alerts about new breaches.
monitor-header-content-logged-in = { -monitor-brand-name } warns you if your info has appeared in a known data breach
monitor-sign-up = Sign up for Breach Alerts
auto-scan = Automatically scanned today
# This string is displayed after a large numeral that indicates the total number
# of email addresses being monitored. Dont add $count to
# your localization, because it would result in the number showing twice.
info-monitored-addresses =
{ $count ->
[one] Email address being monitored.
*[other] Email addresses being monitored.
}
# This string is displayed after a large numeral that indicates the total number
# of known data breaches. Dont add $count to
# your localization, because it would result in the number showing twice.
info-known-breaches =
{ $count ->
[one] Known data breach has exposed your information.
*[other] Known data breaches have exposed your information.
}
# This string is displayed after a large numeral that indicates the total number
# of exposed passwords. Dont add $count to
# your localization, because it would result in the number showing twice.
info-exposed-passwords =
{ $count ->
[one] Password exposed across all breaches.
*[other] Passwords exposed across all breaches.
}
full-report-link = View full report on <a data-l10n-name="monitor-inline-link">{ -monitor-brand-name }</a>
# This string is displayed after a large numeral that indicates the total number
# of saved logins which may have been exposed. Dont add $count to
# your localization, because it would result in the number showing twice.
password-warning =
{ $count ->
[one] Saved login may have been exposed in a data breach. Change this password for better online security. <a data-l10n-name="lockwise-link">View Saved Logins</a>
*[other] Saved logins may have been exposed in a data breach. Change these passwords for better online security. <a data-l10n-name="lockwise-link">View Saved Logins</a>
}

View File

@ -7,7 +7,9 @@
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src chrome: blob:">
<link rel="localization" href="browser/branding/brandings.ftl"/>
<link rel="localization" href="branding/brand.ftl"/>
<link rel="localization" href="browser/branding/sync-brand.ftl">
<!-- rename to browser/protections.ftl when exposing to l10n -->
<link rel="localization" href="preview/protections.ftl">
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
@ -16,22 +18,18 @@
<script type="module" src="chrome://browser/content/protections.js"></script>
<script type="module" src="chrome://browser/content/lockwise-card.js"></script>
<script type="module" src="chrome://browser/content/monitor-card.js"></script>
<title>Protection Report</title>
<title data-l10n-id="protection-report-page-title"></title>
</head>
<body>
<div id="report-content">
<h2 id="report-title">Privacy Protections</h2>
<h2 id="report-title" data-l10n-id="protection-report-content-title"></h2>
<div class="card card-no-hover etp-card">
<div class="card-header">
<div class="icon"></div>
<div class="wrapper">
<h3 class="card-title">
Enhanced Tracking Protection
</h3>
<p class="content">
Trackers follow you around online to collect information about your browsing habits and interests. Firefox blocks many of these trackers and other malicious scripts.
</p>
<h3 class="card-title" data-l10n-id="etp-card-title"></h3>
<p class="content" data-l10n-id="etp-card-content"></p>
<p id="protection-details"></p>
</div>
</div>
@ -57,24 +55,24 @@
<label for="tab-cryptominer" data-type="cryptominer"></label>
<div id="social" class="tab-content">
<p class="content-title">Social Media Trackers</p>
<p>Social media like, post, and comment buttons on other websites can track you — even if you dont use them. Logging in to sites using your Facebook or Twitter account is another way they can track what you do on those sites. We remove these trackers so Facebook and Twitter see less of what you do online.</p>
<p class="content-title" data-l10n-id="social-tab-title"></p>
<p data-l10n-id="social-tab-contant"></p>
</div>
<div id="cookie" class="tab-content">
<p class="content-title">Cross-Site Tracking Cookies</p>
<p>Cross-site tracking cookies follow you from site to site to collect data about your browsing habits. Advertisers and analytics companies gather this data to create a profile of your interests across many sites. Blocking them reduces the number of personalized ads that follow you around.</p>
<p class="content-title" data-l10n-id="cookie-tab-title"></p>
<p data-l10n-id="cookie-tab-content"></p>
</div>
<div id="tracker" class="tab-content">
<p class="content-title">Tracking Content</p>
<p>Websites may load outside ads, videos, and other content that contain hidden trackers. Blocking tracking content can make websites load faster, but some buttons, forms, and login fields might not work.</p>
<p class="content-title" data-l10n-id="tracker-tab-title"></p>
<p data-l10n-id="tracker-tab-content"></p>
</div>
<div id="fingerprinter" class="tab-content">
<p class="content-title">Fingerprinters</p>
<p>Fingerprinting is a form of online tracking thats different from your real fingerprints. Companies use it to create a unique profile of you using data about your browser, device, and other settings. We block ad trackers from fingerprinting your device.</p>
<p class="content-title" data-l10n-id="fingerprinter-tab-title"></p>
<p data-l10n-id="fingerprinter-tab-content"></p>
</div>
<div id="cryptominer" class="tab-content">
<p class="content-title">Cryptominers</p>
<p>Some websites host hidden malware that secretly uses your systems computing power to mine cryptocurrency, or digital money. It drains your battery, slows down your computer, and increases your energy bill. We block known cryptominers from using your computing resources to make money.</p>
<p class="content-title" data-l10n-id="cryptominer-tab-title"></p>
<p data-l10n-id="cryptominer-tab-content"></p>
</div>
</div>
</div>
@ -87,18 +85,14 @@
<div class="card-header">
<div class="icon"></div>
<div class="wrapper">
<h3 id="monitor-title" class="card-title">
Look out for data breaches
</h3>
<h3 id="monitor-title" class="card-title" data-l10n-id="monitor-title"></h3>
<p id="monitor-header-content" class="content">
<span>
<!-- Insert Monitor header content here. -->
</span>
<a href="">How it works</a>
<a href="" data-l10n-id="monitor-link"></a>
</p>
<span class="inline-text-icon monitor-scanned-text">
Automatically scanned today
</span>
<span class="inline-text-icon monitor-scanned-text" data-l10n-id="auto-scan"></span>
</div>
<a target="_blank" id="sign-up-for-monitor-link">
<!-- Insert Monitor link content here. -->
@ -114,7 +108,7 @@
<!-- Display number of stored emails here. -->
</span>
</span>
<span class="info-text">Email addresses being monitored</span>
<span id="info-monitored-addresses" class="info-text"></span>
</div>
<div class="monitor-block breaches">
<span class="monitor-stat">
@ -123,7 +117,7 @@
<!-- Display number of known breaches here. -->
</span>
</span>
<span class="info-text">Known data breaches have exposed your information</span>
<span id="info-known-breaches" class="info-text"></span>
</div>
<div class="monitor-block passwords">
<span class="monitor-stat">
@ -132,19 +126,17 @@
<!-- Display number of exposed passwords here. -->
</span>
</span>
<span class="info-text">Passwords exposed across all breaches</span>
<span id="info-exposed-passwords" class="info-text"></span>
</div>
<div class="monitor-view-full-report">
<span>View full report on</span>
<a href="">Firefox Monitor</a>
<div class="monitor-view-full-report" data-l10n-id="full-report-link">
<a data-l10n-name="monitor-inline-link" href=""></a>
</div>
<div class="monitor-breached-passwords hidden">
<span data-type="breached-lockwise-passwords" class="number-of-breaches block">
<!-- Display number of exposed stored passwords here. -->
</span>
<span>
Passwords saved in Firefox may have been exposed in a data breach. Change these passwords to protect your accounts.
<a href="">View Saved Logins</a>
<span id="password-warning">
<a href="" data-l10n-name="lockwise-link"></a>
</span>
</div>
</div>
@ -163,24 +155,23 @@
<!-- Insert Lockwise header content here. -->
</p>
</div>
<button id="open-about-logins-button" class="primary">Open in Firefox</button>
<button id="open-about-logins-button" class="primary" data-l10n-id="open-about-logins-button"></button>
</div>
<div class="card-body">
<div id="lockwise-body-content" class="body-wrapper">
<div class="no-logins hidden">
<div class="lockwise-mobile-app-icon"></div>
<span>
Get the <a target="_blank" href="https://lockwise.firefox.com/">Firefox Lockwise</a>
app to take your passwords everywhere.
<span data-l10n-id="lockwise-no-logins-content">
<a data-l10n-name="lockwise-inline-link" href=""></a>
</span>
</div>
<div class="has-logins hidden">
<span class="number-of-logins block">
<!-- Display number of stored logins here. -->
</span>
<span class="inline-text-icon passwords-stored-text">
Passwords stored securely.
<a href="">How it works</a>
<span id="lockwise-passwords-stored" class="inline-text-icon passwords-stored-text">
<!-- Display message for stored logins here. -->
<a data-l10n-name="lockwise-how-it-works" href=""></a>
</span>
<span class="number-of-synced-devices block">
<!-- Display number of synced devices here. -->
@ -189,10 +180,9 @@
<span>
<!-- Display message for status of synced devices here. -->
</span>
<a class="hidden" href="" title="Go to sync preferences">Turn on sync…</a>
<a class="hidden" href="" data-l10n-id="turn-on-sync"></a>
</span>
</div>
</div>
</div>
</div>
</section>

View File

@ -14,7 +14,6 @@ document.addEventListener("DOMContentLoaded", e => {
from: weekAgoInMs,
to: todayInMs,
});
let dataTypes = [
"cryptominer",
"fingerprinter",
@ -22,7 +21,6 @@ document.addEventListener("DOMContentLoaded", e => {
"cookie",
"social",
];
let weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
let protectionDetails = document.getElementById("protection-details");
protectionDetails.addEventListener("click", () => {
@ -77,12 +75,11 @@ document.addEventListener("DOMContentLoaded", e => {
cryptominer: 0,
};
let date = new Date();
let graph = document.getElementById("graph");
for (let i = weekdays.length - 1; i >= 0; i--) {
// Start 7 days ago and count down to today.
let date = new Date();
date.setDate(date.getDate() - i);
for (let i = 0; i <= 6; i++) {
let dateString = date.toISOString().split("T")[0];
let bar = document.createElement("div");
bar.className = "graph-bar";
if (data[dateString]) {
@ -109,7 +106,7 @@ document.addEventListener("DOMContentLoaded", e => {
// There were no content blocking events on this day.
bar.classList.add("empty");
}
graph.appendChild(bar);
graph.prepend(bar);
let weekSummary = document.getElementById("graph-week-summary");
weekSummary.setAttribute(
"data-l10n-args",
@ -117,6 +114,7 @@ document.addEventListener("DOMContentLoaded", e => {
);
weekSummary.setAttribute("data-l10n-id", "graph-week-summary");
// Set the total number of each type of tracker on the tabs
for (let type of dataTypes) {
document.querySelector(`label[data-type=${type}]`).textContent =
weekTypeCounts[type];
@ -125,11 +123,12 @@ document.addEventListener("DOMContentLoaded", e => {
let label = document.createElement("span");
label.className = "column-label";
if (i == 6) {
label.textContent = "Today";
label.setAttribute("data-l10n-id", "graph-today");
} else {
label.textContent = weekdays[(i + 1 + new Date().getDay()) % 7];
label.textContent = data.weekdays[(i + 1 + new Date().getDay()) % 7];
}
graph.prepend(label);
graph.append(label);
date.setDate(date.getDate() - 1);
}
addListeners();

View File

@ -121,7 +121,11 @@ add_task(async function() {
0,
"Zero synced devices are displayed."
);
is(syncedDevicesStatusText.textContent, "Not syncing to other devices.");
is(
syncedDevicesStatusText.getAttribute("data-l10n-id"),
"lockwise-sync-not-syncing",
"Not syncing to other devices."
);
});
info(
@ -160,16 +164,12 @@ add_task(async function() {
const numberOfSyncedDevices = content.document.querySelector(
".number-of-synced-devices.block"
);
const syncedDevicesStatusText = content.document.querySelector(
".synced-devices-text span"
);
is(
numberOfSyncedDevices.textContent,
5,
"Five synced devices should be displayed"
);
is(syncedDevicesStatusText.textContent, "Syncing to 5 other devices.");
});
info("Disable showing the Lockwise card.");

View File

@ -63,7 +63,7 @@ add_task(async function() {
await reloadTab(tab);
info("Check that the correct content is displayed for users with no logins.");
await checkNoLoginsContentIsDisplayed(tab, "Sign up for Monitor");
await checkNoLoginsContentIsDisplayed(tab, "monitor-sign-up");
info(
"Check that the correct content is displayed for users with monitor data."
@ -158,7 +158,7 @@ add_task(async function() {
);
AboutProtectionsHandler.getLoginData = mockGetLoginData;
await reloadTab(tab);
await checkNoLoginsContentIsDisplayed(tab, "Turn on Monitor");
await checkNoLoginsContentIsDisplayed(tab);
info("Disable showing the Monitor card.");
Services.prefs.setBoolPref(
@ -194,35 +194,32 @@ add_task(async function() {
});
async function checkNoLoginsContentIsDisplayed(tab, expectedLinkContent) {
await ContentTask.spawn(
tab.linkedBrowser,
{ linkText: expectedLinkContent },
async function({ linkText }) {
await ContentTaskUtils.waitForCondition(() => {
const noLogins = content.document.querySelector(
".monitor-card.no-logins"
);
return ContentTaskUtils.is_visible(noLogins);
}, "Monitor card for user with no logins is shown.");
await ContentTask.spawn(tab.linkedBrowser, null, async function() {
await ContentTaskUtils.waitForCondition(() => {
const noLogins = content.document.querySelector(
".monitor-card.no-logins"
);
return ContentTaskUtils.is_visible(noLogins);
}, "Monitor card for user with no logins is shown.");
const noLoginsHeaderContent = content.document.querySelector(
"#monitor-header-content span"
);
const cardBody = content.document.querySelector(
".monitor-card .card-body"
);
const link = content.document.getElementById("sign-up-for-monitor-link");
const noLoginsHeaderContent = content.document.querySelector(
"#monitor-header-content span"
);
const cardBody = content.document.querySelector(".monitor-card .card-body");
ok(
ContentTaskUtils.is_hidden(cardBody),
"Card body is hidden for users with no logins."
);
is(
noLoginsHeaderContent.textContent,
"Check Firefox Monitor to see if you've been part of a data breach and get alerts about new breaches.",
"Header content for user with no logins is correct"
);
is(link.textContent, linkText, "Text content for link is correct");
}
);
ok(
ContentTaskUtils.is_hidden(cardBody),
"Card body is hidden for users with no logins."
);
is(
noLoginsHeaderContent.getAttribute("data-l10n-id"),
"monitor-header-content",
"Header content for user with no logins is correct"
);
is(
noLoginsHeaderContent.getAttribute("data-l10n-id"),
"monitor-header-content",
"Header content for user with no logins is correct"
);
});
}

View File

@ -1317,7 +1317,11 @@ class UrlbarInput {
let isMouseEvent = event instanceof MouseEvent;
let reuseEmpty = !isMouseEvent;
let where = undefined;
if (!isMouseEvent && event && event.altKey) {
if (
!isMouseEvent &&
event &&
(event.altKey || event.getModifierState("AltGraph"))
) {
// We support using 'alt' to open in a tab, because ctrl/shift
// might be used for canonizing URLs:
where = event.shiftKey ? "tabshifted" : "tab";

View File

@ -15,6 +15,9 @@ const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
PlacesSearchAutocompleteProvider:
"resource://gre/modules/PlacesSearchAutocompleteProvider.jsm",
Services: "resource://gre/modules/Services.jsm",
SkippableTimer: "resource:///modules/UrlbarUtils.jsm",
UrlbarProvider: "resource:///modules/UrlbarUtils.jsm",
UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.jsm",
@ -189,13 +192,12 @@ class UrlbarProviderExtension extends UrlbarProvider {
let extResults = await this._notifyListener("resultsRequested", context);
if (extResults) {
for (let extResult of extResults) {
let result;
try {
result = this._makeUrlbarResult(context, extResult);
} catch (err) {
continue;
let result = await this._makeUrlbarResult(context, extResult).catch(
Cu.reportError
);
if (result) {
addCallback(this, result);
}
addCallback(this, result);
}
}
}
@ -261,7 +263,41 @@ class UrlbarProviderExtension extends UrlbarProvider {
* @returns {UrlbarResult}
* The UrlbarResult object.
*/
_makeUrlbarResult(context, extResult) {
async _makeUrlbarResult(context, extResult) {
// If the result is a search result, make sure its payload has a valid
// `engine` property, which is the name of an engine, and which we use later
// on to look up the nsISearchEngine. We allow the extension to specify the
// engine by its name, alias, or domain. Prefer aliases over domains since
// one domain can have many engines.
if (extResult.type == "search") {
let engine;
if (extResult.payload.engine) {
// Validate the engine name by looking it up.
engine = Services.search.getEngineByName(extResult.payload.engine);
} else if (extResult.payload.keyword) {
// Look up the engine by its alias.
engine = await PlacesSearchAutocompleteProvider.engineForAlias(
extResult.payload.keyword
);
} else if (extResult.payload.url) {
// Look up the engine by its domain.
let host;
try {
host = new URL(extResult.payload.url).hostname;
} catch (err) {}
if (host) {
engine = await PlacesSearchAutocompleteProvider.engineForDomainPrefix(
host
);
}
}
if (!engine) {
// No engine found.
throw new Error("Invalid or missing engine specified by extension");
}
extResult.payload.engine = engine.name;
}
return new UrlbarResult(
UrlbarProviderExtension.RESULT_TYPES[extResult.type],
UrlbarProviderExtension.SOURCE_TYPES[extResult.source],

View File

@ -168,9 +168,9 @@ class UrlbarResult {
* @returns {array} An array [payload, payloadHighlights].
*/
static payloadAndSimpleHighlights(tokens, payloadInfo) {
// Convert string values in payloadInfo to [value, false] arrays.
// Convert scalar values in payloadInfo to [value] arrays.
for (let [name, info] of Object.entries(payloadInfo)) {
if (typeof info == "string") {
if (!Array.isArray(info)) {
payloadInfo[name] = [info];
}
}

View File

@ -124,6 +124,11 @@ add_task(async function load_in_current_tab_test() {
type: "keypress",
details: { altKey: true },
},
{
desc: "AltGr+Return keypress in a blank tab",
type: "keypress",
details: { altGraphKey: true },
},
];
for (let { desc, type, details } of tests) {
@ -166,6 +171,12 @@ add_task(async function load_in_new_tab_test() {
details: { altKey: true },
url: START_VALUE,
},
{
desc: "AltGr+Return keypress in a dirty tab",
type: "keypress",
details: { altGraphKey: true },
url: START_VALUE,
},
];
for (let { desc, type, details, url } of tests) {
@ -174,7 +185,7 @@ add_task(async function load_in_new_tab_test() {
// Add a new tab.
let tab = await promiseOpenNewTab(url);
// Trigger a load and check it occurs in the current tab.
// Trigger a load and check it occurs in a new tab.
let tabSwitchedPromise = promiseNewTabSwitched();
await triggerCommand(type, details);
await tabSwitchedPromise;

View File

@ -52,3 +52,30 @@ add_task(async function altReturnKeypress() {
BrowserTestUtils.removeTab(tab);
BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
add_task(async function altGrReturnKeypress() {
info("AltGr+Return keypress");
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, START_VALUE);
let tabOpenPromise = BrowserTestUtils.waitForEvent(
gBrowser.tabContainer,
"TabOpen"
);
gURLBar.focus();
EventUtils.synthesizeKey("KEY_Enter", { altGraphKey: true });
// wait for the new tab to appear.
await tabOpenPromise;
// Check url bar and selected tab.
is(
gURLBar.textValue,
TEST_VALUE,
"Urlbar should preserve the value on return keypress"
);
isnot(gBrowser.selectedTab, tab, "New URL was loaded in a new tab");
// Cleanup.
BrowserTestUtils.removeTab(tab);
BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

View File

@ -8,6 +8,7 @@ const EMPTY_TAB = "about:blank";
const META_KEY = AppConstants.platform == "macosx" ? "metaKey" : "ctrlKey";
const ENTER = new KeyboardEvent("keydown", {});
const ALT_ENTER = new KeyboardEvent("keydown", { altKey: true });
const ALTGR_ENTER = new KeyboardEvent("keydown", { modifierAltGraph: true });
const CLICK = new MouseEvent("click", { button: 0 });
const META_CLICK = new MouseEvent("click", { button: 0, [META_KEY]: true });
const MIDDLE_CLICK = new MouseEvent("click", { button: 1 });
@ -30,6 +31,11 @@ add_task(async function openInTab() {
event: ALT_ENTER,
desc: "Alt+Enter, non-empty tab, default prefs",
},
{
pref: false,
event: ALTGR_ENTER,
desc: "AltGr+Enter, non-empty tab, default prefs",
},
{
pref: false,
event: META_CLICK,
@ -101,6 +107,11 @@ add_task(async function reuseEmptyTab() {
event: ALT_ENTER,
desc: "Alt+Enter, empty tab, default prefs",
},
{
pref: false,
event: ALTGR_ENTER,
desc: "AltGr+Enter, empty tab, default prefs",
},
{ pref: true, event: ENTER, desc: "Enter, empty tab, openInTab" },
{ pref: true, event: CLICK, desc: "Normal click, empty tab, openInTab" },
]) {
@ -146,6 +157,12 @@ add_task(async function openInCurrentTab() {
event: ALT_ENTER,
desc: "Alt+Enter, non-empty tab, openInTab",
},
{
pref: true,
url: NON_EMPTY_TAB,
event: ALTGR_ENTER,
desc: "AltGr+Enter, non-empty tab, openInTab",
},
{
pref: true,
url: NON_EMPTY_TAB,

View File

@ -11,5 +11,6 @@
-facebook-container-brand-name = Facebook Container
-lockwise-brand-name = Firefox Lockwise
-monitor-brand-name = Firefox Monitor
-monitor-brand-short-name = Monitor
-pocket-brand-name = Pocket
-send-brand-name = Firefox Send

View File

@ -647,6 +647,20 @@ protections.notBlocking.crossSiteTrackingCookies.title=Not Blocking Cross-Site T
protections.notBlocking.trackingContent.title=Not Blocking Tracking Content
protections.notBlocking.socialMediaTrackers.title=Not Blocking Social Media Trackers
# Footer section in the Protections Panel
# LOCALIZATION NOTE (protections.footer.blockedTrackerCounter.description,
# protections.footer.blockedTrackerCounter.tooltip):
# This text indicates the total number of trackers blocked on all sites. In
# its tooltip, we show the date when we started counting this number.
# LOCALIZATION NOTE (protections.footer.blockedTrackerCounter.description):
# Semicolon-separated list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# Replacement for #1 is a locale-string converted positive integer.
protections.footer.blockedTrackerCounter.description=1 Blocked;#1 Blocked
# LOCALIZATION NOTE (protections.footer.blockedTrackerCounter.tooltip):
# %S is the date on which we started counting (e.g., July 17, 2019).
protections.footer.blockedTrackerCounter.tooltip=Since %S
# Edit Bookmark UI
editBookmarkPanel.newBookmarkTitle=New Bookmark
editBookmarkPanel.editBookmarkTitle=Edit This Bookmark

View File

@ -192,9 +192,8 @@
#identity-popup-securityView-body > description,
#identity-popup-permissions-content > description,
#protections-popup-content > description,
.protections-popup-settings-label,
#protections-popup-footer > description,
#protections-popup-footer > label {
.protections-popup-footer-button-label,
#protections-popup-trackers-blocked-counter-description {
font-size: 110%;
margin: 0;
}
@ -433,13 +432,17 @@ description#identity-popup-content-verifier,
.protections-popup-category:-moz-focusring,
.protections-popup-category:hover,
#protections-popup-settings-button:hover {
.protections-popup-footer-button:-moz-focusring,
.protections-popup-footer-button:hover,
#protections-popup-show-report-stack:hover > .protections-popup-footer-button {
border-radius: 2px;
background-color: var(--arrowpanel-dimmed);
outline: none;
}
.protections-popup-category:hover:active {
.protections-popup-category:hover:active,
.protections-popup-footer-button:hover:active,
#protections-popup-show-report-stack:hover:active > .protections-popup-footer-button {
background-color: var(--arrowpanel-dimmed-further);
}
@ -718,7 +721,8 @@ description#identity-popup-content-verifier,
.protections-popup-category-state-label,
.identity-popup-permission-label,
.identity-popup-permission-state-label,
.protections-popup-settings-label {
.protections-popup-footer-button-label,
#protections-popup-trackers-blocked-counter-description {
/* We need to align the action buttons and permission icons with the text.
This is tricky because the icon height is defined in pixels, while the
font height can vary with platform and system settings, and at least on
@ -733,7 +737,7 @@ description#identity-popup-content-verifier,
.protections-popup-category-label,
.identity-popup-permission-label,
.protections-popup-settings-label {
.protections-popup-footer-button-label {
margin-inline-start: 1em;
}
@ -889,39 +893,49 @@ description#identity-popup-content-verifier,
border-top: 1px solid var(--panel-separator-color);
}
#protections-popup-settings-section {
padding: 4px;
-moz-context-properties: fill, fill-opacity;
/* Protection popup footer categories */
.protections-popup-footer-button {
min-height: 24px;
padding-inline-start: 2em;
margin: 0;
}
#protections-popup-settings-button {
/* We need to align the setting button with the labels in the tp switch
section. So we add 6px to offset the margin of labels. */
padding-inline-start: calc(1em + 6px);
min-height: 37px;
}
.protection-settings-icon {
.protection-popup-footer-icon {
width: 16px;
height: 16px;
}
.protections-popup-settings-icon {
list-style-image: url(chrome://browser/skin/settings.svg);
}
.protections-popup-show-report-icon {
list-style-image: url(chrome://browser/skin/controlcenter/dashboard.svg);
}
#protections-popup-footer {
background-color: var(--arrowpanel-dimmed);
border-top: 1px solid var(--panel-separator-color);
min-height: 40px;
-moz-box-align: center;
/* The horizontal padding aligns the content of footer with other sections in
the protections panel */
padding: 4px calc(1em + 4px);
padding: 6px 0;
}
#protections-popup-trackers-blocked-counter-box {
margin-inline-end: calc(4px + 1em);
visibility: hidden;
opacity: 0;
transition: opacity 200ms linear;
}
#protections-popup-trackers-blocked-counter-box[showing] {
visibility: visible;
opacity: 1;
}
#protections-popup-trackers-blocked-counter-description {
font-weight: 600;
/* This padding is added to align the counter text with the beginning of texts
in other sections. */
padding-inline-start: 6px;
color: #737373;
}
:root[lwt-popup-brighttext] #protections-popup-trackers-blocked-counter-description {
color: #f9f9fa;
}
#protections-popup-tp-switch[showdotindicator]::after {

View File

@ -23,7 +23,8 @@
#urlbar[pageproxystate="invalid"] > #identity-box > #blocked-permissions-container,
#urlbar[pageproxystate="invalid"] > #identity-box > #notification-popup-box,
#urlbar[pageproxystate="invalid"] > #identity-box > #identity-icon-labels {
#urlbar[pageproxystate="invalid"] > #identity-box > #identity-icon-labels,
#urlbar[pageproxystate="invalid"] > #identity-box > #remote-control-icon {
display: none;
}

View File

@ -4,3 +4,4 @@ support-files =
../head.js
[browser_controlCenter.js]
skip-if = os == 'mac' # macosx1014 times out, see bug 1554821

View File

@ -4,3 +4,4 @@ support-files =
../head.js
[browser_devtools.js]
skip-if = os == 'mac' # times out on macosx1014, see 1570100

View File

@ -2,5 +2,6 @@
subsuite = screenshots
support-files =
../head.js
[browser_permissionPrompts.js]
skip-if = os == 'mac' # times out on macosx1014, see 1570098

View File

@ -4,3 +4,4 @@ support-files =
../head.js
[browser_primaryUI.js]
skip-if = os == 'mac' # macosx1014 times out, see bug 1570102

View File

@ -10,7 +10,7 @@ set -x
hfplus_version=540.1.linux3
md5sum=0435afc389b919027b69616ad1b05709
filename=diskdev_cmds-${hfplus_version}.tar.gz
make_flags="-j$(getconf _NPROCESSORS_ONLN)"
make_flags="-j$(nproc)"
root_dir="$1"
if [ -z "$root_dir" -o ! -d "$root_dir" ]; then

View File

@ -63,13 +63,11 @@ class ColumnBreakpoints extends Component<Props> {
}
}
const mapStateToProps = state => {
return {
cx: getContext(state),
selectedSource: getSelectedSource(state),
columnBreakpoints: visibleColumnBreakpoints(state),
};
};
const mapStateToProps = state => ({
cx: getContext(state),
selectedSource: getSelectedSource(state),
columnBreakpoints: visibleColumnBreakpoints(state),
});
export default connect(
mapStateToProps,

View File

@ -229,12 +229,11 @@ export class ConditionalPanel extends PureComponent<Props> {
const mapStateToProps = state => {
const location = getConditionalPanelLocation(state);
const log = getLogPointStatus(state);
return {
cx: getContext(state),
breakpoint: getBreakpointForLocation(state, location),
location,
log,
log: getLogPointStatus(state),
};
};

View File

@ -51,17 +51,15 @@ export const continueToHereItem = (
const copyToClipboardItem = (
selectedContent: SourceContent,
editorActions: EditorItemActions
) => {
return {
id: "node-menu-copy-to-clipboard",
label: L10N.getStr("copyToClipboard.label"),
accesskey: L10N.getStr("copyToClipboard.accesskey"),
disabled: false,
click: () =>
selectedContent.type === "text" &&
copyToTheClipboard(selectedContent.value),
};
};
) => ({
id: "node-menu-copy-to-clipboard",
label: L10N.getStr("copyToClipboard.label"),
accesskey: L10N.getStr("copyToClipboard.accesskey"),
disabled: false,
click: () =>
selectedContent.type === "text" &&
copyToTheClipboard(selectedContent.value),
});
const copySourceItem = (
selectedSource: Source,
@ -158,14 +156,12 @@ const downloadFileItem = (
selectedSource: Source,
selectedContent: SourceContent,
editorActions: EditorItemActions
) => {
return {
id: "node-menu-download-file",
label: L10N.getStr("downloadFile.label"),
accesskey: L10N.getStr("downloadFile.accesskey"),
click: () => downloadFile(selectedContent, getFilename(selectedSource)),
};
};
) => ({
id: "node-menu-download-file",
label: L10N.getStr("downloadFile.label"),
accesskey: L10N.getStr("downloadFile.accesskey"),
click: () => downloadFile(selectedContent, getFilename(selectedSource)),
});
export function editorMenuItems({
cx,

View File

@ -159,13 +159,11 @@ class EventListeners extends Component<Props> {
}
}
const mapStateToProps = state => {
return {
activeEventListeners: getActiveEventListeners(state),
categories: getEventListenerBreakpointTypes(state),
expandedCategories: getEventListenerExpanded(state),
};
};
const mapStateToProps = state => ({
activeEventListeners: getActiveEventListeners(state),
categories: getEventListenerBreakpointTypes(state),
expandedCategories: getEventListenerExpanded(state),
});
export default connect(
mapStateToProps,

View File

@ -383,14 +383,12 @@ class Expressions extends Component<Props, State> {
}
}
const mapStateToProps = state => {
return {
cx: getThreadContext(state),
autocompleteMatches: getAutocompleteMatchset(state),
expressions: getExpressions(state),
expressionError: getExpressionError(state),
};
};
const mapStateToProps = state => ({
cx: getThreadContext(state),
autocompleteMatches: getAutocompleteMatchset(state),
expressions: getExpressions(state),
expressionError: getExpressionError(state),
});
export default connect(
mapStateToProps,

View File

@ -34,7 +34,7 @@ export class Workers extends Component<Props> {
onClick={() => openWorkerToolbox(thread)}
>
<div className="icon">
<AccessibleImage className={"worker"} />
<AccessibleImage className="worker" />
</div>
<div className="label">{getDisplayName(thread)}</div>
</div>

View File

@ -338,7 +338,7 @@ class XHRBreakpoints extends Component<Props, State> {
return (
<select
value={this.state.inputMethod}
className={"xhr-input-method"}
className="xhr-input-method"
onChange={this.handleMethodChange}
onMouseDown={this.onMouseDown}
onKeyDown={this.handleTab}
@ -358,12 +358,10 @@ class XHRBreakpoints extends Component<Props, State> {
}
}
const mapStateToProps = state => {
return {
xhrBreakpoints: getXHRBreakpoints(state),
shouldPauseOnAny: shouldPauseOnAnyXHR(state),
};
};
const mapStateToProps = state => ({
xhrBreakpoints: getXHRBreakpoints(state),
shouldPauseOnAny: shouldPauseOnAnyXHR(state),
});
export default connect(
mapStateToProps,

View File

@ -43,9 +43,7 @@ class SourceIcon extends PureComponent<Props> {
}
}
export default connect((state, props) => {
return {
symbols: getSymbols(state, props.source),
framework: getFramework(getTabs(state), props.source.url),
};
})(SourceIcon);
export default connect((state, props) => ({
symbols: getSymbols(state, props.source),
framework: getFramework(getTabs(state), props.source.url),
}))(SourceIcon);

View File

@ -74,16 +74,14 @@ function findScopes(
break;
}
}
return found.map(i => {
return {
type: i.type,
scopeKind: i.scopeKind,
displayName: i.displayName,
start: i.start,
end: i.end,
bindings: i.bindings,
};
});
return found.map(i => ({
type: i.type,
scopeKind: i.scopeKind,
displayName: i.displayName,
start: i.start,
end: i.end,
bindings: i.bindings,
}));
}
function compareLocations(a: SourceLocation, b: SourceLocation): number {

View File

@ -213,22 +213,20 @@ function toParsedScopes(
if (!children || children.length === 0) {
return undefined;
}
return children.map(scope => {
return children.map(scope => ({
// Removing unneed information from TempScope such as parent reference.
// We also need to convert BabelLocation to the Location type.
return {
start: scope.loc.start,
end: scope.loc.end,
type:
scope.type === "module" || scope.type === "function-body"
? "block"
: scope.type,
scopeKind: "",
displayName: scope.displayName,
bindings: scope.bindings,
children: toParsedScopes(scope.children, sourceId),
};
});
start: scope.loc.start,
end: scope.loc.end,
type:
scope.type === "module" || scope.type === "function-body"
? "block"
: scope.type,
scopeKind: "",
displayName: scope.displayName,
bindings: scope.bindings,
children: toParsedScopes(scope.children, sourceId),
}));
}
function createTempScope(
@ -417,10 +415,7 @@ function createGlobalScope(
end: fromBabelLocation(ast.loc.end, sourceId),
});
return {
global,
lexical,
};
return { global, lexical };
}
const scopeCollectionVisitor = {

View File

@ -122,14 +122,12 @@ export function getVariables(dec: Node) {
// e.g. const [{a, b }] = 2
return dec.id.elements
.filter(element => element)
.map(element => {
return {
name: t.isAssignmentPattern(element)
? element.left.name
: element.name || (element.argument && element.argument.name),
location: element.loc,
};
})
.map(element => ({
name: t.isAssignmentPattern(element)
? element.left.name
: element.name || (element.argument && element.argument.name),
location: element.loc,
}))
.filter(({ name }) => name);
}

View File

@ -15,6 +15,7 @@ support-files =
[browser_webconsole_check_stubs_evaluation_result.js]
[browser_webconsole_check_stubs_network_event.js]
[browser_webconsole_check_stubs_page_error.js]
skip-if = (os == 'win' && os_version == '10.0' && bits == 64 && ccov) #Bug 1559605
[browser_webconsole_update_stubs_console_api.js]
skip-if=true # This is only used to update stubs. It is not an actual test.
[browser_webconsole_update_stubs_css_message.js]

View File

@ -1264,33 +1264,6 @@ const browsingContextTargetPrototype = {
return windowUtils.serviceWorkersTestingEnabled;
},
/**
* Prepare to enter a nested event loop by disabling debuggee events.
*/
preNest() {
if (!this.window) {
// The browsing context is already closed.
return;
}
const windowUtils = this.window.windowUtils;
windowUtils.suppressEventHandling(true);
windowUtils.suspendTimeouts();
},
/**
* Prepare to exit a nested event loop by enabling debuggee events.
*/
postNest(nestData) {
if (!this.window) {
// The browsing context is already closed.
return;
}
const windowUtils = this.window.windowUtils;
windowUtils.resumeTimeouts();
windowUtils.suppressEventHandling(false);
},
_changeTopLevelDocument(window) {
// Fake a will-navigate on the previous document
// to let a chance to unregister it

View File

@ -183,14 +183,6 @@ const ContentProcessTargetActor = ActorClassWithSpec(contentProcessTargetSpec, {
this._workerList.onListChanged = null;
}
},
preNest: function() {
// TODO: freeze windows
// window mediator doesn't work in child.
// it doesn't throw, but doesn't return any window
},
postNest: function() {},
});
exports.ContentProcessTargetActor = ContentProcessTargetActor;

View File

@ -157,30 +157,6 @@ parentProcessTargetPrototype._detach = function() {
return BrowsingContextTargetActor.prototype._detach.call(this);
};
/* ThreadActor hooks. */
/**
* Prepare to enter a nested event loop by disabling debuggee events.
*/
parentProcessTargetPrototype.preNest = function() {
// Disable events in all open windows.
for (const { windowUtils } of Services.wm.getEnumerator(null)) {
windowUtils.suppressEventHandling(true);
windowUtils.suspendTimeouts();
}
};
/**
* Prepare to exit a nested event loop by enabling debuggee events.
*/
parentProcessTargetPrototype.postNest = function(nestData) {
// Enable events in all open windows.
for (const { windowUtils } of Services.wm.getEnumerator(null)) {
windowUtils.resumeTimeouts();
windowUtils.suppressEventHandling(false);
}
};
exports.parentProcessTargetPrototype = parentProcessTargetPrototype;
exports.ParentProcessTargetActor = ActorClassWithSpec(
parentProcessTargetSpec,

View File

@ -347,8 +347,6 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
// Initialize an event loop stack. This can't be done in the constructor,
// because this.conn is not yet initialized by the actor pool at that time.
this._nestedEventLoops = new EventLoopStack({
hooks: this._parent,
connection: this.conn,
thread: this,
});
@ -1209,14 +1207,12 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
// only allow resumption in a LIFO order.
if (
this._nestedEventLoops.size &&
this._nestedEventLoops.lastPausedUrl &&
(this._nestedEventLoops.lastPausedUrl !== this._parent.url ||
this._nestedEventLoops.lastConnection !== this.conn)
this._nestedEventLoops.lastPausedThreadActor &&
this._nestedEventLoops.lastPausedThreadActor !== this
) {
return {
error: "wrongOrder",
message: "trying to resume in the wrong order.",
lastPausedUrl: this._nestedEventLoops.lastPausedUrl,
};
}
@ -2158,8 +2154,8 @@ exports.ChromeDebuggerActor = ChromeDebuggerActor;
*/
var oldReportError = reportError;
this.reportError = function(error, prefix = "") {
assert(error instanceof Error, "Must pass Error objects to reportError");
const msg = prefix + error.message + ":\n" + error.stack;
const message = error.message ? error.message : String(error);
const msg = prefix + message + ":\n" + error.stack;
oldReportError(msg);
dumpn(msg);
};

View File

@ -7,8 +7,6 @@
"use strict";
const xpcInspector = require("xpcInspector");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const { dumpn } = DevToolsUtils;
/**
* Manages pushing event loops and automatically pops and exits them in the
@ -16,19 +14,9 @@ const { dumpn } = DevToolsUtils;
*
* @param ThreadActor thread
* The thread actor instance that owns this EventLoopStack.
* @param DebuggerServerConnection connection
* The remote protocol connection associated with this event loop stack.
* @param Object hooks
* An object with the following properties:
* - url: The URL string of the debuggee we are spinning an event loop
* for.
* - preNest: function called before entering a nested event loop
* - postNest: function called after exiting a nested event loop
*/
function EventLoopStack({ thread, connection, hooks }) {
this._hooks = hooks;
function EventLoopStack({ thread }) {
this._thread = thread;
this._connection = connection;
}
EventLoopStack.prototype = {
@ -40,28 +28,13 @@ EventLoopStack.prototype = {
},
/**
* The URL of the debuggee who pushed the event loop on top of the stack.
* The thread actor of the debuggee who pushed the event loop on top of the stack.
*/
get lastPausedUrl() {
let url = null;
get lastPausedThreadActor() {
if (this.size > 0) {
try {
url = xpcInspector.lastNestRequestor.url;
} catch (e) {
// The tab's URL getter may throw if the tab is destroyed by the time
// this code runs, but we don't really care at this point.
dumpn(e);
}
return xpcInspector.lastNestRequestor.thread;
}
return url;
},
/**
* The DebuggerServerConnection of the debugger who pushed the event loop on
* top of the stack
*/
get lastConnection() {
return xpcInspector.lastNestRequestor._connection;
return null;
},
/**
@ -72,8 +45,6 @@ EventLoopStack.prototype = {
push: function() {
return new EventLoop({
thread: this._thread,
connection: this._connection,
hooks: this._hooks,
});
},
};
@ -84,16 +55,9 @@ EventLoopStack.prototype = {
*
* @param ThreadActor thread
* The thread actor that is creating this nested event loop.
* @param DebuggerServerConnection connection
* The remote protocol connection associated with this event loop.
* @param Object hooks
* The same hooks object passed into EventLoopStack during its
* initialization.
*/
function EventLoop({ thread, connection, hooks }) {
function EventLoop({ thread }) {
this._thread = thread;
this._hooks = hooks;
this._connection = connection;
this.enter = this.enter.bind(this);
this.resolve = this.resolve.bind(this);
@ -102,15 +66,15 @@ function EventLoop({ thread, connection, hooks }) {
EventLoop.prototype = {
entered: false,
resolved: false,
get url() {
return this._hooks.url;
get thread() {
return this._thread;
},
/**
* Enter this nested event loop.
*/
enter: function() {
const nestData = this._hooks.preNest ? this._hooks.preNest() : null;
const preNestData = this.preNest();
this.entered = true;
xpcInspector.enterNestedEventLoop(this);
@ -123,9 +87,7 @@ EventLoop.prototype = {
}
}
if (this._hooks.postNest) {
this._hooks.postNest(nestData);
}
this.postNest(preNestData);
},
/**
@ -152,6 +114,55 @@ EventLoop.prototype = {
}
return false;
},
/**
* Retrieve the list of all DOM Windows debugged by the current thread actor.
*/
getAllWindowDebuggees() {
return (
this._thread.dbg
.getDebuggees()
.filter(debuggee => {
// Select only debuggee that relates to windows
// e.g. ignore sandboxes, jsm and such
return debuggee.class == "Window";
})
.map(debuggee => {
// Retrieve the JS reference for these windows
return debuggee.unsafeDereference();
})
// Ignore iframes as they will be paused automatically when pausing their
// owner top level document
.filter(window => window.top === window)
);
},
/**
* Prepare to enter a nested event loop by disabling debuggee events.
*/
preNest() {
const windows = [];
// Disable events in all open windows.
for (const window of this.getAllWindowDebuggees()) {
const { windowUtils } = window;
windowUtils.suppressEventHandling(true);
windowUtils.suspendTimeouts();
windows.push(window);
}
return windows;
},
/**
* Prepare to exit a nested event loop by enabling debuggee events.
*/
postNest(pausedWindows) {
// Enable events in all open windows.
for (const window of pausedWindows) {
const { windowUtils } = window;
windowUtils.resumeTimeouts();
windowUtils.suppressEventHandling(false);
}
},
};
exports.EventLoopStack = EventLoopStack;

View File

@ -0,0 +1,81 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint-disable no-shadow */
"use strict";
/**
* Ensure that the debugger resume page execution when the connection drops
* and when the target is detached.
*/
add_task(
threadFrontTest(({ threadFront, client, debuggee, targetFront }) => {
return new Promise(resolve => {
threadFront.once("paused", async function(packet) {
ok(true, "The page is paused");
ok(!debuggee.foo, "foo is still false after we hit the breakpoint");
await targetFront.detach();
// `detach` will force the destruction of the thread actor, which,
// will resume the page execution. But all of that seems to be
// synchronous and we have to spin the event loop in order to ensure
// having the content javascript to execute the resumed code.
await new Promise(executeSoon);
// Closing the connection will force the thread actor to resume page
// execution
ok(debuggee.foo, "foo is true after target's detach request");
executeSoon(resolve);
});
/* eslint-disable */
Cu.evalInSandbox(
"var foo = false;\n" +
"debugger;\n" +
"foo = true;\n",
debuggee
);
/* eslint-enable */
ok(debuggee.foo, "foo is false at startup");
});
})
);
add_task(
threadFrontTest(({ threadFront, client, debuggee }) => {
return new Promise(resolve => {
threadFront.once("paused", async function(packet) {
ok(true, "The page is paused");
ok(!debuggee.foo, "foo is still false after we hit the breakpoint");
await client.close();
// `detach` will force the destruction of the thread actor, which,
// will resume the page execution. But all of that seems to be
// synchronous and we have to spin the event loop in order to ensure
// having the content javascript to execute the resumed code.
await new Promise(executeSoon);
// Closing the connection will force the thread actor to resume page
// execution
ok(debuggee.foo, "foo is true after client close");
executeSoon(resolve);
dump("resolved\n");
});
/* eslint-disable */
Cu.evalInSandbox(
"var foo = false;\n" +
"debugger;\n" +
"foo = true;\n",
debuggee
);
/* eslint-enable */
ok(debuggee.foo, "foo is false at startup");
});
})
);

View File

@ -170,6 +170,7 @@ TestTargetActor.prototype = {
if (!this._attached) {
return { error: "wrongState" };
}
this.threadActor.exit();
return { type: "detached" };
},

View File

@ -135,6 +135,7 @@ reason = bug 1104838
skip-if = true # breakpoint sliding is not supported bug 1525685
[test_breakpoint-23.js]
[test_breakpoint-24.js]
[test_breakpoint-25.js]
[test_conditional_breakpoint-01.js]
[test_conditional_breakpoint-02.js]
[test_conditional_breakpoint-03.js]

View File

@ -39,6 +39,7 @@
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/StorageAccess.h"
#include "mozilla/TextEditor.h"
#include "mozilla/URLDecorationStripper.h"
#include "mozilla/URLExtraData.h"
#include "mozilla/Base64.h"
#include <algorithm>
@ -5411,7 +5412,7 @@ void Document::GetReferrer(nsAString& aReferrer) const {
}
nsAutoCString uri;
nsresult rv = referrer->GetSpec(uri);
nsresult rv = URLDecorationStripper::StripTrackingIdentifiers(referrer, uri);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}

View File

@ -7995,7 +7995,7 @@ bool nsContentUtils::IsThirdPartyWindowOrChannel(nsPIDOMWindowInner* aWindow,
// want to check the channel URI against the loading principal as well.
nsresult rv =
thirdPartyUtil->IsThirdPartyChannel(aChannel, nullptr, &thirdParty);
if (NS_WARN_IF(NS_FAILED(rv))) {
if (NS_FAILED(rv)) {
// Assume third-party in case of failure
thirdParty = true;
}

View File

@ -115,6 +115,7 @@
#include "mozilla/PreloadedStyleSheet.h"
#include "mozilla/layers/WebRenderBridgeChild.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/ResultExtensions.h"
#ifdef XP_WIN
# undef GetClassName
@ -4103,14 +4104,60 @@ NS_IMETHODIMP
nsDOMWindowUtils::SetCompositionRecording(bool aValue) {
if (CompositorBridgeChild* cbc = GetCompositorBridge()) {
if (aValue) {
cbc->SendBeginRecording(TimeStamp::Now());
RefPtr<nsDOMWindowUtils> self = this;
cbc->SendBeginRecording(TimeStamp::Now())
->Then(
GetCurrentThreadSerialEventTarget(), __func__,
[self](const bool& aSuccess) {
if (!aSuccess) {
self->ReportErrorMessageForWindow(
NS_LITERAL_STRING(
"The composition recorder is already running."),
"DOM", true);
}
},
[self](const mozilla::ipc::ResponseRejectReason&) {
self->ReportErrorMessageForWindow(
NS_LITERAL_STRING(
"Could not start the composition recorder."),
"DOM", true);
});
} else {
cbc->SendEndRecording();
bool success = false;
if (!cbc->SendEndRecording(&success)) {
ReportErrorMessageForWindow(
NS_LITERAL_STRING("Could not stop the composition recorder."),
"DOM", true);
} else if (!success) {
ReportErrorMessageForWindow(
NS_LITERAL_STRING("The composition recorder is not running."),
"DOM", true);
}
}
}
return NS_OK;
}
void nsDOMWindowUtils::ReportErrorMessageForWindow(
const nsAString& aErrorMessage, const char* aClassification,
bool aFromChrome) {
bool isPrivateWindow = false;
if (nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow)) {
if (nsIPrincipal* principal =
nsGlobalWindowOuter::Cast(window)->GetPrincipal()) {
uint32_t privateBrowsingId = 0;
if (NS_SUCCEEDED(principal->GetPrivateBrowsingId(&privateBrowsingId))) {
isPrivateWindow = !!privateBrowsingId;
}
}
}
nsContentUtils::LogSimpleConsoleError(aErrorMessage, aClassification,
isPrivateWindow, aFromChrome);
}
NS_IMETHODIMP
nsDOMWindowUtils::SetSystemFont(const nsACString& aFontName) {
nsIWidget* widget = GetWidget();

View File

@ -101,6 +101,10 @@ class nsDOMWindowUtils final : public nsIDOMWindowUtils,
const nsTArray<float>& aRotationAngles, const nsTArray<float>& aForces,
int32_t aModifiers, bool aIgnoreRootScrollFrame, bool aToWindow,
bool* aPreventDefault);
void ReportErrorMessageForWindow(const nsAString& aErrorMessage,
const char* aClassification,
bool aFromChrome);
};
#endif

View File

@ -2278,23 +2278,18 @@ bool nsGlobalWindowInner::ShouldReportForServiceWorkerScope(
return result;
}
already_AddRefed<InstallTriggerImpl> nsGlobalWindowInner::GetInstallTrigger() {
InstallTriggerImpl* nsGlobalWindowInner::GetInstallTrigger() {
if (!mInstallTrigger) {
JS::Rooted<JSObject*> jsImplObj(RootingCx());
ErrorResult rv;
ConstructJSImplementation("@mozilla.org/addons/installtrigger;1", this,
&jsImplObj, rv);
mInstallTrigger = ConstructJSImplementation<InstallTriggerImpl>(
"@mozilla.org/addons/installtrigger;1", this, rv);
if (rv.Failed()) {
rv.SuppressException();
return nullptr;
}
MOZ_RELEASE_ASSERT(!js::IsWrapper(jsImplObj));
JS::Rooted<JSObject*> jsImplGlobal(RootingCx(),
JS::GetNonCCWObjectGlobal(jsImplObj));
mInstallTrigger = new InstallTriggerImpl(jsImplObj, jsImplGlobal, this);
}
return do_AddRef(mInstallTrigger);
return mInstallTrigger;
}
nsIDOMWindowUtils* nsGlobalWindowInner::GetWindowUtils(ErrorResult& aRv) {
@ -6760,22 +6755,17 @@ bool nsGlobalWindowInner::IsSecureContext() const {
return JS::GetIsSecureContext(realm);
}
already_AddRefed<External> nsGlobalWindowInner::GetExternal(ErrorResult& aRv) {
External* nsGlobalWindowInner::GetExternal(ErrorResult& aRv) {
#ifdef HAVE_SIDEBAR
if (!mExternal) {
JS::Rooted<JSObject*> jsImplObj(RootingCx());
ConstructJSImplementation("@mozilla.org/sidebar;1", this, &jsImplObj, aRv);
mExternal = ConstructJSImplementation<External>("@mozilla.org/sidebar;1",
this, aRv);
if (aRv.Failed()) {
return nullptr;
}
MOZ_RELEASE_ASSERT(!js::IsWrapper(jsImplObj));
JS::Rooted<JSObject*> jsImplGlobal(RootingCx(),
JS::GetNonCCWObjectGlobal(jsImplObj));
mExternal = new External(jsImplObj, jsImplGlobal, this);
}
RefPtr<External> external = static_cast<External*>(mExternal.get());
return external.forget();
return static_cast<External*>(mExternal.get());
#else
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
return nullptr;

View File

@ -653,8 +653,7 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
void GetSidebar(mozilla::dom::OwningExternalOrWindowProxy& aResult,
mozilla::ErrorResult& aRv);
already_AddRefed<mozilla::dom::External> GetExternal(
mozilla::ErrorResult& aRv);
mozilla::dom::External* GetExternal(mozilla::ErrorResult& aRv);
mozilla::dom::Worklet* GetPaintWorklet(mozilla::ErrorResult& aRv);
@ -946,7 +945,7 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
bool ShouldReportForServiceWorkerScope(const nsAString& aScope);
already_AddRefed<mozilla::dom::InstallTriggerImpl> GetInstallTrigger();
mozilla::dom::InstallTriggerImpl* GetInstallTrigger();
nsIDOMWindowUtils* GetWindowUtils(mozilla::ErrorResult& aRv);

View File

@ -2575,24 +2575,6 @@ bool GetContentGlobalForJSImplementedObject(JSContext* cx,
return true;
}
already_AddRefed<nsIGlobalObject> ConstructJSImplementation(
const char* aContractId, const GlobalObject& aGlobal,
JS::MutableHandle<JSObject*> aObject, ErrorResult& aRv) {
// Get the global object to use as a parent and for initialization.
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
if (!global) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
ConstructJSImplementation(aContractId, global, aObject, aRv);
if (aRv.Failed()) {
return nullptr;
}
return global.forget();
}
void ConstructJSImplementation(const char* aContractId,
nsIGlobalObject* aGlobal,
JS::MutableHandle<JSObject*> aObject,

View File

@ -26,6 +26,7 @@
#include "mozilla/dom/Nullable.h"
#include "mozilla/dom/PrototypeList.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/SegmentedVector.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Likely.h"
@ -2468,9 +2469,35 @@ void ConstructJSImplementation(const char* aContractId,
JS::MutableHandle<JSObject*> aObject,
ErrorResult& aRv);
already_AddRefed<nsIGlobalObject> ConstructJSImplementation(
const char* aContractId, const GlobalObject& aGlobal,
JS::MutableHandle<JSObject*> aObject, ErrorResult& aRv);
template <typename T>
already_AddRefed<T> ConstructJSImplementation(const char* aContractId,
nsIGlobalObject* aGlobal,
ErrorResult& aRv) {
JS::RootingContext* cx = RootingCx();
JS::Rooted<JSObject*> jsImplObj(cx);
ConstructJSImplementation(aContractId, aGlobal, &jsImplObj, aRv);
if (aRv.Failed()) {
return nullptr;
}
MOZ_RELEASE_ASSERT(!js::IsWrapper(jsImplObj));
JS::Rooted<JSObject*> jsImplGlobal(cx, JS::GetNonCCWObjectGlobal(jsImplObj));
RefPtr<T> newObj = new T(jsImplObj, jsImplGlobal, aGlobal);
return newObj.forget();
}
template <typename T>
already_AddRefed<T> ConstructJSImplementation(const char* aContractId,
const GlobalObject& aGlobal,
ErrorResult& aRv) {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
if (!global) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
return ConstructJSImplementation<T>(aContractId, global, aRv);
}
/**
* Convert an nsCString to jsval, returning true on success.

View File

@ -15907,7 +15907,7 @@ class CGJSImplMethod(CGJSImplMember):
initCall = fill(
"""
// Wrap the object before calling __Init so that __DOM_IMPL__ is available.
JS::Rooted<JSObject*> scopeObj(cx, globalHolder->GetGlobalJSObject());
JS::Rooted<JSObject*> scopeObj(cx, global.Get());
MOZ_ASSERT(js::IsObjectInContextCompartment(scopeObj, cx));
JS::Rooted<JS::Value> wrappedVal(cx);
if (!GetOrCreateDOMReflector(cx, impl, &wrappedVal, aGivenProto)) {
@ -15930,17 +15930,11 @@ class CGJSImplMethod(CGJSImplMember):
def genConstructorBody(descriptor, initCall=""):
return fill(
"""
JS::Rooted<JSObject*> jsImplObj(cx);
nsCOMPtr<nsIGlobalObject> globalHolder =
ConstructJSImplementation("${contractId}", global, &jsImplObj, aRv);
RefPtr<${implClass}> impl =
ConstructJSImplementation<${implClass}>("${contractId}", global, aRv);
if (aRv.Failed()) {
return nullptr;
}
// We should be getting the implementation object for the relevant
// contract here, which should never be a cross-compartment wrapper.
JS::Rooted<JSObject*> jsImplGlobal(cx, JS::GetNonCCWObjectGlobal(jsImplObj));
// Build the C++ implementation.
RefPtr<${implClass}> impl = new ${implClass}(jsImplObj, jsImplGlobal, globalHolder);
$*{initCall}
return impl.forget();
""",

View File

@ -127,7 +127,6 @@ disabled = won't work as Firefox desktop will intercept ctrl-click
[test_browserElement_inproc_RemoveBrowserElement.html]
[test_browserElement_inproc_ScrollEvent.html]
[test_browserElement_inproc_SecurityChange.html]
skip-if = android_version == '22' # bug 1358876, bug 1322607
[test_browserElement_inproc_TargetBlank.html]
[test_browserElement_inproc_TargetTop.html]
[test_browserElement_inproc_Titlechange.html]

View File

@ -3948,8 +3948,6 @@ nsresult EventStateManager::SetCursor(StyleCursorKind aCursor,
NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
sMouseOverDocument = mDocument.get();
nsCursor c;
NS_ENSURE_TRUE(aWidget, NS_ERROR_FAILURE);
if (aLockCursor) {
if (StyleCursorKind::Auto != aCursor) {
@ -3959,8 +3957,8 @@ nsresult EventStateManager::SetCursor(StyleCursorKind aCursor,
mLockCursor = kInvalidCursorKind;
}
}
nsCursor c;
switch (aCursor) {
default:
case StyleCursorKind::Auto:
case StyleCursorKind::Default:
c = eCursor_standard;
@ -4067,6 +4065,10 @@ nsresult EventStateManager::SetCursor(StyleCursorKind aCursor,
case StyleCursorKind::None:
c = eCursor_none;
break;
default:
MOZ_ASSERT_UNREACHABLE("Unknown cursor kind");
c = eCursor_standard;
break;
}
int32_t x = aHotspot ? aHotspot->x : 0;

View File

@ -2364,34 +2364,6 @@ RefPtr<MediaManager::StreamPromise> MediaManager::GetUserMedia(
docURI->SchemeIs("file", &isFile);
bool isApp;
docURI->SchemeIs("app", &isApp);
// Same localhost check as ServiceWorkers uses
// (see IsOriginPotentiallyTrustworthy())
bool isLocalhost =
NS_SUCCEEDED(rv) && (host.LowerCaseEqualsLiteral("localhost") ||
host.LowerCaseEqualsLiteral("127.0.0.1") ||
host.LowerCaseEqualsLiteral("::1"));
// Record telemetry about whether the source of the call was secure, i.e.,
// privileged or HTTPS. We may handle other cases
if (privileged) {
Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
(uint32_t)GetUserMediaSecurityState::Privileged);
} else if (isHTTPS) {
Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
(uint32_t)GetUserMediaSecurityState::HTTPS);
} else if (isFile) {
Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
(uint32_t)GetUserMediaSecurityState::File);
} else if (isApp) {
Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
(uint32_t)GetUserMediaSecurityState::App);
} else if (isLocalhost) {
Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
(uint32_t)GetUserMediaSecurityState::Localhost);
} else {
Telemetry::Accumulate(Telemetry::WEBRTC_GET_USER_MEDIA_SECURE_ORIGIN,
(uint32_t)GetUserMediaSecurityState::Other);
}
nsCOMPtr<nsIPrincipal> principal =
nsGlobalWindowInner::Cast(aWindow)->GetPrincipal();

View File

@ -2117,29 +2117,6 @@ class PeerConnectionObserver {
if (pc.iceConnectionState === iceConnectionState) {
return;
}
if (pc.iceConnectionState === "new") {
var checking_histogram = Services.telemetry.getHistogramById(
"WEBRTC_ICE_CHECKING_RATE"
);
if (iceConnectionState === "checking") {
checking_histogram.add(true);
} else if (iceConnectionState === "failed") {
checking_histogram.add(false);
}
} else if (pc.iceConnectionState === "checking") {
var success_histogram = Services.telemetry.getHistogramById(
"WEBRTC_ICE_SUCCESS_RATE"
);
if (
iceConnectionState === "completed" ||
iceConnectionState === "connected"
) {
success_histogram.add(true);
pc._pcTelemetry.recordConnected();
} else if (iceConnectionState === "failed") {
success_histogram.add(false);
}
}
if (iceConnectionState === "failed") {
if (!pc._hasStunServer) {

View File

@ -755,6 +755,10 @@ size_t VideoTrackEncoder::SizeOfExcludingThis(
void VideoTrackEncoder::SetKeyFrameInterval(int32_t aKeyFrameInterval) {
MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
if (aKeyFrameInterval == 0) {
mKeyFrameInterval = DEFAULT_KEYFRAME_INTERVAL_MS;
return;
}
mKeyFrameInterval = std::min(aKeyFrameInterval, DEFAULT_KEYFRAME_INTERVAL_MS);
}

View File

@ -60,7 +60,6 @@ skip-if = toolkit == 'android' || (os == "win" && processor == "aarch64") # Not
[test_AutoRevocation.html]
tags = firstpartyisolation
[test_BufferedSeek.html]
skip-if = android_version == '22' # bug 1329532 bug 1066090
[test_BufferedSeek_mp4.html]
skip-if = toolkit == 'android' # Not supported on android
[test_BufferingWait.html]
@ -78,21 +77,21 @@ skip-if = toolkit == 'android' # Not supported on android
[test_DurationUpdated_mp4.html]
skip-if = toolkit == 'android' # Not supported on android
[test_EndedEvent.html]
skip-if = android_version == '22' || toolkit == 'android' # bug 1358640, bug 1401090
skip-if = toolkit == 'android' # bug 1358640, bug 1401090
[test_EndOfStream.html]
[test_EndOfStream_mp4.html]
skip-if = toolkit == 'android' # Not supported on android
[test_Eviction_mp4.html]
[test_ExperimentalAsync.html]
skip-if = android_version == '22' || toolkit == 'android' || (os == "win" && processor == "aarch64") # bug 1341519, bug 1401090, aarch64 due to 1538391
skip-if = toolkit == 'android' || (os == "win" && processor == "aarch64") # bug 1341519, bug 1401090, aarch64 due to 1538391
[test_FrameSelection.html]
skip-if = android_version == '22' || toolkit == 'android' # bug 1341519, bug 1401090
skip-if = toolkit == 'android' # bug 1341519, bug 1401090
[test_FrameSelection_mp4.html]
skip-if = toolkit == 'android' || os == 'win' || (os == 'mac' && os_version == '10.14') # Not supported on android, # bug 1487973, mac due to bug 1487973
[test_isTypeSupportedExtensions.html]
skip-if = android_version >= 28 # bug 1543669 ; cross origins broken on our Android 8.0 emulators?
[test_HaveMetadataUnbufferedSeek.html]
skip-if = android_version == '22' || toolkit == 'android' # bug 1342247, bug 1401090
skip-if = toolkit == 'android' # bug 1342247, bug 1401090
[test_HaveMetadataUnbufferedSeek_mp4.html]
skip-if = toolkit == 'android' # Not supported on android
[test_LiveSeekable.html]
@ -102,9 +101,7 @@ skip-if = toolkit == 'android' # Not supported on android
[test_LoadedMetadataFired_mp4.html]
skip-if = toolkit == 'android' # Not supported on android
[test_MediaSource.html]
skip-if = android_version == '22' # bug 1341146
[test_MediaSource_memory_reporting.html]
skip-if = android_version == '22' # bug 1225758
[test_MediaSource_mp4.html]
skip-if = toolkit == 'android' # Not supported on android
[test_MediaSource_flac_mp4.html]
@ -114,7 +111,6 @@ skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1526064
[test_MultipleInitSegments_mp4.html]
skip-if = toolkit == 'android' # Not supported on android
[test_OnEvents.html]
skip-if = android_version == '22' # bug 1359010
[test_PlayEvents.html]
skip-if = toolkit == 'android' # Not supported on android
[test_PlayEventsAutoPlaying.html]
@ -144,11 +140,9 @@ skip-if = toolkit == 'android' # Not supported on android
skip-if = toolkit == 'android' # Not supported on android
[test_SetModeThrows.html]
[test_SplitAppendDelay.html]
skip-if = android_version == '22' # bug 1293896 bug 1342683
[test_SplitAppendDelay_mp4.html]
skip-if = toolkit == 'android' # Not supported on android
[test_SplitAppend.html]
skip-if = android_version == '22' # bug 1211999
[test_SplitAppend_mp4.html]
skip-if = toolkit == 'android' # Not supported on android
[test_Threshold_mp4.html]
@ -156,7 +150,6 @@ skip-if = toolkit == 'android' # Not supported on android
[test_TimestampOffset_mp4.html]
skip-if = toolkit == 'android' # Not supported on android
[test_TruncatedDuration.html]
skip-if = android_version == '22' # bug 1359012
[test_TruncatedDuration_mp4.html]
skip-if = toolkit == 'android' # Not supported on android
[test_WaitingOnMissingData.html]

View File

@ -709,7 +709,6 @@ support-files =
fail-if = fission
skip-if = android_version == '17' # bug 1292836, android(bug 1232305)
[test_arraybuffer.html]
skip-if = android_version == '22' # bug 1308388, android(bug 1232305)
[test_aspectratio_mp4.html]
[test_audio1.html]
[test_audio2.html]
@ -717,7 +716,7 @@ skip-if = android_version == '22' # bug 1308388, android(bug 1232305)
skip-if = true # bug 475110 - disabled since we don't play Wave files standalone
[test_autoplay.html]
[test_autoplay_contentEditable.html]
skip-if = android_version == '17' || android_version == '22' # android(bug 1232305, bug 1232318, bug 1372457)
skip-if = android_version == '17'
[test_autoplay_policy.html]
skip-if = android_version >= '23' # bug 1424903
[test_autoplay_policy_activation.html]
@ -744,7 +743,6 @@ skip-if = android_version >= '23' # bug 1424903
[test_autoplay_policy_web_audio_createMediaStreamSource.html]
skip-if = android_version >= '23' # bug 1424903
[test_buffered.html]
skip-if = android_version == '22' # bug 1308388, android(bug 1232305)
[test_bug448534.html]
[test_bug463162.xhtml]
[test_bug465498.html]
@ -935,7 +933,7 @@ tags=msg
[test_mediarecorder_pause_resume_video.html]
skip-if = toolkit == 'android' # android(bug 1232305)
[test_mediarecorder_playback_can_repeat.html]
skip-if = android_version == '17' || android_version == '22' # android(bug 1232305, bug 1372457)
skip-if = android_version == '17'
tags=msg
[test_mediarecorder_principals.html]
skip-if = toolkit == 'android' || (os == 'win' && os_version == '10.0' && webrender) # android(bug 1232305), Bug 1453375
@ -1118,7 +1116,7 @@ skip-if = android_version == '17' # android(bug 1232305)
[test_preserve_playbackrate_after_ui_play.html]
skip-if = android_version == '17' && debug # android(bug 1232305)
[test_progress.html]
skip-if = android_version == '17' || android_version == '22' # android(bug 1232305)
skip-if = android_version == '17'
[test_reactivate.html]
skip-if = true # see bug 1319725
[test_readyState.html]
@ -1221,7 +1219,6 @@ tags=msg capturestream
skip-if = toolkit == 'android' # android(bug 1232305)
tags=msg capturestream
[test_texttrack.html]
skip-if = android_version == '22' # android(bug 1368010)
tags = webvtt
[test_texttrack_cors_preload_none.html]
support-files =
@ -1235,36 +1232,29 @@ tags = webvtt
skip-if = toolkit == 'android' # android(bug 1562021)
tags = webvtt
[test_texttrackcue.html]
skip-if = android_version == '17' || android_version == '22' # android(bug 1368010, bug 1372457)
skip-if = android_version == '17'
tags = webvtt
[test_texttrackcue_moz.html]
skip-if = android_version == '22' # bug 1294111, android(bug 1368010)
tags = webvtt
[test_texttrackevents_video.html]
skip-if = android_version == '17' || android_version == '22' # android(bug 1368010, bug 1372457)
skip-if = android_version == '17'
tags = webvtt
[test_texttracklist.html]
skip-if = android_version == '22' # android(bug 1368010)
tags = webvtt
[test_texttracklist_moz.html]
skip-if = android_version == '22' # android(bug 1368010)
tags = webvtt
[test_texttrackregion.html]
skip-if = android_version == '22' # android(bug 1368010)
tags = webvtt
[test_texttrack_moz.html]
skip-if = android_version == '22' # android(bug 1368010)
tags = webvtt
[test_timeupdate_small_files.html]
skip-if = toolkit == 'android' # bug 1195570, android(bug 1232305)
[test_trackelementevent.html]
skip-if = android_version == '22' # bug 1294833, android(bug 1368010)
tags = webvtt
[test_trackelementsrc.html]
skip-if = android_version == '17' || android_version == '22' # android(bug 1368010, bug 1372457)
skip-if = android_version == '17'
tags = webvtt
[test_trackevent.html]
skip-if = android_version == '22' # android(bug 1368010)
tags = webvtt
[test_webvtt_event_same_time.html]
tags = webvtt
@ -1286,19 +1276,15 @@ skip-if = toolkit == 'android' # android(bug 1232305)
[test_vp9_superframes.html]
skip-if = os == 'mac' && os_version == '10.14' # mac due to bug 1545737
[test_vttparser.html]
skip-if = android_version == '22' # android(bug 1368010)
tags = webvtt
[test_webvtt_empty_displaystate.html]
skip-if = android_version == '17' || android_version == '22' # android(bug 1368010, bug 1372457)
skip-if = android_version == '17'
tags = webvtt
[test_webvtt_update_display_after_adding_or_removing_cue.html]
skip-if = android_version == '22' # android(bug 1368010)
tags = webvtt
[test_webvtt_overlapping_time.html]
skip-if = android_version == '22' # android(bug 1368010)
tags = webvtt
[test_webvtt_positionalign.html]
skip-if = android_version == '22' # android(bug 1368010)
tags = webvtt
[test_webvtt_seeking.html]
skip-if = toolkit == 'android' # bug 1368010, bug 1548446

View File

@ -29,28 +29,20 @@ support-files =
/.well-known/idp-proxy/idp-redirect-https-odd-path.js^headers^
/.well-known/idp-min.js
/.well-known/idp-proxy/idp-bad.js
skip-if = android_version == '22' # bug 1358876, bug 1361325
[test_fingerprints.html]
skip-if = android_version == '22' # bug 1329257, bug 1358876, bug 1361325
scheme=https
[test_getIdentityAssertion.html]
skip-if = android_version == '22' # bug 1358876, bug 1361325
[test_setIdentityProvider.html]
skip-if = android_version == '22' # bug 1358876, bug 1361325
scheme=https
[test_setIdentityProviderWithErrors.html]
skip-if = android_version == '22' # bug 1358876, bug 1361325
scheme=https
[test_peerConnection_peerIdentity.html]
skip-if = android_version == '22' # bug 1358876, bug 1361325
scheme=https
[test_peerConnection_asymmetricIsolation.html]
skip-if = android_version == '22' # bug 1358876, bug 1361325
scheme=https
[test_loginNeeded.html]
fail-if = fission
support-files =
/.well-known/idp-proxy/login.html
/.well-known/idp-proxy/idp.sjs
skip-if = android_version == '22' # bug 1358876, bug 1361325

View File

@ -44,7 +44,6 @@ class MediaTransportParent : public dom::PMediaTransportParent {
mozilla::ipc::IPCResult RecvRemoveTransportsExcept(
const StringVector& transportIds);
mozilla::ipc::IPCResult RecvStartIceChecks(const bool& isControlling,
const bool& isOfferer,
const StringVector& iceOptions);
mozilla::ipc::IPCResult RecvSendPacket(const string& transportId,
const MediaPacket& packet);

View File

@ -76,7 +76,6 @@ parent:
async RemoveTransportsExcept(StringVector transportIds);
async StartIceChecks(bool isControlling,
bool isOfferer,
StringVector iceOptions);
async SendPacket(string transportId, MediaPacket packet);

View File

@ -0,0 +1,32 @@
// Custom *.sjs file specifically for the needs of Bug 1286861
// small red image
const IMG = atob(
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
"P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
function getSniffableContent(selector){
switch(selector){
case "xml":
return `<?xml version="1.0"?><test/>`;
case "html":
return `<!Doctype html> <html> <head></head> <body> Test test </body></html>`;
case "css":
return `*{ color: pink !important; }`;
case 'json':
return `{ 'test':'yes' }`;
case 'img':
return IMG;
}
return "Basic UTF-8 Text";
}
function handleRequest(request, response)
{
// avoid confusing cache behaviors
response.setHeader('X-Content-Type-Options', 'nosniff'); // Disable Sniffing
response.setHeader("Content-Type","*/*"); // Try Browser to force sniffing.
response.write(getSniffableContent(request.queryString));
return;
}

View File

@ -0,0 +1,33 @@
// Custom *.sjs file specifically for the needs of Bug 1286861
// small red image
const IMG = atob(
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
"P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
function getSniffableContent(selector){
switch(selector){
case "xml":
return `<?xml version="1.0"?><test/>`;
case "html":
return `<!Doctype html> <html> <head></head> <body> Test test </body></html>`;
case 'js':
return `<script> alert("This shouldt not be executed"); </script>`
case "css":
return `*{ color: pink !important; }`;
case 'json':
return `{ 'test':'yes' }`;
case 'img':
return IMG;
}
return "Basic UTF-8 Text";
}
function handleRequest(request, response)
{
// avoid confusing cache behaviors
response.setHeader('X-Content-Type-Options', 'nosniff'); // Disable Sniffing
response.setHeader("Content-Type","garbage/garbage"); // Try Browser to force sniffing.
response.write(getSniffableContent(request.queryString));
return;
}

View File

@ -0,0 +1,33 @@
// Custom *.sjs file specifically for the needs of Bug 1286861
// small red image
const IMG = atob(
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
"P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
function getSniffableContent(selector){
switch(selector){
case "xml":
return `<?xml version="1.0"?><test/>`;
case "html":
return `<!Doctype html> <html> <head></head> <body> Test test </body></html>`;
case 'js':
return `<script> alert("This shouldt not be executed"); </script>`
case "css":
return `*{ color: pink !important; }`;
case 'json':
return `{ 'test':'yes' }`;
case 'img':
return IMG;
}
return "Basic UTF-8 Text";
}
function handleRequest(request, response)
{
// avoid confusing cache behaviors
response.setHeader('X-Content-Type-Options', 'nosniff'); // Disable Sniffing
response.setHeader("Content-Type","picture/png"); // Try Browser to force sniffing.
response.write(getSniffableContent(request.queryString));
return;
}

View File

@ -2,6 +2,9 @@
support-files =
file_contentpolicytype_targeted_link_iframe.sjs
file_nosniff_testserver.sjs
file_nosniff_navigation.sjs
file_nosniff_navigation_mismatch.sjs
file_nosniff_navigation_garbage.sjs
file_block_script_wrong_mime_server.sjs
file_block_toplevel_data_navigation.html
file_block_toplevel_data_navigation2.html
@ -24,6 +27,7 @@ support-files =
[test_contentpolicytype_targeted_link_iframe.html]
[test_nosniff.html]
[test_nosniff_navigation.html]
[test_block_script_wrong_mime.html]
[test_block_toplevel_data_navigation.html]
skip-if = toolkit == 'android' # intermittent failure

View File

@ -0,0 +1,65 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Bug 1428473 Support X-Content-Type-Options: nosniff when navigating</title>
<!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<style>
iframe{
border: 1px solid orange;
}
</style>
<iframe src="file_nosniff_navigation.sjs?xml"> </iframe>
<iframe src="file_nosniff_navigation.sjs?html"></iframe>
<iframe src="file_nosniff_navigation.sjs?css" ></iframe>
<iframe src="file_nosniff_navigation.sjs?json"></iframe>
<iframe src="file_nosniff_navigation.sjs?img"></iframe>
<hr>
<iframe src="file_nosniff_navigation_mismatch.sjs?html"></iframe>
<iframe src="file_nosniff_navigation_mismatch.sjs?xml"></iframe>
<iframe src="file_nosniff_navigation_mismatch.sjs"></iframe>
<iframe src="file_nosniff_navigation_garbage.sjs?xml"> </iframe>
<iframe src="file_nosniff_navigation_garbage.sjs?html"></iframe>
<iframe src="file_nosniff_navigation_garbage.sjs?css" ></iframe>
<iframe src="file_nosniff_navigation_garbage.sjs?json"></iframe>
<iframe src="file_nosniff_navigation_garbage.sjs?img"></iframe>
</head>
<body>
<!-- add the two script tests -->
<script id="scriptCorrectType"></script>
<script id="scriptWrongType"></script>
<script class="testbody" type="text/javascript">
/* Description of the test:
* We're testing if Firefox respects the nosniff Header for Top-Level
* Navigations.
* If Firefox cant Display the Page, it will prompt a download
* and the URL of the Page will be about:blank.
* So we will try to open different content send with
* no-mime, mismatched-mime and garbage-mime types.
*
*/
SimpleTest.waitForExplicitFinish();
window.addEventListener("load", ()=>{
let iframes = Array.from(document.querySelectorAll("iframe"));
iframes.forEach( frame => {
let result = frame.contentWindow.document.URL == "about:blank";
let sniffTarget = (new URL(frame.src)).search;
ok(result, `${sniffTarget} - was not Sniffed`);
});
SimpleTest.finish();
});
</script>
</body>
</html>

View File

@ -12,7 +12,7 @@ interface DataTransferItem {
readonly attribute DOMString kind;
readonly attribute DOMString type;
[Throws, NeedsSubjectPrincipal]
void getAsString(FunctionStringCallback? _callback);
void getAsString(FunctionStringCallback? callback);
[Throws, NeedsSubjectPrincipal]
File? getAsFile();
};

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