mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Merge mozilla-central to autoland. a=merge CLOSED TREE
This commit is contained in:
commit
1a7c7d8823
@ -21,8 +21,8 @@ ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "BrowserWindowTracker",
|
||||
"resource:///modules/BrowserWindowTracker.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "MasterPassword",
|
||||
"resource://formautofill/MasterPassword.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "OSKeyStore",
|
||||
"resource://formautofill/OSKeyStore.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
@ -155,7 +155,7 @@ var paymentDialogWrapper = {
|
||||
/**
|
||||
* @param {string} guid The GUID of the basic card record from storage.
|
||||
* @param {string} cardSecurityCode The associated card security code (CVV/CCV/etc.)
|
||||
* @throws if the user cancels entering their master password or an error decrypting
|
||||
* @throws If there is an error decrypting
|
||||
* @returns {nsIBasicCardResponseData?} returns response data or null (if the
|
||||
* master password dialog was cancelled);
|
||||
*/
|
||||
@ -168,7 +168,7 @@ var paymentDialogWrapper = {
|
||||
|
||||
let cardNumber;
|
||||
try {
|
||||
cardNumber = await MasterPassword.decrypt(cardData["cc-number-encrypted"], true);
|
||||
cardNumber = await OSKeyStore.decrypt(cardData["cc-number-encrypted"], true);
|
||||
} catch (ex) {
|
||||
if (ex.result != Cr.NS_ERROR_ABORT) {
|
||||
throw ex;
|
||||
@ -507,8 +507,16 @@ var paymentDialogWrapper = {
|
||||
selectedPaymentCardSecurityCode: cardSecurityCode,
|
||||
selectedShippingAddressGUID: shippingGUID,
|
||||
}) {
|
||||
let methodData = await this._convertProfileBasicCardToPaymentMethodData(paymentCardGUID,
|
||||
cardSecurityCode);
|
||||
let methodData;
|
||||
try {
|
||||
methodData = await this._convertProfileBasicCardToPaymentMethodData(paymentCardGUID,
|
||||
cardSecurityCode);
|
||||
} catch (ex) {
|
||||
// TODO (Bug 1498403): Some kind of "credit card storage error" here, perhaps asking user
|
||||
// to re-enter credit card # from management UI.
|
||||
Cu.reportError(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!methodData) {
|
||||
// TODO (Bug 1429265/Bug 1429205): Handle when a user hits cancel on the
|
||||
|
@ -327,7 +327,7 @@ let BASIC_CARDS_1 = {
|
||||
methodName: "basic-card",
|
||||
"cc-number": "************5461",
|
||||
"guid": "53f9d009aed2",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"timeCreated": 1505240896213,
|
||||
"timeLastModified": 1515609524588,
|
||||
"timeLastUsed": 10000,
|
||||
@ -345,7 +345,7 @@ let BASIC_CARDS_1 = {
|
||||
methodName: "basic-card",
|
||||
"cc-number": "************0954",
|
||||
"guid": "9h5d4h6f4d1s",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"timeCreated": 1517890536491,
|
||||
"timeLastModified": 1517890564518,
|
||||
"timeLastUsed": 50000,
|
||||
@ -363,7 +363,7 @@ let BASIC_CARDS_1 = {
|
||||
methodName: "basic-card",
|
||||
"cc-number": "************1234",
|
||||
"guid": "123456789abc",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"timeCreated": 1517890536491,
|
||||
"timeLastModified": 1517890564518,
|
||||
"timeLastUsed": 90000,
|
||||
@ -397,7 +397,7 @@ let BASIC_CARDS_1 = {
|
||||
methodName: "basic-card",
|
||||
"cc-number": "************8563",
|
||||
"guid": "missing-cc-name",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"timeCreated": 1517890536491,
|
||||
"timeLastModified": 1517890564518,
|
||||
"timeLastUsed": 30000,
|
||||
|
@ -3,6 +3,7 @@ head = head.js
|
||||
prefs =
|
||||
browser.pagethumbnails.capturing_disabled=true
|
||||
dom.payments.request.enabled=true
|
||||
extensions.formautofill.creditCards.available=true
|
||||
skip-if = !e10s # Bug 1365964 - Payment Request isn't implemented for non-e10s
|
||||
support-files =
|
||||
blank_page.html
|
||||
|
@ -21,6 +21,8 @@ const paymentUISrv = Cc["@mozilla.org/dom/payments/payment-ui-service;1"]
|
||||
const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
|
||||
const {formAutofillStorage} = ChromeUtils.import(
|
||||
"resource://formautofill/FormAutofillStorage.jsm", {});
|
||||
const {OSKeyStoreTestUtils} = ChromeUtils.import(
|
||||
"resource://testing-common/OSKeyStoreTestUtils.jsm", {});
|
||||
const {PaymentTestUtils: PTU} = ChromeUtils.import(
|
||||
"resource://testing-common/PaymentTestUtils.jsm", {});
|
||||
ChromeUtils.import("resource:///modules/BrowserWindowTracker.jsm");
|
||||
@ -362,10 +364,12 @@ add_task(async function setup_head() {
|
||||
}
|
||||
ok(false, msg.message || msg.errorMessage);
|
||||
});
|
||||
OSKeyStoreTestUtils.setup();
|
||||
await setupFormAutofillStorage();
|
||||
registerCleanupFunction(function cleanup() {
|
||||
registerCleanupFunction(async function cleanup() {
|
||||
paymentSrv.cleanup();
|
||||
cleanupFormAutofillStorage();
|
||||
await OSKeyStoreTestUtils.cleanup();
|
||||
Services.prefs.clearUserPref(RESPONSE_TIMEOUT_PREF);
|
||||
Services.prefs.clearUserPref(SAVE_CREDITCARD_DEFAULT_PREF);
|
||||
Services.prefs.clearUserPref(SAVE_ADDRESS_DEFAULT_PREF);
|
||||
|
@ -42,7 +42,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
FormAutofillPreferences: "resource://formautofill/FormAutofillPreferences.jsm",
|
||||
FormAutofillDoorhanger: "resource://formautofill/FormAutofillDoorhanger.jsm",
|
||||
FormAutofillUtils: "resource://formautofill/FormAutofillUtils.jsm",
|
||||
MasterPassword: "resource://formautofill/MasterPassword.jsm",
|
||||
OSKeyStore: "resource://formautofill/OSKeyStore.jsm",
|
||||
});
|
||||
|
||||
this.log = null;
|
||||
@ -225,8 +225,8 @@ FormAutofillParent.prototype = {
|
||||
break;
|
||||
}
|
||||
case "FormAutofill:SaveCreditCard": {
|
||||
if (!await MasterPassword.ensureLoggedIn()) {
|
||||
log.warn("User canceled master password entry");
|
||||
if (!await OSKeyStore.ensureLoggedIn()) {
|
||||
log.warn("User canceled encryption login");
|
||||
return;
|
||||
}
|
||||
await this.formAutofillStorage.creditCards.add(data.creditcard);
|
||||
@ -253,12 +253,12 @@ FormAutofillParent.prototype = {
|
||||
let {cipherText, reauth} = data;
|
||||
let string;
|
||||
try {
|
||||
string = await MasterPassword.decrypt(cipherText, reauth);
|
||||
string = await OSKeyStore.decrypt(cipherText, reauth);
|
||||
} catch (e) {
|
||||
if (e.result != Cr.NS_ERROR_ABORT) {
|
||||
throw e;
|
||||
}
|
||||
log.warn("User canceled master password entry");
|
||||
log.warn("User canceled encryption login");
|
||||
}
|
||||
target.sendAsyncMessage("FormAutofill:DecryptedString", string);
|
||||
break;
|
||||
@ -292,7 +292,7 @@ FormAutofillParent.prototype = {
|
||||
/**
|
||||
* Get the records from profile store and return results back to content
|
||||
* process. It will decrypt the credit card number and append
|
||||
* "cc-number-decrypted" to each record if MasterPassword isn't set.
|
||||
* "cc-number-decrypted" to each record if OSKeyStore isn't set.
|
||||
*
|
||||
* @private
|
||||
* @param {string} data.collectionName
|
||||
@ -317,9 +317,9 @@ FormAutofillParent.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
let isCCAndMPEnabled = collectionName == CREDITCARDS_COLLECTION_NAME && MasterPassword.isEnabled;
|
||||
// We don't filter "cc-number" when MasterPassword is set.
|
||||
if (isCCAndMPEnabled && info.fieldName == "cc-number") {
|
||||
let isCC = collectionName == CREDITCARDS_COLLECTION_NAME;
|
||||
// We don't filter "cc-number"
|
||||
if (isCC && info.fieldName == "cc-number") {
|
||||
recordsInCollection = recordsInCollection.filter(record => !!record["cc-number"]);
|
||||
target.sendAsyncMessage("FormAutofill:Records", recordsInCollection);
|
||||
return;
|
||||
@ -334,17 +334,6 @@ FormAutofillParent.prototype = {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cache the decrypted "cc-number" in each record for content to preview
|
||||
// when MasterPassword isn't set.
|
||||
if (!isCCAndMPEnabled && record["cc-number-encrypted"]) {
|
||||
record["cc-number-decrypted"] = await MasterPassword.decrypt(record["cc-number-encrypted"]);
|
||||
}
|
||||
|
||||
// Filter "cc-number" based on the decrypted one.
|
||||
if (info.fieldName == "cc-number") {
|
||||
fieldValue = record["cc-number-decrypted"];
|
||||
}
|
||||
|
||||
if (collectionName == ADDRESSES_COLLECTION_NAME && record.country
|
||||
&& !FormAutofill.supportedCountries.includes(record.country)) {
|
||||
// Address autofill isn't supported for the record's country so we don't
|
||||
@ -538,8 +527,8 @@ FormAutofillParent.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await MasterPassword.ensureLoggedIn()) {
|
||||
log.warn("User canceled master password entry");
|
||||
if (!await OSKeyStore.ensureLoggedIn()) {
|
||||
log.warn("User canceled encryption login");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -142,11 +142,14 @@ ChromeUtils.defineModuleGetter(this, "FormAutofillNameUtils",
|
||||
"resource://formautofill/FormAutofillNameUtils.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "FormAutofillUtils",
|
||||
"resource://formautofill/FormAutofillUtils.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "MasterPassword",
|
||||
"resource://formautofill/MasterPassword.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "OSKeyStore",
|
||||
"resource://formautofill/OSKeyStore.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "PhoneNumber",
|
||||
"resource://formautofill/phonenumberutils/PhoneNumber.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "cryptoSDR",
|
||||
"@mozilla.org/login-manager/crypto/SDR;1",
|
||||
Ci.nsILoginManagerCrypto);
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
|
||||
"@mozilla.org/uuid-generator;1",
|
||||
"nsIUUIDGenerator");
|
||||
@ -158,7 +161,7 @@ const PROFILE_JSON_FILE_NAME = "autofill-profiles.json";
|
||||
|
||||
const STORAGE_SCHEMA_VERSION = 1;
|
||||
const ADDRESS_SCHEMA_VERSION = 1;
|
||||
const CREDIT_CARD_SCHEMA_VERSION = 1;
|
||||
const CREDIT_CARD_SCHEMA_VERSION = 2;
|
||||
|
||||
const VALID_ADDRESS_FIELDS = [
|
||||
"given-name",
|
||||
@ -264,13 +267,14 @@ class AutofillRecords {
|
||||
this._collectionName = collectionName;
|
||||
this._schemaVersion = schemaVersion;
|
||||
|
||||
Promise.all(this._data.map(record => this._migrateRecord(record)))
|
||||
.then(hasChangesArr => {
|
||||
let dataHasChanges = hasChangesArr.find(hasChanges => hasChanges);
|
||||
if (dataHasChanges) {
|
||||
this._store.saveSoon();
|
||||
}
|
||||
});
|
||||
this._initializePromise =
|
||||
Promise.all(this._data.map(async (record, index) => this._migrateRecord(record, index)))
|
||||
.then(hasChangesArr => {
|
||||
let dataHasChanges = hasChangesArr.includes(true);
|
||||
if (dataHasChanges) {
|
||||
this._store.saveSoon();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -303,6 +307,14 @@ class AutofillRecords {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the records in the collection, resolves when the migration completes.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
initialize() {
|
||||
return this._initializePromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new record.
|
||||
*
|
||||
@ -1176,7 +1188,7 @@ class AutofillRecords {
|
||||
});
|
||||
}
|
||||
|
||||
async _migrateRecord(record) {
|
||||
async _migrateRecord(record, index) {
|
||||
let hasChanges = false;
|
||||
|
||||
if (record.deleted) {
|
||||
@ -1192,10 +1204,21 @@ class AutofillRecords {
|
||||
|
||||
if (record.version < this.version) {
|
||||
hasChanges = true;
|
||||
record.version = this.version;
|
||||
|
||||
// Force to recompute fields if we upgrade the schema.
|
||||
await this._stripComputedFields(record);
|
||||
record = await this._computeMigratedRecord(record);
|
||||
|
||||
if (record.deleted) {
|
||||
// record is deleted by _computeMigratedRecord(),
|
||||
// go ahead and put it in the store.
|
||||
this._data[index] = record;
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
// Compute the computed fields before putting it to store.
|
||||
await this.computeFields(record);
|
||||
this._data[index] = record;
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
hasChanges |= await this.computeFields(record);
|
||||
@ -1256,6 +1279,24 @@ class AutofillRecords {
|
||||
}}, "formautofill-storage-changed", "removeAll");
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip the computed fields based on the record version.
|
||||
* @param {Object} record The record to migrate
|
||||
* @returns {Object} Migrated record.
|
||||
* Record is always cloned, with version updated,
|
||||
* with computed fields stripped.
|
||||
* Could be a tombstone record, if the record
|
||||
* should be discorded.
|
||||
*/
|
||||
async _computeMigratedRecord(record) {
|
||||
if (!record.deleted) {
|
||||
record = this._clone(record);
|
||||
await this._stripComputedFields(record);
|
||||
record.version = this.version;
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
async _stripComputedFields(record) {
|
||||
this.VALID_COMPUTED_FIELDS.forEach(field => delete record[field]);
|
||||
}
|
||||
@ -1604,7 +1645,7 @@ class CreditCards extends AutofillRecords {
|
||||
if ("cc-number" in creditCard) {
|
||||
let ccNumber = creditCard["cc-number"];
|
||||
creditCard["cc-number"] = CreditCard.getLongMaskedNumber(ccNumber);
|
||||
creditCard["cc-number-encrypted"] = await MasterPassword.encrypt(ccNumber);
|
||||
creditCard["cc-number-encrypted"] = await OSKeyStore.encrypt(ccNumber);
|
||||
} else {
|
||||
creditCard["cc-number-encrypted"] = "";
|
||||
}
|
||||
@ -1613,9 +1654,63 @@ class CreditCards extends AutofillRecords {
|
||||
return hasNewComputedFields;
|
||||
}
|
||||
|
||||
async _computeMigratedRecord(creditCard) {
|
||||
if (creditCard["cc-number-encrypted"]) {
|
||||
switch (creditCard.version) {
|
||||
case 1: {
|
||||
if (!cryptoSDR.isLoggedIn) {
|
||||
// We cannot decrypt the data, so silently remove the record for
|
||||
// the user.
|
||||
if (creditCard.deleted) {
|
||||
break;
|
||||
}
|
||||
|
||||
this.log.warn("Removing version 1 credit card record to migrate to new encryption:", creditCard.guid);
|
||||
|
||||
// Replace the record with a tombstone record here,
|
||||
// regardless of existence of sync metadata.
|
||||
let existingSync = this._getSyncMetaData(creditCard);
|
||||
creditCard = {
|
||||
guid: creditCard.guid,
|
||||
timeLastModified: Date.now(),
|
||||
deleted: true,
|
||||
};
|
||||
|
||||
if (existingSync) {
|
||||
creditCard._sync = existingSync;
|
||||
existingSync.changeCounter++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
creditCard = this._clone(creditCard);
|
||||
|
||||
// Decrypt the cc-number using version 1 encryption.
|
||||
let ccNumber = cryptoSDR.decrypt(creditCard["cc-number-encrypted"]);
|
||||
// Re-encrypt the cc-number with version 2 encryption.
|
||||
creditCard["cc-number-encrypted"] = await OSKeyStore.encrypt(ccNumber);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error("Unknown credit card version to migrate: " + creditCard.version);
|
||||
}
|
||||
}
|
||||
return super._computeMigratedRecord(creditCard);
|
||||
}
|
||||
|
||||
async _stripComputedFields(creditCard) {
|
||||
if (creditCard["cc-number-encrypted"]) {
|
||||
creditCard["cc-number"] = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
|
||||
try {
|
||||
creditCard["cc-number"] = await OSKeyStore.decrypt(creditCard["cc-number-encrypted"]);
|
||||
} catch (ex) {
|
||||
if (ex.result == Cr.NS_ERROR_ABORT) {
|
||||
throw ex;
|
||||
}
|
||||
// Quietly recover from encryption error,
|
||||
// so existing credit card entry with undecryptable number
|
||||
// can be updated.
|
||||
}
|
||||
}
|
||||
await super._stripComputedFields(creditCard);
|
||||
}
|
||||
@ -1676,6 +1771,33 @@ class CreditCards extends AutofillRecords {
|
||||
}
|
||||
}
|
||||
|
||||
_ensureMatchingVersion(record) {
|
||||
if (!record.version || isNaN(record.version) || record.version < 1) {
|
||||
throw new Error(`Got invalid record version ${
|
||||
record.version}; want ${this.version}`);
|
||||
}
|
||||
|
||||
if (record.version < this.version) {
|
||||
switch (record.version) {
|
||||
case 1:
|
||||
// The difference between version 1 and 2 is only about the encryption
|
||||
// method used for the cc-number-encrypted field.
|
||||
// As long as the record is already decrypted, it is safe to bump the
|
||||
// version directly.
|
||||
if (!record["cc-number-encrypted"]) {
|
||||
record.version = this.version;
|
||||
} else {
|
||||
throw new Error("Unexpected record migration path.");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unknown credit card version to match: " + record.version);
|
||||
}
|
||||
}
|
||||
|
||||
return super._ensureMatchingVersion(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the given record and return the first matched guid if storage has the same record.
|
||||
* @param {Object} targetCreditCard
|
||||
@ -1692,12 +1814,9 @@ class CreditCards extends AutofillRecords {
|
||||
return !creditCard[field];
|
||||
}
|
||||
if (field == "cc-number" && creditCard[field]) {
|
||||
if (MasterPassword.isEnabled) {
|
||||
// Compare the masked numbers instead when the master password is
|
||||
// enabled because we don't want to leak the credit card number.
|
||||
return CreditCard.getLongMaskedNumber(clonedTargetCreditCard[field]) == creditCard[field];
|
||||
}
|
||||
return (clonedTargetCreditCard[field] == await MasterPassword.decrypt(creditCard["cc-number-encrypted"]));
|
||||
// Compare the masked numbers instead when decryption requires a password
|
||||
// because we don't want to leak the credit card number.
|
||||
return CreditCard.getLongMaskedNumber(clonedTargetCreditCard[field]) == creditCard[field];
|
||||
}
|
||||
return clonedTargetCreditCard[field] == creditCard[field];
|
||||
})).then(fieldResults => fieldResults.every(result => result));
|
||||
@ -1805,7 +1924,23 @@ FormAutofillStorage.prototype = {
|
||||
path: this._path,
|
||||
dataPostProcessor: this._dataPostProcessor.bind(this),
|
||||
});
|
||||
this._initializePromise = this._store.load();
|
||||
this._initializePromise = this._store.load()
|
||||
.then(() => {
|
||||
let initializeAutofillRecords = [this.addresses.initialize()];
|
||||
if (FormAutofill.isAutofillCreditCardsEnabled) {
|
||||
initializeAutofillRecords.push(this.creditCards.initialize());
|
||||
} else {
|
||||
// Make creditCards records unavailable to other modules
|
||||
// because we never initialize it.
|
||||
Object.defineProperty(this, "creditCards", {
|
||||
get() {
|
||||
throw new Error("CreditCards is not initialized. " +
|
||||
"Please restart if you flip the pref manually.");
|
||||
},
|
||||
});
|
||||
}
|
||||
return Promise.all(initializeAutofillRecords);
|
||||
});
|
||||
}
|
||||
return this._initializePromise;
|
||||
},
|
||||
|
@ -17,7 +17,7 @@ const EDIT_ADDRESS_KEYWORDS = [
|
||||
"givenName", "additionalName", "familyName", "organization2", "streetAddress",
|
||||
"state", "province", "city", "country", "zip", "postalCode", "email", "tel",
|
||||
];
|
||||
const MANAGE_CREDITCARDS_KEYWORDS = ["manageCreditCardsTitle", "addNewCreditCardTitle", "showCreditCardsBtnLabel"];
|
||||
const MANAGE_CREDITCARDS_KEYWORDS = ["manageCreditCardsTitle", "addNewCreditCardTitle"];
|
||||
const EDIT_CREDITCARD_KEYWORDS = ["cardNumber", "nameOnCard", "cardExpiresMonth", "cardExpiresYear", "cardNetwork"];
|
||||
const FIELD_STATES = {
|
||||
NORMAL: "NORMAL",
|
||||
|
@ -1,184 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Helpers for the Master Password Dialog.
|
||||
* In the future the Master Password implementation may move here.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = [
|
||||
"MasterPassword",
|
||||
];
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "cryptoSDR",
|
||||
"@mozilla.org/login-manager/crypto/SDR;1",
|
||||
Ci.nsILoginManagerCrypto);
|
||||
|
||||
var MasterPassword = {
|
||||
get _token() {
|
||||
let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"].createInstance(Ci.nsIPK11TokenDB);
|
||||
return tokendb.getInternalKeyToken();
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {boolean} True if a master password is set and false otherwise.
|
||||
*/
|
||||
get isEnabled() {
|
||||
return this._token.hasPassword;
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {boolean} True if master password is logged in and false if not.
|
||||
*/
|
||||
get isLoggedIn() {
|
||||
return Services.logins.isLoggedIn;
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {boolean} True if there is another master password login dialog
|
||||
* existing and false otherwise.
|
||||
*/
|
||||
get isUIBusy() {
|
||||
return Services.logins.uiBusy;
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensure the master password is logged in. It will display the master password
|
||||
* login prompt or do nothing if it's logged in already. If an existing MP
|
||||
* prompt is already prompted, the result from it will be used instead.
|
||||
*
|
||||
* @param {boolean} reauth Prompt the login dialog no matter it's logged in
|
||||
* or not if it's set to true.
|
||||
* @returns {Promise<boolean>} True if it's logged in or no password is set
|
||||
* and false if it's still not logged in (prompt
|
||||
* canceled or other error).
|
||||
*/
|
||||
async ensureLoggedIn(reauth = false) {
|
||||
if (!this.isEnabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isLoggedIn && !reauth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If a prompt is already showing then wait for and focus it.
|
||||
if (this.isUIBusy) {
|
||||
return this.waitForExistingDialog();
|
||||
}
|
||||
|
||||
let token = this._token;
|
||||
try {
|
||||
// 'true' means always prompt for token password. User will be prompted until
|
||||
// clicking 'Cancel' or entering the correct password.
|
||||
token.login(true);
|
||||
} catch (e) {
|
||||
// An exception will be thrown if the user cancels the login prompt dialog.
|
||||
// User is also logged out.
|
||||
}
|
||||
|
||||
// If we triggered a master password prompt, notify observers.
|
||||
if (token.isLoggedIn()) {
|
||||
Services.obs.notifyObservers(null, "passwordmgr-crypto-login");
|
||||
} else {
|
||||
Services.obs.notifyObservers(null, "passwordmgr-crypto-loginCanceled");
|
||||
}
|
||||
|
||||
return token.isLoggedIn();
|
||||
},
|
||||
|
||||
/**
|
||||
* Decrypts cipherText.
|
||||
*
|
||||
* @param {string} cipherText Encrypted string including the algorithm details.
|
||||
* @param {boolean} reauth True if we want to force the prompt to show up
|
||||
* even if the user is already logged in.
|
||||
* @returns {Promise<string>} resolves to the decrypted string, or rejects otherwise.
|
||||
*/
|
||||
async decrypt(cipherText, reauth = false) {
|
||||
if (!await this.ensureLoggedIn(reauth)) {
|
||||
throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
|
||||
}
|
||||
return cryptoSDR.decrypt(cipherText);
|
||||
},
|
||||
|
||||
/**
|
||||
* Encrypts a string and returns cipher text containing algorithm information used for decryption.
|
||||
*
|
||||
* @param {string} plainText Original string without encryption.
|
||||
* @returns {Promise<string>} resolves to the encrypted string (with algorithm), otherwise rejects.
|
||||
*/
|
||||
async encrypt(plainText) {
|
||||
if (!await this.ensureLoggedIn()) {
|
||||
throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
|
||||
}
|
||||
|
||||
return cryptoSDR.encrypt(plainText);
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolve when master password dialogs are closed, immediately if none are open.
|
||||
*
|
||||
* An existing MP dialog will be focused and will request attention.
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
* Resolves with whether the user is logged in to MP.
|
||||
*/
|
||||
async waitForExistingDialog() {
|
||||
if (!this.isUIBusy) {
|
||||
log.debug("waitForExistingDialog: Dialog isn't showing. isLoggedIn:", this.isLoggedIn);
|
||||
return this.isLoggedIn;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
log.debug("waitForExistingDialog: Observing the open dialog");
|
||||
let observer = {
|
||||
QueryInterface: ChromeUtils.generateQI([
|
||||
Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference,
|
||||
]),
|
||||
|
||||
observe(subject, topic, data) {
|
||||
log.debug("waitForExistingDialog: Got notification:", topic);
|
||||
// Only run observer once.
|
||||
Services.obs.removeObserver(this, "passwordmgr-crypto-login");
|
||||
Services.obs.removeObserver(this, "passwordmgr-crypto-loginCanceled");
|
||||
if (topic == "passwordmgr-crypto-loginCanceled") {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(true);
|
||||
},
|
||||
};
|
||||
|
||||
// Possible leak: it's possible that neither of these notifications
|
||||
// will fire, and if that happens, we'll leak the observer (and
|
||||
// never return). We should guarantee that at least one of these
|
||||
// will fire.
|
||||
// See bug XXX.
|
||||
Services.obs.addObserver(observer, "passwordmgr-crypto-login");
|
||||
Services.obs.addObserver(observer, "passwordmgr-crypto-loginCanceled");
|
||||
|
||||
// Focus and draw attention to the existing master password dialog for the
|
||||
// occassions where it's not attached to the current window.
|
||||
let promptWin = Services.wm.getMostRecentWindow("prompt:promptPassword");
|
||||
promptWin.focus();
|
||||
promptWin.getAttention();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "log", () => {
|
||||
let ConsoleAPI = ChromeUtils.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
|
||||
return new ConsoleAPI({
|
||||
maxLogLevelPref: "masterPassword.loglevel",
|
||||
prefix: "Master Password",
|
||||
});
|
||||
});
|
251
browser/extensions/formautofill/OSKeyStore.jsm
Normal file
251
browser/extensions/formautofill/OSKeyStore.jsm
Normal file
@ -0,0 +1,251 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Helpers for using OS Key Store.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = [
|
||||
"OSKeyStore",
|
||||
];
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "nativeOSKeyStore",
|
||||
"@mozilla.org/security/oskeystore;1", Ci.nsIOSKeyStore);
|
||||
|
||||
// Skip reauth during tests, only works in non-official builds.
|
||||
const TEST_ONLY_REAUTH = "extensions.formautofill.osKeyStore.unofficialBuildOnlyLogin";
|
||||
|
||||
var OSKeyStore = {
|
||||
/**
|
||||
* On macOS this becomes part of the name label visible on Keychain Acesss as
|
||||
* "org.mozilla.nss.keystore.firefox" (where "firefox" is the MOZ_APP_NAME).
|
||||
*/
|
||||
STORE_LABEL: AppConstants.MOZ_APP_NAME,
|
||||
|
||||
/**
|
||||
* Consider the module is initialized as locked. OS might unlock without a
|
||||
* prompt.
|
||||
* @type {Boolean}
|
||||
*/
|
||||
_isLocked: true,
|
||||
|
||||
_pendingUnlockPromise: null,
|
||||
|
||||
/**
|
||||
* @returns {boolean} True if logged in (i.e. decrypt(reauth = false) will
|
||||
* not retrigger a dialog) and false if not.
|
||||
* User might log out elsewhere in the OS, so even if this
|
||||
* is true a prompt might still pop up.
|
||||
*/
|
||||
get isLoggedIn() {
|
||||
return !this._isLocked;
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {boolean} True if there is another login dialog existing and false
|
||||
* otherwise.
|
||||
*/
|
||||
get isUIBusy() {
|
||||
return !!this._pendingUnlockPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* If the test pref exist and applicable,
|
||||
* this method will dispatch a observer message and return
|
||||
* to simulate successful reauth, or throw to simulate
|
||||
* failed reauth.
|
||||
*
|
||||
* @returns {boolean} True when reauth should NOT be skipped,
|
||||
* false when reauth has been skipped.
|
||||
* @throws If it needs to simulate reauth login failure.
|
||||
*/
|
||||
_maybeSkipReauthForTest() {
|
||||
// Don't take test reauth pref in the following configurations.
|
||||
if (nativeOSKeyStore.isNSSKeyStore ||
|
||||
AppConstants.MOZILLA_OFFICIAL ||
|
||||
!this._testReauth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip this reauth because there is no way to mock the
|
||||
// native dialog in the testing environment, for now.
|
||||
log.debug("_ensureReauth: _testReauth: ", this._testReauth);
|
||||
switch (this._testReauth) {
|
||||
case "pass":
|
||||
Services.obs.notifyObservers(null, "oskeystore-testonly-reauth", "pass");
|
||||
return false;
|
||||
case "cancel":
|
||||
Services.obs.notifyObservers(null, "oskeystore-testonly-reauth", "cancel");
|
||||
throw new Components.Exception("Simulating user cancelling login dialog", Cr.NS_ERROR_FAILURE);
|
||||
default:
|
||||
throw new Components.Exception("Unknown test pref value", Cr.NS_ERROR_FAILURE);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensure the store in use is logged in. It will display the OS login
|
||||
* login prompt or do nothing if it's logged in already. If an existing login
|
||||
* prompt is already prompted, the result from it will be used instead.
|
||||
*
|
||||
* Note: This method must set _pendingUnlockPromise before returning the
|
||||
* promise (i.e. the first |await|), otherwise we'll risk re-entry.
|
||||
* This is why there aren't an |await| in the method. The method is marked as
|
||||
* |async| to communicate that it's async.
|
||||
*
|
||||
* @param {boolean} reauth Prompt the login dialog no matter it's logged in
|
||||
* or not if it's set to true.
|
||||
* @returns {Promise<boolean>} True if it's logged in or no password is set
|
||||
* and false if it's still not logged in (prompt
|
||||
* canceled or other error).
|
||||
*/
|
||||
async ensureLoggedIn(reauth = false) {
|
||||
if (this._pendingUnlockPromise) {
|
||||
log.debug("ensureLoggedIn: Has a pending unlock operation");
|
||||
return this._pendingUnlockPromise;
|
||||
}
|
||||
log.debug("ensureLoggedIn: Creating new pending unlock promise. reauth: ", reauth);
|
||||
|
||||
// TODO: Implementing re-auth by passing this value to the native implementation
|
||||
// in some way. Set this to false for now to ignore the reauth request (bug 1429265).
|
||||
reauth = false;
|
||||
|
||||
let unlockPromise = Promise.resolve().then(async () => {
|
||||
if (reauth) {
|
||||
reauth = this._maybeSkipReauthForTest();
|
||||
}
|
||||
|
||||
if (!await nativeOSKeyStore.asyncSecretAvailable(this.STORE_LABEL)) {
|
||||
log.debug("ensureLoggedIn: Secret unavailable, attempt to generate new secret.");
|
||||
let recoveryPhrase = await nativeOSKeyStore.asyncGenerateSecret(this.STORE_LABEL);
|
||||
// TODO We should somehow have a dialog to ask the user to write this down,
|
||||
// and another dialog somewhere for the user to restore the secret with it.
|
||||
// (Intentionally not printing it out in the console)
|
||||
log.debug("ensureLoggedIn: Secret generated. Recovery phrase length: " + recoveryPhrase.length);
|
||||
}
|
||||
});
|
||||
|
||||
if (nativeOSKeyStore.isNSSKeyStore) {
|
||||
// Workaround bug 1492305: NSS-implemented methods don't reject when user cancels.
|
||||
unlockPromise = unlockPromise.then(() => {
|
||||
log.debug("ensureLoggedIn: isNSSKeyStore: ", reauth, Services.logins.isLoggedIn);
|
||||
// User has hit the cancel button on the master password prompt.
|
||||
// We must reject the promise chain here.
|
||||
if (!Services.logins.isLoggedIn) {
|
||||
throw Components.Exception("User canceled OS unlock entry (Workaround)", Cr.NS_ERROR_FAILURE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
unlockPromise = unlockPromise.then(() => {
|
||||
log.debug("ensureLoggedIn: Logged in");
|
||||
this._pendingUnlockPromise = null;
|
||||
this._isLocked = false;
|
||||
|
||||
return true;
|
||||
}, (err) => {
|
||||
log.debug("ensureLoggedIn: Not logged in", err);
|
||||
this._pendingUnlockPromise = null;
|
||||
this._isLocked = true;
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this._pendingUnlockPromise = unlockPromise;
|
||||
|
||||
return this._pendingUnlockPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Decrypts cipherText.
|
||||
*
|
||||
* Note: In the event of an rejection, check the result property of the Exception
|
||||
* object. Handles NS_ERROR_ABORT as user has cancelled the action (e.g.,
|
||||
* don't show that dialog), apart from other errors (e.g., gracefully
|
||||
* recover from that and still shows the dialog.)
|
||||
*
|
||||
* @param {string} cipherText Encrypted string including the algorithm details.
|
||||
* @param {boolean} reauth True if we want to force the prompt to show up
|
||||
* even if the user is already logged in.
|
||||
* @returns {Promise<string>} resolves to the decrypted string, or rejects otherwise.
|
||||
*/
|
||||
async decrypt(cipherText, reauth = false) {
|
||||
if (!await this.ensureLoggedIn(reauth)) {
|
||||
throw Components.Exception("User canceled OS unlock entry", Cr.NS_ERROR_ABORT);
|
||||
}
|
||||
let bytes = await nativeOSKeyStore.asyncDecryptBytes(this.STORE_LABEL, cipherText);
|
||||
return String.fromCharCode.apply(String, bytes);
|
||||
},
|
||||
|
||||
/**
|
||||
* Encrypts a string and returns cipher text containing algorithm information used for decryption.
|
||||
*
|
||||
* @param {string} plainText Original string without encryption.
|
||||
* @returns {Promise<string>} resolves to the encrypted string (with algorithm), otherwise rejects.
|
||||
*/
|
||||
async encrypt(plainText) {
|
||||
if (!await this.ensureLoggedIn()) {
|
||||
throw Components.Exception("User canceled OS unlock entry", Cr.NS_ERROR_ABORT);
|
||||
}
|
||||
|
||||
// Convert plain text into a UTF-8 binary string
|
||||
plainText = unescape(encodeURIComponent(plainText));
|
||||
|
||||
// Convert it to an array
|
||||
let textArr = [];
|
||||
for (let char of plainText) {
|
||||
textArr.push(char.charCodeAt(0));
|
||||
}
|
||||
|
||||
let rawEncryptedText = await nativeOSKeyStore.asyncEncryptBytes(this.STORE_LABEL, textArr.length, textArr);
|
||||
|
||||
// Mark the output with a version number.
|
||||
return rawEncryptedText;
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolve when the login dialogs are closed, immediately if none are open.
|
||||
*
|
||||
* An existing MP dialog will be focused and will request attention.
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
* Resolves with whether the user is logged in to MP.
|
||||
*/
|
||||
async waitForExistingDialog() {
|
||||
if (this.isUIBusy) {
|
||||
return this._pendingUnlockPromise;
|
||||
}
|
||||
return this.isLoggedIn;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the store. For tests.
|
||||
*/
|
||||
async cleanup() {
|
||||
return nativeOSKeyStore.asyncDeleteSecret(this.STORE_LABEL);
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the implementation is using the NSS key store.
|
||||
* If so, tests will be able to handle the reauth dialog.
|
||||
*/
|
||||
get isNSSKeyStore() {
|
||||
return nativeOSKeyStore.isNSSKeyStore;
|
||||
},
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "log", () => {
|
||||
let ConsoleAPI = ChromeUtils.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
|
||||
return new ConsoleAPI({
|
||||
maxLogLevelPref: "extensions.formautofill.loglevel",
|
||||
prefix: "OSKeyStore",
|
||||
});
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(OSKeyStore, "_testReauth", TEST_ONLY_REAUTH, "");
|
@ -20,7 +20,6 @@
|
||||
</fieldset>
|
||||
<div id="controls-container">
|
||||
<button id="remove" disabled="disabled" data-localization="removeBtnLabel"/>
|
||||
<button id="show-hide-credit-cards" data-localization="showCreditCardsBtnLabel"/>
|
||||
<!-- Wrapper is used to properly compute the search tooltip position -->
|
||||
<div>
|
||||
<button id="add" data-localization="addBtnLabel"/>
|
||||
@ -34,7 +33,6 @@
|
||||
records: document.getElementById("credit-cards"),
|
||||
controlsContainer: document.getElementById("controls-container"),
|
||||
remove: document.getElementById("remove"),
|
||||
showHideCreditCards: document.getElementById("show-hide-credit-cards"),
|
||||
add: document.getElementById("add"),
|
||||
edit: document.getElementById("edit"),
|
||||
});
|
||||
|
@ -19,8 +19,8 @@ ChromeUtils.defineModuleGetter(this, "formAutofillStorage",
|
||||
"resource://formautofill/FormAutofillStorage.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "FormAutofillUtils",
|
||||
"resource://formautofill/FormAutofillUtils.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "MasterPassword",
|
||||
"resource://formautofill/MasterPassword.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "OSKeyStore",
|
||||
"resource://formautofill/OSKeyStore.jsm");
|
||||
|
||||
this.log = null;
|
||||
FormAutofill.defineLazyLogGetter(this, "manageAddresses");
|
||||
@ -317,11 +317,7 @@ class ManageCreditCards extends ManageRecords {
|
||||
elements.add.setAttribute("searchkeywords", FormAutofillUtils.EDIT_CREDITCARD_KEYWORDS
|
||||
.map(key => FormAutofillUtils.stringBundle.GetStringFromName(key))
|
||||
.join("\n"));
|
||||
this._hasMasterPassword = MasterPassword.isEnabled;
|
||||
this._isDecrypted = false;
|
||||
if (this._hasMasterPassword) {
|
||||
elements.showHideCreditCards.setAttribute("hidden", true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -330,12 +326,24 @@ class ManageCreditCards extends ManageRecords {
|
||||
* @param {object} creditCard [optional]
|
||||
*/
|
||||
async openEditDialog(creditCard) {
|
||||
// If master password is set, ask for password if user is trying to edit an
|
||||
// existing credit card.
|
||||
if (!creditCard || !this._hasMasterPassword || await MasterPassword.ensureLoggedIn(true)) {
|
||||
// Ask for reauth if user is trying to edit an existing credit card.
|
||||
if (!creditCard || await OSKeyStore.ensureLoggedIn(true)) {
|
||||
let decryptedCCNumObj = {};
|
||||
if (creditCard) {
|
||||
decryptedCCNumObj["cc-number"] = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
|
||||
try {
|
||||
decryptedCCNumObj["cc-number"] = await OSKeyStore.decrypt(creditCard["cc-number-encrypted"]);
|
||||
} catch (ex) {
|
||||
if (ex.result == Cr.NS_ERROR_ABORT) {
|
||||
// User shouldn't be ask to reauth here, but it could happen.
|
||||
// Return here and skip opening the dialog.
|
||||
return;
|
||||
}
|
||||
// We've got ourselves a real error.
|
||||
// Recover from encryption error so the user gets a chance to re-enter
|
||||
// unencrypted credit card number.
|
||||
decryptedCCNumObj["cc-number"] = "";
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
}
|
||||
let decryptedCreditCard = Object.assign({}, creditCard, decryptedCCNumObj);
|
||||
this.prefWin.gSubDialog.open(EDIT_CREDIT_CARD_URL, "resizable=no", {
|
||||
@ -363,12 +371,6 @@ class ManageCreditCards extends ManageRecords {
|
||||
return cardObj.getLabel({showNumbers: showCreditCards});
|
||||
}
|
||||
|
||||
async toggleShowHideCards(options) {
|
||||
this._isDecrypted = !this._isDecrypted;
|
||||
this.updateShowHideButtonState();
|
||||
await this.updateLabels(options, this._isDecrypted);
|
||||
}
|
||||
|
||||
async updateLabels(options, isDecrypted) {
|
||||
for (let option of options) {
|
||||
option.text = await this.getLabel(option.record, isDecrypted);
|
||||
@ -396,25 +398,10 @@ class ManageCreditCards extends ManageRecords {
|
||||
}
|
||||
|
||||
updateButtonsStates(selectedCount) {
|
||||
this.updateShowHideButtonState();
|
||||
super.updateButtonsStates(selectedCount);
|
||||
}
|
||||
|
||||
updateShowHideButtonState() {
|
||||
if (this._elements.records.length) {
|
||||
this._elements.showHideCreditCards.removeAttribute("disabled");
|
||||
} else {
|
||||
this._elements.showHideCreditCards.setAttribute("disabled", true);
|
||||
}
|
||||
this._elements.showHideCreditCards.textContent =
|
||||
this._isDecrypted ? FormAutofillUtils.stringBundle.GetStringFromName("hideCreditCardsBtnLabel") :
|
||||
FormAutofillUtils.stringBundle.GetStringFromName("showCreditCardsBtnLabel");
|
||||
}
|
||||
|
||||
handleClick(event) {
|
||||
if (event.target == this._elements.showHideCreditCards) {
|
||||
this.toggleShowHideCards(this._elements.records.options);
|
||||
}
|
||||
super.handleClick(event);
|
||||
}
|
||||
}
|
||||
|
@ -104,8 +104,6 @@ manageCreditCardsTitle = Saved Credit Cards
|
||||
# in browser preferences.
|
||||
addressesListHeader = Addresses
|
||||
creditCardsListHeader = Credit Cards
|
||||
showCreditCardsBtnLabel = Show Credit Cards
|
||||
hideCreditCardsBtnLabel = Hide Credit Cards
|
||||
removeBtnLabel = Remove
|
||||
addBtnLabel = Add…
|
||||
editBtnLabel = Edit…
|
||||
|
@ -32,6 +32,8 @@ elif CONFIG['OS_ARCH'] == 'WINNT':
|
||||
'skin/windows/editDialog.css',
|
||||
]
|
||||
|
||||
TESTING_JS_MODULES += ['test/fixtures/OSKeyStoreTestUtils.jsm']
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
|
||||
|
@ -15,7 +15,8 @@ skip-if = (verify && (os == 'win' || os == 'mac'))
|
||||
[browser_check_installed.js]
|
||||
[browser_creditCard_doorhanger.js]
|
||||
skip-if = (os == "linux") || (os == "mac" && debug) || (os == "win") # bug 1425884
|
||||
[browser_creditCard_fill_master_password.js]
|
||||
[browser_creditCard_fill_cancel_login.js]
|
||||
skip-if = true # Re-auth is not implemented, cannot cancel OS key store login (bug 1429265)
|
||||
[browser_dropdown_layout.js]
|
||||
[browser_editAddressDialog.js]
|
||||
[browser_editCreditCardDialog.js]
|
||||
|
@ -51,6 +51,7 @@ add_task(async function test_submit_creditCard_saved() {
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
"popupshown");
|
||||
let onChanged = TestUtils.topicObserved("formautofill-storage-changed");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
@ -69,6 +70,7 @@ add_task(async function test_submit_creditCard_saved() {
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
await onChanged;
|
||||
}
|
||||
);
|
||||
|
||||
@ -82,6 +84,11 @@ add_task(async function test_submit_creditCard_saved() {
|
||||
});
|
||||
|
||||
add_task(async function test_submit_untouched_creditCard_form() {
|
||||
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
|
||||
todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
[CREDITCARDS_USED_STATUS_PREF, 0],
|
||||
@ -90,11 +97,16 @@ add_task(async function test_submit_untouched_creditCard_form() {
|
||||
await saveCreditCard(TEST_CREDIT_CARD_1);
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "1 credit card in storage");
|
||||
|
||||
let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
|
||||
let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "notifyUsed");
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
await openPopupOn(browser, "form #cc-name");
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
|
||||
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
|
||||
await osKeyStoreLoginShown;
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
let form = content.document.getElementById("form");
|
||||
|
||||
@ -107,6 +119,7 @@ add_task(async function test_submit_untouched_creditCard_form() {
|
||||
is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
|
||||
}
|
||||
);
|
||||
await onUsed;
|
||||
|
||||
creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "Still 1 credit card");
|
||||
@ -125,6 +138,9 @@ add_task(async function test_submit_changed_subset_creditCard_form() {
|
||||
await saveCreditCard(TEST_CREDIT_CARD_1);
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "1 credit card in storage");
|
||||
|
||||
let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "notifyUsed");
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
@ -149,6 +165,7 @@ add_task(async function test_submit_changed_subset_creditCard_form() {
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
}
|
||||
);
|
||||
await onUsed;
|
||||
|
||||
creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "Still 1 credit card in storage");
|
||||
@ -274,78 +291,6 @@ add_task(async function test_submit_creditCard_never_save() {
|
||||
SpecialPowers.clearUserPref(ENABLED_AUTOFILL_CREDITCARDS_PREF);
|
||||
});
|
||||
|
||||
add_task(async function test_submit_creditCard_saved_with_mp_enabled() {
|
||||
LoginTestUtils.masterPassword.enable();
|
||||
// Login with the masterPassword in LoginTestUtils.
|
||||
let masterPasswordDialogShown = waitForMasterPasswordDialog(true);
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
"popupshown");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
name.focus();
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
name.setUserInput("User 0");
|
||||
|
||||
let number = form.querySelector("#cc-number");
|
||||
number.setUserInput("6387060366272981");
|
||||
|
||||
// Wait 1000ms before submission to make sure the input value applied
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
form.querySelector("input[type=submit]").click();
|
||||
});
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
await masterPasswordDialogShown;
|
||||
await TestUtils.topicObserved("formautofill-storage-changed");
|
||||
}
|
||||
);
|
||||
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "1 credit card in storage");
|
||||
is(creditCards[0]["cc-name"], "User 0", "Verify the name field");
|
||||
is(creditCards[0]["cc-number"], "************2981", "Verify the card number field");
|
||||
LoginTestUtils.masterPassword.disable();
|
||||
await removeAllRecords();
|
||||
});
|
||||
|
||||
add_task(async function test_submit_creditCard_saved_with_mp_enabled_but_canceled() {
|
||||
LoginTestUtils.masterPassword.enable();
|
||||
let masterPasswordDialogShown = waitForMasterPasswordDialog();
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
"popupshown");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
name.focus();
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
name.setUserInput("User 2");
|
||||
|
||||
let number = form.querySelector("#cc-number");
|
||||
number.setUserInput("5471839082338112");
|
||||
|
||||
// Wait 1000ms before submission to make sure the input value applied
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
form.querySelector("input[type=submit]").click();
|
||||
});
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
await masterPasswordDialogShown;
|
||||
}
|
||||
);
|
||||
|
||||
await sleep(1000);
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 0, "No credit cards in storage");
|
||||
LoginTestUtils.masterPassword.disable();
|
||||
});
|
||||
|
||||
add_task(async function test_submit_creditCard_with_sync_account() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
@ -445,6 +390,8 @@ add_task(async function test_submit_manual_mergeable_creditCard_form() {
|
||||
await saveCreditCard(TEST_CREDIT_CARD_3);
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "1 credit card in storage");
|
||||
let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "notifyUsed");
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
@ -467,6 +414,7 @@ add_task(async function test_submit_manual_mergeable_creditCard_form() {
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
}
|
||||
);
|
||||
await onUsed;
|
||||
|
||||
creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "Still 1 credit card in storage");
|
||||
@ -477,6 +425,11 @@ add_task(async function test_submit_manual_mergeable_creditCard_form() {
|
||||
});
|
||||
|
||||
add_task(async function test_update_autofill_form_name() {
|
||||
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
|
||||
todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
[CREDITCARDS_USED_STATUS_PREF, 0],
|
||||
@ -485,6 +438,9 @@ add_task(async function test_update_autofill_form_name() {
|
||||
await saveCreditCard(TEST_CREDIT_CARD_1);
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "1 credit card in storage");
|
||||
let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
|
||||
let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "notifyUsed");
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
@ -492,7 +448,13 @@ add_task(async function test_update_autofill_form_name() {
|
||||
await openPopupOn(browser, "form #cc-name");
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
|
||||
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
|
||||
await osKeyStoreLoginShown;
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
await ContentTaskUtils.waitForCondition(() => {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
return name.value == "John Doe";
|
||||
}, "Credit card detail never fills");
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
name.setUserInput("User 1");
|
||||
@ -501,11 +463,11 @@ add_task(async function test_update_autofill_form_name() {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
form.querySelector("input[type=submit]").click();
|
||||
});
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
}
|
||||
);
|
||||
await onUsed;
|
||||
|
||||
creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "Still 1 credit card");
|
||||
@ -517,6 +479,11 @@ add_task(async function test_update_autofill_form_name() {
|
||||
});
|
||||
|
||||
add_task(async function test_update_autofill_form_exp_date() {
|
||||
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
|
||||
todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
[CREDITCARDS_USED_STATUS_PREF, 0],
|
||||
@ -525,6 +492,9 @@ add_task(async function test_update_autofill_form_exp_date() {
|
||||
await saveCreditCard(TEST_CREDIT_CARD_1);
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "1 credit card in storage");
|
||||
let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
|
||||
let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "notifyUsed");
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
@ -533,6 +503,11 @@ add_task(async function test_update_autofill_form_exp_date() {
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
|
||||
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
await ContentTaskUtils.waitForCondition(() => {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
return name.value == "John Doe";
|
||||
}, "Credit card detail never fills");
|
||||
let form = content.document.getElementById("form");
|
||||
let year = form.querySelector("#cc-exp-year");
|
||||
year.setUserInput("2020");
|
||||
@ -544,8 +519,10 @@ add_task(async function test_update_autofill_form_exp_date() {
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
await osKeyStoreLoginShown;
|
||||
}
|
||||
);
|
||||
await onUsed;
|
||||
|
||||
creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "Still 1 credit card");
|
||||
@ -557,6 +534,11 @@ add_task(async function test_update_autofill_form_exp_date() {
|
||||
});
|
||||
|
||||
add_task(async function test_create_new_autofill_form() {
|
||||
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
|
||||
todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
[CREDITCARDS_USED_STATUS_PREF, 0],
|
||||
@ -565,14 +547,21 @@ add_task(async function test_create_new_autofill_form() {
|
||||
await saveCreditCard(TEST_CREDIT_CARD_1);
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "1 credit card in storage");
|
||||
let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
"popupshown");
|
||||
let onChanged = TestUtils.topicObserved("formautofill-storage-changed");
|
||||
await openPopupOn(browser, "form #cc-name");
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
|
||||
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
await ContentTaskUtils.waitForCondition(() => {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
return name.value == "John Doe";
|
||||
}, "Credit card detail never fills");
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
name.setUserInput("User 1");
|
||||
@ -584,6 +573,8 @@ add_task(async function test_create_new_autofill_form() {
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(SECONDARY_BUTTON);
|
||||
await osKeyStoreLoginShown;
|
||||
await onChanged;
|
||||
}
|
||||
);
|
||||
|
||||
@ -598,6 +589,11 @@ add_task(async function test_create_new_autofill_form() {
|
||||
});
|
||||
|
||||
add_task(async function test_update_duplicate_autofill_form() {
|
||||
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
|
||||
todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
[CREDITCARDS_USED_STATUS_PREF, 0],
|
||||
@ -611,16 +607,24 @@ add_task(async function test_update_duplicate_autofill_form() {
|
||||
});
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 2, "2 credit card in storage");
|
||||
let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
|
||||
let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "notifyUsed");
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
await openPopupOn(browser, "form #cc-number");
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
|
||||
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
await ContentTaskUtils.waitForCondition(() => {
|
||||
let form = content.document.getElementById("form");
|
||||
let number = form.querySelector("#cc-number");
|
||||
return number.value == "6387060366272981";
|
||||
}, "Should be the first credit card number");
|
||||
|
||||
// Change number to the second credit card number
|
||||
let form = content.document.getElementById("form");
|
||||
let number = form.querySelector("#cc-number");
|
||||
is(number.value, "6387060366272981", "Should be the first credit card number");
|
||||
// Change number to the second credit card number
|
||||
number.setUserInput("5038146897157463");
|
||||
|
||||
// Wait 1000ms before submission to make sure the input value applied
|
||||
@ -630,8 +634,10 @@ add_task(async function test_update_duplicate_autofill_form() {
|
||||
|
||||
await sleep(1000);
|
||||
is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
|
||||
await osKeyStoreLoginShown;
|
||||
}
|
||||
);
|
||||
await onUsed;
|
||||
|
||||
creditCards = await getCreditCards();
|
||||
is(creditCards.length, 2, "Still 2 credit card");
|
||||
@ -646,6 +652,7 @@ add_task(async function test_submit_creditCard_with_invalid_network() {
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
"popupshown");
|
||||
let onChanged = TestUtils.topicObserved("formautofill-storage-changed");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
@ -664,6 +671,7 @@ add_task(async function test_submit_creditCard_with_invalid_network() {
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
await onChanged;
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -1,20 +1,20 @@
|
||||
"use strict";
|
||||
|
||||
add_task(async function test_fill_creditCard_with_mp_enabled_but_canceled() {
|
||||
add_task(async function test_fill_creditCard_but_cancel_login() {
|
||||
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
|
||||
todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
await saveCreditCard(TEST_CREDIT_CARD_2);
|
||||
|
||||
LoginTestUtils.masterPassword.enable();
|
||||
registerCleanupFunction(() => {
|
||||
LoginTestUtils.masterPassword.disable();
|
||||
});
|
||||
|
||||
let masterPasswordDialogShown = waitForMasterPasswordDialog(false); // cancel
|
||||
let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(false); // cancel
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
await openPopupOn(browser, "#cc-name");
|
||||
const ccItem = getDisplayedPopupItems(browser)[0];
|
||||
await EventUtils.synthesizeMouseAtCenter(ccItem, {});
|
||||
await Promise.all([masterPasswordDialogShown, expectPopupClose(browser)]);
|
||||
await Promise.all([osKeyStoreLoginShown, expectPopupClose(browser)]);
|
||||
|
||||
await ContentTask.spawn(browser, {}, async function() {
|
||||
is(content.document.querySelector("#cc-name").value, "", "Check name");
|
@ -3,7 +3,6 @@
|
||||
const TEST_SELECTORS = {
|
||||
selRecords: "#credit-cards",
|
||||
btnRemove: "#remove",
|
||||
btnShowHideCreditCards: "#show-hide-credit-cards",
|
||||
btnAdd: "#add",
|
||||
btnEdit: "#edit",
|
||||
};
|
||||
@ -15,13 +14,11 @@ add_task(async function test_manageCreditCardsInitialState() {
|
||||
await ContentTask.spawn(browser, TEST_SELECTORS, (args) => {
|
||||
let selRecords = content.document.querySelector(args.selRecords);
|
||||
let btnRemove = content.document.querySelector(args.btnRemove);
|
||||
let btnShowHideCreditCards = content.document.querySelector(args.btnShowHideCreditCards);
|
||||
let btnAdd = content.document.querySelector(args.btnAdd);
|
||||
let btnEdit = content.document.querySelector(args.btnEdit);
|
||||
|
||||
is(selRecords.length, 0, "No credit card");
|
||||
is(btnRemove.disabled, true, "Remove button disabled");
|
||||
is(btnShowHideCreditCards.disabled, true, "Show Credit Cards button disabled");
|
||||
is(btnAdd.disabled, false, "Add button enabled");
|
||||
is(btnEdit.disabled, true, "Edit button disabled");
|
||||
});
|
||||
@ -107,57 +104,6 @@ add_task(async function test_creditCardsDialogWatchesStorageChanges() {
|
||||
win.close();
|
||||
});
|
||||
|
||||
add_task(async function test_showCreditCards() {
|
||||
await SpecialPowers.pushPrefEnv({"set": [["privacy.reduceTimerPrecision", false]]});
|
||||
await saveCreditCard(TEST_CREDIT_CARD_1);
|
||||
await saveCreditCard(TEST_CREDIT_CARD_2);
|
||||
await saveCreditCard(TEST_CREDIT_CARD_3);
|
||||
|
||||
let win = window.openDialog(MANAGE_CREDIT_CARDS_DIALOG_URL, null, DIALOG_SIZE);
|
||||
await waitForFocusAndFormReady(win);
|
||||
|
||||
let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
|
||||
let btnShowHideCreditCards = win.document.querySelector(TEST_SELECTORS.btnShowHideCreditCards);
|
||||
|
||||
is(btnShowHideCreditCards.disabled, false, "Show credit cards button enabled");
|
||||
is(btnShowHideCreditCards.textContent, "Show Credit Cards", "Label should be 'Show Credit Cards'");
|
||||
|
||||
// Show credit card numbers
|
||||
EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
|
||||
await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
|
||||
is(selRecords[0].text, "5103059495477870", "Decrypted credit card 3");
|
||||
is(selRecords[1].text, "4929001587121045, Timothy Berners-Lee", "Decrypted credit card 2");
|
||||
is(selRecords[2].text, "4111111111111111, John Doe", "Decrypted credit card 1");
|
||||
is(btnShowHideCreditCards.textContent, "Hide Credit Cards", "Label should be 'Hide Credit Cards'");
|
||||
|
||||
// Hide credit card numbers
|
||||
EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
|
||||
await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
|
||||
is(selRecords[0].text, "**** 7870", "Masked credit card 3");
|
||||
is(selRecords[1].text, "**** 1045, Timothy Berners-Lee", "Masked credit card 2");
|
||||
is(selRecords[2].text, "**** 1111, John Doe", "Masked credit card 1");
|
||||
is(btnShowHideCreditCards.textContent, "Show Credit Cards", "Label should be 'Show Credit Cards'");
|
||||
|
||||
// Show credit card numbers again to test if they revert back to masked form when reloaded
|
||||
EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
|
||||
await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
|
||||
// Ensure credit card numbers are shown again
|
||||
is(selRecords[0].text, "5103059495477870", "Decrypted credit card 3");
|
||||
// Remove a card to trigger reloading
|
||||
await removeCreditCards([selRecords.options[2].value]);
|
||||
await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
|
||||
is(selRecords[0].text, "**** 7870", "Masked credit card 3");
|
||||
is(selRecords[1].text, "**** 1045, Timothy Berners-Lee", "Masked credit card 2");
|
||||
|
||||
// Remove the rest of the cards
|
||||
await removeCreditCards([selRecords.options[1].value]);
|
||||
await removeCreditCards([selRecords.options[0].value]);
|
||||
await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
|
||||
is(btnShowHideCreditCards.disabled, true, "Show credit cards button is disabled when there is no card");
|
||||
|
||||
win.close();
|
||||
});
|
||||
|
||||
add_task(async function test_showCreditCardIcons() {
|
||||
await SpecialPowers.pushPrefEnv({"set": [["privacy.reduceTimerPrecision", false]]});
|
||||
await saveCreditCard(TEST_CREDIT_CARD_1);
|
||||
@ -193,35 +139,41 @@ add_task(async function test_showCreditCardIcons() {
|
||||
win.close();
|
||||
});
|
||||
|
||||
add_task(async function test_hasEditLoginPrompt() {
|
||||
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
|
||||
todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
add_task(async function test_hasMasterPassword() {
|
||||
await saveCreditCard(TEST_CREDIT_CARD_1);
|
||||
LoginTestUtils.masterPassword.enable();
|
||||
|
||||
let win = window.openDialog(MANAGE_CREDIT_CARDS_DIALOG_URL, null, DIALOG_SIZE);
|
||||
await waitForFocusAndFormReady(win);
|
||||
|
||||
let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
|
||||
let btnRemove = win.document.querySelector(TEST_SELECTORS.btnRemove);
|
||||
let btnShowHideCreditCards = win.document.querySelector(TEST_SELECTORS.btnShowHideCreditCards);
|
||||
let btnAdd = win.document.querySelector(TEST_SELECTORS.btnAdd);
|
||||
let btnEdit = win.document.querySelector(TEST_SELECTORS.btnEdit);
|
||||
let masterPasswordDialogShown = waitForMasterPasswordDialog();
|
||||
// let btnEdit = win.document.querySelector(TEST_SELECTORS.btnEdit);
|
||||
|
||||
is(btnShowHideCreditCards.hidden, true, "Show credit cards button is hidden");
|
||||
|
||||
// Master password dialog should show when trying to edit a credit card record.
|
||||
EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
|
||||
EventUtils.synthesizeMouseAtCenter(btnEdit, {}, win);
|
||||
await masterPasswordDialogShown;
|
||||
|
||||
// Master password is not required for removing credit cards.
|
||||
// Login dialog should show when trying to edit a credit card record.
|
||||
// TODO: test disabled because re-auth is not implemented yet (bug 1429265).
|
||||
/*
|
||||
let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(); // cancel
|
||||
EventUtils.synthesizeMouseAtCenter(btnEdit, {}, win);
|
||||
await osKeyStoreLoginShown;
|
||||
await new Promise(resolve => waitForFocus(resolve, win));
|
||||
await new Promise(resolve => executeSoon(resolve));
|
||||
*/
|
||||
|
||||
// Login is not required for removing credit cards.
|
||||
EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);
|
||||
await BrowserTestUtils.waitForEvent(selRecords, "RecordsRemoved");
|
||||
is(selRecords.length, 0, "Credit card is removed");
|
||||
|
||||
// gSubDialog.open should be called when trying to add a credit card,
|
||||
// no master password is required.
|
||||
// no OS login dialog is required.
|
||||
window.gSubDialog = {
|
||||
open: url => is(url, EDIT_CREDIT_CARD_DIALOG_URL, "Edit credit card dialog is called"),
|
||||
};
|
||||
|
@ -8,13 +8,13 @@
|
||||
DEFAULT_REGION_PREF,
|
||||
sleep, expectPopupOpen, openPopupOn, expectPopupClose, closePopup, clickDoorhangerButton,
|
||||
getAddresses, saveAddress, removeAddresses, saveCreditCard,
|
||||
getDisplayedPopupItems, getDoorhangerCheckbox, waitForMasterPasswordDialog,
|
||||
getDisplayedPopupItems, getDoorhangerCheckbox,
|
||||
getNotification, getDoorhangerButton, removeAllRecords, testDialog */
|
||||
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.import("resource://testing-common/LoginTestUtils.jsm", this);
|
||||
ChromeUtils.import("resource://formautofill/MasterPassword.jsm", this);
|
||||
ChromeUtils.import("resource://formautofill/OSKeyStore.jsm", this);
|
||||
ChromeUtils.import("resource://testing-common/OSKeyStoreTestUtils.jsm", this);
|
||||
|
||||
const MANAGE_ADDRESSES_DIALOG_URL = "chrome://formautofill/content/manageAddresses.xhtml";
|
||||
const MANAGE_CREDIT_CARDS_DIALOG_URL = "chrome://formautofill/content/manageCreditCards.xhtml";
|
||||
@ -22,8 +22,7 @@ const EDIT_ADDRESS_DIALOG_URL = "chrome://formautofill/content/editAddress.xhtml
|
||||
const EDIT_CREDIT_CARD_DIALOG_URL = "chrome://formautofill/content/editCreditCard.xhtml";
|
||||
const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/";
|
||||
const FORM_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_basic.html";
|
||||
const CREDITCARD_FORM_URL =
|
||||
"https://example.org/browser/browser/extensions/formautofill/test/browser/autocomplete_creditcard_basic.html";
|
||||
const CREDITCARD_FORM_URL = "https://example.org/browser/browser/extensions/formautofill/test/browser/autocomplete_creditcard_basic.html";
|
||||
const FTU_PREF = "extensions.formautofill.firstTimeUse";
|
||||
const CREDITCARDS_USED_STATUS_PREF = "extensions.formautofill.creditCards.used";
|
||||
const ENABLED_AUTOFILL_ADDRESSES_PREF = "extensions.formautofill.addresses.enabled";
|
||||
@ -326,24 +325,6 @@ function getDoorhangerButton(button) {
|
||||
return getNotification()[button];
|
||||
}
|
||||
|
||||
|
||||
// Wait for the master password dialog to popup and enter the password to log in
|
||||
// if "login" is "true" or dismiss it directly if otherwise.
|
||||
function waitForMasterPasswordDialog(login = false) {
|
||||
info("expecting master password dialog loaded");
|
||||
let dialogShown = TestUtils.topicObserved("common-dialog-loaded");
|
||||
return dialogShown.then(([subject]) => {
|
||||
let dialog = subject.Dialog;
|
||||
is(dialog.args.title, "Password Required", "Master password dialog shown");
|
||||
if (login) {
|
||||
dialog.ui.password1Textbox.value = LoginTestUtils.masterPassword.masterPassword;
|
||||
dialog.ui.button0.click();
|
||||
} else {
|
||||
dialog.ui.button1.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function removeAllRecords() {
|
||||
let addresses = await getAddresses();
|
||||
if (addresses.length) {
|
||||
@ -366,7 +347,7 @@ async function waitForFocusAndFormReady(win) {
|
||||
async function testDialog(url, testFn, arg = undefined) {
|
||||
if (url == EDIT_CREDIT_CARD_DIALOG_URL && arg && arg.record) {
|
||||
arg.record = Object.assign({}, arg.record, {
|
||||
"cc-number": await MasterPassword.decrypt(arg.record["cc-number-encrypted"]),
|
||||
"cc-number": await OSKeyStore.decrypt(arg.record["cc-number-encrypted"]),
|
||||
});
|
||||
}
|
||||
let win = window.openDialog(url, null, "width=600,height=600", arg);
|
||||
@ -376,4 +357,11 @@ async function testDialog(url, testFn, arg = undefined) {
|
||||
return unloadPromise;
|
||||
}
|
||||
|
||||
add_task(function setup() {
|
||||
OSKeyStoreTestUtils.setup();
|
||||
});
|
||||
|
||||
registerCleanupFunction(removeAllRecords);
|
||||
registerCleanupFunction(async () => {
|
||||
await OSKeyStoreTestUtils.cleanup();
|
||||
});
|
||||
|
93
browser/extensions/formautofill/test/fixtures/OSKeyStoreTestUtils.jsm
vendored
Normal file
93
browser/extensions/formautofill/test/fixtures/OSKeyStoreTestUtils.jsm
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = [
|
||||
"OSKeyStoreTestUtils",
|
||||
];
|
||||
|
||||
ChromeUtils.import("resource://formautofill/OSKeyStore.jsm", this);
|
||||
// TODO: Consider AppConstants.MOZILLA_OFFICIAL to decide if we could test re-auth (bug 1429265).
|
||||
/*
|
||||
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
*/
|
||||
ChromeUtils.import("resource://testing-common/LoginTestUtils.jsm", this);
|
||||
ChromeUtils.import("resource://testing-common/TestUtils.jsm");
|
||||
|
||||
var OSKeyStoreTestUtils = {
|
||||
/*
|
||||
TEST_ONLY_REAUTH: "extensions.formautofill.osKeyStore.unofficialBuildOnlyLogin",
|
||||
*/
|
||||
|
||||
setup() {
|
||||
// TODO: run tests with master password enabled to ensure NSS-implemented
|
||||
// key store prompts on re-auth (bug 1429265)
|
||||
/*
|
||||
LoginTestUtils.masterPassword.enable();
|
||||
*/
|
||||
|
||||
this.ORIGINAL_STORE_LABEL = OSKeyStore.STORE_LABEL;
|
||||
OSKeyStore.STORE_LABEL = "test-" + Math.random().toString(36).substr(2);
|
||||
},
|
||||
|
||||
async cleanup() {
|
||||
// TODO: run tests with master password enabled to ensure NSS-implemented
|
||||
// key store prompts on re-auth (bug 1429265)
|
||||
/*
|
||||
LoginTestUtils.masterPassword.disable();
|
||||
*/
|
||||
|
||||
await OSKeyStore.cleanup();
|
||||
OSKeyStore.STORE_LABEL = this.ORIGINAL_STORE_LABEL;
|
||||
},
|
||||
|
||||
canTestOSKeyStoreLogin() {
|
||||
// TODO: return true based on whether or not we could test the prompt on
|
||||
// the platform (bug 1429265).
|
||||
/*
|
||||
return OSKeyStore.isNSSKeyStore || !AppConstants.MOZILLA_OFFICIAL;
|
||||
*/
|
||||
return true;
|
||||
},
|
||||
|
||||
// Wait for the master password dialog to popup and enter the password to log in
|
||||
// if "login" is "true" or dismiss it directly if otherwise.
|
||||
async waitForOSKeyStoreLogin(login = false) {
|
||||
// TODO: Always resolves for now, because we are skipping re-auth on all
|
||||
// platforms (bug 1429265).
|
||||
/*
|
||||
if (OSKeyStore.isNSSKeyStore) {
|
||||
await this.waitForMasterPasswordDialog(login);
|
||||
return;
|
||||
}
|
||||
|
||||
const str = login ? "pass" : "cancel";
|
||||
|
||||
Services.prefs.setStringPref(this.TEST_ONLY_REAUTH, str);
|
||||
|
||||
await TestUtils.topicObserved("oskeystore-testonly-reauth",
|
||||
(subject, data) => data == str);
|
||||
|
||||
Services.prefs.setStringPref(this.TEST_ONLY_REAUTH, "");
|
||||
*/
|
||||
},
|
||||
|
||||
async waitForMasterPasswordDialog(login = false) {
|
||||
let [subject] = await TestUtils.topicObserved("common-dialog-loaded");
|
||||
|
||||
let dialog = subject.Dialog;
|
||||
if (dialog.args.title !== "Password Required") {
|
||||
throw new Error("Incorrect master password dialog title");
|
||||
}
|
||||
|
||||
if (login) {
|
||||
dialog.ui.password1Textbox.value = LoginTestUtils.masterPassword.masterPassword;
|
||||
dialog.ui.button0.click();
|
||||
} else {
|
||||
dialog.ui.button1.click();
|
||||
}
|
||||
await TestUtils.waitForTick();
|
||||
},
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/SimpleTest.js */
|
||||
/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/EventUtils.js */
|
||||
/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/AddTask.js */
|
||||
/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
@ -201,6 +202,15 @@ async function cleanUpStorage() {
|
||||
await cleanUpCreditCards();
|
||||
}
|
||||
|
||||
async function canTestOSKeyStoreLogin() {
|
||||
let {canTest} = await invokeAsyncChromeTask("FormAutofillTest:CanTestOSKeyStoreLogin", "FormAutofillTest:CanTestOSKeyStoreLoginResult");
|
||||
return canTest;
|
||||
}
|
||||
|
||||
async function waitForOSKeyStoreLogin(login = false) {
|
||||
await invokeAsyncChromeTask("FormAutofillTest:OSKeyStoreLogin", "FormAutofillTest:OSKeyStoreLoggedIn", {login});
|
||||
}
|
||||
|
||||
function patchRecordCCNumber(record) {
|
||||
const number = record["cc-number"];
|
||||
const ccNumberFmt = {
|
||||
@ -263,6 +273,12 @@ function formAutoFillCommonSetup() {
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function setup() {
|
||||
formFillChromeScript.sendAsyncMessage("setup");
|
||||
info(`expecting the storage setup`);
|
||||
await formFillChromeScript.promiseOneMessage("setup-finished");
|
||||
});
|
||||
|
||||
SimpleTest.registerCleanupFunction(async () => {
|
||||
formFillChromeScript.sendAsyncMessage("cleanup");
|
||||
info(`expecting the storage cleanup`);
|
||||
|
@ -4,8 +4,11 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
|
||||
ChromeUtils.import("resource://formautofill/OSKeyStore.jsm");
|
||||
ChromeUtils.import("resource://testing-common/OSKeyStoreTestUtils.jsm");
|
||||
|
||||
let {formAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
|
||||
|
||||
@ -114,9 +117,15 @@ var ParentUtils = {
|
||||
await this.operateCreditCard("remove", {guids}, "FormAutofillTest:CreditCardsCleanedUp");
|
||||
},
|
||||
|
||||
setup() {
|
||||
OSKeyStoreTestUtils.setup();
|
||||
},
|
||||
|
||||
async cleanup() {
|
||||
await this.cleanUpAddresses();
|
||||
await this.cleanUpCreditCards();
|
||||
await OSKeyStoreTestUtils.cleanup();
|
||||
|
||||
Services.obs.removeObserver(this, "formautofill-storage-changed");
|
||||
},
|
||||
|
||||
@ -215,8 +224,23 @@ addMessageListener("FormAutofillTest:CleanUpCreditCards", (msg) => {
|
||||
ParentUtils.cleanUpCreditCards();
|
||||
});
|
||||
|
||||
addMessageListener("cleanup", () => {
|
||||
ParentUtils.cleanup().then(() => {
|
||||
sendAsyncMessage("cleanup-finished", {});
|
||||
});
|
||||
addMessageListener("FormAutofillTest:CanTestOSKeyStoreLogin", (msg) => {
|
||||
sendAsyncMessage("FormAutofillTest:CanTestOSKeyStoreLoginResult",
|
||||
{canTest: OSKeyStore.isNSSKeyStore || !AppConstants.MOZILLA_OFFICIAL});
|
||||
});
|
||||
|
||||
addMessageListener("FormAutofillTest:OSKeyStoreLogin", async (msg) => {
|
||||
await OSKeyStoreTestUtils.waitForOSKeyStoreLogin(msg.login);
|
||||
sendAsyncMessage("FormAutofillTest:OSKeyStoreLoggedIn");
|
||||
});
|
||||
|
||||
addMessageListener("setup", async () => {
|
||||
ParentUtils.setup();
|
||||
sendAsyncMessage("setup-finished", {});
|
||||
});
|
||||
|
||||
addMessageListener("cleanup", async () => {
|
||||
await ParentUtils.cleanup();
|
||||
|
||||
sendAsyncMessage("cleanup-finished", {});
|
||||
});
|
||||
|
@ -150,8 +150,16 @@ add_task(async function check_search_result_for_pref_off() {
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
||||
let canTest;
|
||||
|
||||
// Autofill the credit card from dropdown menu.
|
||||
add_task(async function check_fields_after_form_autofill() {
|
||||
canTest = await canTestOSKeyStoreLogin();
|
||||
if (!canTest) {
|
||||
todo(canTest, "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
await setInput("#cc-exp-year", 202);
|
||||
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
@ -162,11 +170,18 @@ add_task(async function check_fields_after_form_autofill() {
|
||||
})));
|
||||
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
let osKeyStoreLoginShown = waitForOSKeyStoreLogin(true);
|
||||
await new Promise(resolve => SimpleTest.executeSoon(resolve));
|
||||
await triggerAutofillAndCheckProfile(MOCK_STORAGE[1]);
|
||||
await osKeyStoreLoginShown;
|
||||
});
|
||||
|
||||
// Fallback to history search after autofill address.
|
||||
add_task(async function check_fallback_after_form_autofill() {
|
||||
if (!canTest) {
|
||||
return;
|
||||
}
|
||||
|
||||
await setInput("#cc-name", "", true);
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
await expectPopup();
|
||||
@ -175,6 +190,10 @@ add_task(async function check_fallback_after_form_autofill() {
|
||||
|
||||
// Resume form autofill once all the autofilled fileds are changed.
|
||||
add_task(async function check_form_autofill_resume() {
|
||||
if (!canTest) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.querySelector("#cc-name").blur();
|
||||
document.querySelector("#form1").reset();
|
||||
|
||||
|
@ -103,9 +103,16 @@ add_task(async function clear_modified_form() {
|
||||
});
|
||||
|
||||
add_task(async function clear_distinct_section() {
|
||||
if (!(await canTestOSKeyStoreLogin())) {
|
||||
todo(false, "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById("form1").reset();
|
||||
await triggerPopupAndHoverItem("#cc-name", 0);
|
||||
let osKeyStoreLoginShown = waitForOSKeyStoreLogin(true);
|
||||
await triggerAutofillAndCheckProfile(MOCK_CC_STORAGE[0]);
|
||||
await osKeyStoreLoginShown;
|
||||
|
||||
await triggerPopupAndHoverItem("#organization", 0);
|
||||
await triggerAutofillAndCheckProfile(MOCK_ADDR_STORAGE[0]);
|
||||
|
@ -99,7 +99,7 @@ async function initProfileStorage(fileName, records, collectionName = "addresses
|
||||
subject.wrappedJSObject.collectionName == collectionName
|
||||
);
|
||||
for (let record of records) {
|
||||
Assert.ok(profileStorage[collectionName].add(record));
|
||||
Assert.ok(await profileStorage[collectionName].add(record));
|
||||
await onChanged;
|
||||
}
|
||||
await profileStorage._saveImmediately();
|
||||
@ -223,3 +223,13 @@ add_task(async function head_initialize() {
|
||||
|
||||
await loadExtension();
|
||||
});
|
||||
|
||||
let OSKeyStoreTestUtils;
|
||||
add_task(async function os_key_store_setup() {
|
||||
({OSKeyStoreTestUtils} =
|
||||
ChromeUtils.import("resource://testing-common/OSKeyStoreTestUtils.jsm", {}));
|
||||
OSKeyStoreTestUtils.setup();
|
||||
registerCleanupFunction(async function cleanup() {
|
||||
await OSKeyStoreTestUtils.cleanup();
|
||||
});
|
||||
});
|
||||
|
@ -5,10 +5,9 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
let MasterPassword;
|
||||
add_task(async function setup() {
|
||||
ChromeUtils.import("resource://formautofill/FormAutofillHandler.jsm");
|
||||
({MasterPassword} = ChromeUtils.import("resource://formautofill/MasterPassword.jsm", {}));
|
||||
ChromeUtils.import("resource://formautofill/OSKeyStore.jsm");
|
||||
});
|
||||
|
||||
const TESTCASES = [
|
||||
@ -477,7 +476,7 @@ function do_test(testcases, testFn) {
|
||||
info("Starting testcase: " + testcase.description);
|
||||
let ccNumber = testcase.profileData["cc-number"];
|
||||
if (ccNumber) {
|
||||
testcase.profileData["cc-number-encrypted"] = await MasterPassword.encrypt(ccNumber);
|
||||
testcase.profileData["cc-number-encrypted"] = await OSKeyStore.encrypt(ccNumber);
|
||||
delete testcase.profileData["cc-number"];
|
||||
}
|
||||
|
||||
@ -487,18 +486,11 @@ function do_test(testcases, testFn) {
|
||||
let formLike = FormLikeFactory.createFromForm(form);
|
||||
let handler = new FormAutofillHandler(formLike);
|
||||
let promises = [];
|
||||
// Replace the interal decrypt method with MasterPassword API
|
||||
// Replace the internal decrypt method with OSKeyStore API,
|
||||
// but don't pass the reauth parameter to avoid triggering
|
||||
// reauth login dialog in these tests.
|
||||
let decryptHelper = async (cipherText, reauth) => {
|
||||
let string;
|
||||
try {
|
||||
string = await MasterPassword.decrypt(cipherText, reauth);
|
||||
} catch (e) {
|
||||
if (e.result != Cr.NS_ERROR_ABORT) {
|
||||
throw e;
|
||||
}
|
||||
info("User canceled master password entry");
|
||||
}
|
||||
return string;
|
||||
return OSKeyStore.decrypt(cipherText, false);
|
||||
};
|
||||
|
||||
handler.collectFormFields();
|
||||
|
@ -316,7 +316,7 @@ add_task(async function test_add() {
|
||||
do_check_credit_card_matches(creditCards[1], TEST_CREDIT_CARD_2);
|
||||
|
||||
Assert.notEqual(creditCards[0].guid, undefined);
|
||||
Assert.equal(creditCards[0].version, 1);
|
||||
Assert.equal(creditCards[0].version, 2);
|
||||
Assert.notEqual(creditCards[0].timeCreated, undefined);
|
||||
Assert.equal(creditCards[0].timeLastModified, creditCards[0].timeCreated);
|
||||
Assert.equal(creditCards[0].timeLastUsed, 0);
|
||||
@ -422,6 +422,14 @@ add_task(async function test_update() {
|
||||
Assert.equal(creditCard["cc-number"],
|
||||
CreditCard.getLongMaskedNumber(TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD["cc-number"]));
|
||||
|
||||
// Decryption failure of existing record should not prevent it from being updated.
|
||||
creditCard = profileStorage.creditCards._data[0];
|
||||
creditCard["cc-number-encrypted"] = "INVALID";
|
||||
await profileStorage.creditCards.update(profileStorage.creditCards._data[0].guid, TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD, false);
|
||||
creditCard = profileStorage.creditCards._data[0];
|
||||
Assert.equal(creditCard["cc-number"],
|
||||
CreditCard.getLongMaskedNumber(TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD["cc-number"]));
|
||||
|
||||
await Assert.rejects(profileStorage.creditCards.update("INVALID_GUID", TEST_CREDIT_CARD_3),
|
||||
/No matching record\./
|
||||
);
|
||||
@ -655,18 +663,12 @@ add_task(async function test_getDuplicateGuid() {
|
||||
// This number differs from TEST_CREDIT_CARD_3 by swapping the order of the
|
||||
// 09 and 90 adjacent digits, which is still a valid credit card number.
|
||||
record["cc-number"] = "358999378390" + last4Digits;
|
||||
Assert.equal(await profileStorage.creditCards.getDuplicateGuid(record), null);
|
||||
|
||||
// ... However, we treat numbers with the same last 4 digits as a duplicate if
|
||||
// the master password is enabled.
|
||||
let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"].createInstance(Ci.nsIPK11TokenDB);
|
||||
let token = tokendb.getInternalKeyToken();
|
||||
token.reset();
|
||||
token.initPassword("password");
|
||||
// We treat numbers with the same last 4 digits as a duplicate.
|
||||
Assert.equal(await profileStorage.creditCards.getDuplicateGuid(record), guid);
|
||||
|
||||
// ... Even though the master password is enabled and the last 4 digits are the
|
||||
// same, an invalid credit card number should never be treated as a duplicate.
|
||||
// Even though the last 4 digits are the same, an invalid credit card number
|
||||
// should never be treated as a duplicate.
|
||||
record["cc-number"] = "************" + last4Digits;
|
||||
Assert.equal(await profileStorage.creditCards.getDuplicateGuid(record), null);
|
||||
});
|
||||
|
@ -7,9 +7,10 @@
|
||||
ChromeUtils.import("resource://gre/modules/CreditCard.jsm");
|
||||
|
||||
let FormAutofillParent;
|
||||
let OSKeyStore;
|
||||
add_task(async function setup() {
|
||||
({FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.jsm", {}));
|
||||
ChromeUtils.import("resource://formautofill/MasterPassword.jsm");
|
||||
({OSKeyStore} = ChromeUtils.import("resource://formautofill/OSKeyStore.jsm", {}));
|
||||
});
|
||||
|
||||
const TEST_ADDRESS_1 = {
|
||||
@ -176,37 +177,24 @@ add_task(async function test_getRecords_creditCards() {
|
||||
let encryptedCCRecords = await Promise.all([TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2].map(async record => {
|
||||
let clonedRecord = Object.assign({}, record);
|
||||
clonedRecord["cc-number"] = CreditCard.getLongMaskedNumber(record["cc-number"]);
|
||||
clonedRecord["cc-number-encrypted"] = await MasterPassword.encrypt(record["cc-number"]);
|
||||
clonedRecord["cc-number-encrypted"] = await OSKeyStore.encrypt(record["cc-number"]);
|
||||
return clonedRecord;
|
||||
}));
|
||||
sinon.stub(collection, "getAll", () =>
|
||||
Promise.resolve([Object.assign({}, encryptedCCRecords[0]), Object.assign({}, encryptedCCRecords[1])]));
|
||||
let CreditCardsWithDecryptedNumber = [
|
||||
Object.assign({}, encryptedCCRecords[0], {"cc-number-decrypted": TEST_CREDIT_CARD_1["cc-number"]}),
|
||||
Object.assign({}, encryptedCCRecords[1], {"cc-number-decrypted": TEST_CREDIT_CARD_2["cc-number"]}),
|
||||
];
|
||||
|
||||
let testCases = [
|
||||
{
|
||||
description: "If the search string could match 1 creditCard (without masterpassword)",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-name"},
|
||||
searchString: "John Doe",
|
||||
},
|
||||
expectedResult: CreditCardsWithDecryptedNumber.slice(0, 1),
|
||||
},
|
||||
{
|
||||
description: "If the search string could match multiple creditCards (without masterpassword)",
|
||||
description: "If the search string could match multiple creditCards",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-name"},
|
||||
searchString: "John",
|
||||
},
|
||||
expectedResult: CreditCardsWithDecryptedNumber,
|
||||
expectedResult: encryptedCCRecords,
|
||||
},
|
||||
{
|
||||
description: "If the search string could not match any creditCard (without masterpassword)",
|
||||
description: "If the search string could not match any creditCard",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-name"},
|
||||
@ -215,25 +203,17 @@ add_task(async function test_getRecords_creditCards() {
|
||||
expectedResult: [],
|
||||
},
|
||||
{
|
||||
description: "If the search number string could match 1 creditCard (without masterpassword)",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-number"},
|
||||
searchString: "411",
|
||||
},
|
||||
expectedResult: CreditCardsWithDecryptedNumber.slice(0, 1),
|
||||
},
|
||||
{
|
||||
description: "If the search string could match multiple creditCards (without masterpassword)",
|
||||
description: "Return all creditCards if focused field is cc number; " +
|
||||
"if the search string could match multiple creditCards",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-number"},
|
||||
searchString: "4",
|
||||
},
|
||||
expectedResult: CreditCardsWithDecryptedNumber,
|
||||
expectedResult: encryptedCCRecords,
|
||||
},
|
||||
{
|
||||
description: "If the search string could match 1 creditCard (with masterpassword)",
|
||||
description: "If the search string could match 1 creditCard",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-name"},
|
||||
@ -243,7 +223,7 @@ add_task(async function test_getRecords_creditCards() {
|
||||
expectedResult: encryptedCCRecords.slice(0, 1),
|
||||
},
|
||||
{
|
||||
description: "Return all creditCards if focused field is cc number (with masterpassword)",
|
||||
description: "Return all creditCards if focused field is cc number",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-number"},
|
||||
|
@ -1,119 +0,0 @@
|
||||
/**
|
||||
* Tests of MasterPassword.jsm
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
const {MockRegistrar} =
|
||||
ChromeUtils.import("resource://testing-common/MockRegistrar.jsm", {});
|
||||
|
||||
let MasterPassword;
|
||||
add_task(async function setup() {
|
||||
({MasterPassword} = ChromeUtils.import("resource://formautofill/MasterPassword.jsm", {}));
|
||||
});
|
||||
|
||||
const TESTCASES = [{
|
||||
description: "With master password set",
|
||||
masterPassword: "fakemp",
|
||||
mpEnabled: true,
|
||||
},
|
||||
{
|
||||
description: "Without master password set",
|
||||
masterPassword: "", // "" means no master password
|
||||
mpEnabled: false,
|
||||
}];
|
||||
|
||||
|
||||
// Tests that PSM can successfully ask for a password from the user and relay it
|
||||
// back to NSS. Does so by mocking out the actual dialog and "filling in" the
|
||||
// password. Also tests that providing an incorrect password will fail (well,
|
||||
// technically the user will just get prompted again, but if they then cancel
|
||||
// the dialog the overall operation will fail).
|
||||
|
||||
let gMockPrompter = {
|
||||
passwordToTry: null,
|
||||
numPrompts: 0,
|
||||
|
||||
// This intentionally does not use arrow function syntax to avoid an issue
|
||||
// where in the context of the arrow function, |this != gMockPrompter| due to
|
||||
// how objects get wrapped when going across xpcom boundaries.
|
||||
promptPassword(dialogTitle, text, password, checkMsg, checkValue) {
|
||||
this.numPrompts++;
|
||||
if (this.numPrompts > 1) { // don't keep retrying a bad password
|
||||
return false;
|
||||
}
|
||||
equal(text,
|
||||
"Please enter your master password.",
|
||||
"password prompt text should be as expected");
|
||||
equal(checkMsg, null, "checkMsg should be null");
|
||||
ok(this.passwordToTry, "passwordToTry should be non-null");
|
||||
password.value = this.passwordToTry;
|
||||
return true;
|
||||
},
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIPrompt]),
|
||||
};
|
||||
|
||||
// Mock nsIWindowWatcher. PSM calls getNewPrompter on this to get an nsIPrompt
|
||||
// to call promptPassword. We return the mock one, above.
|
||||
let gWindowWatcher = {
|
||||
getNewPrompter: () => gMockPrompter,
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIWindowWatcher]),
|
||||
};
|
||||
|
||||
// Ensure that the appropriate initialization has happened.
|
||||
do_get_profile();
|
||||
|
||||
let windowWatcherCID =
|
||||
MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1",
|
||||
gWindowWatcher);
|
||||
registerCleanupFunction(() => {
|
||||
MockRegistrar.unregister(windowWatcherCID);
|
||||
});
|
||||
|
||||
TESTCASES.forEach(testcase => {
|
||||
add_task(async function test_encrypt_decrypt() {
|
||||
info("Starting testcase: " + testcase.description);
|
||||
|
||||
let token = MasterPassword._token;
|
||||
token.initPassword(testcase.masterPassword);
|
||||
|
||||
// Test only: Force the token login without asking for master password
|
||||
token.login(/* force */ false);
|
||||
Assert.equal(testcase.mpEnabled, token.isLoggedIn(), "Token should now be logged into");
|
||||
Assert.equal(MasterPassword.isEnabled, testcase.mpEnabled);
|
||||
|
||||
let testText = "test string";
|
||||
let cipherText = await MasterPassword.encrypt(testText);
|
||||
Assert.notEqual(testText, cipherText);
|
||||
let plainText = await MasterPassword.decrypt(cipherText);
|
||||
Assert.equal(testText, plainText);
|
||||
if (token.isLoggedIn()) {
|
||||
// Reset state.
|
||||
gMockPrompter.numPrompts = 0;
|
||||
token.logoutSimple();
|
||||
|
||||
ok(!token.isLoggedIn(),
|
||||
"Token should be logged out after calling logoutSimple()");
|
||||
|
||||
// Try with the correct password.
|
||||
gMockPrompter.passwordToTry = testcase.masterPassword;
|
||||
await MasterPassword.encrypt(testText);
|
||||
Assert.equal(gMockPrompter.numPrompts, 1, "should have prompted for encryption");
|
||||
|
||||
// Reset state.
|
||||
gMockPrompter.numPrompts = 0;
|
||||
token.logoutSimple();
|
||||
|
||||
try {
|
||||
// Try with the incorrect password.
|
||||
gMockPrompter.passwordToTry = "XXX";
|
||||
await MasterPassword.decrypt(cipherText);
|
||||
throw new Error("Not receiving canceled master password error");
|
||||
} catch (e) {
|
||||
Assert.equal(e.message, "User canceled master password entry");
|
||||
}
|
||||
}
|
||||
|
||||
token.reset();
|
||||
});
|
||||
});
|
@ -4,16 +4,19 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
let FormAutofillStorage;
|
||||
ChromeUtils.import("resource://testing-common/LoginTestUtils.jsm", this);
|
||||
|
||||
let FormAutofillStorage;
|
||||
let OSKeyStore;
|
||||
add_task(async function setup() {
|
||||
({FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {}));
|
||||
({OSKeyStore} = ChromeUtils.import("resource://formautofill/OSKeyStore.jsm", {}));
|
||||
});
|
||||
|
||||
const TEST_STORE_FILE_NAME = "test-profile.json";
|
||||
|
||||
const ADDRESS_SCHEMA_VERSION = 1;
|
||||
const CREDIT_CARD_SCHEMA_VERSION = 1;
|
||||
const CREDIT_CARD_SCHEMA_VERSION = 2;
|
||||
|
||||
const ADDRESS_TESTCASES = [
|
||||
{
|
||||
@ -246,11 +249,12 @@ add_task(async function test_migrateAddressRecords() {
|
||||
let profileStorage = new FormAutofillStorage(path);
|
||||
await profileStorage.initialize();
|
||||
|
||||
await Promise.all(ADDRESS_TESTCASES.map(async testcase => {
|
||||
for (let testcase of ADDRESS_TESTCASES) {
|
||||
info(testcase.description);
|
||||
await profileStorage.addresses._migrateRecord(testcase.record);
|
||||
do_check_record_matches(testcase.expectedResult, testcase.record);
|
||||
}));
|
||||
profileStorage._store.data.addresses = [testcase.record];
|
||||
await profileStorage.addresses._migrateRecord(testcase.record, 0);
|
||||
do_check_record_matches(testcase.expectedResult, profileStorage.addresses._data[0]);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_migrateCreditCardRecords() {
|
||||
@ -259,9 +263,79 @@ add_task(async function test_migrateCreditCardRecords() {
|
||||
let profileStorage = new FormAutofillStorage(path);
|
||||
await profileStorage.initialize();
|
||||
|
||||
await Promise.all(CREDIT_CARD_TESTCASES.map(async testcase => {
|
||||
for (let testcase of CREDIT_CARD_TESTCASES) {
|
||||
info(testcase.description);
|
||||
await profileStorage.creditCards._migrateRecord(testcase.record);
|
||||
do_check_record_matches(testcase.expectedResult, testcase.record);
|
||||
}));
|
||||
profileStorage._store.data.creditCards = [testcase.record];
|
||||
await profileStorage.creditCards._migrateRecord(testcase.record, 0);
|
||||
do_check_record_matches(testcase.expectedResult, profileStorage.creditCards._data[0]);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_migrateEncryptedCreditCardNumber() {
|
||||
let path = getTempFile(TEST_STORE_FILE_NAME).path;
|
||||
|
||||
let profileStorage = new FormAutofillStorage(path);
|
||||
await profileStorage.initialize();
|
||||
|
||||
const ccNumber = "4111111111111111";
|
||||
let cryptoSDR = Cc["@mozilla.org/login-manager/crypto/SDR;1"]
|
||||
.createInstance(Ci.nsILoginManagerCrypto);
|
||||
|
||||
info("Encrypted credit card should be migrated from v1 to v2");
|
||||
|
||||
let record = {
|
||||
guid: "test-guid",
|
||||
version: 1,
|
||||
"cc-name": "Timothy",
|
||||
"cc-number-encrypted": cryptoSDR.encrypt(ccNumber),
|
||||
};
|
||||
|
||||
let expectedRecord = {
|
||||
guid: "test-guid",
|
||||
version: CREDIT_CARD_SCHEMA_VERSION,
|
||||
"cc-name": "Timothy",
|
||||
"cc-given-name": "Timothy",
|
||||
};
|
||||
profileStorage._store.data.creditCards = [record];
|
||||
await profileStorage.creditCards._migrateRecord(record, 0);
|
||||
record = profileStorage.creditCards._data[0];
|
||||
|
||||
Assert.equal(expectedRecord.guid, record.guid);
|
||||
Assert.equal(expectedRecord.version, record.version);
|
||||
Assert.equal(expectedRecord["cc-name"], record["cc-name"]);
|
||||
Assert.equal(expectedRecord["cc-given-name"], record["cc-given-name"]);
|
||||
|
||||
// Ciphertext of OS Key Store is not stable, must compare decrypted text here.
|
||||
Assert.equal(ccNumber, await OSKeyStore.decrypt(record["cc-number-encrypted"]));
|
||||
});
|
||||
|
||||
add_task(async function test_migrateEncryptedCreditCardNumberWithMP() {
|
||||
LoginTestUtils.masterPassword.enable();
|
||||
|
||||
let path = getTempFile(TEST_STORE_FILE_NAME).path;
|
||||
|
||||
let profileStorage = new FormAutofillStorage(path);
|
||||
await profileStorage.initialize();
|
||||
|
||||
info("Encrypted credit card should be migrated a tombstone if MP is enabled");
|
||||
|
||||
let record = {
|
||||
guid: "test-guid",
|
||||
version: 1,
|
||||
"cc-name": "Timothy",
|
||||
"cc-number-encrypted": "(encrypted to be discarded)",
|
||||
};
|
||||
|
||||
profileStorage._store.data.creditCards = [record];
|
||||
await profileStorage.creditCards._migrateRecord(record, 0);
|
||||
record = profileStorage.creditCards._data[0];
|
||||
|
||||
Assert.equal(record.guid, "test-guid");
|
||||
Assert.equal(record.deleted, true);
|
||||
Assert.equal(typeof record.version, "undefined");
|
||||
Assert.equal(typeof record["cc-name"], "undefined");
|
||||
Assert.equal(typeof record["cc-number-encrypted"], "undefined");
|
||||
|
||||
LoginTestUtils.masterPassword.disable();
|
||||
});
|
||||
|
||||
|
144
browser/extensions/formautofill/test/unit/test_osKeyStore.js
Normal file
144
browser/extensions/formautofill/test/unit/test_osKeyStore.js
Normal file
@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Tests of OSKeyStore.jsm
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
|
||||
ChromeUtils.import("resource://testing-common/MockRegistrar.jsm");
|
||||
|
||||
let OSKeyStore;
|
||||
add_task(async function setup() {
|
||||
({OSKeyStore} = ChromeUtils.import("resource://formautofill/OSKeyStore.jsm", {}));
|
||||
});
|
||||
|
||||
// Ensure that the appropriate initialization has happened.
|
||||
do_get_profile();
|
||||
|
||||
// For NSS key store, mocking out the dialog and control it from here.
|
||||
let gMockPrompter = {
|
||||
passwordToTry: "hunter2",
|
||||
resolve: null,
|
||||
login: undefined,
|
||||
|
||||
// This intentionally does not use arrow function syntax to avoid an issue
|
||||
// where in the context of the arrow function, |this != gMockPrompter| due to
|
||||
// how objects get wrapped when going across xpcom boundaries.
|
||||
promptPassword(dialogTitle, text, password, checkMsg, checkValue) {
|
||||
equal(text,
|
||||
"Please enter your master password.",
|
||||
"password prompt text should be as expected");
|
||||
equal(checkMsg, null, "checkMsg should be null");
|
||||
if (this.login) {
|
||||
password.value = this.passwordToTry;
|
||||
}
|
||||
this.resolve();
|
||||
this.resolve = null;
|
||||
|
||||
return this.login;
|
||||
},
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIPrompt]),
|
||||
};
|
||||
|
||||
// Mock nsIWindowWatcher. PSM calls getNewPrompter on this to get an nsIPrompt
|
||||
// to call promptPassword. We return the mock one, above.
|
||||
let gWindowWatcher = {
|
||||
getNewPrompter: () => gMockPrompter,
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIWindowWatcher]),
|
||||
};
|
||||
|
||||
let nssToken;
|
||||
|
||||
const TEST_ONLY_REAUTH = "extensions.formautofill.osKeyStore.unofficialBuildOnlyLogin";
|
||||
|
||||
async function waitForReauth(login = false) {
|
||||
if (OSKeyStore.isNSSKeyStore) {
|
||||
gMockPrompter.login = login;
|
||||
await new Promise(resolve => { gMockPrompter.resolve = resolve; });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let value = login ? "pass" : "cancel";
|
||||
Services.prefs.setStringPref(TEST_ONLY_REAUTH, value);
|
||||
await TestUtils.topicObserved("oskeystore-testonly-reauth",
|
||||
(subject, data) => data == value);
|
||||
}
|
||||
|
||||
const testText = "test string";
|
||||
let cipherText;
|
||||
|
||||
add_task(async function test_encrypt_decrypt() {
|
||||
Assert.equal(await OSKeyStore.ensureLoggedIn(), true, "Started logged in.");
|
||||
|
||||
cipherText = await OSKeyStore.encrypt(testText);
|
||||
Assert.notEqual(testText, cipherText);
|
||||
|
||||
let plainText = await OSKeyStore.decrypt(cipherText);
|
||||
Assert.equal(testText, plainText);
|
||||
});
|
||||
|
||||
// TODO: skipped because re-auth is not implemented (bug 1429265).
|
||||
add_task(async function test_reauth() {
|
||||
let canTest = OSKeyStore.isNSSKeyStore || !AppConstants.MOZILLA_OFFICIAL;
|
||||
if (!canTest) {
|
||||
todo_check_false(canTest,
|
||||
"test_reauth: Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (OSKeyStore.isNSSKeyStore) {
|
||||
let windowWatcherCID;
|
||||
windowWatcherCID =
|
||||
MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1",
|
||||
gWindowWatcher);
|
||||
registerCleanupFunction(() => {
|
||||
MockRegistrar.unregister(windowWatcherCID);
|
||||
});
|
||||
|
||||
// If we use the NSS key store implementation test that everything works
|
||||
// when a master password is set.
|
||||
// Set an initial password.
|
||||
let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
|
||||
.getService(Ci.nsIPK11TokenDB);
|
||||
nssToken = tokenDB.getInternalKeyToken();
|
||||
nssToken.initPassword("hunter2");
|
||||
}
|
||||
|
||||
let reauthObserved = waitForReauth(false);
|
||||
await new Promise(resolve => TestUtils.executeSoon(resolve));
|
||||
try {
|
||||
await OSKeyStore.decrypt(cipherText, true);
|
||||
throw new Error("Not receiving canceled OS unlock error");
|
||||
} catch (ex) {
|
||||
Assert.equal(ex.message, "User canceled OS unlock entry");
|
||||
Assert.equal(ex.result, Cr.NS_ERROR_ABORT);
|
||||
}
|
||||
await reauthObserved;
|
||||
|
||||
reauthObserved = waitForReauth(false);
|
||||
await new Promise(resolve => TestUtils.executeSoon(resolve));
|
||||
Assert.equal(await OSKeyStore.ensureLoggedIn(true), false, "Reauth cancelled.");
|
||||
await reauthObserved;
|
||||
|
||||
reauthObserved = waitForReauth(true);
|
||||
await new Promise(resolve => TestUtils.executeSoon(resolve));
|
||||
let plainText2 = await OSKeyStore.decrypt(cipherText, true);
|
||||
await reauthObserved;
|
||||
Assert.equal(testText, plainText2);
|
||||
|
||||
reauthObserved = waitForReauth(true);
|
||||
await new Promise(resolve => TestUtils.executeSoon(resolve));
|
||||
Assert.equal(await OSKeyStore.ensureLoggedIn(true), true, "Reauth logged in.");
|
||||
await reauthObserved;
|
||||
}).skip();
|
||||
|
||||
add_task(async function test_decryption_failure() {
|
||||
try {
|
||||
await OSKeyStore.decrypt("Malformed cipher text");
|
||||
throw new Error("Not receiving decryption error");
|
||||
} catch (ex) {
|
||||
Assert.notEqual(ex.result, Cr.NS_ERROR_ABORT);
|
||||
}
|
||||
});
|
@ -470,7 +470,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
parent: {
|
||||
// So when we last wrote the record to the server, it had these values.
|
||||
"guid": "2bbd2d8fbc6b",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -485,7 +485,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
// we can deduce the record hasn't actually been changed remotely so we
|
||||
// can safely ignore the incoming record and write our local changes.
|
||||
"guid": "2bbd2d8fbc6b",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -499,7 +499,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
description: "Remote change",
|
||||
parent: {
|
||||
"guid": "e3680e9f890d",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -509,7 +509,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "e3680e9f890d",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4929001587121045",
|
||||
},
|
||||
@ -524,7 +524,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
description: "New local field",
|
||||
parent: {
|
||||
"guid": "0cba738b1be0",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -535,7 +535,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "0cba738b1be0",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -550,7 +550,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
description: "New remote field",
|
||||
parent: {
|
||||
"guid": "be3ef97f8285",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -560,7 +560,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "be3ef97f8285",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-exp-month": 12,
|
||||
@ -576,7 +576,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
description: "Deleted field locally",
|
||||
parent: {
|
||||
"guid": "9627322248ec",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-exp-month": 12,
|
||||
@ -587,7 +587,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "9627322248ec",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-exp-month": 12,
|
||||
@ -602,7 +602,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
description: "Deleted field remotely",
|
||||
parent: {
|
||||
"guid": "7d7509f3eeb2",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-exp-month": 12,
|
||||
@ -614,7 +614,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "7d7509f3eeb2",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -629,7 +629,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
parent: {
|
||||
// The last time we wrote this to the server, "cc-exp-month" was 12.
|
||||
"guid": "e087a06dfc57",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-exp-month": 12,
|
||||
@ -643,7 +643,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
remote: {
|
||||
// Remotely, we've changed "cc-exp-month" to 1.
|
||||
"guid": "e087a06dfc57",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-exp-month": 1,
|
||||
@ -659,7 +659,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
description: "Multiple local changes",
|
||||
parent: {
|
||||
"guid": "340a078c596f",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -673,7 +673,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "340a078c596f",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-exp-year": 2000,
|
||||
@ -692,7 +692,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
description: "Same change to local and remote",
|
||||
parent: {
|
||||
"guid": "0b3a72a1bea2",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -702,7 +702,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "0b3a72a1bea2",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4929001587121045",
|
||||
},
|
||||
@ -717,7 +717,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
parent: {
|
||||
// This is what we last wrote to the sync server.
|
||||
"guid": "62068784d089",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -729,7 +729,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
remote: {
|
||||
// An incoming record has a different cc-number than any of the above!
|
||||
"guid": "62068784d089",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4929001587121045",
|
||||
},
|
||||
@ -750,7 +750,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
description: "Conflicting changes to multiple fields",
|
||||
parent: {
|
||||
"guid": "244dbb692e94",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-exp-month": 12,
|
||||
@ -762,7 +762,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "244dbb692e94",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4929001587121045",
|
||||
"cc-exp-month": 3,
|
||||
@ -783,7 +783,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
description: "Field deleted locally, changed remotely",
|
||||
parent: {
|
||||
"guid": "6fc45e03d19a",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-exp-month": 12,
|
||||
@ -794,7 +794,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "6fc45e03d19a",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-exp-month": 3,
|
||||
@ -814,7 +814,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
description: "Field changed locally, deleted remotely",
|
||||
parent: {
|
||||
"guid": "fff9fa27fa18",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-exp-month": 12,
|
||||
@ -826,7 +826,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "fff9fa27fa18",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -848,7 +848,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
description: "Created, last modified time reconciliation without local changes",
|
||||
parent: {
|
||||
"guid": "5113f329c42f",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"timeCreated": 1234,
|
||||
@ -859,7 +859,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
local: [],
|
||||
remote: {
|
||||
"guid": "5113f329c42f",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"timeCreated": 1200,
|
||||
@ -883,7 +883,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
description: "Created, last modified time reconciliation with local changes",
|
||||
parent: {
|
||||
"guid": "791e5608b80a",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"timeCreated": 1234,
|
||||
@ -897,7 +897,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "791e5608b80a",
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"timeCreated": 1300,
|
||||
@ -922,7 +922,7 @@ add_task(async function test_reconcile_unknown_version() {
|
||||
// Cross-version reconciliation isn't supported yet. See bug 1377204.
|
||||
await Assert.rejects(profileStorage.addresses.reconcile({
|
||||
"guid": "31d83d2725ec",
|
||||
"version": 2,
|
||||
"version": 3,
|
||||
"given-name": "Mark",
|
||||
"family-name": "Hammond",
|
||||
}), /Got unknown record version/);
|
||||
|
@ -38,10 +38,10 @@ support-files =
|
||||
[test_isCJKName.js]
|
||||
[test_isFieldEligibleForAutofill.js]
|
||||
[test_markAsAutofillField.js]
|
||||
[test_masterPassword.js]
|
||||
[test_migrateRecords.js]
|
||||
[test_nameUtils.js]
|
||||
[test_onFormSubmitted.js]
|
||||
[test_osKeyStore.js]
|
||||
[test_parseAddressFormat.js]
|
||||
[test_profileAutocompleteResult.js]
|
||||
[test_phoneNumber.js]
|
||||
|
@ -65,6 +65,8 @@
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "mozilla/dom/WorkerCommon.h"
|
||||
#include "mozilla/dom/WorkerPrivate.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsJSUtils.h"
|
||||
#include "nsILoadInfo.h"
|
||||
@ -492,6 +494,15 @@ nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(JSContext *cx,
|
||||
if (!csp)
|
||||
return true;
|
||||
|
||||
nsCOMPtr<nsICSPEventListener> cspEventListener;
|
||||
if (!NS_IsMainThread()) {
|
||||
WorkerPrivate* workerPrivate =
|
||||
mozilla::dom::GetWorkerPrivateFromContext(cx);
|
||||
if (workerPrivate) {
|
||||
cspEventListener = workerPrivate->CSPEventListener();
|
||||
}
|
||||
}
|
||||
|
||||
bool evalOK = true;
|
||||
bool reportViolation = false;
|
||||
rv = csp->GetAllowsEval(&reportViolation, &evalOK);
|
||||
@ -529,6 +540,7 @@ nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(JSContext *cx,
|
||||
}
|
||||
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
|
||||
nullptr, // triggering element
|
||||
cspEventListener,
|
||||
fileName,
|
||||
scriptSample,
|
||||
lineNum,
|
||||
|
@ -12378,6 +12378,7 @@ nsIDocument::InlineScriptAllowedByCSP()
|
||||
EmptyString(), // aNonce
|
||||
true, // aParserCreated
|
||||
nullptr, // aTriggeringElement
|
||||
nullptr, // aCSPEventListener
|
||||
EmptyString(), // FIXME get script sample (bug 1314567)
|
||||
0, // aLineNumber
|
||||
0, // aColumnNumber
|
||||
|
@ -1397,7 +1397,7 @@ ImageBitmap::Create(nsIGlobalObject* aGlobal, const ImageBitmapSource& aSrc,
|
||||
}
|
||||
|
||||
if (aCropRect.isSome() && (aCropRect->Width() == 0 || aCropRect->Height() == 0)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
|
||||
aRv.Throw(NS_ERROR_RANGE_ERR);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
|
@ -855,6 +855,7 @@ EventListenerManager::SetEventHandler(nsAtom* aName,
|
||||
EmptyString(), // aNonce
|
||||
true, // aParserCreated (true because attribute event handler)
|
||||
aElement,
|
||||
nullptr, // nsICSPEventListener
|
||||
aBody,
|
||||
lineNum,
|
||||
columnNum,
|
||||
|
@ -403,17 +403,20 @@ class MainThreadFetchRunnable : public Runnable
|
||||
RefPtr<WorkerFetchResolver> mResolver;
|
||||
const ClientInfo mClientInfo;
|
||||
const Maybe<ServiceWorkerDescriptor> mController;
|
||||
nsCOMPtr<nsICSPEventListener> mCSPEventListener;
|
||||
RefPtr<InternalRequest> mRequest;
|
||||
|
||||
public:
|
||||
MainThreadFetchRunnable(WorkerFetchResolver* aResolver,
|
||||
const ClientInfo& aClientInfo,
|
||||
const Maybe<ServiceWorkerDescriptor>& aController,
|
||||
nsICSPEventListener* aCSPEventListener,
|
||||
InternalRequest* aRequest)
|
||||
: Runnable("dom::MainThreadFetchRunnable")
|
||||
, mResolver(aResolver)
|
||||
, mClientInfo(aClientInfo)
|
||||
, mController(aController)
|
||||
, mCSPEventListener(aCSPEventListener)
|
||||
, mRequest(aRequest)
|
||||
{
|
||||
MOZ_ASSERT(mResolver);
|
||||
@ -454,6 +457,7 @@ public:
|
||||
|
||||
fetch->SetClientInfo(mClientInfo);
|
||||
fetch->SetController(mController);
|
||||
fetch->SetCSPEventListener(mCSPEventListener);
|
||||
}
|
||||
|
||||
RefPtr<AbortSignalImpl> signalImpl =
|
||||
@ -586,7 +590,8 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
|
||||
|
||||
RefPtr<MainThreadFetchRunnable> run =
|
||||
new MainThreadFetchRunnable(resolver, clientInfo.ref(),
|
||||
worker->GetController(), r);
|
||||
worker->GetController(),
|
||||
worker->CSPEventListener(), r);
|
||||
worker->DispatchToMainThread(run.forget());
|
||||
}
|
||||
|
||||
|
@ -580,6 +580,16 @@ FetchDriver::HttpFetch(const nsACString& aPreferredAlternativeDataType)
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (mCSPEventListener) {
|
||||
nsCOMPtr<nsILoadInfo> loadInfo = chan->GetLoadInfo();
|
||||
if (NS_WARN_IF(!loadInfo)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
rv = loadInfo->SetCspEventListener(mCSPEventListener);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// Insert ourselves into the notification callbacks chain so we can set
|
||||
// headers on redirects.
|
||||
#ifdef DEBUG
|
||||
@ -1415,6 +1425,13 @@ FetchDriver::SetDocument(nsIDocument* aDocument)
|
||||
mDocument = aDocument;
|
||||
}
|
||||
|
||||
void
|
||||
FetchDriver::SetCSPEventListener(nsICSPEventListener* aCSPEventListener)
|
||||
{
|
||||
MOZ_ASSERT(!mFetchCalled);
|
||||
mCSPEventListener = aCSPEventListener;
|
||||
}
|
||||
|
||||
void
|
||||
FetchDriver::SetClientInfo(const ClientInfo& aClientInfo)
|
||||
{
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "mozilla/net/ReferrerPolicy.h"
|
||||
|
||||
class nsIConsoleReportCollector;
|
||||
class nsICSPEventListener;
|
||||
class nsIDocument;
|
||||
class nsIEventTarget;
|
||||
class nsIOutputStream;
|
||||
@ -119,6 +120,9 @@ public:
|
||||
void
|
||||
SetDocument(nsIDocument* aDocument);
|
||||
|
||||
void
|
||||
SetCSPEventListener(nsICSPEventListener* aCSPEventListener);
|
||||
|
||||
void
|
||||
SetClientInfo(const ClientInfo& aClientInfo);
|
||||
|
||||
@ -144,6 +148,7 @@ private:
|
||||
nsCOMPtr<nsIOutputStream> mPipeOutputStream;
|
||||
RefPtr<FetchDriverObserver> mObserver;
|
||||
nsCOMPtr<nsIDocument> mDocument;
|
||||
nsCOMPtr<nsICSPEventListener> mCSPEventListener;
|
||||
Maybe<ClientInfo> mClientInfo;
|
||||
Maybe<ServiceWorkerDescriptor> mController;
|
||||
nsCOMPtr<nsIChannel> mChannel;
|
||||
|
@ -1679,7 +1679,7 @@ HTMLFormElement::GetActionURL(nsIURI** aActionURL,
|
||||
// form-action is only enforced if explicitly defined in the
|
||||
// policy - do *not* consult default-src, see:
|
||||
// http://www.w3.org/TR/CSP2/#directive-default-src
|
||||
rv = csp->Permits(this, actionURL,
|
||||
rv = csp->Permits(this, nullptr /* nsICSPEventListener */, actionURL,
|
||||
nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE,
|
||||
true, &permitsFormAction);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
@ -175,7 +175,8 @@ SetBaseURIUsingFirstBaseWithHref(nsIDocument* aDocument, nsIContent* aMustMatch)
|
||||
// policy - do *not* consult default-src, see:
|
||||
// http://www.w3.org/TR/CSP2/#directive-default-src
|
||||
bool cspPermitsBaseURI = true;
|
||||
rv = csp->Permits(child->AsElement(), newBaseURI,
|
||||
rv = csp->Permits(child->AsElement(), nullptr /* nsICSPEventListener */,
|
||||
newBaseURI,
|
||||
nsIContentSecurityPolicy::BASE_URI_DIRECTIVE,
|
||||
true, &cspPermitsBaseURI);
|
||||
if (NS_FAILED(rv) || !cspPermitsBaseURI) {
|
||||
|
@ -141,6 +141,7 @@ interface nsIContentSecurityPolicy : nsISerializable
|
||||
in AString aNonce,
|
||||
in boolean aParserCreated,
|
||||
in Element aTriggeringElement,
|
||||
in nsICSPEventListener aCSPEventListener,
|
||||
in AString aContentOfPseudoScript,
|
||||
in unsigned long aLineNumber,
|
||||
in unsigned long aColumnNumber);
|
||||
@ -199,6 +200,7 @@ interface nsIContentSecurityPolicy : nsISerializable
|
||||
*/
|
||||
void logViolationDetails(in unsigned short violationType,
|
||||
in Element triggeringElement,
|
||||
in nsICSPEventListener aCSPEventListener,
|
||||
in AString sourceFile,
|
||||
in AString scriptSample,
|
||||
in int32_t lineNum,
|
||||
@ -225,13 +227,6 @@ interface nsIContentSecurityPolicy : nsISerializable
|
||||
void setRequestContext(in Document aDocument,
|
||||
in nsIPrincipal aPrincipal);
|
||||
|
||||
/**
|
||||
* Called after the CSP object is created to fill in event listener.
|
||||
* @param aListener this listener will be notified when a CSP violation event
|
||||
* must be dispatched.
|
||||
*/
|
||||
void setEventListener(in nsICSPEventListener aListener);
|
||||
|
||||
/**
|
||||
* Ensure we have a nsIEventTarget to use to label CSPReportSenderRunnable
|
||||
*/
|
||||
@ -283,6 +278,7 @@ interface nsIContentSecurityPolicy : nsISerializable
|
||||
* directive. (block the pending operation if false).
|
||||
*/
|
||||
boolean permits(in Element aTriggeringElement,
|
||||
in nsICSPEventListener aCSPEventListener,
|
||||
in nsIURI aURI,
|
||||
in CSPDirective aDir,
|
||||
in boolean aSpecific);
|
||||
@ -300,6 +296,7 @@ interface nsIContentSecurityPolicy : nsISerializable
|
||||
* URL.
|
||||
*/
|
||||
short shouldLoad(in nsContentPolicyType aContentType,
|
||||
in nsICSPEventListener aCSPEventListener,
|
||||
in nsIURI aContentLocation,
|
||||
in nsIURI aRequestOrigin,
|
||||
in nsISupports aContext,
|
||||
|
@ -181,6 +181,7 @@ nsresult nsJSThunk::EvaluateScript(nsIChannel *aChannel,
|
||||
EmptyString(), // aNonce
|
||||
true, // aParserCreated
|
||||
nullptr, // aElement,
|
||||
nullptr, // nsICSPEventListener
|
||||
EmptyString(), // aContent
|
||||
0, // aLineNumber
|
||||
0, // aColumnNumber
|
||||
|
@ -58,8 +58,10 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
// This must match AudioIpcInitParams in media/audioipc/client/src/lib.rs.
|
||||
// TODO: Generate this from the Rust definition rather than duplicating it.
|
||||
struct AudioIpcInitParams {
|
||||
int mServerConnection;
|
||||
mozilla::ipc::FileDescriptor::PlatformHandleType mServerConnection;
|
||||
size_t mPoolSize;
|
||||
size_t mStackSize;
|
||||
void (*mThreadCreateCallback)(const char*);
|
||||
@ -411,9 +413,19 @@ ipc::FileDescriptor CreateAudioIPCConnection()
|
||||
{
|
||||
#ifdef MOZ_CUBEB_REMOTING
|
||||
MOZ_ASSERT(sServerHandle);
|
||||
int rawFD = audioipc_server_new_client(sServerHandle);
|
||||
ipc::FileDescriptor::PlatformHandleType rawFD = audioipc_server_new_client(sServerHandle);
|
||||
ipc::FileDescriptor fd(rawFD);
|
||||
if (!fd.IsValid()) {
|
||||
MOZ_LOG(gCubebLog, LogLevel::Error, ("audioipc_server_new_client failed"));
|
||||
return ipc::FileDescriptor();
|
||||
}
|
||||
// Close rawFD since FileDescriptor's ctor cloned it.
|
||||
// TODO: Find cleaner cross-platform way to close rawFD.
|
||||
#ifdef XP_WIN
|
||||
CloseHandle(rawFD);
|
||||
#else
|
||||
close(rawFD);
|
||||
#endif
|
||||
return fd;
|
||||
#else
|
||||
return ipc::FileDescriptor();
|
||||
|
@ -1778,31 +1778,34 @@ PeerConnectionWrapper.prototype = {
|
||||
* The stats to check from this PeerConnectionWrapper
|
||||
*/
|
||||
checkStats : function(stats, twoMachines) {
|
||||
// Allow for clock drift observed on Windows 7. (Bug 979649)
|
||||
const isWin7 = navigator.userAgent.includes("Windows NT 6.1");
|
||||
const clockDriftAllowance = isWin7 ? 1000 : 0;
|
||||
|
||||
// Use spec way of enumerating stats
|
||||
var counters = {};
|
||||
for (let [key, res] of stats) {
|
||||
info("Checking stats for " + key + " : " + res);
|
||||
// validate stats
|
||||
ok(res.id == key, "Coherent stats id");
|
||||
var nowish = Date.now();
|
||||
var nowish = Date.now() + clockDriftAllowance;
|
||||
var minimum = this.whenCreated;
|
||||
if (false) { // Bug 1325430 - timestamps aren't working properly in update 49
|
||||
// if (!twoMachines) {
|
||||
if (!twoMachines) {
|
||||
// Bug 1225729: On android, sometimes the first RTCP of the first
|
||||
// test run gets this value, likely because no RTP has been sent yet.
|
||||
if (res.timestamp != 2085978496000) {
|
||||
if (false) {
|
||||
ok(res.timestamp >= minimum,
|
||||
"Valid " + (res.isRemote? "rtcp" : "rtp") + " timestamp " +
|
||||
res.timestamp + " >= " + minimum + " (" +
|
||||
(res.timestamp - minimum) + " ms)");
|
||||
ok(res.timestamp <= nowish,
|
||||
"Valid " + (res.isRemote? "rtcp" : "rtp") + " timestamp " +
|
||||
res.timestamp + " <= " + nowish + " (" +
|
||||
(res.timestamp - nowish) + " ms)");
|
||||
} else {
|
||||
info("Bug 1225729: Uninitialized timestamp (" + res.timestamp +
|
||||
"), should be >=" + minimum + " and <= " + nowish);
|
||||
info("FIXME bug 1495446: uncomment the timestamp test case " +
|
||||
"above after RTCP epoch bug 1495446 is fixed.");
|
||||
}
|
||||
ok(res.timestamp <= nowish,
|
||||
"Valid " + (res.isRemote? "rtcp" : "rtp") + " timestamp " +
|
||||
res.timestamp + " <= " + nowish + " (" +
|
||||
(res.timestamp - nowish) + " ms)");
|
||||
}
|
||||
if (res.isRemote) {
|
||||
continue;
|
||||
@ -1839,17 +1842,17 @@ PeerConnectionWrapper.prototype = {
|
||||
ok(rem.packetsReceived !== undefined, "Rtcp packetsReceived");
|
||||
ok(rem.packetsLost !== undefined, "Rtcp packetsLost");
|
||||
ok(rem.bytesReceived >= rem.packetsReceived, "Rtcp bytesReceived");
|
||||
if (false) { // Bug 1325430 if (!this.disableRtpCountChecking) {
|
||||
// no guarantee which one is newer!
|
||||
// Note: this must change when we add a timestamp field to remote RTCP reports
|
||||
// and make rem.timestamp be the reception time
|
||||
if (res.timestamp >= rem.timestamp) {
|
||||
ok(rem.packetsReceived <= res.packetsSent, "No more than sent packets");
|
||||
} else {
|
||||
if (!this.disableRtpCountChecking) {
|
||||
// no guarantee which one is newer!
|
||||
// Note: this must change when we add a timestamp field to remote RTCP reports
|
||||
// and make rem.timestamp be the reception time
|
||||
if (res.timestamp >= rem.timestamp) {
|
||||
ok(rem.packetsReceived <= res.packetsSent, "No more than sent packets");
|
||||
} else {
|
||||
info("REVERSED timestamps: rec:" +
|
||||
rem.packetsReceived + " time:" + rem.timestamp + " sent:" + res.packetsSent + " time:" + res.timestamp);
|
||||
}
|
||||
// Else we may have received more than outdated Rtcp packetsSent
|
||||
rem.packetsReceived + " time:" + rem.timestamp + " sent:" + res.packetsSent + " time:" + res.timestamp);
|
||||
}
|
||||
// Else we may have received more than outdated Rtcp packetsSent
|
||||
ok(rem.bytesReceived <= res.bytesSent, "No more than sent bytes");
|
||||
}
|
||||
ok(rem.jitter !== undefined, "Rtcp jitter");
|
||||
|
@ -1250,7 +1250,9 @@ CSPAllowsInlineScript(nsIScriptElement* aElement, nsIDocument* aDocument)
|
||||
|
||||
bool allowInlineScript = false;
|
||||
rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT,
|
||||
nonce, parserCreated, scriptContent, EmptyString(),
|
||||
nonce, parserCreated, scriptContent,
|
||||
nullptr /* nsICSPEventListener */,
|
||||
EmptyString(),
|
||||
aElement->GetScriptLineNumber(),
|
||||
aElement->GetScriptColumnNumber(),
|
||||
&allowInlineScript);
|
||||
@ -2942,6 +2944,7 @@ ScriptLoader::VerifySRI(ScriptLoadRequest* aRequest,
|
||||
csp->LogViolationDetails(
|
||||
nsIContentSecurityPolicy::VIOLATION_TYPE_REQUIRE_SRI_FOR_SCRIPT,
|
||||
nullptr, // triggering element
|
||||
nullptr, // nsICSPEventListener
|
||||
NS_ConvertUTF8toUTF16(violationURISpec),
|
||||
EmptyString(), lineNo, columnNo, EmptyString(), EmptyString());
|
||||
rv = NS_ERROR_SRI_CORRUPT;
|
||||
|
@ -20,6 +20,7 @@ namespace {
|
||||
|
||||
nsresult
|
||||
CheckInternal(nsIContentSecurityPolicy* aCSP,
|
||||
nsICSPEventListener* aCSPEventListener,
|
||||
const nsAString& aExpression,
|
||||
const nsAString& aFileNameString,
|
||||
uint32_t aLineNum,
|
||||
@ -47,6 +48,7 @@ CheckInternal(nsIContentSecurityPolicy* aCSP,
|
||||
if (reportViolation) {
|
||||
aCSP->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
|
||||
nullptr, // triggering element
|
||||
aCSPEventListener,
|
||||
aFileNameString, aExpression, aLineNum,
|
||||
aColumnNum, EmptyString(), EmptyString());
|
||||
}
|
||||
@ -74,8 +76,9 @@ public:
|
||||
bool
|
||||
MainThreadRun() override
|
||||
{
|
||||
mResult = CheckInternal(mWorkerPrivate->GetCSP(), mExpression,
|
||||
mFileNameString, mLineNum, mColumnNum,
|
||||
mResult = CheckInternal(mWorkerPrivate->GetCSP(),
|
||||
mWorkerPrivate->CSPEventListener(),
|
||||
mExpression, mFileNameString, mLineNum, mColumnNum,
|
||||
&mEvalAllowed);
|
||||
return true;
|
||||
}
|
||||
@ -135,7 +138,8 @@ CSPEvalChecker::CheckForWindow(JSContext* aCx, nsGlobalWindowInner* aWindow,
|
||||
fileNameString.AssignLiteral("unknown");
|
||||
}
|
||||
|
||||
rv = CheckInternal(csp, aExpression, fileNameString, lineNum, columnNum,
|
||||
rv = CheckInternal(csp, nullptr /* no CSPEventListener for window */,
|
||||
aExpression, fileNameString, lineNum, columnNum,
|
||||
aAllowEval);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
*aAllowEval = false;
|
||||
|
@ -118,6 +118,7 @@ BlockedContentSourceToString(nsCSPContext::BlockedContentSource aSource,
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsCSPContext::ShouldLoad(nsContentPolicyType aContentType,
|
||||
nsICSPEventListener* aCSPEventListener,
|
||||
nsIURI* aContentLocation,
|
||||
nsIURI* aRequestOrigin,
|
||||
nsISupports* aRequestContext,
|
||||
@ -178,6 +179,7 @@ nsCSPContext::ShouldLoad(nsContentPolicyType aContentType,
|
||||
|
||||
bool permitted = permitsInternal(dir,
|
||||
nullptr, // aTriggeringElement
|
||||
aCSPEventListener,
|
||||
aContentLocation,
|
||||
aOriginalURIIfRedirect,
|
||||
nonce,
|
||||
@ -202,6 +204,7 @@ nsCSPContext::ShouldLoad(nsContentPolicyType aContentType,
|
||||
bool
|
||||
nsCSPContext::permitsInternal(CSPDirective aDir,
|
||||
Element* aTriggeringElement,
|
||||
nsICSPEventListener* aCSPEventListener,
|
||||
nsIURI* aContentLocation,
|
||||
nsIURI* aOriginalURIIfRedirect,
|
||||
const nsAString& aNonce,
|
||||
@ -234,6 +237,7 @@ nsCSPContext::permitsInternal(CSPDirective aDir,
|
||||
// incorrectly fail the unit tests.
|
||||
if (!aIsPreload && aSendViolationReports) {
|
||||
AsyncReportViolation(aTriggeringElement,
|
||||
aCSPEventListener,
|
||||
(aSendContentLocationInViolationReports ?
|
||||
aContentLocation : nullptr),
|
||||
BlockedContentSource::eUnknown, /* a BlockedContentSource */
|
||||
@ -405,6 +409,7 @@ nsCSPContext::GetAllowsEval(bool* outShouldReportViolation,
|
||||
void
|
||||
nsCSPContext::reportInlineViolation(nsContentPolicyType aContentType,
|
||||
Element* aTriggeringElement,
|
||||
nsICSPEventListener* aCSPEventListener,
|
||||
const nsAString& aNonce,
|
||||
const nsAString& aContent,
|
||||
const nsAString& aViolatedDirective,
|
||||
@ -434,8 +439,9 @@ nsCSPContext::reportInlineViolation(nsContentPolicyType aContentType,
|
||||
}
|
||||
|
||||
AsyncReportViolation(aTriggeringElement,
|
||||
aCSPEventListener,
|
||||
nullptr, // aBlockedURI
|
||||
BlockedContentSource::eInline, // aBloeckedSource
|
||||
BlockedContentSource::eInline, // aBlockedSource
|
||||
mSelfURI, // aOriginalURI
|
||||
aViolatedDirective, // aViolatedDirective
|
||||
aViolatedPolicyIndex, // aViolatedPolicyIndex
|
||||
@ -451,6 +457,7 @@ nsCSPContext::GetAllowsInline(nsContentPolicyType aContentType,
|
||||
const nsAString& aNonce,
|
||||
bool aParserCreated,
|
||||
Element* aTriggeringElement,
|
||||
nsICSPEventListener* aCSPEventListener,
|
||||
const nsAString& aContentOfPseudoScript,
|
||||
uint32_t aLineNumber,
|
||||
uint32_t aColumnNumber,
|
||||
@ -511,6 +518,7 @@ nsCSPContext::GetAllowsInline(nsContentPolicyType aContentType,
|
||||
&reportSample);
|
||||
reportInlineViolation(aContentType,
|
||||
aTriggeringElement,
|
||||
aCSPEventListener,
|
||||
aNonce,
|
||||
reportSample ? content : EmptyString(),
|
||||
violatedDirective,
|
||||
@ -563,7 +571,8 @@ nsCSPContext::GetAllowsInline(nsContentPolicyType aContentType,
|
||||
mPolicies[p]->getDirectiveStringAndReportSampleForContentType( \
|
||||
nsIContentPolicy::TYPE_ ## contentPolicyType, \
|
||||
violatedDirective, &reportSample); \
|
||||
AsyncReportViolation(aTriggeringElement, nullptr, blockedContentSource, \
|
||||
AsyncReportViolation(aTriggeringElement, aCSPEventListener, \
|
||||
nullptr, blockedContentSource, \
|
||||
nullptr, violatedDirective, p, \
|
||||
NS_LITERAL_STRING(observerTopic), \
|
||||
aSourceFile, \
|
||||
@ -601,6 +610,7 @@ nsCSPContext::GetAllowsInline(nsContentPolicyType aContentType,
|
||||
NS_IMETHODIMP
|
||||
nsCSPContext::LogViolationDetails(uint16_t aViolationType,
|
||||
Element* aTriggeringElement,
|
||||
nsICSPEventListener* aCSPEventListener,
|
||||
const nsAString& aSourceFile,
|
||||
const nsAString& aScriptSample,
|
||||
int32_t aLineNum,
|
||||
@ -691,13 +701,6 @@ nsCSPContext::SetRequestContext(nsIDocument* aDocument,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsCSPContext::SetEventListener(nsICSPEventListener* aEventListener)
|
||||
{
|
||||
mEventListener = aEventListener;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsCSPContext::EnsureEventTarget(nsIEventTarget* aEventTarget)
|
||||
{
|
||||
@ -1135,12 +1138,13 @@ nsCSPContext::SendReports(
|
||||
nsresult
|
||||
nsCSPContext::FireViolationEvent(
|
||||
Element* aTriggeringElement,
|
||||
nsICSPEventListener* aCSPEventListener,
|
||||
const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit)
|
||||
{
|
||||
if (mEventListener) {
|
||||
if (aCSPEventListener) {
|
||||
nsAutoString json;
|
||||
if (aViolationEventInit.ToJSON(json)) {
|
||||
mEventListener->OnCSPViolationEvent(json);
|
||||
aCSPEventListener->OnCSPViolationEvent(json);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1185,6 +1189,7 @@ class CSPReportSenderRunnable final : public Runnable
|
||||
{
|
||||
public:
|
||||
CSPReportSenderRunnable(Element* aTriggeringElement,
|
||||
nsICSPEventListener* aCSPEventListener,
|
||||
nsIURI* aBlockedURI,
|
||||
nsCSPContext::BlockedContentSource aBlockedContentSource,
|
||||
nsIURI* aOriginalURI,
|
||||
@ -1199,6 +1204,7 @@ class CSPReportSenderRunnable final : public Runnable
|
||||
nsCSPContext* aCSPContext)
|
||||
: mozilla::Runnable("CSPReportSenderRunnable")
|
||||
, mTriggeringElement(aTriggeringElement)
|
||||
, mCSPEventListener(aCSPEventListener)
|
||||
, mBlockedURI(aBlockedURI)
|
||||
, mBlockedContentSource(aBlockedContentSource)
|
||||
, mOriginalURI(aOriginalURI)
|
||||
@ -1292,13 +1298,14 @@ class CSPReportSenderRunnable final : public Runnable
|
||||
}
|
||||
|
||||
// 4) fire violation event
|
||||
mCSPContext->FireViolationEvent(mTriggeringElement, init);
|
||||
mCSPContext->FireViolationEvent(mTriggeringElement, mCSPEventListener, init);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<Element> mTriggeringElement;
|
||||
nsCOMPtr<nsICSPEventListener> mCSPEventListener;
|
||||
nsCOMPtr<nsIURI> mBlockedURI;
|
||||
nsCSPContext::BlockedContentSource mBlockedContentSource;
|
||||
nsCOMPtr<nsIURI> mOriginalURI;
|
||||
@ -1344,6 +1351,7 @@ class CSPReportSenderRunnable final : public Runnable
|
||||
*/
|
||||
nsresult
|
||||
nsCSPContext::AsyncReportViolation(Element* aTriggeringElement,
|
||||
nsICSPEventListener* aCSPEventListener,
|
||||
nsIURI* aBlockedURI,
|
||||
BlockedContentSource aBlockedContentSource,
|
||||
nsIURI* aOriginalURI,
|
||||
@ -1359,6 +1367,7 @@ nsCSPContext::AsyncReportViolation(Element* aTriggeringElement,
|
||||
|
||||
nsCOMPtr<nsIRunnable> task =
|
||||
new CSPReportSenderRunnable(aTriggeringElement,
|
||||
aCSPEventListener,
|
||||
aBlockedURI,
|
||||
aBlockedContentSource,
|
||||
aOriginalURI,
|
||||
@ -1489,7 +1498,8 @@ nsCSPContext::PermitsAncestry(nsIDocShell* aDocShell, bool* outPermitsAncestry)
|
||||
|
||||
|
||||
bool permits = permitsInternal(nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE,
|
||||
nullptr,
|
||||
nullptr, // triggering element
|
||||
nullptr, // nsICSPEventListener
|
||||
ancestorsArray[a],
|
||||
nullptr, // no redirect here.
|
||||
EmptyString(), // no nonce
|
||||
@ -1507,6 +1517,7 @@ nsCSPContext::PermitsAncestry(nsIDocShell* aDocShell, bool* outPermitsAncestry)
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsCSPContext::Permits(Element* aTriggeringElement,
|
||||
nsICSPEventListener* aCSPEventListener,
|
||||
nsIURI* aURI,
|
||||
CSPDirective aDir,
|
||||
bool aSpecific,
|
||||
@ -1519,6 +1530,7 @@ nsCSPContext::Permits(Element* aTriggeringElement,
|
||||
|
||||
*outPermits = permitsInternal(aDir,
|
||||
aTriggeringElement,
|
||||
aCSPEventListener,
|
||||
aURI,
|
||||
nullptr, // no original (pre-redirect) URI
|
||||
EmptyString(), // no nonce
|
||||
|
@ -105,6 +105,7 @@ class nsCSPContext : public nsIContentSecurityPolicy
|
||||
|
||||
nsresult FireViolationEvent(
|
||||
mozilla::dom::Element* aTriggeringElement,
|
||||
nsICSPEventListener* aCSPEventListener,
|
||||
const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit);
|
||||
|
||||
enum BlockedContentSource
|
||||
@ -116,6 +117,7 @@ class nsCSPContext : public nsIContentSecurityPolicy
|
||||
};
|
||||
|
||||
nsresult AsyncReportViolation(mozilla::dom::Element* aTriggeringElement,
|
||||
nsICSPEventListener* aCSPEventListener,
|
||||
nsIURI* aBlockedURI,
|
||||
BlockedContentSource aBlockedContentSource,
|
||||
nsIURI* aOriginalURI,
|
||||
@ -148,6 +150,7 @@ class nsCSPContext : public nsIContentSecurityPolicy
|
||||
private:
|
||||
bool permitsInternal(CSPDirective aDir,
|
||||
mozilla::dom::Element* aTriggeringElement,
|
||||
nsICSPEventListener* aCSPEventListener,
|
||||
nsIURI* aContentLocation,
|
||||
nsIURI* aOriginalURIIfRedirect,
|
||||
const nsAString& aNonce,
|
||||
@ -160,6 +163,7 @@ class nsCSPContext : public nsIContentSecurityPolicy
|
||||
// helper to report inline script/style violations
|
||||
void reportInlineViolation(nsContentPolicyType aContentType,
|
||||
mozilla::dom::Element* aTriggeringElement,
|
||||
nsICSPEventListener* aCSPEventListener,
|
||||
const nsAString& aNonce,
|
||||
const nsAString& aContent,
|
||||
const nsAString& aViolatedDirective,
|
||||
@ -177,7 +181,6 @@ class nsCSPContext : public nsIContentSecurityPolicy
|
||||
// to avoid memory leaks. Within the destructor of the principal we explicitly
|
||||
// set mLoadingPrincipal to null.
|
||||
nsIPrincipal* mLoadingPrincipal;
|
||||
nsCOMPtr<nsICSPEventListener> mEventListener;
|
||||
|
||||
// helper members used to queue up web console messages till
|
||||
// the windowID becomes available. see flushConsoleMessages()
|
||||
|
@ -136,6 +136,10 @@ CSPService::ShouldLoad(nsIURI *aContentLocation,
|
||||
loadingPrincipal->GetURI(getter_AddRefs(requestOrigin));
|
||||
}
|
||||
|
||||
nsCOMPtr<nsICSPEventListener> cspEventListener;
|
||||
nsresult rv = aLoadInfo->GetCspEventListener(getter_AddRefs(cspEventListener));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
|
||||
MOZ_LOG(gCspPRLog, LogLevel::Debug,
|
||||
("CSPService::ShouldLoad called for %s",
|
||||
@ -171,7 +175,6 @@ CSPService::ShouldLoad(nsIURI *aContentLocation,
|
||||
// if we can't query a principal, then there is nothing to do.
|
||||
return NS_OK;
|
||||
}
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
// 1) Apply speculate CSP for preloads
|
||||
bool isPreload = nsContentUtils::IsPreloadType(contentType);
|
||||
@ -184,6 +187,7 @@ CSPService::ShouldLoad(nsIURI *aContentLocation,
|
||||
if (preloadCsp) {
|
||||
// obtain the enforcement decision
|
||||
rv = preloadCsp->ShouldLoad(contentType,
|
||||
cspEventListener,
|
||||
aContentLocation,
|
||||
requestOrigin,
|
||||
requestContext,
|
||||
@ -209,6 +213,7 @@ CSPService::ShouldLoad(nsIURI *aContentLocation,
|
||||
if (csp) {
|
||||
// obtain the enforcement decision
|
||||
rv = csp->ShouldLoad(contentType,
|
||||
cspEventListener,
|
||||
aContentLocation,
|
||||
requestOrigin,
|
||||
requestContext,
|
||||
@ -272,6 +277,10 @@ CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel,
|
||||
|
||||
nsCOMPtr<nsILoadInfo> loadInfo = oldChannel->GetLoadInfo();
|
||||
|
||||
nsCOMPtr<nsICSPEventListener> cspEventListener;
|
||||
rv = loadInfo->GetCspEventListener(getter_AddRefs(cspEventListener));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// if no loadInfo on the channel, nothing for us to do
|
||||
if (!loadInfo) {
|
||||
return NS_OK;
|
||||
@ -322,6 +331,7 @@ CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel,
|
||||
if (preloadCsp) {
|
||||
// Pass originalURI to indicate the redirect
|
||||
preloadCsp->ShouldLoad(policyType, // load type per nsIContentPolicy (uint32_t)
|
||||
cspEventListener,
|
||||
newUri, // nsIURI
|
||||
nullptr, // nsIURI
|
||||
requestContext, // nsISupports
|
||||
@ -347,6 +357,7 @@ CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel,
|
||||
if (csp) {
|
||||
// Pass originalURI to indicate the redirect
|
||||
csp->ShouldLoad(policyType, // load type per nsIContentPolicy (uint32_t)
|
||||
cspEventListener,
|
||||
newUri, // nsIURI
|
||||
nullptr, // nsIURI
|
||||
requestContext, // nsISupports
|
||||
|
@ -110,6 +110,7 @@ function run_test() {
|
||||
"", // aNonce
|
||||
false, // aParserCreated
|
||||
null, // aTriggeringElement
|
||||
null, // nsICSPEventListener
|
||||
"", // aContentOfPseudoScript
|
||||
0, // aLineNumber
|
||||
0); // aColumnNumber
|
||||
@ -137,6 +138,7 @@ function run_test() {
|
||||
// force the logging, since the getter doesn't.
|
||||
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL,
|
||||
null, // aTriggeringElement
|
||||
null, // nsICSPEventListener
|
||||
selfuri.asciiSpec,
|
||||
// sending UTF-16 script sample to make sure
|
||||
// csp report in JSON is not cut-off, please
|
||||
@ -151,6 +153,7 @@ function run_test() {
|
||||
function(csp) {
|
||||
// shouldLoad creates and sends out the report here.
|
||||
csp.shouldLoad(Ci.nsIContentPolicy.TYPE_SCRIPT,
|
||||
null, // nsICSPEventListener
|
||||
NetUtil.newURI("http://blocked.test/foo.js"),
|
||||
null, null, null, null, true);
|
||||
});
|
||||
@ -163,6 +166,7 @@ function run_test() {
|
||||
"", // aNonce
|
||||
false, // aParserCreated
|
||||
null, // aTriggeringElement
|
||||
null, // nsICSPEventListener
|
||||
"", // aContentOfPseudoScript
|
||||
0, // aLineNumber
|
||||
0); // aColumnNumber
|
||||
@ -186,6 +190,7 @@ function run_test() {
|
||||
// force the logging, since the getter doesn't.
|
||||
csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
|
||||
null, // aTriggeringElement
|
||||
null, // nsICSPEventListener
|
||||
selfuri.asciiSpec,
|
||||
"script sample",
|
||||
4, // line number
|
||||
@ -201,6 +206,7 @@ function run_test() {
|
||||
"P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
|
||||
// shouldLoad creates and sends out the report here.
|
||||
csp.shouldLoad(Ci.nsIContentPolicy.TYPE_IMAGE,
|
||||
null, // nsICSPEventListener
|
||||
NetUtil.newURI("data:image/png;base64," + base64data),
|
||||
null, null, null, null, true);
|
||||
});
|
||||
@ -210,6 +216,7 @@ function run_test() {
|
||||
function(csp) {
|
||||
// shouldLoad creates and sends out the report here.
|
||||
csp.shouldLoad(Ci.nsIContentPolicy.TYPE_SUBDOCUMENT,
|
||||
null, // nsICSPEventListener
|
||||
NetUtil.newURI("intent://mymaps.com/maps?um=1&ie=UTF-8&fb=1&sll"),
|
||||
null, null, null, null, true);
|
||||
});
|
||||
@ -221,6 +228,7 @@ function run_test() {
|
||||
var uri = NetUtil
|
||||
// shouldLoad creates and sends out the report here.
|
||||
csp.shouldLoad(Ci.nsIContentPolicy.TYPE_SCRIPT,
|
||||
null, // nsICSPEventListener
|
||||
NetUtil.newURI(selfSpec + "#bar"),
|
||||
null, null, null, null, true);
|
||||
});
|
||||
@ -230,6 +238,7 @@ function run_test() {
|
||||
function(csp) {
|
||||
// shouldLoad creates and sends out the report here.
|
||||
csp.shouldLoad(Ci.nsIContentPolicy.TYPE_SCRIPT,
|
||||
null, // nsICSPEventListener
|
||||
NetUtil.newURI("ftp://blocked.test/profile.png"),
|
||||
null, null, null, null, true);
|
||||
});
|
||||
|
@ -2673,6 +2673,7 @@ LogViolationDetailsRunnable::MainThreadRun()
|
||||
if (mWorkerPrivate->GetReportCSPViolations()) {
|
||||
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
|
||||
nullptr, // triggering element
|
||||
mWorkerPrivate->CSPEventListener(),
|
||||
mFileName, mScriptSample, mLineNum, mColumnNum,
|
||||
EmptyString(), EmptyString());
|
||||
}
|
||||
|
@ -221,6 +221,7 @@ ChannelFromScriptURL(nsIPrincipal* principal,
|
||||
nullptr, // aCallbacks
|
||||
aLoadFlags,
|
||||
ios);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else {
|
||||
// We must have a loadGroup with a load context for the principal to
|
||||
// traverse the channel correctly.
|
||||
@ -228,8 +229,10 @@ ChannelFromScriptURL(nsIPrincipal* principal,
|
||||
MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal));
|
||||
|
||||
RefPtr<PerformanceStorage> performanceStorage;
|
||||
nsCOMPtr<nsICSPEventListener> cspEventListener;
|
||||
if (aWorkerPrivate && !aIsMainScript) {
|
||||
performanceStorage = aWorkerPrivate->GetPerformanceStorage();
|
||||
cspEventListener = aWorkerPrivate->CSPEventListener();
|
||||
}
|
||||
|
||||
if (aClientInfo.isSome()) {
|
||||
@ -257,9 +260,19 @@ ChannelFromScriptURL(nsIPrincipal* principal,
|
||||
aLoadFlags,
|
||||
ios);
|
||||
}
|
||||
}
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (cspEventListener) {
|
||||
nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
|
||||
if (NS_WARN_IF(!loadInfo)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
rv = loadInfo->SetCspEventListener(cspEventListener);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
}
|
||||
|
||||
if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel)) {
|
||||
mozilla::net::ReferrerPolicy referrerPolicy = parentDoc ?
|
||||
@ -1229,6 +1242,7 @@ private:
|
||||
wcsp->LogViolationDetails(
|
||||
nsIContentSecurityPolicy::VIOLATION_TYPE_REQUIRE_SRI_FOR_SCRIPT,
|
||||
nullptr, // triggering element
|
||||
mWorkerPrivate->CSPEventListener(),
|
||||
aLoadInfo.mURL, EmptyString(), 0, 0, EmptyString(), EmptyString());
|
||||
}
|
||||
return NS_ERROR_SRI_CORRUPT;
|
||||
|
@ -1473,7 +1473,6 @@ WorkerPrivate::SetCSP(nsIContentSecurityPolicy* aCSP)
|
||||
aCSP->EnsureEventTarget(mMainThreadEventTarget);
|
||||
|
||||
mLoadInfo.mCSP = aCSP;
|
||||
EnsureCSPEventListener();
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -1514,7 +1513,6 @@ WorkerPrivate::SetCSPFromHeaderValues(const nsACString& aCSPHeaderValue,
|
||||
mLoadInfo.mCSP = csp;
|
||||
mLoadInfo.mEvalAllowed = evalAllowed;
|
||||
mLoadInfo.mReportCSPViolations = reportEvalViolations;
|
||||
EnsureCSPEventListener();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
@ -3439,14 +3437,16 @@ WorkerPrivate::EnsureCSPEventListener()
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mLoadInfo.mCSP) {
|
||||
mLoadInfo.mCSP->SetEventListener(mCSPEventListener);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
nsICSPEventListener*
|
||||
WorkerPrivate::CSPEventListener() const
|
||||
{
|
||||
MOZ_ASSERT(mCSPEventListener);
|
||||
return mCSPEventListener;
|
||||
}
|
||||
|
||||
void
|
||||
WorkerPrivate::EnsurePerformanceStorage()
|
||||
{
|
||||
|
@ -412,6 +412,9 @@ public:
|
||||
return mDebuggerScope;
|
||||
}
|
||||
|
||||
nsICSPEventListener*
|
||||
CSPEventListener() const;
|
||||
|
||||
void
|
||||
SetThread(WorkerThread* aThread);
|
||||
|
||||
|
@ -2477,6 +2477,16 @@ XMLHttpRequestMainThread::CreateChannel()
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (mCSPEventListener) {
|
||||
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
|
||||
if (NS_WARN_IF(!loadInfo)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
rv = loadInfo->SetCspEventListener(mCSPEventListener);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
|
||||
if (httpChannel) {
|
||||
rv = httpChannel->SetRequestMethod(mRequestMethod);
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "nsISizeOfEventTarget.h"
|
||||
#include "nsIXPConnect.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIContentSecurityPolicy.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/DOMEventTargetHelper.h"
|
||||
@ -203,7 +204,8 @@ public:
|
||||
nsIGlobalObject* aGlobalObject,
|
||||
nsIURI* aBaseURI = nullptr,
|
||||
nsILoadGroup* aLoadGroup = nullptr,
|
||||
PerformanceStorage* aPerformanceStorage = nullptr)
|
||||
PerformanceStorage* aPerformanceStorage = nullptr,
|
||||
nsICSPEventListener* aCSPEventListener = nullptr)
|
||||
{
|
||||
MOZ_ASSERT(aPrincipal);
|
||||
mPrincipal = aPrincipal;
|
||||
@ -211,6 +213,7 @@ public:
|
||||
mBaseURI = aBaseURI;
|
||||
mLoadGroup = aLoadGroup;
|
||||
mPerformanceStorage = aPerformanceStorage;
|
||||
mCSPEventListener = aCSPEventListener;
|
||||
}
|
||||
|
||||
void InitParameters(bool aAnon, bool aSystem);
|
||||
@ -546,6 +549,7 @@ protected:
|
||||
nsCOMPtr<nsIStreamListener> mXMLParserStreamListener;
|
||||
|
||||
RefPtr<PerformanceStorage> mPerformanceStorage;
|
||||
nsCOMPtr<nsICSPEventListener> mCSPEventListener;
|
||||
|
||||
// used to implement getAllResponseHeaders()
|
||||
class nsHeaderVisitor : public nsIHttpHeaderVisitor
|
||||
|
@ -867,7 +867,8 @@ Proxy::Init()
|
||||
ownerWindow ? ownerWindow->AsGlobal() : nullptr,
|
||||
mWorkerPrivate->GetBaseURI(),
|
||||
mWorkerPrivate->GetLoadGroup(),
|
||||
mWorkerPrivate->GetPerformanceStorage());
|
||||
mWorkerPrivate->GetPerformanceStorage(),
|
||||
mWorkerPrivate->CSPEventListener());
|
||||
|
||||
mXHR->SetParameters(mMozAnon, mMozSystem);
|
||||
mXHR->SetClientInfoAndController(mClientInfo, mController);
|
||||
|
@ -335,9 +335,13 @@ static void
|
||||
NotifyDidRender(layers::CompositorBridgeParent* aBridge,
|
||||
wr::WrPipelineInfo aInfo,
|
||||
TimeStamp aStart,
|
||||
TimeStamp aEnd)
|
||||
TimeStamp aEnd,
|
||||
bool aRender)
|
||||
{
|
||||
if (aBridge->GetWrBridge()) {
|
||||
if (aRender && aBridge->GetWrBridge()) {
|
||||
// We call this here to mimic the behavior in LayerManagerComposite, as to
|
||||
// not change what Talos measures. That is, we do not record an empty frame
|
||||
// as a frame.
|
||||
aBridge->GetWrBridge()->RecordFrame();
|
||||
}
|
||||
|
||||
@ -376,6 +380,8 @@ RenderThread::UpdateAndRender(wr::WindowId aWindowId,
|
||||
} else {
|
||||
renderer->Update();
|
||||
}
|
||||
// Check graphics reset status even when rendering is skipped.
|
||||
renderer->CheckGraphicsResetStatus();
|
||||
|
||||
TimeStamp end = TimeStamp::Now();
|
||||
|
||||
@ -395,7 +401,8 @@ RenderThread::UpdateAndRender(wr::WindowId aWindowId,
|
||||
&NotifyDidRender,
|
||||
renderer->GetCompositorBridge(),
|
||||
info,
|
||||
aStartTime, end
|
||||
aStartTime, end,
|
||||
aRender
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -168,6 +168,19 @@ RendererOGL::UpdateAndRender(const Maybe<gfx::IntSize>& aReadbackSize, const May
|
||||
mFrameStartTime = TimeStamp();
|
||||
#endif
|
||||
|
||||
// TODO: Flush pending actions such as texture deletions/unlocks and
|
||||
// textureHosts recycling.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
RendererOGL::CheckGraphicsResetStatus()
|
||||
{
|
||||
if (!mCompositor || !mCompositor->gl()) {
|
||||
return;
|
||||
}
|
||||
|
||||
gl::GLContext* gl = mCompositor->gl();
|
||||
if (gl->IsSupported(gl::GLFeature::robustness)) {
|
||||
GLenum resetStatus = gl->fGetGraphicsResetStatus();
|
||||
@ -179,11 +192,6 @@ RendererOGL::UpdateAndRender(const Maybe<gfx::IntSize>& aReadbackSize, const May
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Flush pending actions such as texture deletions/unlocks and
|
||||
// textureHosts recycling.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -84,6 +84,9 @@ public:
|
||||
/// This can be called on the render thread only.
|
||||
bool Resume();
|
||||
|
||||
/// This can be called on the render thread only.
|
||||
void CheckGraphicsResetStatus();
|
||||
|
||||
layers::SyncObjectHost* GetSyncObject() const;
|
||||
|
||||
layers::CompositorBridgeParent* GetCompositorBridge() { return mBridge; }
|
||||
|
@ -817,6 +817,7 @@ SheetLoadData::VerifySheetReadyToParse(nsresult aStatus,
|
||||
csp->LogViolationDetails(
|
||||
nsIContentSecurityPolicy::VIOLATION_TYPE_REQUIRE_SRI_FOR_STYLE,
|
||||
nullptr, // triggering element
|
||||
nullptr, // nsICSPEventListener
|
||||
NS_ConvertUTF8toUTF16(spec), EmptyString(),
|
||||
0, 0, EmptyString(), EmptyString());
|
||||
return NS_OK;
|
||||
|
@ -483,7 +483,8 @@ nsStyleUtil::CSPAllowsInlineStyle(Element* aElement,
|
||||
rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_STYLESHEET,
|
||||
nonce,
|
||||
false, // aParserCreated only applies to scripts
|
||||
aElement, aStyleText, aLineNumber, aColumnNumber,
|
||||
aElement, nullptr, // nsICSPEventListener
|
||||
aStyleText, aLineNumber, aColumnNumber,
|
||||
&allowInlineStyle);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
|
@ -5,4 +5,4 @@ Makefile.in build files for the Mozilla build system.
|
||||
|
||||
The audioipc-2 git repository is: https://github.com/djg/audioipc-2.git
|
||||
|
||||
The git commit ID used was 3d716fe897ccb3ea43a2af0c794ea57c433400d7 (2018-07-30 08:51:04 +1200)
|
||||
The git commit ID used was 572d6a6a16501cde726dcc09604a0cbc895d93e3 (2018-10-23 16:43:12 +1300)
|
||||
|
@ -5,10 +5,10 @@
|
||||
|
||||
//! Various async helpers modelled after futures-rs and tokio-io.
|
||||
|
||||
use {RecvMsg, SendMsg};
|
||||
use bytes::{Buf, BufMut};
|
||||
use futures::{Async, Poll};
|
||||
use iovec::IoVec;
|
||||
use msg::{RecvMsg, SendMsg};
|
||||
use std::io;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use tokio_uds::UnixStream;
|
||||
@ -139,7 +139,7 @@ impl AsyncSendMsg for UnixStream {
|
||||
static DUMMY: &[u8] = &[0];
|
||||
let nom = <&IoVec>::from(DUMMY);
|
||||
let mut bufs = [
|
||||
nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom
|
||||
nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom,
|
||||
];
|
||||
let n = buf.bytes_vec(&mut bufs);
|
||||
self.send_msg(&bufs[..n], cmsg.bytes())
|
||||
|
@ -5,8 +5,8 @@
|
||||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use libc::{self, cmsghdr};
|
||||
use std::{convert, mem, ops, slice};
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::{convert, mem, ops, slice};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Fds {
|
||||
|
@ -164,7 +164,7 @@ where
|
||||
|
||||
buf.reserve((encoded_len + 2) as usize);
|
||||
|
||||
buf.put_u16::<LittleEndian>(encoded_len as u16);
|
||||
buf.put_u16_le(encoded_len as u16);
|
||||
|
||||
if let Err(e) = bincode::config()
|
||||
.limit(encoded_len)
|
||||
|
@ -1,9 +1,14 @@
|
||||
// Copyright © 2017 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
|
||||
// Ease accessing reactor::Core handles.
|
||||
|
||||
use futures::{Future, IntoFuture};
|
||||
use futures::sync::oneshot;
|
||||
use std::{fmt, io, thread};
|
||||
use futures::{Future, IntoFuture};
|
||||
use std::sync::mpsc;
|
||||
use std::{fmt, io, thread};
|
||||
use tokio_core::reactor::{Core, Handle, Remote};
|
||||
|
||||
scoped_thread_local! {
|
||||
|
@ -1,3 +1,8 @@
|
||||
// Copyright © 2017 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
|
||||
use bincode;
|
||||
use cubeb;
|
||||
use std;
|
||||
|
@ -10,9 +10,9 @@ use codec::Codec;
|
||||
use futures::{AsyncSink, Poll, Sink, StartSend, Stream};
|
||||
use libc;
|
||||
use messages::AssocRawFd;
|
||||
use std::{fmt, io, mem};
|
||||
use std::collections::VecDeque;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::{fmt, io, mem};
|
||||
|
||||
const INITIAL_CAPACITY: usize = 1024;
|
||||
const BACKPRESSURE_THRESHOLD: usize = 4 * INITIAL_CAPACITY;
|
||||
@ -34,7 +34,8 @@ impl IncomingFds {
|
||||
|
||||
pub fn take_fds(&mut self) -> Option<[RawFd; 3]> {
|
||||
loop {
|
||||
let fds = self.recv_fds
|
||||
let fds = self
|
||||
.recv_fds
|
||||
.as_mut()
|
||||
.and_then(|recv_fds| recv_fds.next())
|
||||
.and_then(|fds| Some(clone_into_array(&fds)));
|
||||
@ -293,10 +294,6 @@ pub fn framed_with_fds<A, C>(io: A, codec: C) -> FramedWithFds<A, C> {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_zero() -> io::Error {
|
||||
io::Error::new(io::ErrorKind::WriteZero, "failed to write frame to io")
|
||||
}
|
||||
|
||||
fn clone_into_array<A, T>(slice: &[T]) -> A
|
||||
where
|
||||
A: Sized + Default + AsMut<[T]>,
|
||||
|
@ -147,10 +147,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn write_zero() -> io::Error {
|
||||
io::Error::new(io::ErrorKind::WriteZero, "failed to write frame to io")
|
||||
}
|
||||
|
||||
pub fn framed<A, C>(io: A, codec: C) -> Framed<A, C> {
|
||||
Framed {
|
||||
io: io,
|
||||
|
@ -2,7 +2,7 @@
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details
|
||||
#![allow(dead_code)] // TODO: Remove.
|
||||
|
||||
#![recursion_limit = "1024"]
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
@ -29,100 +29,28 @@ extern crate tokio_core;
|
||||
extern crate tokio_io;
|
||||
extern crate tokio_uds;
|
||||
|
||||
pub mod async;
|
||||
pub mod cmsg;
|
||||
mod async;
|
||||
mod cmsg;
|
||||
pub mod codec;
|
||||
pub mod core;
|
||||
pub mod errors;
|
||||
pub mod fd_passing;
|
||||
pub mod frame;
|
||||
pub mod rpc;
|
||||
pub mod core;
|
||||
pub mod messages;
|
||||
mod msg;
|
||||
pub mod rpc;
|
||||
pub mod shm;
|
||||
|
||||
use iovec::IoVec;
|
||||
#[cfg(target_os = "linux")]
|
||||
use libc::MSG_CMSG_CLOEXEC;
|
||||
pub use messages::{ClientMessage, ServerMessage};
|
||||
use std::env::temp_dir;
|
||||
use std::io;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::path::PathBuf;
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
const MSG_CMSG_CLOEXEC: libc::c_int = 0;
|
||||
|
||||
// Extend sys::os::unix::net::UnixStream to support sending and receiving a single file desc.
|
||||
// We can extend UnixStream by using traits, eliminating the need to introduce a new wrapped
|
||||
// UnixStream type.
|
||||
pub trait RecvMsg {
|
||||
fn recv_msg(
|
||||
&mut self,
|
||||
iov: &mut [&mut IoVec],
|
||||
cmsg: &mut [u8],
|
||||
) -> io::Result<(usize, usize, i32)>;
|
||||
}
|
||||
|
||||
pub trait SendMsg {
|
||||
fn send_msg(&mut self, iov: &[&IoVec], cmsg: &[u8]) -> io::Result<usize>;
|
||||
}
|
||||
|
||||
impl<T: AsRawFd> RecvMsg for T {
|
||||
fn recv_msg(
|
||||
&mut self,
|
||||
iov: &mut [&mut IoVec],
|
||||
cmsg: &mut [u8],
|
||||
) -> io::Result<(usize, usize, i32)> {
|
||||
msg::recv_msg_with_flags(self.as_raw_fd(), iov, cmsg, MSG_CMSG_CLOEXEC)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRawFd> SendMsg for T {
|
||||
fn send_msg(&mut self, iov: &[&IoVec], cmsg: &[u8]) -> io::Result<usize> {
|
||||
msg::send_msg_with_flags(self.as_raw_fd(), iov, cmsg, 0)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AutoCloseFd(RawFd);
|
||||
|
||||
impl Drop for AutoCloseFd {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
libc::close(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRawFd for AutoCloseFd {
|
||||
unsafe fn from_raw_fd(fd: RawFd) -> Self {
|
||||
AutoCloseFd(fd)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoRawFd for AutoCloseFd {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
let fd = self.0;
|
||||
::std::mem::forget(self);
|
||||
fd
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for AutoCloseFd {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsRawFd for &'a AutoCloseFd {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// This must match the definition of
|
||||
// ipc::FileDescriptor::PlatformHandleType in Gecko.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub type PlatformHandleType = *mut std::os::raw::c_void;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub type PlatformHandleType = libc::c_int;
|
||||
|
||||
pub fn get_shm_path(dir: &str) -> PathBuf {
|
||||
let pid = unsafe { libc::getpid() };
|
||||
|
@ -1,8 +1,48 @@
|
||||
use iovec::IoVec;
|
||||
// Copyright © 2017 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
|
||||
use iovec::unix as iovec;
|
||||
use iovec::IoVec;
|
||||
use libc;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::{cmp, io, mem, ptr};
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
// Extend sys::os::unix::net::UnixStream to support sending and receiving a single file desc.
|
||||
// We can extend UnixStream by using traits, eliminating the need to introduce a new wrapped
|
||||
// UnixStream type.
|
||||
pub trait RecvMsg {
|
||||
fn recv_msg(
|
||||
&mut self,
|
||||
iov: &mut [&mut IoVec],
|
||||
cmsg: &mut [u8],
|
||||
) -> io::Result<(usize, usize, i32)>;
|
||||
}
|
||||
|
||||
pub trait SendMsg {
|
||||
fn send_msg(&mut self, iov: &[&IoVec], cmsg: &[u8]) -> io::Result<usize>;
|
||||
}
|
||||
|
||||
impl<T: AsRawFd> RecvMsg for T {
|
||||
fn recv_msg(
|
||||
&mut self,
|
||||
iov: &mut [&mut IoVec],
|
||||
cmsg: &mut [u8],
|
||||
) -> io::Result<(usize, usize, i32)> {
|
||||
#[cfg(target_os = "linux")]
|
||||
let flags = libc::MSG_CMSG_CLOEXEC;
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let flags = 0;
|
||||
recv_msg_with_flags(self.as_raw_fd(), iov, cmsg, flags)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRawFd> SendMsg for T {
|
||||
fn send_msg(&mut self, iov: &[&IoVec], cmsg: &[u8]) -> io::Result<usize> {
|
||||
send_msg_with_flags(self.as_raw_fd(), iov, cmsg, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt(r: libc::ssize_t) -> io::Result<usize> {
|
||||
if r == -1 {
|
||||
|
@ -39,10 +39,10 @@
|
||||
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
use futures::{Async, Future, Poll, Sink, Stream};
|
||||
use futures::sync::oneshot;
|
||||
use rpc::Handler;
|
||||
use futures::{Async, Future, Poll, Sink, Stream};
|
||||
use rpc::driver::Driver;
|
||||
use rpc::Handler;
|
||||
use std::collections::VecDeque;
|
||||
use std::io;
|
||||
use tokio_core::reactor::Handle;
|
||||
|
@ -43,8 +43,8 @@
|
||||
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
use futures::{Async, Future, Poll};
|
||||
use futures::sync::{mpsc, oneshot};
|
||||
use futures::{Async, Future, Poll};
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
|
||||
|
@ -6,8 +6,8 @@
|
||||
use futures::{Poll, Sink, Stream};
|
||||
use std::io;
|
||||
|
||||
mod driver;
|
||||
mod client;
|
||||
mod driver;
|
||||
mod server;
|
||||
|
||||
pub use self::client::{bind_client, Client, ClientProxy, Response};
|
||||
|
@ -40,8 +40,8 @@
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
use futures::{Async, Future, Poll, Sink, Stream};
|
||||
use rpc::Handler;
|
||||
use rpc::driver::Driver;
|
||||
use rpc::Handler;
|
||||
use std::collections::VecDeque;
|
||||
use std::io;
|
||||
use tokio_core::reactor::Handle;
|
||||
|
@ -1,3 +1,8 @@
|
||||
// Copyright © 2017 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
|
||||
use errors::*;
|
||||
use memmap::{Mmap, MmapViewSync, Protection};
|
||||
use std::fs::{remove_file, File, OpenOptions};
|
||||
|
@ -3,27 +3,29 @@
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details
|
||||
|
||||
use {ClientStream, CPUPOOL_INIT_PARAMS, G_SERVER_FD};
|
||||
use assert_not_in_callback;
|
||||
use audioipc::{messages, ClientMessage, ServerMessage};
|
||||
use audioipc::{core, rpc};
|
||||
use audioipc::codec::LengthDelimitedCodec;
|
||||
use audioipc::fd_passing::{framed_with_fds, FramedWithFds};
|
||||
use cubeb_backend::{ffi, Context, ContextOps, DeviceCollectionRef, DeviceId, DeviceType, Error,
|
||||
Ops, Result, Stream, StreamParams, StreamParamsRef};
|
||||
use audioipc::{core, rpc};
|
||||
use audioipc::{messages, ClientMessage, ServerMessage};
|
||||
use cubeb_backend::{
|
||||
ffi, Context, ContextOps, DeviceCollectionRef, DeviceId, DeviceType, Error, Ops, Result,
|
||||
Stream, StreamParams, StreamParamsRef,
|
||||
};
|
||||
use futures::Future;
|
||||
use futures_cpupool::{self, CpuPool};
|
||||
use libc;
|
||||
use std::{fmt, io, mem, ptr};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::c_void;
|
||||
use std::os::unix::io::FromRawFd;
|
||||
use std::os::unix::net;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::{fmt, io, mem, ptr};
|
||||
use stream;
|
||||
use tokio_core::reactor::{Handle, Remote};
|
||||
use tokio_uds::UnixStream;
|
||||
use {ClientStream, CPUPOOL_INIT_PARAMS, G_SERVER_FD};
|
||||
|
||||
struct CubebClient;
|
||||
|
||||
@ -100,9 +102,7 @@ impl ContextOps for ClientContext {
|
||||
|
||||
let (tx_rpc, rx_rpc) = mpsc::channel();
|
||||
|
||||
let params = CPUPOOL_INIT_PARAMS.with(|p| {
|
||||
p.replace(None).unwrap()
|
||||
});
|
||||
let params = CPUPOOL_INIT_PARAMS.with(|p| p.replace(None).unwrap());
|
||||
|
||||
let thread_create_callback = params.thread_create_callback;
|
||||
|
||||
@ -134,11 +134,11 @@ impl ContextOps for ClientContext {
|
||||
let rpc = t!(rx_rpc.recv());
|
||||
|
||||
let cpupool = futures_cpupool::Builder::new()
|
||||
.name_prefix("AudioIPC")
|
||||
.after_start(register_thread)
|
||||
.pool_size(params.pool_size)
|
||||
.stack_size(params.stack_size)
|
||||
.create();
|
||||
.name_prefix("AudioIPC")
|
||||
.after_start(register_thread)
|
||||
.pool_size(params.pool_size)
|
||||
.stack_size(params.stack_size)
|
||||
.create();
|
||||
|
||||
let ctx = Box::new(ClientContext {
|
||||
_ops: &CLIENT_OPS as *const _,
|
||||
|
@ -20,6 +20,7 @@ mod send_recv;
|
||||
mod context;
|
||||
mod stream;
|
||||
|
||||
use audioipc::PlatformHandleType;
|
||||
use context::ClientContext;
|
||||
use cubeb_backend::{capi, ffi};
|
||||
use std::os::raw::{c_char, c_int};
|
||||
@ -31,10 +32,13 @@ type InitParamsTls = std::cell::RefCell<Option<CpuPoolInitParams>>;
|
||||
thread_local!(static IN_CALLBACK: std::cell::RefCell<bool> = std::cell::RefCell::new(false));
|
||||
thread_local!(static CPUPOOL_INIT_PARAMS: InitParamsTls = std::cell::RefCell::new(None));
|
||||
|
||||
// This must match the definition of AudioIpcInitParams in
|
||||
// dom/media/CubebUtils.cpp in Gecko.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct AudioIpcInitParams {
|
||||
pub server_connection: c_int,
|
||||
// Fields only need to be public for ipctest.
|
||||
pub server_connection: PlatformHandleType,
|
||||
pub pool_size: usize,
|
||||
pub stack_size: usize,
|
||||
pub thread_create_callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>,
|
||||
@ -42,13 +46,13 @@ pub struct AudioIpcInitParams {
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct CpuPoolInitParams {
|
||||
pub pool_size: usize,
|
||||
pub stack_size: usize,
|
||||
pub thread_create_callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>,
|
||||
pool_size: usize,
|
||||
stack_size: usize,
|
||||
thread_create_callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>,
|
||||
}
|
||||
|
||||
impl CpuPoolInitParams {
|
||||
pub fn init_with(params: &AudioIpcInitParams) -> Self {
|
||||
fn init_with(params: &AudioIpcInitParams) -> Self {
|
||||
CpuPoolInitParams {
|
||||
pool_size: params.pool_size,
|
||||
stack_size: params.stack_size,
|
||||
|
@ -1,3 +1,8 @@
|
||||
// Copyright © 2017 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details.
|
||||
|
||||
use cubeb_backend::Error;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
|
@ -3,8 +3,6 @@
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details
|
||||
|
||||
use {assert_not_in_callback, set_in_callback};
|
||||
use ClientContext;
|
||||
use audioipc::codec::LengthDelimitedCodec;
|
||||
use audioipc::frame::{framed, Framed};
|
||||
use audioipc::messages::{self, CallbackReq, CallbackResp, ClientMessage, ServerMessage};
|
||||
@ -21,6 +19,8 @@ use std::os::unix::net;
|
||||
use std::ptr;
|
||||
use std::sync::mpsc;
|
||||
use tokio_uds::UnixStream;
|
||||
use ClientContext;
|
||||
use {assert_not_in_callback, set_in_callback};
|
||||
|
||||
// TODO: Remove and let caller allocate based on cubeb backend requirements.
|
||||
const SHM_AREA_SIZE: usize = 2 * 1024 * 1024;
|
||||
@ -95,14 +95,14 @@ impl rpc::Server for CallbackServer {
|
||||
.get_slice(nframes as usize * frame_size)
|
||||
.unwrap()
|
||||
.as_ptr(),
|
||||
None => ptr::null()
|
||||
None => ptr::null(),
|
||||
};
|
||||
let output_ptr: *mut u8 = match output_shm {
|
||||
Some(ref mut shm) => shm
|
||||
.get_mut_slice(nframes as usize * frame_size)
|
||||
.unwrap()
|
||||
.as_mut_ptr(),
|
||||
None => ptr::null_mut()
|
||||
None => ptr::null_mut(),
|
||||
};
|
||||
|
||||
set_in_callback(true);
|
||||
|
@ -1,3 +1,8 @@
|
||||
// Copyright © 2017 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details
|
||||
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
|
||||
@ -14,30 +19,21 @@ extern crate slab;
|
||||
extern crate tokio_core;
|
||||
extern crate tokio_uds;
|
||||
|
||||
use audioipc::codec::LengthDelimitedCodec;
|
||||
use audioipc::core;
|
||||
use audioipc::fd_passing::{framed_with_fds, FramedWithFds};
|
||||
use audioipc::frame::{framed, Framed};
|
||||
use audioipc::messages::{CallbackReq, CallbackResp, ClientMessage, Device, DeviceInfo,
|
||||
ServerMessage, StreamCreate, StreamInitParams, StreamParams};
|
||||
use audioipc::fd_passing::framed_with_fds;
|
||||
use audioipc::rpc;
|
||||
use audioipc::shm::{SharedMemReader, SharedMemWriter};
|
||||
use cubeb::ffi;
|
||||
use futures::future::{self, FutureResult};
|
||||
use audioipc::PlatformHandleType;
|
||||
use futures::sync::oneshot;
|
||||
use futures::Future;
|
||||
use std::cell::RefCell;
|
||||
use std::convert::From;
|
||||
use std::error::Error;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::mem::{size_of, ManuallyDrop};
|
||||
use std::os::raw::{c_long, c_void};
|
||||
use std::os::raw::c_void;
|
||||
use std::os::unix::io::IntoRawFd;
|
||||
use std::os::unix::net;
|
||||
use std::os::unix::prelude::*;
|
||||
use std::{panic, ptr, slice};
|
||||
use tokio_core::reactor::Remote;
|
||||
use std::ptr;
|
||||
use tokio_uds::UnixStream;
|
||||
|
||||
mod server;
|
||||
|
||||
pub mod errors {
|
||||
error_chain! {
|
||||
links {
|
||||
@ -53,394 +49,6 @@ pub mod errors {
|
||||
|
||||
use errors::*;
|
||||
|
||||
type ContextKey = RefCell<Option<cubeb::Result<cubeb::Context>>>;
|
||||
thread_local!(static CONTEXT_KEY:ContextKey = RefCell::new(None));
|
||||
|
||||
fn with_local_context<T, F>(f: F) -> T
|
||||
where
|
||||
F: FnOnce(&cubeb::Result<cubeb::Context>) -> T,
|
||||
{
|
||||
CONTEXT_KEY.with(|k| {
|
||||
let mut context = k.borrow_mut();
|
||||
if context.is_none() {
|
||||
let name = CString::new("AudioIPC Server").unwrap();
|
||||
*context = Some(cubeb::Context::init(Some(name.as_c_str()), None));
|
||||
}
|
||||
f(context.as_ref().unwrap())
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Remove and let caller allocate based on cubeb backend requirements.
|
||||
const SHM_AREA_SIZE: usize = 2 * 1024 * 1024;
|
||||
|
||||
// The size in which the stream slab is grown.
|
||||
const STREAM_CONN_CHUNK_SIZE: usize = 64;
|
||||
|
||||
struct CallbackClient;
|
||||
|
||||
impl rpc::Client for CallbackClient {
|
||||
type Request = CallbackReq;
|
||||
type Response = CallbackResp;
|
||||
type Transport = Framed<UnixStream, LengthDelimitedCodec<Self::Request, Self::Response>>;
|
||||
}
|
||||
|
||||
struct ServerStreamCallbacks {
|
||||
/// Size of input frame in bytes
|
||||
input_frame_size: u16,
|
||||
/// Size of output frame in bytes
|
||||
output_frame_size: u16,
|
||||
/// Shared memory buffer for sending input data to client
|
||||
input_shm: SharedMemWriter,
|
||||
/// Shared memory buffer for receiving output data from client
|
||||
output_shm: SharedMemReader,
|
||||
/// RPC interface to callback server running in client
|
||||
rpc: rpc::ClientProxy<CallbackReq, CallbackResp>,
|
||||
}
|
||||
|
||||
impl ServerStreamCallbacks {
|
||||
fn data_callback(&mut self, input: &[u8], output: &mut [u8]) -> isize {
|
||||
trace!("Stream data callback: {} {}", input.len(), output.len());
|
||||
|
||||
// len is of input and output is frame len. Turn these into the real lengths.
|
||||
let real_input = unsafe {
|
||||
let nbytes = input.len() * self.input_frame_size as usize;
|
||||
slice::from_raw_parts(input.as_ptr(), nbytes)
|
||||
};
|
||||
|
||||
self.input_shm.write(real_input).unwrap();
|
||||
|
||||
let r = self.rpc
|
||||
.call(CallbackReq::Data(
|
||||
output.len() as isize,
|
||||
self.output_frame_size as usize,
|
||||
))
|
||||
.wait();
|
||||
|
||||
match r {
|
||||
Ok(CallbackResp::Data(frames)) => {
|
||||
if frames >= 0 {
|
||||
let nbytes = frames as usize * self.output_frame_size as usize;
|
||||
let real_output = unsafe {
|
||||
trace!("Resize output to {}", nbytes);
|
||||
slice::from_raw_parts_mut(output.as_mut_ptr(), nbytes)
|
||||
};
|
||||
self.output_shm.read(&mut real_output[..nbytes]).unwrap();
|
||||
}
|
||||
frames
|
||||
}
|
||||
_ => {
|
||||
debug!("Unexpected message {:?} during data_callback", r);
|
||||
-1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn state_callback(&mut self, state: cubeb::State) {
|
||||
trace!("Stream state callback: {:?}", state);
|
||||
let r = self.rpc.call(CallbackReq::State(state.into())).wait();
|
||||
match r {
|
||||
Ok(CallbackResp::State) => {}
|
||||
_ => {
|
||||
debug!("Unexpected message {:?} during callback", r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ServerStream {
|
||||
stream: ManuallyDrop<cubeb::Stream>,
|
||||
cbs: ManuallyDrop<Box<ServerStreamCallbacks>>,
|
||||
}
|
||||
|
||||
impl Drop for ServerStream {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
ManuallyDrop::drop(&mut self.stream);
|
||||
ManuallyDrop::drop(&mut self.cbs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type StreamSlab = slab::Slab<ServerStream, usize>;
|
||||
|
||||
pub struct CubebServer {
|
||||
cb_remote: Remote,
|
||||
streams: StreamSlab,
|
||||
}
|
||||
|
||||
impl rpc::Server for CubebServer {
|
||||
type Request = ServerMessage;
|
||||
type Response = ClientMessage;
|
||||
type Future = FutureResult<Self::Response, ()>;
|
||||
type Transport = FramedWithFds<UnixStream, LengthDelimitedCodec<Self::Response, Self::Request>>;
|
||||
|
||||
fn process(&mut self, req: Self::Request) -> Self::Future {
|
||||
let resp = with_local_context(|context| match *context {
|
||||
Err(_) => error(cubeb::Error::error()),
|
||||
Ok(ref context) => self.process_msg(context, &req),
|
||||
});
|
||||
future::ok(resp)
|
||||
}
|
||||
}
|
||||
|
||||
impl CubebServer {
|
||||
pub fn new(cb_remote: Remote) -> Self {
|
||||
CubebServer {
|
||||
cb_remote: cb_remote,
|
||||
streams: StreamSlab::with_capacity(STREAM_CONN_CHUNK_SIZE),
|
||||
}
|
||||
}
|
||||
|
||||
// Process a request coming from the client.
|
||||
fn process_msg(&mut self, context: &cubeb::Context, msg: &ServerMessage) -> ClientMessage {
|
||||
let resp: ClientMessage = match *msg {
|
||||
ServerMessage::ClientConnect => panic!("already connected"),
|
||||
|
||||
ServerMessage::ClientDisconnect => {
|
||||
// TODO:
|
||||
//self.connection.client_disconnect();
|
||||
ClientMessage::ClientDisconnected
|
||||
}
|
||||
|
||||
ServerMessage::ContextGetBackendId => ClientMessage::ContextBackendId(),
|
||||
|
||||
ServerMessage::ContextGetMaxChannelCount => context
|
||||
.max_channel_count()
|
||||
.map(ClientMessage::ContextMaxChannelCount)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::ContextGetMinLatency(ref params) => {
|
||||
let format = cubeb::SampleFormat::from(params.format);
|
||||
let layout = cubeb::ChannelLayout::from(params.layout);
|
||||
|
||||
let params = cubeb::StreamParamsBuilder::new()
|
||||
.format(format)
|
||||
.rate(u32::from(params.rate))
|
||||
.channels(u32::from(params.channels))
|
||||
.layout(layout)
|
||||
.take();
|
||||
|
||||
context
|
||||
.min_latency(¶ms)
|
||||
.map(ClientMessage::ContextMinLatency)
|
||||
.unwrap_or_else(error)
|
||||
}
|
||||
|
||||
ServerMessage::ContextGetPreferredSampleRate => context
|
||||
.preferred_sample_rate()
|
||||
.map(ClientMessage::ContextPreferredSampleRate)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::ContextGetDeviceEnumeration(device_type) => context
|
||||
.enumerate_devices(cubeb::DeviceType::from_bits_truncate(device_type))
|
||||
.map(|devices| {
|
||||
let v: Vec<DeviceInfo> = devices.iter().map(|i| i.as_ref().into()).collect();
|
||||
ClientMessage::ContextEnumeratedDevices(v)
|
||||
})
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::StreamInit(ref params) => self.process_stream_init(context, params)
|
||||
.unwrap_or_else(|_| error(cubeb::Error::error())),
|
||||
|
||||
ServerMessage::StreamDestroy(stm_tok) => {
|
||||
self.streams.remove(stm_tok);
|
||||
ClientMessage::StreamDestroyed
|
||||
}
|
||||
|
||||
ServerMessage::StreamStart(stm_tok) => self.streams[stm_tok]
|
||||
.stream
|
||||
.start()
|
||||
.map(|_| ClientMessage::StreamStarted)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::StreamStop(stm_tok) => self.streams[stm_tok]
|
||||
.stream
|
||||
.stop()
|
||||
.map(|_| ClientMessage::StreamStopped)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::StreamResetDefaultDevice(stm_tok) => self.streams[stm_tok]
|
||||
.stream
|
||||
.reset_default_device()
|
||||
.map(|_| ClientMessage::StreamDefaultDeviceReset)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::StreamGetPosition(stm_tok) => self.streams[stm_tok]
|
||||
.stream
|
||||
.position()
|
||||
.map(ClientMessage::StreamPosition)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::StreamGetLatency(stm_tok) => self.streams[stm_tok]
|
||||
.stream
|
||||
.latency()
|
||||
.map(ClientMessage::StreamLatency)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::StreamSetVolume(stm_tok, volume) => self.streams[stm_tok]
|
||||
.stream
|
||||
.set_volume(volume)
|
||||
.map(|_| ClientMessage::StreamVolumeSet)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::StreamSetPanning(stm_tok, panning) => self.streams[stm_tok]
|
||||
.stream
|
||||
.set_panning(panning)
|
||||
.map(|_| ClientMessage::StreamPanningSet)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::StreamGetCurrentDevice(stm_tok) => self.streams[stm_tok]
|
||||
.stream
|
||||
.current_device()
|
||||
.map(|device| ClientMessage::StreamCurrentDevice(Device::from(device)))
|
||||
.unwrap_or_else(error),
|
||||
};
|
||||
|
||||
trace!("process_msg: req={:?}, resp={:?}", msg, resp);
|
||||
|
||||
resp
|
||||
}
|
||||
|
||||
// Stream init is special, so it's been separated from process_msg.
|
||||
fn process_stream_init(
|
||||
&mut self,
|
||||
context: &cubeb::Context,
|
||||
params: &StreamInitParams,
|
||||
) -> Result<ClientMessage> {
|
||||
fn frame_size_in_bytes(params: Option<&StreamParams>) -> u16 {
|
||||
params
|
||||
.map(|p| {
|
||||
let format = p.format.into();
|
||||
let sample_size = match format {
|
||||
cubeb::SampleFormat::S16LE
|
||||
| cubeb::SampleFormat::S16BE
|
||||
| cubeb::SampleFormat::S16NE => 2,
|
||||
cubeb::SampleFormat::Float32LE
|
||||
| cubeb::SampleFormat::Float32BE
|
||||
| cubeb::SampleFormat::Float32NE => 4,
|
||||
};
|
||||
let channel_count = p.channels as u16;
|
||||
sample_size * channel_count
|
||||
})
|
||||
.unwrap_or(0u16)
|
||||
}
|
||||
|
||||
// Create the callback handling struct which is attached the cubeb stream.
|
||||
let input_frame_size = frame_size_in_bytes(params.input_stream_params.as_ref());
|
||||
let output_frame_size = frame_size_in_bytes(params.output_stream_params.as_ref());
|
||||
|
||||
let (stm1, stm2) = net::UnixStream::pair()?;
|
||||
debug!("Created callback pair: {:?}-{:?}", stm1, stm2);
|
||||
let (input_shm, input_file) =
|
||||
SharedMemWriter::new(&audioipc::get_shm_path("input"), SHM_AREA_SIZE)?;
|
||||
let (output_shm, output_file) =
|
||||
SharedMemReader::new(&audioipc::get_shm_path("output"), SHM_AREA_SIZE)?;
|
||||
|
||||
// This code is currently running on the Client/Server RPC
|
||||
// handling thread. We need to move the registration of the
|
||||
// bind_client to the callback RPC handling thread. This is
|
||||
// done by spawning a future on cb_remote.
|
||||
|
||||
let id = core::handle().id();
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.cb_remote.spawn(move |handle| {
|
||||
// Ensure we're running on a loop different to the one
|
||||
// invoking spawn_fn.
|
||||
assert_ne!(id, handle.id());
|
||||
let stream = UnixStream::from_stream(stm2, handle).unwrap();
|
||||
let transport = framed(stream, Default::default());
|
||||
let rpc = rpc::bind_client::<CallbackClient>(transport, handle);
|
||||
drop(tx.send(rpc));
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let rpc: rpc::ClientProxy<CallbackReq, CallbackResp> = match rx.wait() {
|
||||
Ok(rpc) => rpc,
|
||||
Err(_) => bail!("Failed to create callback rpc."),
|
||||
};
|
||||
|
||||
let cbs = Box::new(ServerStreamCallbacks {
|
||||
input_frame_size,
|
||||
output_frame_size,
|
||||
input_shm,
|
||||
output_shm,
|
||||
rpc,
|
||||
});
|
||||
|
||||
// Create cubeb stream from params
|
||||
let stream_name = params
|
||||
.stream_name
|
||||
.as_ref()
|
||||
.and_then(|name| CStr::from_bytes_with_nul(name).ok());
|
||||
|
||||
let input_device = params.input_device as *const _;
|
||||
let input_stream_params = params.input_stream_params.as_ref().map(|isp| unsafe {
|
||||
cubeb::StreamParamsRef::from_ptr(isp as *const StreamParams as *mut _)
|
||||
});
|
||||
|
||||
let output_device = params.output_device as *const _;
|
||||
let output_stream_params = params.output_stream_params.as_ref().map(|osp| unsafe {
|
||||
cubeb::StreamParamsRef::from_ptr(osp as *const StreamParams as *mut _)
|
||||
});
|
||||
|
||||
let latency = params.latency_frames;
|
||||
assert!(size_of::<Box<ServerStreamCallbacks>>() == size_of::<usize>());
|
||||
let user_ptr = cbs.as_ref() as *const ServerStreamCallbacks as *mut c_void;
|
||||
|
||||
unsafe {
|
||||
context
|
||||
.stream_init(
|
||||
stream_name,
|
||||
input_device,
|
||||
input_stream_params,
|
||||
output_device,
|
||||
output_stream_params,
|
||||
latency,
|
||||
Some(data_cb_c),
|
||||
Some(state_cb_c),
|
||||
user_ptr,
|
||||
)
|
||||
.and_then(|stream| {
|
||||
if !self.streams.has_available() {
|
||||
trace!(
|
||||
"server connection ran out of stream slots. reserving {} more.",
|
||||
STREAM_CONN_CHUNK_SIZE
|
||||
);
|
||||
self.streams.reserve_exact(STREAM_CONN_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
let stm_tok = match self.streams.vacant_entry() {
|
||||
Some(entry) => {
|
||||
debug!("Registering stream {:?}", entry.index(),);
|
||||
|
||||
entry
|
||||
.insert(ServerStream {
|
||||
stream: ManuallyDrop::new(stream),
|
||||
cbs: ManuallyDrop::new(cbs),
|
||||
})
|
||||
.index()
|
||||
}
|
||||
None => {
|
||||
// TODO: Turn into error
|
||||
panic!("Failed to insert stream into slab. No entries")
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ClientMessage::StreamCreated(StreamCreate {
|
||||
token: stm_tok,
|
||||
fds: [
|
||||
stm1.into_raw_fd(),
|
||||
input_file.into_raw_fd(),
|
||||
output_file.into_raw_fd(),
|
||||
],
|
||||
}))
|
||||
})
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ServerWrapper {
|
||||
core_thread: core::CoreThread,
|
||||
callback_thread: core::CoreThread,
|
||||
@ -487,7 +95,7 @@ pub extern "C" fn audioipc_server_start() -> *mut c_void {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn audioipc_server_new_client(p: *mut c_void) -> libc::c_int {
|
||||
pub extern "C" fn audioipc_server_new_client(p: *mut c_void) -> PlatformHandleType {
|
||||
let (wait_tx, wait_rx) = oneshot::channel();
|
||||
let wrapper: &ServerWrapper = unsafe { &*(p as *mut _) };
|
||||
|
||||
@ -505,10 +113,9 @@ pub extern "C" fn audioipc_server_new_client(p: *mut c_void) -> libc::c_int {
|
||||
UnixStream::from_stream(sock2, handle)
|
||||
.and_then(|sock| {
|
||||
let transport = framed_with_fds(sock, Default::default());
|
||||
rpc::bind_server(transport, CubebServer::new(cb_remote), handle);
|
||||
rpc::bind_server(transport, server::CubebServer::new(cb_remote), handle);
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|_| ())
|
||||
}).map_err(|_| ())
|
||||
// Notify waiting thread that sock2 has been registered.
|
||||
.and_then(|_| wait_tx.send(()))
|
||||
});
|
||||
@ -516,8 +123,7 @@ pub extern "C" fn audioipc_server_new_client(p: *mut c_void) -> libc::c_int {
|
||||
// with reactor::Core.
|
||||
let _ = wait_rx.wait();
|
||||
Ok(sock1.into_raw_fd())
|
||||
})
|
||||
.unwrap_or(-1)
|
||||
}).unwrap_or(-1)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@ -525,45 +131,3 @@ pub extern "C" fn audioipc_server_stop(p: *mut c_void) {
|
||||
let wrapper = unsafe { Box::<ServerWrapper>::from_raw(p as *mut _) };
|
||||
drop(wrapper);
|
||||
}
|
||||
|
||||
fn error(error: cubeb::Error) -> ClientMessage {
|
||||
ClientMessage::Error(error.raw_code())
|
||||
}
|
||||
|
||||
// C callable callbacks
|
||||
unsafe extern "C" fn data_cb_c(
|
||||
_: *mut ffi::cubeb_stream,
|
||||
user_ptr: *mut c_void,
|
||||
input_buffer: *const c_void,
|
||||
output_buffer: *mut c_void,
|
||||
nframes: c_long,
|
||||
) -> c_long {
|
||||
let ok = panic::catch_unwind(|| {
|
||||
let cbs = &mut *(user_ptr as *mut ServerStreamCallbacks);
|
||||
let input = if input_buffer.is_null() {
|
||||
&[]
|
||||
} else {
|
||||
slice::from_raw_parts(input_buffer as *const u8, nframes as usize)
|
||||
};
|
||||
let output: &mut [u8] = if output_buffer.is_null() {
|
||||
&mut []
|
||||
} else {
|
||||
slice::from_raw_parts_mut(output_buffer as *mut u8, nframes as usize)
|
||||
};
|
||||
cbs.data_callback(input, output) as c_long
|
||||
});
|
||||
ok.unwrap_or(0)
|
||||
}
|
||||
|
||||
unsafe extern "C" fn state_cb_c(
|
||||
_: *mut ffi::cubeb_stream,
|
||||
user_ptr: *mut c_void,
|
||||
state: ffi::cubeb_state,
|
||||
) {
|
||||
let ok = panic::catch_unwind(|| {
|
||||
let state = cubeb::State::from(state);
|
||||
let cbs = &mut *(user_ptr as *mut ServerStreamCallbacks);
|
||||
cbs.state_callback(state);
|
||||
});
|
||||
ok.expect("State callback panicked");
|
||||
}
|
||||
|
460
media/audioipc/server/src/server.rs
Normal file
460
media/audioipc/server/src/server.rs
Normal file
@ -0,0 +1,460 @@
|
||||
// Copyright © 2017 Mozilla Foundation
|
||||
//
|
||||
// This program is made available under an ISC-style license. See the
|
||||
// accompanying file LICENSE for details
|
||||
|
||||
use audioipc;
|
||||
use audioipc::codec::LengthDelimitedCodec;
|
||||
use audioipc::core;
|
||||
use audioipc::fd_passing::FramedWithFds;
|
||||
use audioipc::frame::{framed, Framed};
|
||||
use audioipc::messages::{
|
||||
CallbackReq, CallbackResp, ClientMessage, Device, DeviceInfo, ServerMessage, StreamCreate,
|
||||
StreamInitParams, StreamParams,
|
||||
};
|
||||
use audioipc::rpc;
|
||||
use audioipc::shm::{SharedMemReader, SharedMemWriter};
|
||||
use cubeb;
|
||||
use cubeb::ffi;
|
||||
use futures::future::{self, FutureResult};
|
||||
use futures::sync::oneshot;
|
||||
use futures::Future;
|
||||
use slab;
|
||||
use std::cell::RefCell;
|
||||
use std::convert::From;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::mem::{size_of, ManuallyDrop};
|
||||
use std::os::raw::{c_long, c_void};
|
||||
use std::os::unix::io::IntoRawFd;
|
||||
use std::os::unix::net;
|
||||
use std::{panic, slice};
|
||||
use tokio_core::reactor::Remote;
|
||||
use tokio_uds::UnixStream;
|
||||
|
||||
use errors::*;
|
||||
|
||||
fn error(error: cubeb::Error) -> ClientMessage {
|
||||
ClientMessage::Error(error.raw_code())
|
||||
}
|
||||
|
||||
type ContextKey = RefCell<Option<cubeb::Result<cubeb::Context>>>;
|
||||
thread_local!(static CONTEXT_KEY:ContextKey = RefCell::new(None));
|
||||
|
||||
fn with_local_context<T, F>(f: F) -> T
|
||||
where
|
||||
F: FnOnce(&cubeb::Result<cubeb::Context>) -> T,
|
||||
{
|
||||
CONTEXT_KEY.with(|k| {
|
||||
let mut context = k.borrow_mut();
|
||||
if context.is_none() {
|
||||
let name = CString::new("AudioIPC Server").unwrap();
|
||||
*context = Some(cubeb::Context::init(Some(name.as_c_str()), None));
|
||||
}
|
||||
f(context.as_ref().unwrap())
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Remove and let caller allocate based on cubeb backend requirements.
|
||||
const SHM_AREA_SIZE: usize = 2 * 1024 * 1024;
|
||||
|
||||
// The size in which the stream slab is grown.
|
||||
const STREAM_CONN_CHUNK_SIZE: usize = 64;
|
||||
|
||||
struct CallbackClient;
|
||||
|
||||
impl rpc::Client for CallbackClient {
|
||||
type Request = CallbackReq;
|
||||
type Response = CallbackResp;
|
||||
type Transport = Framed<UnixStream, LengthDelimitedCodec<Self::Request, Self::Response>>;
|
||||
}
|
||||
|
||||
struct ServerStreamCallbacks {
|
||||
/// Size of input frame in bytes
|
||||
input_frame_size: u16,
|
||||
/// Size of output frame in bytes
|
||||
output_frame_size: u16,
|
||||
/// Shared memory buffer for sending input data to client
|
||||
input_shm: SharedMemWriter,
|
||||
/// Shared memory buffer for receiving output data from client
|
||||
output_shm: SharedMemReader,
|
||||
/// RPC interface to callback server running in client
|
||||
rpc: rpc::ClientProxy<CallbackReq, CallbackResp>,
|
||||
}
|
||||
|
||||
impl ServerStreamCallbacks {
|
||||
fn data_callback(&mut self, input: &[u8], output: &mut [u8]) -> isize {
|
||||
trace!("Stream data callback: {} {}", input.len(), output.len());
|
||||
|
||||
// len is of input and output is frame len. Turn these into the real lengths.
|
||||
let real_input = unsafe {
|
||||
let nbytes = input.len() * self.input_frame_size as usize;
|
||||
slice::from_raw_parts(input.as_ptr(), nbytes)
|
||||
};
|
||||
|
||||
self.input_shm.write(real_input).unwrap();
|
||||
|
||||
let r = self
|
||||
.rpc
|
||||
.call(CallbackReq::Data(
|
||||
output.len() as isize,
|
||||
self.output_frame_size as usize,
|
||||
)).wait();
|
||||
|
||||
match r {
|
||||
Ok(CallbackResp::Data(frames)) => {
|
||||
if frames >= 0 {
|
||||
let nbytes = frames as usize * self.output_frame_size as usize;
|
||||
let real_output = unsafe {
|
||||
trace!("Resize output to {}", nbytes);
|
||||
slice::from_raw_parts_mut(output.as_mut_ptr(), nbytes)
|
||||
};
|
||||
self.output_shm.read(&mut real_output[..nbytes]).unwrap();
|
||||
}
|
||||
frames
|
||||
}
|
||||
_ => {
|
||||
debug!("Unexpected message {:?} during data_callback", r);
|
||||
-1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn state_callback(&mut self, state: cubeb::State) {
|
||||
trace!("Stream state callback: {:?}", state);
|
||||
let r = self.rpc.call(CallbackReq::State(state.into())).wait();
|
||||
match r {
|
||||
Ok(CallbackResp::State) => {}
|
||||
_ => {
|
||||
debug!("Unexpected message {:?} during callback", r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ServerStream {
|
||||
stream: ManuallyDrop<cubeb::Stream>,
|
||||
cbs: ManuallyDrop<Box<ServerStreamCallbacks>>,
|
||||
}
|
||||
|
||||
impl Drop for ServerStream {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
ManuallyDrop::drop(&mut self.stream);
|
||||
ManuallyDrop::drop(&mut self.cbs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type StreamSlab = slab::Slab<ServerStream, usize>;
|
||||
|
||||
pub struct CubebServer {
|
||||
cb_remote: Remote,
|
||||
streams: StreamSlab,
|
||||
}
|
||||
|
||||
impl rpc::Server for CubebServer {
|
||||
type Request = ServerMessage;
|
||||
type Response = ClientMessage;
|
||||
type Future = FutureResult<Self::Response, ()>;
|
||||
type Transport = FramedWithFds<UnixStream, LengthDelimitedCodec<Self::Response, Self::Request>>;
|
||||
|
||||
fn process(&mut self, req: Self::Request) -> Self::Future {
|
||||
let resp = with_local_context(|context| match *context {
|
||||
Err(_) => error(cubeb::Error::error()),
|
||||
Ok(ref context) => self.process_msg(context, &req),
|
||||
});
|
||||
future::ok(resp)
|
||||
}
|
||||
}
|
||||
|
||||
impl CubebServer {
|
||||
pub fn new(cb_remote: Remote) -> Self {
|
||||
CubebServer {
|
||||
cb_remote: cb_remote,
|
||||
streams: StreamSlab::with_capacity(STREAM_CONN_CHUNK_SIZE),
|
||||
}
|
||||
}
|
||||
|
||||
// Process a request coming from the client.
|
||||
fn process_msg(&mut self, context: &cubeb::Context, msg: &ServerMessage) -> ClientMessage {
|
||||
let resp: ClientMessage = match *msg {
|
||||
ServerMessage::ClientConnect => panic!("already connected"),
|
||||
|
||||
ServerMessage::ClientDisconnect => {
|
||||
// TODO:
|
||||
//self.connection.client_disconnect();
|
||||
ClientMessage::ClientDisconnected
|
||||
}
|
||||
|
||||
ServerMessage::ContextGetBackendId => ClientMessage::ContextBackendId(),
|
||||
|
||||
ServerMessage::ContextGetMaxChannelCount => context
|
||||
.max_channel_count()
|
||||
.map(ClientMessage::ContextMaxChannelCount)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::ContextGetMinLatency(ref params) => {
|
||||
let format = cubeb::SampleFormat::from(params.format);
|
||||
let layout = cubeb::ChannelLayout::from(params.layout);
|
||||
|
||||
let params = cubeb::StreamParamsBuilder::new()
|
||||
.format(format)
|
||||
.rate(u32::from(params.rate))
|
||||
.channels(u32::from(params.channels))
|
||||
.layout(layout)
|
||||
.take();
|
||||
|
||||
context
|
||||
.min_latency(¶ms)
|
||||
.map(ClientMessage::ContextMinLatency)
|
||||
.unwrap_or_else(error)
|
||||
}
|
||||
|
||||
ServerMessage::ContextGetPreferredSampleRate => context
|
||||
.preferred_sample_rate()
|
||||
.map(ClientMessage::ContextPreferredSampleRate)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::ContextGetDeviceEnumeration(device_type) => context
|
||||
.enumerate_devices(cubeb::DeviceType::from_bits_truncate(device_type))
|
||||
.map(|devices| {
|
||||
let v: Vec<DeviceInfo> = devices.iter().map(|i| i.as_ref().into()).collect();
|
||||
ClientMessage::ContextEnumeratedDevices(v)
|
||||
}).unwrap_or_else(error),
|
||||
|
||||
ServerMessage::StreamInit(ref params) => self
|
||||
.process_stream_init(context, params)
|
||||
.unwrap_or_else(|_| error(cubeb::Error::error())),
|
||||
|
||||
ServerMessage::StreamDestroy(stm_tok) => {
|
||||
self.streams.remove(stm_tok);
|
||||
ClientMessage::StreamDestroyed
|
||||
}
|
||||
|
||||
ServerMessage::StreamStart(stm_tok) => self.streams[stm_tok]
|
||||
.stream
|
||||
.start()
|
||||
.map(|_| ClientMessage::StreamStarted)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::StreamStop(stm_tok) => self.streams[stm_tok]
|
||||
.stream
|
||||
.stop()
|
||||
.map(|_| ClientMessage::StreamStopped)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::StreamResetDefaultDevice(stm_tok) => self.streams[stm_tok]
|
||||
.stream
|
||||
.reset_default_device()
|
||||
.map(|_| ClientMessage::StreamDefaultDeviceReset)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::StreamGetPosition(stm_tok) => self.streams[stm_tok]
|
||||
.stream
|
||||
.position()
|
||||
.map(ClientMessage::StreamPosition)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::StreamGetLatency(stm_tok) => self.streams[stm_tok]
|
||||
.stream
|
||||
.latency()
|
||||
.map(ClientMessage::StreamLatency)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::StreamSetVolume(stm_tok, volume) => self.streams[stm_tok]
|
||||
.stream
|
||||
.set_volume(volume)
|
||||
.map(|_| ClientMessage::StreamVolumeSet)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::StreamSetPanning(stm_tok, panning) => self.streams[stm_tok]
|
||||
.stream
|
||||
.set_panning(panning)
|
||||
.map(|_| ClientMessage::StreamPanningSet)
|
||||
.unwrap_or_else(error),
|
||||
|
||||
ServerMessage::StreamGetCurrentDevice(stm_tok) => self.streams[stm_tok]
|
||||
.stream
|
||||
.current_device()
|
||||
.map(|device| ClientMessage::StreamCurrentDevice(Device::from(device)))
|
||||
.unwrap_or_else(error),
|
||||
};
|
||||
|
||||
trace!("process_msg: req={:?}, resp={:?}", msg, resp);
|
||||
|
||||
resp
|
||||
}
|
||||
|
||||
// Stream init is special, so it's been separated from process_msg.
|
||||
fn process_stream_init(
|
||||
&mut self,
|
||||
context: &cubeb::Context,
|
||||
params: &StreamInitParams,
|
||||
) -> Result<ClientMessage> {
|
||||
fn frame_size_in_bytes(params: Option<&StreamParams>) -> u16 {
|
||||
params
|
||||
.map(|p| {
|
||||
let format = p.format.into();
|
||||
let sample_size = match format {
|
||||
cubeb::SampleFormat::S16LE
|
||||
| cubeb::SampleFormat::S16BE
|
||||
| cubeb::SampleFormat::S16NE => 2,
|
||||
cubeb::SampleFormat::Float32LE
|
||||
| cubeb::SampleFormat::Float32BE
|
||||
| cubeb::SampleFormat::Float32NE => 4,
|
||||
};
|
||||
let channel_count = p.channels as u16;
|
||||
sample_size * channel_count
|
||||
}).unwrap_or(0u16)
|
||||
}
|
||||
|
||||
// Create the callback handling struct which is attached the cubeb stream.
|
||||
let input_frame_size = frame_size_in_bytes(params.input_stream_params.as_ref());
|
||||
let output_frame_size = frame_size_in_bytes(params.output_stream_params.as_ref());
|
||||
|
||||
let (stm1, stm2) = net::UnixStream::pair()?;
|
||||
debug!("Created callback pair: {:?}-{:?}", stm1, stm2);
|
||||
let (input_shm, input_file) =
|
||||
SharedMemWriter::new(&audioipc::get_shm_path("input"), SHM_AREA_SIZE)?;
|
||||
let (output_shm, output_file) =
|
||||
SharedMemReader::new(&audioipc::get_shm_path("output"), SHM_AREA_SIZE)?;
|
||||
|
||||
// This code is currently running on the Client/Server RPC
|
||||
// handling thread. We need to move the registration of the
|
||||
// bind_client to the callback RPC handling thread. This is
|
||||
// done by spawning a future on cb_remote.
|
||||
|
||||
let id = core::handle().id();
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.cb_remote.spawn(move |handle| {
|
||||
// Ensure we're running on a loop different to the one
|
||||
// invoking spawn_fn.
|
||||
assert_ne!(id, handle.id());
|
||||
let stream = UnixStream::from_stream(stm2, handle).unwrap();
|
||||
let transport = framed(stream, Default::default());
|
||||
let rpc = rpc::bind_client::<CallbackClient>(transport, handle);
|
||||
drop(tx.send(rpc));
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let rpc: rpc::ClientProxy<CallbackReq, CallbackResp> = match rx.wait() {
|
||||
Ok(rpc) => rpc,
|
||||
Err(_) => bail!("Failed to create callback rpc."),
|
||||
};
|
||||
|
||||
let cbs = Box::new(ServerStreamCallbacks {
|
||||
input_frame_size,
|
||||
output_frame_size,
|
||||
input_shm,
|
||||
output_shm,
|
||||
rpc,
|
||||
});
|
||||
|
||||
// Create cubeb stream from params
|
||||
let stream_name = params
|
||||
.stream_name
|
||||
.as_ref()
|
||||
.and_then(|name| CStr::from_bytes_with_nul(name).ok());
|
||||
|
||||
let input_device = params.input_device as *const _;
|
||||
let input_stream_params = params.input_stream_params.as_ref().map(|isp| unsafe {
|
||||
cubeb::StreamParamsRef::from_ptr(isp as *const StreamParams as *mut _)
|
||||
});
|
||||
|
||||
let output_device = params.output_device as *const _;
|
||||
let output_stream_params = params.output_stream_params.as_ref().map(|osp| unsafe {
|
||||
cubeb::StreamParamsRef::from_ptr(osp as *const StreamParams as *mut _)
|
||||
});
|
||||
|
||||
let latency = params.latency_frames;
|
||||
assert!(size_of::<Box<ServerStreamCallbacks>>() == size_of::<usize>());
|
||||
let user_ptr = cbs.as_ref() as *const ServerStreamCallbacks as *mut c_void;
|
||||
|
||||
unsafe {
|
||||
context
|
||||
.stream_init(
|
||||
stream_name,
|
||||
input_device,
|
||||
input_stream_params,
|
||||
output_device,
|
||||
output_stream_params,
|
||||
latency,
|
||||
Some(data_cb_c),
|
||||
Some(state_cb_c),
|
||||
user_ptr,
|
||||
).and_then(|stream| {
|
||||
if !self.streams.has_available() {
|
||||
trace!(
|
||||
"server connection ran out of stream slots. reserving {} more.",
|
||||
STREAM_CONN_CHUNK_SIZE
|
||||
);
|
||||
self.streams.reserve_exact(STREAM_CONN_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
let stm_tok = match self.streams.vacant_entry() {
|
||||
Some(entry) => {
|
||||
debug!("Registering stream {:?}", entry.index(),);
|
||||
|
||||
entry
|
||||
.insert(ServerStream {
|
||||
stream: ManuallyDrop::new(stream),
|
||||
cbs: ManuallyDrop::new(cbs),
|
||||
}).index()
|
||||
}
|
||||
None => {
|
||||
// TODO: Turn into error
|
||||
panic!("Failed to insert stream into slab. No entries")
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ClientMessage::StreamCreated(StreamCreate {
|
||||
token: stm_tok,
|
||||
fds: [
|
||||
stm1.into_raw_fd(),
|
||||
input_file.into_raw_fd(),
|
||||
output_file.into_raw_fd(),
|
||||
],
|
||||
}))
|
||||
}).map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// C callable callbacks
|
||||
unsafe extern "C" fn data_cb_c(
|
||||
_: *mut ffi::cubeb_stream,
|
||||
user_ptr: *mut c_void,
|
||||
input_buffer: *const c_void,
|
||||
output_buffer: *mut c_void,
|
||||
nframes: c_long,
|
||||
) -> c_long {
|
||||
let ok = panic::catch_unwind(|| {
|
||||
let cbs = &mut *(user_ptr as *mut ServerStreamCallbacks);
|
||||
let input = if input_buffer.is_null() {
|
||||
&[]
|
||||
} else {
|
||||
slice::from_raw_parts(input_buffer as *const u8, nframes as usize)
|
||||
};
|
||||
let output: &mut [u8] = if output_buffer.is_null() {
|
||||
&mut []
|
||||
} else {
|
||||
slice::from_raw_parts_mut(output_buffer as *mut u8, nframes as usize)
|
||||
};
|
||||
cbs.data_callback(input, output) as c_long
|
||||
});
|
||||
ok.unwrap_or(0)
|
||||
}
|
||||
|
||||
unsafe extern "C" fn state_cb_c(
|
||||
_: *mut ffi::cubeb_stream,
|
||||
user_ptr: *mut c_void,
|
||||
state: ffi::cubeb_state,
|
||||
) {
|
||||
let ok = panic::catch_unwind(|| {
|
||||
let state = cubeb::State::from(state);
|
||||
let cbs = &mut *(user_ptr as *mut ServerStreamCallbacks);
|
||||
cbs.state_callback(state);
|
||||
});
|
||||
ok.expect("State callback panicked");
|
||||
}
|
@ -1573,5 +1573,19 @@ LoadInfo::GetPerformanceStorage()
|
||||
return mPerformanceStorage;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LoadInfo::GetCspEventListener(nsICSPEventListener** aCSPEventListener)
|
||||
{
|
||||
NS_IF_ADDREF(*aCSPEventListener = mCSPEventListener);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LoadInfo::SetCspEventListener(nsICSPEventListener* aCSPEventListener)
|
||||
{
|
||||
mCSPEventListener = aCSPEventListener;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
} // namespace mozilla
|
||||
|
@ -167,6 +167,7 @@ private:
|
||||
nsCOMPtr<nsIPrincipal> mTopLevelPrincipal;
|
||||
nsCOMPtr<nsIPrincipal> mTopLevelStorageAreaPrincipal;
|
||||
nsCOMPtr<nsIURI> mResultPrincipalURI;
|
||||
nsCOMPtr<nsICSPEventListener> mCSPEventListener;
|
||||
|
||||
Maybe<mozilla::dom::ClientInfo> mClientInfo;
|
||||
UniquePtr<mozilla::dom::ClientSource> mReservedClientSource;
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "nsIContentPolicy.idl"
|
||||
|
||||
interface nsIChannel;
|
||||
interface nsICSPEventListener;
|
||||
interface nsINode;
|
||||
interface nsIPrincipal;
|
||||
interface nsIRedirectHistoryEntry;
|
||||
@ -1072,4 +1073,11 @@ interface nsILoadInfo : nsISupports
|
||||
* load belongs had finished loading when the load was initiated.
|
||||
*/
|
||||
[infallible] attribute boolean documentHasLoaded;
|
||||
|
||||
/**
|
||||
* The object in charged to receive CSP violation events. It can be null.
|
||||
* This attribute will be merged into the CSP object eventually.
|
||||
* See bug 1500908.
|
||||
*/
|
||||
attribute nsICSPEventListener cspEventListener;
|
||||
};
|
||||
|
@ -15,7 +15,7 @@ interface nsIOSKeyStore: nsISupports {
|
||||
* Usage:
|
||||
*
|
||||
* // obtain the singleton OSKeyStore instance
|
||||
* const oskeystore = Cc["@mozilla.org/oskeystore;1"].getService(Ci.nsIOSKeyStore);
|
||||
* const oskeystore = Cc["@mozilla.org/security/oskeystore;1"].getService(Ci.nsIOSKeyStore);
|
||||
*
|
||||
* const PASSWORD_LABEL = "mylabel1";
|
||||
* const COOKIE_LABEL = "mylabel2";
|
||||
|
@ -14,8 +14,8 @@ ChromeUtils.import("resource://tps/logger.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "formAutofillStorage",
|
||||
"resource://formautofill/FormAutofillStorage.jsm");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "MasterPassword",
|
||||
"resource://formautofill/MasterPassword.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "OSKeyStore",
|
||||
"resource://formautofill/OSKeyStore.jsm");
|
||||
|
||||
class FormAutofillBase {
|
||||
constructor(props, subStorageName, fields) {
|
||||
@ -111,7 +111,7 @@ class CreditCard extends FormAutofillBase {
|
||||
async Find() {
|
||||
const storage = await this.getStorage();
|
||||
await Promise.all(storage._data.map(
|
||||
async entry => entry["cc-number"] = await MasterPassword.decrypt(entry["cc-number-encrypted"])));
|
||||
async entry => entry["cc-number"] = await OSKeyStore.decrypt(entry["cc-number-encrypted"])));
|
||||
return storage._data.find(entry => {
|
||||
return this._fields.every(field => entry[field] === this.props[field]);
|
||||
});
|
||||
|
@ -1,64 +1,4 @@
|
||||
[createImageBitmap-invalid-args.html]
|
||||
[createImageBitmap with a HTMLImageElement source and sw set to 0 rejects with a RangeError.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a HTMLImageElement source and sh set to 0 rejects with a RangeError.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a HTMLImageElement source and oversized (unallocatable) crop region rejects with an InvalidStateError DOMException.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a HTMLVideoElement source and sw set to 0 rejects with a RangeError.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a HTMLVideoElement source and sh set to 0 rejects with a RangeError.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a HTMLVideoElement source and oversized (unallocatable) crop region rejects with an InvalidStateError DOMException.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a HTMLCanvasElement source and sw set to 0 rejects with a RangeError.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a HTMLCanvasElement source and sh set to 0 rejects with a RangeError.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a HTMLCanvasElement source and oversized (unallocatable) crop region rejects with an InvalidStateError DOMException.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a OffscreenCanvas source and sw set to 0 rejects with a RangeError.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a OffscreenCanvas source and sh set to 0 rejects with a RangeError.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a OffscreenCanvas source and oversized (unallocatable) crop region rejects with an InvalidStateError DOMException.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a ImageData source and sw set to 0 rejects with a RangeError.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a ImageData source and sh set to 0 rejects with a RangeError.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a ImageData source and oversized (unallocatable) crop region rejects with an InvalidStateError DOMException.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a ImageBitmap source and sw set to 0 rejects with a RangeError.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a ImageBitmap source and sh set to 0 rejects with a RangeError.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a ImageBitmap source and oversized (unallocatable) crop region rejects with an InvalidStateError DOMException.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a Blob source and sw set to 0 rejects with a RangeError.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a Blob source and sh set to 0 rejects with a RangeError.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with an oversized canvas source rejects with a RangeError.]
|
||||
expected: FAIL
|
||||
|
||||
@ -71,64 +11,31 @@
|
||||
[createImageBitmap with an available but undecodable image source rejects with an InvalidStateError.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an HTMLCanvasElement source and sw set to 0]
|
||||
[createImageBitmap with an HTMLCanvasElement source and oversized (unallocatable) crop region]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an HTMLCanvasElement source and sh set to 0]
|
||||
[createImageBitmap with an HTMLVideoElement source and oversized (unallocatable) crop region]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an HTMLCanvasElement source and oversized (unallocatable) crop region]
|
||||
[createImageBitmap with an HTMLImageElement source and sw set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an HTMLVideoElement source and sw set to 0]
|
||||
[createImageBitmap with an HTMLImageElement source and oversized (unallocatable) crop region]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an HTMLVideoElement source and sh set to 0]
|
||||
[createImageBitmap with an OffscreenCanvas source and sw set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an HTMLVideoElement source and oversized (unallocatable) crop region]
|
||||
[createImageBitmap with an OffscreenCanvas source and sh set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an HTMLImageElement source and sw set to 0]
|
||||
[createImageBitmap with an OffscreenCanvas source and oversized (unallocatable) crop region]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an HTMLImageElement source and sh set to 0]
|
||||
[createImageBitmap with an ImageData source and oversized (unallocatable) crop region]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an HTMLImageElement source and oversized (unallocatable) crop region]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an OffscreenCanvas source and sw set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an OffscreenCanvas source and sh set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an OffscreenCanvas source and oversized (unallocatable) crop region]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an ImageData source and sw set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an ImageData source and sh set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an ImageData source and oversized (unallocatable) crop region]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an ImageBitmap source and sw set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an ImageBitmap source and sh set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an ImageBitmap source and oversized (unallocatable) crop region]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a a Blob source and sw set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a a Blob source and sh set to 0]
|
||||
[createImageBitmap with an ImageBitmap source and oversized (unallocatable) crop region]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with an oversized canvas source.]
|
||||
@ -146,37 +53,25 @@
|
||||
[createImageBitmap with a closed ImageBitmap.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a a bitmap HTMLImageElement source and sw set to 0]
|
||||
[createImageBitmap with a bitmap HTMLImageElement source and oversized (unallocatable) crop region]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a a bitmap HTMLImageElement source and sh set to 0]
|
||||
[createImageBitmap with a bitmap SVGImageElement source and sw set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a a bitmap HTMLImageElement source and oversized (unallocatable) crop region]
|
||||
[createImageBitmap with a bitmap SVGImageElement source and sh set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a a vector HTMLImageElement source and sw set to 0]
|
||||
[createImageBitmap with a bitmap SVGImageElement source and oversized (unallocatable) crop region]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a a vector HTMLImageElement source and sh set to 0]
|
||||
[createImageBitmap with a vector SVGImageElement source and sw set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a a bitmap SVGImageElement source and sw set to 0]
|
||||
[createImageBitmap with a vector SVGImageElement source and sh set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a a bitmap SVGImageElement source and sh set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a a bitmap SVGImageElement source and oversized (unallocatable) crop region]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a a vector SVGImageElement source and sw set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a a vector SVGImageElement source and sh set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a a vector SVGImageElement source and oversized (unallocatable) crop region]
|
||||
[createImageBitmap with a vector SVGImageElement source and oversized (unallocatable) crop region]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with CanvasRenderingContext2D image source.]
|
||||
@ -188,12 +83,6 @@
|
||||
[createImageBitmap with ArrayBuffer image source.]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an HTMLVideoElement from a data URL source and sw set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an HTMLVideoElement from a data URL source and sh set to 0]
|
||||
expected: FAIL
|
||||
|
||||
[createImageBitmap with a an HTMLVideoElement from a data URL source and oversized (unallocatable) crop region]
|
||||
[createImageBitmap with an HTMLVideoElement from a data URL source and oversized (unallocatable) crop region]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -49,7 +49,7 @@ function makeAvailableButBrokenImage(path) {
|
||||
|
||||
testCases = [
|
||||
{
|
||||
description: 'createImageBitmap with a <sourceType> source and sw set to 0',
|
||||
description: 'createImageBitmap with <sourceType> source and sw set to 0',
|
||||
promiseTestFunction:
|
||||
(source, t) => {
|
||||
return promise_rejects(t, new RangeError(),
|
||||
@ -57,7 +57,7 @@ testCases = [
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'createImageBitmap with a <sourceType> source and sh set to 0',
|
||||
description: 'createImageBitmap with <sourceType> source and sh set to 0',
|
||||
promiseTestFunction:
|
||||
(source, t) => {
|
||||
return promise_rejects(t, new RangeError(),
|
||||
@ -70,7 +70,7 @@ testCases = [
|
||||
// InvalidStateError.
|
||||
//
|
||||
// Note: https://bugs.chromium.org/p/chromium/issues/detail?id=799025
|
||||
description: 'createImageBitmap with a <sourceType> source and oversized ' +
|
||||
description: 'createImageBitmap with <sourceType> source and oversized ' +
|
||||
'(unallocatable) crop region',
|
||||
promiseTestFunction:
|
||||
(source, t) => {
|
||||
|
@ -142,7 +142,11 @@ function testCompositor(test, win, ctx) {
|
||||
// a device reset so the screen redraws.
|
||||
if (Services.prefs.getBoolPref(AL_ENABLED_PREF, false)) {
|
||||
Services.prefs.setBoolPref(AL_TEST_FAILED_PREF, true);
|
||||
test.utils.triggerDeviceReset();
|
||||
// Do not need to reset device when WebRender is used.
|
||||
// When WebRender is used, advanced layers are not used.
|
||||
if (test.utils.layerManagerType != "WebRender") {
|
||||
test.utils.triggerDeviceReset();
|
||||
}
|
||||
}
|
||||
reportResult(TEST_FAILED_RENDER);
|
||||
testPassed = false;
|
||||
|
@ -6,8 +6,8 @@
|
||||
|
||||
var EXPORTED_SYMBOLS = ["CreditCard"];
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "MasterPassword",
|
||||
"resource://formautofill/MasterPassword.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "OSKeyStore",
|
||||
"resource://formautofill/OSKeyStore.jsm");
|
||||
|
||||
// The list of known and supported credit card network ids ("types")
|
||||
// This list mirrors the networks from dom/payments/BasicCardPayment.cpp
|
||||
@ -208,7 +208,13 @@ class CreditCard {
|
||||
|
||||
if (showNumbers) {
|
||||
if (this._encryptedNumber) {
|
||||
label = await MasterPassword.decrypt(this._encryptedNumber);
|
||||
try {
|
||||
label = await OSKeyStore.decrypt(this._encryptedNumber);
|
||||
} catch (ex) {
|
||||
// Quietly recover from decryption error.
|
||||
label = this._number;
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
} else {
|
||||
label = this._number;
|
||||
}
|
||||
|
@ -4,38 +4,35 @@
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/CreditCard.jsm");
|
||||
ChromeUtils.import("resource://formautofill/MasterPassword.jsm");
|
||||
ChromeUtils.import("resource://formautofill/OSKeyStore.jsm");
|
||||
ChromeUtils.import("resource://testing-common/OSKeyStoreTestUtils.jsm");
|
||||
|
||||
let oldGetters = {};
|
||||
let gFakeLoggedIn = true;
|
||||
|
||||
add_task(function setup() {
|
||||
oldGetters._token = Object.getOwnPropertyDescriptor(MasterPassword, "_token").get;
|
||||
oldGetters.isEnabled = Object.getOwnPropertyDescriptor(MasterPassword, "isEnabled").get;
|
||||
oldGetters.isLoggedIn = Object.getOwnPropertyDescriptor(MasterPassword, "isLoggedIn").get;
|
||||
MasterPassword.__defineGetter__("_token", () => { return {hasPassword: true}; });
|
||||
MasterPassword.__defineGetter__("isEnabled", () => true);
|
||||
MasterPassword.__defineGetter__("isLoggedIn", () => gFakeLoggedIn);
|
||||
registerCleanupFunction(() => {
|
||||
MasterPassword.__defineGetter__("_token", oldGetters._token);
|
||||
MasterPassword.__defineGetter__("isEnabled", oldGetters.isEnabled);
|
||||
MasterPassword.__defineGetter__("isLoggedIn", oldGetters.isLoggedIn);
|
||||
OSKeyStoreTestUtils.setup();
|
||||
oldGetters.isLoggedIn = Object.getOwnPropertyDescriptor(OSKeyStore, "isLoggedIn").get;
|
||||
OSKeyStore.__defineGetter__("isLoggedIn", () => gFakeLoggedIn);
|
||||
registerCleanupFunction(async () => {
|
||||
OSKeyStore.__defineGetter__("isLoggedIn", oldGetters.isLoggedIn);
|
||||
await OSKeyStoreTestUtils.cleanup();
|
||||
|
||||
// CreditCard.jsm and MasterPassword.jsm are imported into the global scope
|
||||
// -- the window -- above. If they're not deleted, they outlive the test and
|
||||
// are reported as a leak.
|
||||
delete window.MasterPassword;
|
||||
// CreditCard.jsm, OSKeyStore.jsm, and OSKeyStoreTestUtils.jsm are imported
|
||||
// into the global scope -- the window -- above. If they're not deleted,
|
||||
// they outlive the test and are reported as a leak.
|
||||
delete window.OSKeyStore;
|
||||
delete window.CreditCard;
|
||||
delete window.OSKeyStoreTestUtils;
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_getLabel_withMasterPassword() {
|
||||
ok(MasterPassword.isEnabled, "Confirm that MasterPassword is faked and thinks it is enabled");
|
||||
ok(MasterPassword.isLoggedIn, "Confirm that MasterPassword is faked and thinks it is logged in");
|
||||
add_task(async function test_getLabel_withOSKeyStore() {
|
||||
ok(OSKeyStore.isLoggedIn, "Confirm that OSKeyStore is faked and thinks it is logged in");
|
||||
|
||||
const ccNumber = "4111111111111111";
|
||||
const encryptedNumber = await MasterPassword.encrypt(ccNumber);
|
||||
const decryptedNumber = await MasterPassword.decrypt(encryptedNumber);
|
||||
const encryptedNumber = await OSKeyStore.encrypt(ccNumber);
|
||||
const decryptedNumber = await OSKeyStore.decrypt(encryptedNumber);
|
||||
is(decryptedNumber, ccNumber, "Decrypted CC number should match original");
|
||||
|
||||
const name = "Foxkeh";
|
||||
|
Loading…
Reference in New Issue
Block a user