mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-19 00:05:36 +00:00
Merge mozilla-central to autoland. a=merge CLOSED TREE
This commit is contained in:
commit
c41508657f
@ -19,6 +19,16 @@
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="mozglue"
|
||||
version="1.0.0.0"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<ms_asmv3:security>
|
||||
<ms_asmv3:requestedPrivileges>
|
||||
|
@ -16,8 +16,8 @@ ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "OSKeyStore",
|
||||
"resource://formautofill/OSKeyStore.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "MasterPassword",
|
||||
"resource://formautofill/MasterPassword.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
@ -149,7 +149,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 there is an error decrypting
|
||||
* @throws if the user cancels entering their master password or an error decrypting
|
||||
* @returns {nsIBasicCardResponseData?} returns response data or null (if the
|
||||
* master password dialog was cancelled);
|
||||
*/
|
||||
@ -162,7 +162,7 @@ var paymentDialogWrapper = {
|
||||
|
||||
let cardNumber;
|
||||
try {
|
||||
cardNumber = await OSKeyStore.decrypt(cardData["cc-number-encrypted"], true);
|
||||
cardNumber = await MasterPassword.decrypt(cardData["cc-number-encrypted"], true);
|
||||
} catch (ex) {
|
||||
if (ex.result != Cr.NS_ERROR_ABORT) {
|
||||
throw ex;
|
||||
@ -503,16 +503,8 @@ var paymentDialogWrapper = {
|
||||
selectedPaymentCardGUID: paymentCardGUID,
|
||||
selectedPaymentCardSecurityCode: 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;
|
||||
}
|
||||
let methodData = await this._convertProfileBasicCardToPaymentMethodData(paymentCardGUID,
|
||||
cardSecurityCode);
|
||||
|
||||
if (!methodData) {
|
||||
// TODO (Bug 1429265/Bug 1429205): Handle when a user hits cancel on the
|
||||
|
@ -318,7 +318,7 @@ let BASIC_CARDS_1 = {
|
||||
methodName: "basic-card",
|
||||
"cc-number": "************5461",
|
||||
"guid": "53f9d009aed2",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"timeCreated": 1505240896213,
|
||||
"timeLastModified": 1515609524588,
|
||||
"timeLastUsed": 0,
|
||||
@ -336,7 +336,7 @@ let BASIC_CARDS_1 = {
|
||||
methodName: "basic-card",
|
||||
"cc-number": "************0954",
|
||||
"guid": "9h5d4h6f4d1s",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"timeCreated": 1517890536491,
|
||||
"timeLastModified": 1517890564518,
|
||||
"timeLastUsed": 0,
|
||||
@ -354,7 +354,7 @@ let BASIC_CARDS_1 = {
|
||||
methodName: "basic-card",
|
||||
"cc-number": "************1234",
|
||||
"guid": "123456789abc",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"timeCreated": 1517890536491,
|
||||
"timeLastModified": 1517890564518,
|
||||
"timeLastUsed": 0,
|
||||
@ -388,7 +388,7 @@ let BASIC_CARDS_1 = {
|
||||
methodName: "basic-card",
|
||||
"cc-number": "************8563",
|
||||
"guid": "missing-cc-name",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"timeCreated": 1517890536491,
|
||||
"timeLastModified": 1517890564518,
|
||||
"timeLastUsed": 0,
|
||||
|
@ -30,6 +30,7 @@ ac_add_options --target=i686-w64-mingw32
|
||||
ac_add_options --with-toolchain-prefix=i686-w64-mingw32-
|
||||
|
||||
ac_add_options --disable-warnings-as-errors
|
||||
MOZ_COPY_PDBS=1
|
||||
|
||||
# Temporary config settings until we get these working on mingw
|
||||
ac_add_options --disable-accessibility # https://sourceforge.net/p/mingw-w64/bugs/648/
|
||||
|
@ -30,6 +30,7 @@ ac_add_options --target=x86_64-w64-mingw32
|
||||
ac_add_options --with-toolchain-prefix=x86_64-w64-mingw32-
|
||||
|
||||
ac_add_options --disable-warnings-as-errors
|
||||
MOZ_COPY_PDBS=1
|
||||
|
||||
# Temporary config settings until we get these working on mingw
|
||||
ac_add_options --disable-accessibility # https://sourceforge.net/p/mingw-w64/bugs/648/
|
||||
|
@ -42,7 +42,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
FormAutofillPreferences: "resource://formautofill/FormAutofillPreferences.jsm",
|
||||
FormAutofillDoorhanger: "resource://formautofill/FormAutofillDoorhanger.jsm",
|
||||
FormAutofillUtils: "resource://formautofill/FormAutofillUtils.jsm",
|
||||
OSKeyStore: "resource://formautofill/OSKeyStore.jsm",
|
||||
MasterPassword: "resource://formautofill/MasterPassword.jsm",
|
||||
});
|
||||
|
||||
this.log = null;
|
||||
@ -225,8 +225,8 @@ FormAutofillParent.prototype = {
|
||||
break;
|
||||
}
|
||||
case "FormAutofill:SaveCreditCard": {
|
||||
if (!await OSKeyStore.ensureLoggedIn()) {
|
||||
log.warn("User canceled encryption login");
|
||||
if (!await MasterPassword.ensureLoggedIn()) {
|
||||
log.warn("User canceled master password entry");
|
||||
return;
|
||||
}
|
||||
await this.formAutofillStorage.creditCards.add(data.creditcard);
|
||||
@ -253,12 +253,12 @@ FormAutofillParent.prototype = {
|
||||
let {cipherText, reauth} = data;
|
||||
let string;
|
||||
try {
|
||||
string = await OSKeyStore.decrypt(cipherText, reauth);
|
||||
string = await MasterPassword.decrypt(cipherText, reauth);
|
||||
} catch (e) {
|
||||
if (e.result != Cr.NS_ERROR_ABORT) {
|
||||
throw e;
|
||||
}
|
||||
log.warn("User canceled encryption login");
|
||||
log.warn("User canceled master password entry");
|
||||
}
|
||||
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 OSKeyStore isn't set.
|
||||
* "cc-number-decrypted" to each record if MasterPassword isn't set.
|
||||
*
|
||||
* @private
|
||||
* @param {string} data.collectionName
|
||||
@ -317,9 +317,9 @@ FormAutofillParent.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
let isCC = collectionName == CREDITCARDS_COLLECTION_NAME;
|
||||
// We don't filter "cc-number"
|
||||
if (isCC && info.fieldName == "cc-number") {
|
||||
let isCCAndMPEnabled = collectionName == CREDITCARDS_COLLECTION_NAME && MasterPassword.isEnabled;
|
||||
// We don't filter "cc-number" when MasterPassword is set.
|
||||
if (isCCAndMPEnabled && info.fieldName == "cc-number") {
|
||||
recordsInCollection = recordsInCollection.filter(record => !!record["cc-number"]);
|
||||
target.sendAsyncMessage("FormAutofill:Records", recordsInCollection);
|
||||
return;
|
||||
@ -334,6 +334,17 @@ 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
|
||||
@ -527,8 +538,8 @@ FormAutofillParent.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await OSKeyStore.ensureLoggedIn()) {
|
||||
log.warn("User canceled encryption login");
|
||||
if (!await MasterPassword.ensureLoggedIn()) {
|
||||
log.warn("User canceled master password entry");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -142,14 +142,11 @@ ChromeUtils.defineModuleGetter(this, "FormAutofillNameUtils",
|
||||
"resource://formautofill/FormAutofillNameUtils.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "FormAutofillUtils",
|
||||
"resource://formautofill/FormAutofillUtils.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "OSKeyStore",
|
||||
"resource://formautofill/OSKeyStore.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "MasterPassword",
|
||||
"resource://formautofill/MasterPassword.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");
|
||||
@ -161,7 +158,7 @@ const PROFILE_JSON_FILE_NAME = "autofill-profiles.json";
|
||||
|
||||
const STORAGE_SCHEMA_VERSION = 1;
|
||||
const ADDRESS_SCHEMA_VERSION = 1;
|
||||
const CREDIT_CARD_SCHEMA_VERSION = 2;
|
||||
const CREDIT_CARD_SCHEMA_VERSION = 1;
|
||||
|
||||
const VALID_ADDRESS_FIELDS = [
|
||||
"given-name",
|
||||
@ -267,14 +264,13 @@ class AutofillRecords {
|
||||
this._collectionName = collectionName;
|
||||
this._schemaVersion = schemaVersion;
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
Promise.all(this._data.map(record => this._migrateRecord(record)))
|
||||
.then(hasChangesArr => {
|
||||
let dataHasChanges = hasChangesArr.find(hasChanges => hasChanges);
|
||||
if (dataHasChanges) {
|
||||
this._store.saveSoon();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -307,14 +303,6 @@ class AutofillRecords {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the records in the collection, resolves when the migration completes.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
initialize() {
|
||||
return this._initializePromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new record.
|
||||
*
|
||||
@ -1188,7 +1176,7 @@ class AutofillRecords {
|
||||
});
|
||||
}
|
||||
|
||||
async _migrateRecord(record, index) {
|
||||
async _migrateRecord(record) {
|
||||
let hasChanges = false;
|
||||
|
||||
if (record.deleted) {
|
||||
@ -1204,21 +1192,10 @@ class AutofillRecords {
|
||||
|
||||
if (record.version < this.version) {
|
||||
hasChanges = true;
|
||||
record.version = this.version;
|
||||
|
||||
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;
|
||||
// Force to recompute fields if we upgrade the schema.
|
||||
await this._stripComputedFields(record);
|
||||
}
|
||||
|
||||
hasChanges |= await this.computeFields(record);
|
||||
@ -1279,24 +1256,6 @@ 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]);
|
||||
}
|
||||
@ -1645,7 +1604,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 OSKeyStore.encrypt(ccNumber);
|
||||
creditCard["cc-number-encrypted"] = await MasterPassword.encrypt(ccNumber);
|
||||
} else {
|
||||
creditCard["cc-number-encrypted"] = "";
|
||||
}
|
||||
@ -1654,63 +1613,9 @@ 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"]) {
|
||||
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.
|
||||
}
|
||||
creditCard["cc-number"] = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
|
||||
}
|
||||
await super._stripComputedFields(creditCard);
|
||||
}
|
||||
@ -1771,33 +1676,6 @@ 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
|
||||
@ -1814,9 +1692,12 @@ class CreditCards extends AutofillRecords {
|
||||
return !creditCard[field];
|
||||
}
|
||||
if (field == "cc-number" && creditCard[field]) {
|
||||
// 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];
|
||||
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"]));
|
||||
}
|
||||
return clonedTargetCreditCard[field] == creditCard[field];
|
||||
})).then(fieldResults => fieldResults.every(result => result));
|
||||
@ -1924,10 +1805,7 @@ FormAutofillStorage.prototype = {
|
||||
path: this._path,
|
||||
dataPostProcessor: this._dataPostProcessor.bind(this),
|
||||
});
|
||||
this._initializePromise = this._store.load()
|
||||
.then(() => Promise.all([
|
||||
this.addresses.initialize(),
|
||||
this.creditCards.initialize()]));
|
||||
this._initializePromise = this._store.load();
|
||||
}
|
||||
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"];
|
||||
const MANAGE_CREDITCARDS_KEYWORDS = ["manageCreditCardsTitle", "addNewCreditCardTitle", "showCreditCardsBtnLabel"];
|
||||
const EDIT_CREDITCARD_KEYWORDS = ["cardNumber", "nameOnCard", "cardExpiresMonth", "cardExpiresYear", "cardNetwork"];
|
||||
const FIELD_STATES = {
|
||||
NORMAL: "NORMAL",
|
||||
|
184
browser/extensions/formautofill/MasterPassword.jsm
Normal file
184
browser/extensions/formautofill/MasterPassword.jsm
Normal file
@ -0,0 +1,184 @@
|
||||
/* 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",
|
||||
});
|
||||
});
|
@ -1,251 +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 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,6 +20,7 @@
|
||||
</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"/>
|
||||
@ -33,6 +34,7 @@
|
||||
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, "OSKeyStore",
|
||||
"resource://formautofill/OSKeyStore.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "MasterPassword",
|
||||
"resource://formautofill/MasterPassword.jsm");
|
||||
|
||||
this.log = null;
|
||||
FormAutofill.defineLazyLogGetter(this, "manageAddresses");
|
||||
@ -313,7 +313,11 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -322,24 +326,12 @@ class ManageCreditCards extends ManageRecords {
|
||||
* @param {object} creditCard [optional]
|
||||
*/
|
||||
async openEditDialog(creditCard) {
|
||||
// Ask for reauth if user is trying to edit an existing credit card.
|
||||
if (!creditCard || await OSKeyStore.ensureLoggedIn(true)) {
|
||||
// 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)) {
|
||||
let decryptedCCNumObj = {};
|
||||
if (creditCard) {
|
||||
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);
|
||||
}
|
||||
decryptedCCNumObj["cc-number"] = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
|
||||
}
|
||||
let decryptedCreditCard = Object.assign({}, creditCard, decryptedCCNumObj);
|
||||
this.prefWin.gSubDialog.open(EDIT_CREDIT_CARD_URL, "resizable=no", {
|
||||
@ -367,6 +359,12 @@ 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);
|
||||
@ -394,10 +392,25 @@ 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,6 +104,8 @@ 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,8 +32,6 @@ 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,8 +15,7 @@ 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_cancel_login.js]
|
||||
skip-if = true # Re-auth is not implemented, cannot cancel OS key store login (bug 1429265)
|
||||
[browser_creditCard_fill_master_password.js]
|
||||
[browser_dropdown_layout.js]
|
||||
[browser_editAddressDialog.js]
|
||||
[browser_editCreditCardDialog.js]
|
||||
|
@ -51,7 +51,6 @@ 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");
|
||||
@ -70,7 +69,6 @@ add_task(async function test_submit_creditCard_saved() {
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
await onChanged;
|
||||
}
|
||||
);
|
||||
|
||||
@ -84,11 +82,6 @@ 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],
|
||||
@ -97,16 +90,11 @@ 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");
|
||||
|
||||
@ -119,7 +107,6 @@ 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");
|
||||
@ -138,9 +125,6 @@ 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,
|
||||
@ -165,7 +149,6 @@ 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");
|
||||
@ -291,6 +274,78 @@ 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": [
|
||||
@ -390,8 +445,6 @@ 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,
|
||||
@ -414,7 +467,6 @@ 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");
|
||||
@ -425,11 +477,6 @@ 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],
|
||||
@ -438,9 +485,6 @@ 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,
|
||||
@ -448,13 +492,7 @@ 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");
|
||||
@ -463,11 +501,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");
|
||||
@ -479,11 +517,6 @@ 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],
|
||||
@ -492,9 +525,6 @@ 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,
|
||||
@ -503,11 +533,6 @@ 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");
|
||||
@ -519,10 +544,8 @@ 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");
|
||||
@ -534,11 +557,6 @@ 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],
|
||||
@ -547,21 +565,14 @@ 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");
|
||||
@ -573,8 +584,6 @@ add_task(async function test_create_new_autofill_form() {
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(SECONDARY_BUTTON);
|
||||
await osKeyStoreLoginShown;
|
||||
await onChanged;
|
||||
}
|
||||
);
|
||||
|
||||
@ -589,11 +598,6 @@ 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],
|
||||
@ -607,24 +611,16 @@ 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
|
||||
@ -634,10 +630,8 @@ 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");
|
||||
@ -652,7 +646,6 @@ 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");
|
||||
@ -671,7 +664,6 @@ 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_but_cancel_login() {
|
||||
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
|
||||
todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
add_task(async function test_fill_creditCard_with_mp_enabled_but_canceled() {
|
||||
await saveCreditCard(TEST_CREDIT_CARD_2);
|
||||
|
||||
let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(false); // cancel
|
||||
LoginTestUtils.masterPassword.enable();
|
||||
registerCleanupFunction(() => {
|
||||
LoginTestUtils.masterPassword.disable();
|
||||
});
|
||||
|
||||
let masterPasswordDialogShown = waitForMasterPasswordDialog(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([osKeyStoreLoginShown, expectPopupClose(browser)]);
|
||||
await Promise.all([masterPasswordDialogShown, expectPopupClose(browser)]);
|
||||
|
||||
await ContentTask.spawn(browser, {}, async function() {
|
||||
is(content.document.querySelector("#cc-name").value, "", "Check name");
|
@ -3,6 +3,7 @@
|
||||
const TEST_SELECTORS = {
|
||||
selRecords: "#credit-cards",
|
||||
btnRemove: "#remove",
|
||||
btnShowHideCreditCards: "#show-hide-credit-cards",
|
||||
btnAdd: "#add",
|
||||
btnEdit: "#edit",
|
||||
};
|
||||
@ -14,11 +15,13 @@ 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");
|
||||
});
|
||||
@ -104,6 +107,57 @@ 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);
|
||||
@ -139,41 +193,35 @@ 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 btnEdit = win.document.querySelector(TEST_SELECTORS.btnEdit);
|
||||
let masterPasswordDialogShown = waitForMasterPasswordDialog();
|
||||
|
||||
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);
|
||||
|
||||
// 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));
|
||||
*/
|
||||
await masterPasswordDialogShown;
|
||||
|
||||
// Login is not required for removing credit cards.
|
||||
// Master password 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 OS login dialog is required.
|
||||
// no master password 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,
|
||||
getDisplayedPopupItems, getDoorhangerCheckbox, waitForMasterPasswordDialog,
|
||||
getNotification, getDoorhangerButton, removeAllRecords, testDialog */
|
||||
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.import("resource://formautofill/OSKeyStore.jsm", this);
|
||||
ChromeUtils.import("resource://testing-common/OSKeyStoreTestUtils.jsm", this);
|
||||
ChromeUtils.import("resource://testing-common/LoginTestUtils.jsm", this);
|
||||
ChromeUtils.import("resource://formautofill/MasterPassword.jsm", this);
|
||||
|
||||
const MANAGE_ADDRESSES_DIALOG_URL = "chrome://formautofill/content/manageAddresses.xhtml";
|
||||
const MANAGE_CREDIT_CARDS_DIALOG_URL = "chrome://formautofill/content/manageCreditCards.xhtml";
|
||||
@ -22,7 +22,8 @@ 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";
|
||||
@ -325,6 +326,24 @@ 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) {
|
||||
@ -347,7 +366,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 OSKeyStore.decrypt(arg.record["cc-number-encrypted"]),
|
||||
"cc-number": await MasterPassword.decrypt(arg.record["cc-number-encrypted"]),
|
||||
});
|
||||
}
|
||||
let win = window.openDialog(url, null, "width=600,height=600", arg);
|
||||
@ -357,11 +376,4 @@ async function testDialog(url, testFn, arg = undefined) {
|
||||
return unloadPromise;
|
||||
}
|
||||
|
||||
add_task(function setup() {
|
||||
OSKeyStoreTestUtils.setup();
|
||||
});
|
||||
|
||||
registerCleanupFunction(removeAllRecords);
|
||||
registerCleanupFunction(async () => {
|
||||
await OSKeyStoreTestUtils.cleanup();
|
||||
});
|
||||
|
@ -1,93 +0,0 @@
|
||||
/* 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,6 +1,5 @@
|
||||
/* 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 */
|
||||
|
||||
@ -202,15 +201,6 @@ 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 = {
|
||||
@ -273,12 +263,6 @@ 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,11 +4,8 @@
|
||||
|
||||
"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", {});
|
||||
|
||||
@ -117,15 +114,9 @@ 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");
|
||||
},
|
||||
|
||||
@ -224,23 +215,8 @@ addMessageListener("FormAutofillTest:CleanUpCreditCards", (msg) => {
|
||||
ParentUtils.cleanUpCreditCards();
|
||||
});
|
||||
|
||||
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", {});
|
||||
addMessageListener("cleanup", () => {
|
||||
ParentUtils.cleanup().then(() => {
|
||||
sendAsyncMessage("cleanup-finished", {});
|
||||
});
|
||||
});
|
||||
|
@ -150,16 +150,8 @@ 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");
|
||||
@ -170,18 +162,11 @@ 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();
|
||||
@ -190,10 +175,6 @@ 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,16 +103,9 @@ 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(await profileStorage[collectionName].add(record));
|
||||
Assert.ok(profileStorage[collectionName].add(record));
|
||||
await onChanged;
|
||||
}
|
||||
await profileStorage._saveImmediately();
|
||||
@ -223,13 +223,3 @@ 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,9 +5,10 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
let MasterPassword;
|
||||
add_task(async function setup() {
|
||||
ChromeUtils.import("resource://formautofill/FormAutofillHandler.jsm");
|
||||
ChromeUtils.import("resource://formautofill/OSKeyStore.jsm");
|
||||
({MasterPassword} = ChromeUtils.import("resource://formautofill/MasterPassword.jsm", {}));
|
||||
});
|
||||
|
||||
const TESTCASES = [
|
||||
@ -476,7 +477,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 OSKeyStore.encrypt(ccNumber);
|
||||
testcase.profileData["cc-number-encrypted"] = await MasterPassword.encrypt(ccNumber);
|
||||
delete testcase.profileData["cc-number"];
|
||||
}
|
||||
|
||||
@ -486,11 +487,18 @@ function do_test(testcases, testFn) {
|
||||
let formLike = FormLikeFactory.createFromForm(form);
|
||||
let handler = new FormAutofillHandler(formLike);
|
||||
let promises = [];
|
||||
// Replace the internal decrypt method with OSKeyStore API,
|
||||
// but don't pass the reauth parameter to avoid triggering
|
||||
// reauth login dialog in these tests.
|
||||
// Replace the interal decrypt method with MasterPassword API
|
||||
let decryptHelper = async (cipherText, reauth) => {
|
||||
return OSKeyStore.decrypt(cipherText, false);
|
||||
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;
|
||||
};
|
||||
|
||||
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, 2);
|
||||
Assert.equal(creditCards[0].version, 1);
|
||||
Assert.notEqual(creditCards[0].timeCreated, undefined);
|
||||
Assert.equal(creditCards[0].timeLastModified, creditCards[0].timeCreated);
|
||||
Assert.equal(creditCards[0].timeLastUsed, 0);
|
||||
@ -422,14 +422,6 @@ 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\./
|
||||
);
|
||||
@ -663,12 +655,18 @@ 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);
|
||||
|
||||
// We treat numbers with the same last 4 digits as a duplicate.
|
||||
// ... 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");
|
||||
Assert.equal(await profileStorage.creditCards.getDuplicateGuid(record), guid);
|
||||
|
||||
// Even though the last 4 digits are the same, an invalid credit card number
|
||||
// should never be treated as a duplicate.
|
||||
// ... 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.
|
||||
record["cc-number"] = "************" + last4Digits;
|
||||
Assert.equal(await profileStorage.creditCards.getDuplicateGuid(record), null);
|
||||
});
|
||||
|
@ -7,10 +7,9 @@
|
||||
ChromeUtils.import("resource://gre/modules/CreditCard.jsm");
|
||||
|
||||
let FormAutofillParent;
|
||||
let OSKeyStore;
|
||||
add_task(async function setup() {
|
||||
({FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.jsm", {}));
|
||||
({OSKeyStore} = ChromeUtils.import("resource://formautofill/OSKeyStore.jsm", {}));
|
||||
ChromeUtils.import("resource://formautofill/MasterPassword.jsm");
|
||||
});
|
||||
|
||||
const TEST_ADDRESS_1 = {
|
||||
@ -177,24 +176,37 @@ 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 OSKeyStore.encrypt(record["cc-number"]);
|
||||
clonedRecord["cc-number-encrypted"] = await MasterPassword.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 multiple creditCards",
|
||||
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)",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-name"},
|
||||
searchString: "John",
|
||||
},
|
||||
expectedResult: encryptedCCRecords,
|
||||
expectedResult: CreditCardsWithDecryptedNumber,
|
||||
},
|
||||
{
|
||||
description: "If the search string could not match any creditCard",
|
||||
description: "If the search string could not match any creditCard (without masterpassword)",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-name"},
|
||||
@ -203,17 +215,25 @@ add_task(async function test_getRecords_creditCards() {
|
||||
expectedResult: [],
|
||||
},
|
||||
{
|
||||
description: "Return all creditCards if focused field is cc number; " +
|
||||
"if the search string could match multiple creditCards",
|
||||
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)",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-number"},
|
||||
searchString: "4",
|
||||
},
|
||||
expectedResult: encryptedCCRecords,
|
||||
expectedResult: CreditCardsWithDecryptedNumber,
|
||||
},
|
||||
{
|
||||
description: "If the search string could match 1 creditCard",
|
||||
description: "If the search string could match 1 creditCard (with masterpassword)",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-name"},
|
||||
@ -223,7 +243,7 @@ add_task(async function test_getRecords_creditCards() {
|
||||
expectedResult: encryptedCCRecords.slice(0, 1),
|
||||
},
|
||||
{
|
||||
description: "Return all creditCards if focused field is cc number",
|
||||
description: "Return all creditCards if focused field is cc number (with masterpassword)",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-number"},
|
||||
|
119
browser/extensions/formautofill/test/unit/test_masterPassword.js
Normal file
119
browser/extensions/formautofill/test/unit/test_masterPassword.js
Normal file
@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 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,19 +4,16 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
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 = 2;
|
||||
const CREDIT_CARD_SCHEMA_VERSION = 1;
|
||||
|
||||
const ADDRESS_TESTCASES = [
|
||||
{
|
||||
@ -249,12 +246,11 @@ add_task(async function test_migrateAddressRecords() {
|
||||
let profileStorage = new FormAutofillStorage(path);
|
||||
await profileStorage.initialize();
|
||||
|
||||
for (let testcase of ADDRESS_TESTCASES) {
|
||||
await Promise.all(ADDRESS_TESTCASES.map(async testcase => {
|
||||
info(testcase.description);
|
||||
profileStorage._store.data.addresses = [testcase.record];
|
||||
await profileStorage.addresses._migrateRecord(testcase.record, 0);
|
||||
do_check_record_matches(testcase.expectedResult, profileStorage.addresses._data[0]);
|
||||
}
|
||||
await profileStorage.addresses._migrateRecord(testcase.record);
|
||||
do_check_record_matches(testcase.expectedResult, testcase.record);
|
||||
}));
|
||||
});
|
||||
|
||||
add_task(async function test_migrateCreditCardRecords() {
|
||||
@ -263,79 +259,9 @@ add_task(async function test_migrateCreditCardRecords() {
|
||||
let profileStorage = new FormAutofillStorage(path);
|
||||
await profileStorage.initialize();
|
||||
|
||||
for (let testcase of CREDIT_CARD_TESTCASES) {
|
||||
await Promise.all(CREDIT_CARD_TESTCASES.map(async testcase => {
|
||||
info(testcase.description);
|
||||
profileStorage._store.data.creditCards = [testcase.record];
|
||||
await profileStorage.creditCards._migrateRecord(testcase.record, 0);
|
||||
do_check_record_matches(testcase.expectedResult, profileStorage.creditCards._data[0]);
|
||||
}
|
||||
await profileStorage.creditCards._migrateRecord(testcase.record);
|
||||
do_check_record_matches(testcase.expectedResult, testcase.record);
|
||||
}));
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
|
@ -1,144 +0,0 @@
|
||||
/**
|
||||
* 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": 2,
|
||||
"version": 1,
|
||||
"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": 2,
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -499,7 +499,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
description: "Remote change",
|
||||
parent: {
|
||||
"guid": "e3680e9f890d",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -509,7 +509,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "e3680e9f890d",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4929001587121045",
|
||||
},
|
||||
@ -524,7 +524,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
description: "New local field",
|
||||
parent: {
|
||||
"guid": "0cba738b1be0",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -535,7 +535,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "0cba738b1be0",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -550,7 +550,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
description: "New remote field",
|
||||
parent: {
|
||||
"guid": "be3ef97f8285",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -560,7 +560,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "be3ef97f8285",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"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": 2,
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-exp-month": 12,
|
||||
@ -587,7 +587,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "9627322248ec",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"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": 2,
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-exp-month": 12,
|
||||
@ -614,7 +614,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "7d7509f3eeb2",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"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": 2,
|
||||
"version": 1,
|
||||
"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": 2,
|
||||
"version": 1,
|
||||
"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": 2,
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -673,7 +673,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "340a078c596f",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"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": 2,
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
},
|
||||
@ -702,7 +702,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "0b3a72a1bea2",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"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": 2,
|
||||
"version": 1,
|
||||
"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": 2,
|
||||
"version": 1,
|
||||
"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": 2,
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-exp-month": 12,
|
||||
@ -762,7 +762,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "244dbb692e94",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"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": 2,
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-exp-month": 12,
|
||||
@ -794,7 +794,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "6fc45e03d19a",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"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": 2,
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"cc-exp-month": 12,
|
||||
@ -826,7 +826,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "fff9fa27fa18",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"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": 2,
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"timeCreated": 1234,
|
||||
@ -859,7 +859,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
local: [],
|
||||
remote: {
|
||||
"guid": "5113f329c42f",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"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": 2,
|
||||
"version": 1,
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "4111111111111111",
|
||||
"timeCreated": 1234,
|
||||
@ -897,7 +897,7 @@ const CREDIT_CARD_RECONCILE_TESTCASES = [
|
||||
}],
|
||||
remote: {
|
||||
"guid": "791e5608b80a",
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"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": 3,
|
||||
"version": 2,
|
||||
"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]
|
||||
|
@ -711,6 +711,14 @@ async function sanitizeSessionPrincipals() {
|
||||
return;
|
||||
}
|
||||
|
||||
// When PREF_COOKIE_LIFETIME is set to ACCEPT_SESSION, any new cookie will be
|
||||
// marked as session only. But we don't touch the existing ones. For this
|
||||
// reason, here we delete any existing cookie, at shutdown.
|
||||
await new Promise(resolve => {
|
||||
Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_COOKIES,
|
||||
resolve);
|
||||
});
|
||||
|
||||
let principals = await new Promise(resolve => {
|
||||
quotaManagerService.getUsage(request => {
|
||||
if (request.resultCode != Cr.NS_OK) {
|
||||
|
@ -1126,11 +1126,13 @@ def check_have_64_bit(have_64_bit, compiler_have_64_bit):
|
||||
'about the target bitness.')
|
||||
|
||||
|
||||
@depends(c_compiler)
|
||||
def default_debug_flags(compiler_info):
|
||||
@depends(c_compiler, target)
|
||||
def default_debug_flags(compiler_info, target):
|
||||
# Debug info is ON by default.
|
||||
if compiler_info.type in ('msvc', 'clang-cl'):
|
||||
return '-Zi'
|
||||
elif target.kernel == 'WINNT' and compiler_info.type == 'clang':
|
||||
return '-g -gcodeview'
|
||||
return '-g'
|
||||
|
||||
|
||||
@ -1992,11 +1994,16 @@ add_old_configure_assignment('LIBFUZZER_FLAGS', libfuzzer_flags.use_flags)
|
||||
@depends(target, c_compiler)
|
||||
def make_shared_library(target, compiler):
|
||||
if target.os == 'WINNT':
|
||||
if compiler.type in ('gcc', 'clang'):
|
||||
if compiler.type == 'gcc':
|
||||
return namespace(
|
||||
mkshlib=['$(CXX)', '$(DSO_LDOPTS)', '-o', '$@'],
|
||||
mkcshlib=['$(CC)', '$(DSO_LDOPTS)', '-o', '$@'],
|
||||
)
|
||||
elif compiler.type == 'clang':
|
||||
return namespace(
|
||||
mkshlib=['$(CXX)', '$(DSO_LDOPTS)', '-Wl,-pdb,$(LINK_PDBFILE)', '-o', '$@'],
|
||||
mkcshlib=['$(CC)', '$(DSO_LDOPTS)', '-Wl,-pdb,$(LINK_PDBFILE)', '-o', '$@'],
|
||||
)
|
||||
else:
|
||||
linker = [
|
||||
'$(LINKER)',
|
||||
|
@ -7,6 +7,7 @@ if [ -d "$topsrcdir/clang" ]; then
|
||||
mk_export_correct_style LIB
|
||||
export LDFLAGS="clang_rt.asan_dynamic-x86_64.lib clang_rt.asan_dynamic_runtime_thunk-x86_64.lib"
|
||||
|
||||
export MOZ_COPY_PDBS=1
|
||||
export LLVM_SYMBOLIZER="$topsrcdir/clang/bin/llvm-symbolizer.exe"
|
||||
export MOZ_CLANG_RT_ASAN_LIB_PATH="${CLANG_LIB_DIR}/clang_rt.asan_dynamic-x86_64.dll"
|
||||
fi
|
||||
|
@ -126,6 +126,9 @@ endif # MKSHLIB
|
||||
endif # FORCE_SHARED_LIB
|
||||
|
||||
ifeq ($(OS_ARCH),WINNT)
|
||||
|
||||
LINK_PDBFILE ?= $(basename $(@F)).pdb
|
||||
|
||||
ifndef GNU_CC
|
||||
|
||||
#
|
||||
@ -147,7 +150,6 @@ endif
|
||||
COMPILE_CFLAGS += $(COMPILE_PDB_FLAG)
|
||||
COMPILE_CXXFLAGS += $(COMPILE_PDB_FLAG)
|
||||
|
||||
LINK_PDBFILE ?= $(basename $(@F)).pdb
|
||||
ifdef MOZ_DEBUG
|
||||
CODFILE=$(basename $(@F)).cod
|
||||
endif
|
||||
@ -161,6 +163,12 @@ MOZ_PROGRAM_LDFLAGS += -Wl,-rpath -Wl,@executable_path/Frameworks
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(OS_ARCH),WINNT)
|
||||
ifeq ($(CC_TYPE),clang)
|
||||
MOZ_PROGRAM_LDFLAGS += -Wl,-pdb,$(dir $@)/$(LINK_PDBFILE)
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(HOST_OS_ARCH),WINNT)
|
||||
HOST_PDBFILE=$(basename $(@F)).pdb
|
||||
HOST_PDB_FLAG ?= -Fd$(HOST_PDBFILE)
|
||||
@ -819,13 +827,13 @@ DUMP_SYMS_TARGETS :=
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef MOZ_CRASHREPORTER
|
||||
$(foreach file,$(DUMP_SYMS_TARGETS),$(eval $(call syms_template,$(file),$(notdir $(file))_syms.track)))
|
||||
else ifneq (,$(and $(LLVM_SYMBOLIZER),$(filter WINNT,$(OS_ARCH)),$(MOZ_AUTOMATION)))
|
||||
ifdef MOZ_COPY_PDBS
|
||||
PDB_FILES = $(addsuffix .pdb,$(basename $(DUMP_SYMS_TARGETS)))
|
||||
PDB_DEST ?= $(FINAL_TARGET)
|
||||
PDB_TARGET = syms
|
||||
INSTALL_TARGETS += PDB
|
||||
else ifdef MOZ_CRASHREPORTER
|
||||
$(foreach file,$(DUMP_SYMS_TARGETS),$(eval $(call syms_template,$(file),$(notdir $(file))_syms.track)))
|
||||
endif
|
||||
|
||||
cargo_host_flag := --target=$(RUST_HOST_TARGET)
|
||||
|
@ -274,12 +274,14 @@ print RCFILE qq{
|
||||
|
||||
my $versionlevel=0;
|
||||
my $insideversion=0;
|
||||
my $has_manifest=0;
|
||||
if (open(RCINCLUDE, "<$rcinclude"))
|
||||
{
|
||||
print RCFILE "// From included resource $rcinclude\n";
|
||||
# my $mstring="";
|
||||
while (<RCINCLUDE>)
|
||||
{
|
||||
$has_manifest = 1 if /^1 (24|RT_MANIFEST) "$binary.manifest"/;
|
||||
$_ =~ s/\@MOZ_APP_DISPLAYNAME\@/$displayname/g;
|
||||
print RCFILE $_;
|
||||
# my $instr=$_;
|
||||
@ -330,6 +332,10 @@ if (open(RCINCLUDE, "<$rcinclude"))
|
||||
|
||||
my $fileflags = join(' | ', @fileflags);
|
||||
|
||||
print RCFILE qq{
|
||||
1 RT_MANIFEST "$binary.manifest"
|
||||
} if !$has_manifest && $binary =~ /\.exe$/ && -e "$objdir/$binary.manifest";
|
||||
|
||||
print RCFILE qq{
|
||||
|
||||
|
||||
|
@ -876,7 +876,7 @@ Inspector.prototype = {
|
||||
let defaultTab = Services.prefs.getCharPref("devtools.inspector.activeSidebar");
|
||||
|
||||
if (this.is3PaneModeEnabled && defaultTab === "ruleview") {
|
||||
defaultTab = "computedview";
|
||||
defaultTab = "layoutview";
|
||||
}
|
||||
|
||||
// Append all side panels
|
||||
|
@ -181,6 +181,7 @@ skip-if = (os == "win" && debug) # bug 963492: win.
|
||||
[browser_rules_grid-toggle_02.js]
|
||||
[browser_rules_grid-toggle_03.js]
|
||||
[browser_rules_grid-toggle_04.js]
|
||||
[browser_rules_grid-toggle_05.js]
|
||||
[browser_rules_gridline-names-autocomplete.js]
|
||||
[browser_rules_guessIndentation.js]
|
||||
[browser_rules_highlight-used-fonts.js]
|
||||
|
@ -29,7 +29,7 @@ add_task(async function() {
|
||||
const gridToggle = container.querySelector(".ruleview-grid");
|
||||
|
||||
info("Checking the initial state of the CSS grid toggle in the rule-view.");
|
||||
ok(gridToggle, "Grid highlighter toggle is visible.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(!gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle button is not active.");
|
||||
ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
|
||||
@ -41,6 +41,7 @@ add_task(async function() {
|
||||
|
||||
info("Checking the CSS grid highlighter is created and toggle button is active in " +
|
||||
"the rule-view.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle is active.");
|
||||
is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
|
||||
@ -52,6 +53,7 @@ add_task(async function() {
|
||||
|
||||
info("Checking the CSS grid highlighter is not shown and toggle button is not active " +
|
||||
"in the rule-view.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(!gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle button is not active.");
|
||||
ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
|
||||
|
@ -29,7 +29,7 @@ add_task(async function() {
|
||||
const gridToggle = container.querySelector(".ruleview-grid");
|
||||
|
||||
info("Checking the initial state of the CSS grid toggle in the rule-view.");
|
||||
ok(gridToggle, "Grid highlighter toggle is visible.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(!gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle button is not active.");
|
||||
ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
|
||||
@ -41,6 +41,7 @@ add_task(async function() {
|
||||
|
||||
info("Checking the CSS grid highlighter is created and toggle button is active in " +
|
||||
"the rule-view.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle is active.");
|
||||
is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
|
||||
@ -52,6 +53,7 @@ add_task(async function() {
|
||||
|
||||
info("Checking the CSS grid highlighter is not shown and toggle button is not active " +
|
||||
"in the rule-view.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(!gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle button is not active.");
|
||||
ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
|
||||
|
@ -34,7 +34,9 @@ add_task(async function() {
|
||||
const overriddenGridToggle = overriddenContainer.querySelector(".ruleview-grid");
|
||||
|
||||
info("Checking the initial state of the CSS grid toggle in the rule-view.");
|
||||
ok(gridToggle && overriddenGridToggle, "Grid highlighter toggles are visible.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(!overriddenGridToggle.hasAttribute("disabled"),
|
||||
"Grid highlighter toggle is not disabled.");
|
||||
ok(!gridToggle.classList.contains("active") &&
|
||||
!overriddenGridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle buttons are not active.");
|
||||
|
@ -35,7 +35,7 @@ add_task(async function() {
|
||||
|
||||
info("Checking the state of the CSS grid toggle for the first grid container in the " +
|
||||
"rule-view.");
|
||||
ok(gridToggle, "Grid highlighter toggle is visible.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(!gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle button is not active.");
|
||||
ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
|
||||
@ -48,6 +48,7 @@ add_task(async function() {
|
||||
|
||||
info("Checking the CSS grid highlighter is created and toggle button is active in " +
|
||||
"the rule-view.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle is active.");
|
||||
is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
|
||||
@ -60,7 +61,7 @@ add_task(async function() {
|
||||
|
||||
info("Checking the state of the CSS grid toggle for the second grid container in the " +
|
||||
"rule-view.");
|
||||
ok(gridToggle, "Grid highlighter toggle is visible.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(!gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle button is not active.");
|
||||
is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is still shown.");
|
||||
@ -85,7 +86,7 @@ add_task(async function() {
|
||||
|
||||
info("Checking the state of the CSS grid toggle for the first grid container in the " +
|
||||
"rule-view.");
|
||||
ok(gridToggle, "Grid highlighter toggle is visible.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(!gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle button is not active.");
|
||||
});
|
||||
|
@ -29,7 +29,7 @@ add_task(async function() {
|
||||
const gridToggle = container.querySelector(".ruleview-grid");
|
||||
|
||||
info("Checking the initial state of the CSS grid toggle in the rule-view.");
|
||||
ok(gridToggle, "Grid highlighter toggle is visible.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(!gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle button is not active.");
|
||||
ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
|
||||
@ -41,6 +41,7 @@ add_task(async function() {
|
||||
|
||||
info("Checking the CSS grid highlighter is created and toggle button is active in " +
|
||||
"the rule-view.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle is active.");
|
||||
is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
|
||||
@ -52,6 +53,7 @@ add_task(async function() {
|
||||
|
||||
info("Checking the CSS grid highlighter is not shown and toggle button is not active " +
|
||||
"in the rule-view.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(!gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle button is not active.");
|
||||
ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
|
||||
|
@ -0,0 +1,102 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the grid toggle is hidden when the maximum number of grid highlighters
|
||||
// have been reached.
|
||||
|
||||
const TEST_URI = `
|
||||
<style type='text/css'>
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
</style>
|
||||
<div id="grid1" class="grid">
|
||||
<div class="cell1">cell1</div>
|
||||
<div class="cell2">cell2</div>
|
||||
</div>
|
||||
<div id="grid2" class="grid">
|
||||
<div class="cell1">cell1</div>
|
||||
<div class="cell2">cell2</div>
|
||||
</div>
|
||||
<div id="grid3" class="grid">
|
||||
<div class="cell1">cell1</div>
|
||||
<div class="cell2">cell2</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
add_task(async function() {
|
||||
await pushPref("devtools.gridinspector.maxHighlighters", 2);
|
||||
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
const { inspector, gridInspector } = await openLayoutView();
|
||||
const ruleView = selectRuleView(inspector);
|
||||
const { document: doc } = gridInspector;
|
||||
const { highlighters } = inspector;
|
||||
|
||||
await selectNode("#grid1", inspector);
|
||||
const gridList = doc.getElementById("grid-list");
|
||||
const checkbox2 = gridList.children[1].querySelector("input");
|
||||
const checkbox3 = gridList.children[2].querySelector("input");
|
||||
const container = getRuleViewProperty(ruleView, ".grid", "display").valueSpan;
|
||||
const gridToggle = container.querySelector(".ruleview-grid");
|
||||
|
||||
info("Checking the initial state of the CSS grid toggle in the rule-view.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(!gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle button is not active.");
|
||||
ok(!highlighters.gridHighlighters.size, "No CSS grid highlighter is shown.");
|
||||
|
||||
info("Toggling ON the CSS grid highlighter for #grid2.");
|
||||
let onHighlighterShown = highlighters.once("grid-highlighter-shown");
|
||||
checkbox2.click();
|
||||
await onHighlighterShown;
|
||||
|
||||
info("Checking the CSS grid toggle for #grid1 is not disabled and not active.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(!gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle button is not active.");
|
||||
is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
|
||||
|
||||
info("Toggling ON the CSS grid highlighter for #grid3.");
|
||||
onHighlighterShown = highlighters.once("grid-highlighter-shown");
|
||||
checkbox3.click();
|
||||
await onHighlighterShown;
|
||||
|
||||
info("Checking the CSS grid toggle for #grid1 is disabled.");
|
||||
ok(gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is disabled.");
|
||||
is(highlighters.gridHighlighters.size, 2, "CSS grid highlighters are shown.");
|
||||
|
||||
info("Toggling OFF the CSS grid highlighter for #grid3.");
|
||||
let onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
|
||||
checkbox3.click();
|
||||
await onHighlighterHidden;
|
||||
|
||||
info("Checking the CSS grid toggle for #grid1 is not disabled and not active.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(!gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle button is not active.");
|
||||
is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
|
||||
|
||||
info("Toggling ON the CSS grid highlighter for #grid1 from the rule-view.");
|
||||
onHighlighterShown = highlighters.once("grid-highlighter-shown");
|
||||
gridToggle.click();
|
||||
await onHighlighterShown;
|
||||
|
||||
info("Checking the CSS grid toggle for #grid1 is not disabled.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(gridToggle.classList.contains("active"), "Grid highlighter toggle is active.");
|
||||
is(highlighters.gridHighlighters.size, 2, "CSS grid highlighters are shown.");
|
||||
|
||||
info("Toggling OFF the CSS grid highlighter for #grid1 from the rule-view.");
|
||||
onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
|
||||
gridToggle.click();
|
||||
await onHighlighterHidden;
|
||||
|
||||
info("Checking the CSS grid toggle for #grid1 is not disabled and not active.");
|
||||
ok(!gridToggle.hasAttribute("disabled"), "Grid highlighter toggle is not disabled.");
|
||||
ok(!gridToggle.classList.contains("active"),
|
||||
"Grid highlighter toggle button is not active.");
|
||||
is(highlighters.gridHighlighters.size, 1, "CSS grid highlighter is shown.");
|
||||
});
|
@ -76,6 +76,7 @@ function TextPropertyEditor(ruleEditor, property) {
|
||||
this.prop = property;
|
||||
this.prop.editor = this;
|
||||
this.browserWindow = this.doc.defaultView.top;
|
||||
|
||||
this._populatedComputed = false;
|
||||
this._hasPendingClick = false;
|
||||
this._clickedElementOptions = null;
|
||||
@ -521,22 +522,22 @@ TextPropertyEditor.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
const nodeFront = this.ruleView.inspector.selection.nodeFront;
|
||||
|
||||
const flexToggle = this.valueSpan.querySelector(".ruleview-flex");
|
||||
if (flexToggle) {
|
||||
flexToggle.setAttribute("title", l10n("rule.flexToggle.tooltip"));
|
||||
if (this.ruleView.highlighters.flexboxHighlighterShown ===
|
||||
this.ruleView.inspector.selection.nodeFront) {
|
||||
flexToggle.classList.add("active");
|
||||
}
|
||||
flexToggle.classList.toggle("active",
|
||||
this.ruleView.highlighters.flexboxHighlighterShown === nodeFront);
|
||||
}
|
||||
|
||||
const gridToggle = this.valueSpan.querySelector(".ruleview-grid");
|
||||
if (gridToggle) {
|
||||
gridToggle.setAttribute("title", l10n("rule.gridToggle.tooltip"));
|
||||
if (this.ruleView.highlighters.gridHighlighters.has(
|
||||
this.ruleView.inspector.selection.nodeFront)) {
|
||||
gridToggle.classList.add("active");
|
||||
}
|
||||
gridToggle.classList.toggle("active",
|
||||
this.ruleView.highlighters.gridHighlighters.has(nodeFront));
|
||||
gridToggle.toggleAttribute("disabled",
|
||||
!this.ruleView.highlighters.canGridHighlighterToggle(nodeFront));
|
||||
}
|
||||
|
||||
const shapeToggle = this.valueSpan.querySelector(".ruleview-shapeswatch");
|
||||
|
@ -28,6 +28,8 @@ class HighlightersOverlay {
|
||||
this.highlighterUtils = this.inspector.toolbox.highlighterUtils;
|
||||
this.store = this.inspector.store;
|
||||
this.telemetry = inspector.telemetry;
|
||||
this.maxGridHighlighters =
|
||||
Services.prefs.getIntPref("devtools.gridinspector.maxHighlighters");
|
||||
|
||||
// Collection of instantiated highlighter actors like FlexboxHighlighter,
|
||||
// ShapesHighlighter and GeometryEditorHighlighter.
|
||||
@ -432,19 +434,16 @@ class HighlightersOverlay {
|
||||
* "rule" represents the rule view.
|
||||
*/
|
||||
async showGridHighlighter(node, options, trigger) {
|
||||
const maxHighlighters =
|
||||
Services.prefs.getIntPref("devtools.gridinspector.maxHighlighters");
|
||||
|
||||
// When the grid highlighter has the given node, it is probably called with new
|
||||
// highlighting options, so skip any extra grid highlighter handling.
|
||||
if (!this.gridHighlighters.has(node)) {
|
||||
if (maxHighlighters === 1) {
|
||||
if (this.maxGridHighlighters === 1) {
|
||||
// Only one grid highlighter can be shown at a time. Hides any instantiated
|
||||
// grid highlighters.
|
||||
for (const nodeFront of this.gridHighlighters.keys()) {
|
||||
await this.hideGridHighlighter(nodeFront);
|
||||
}
|
||||
} else if (this.gridHighlighters.size === maxHighlighters) {
|
||||
} else if (this.gridHighlighters.size === this.maxGridHighlighters) {
|
||||
// The maximum number of grid highlighters shown have been reached. Don't show
|
||||
// any additional grid highlighters.
|
||||
return;
|
||||
@ -496,8 +495,6 @@ class HighlightersOverlay {
|
||||
return;
|
||||
}
|
||||
|
||||
this._toggleRuleViewIcon(node, false, ".ruleview-grid");
|
||||
|
||||
// Hide the highlighter and put it in the pool of extra grid highlighters
|
||||
// so that it can be reused.
|
||||
const highlighter = this.gridHighlighters.get(node);
|
||||
@ -507,6 +504,8 @@ class HighlightersOverlay {
|
||||
this.state.grids.delete(node);
|
||||
this.gridHighlighters.delete(node);
|
||||
|
||||
this._toggleRuleViewIcon(node, false, ".ruleview-grid");
|
||||
|
||||
// Emit the NodeFront of the grid container element that the grid highlighter was
|
||||
// hidden for.
|
||||
this.emit("grid-highlighter-hidden", node);
|
||||
@ -790,12 +789,22 @@ class HighlightersOverlay {
|
||||
* The selector of the rule view icon to toggle.
|
||||
*/
|
||||
_toggleRuleViewIcon(node, active, selector) {
|
||||
if (this.inspector.selection.nodeFront != node) {
|
||||
const ruleViewEl = this.inspector.getPanel("ruleview").view.element;
|
||||
|
||||
if (this.inspector.selection.nodeFront !== node) {
|
||||
if (selector === ".ruleview-grid") {
|
||||
for (const icon of ruleViewEl.querySelectorAll(selector)) {
|
||||
if (this.canGridHighlighterToggle(this.inspector.selection.nodeFront)) {
|
||||
icon.removeAttribute("disabled");
|
||||
} else {
|
||||
icon.setAttribute("disabled", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const ruleViewEl = this.inspector.getPanel("ruleview").view.element;
|
||||
|
||||
for (const icon of ruleViewEl.querySelectorAll(selector)) {
|
||||
icon.classList.toggle("active", active);
|
||||
}
|
||||
|
@ -8,16 +8,6 @@ const TEST_URI = "data:text/html;charset=UTF-8," +
|
||||
const OPTOUT = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT;
|
||||
|
||||
const TELEMETRY_DATA = [
|
||||
{
|
||||
timestamp: null,
|
||||
category: "devtools.main",
|
||||
method: "tool_timer",
|
||||
object: "computedview",
|
||||
value: null,
|
||||
extra: {
|
||||
time_open: ""
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamp: null,
|
||||
category: "devtools.main",
|
||||
@ -32,7 +22,17 @@ const TELEMETRY_DATA = [
|
||||
timestamp: null,
|
||||
category: "devtools.main",
|
||||
method: "tool_timer",
|
||||
object: "ruleview",
|
||||
object: "fontinspector",
|
||||
value: null,
|
||||
extra: {
|
||||
time_open: ""
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamp: null,
|
||||
category: "devtools.main",
|
||||
method: "tool_timer",
|
||||
object: "computedview",
|
||||
value: null,
|
||||
extra: {
|
||||
time_open: ""
|
||||
@ -50,18 +50,18 @@ add_task(async function() {
|
||||
|
||||
let { inspector, toolbox } = await openInspectorForURL(TEST_URI);
|
||||
|
||||
info("Selecting font inspector.");
|
||||
inspector.sidebar.select("fontinspector");
|
||||
|
||||
is(inspector.sidebar.getCurrentTabID(), "fontinspector",
|
||||
"Font Inspector is selected");
|
||||
|
||||
info("Selecting computed view.");
|
||||
inspector.sidebar.select("computedview");
|
||||
|
||||
is(inspector.sidebar.getCurrentTabID(), "computedview",
|
||||
"Computed View is selected");
|
||||
|
||||
info("Selecting layout view.");
|
||||
inspector.sidebar.select("layoutview");
|
||||
|
||||
is(inspector.sidebar.getCurrentTabID(), "layoutview",
|
||||
"Layout View is selected");
|
||||
|
||||
info("Closing inspector.");
|
||||
await toolbox.destroy();
|
||||
|
||||
@ -73,8 +73,8 @@ add_task(async function() {
|
||||
await inspector.sidebar.once("select");
|
||||
}
|
||||
|
||||
is(inspector.sidebar.getCurrentTabID(), "layoutview",
|
||||
"Layout view is selected by default.");
|
||||
is(inspector.sidebar.getCurrentTabID(), "computedview",
|
||||
"Computed view is selected by default.");
|
||||
|
||||
checkTelemetryResults();
|
||||
});
|
||||
|
@ -33,7 +33,7 @@ pref("devtools.command-button-noautohide.enabled", false);
|
||||
// Enable the Inspector
|
||||
pref("devtools.inspector.enabled", true);
|
||||
// What was the last active sidebar in the inspector
|
||||
pref("devtools.inspector.activeSidebar", "ruleview");
|
||||
pref("devtools.inspector.activeSidebar", "layoutview");
|
||||
pref("devtools.inspector.remote", false);
|
||||
|
||||
// Enable the 3 pane mode in the inspector
|
||||
|
@ -19,7 +19,7 @@ const DATA = [
|
||||
object: "inspector",
|
||||
value: null,
|
||||
extra: {
|
||||
oldpanel: "computedview",
|
||||
oldpanel: "layoutview",
|
||||
newpanel: "animationinspector"
|
||||
}
|
||||
},
|
||||
@ -156,8 +156,8 @@ function checkResults() {
|
||||
// here.
|
||||
checkTelemetry("DEVTOOLS_INSPECTOR_OPENED_COUNT", "", [1, 0, 0], "array");
|
||||
checkTelemetry("DEVTOOLS_RULEVIEW_OPENED_COUNT", "", [1, 0, 0], "array");
|
||||
checkTelemetry("DEVTOOLS_COMPUTEDVIEW_OPENED_COUNT", "", [3, 0, 0], "array");
|
||||
checkTelemetry("DEVTOOLS_LAYOUTVIEW_OPENED_COUNT", "", [2, 0, 0], "array");
|
||||
checkTelemetry("DEVTOOLS_COMPUTEDVIEW_OPENED_COUNT", "", [2, 0, 0], "array");
|
||||
checkTelemetry("DEVTOOLS_LAYOUTVIEW_OPENED_COUNT", "", [3, 0, 0], "array");
|
||||
checkTelemetry("DEVTOOLS_FONTINSPECTOR_OPENED_COUNT", "", [2, 0, 0], "array");
|
||||
checkTelemetry("DEVTOOLS_COMPUTEDVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
|
||||
checkTelemetry("DEVTOOLS_LAYOUTVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
|
||||
|
@ -440,6 +440,11 @@
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.ruleview-grid[disabled] {
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.ruleview-shape-point.active,
|
||||
.ruleview-shapeswatch.active + .ruleview-shape > .ruleview-shape-point:hover {
|
||||
background-color: var(--rule-highlight-background-color);
|
||||
|
@ -56,22 +56,18 @@ TimeoutExecutor::ScheduleDelayed(const TimeStamp& aDeadline,
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
if (!mTimer) {
|
||||
mTimer = NS_NewTimer();
|
||||
mTimer = NS_NewTimer(mOwner->EventTarget());
|
||||
NS_ENSURE_TRUE(mTimer, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
uint32_t earlyMicros = 0;
|
||||
MOZ_ALWAYS_SUCCEEDS(mTimer->GetAllowedEarlyFiringMicroseconds(&earlyMicros));
|
||||
mAllowedEarlyFiringTime = TimeDuration::FromMicroseconds(earlyMicros);
|
||||
} else {
|
||||
// Always call Cancel() in case we are re-using a timer.
|
||||
rv = mTimer->Cancel();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// Always call Cancel() in case we are re-using a timer. Otherwise
|
||||
// the subsequent SetTarget() may fail.
|
||||
rv = mTimer->Cancel();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = mTimer->SetTarget(mOwner->EventTarget());
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Calculate the delay based on the deadline and current time. If we have
|
||||
// a minimum delay set then clamp to that value.
|
||||
//
|
||||
|
@ -531,14 +531,13 @@ void
|
||||
FileReader::StartProgressEventTimer()
|
||||
{
|
||||
if (!mProgressNotifier) {
|
||||
mProgressNotifier = NS_NewTimer();
|
||||
mProgressNotifier = NS_NewTimer(mTarget);
|
||||
}
|
||||
|
||||
if (mProgressNotifier) {
|
||||
mProgressEventWasDelayed = false;
|
||||
mTimerIsActive = true;
|
||||
mProgressNotifier->Cancel();
|
||||
mProgressNotifier->SetTarget(mTarget);
|
||||
mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL,
|
||||
nsITimer::TYPE_ONE_SHOT);
|
||||
}
|
||||
|
@ -1592,7 +1592,7 @@ ContentParent::MarkAsDead()
|
||||
void
|
||||
ContentParent::OnChannelError()
|
||||
{
|
||||
RefPtr<ContentParent> content(this);
|
||||
RefPtr<ContentParent> kungFuDeathGrip(this);
|
||||
PContentParent::OnChannelError();
|
||||
}
|
||||
|
||||
@ -1738,14 +1738,14 @@ struct DelayedDeleteContentParentTask : public Runnable
|
||||
{
|
||||
explicit DelayedDeleteContentParentTask(ContentParent* aObj)
|
||||
: Runnable("dom::DelayedDeleteContentParentTask")
|
||||
, mObj(aObj)
|
||||
, mKungFuDeathGrip(aObj)
|
||||
{
|
||||
}
|
||||
|
||||
// No-op
|
||||
NS_IMETHOD Run() override { return NS_OK; }
|
||||
|
||||
RefPtr<ContentParent> mObj;
|
||||
RefPtr<ContentParent> mKungFuDeathGrip;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
@ -23,16 +23,16 @@ struct FeatureMap {
|
||||
*/
|
||||
static FeatureMap sSupportedFeatures[] = {
|
||||
{ "autoplay", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "camera", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "encrypted-media", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "fullscreen", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "geolocation", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "microphone", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "midi", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "payment", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "camera", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "encrypted-media", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "fullscreen", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "geolocation", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "microphone", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "midi", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "payment", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
// TODO: not supported yet!!!
|
||||
{ "speaker", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "vr", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "speaker", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
{ "vr", FeaturePolicyUtils::FeaturePolicyValue::eAll },
|
||||
};
|
||||
|
||||
/* static */ bool
|
||||
|
@ -3142,13 +3142,13 @@ XMLHttpRequestMainThread::SetTimeout(uint32_t aTimeout, ErrorResult& aRv)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
XMLHttpRequestMainThread::SetTimerEventTarget(nsITimer* aTimer)
|
||||
nsIEventTarget*
|
||||
XMLHttpRequestMainThread::GetTimerEventTarget()
|
||||
{
|
||||
if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
|
||||
nsCOMPtr<nsIEventTarget> target = global->EventTargetFor(TaskCategory::Other);
|
||||
aTimer->SetTarget(target);
|
||||
return global->EventTargetFor(TaskCategory::Other);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -3183,8 +3183,7 @@ XMLHttpRequestMainThread::StartTimeoutTimer()
|
||||
}
|
||||
|
||||
if (!mTimeoutTimer) {
|
||||
mTimeoutTimer = NS_NewTimer();
|
||||
SetTimerEventTarget(mTimeoutTimer);
|
||||
mTimeoutTimer = NS_NewTimer(GetTimerEventTarget());
|
||||
}
|
||||
uint32_t elapsed =
|
||||
(uint32_t)((PR_Now() - mRequestSentTime) / PR_USEC_PER_MSEC);
|
||||
@ -3601,8 +3600,7 @@ void
|
||||
XMLHttpRequestMainThread::StartProgressEventTimer()
|
||||
{
|
||||
if (!mProgressNotifier) {
|
||||
mProgressNotifier = NS_NewTimer();
|
||||
SetTimerEventTarget(mProgressNotifier);
|
||||
mProgressNotifier = NS_NewTimer(GetTimerEventTarget());
|
||||
}
|
||||
if (mProgressNotifier) {
|
||||
mProgressTimerIsActive = true;
|
||||
@ -3628,8 +3626,7 @@ XMLHttpRequestMainThread::MaybeStartSyncTimeoutTimer()
|
||||
return eErrorOrExpired;
|
||||
}
|
||||
|
||||
mSyncTimeoutTimer = NS_NewTimer();
|
||||
SetTimerEventTarget(mSyncTimeoutTimer);
|
||||
mSyncTimeoutTimer = NS_NewTimer(GetTimerEventTarget());
|
||||
if (!mSyncTimeoutTimer) {
|
||||
return eErrorOrExpired;
|
||||
}
|
||||
|
@ -514,7 +514,7 @@ protected:
|
||||
|
||||
nsresult OnRedirectVerifyCallback(nsresult result);
|
||||
|
||||
void SetTimerEventTarget(nsITimer* aTimer);
|
||||
nsIEventTarget* GetTimerEventTarget();
|
||||
|
||||
nsresult DispatchToMainThread(already_AddRefed<nsIRunnable> aRunnable);
|
||||
|
||||
|
@ -737,6 +737,7 @@ WebRenderMemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
|
||||
helper.ReportTexture(aReport.vertex_data_textures, "vertex-data");
|
||||
helper.ReportTexture(aReport.render_target_textures, "render-targets");
|
||||
helper.ReportTexture(aReport.texture_cache_textures, "texture-cache");
|
||||
helper.ReportTexture(aReport.depth_target_textures, "depth-targets");
|
||||
|
||||
FinishAsyncMemoryReport();
|
||||
},
|
||||
|
@ -22,6 +22,7 @@ void brush_vs(
|
||||
#define BRUSH_FLAG_SEGMENT_RELATIVE 2
|
||||
#define BRUSH_FLAG_SEGMENT_REPEAT_X 4
|
||||
#define BRUSH_FLAG_SEGMENT_REPEAT_Y 8
|
||||
#define BRUSH_FLAG_TEXEL_RECT 16
|
||||
|
||||
void main(void) {
|
||||
// Load the brush instance from vertex attributes.
|
||||
|
@ -51,7 +51,7 @@ void brush_vs(
|
||||
mat4 transform,
|
||||
PictureTask pic_task,
|
||||
int brush_flags,
|
||||
vec4 texel_rect
|
||||
vec4 segment_data
|
||||
) {
|
||||
ImageBrushData image_data = fetch_image_data(prim_address);
|
||||
|
||||
@ -76,19 +76,18 @@ void brush_vs(
|
||||
local_rect = segment_rect;
|
||||
stretch_size = local_rect.size;
|
||||
|
||||
// Note: Here we can assume that texels in device
|
||||
// space map to local space, due to how border-image
|
||||
// works. That assumption may not hold if this
|
||||
// is used for other purposes in the future.
|
||||
if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X) != 0) {
|
||||
stretch_size.x = (texel_rect.z - texel_rect.x) / pic_task.common_data.device_pixel_scale;
|
||||
stretch_size.x = (segment_data.z - segment_data.x);
|
||||
}
|
||||
if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y) != 0) {
|
||||
stretch_size.y = (texel_rect.w - texel_rect.y) / pic_task.common_data.device_pixel_scale;
|
||||
stretch_size.y = (segment_data.w - segment_data.y);
|
||||
}
|
||||
|
||||
uv0 = res.uv_rect.p0 + texel_rect.xy;
|
||||
uv1 = res.uv_rect.p0 + texel_rect.zw;
|
||||
// If the extra data is a texel rect, modify the UVs.
|
||||
if ((brush_flags & BRUSH_FLAG_TEXEL_RECT) != 0) {
|
||||
uv0 = res.uv_rect.p0 + segment_data.xy;
|
||||
uv1 = res.uv_rect.p0 + segment_data.zw;
|
||||
}
|
||||
}
|
||||
|
||||
vUv.z = res.layer;
|
||||
|
@ -10,7 +10,6 @@ flat varying vec4 vUvSampleBounds;
|
||||
#ifdef WR_VERTEX_SHADER
|
||||
struct SplitGeometry {
|
||||
vec2 local[4];
|
||||
RectWithSize local_rect;
|
||||
};
|
||||
|
||||
SplitGeometry fetch_split_geometry(int address) {
|
||||
@ -18,7 +17,6 @@ SplitGeometry fetch_split_geometry(int address) {
|
||||
|
||||
vec4 data0 = TEXEL_FETCH(sGpuCache, uv, 0, ivec2(0, 0));
|
||||
vec4 data1 = TEXEL_FETCH(sGpuCache, uv, 0, ivec2(1, 0));
|
||||
vec4 data2 = TEXEL_FETCH(sGpuCache, uv, 0, ivec2(2, 0));
|
||||
|
||||
SplitGeometry geo;
|
||||
geo.local = vec2[4](
|
||||
@ -27,7 +25,6 @@ SplitGeometry fetch_split_geometry(int address) {
|
||||
data1.xy,
|
||||
data1.zw
|
||||
);
|
||||
geo.local_rect = RectWithSize(data2.xy, data2.zw);
|
||||
|
||||
return geo;
|
||||
}
|
||||
@ -98,7 +95,7 @@ void main(void) {
|
||||
max_uv - vec2(0.5)
|
||||
) / texture_size.xyxy;
|
||||
|
||||
vec2 f = (local_pos - geometry.local_rect.p0) / geometry.local_rect.size;
|
||||
vec2 f = (local_pos - ph.local_rect.p0) / ph.local_rect.size;
|
||||
|
||||
f = bilerp(
|
||||
extra_data.st_tl, extra_data.st_tr,
|
||||
|
@ -20,12 +20,13 @@ use plane_split::{BspSplitter, Clipper, Polygon, Splitter};
|
||||
use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, DeferredResolve};
|
||||
use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveIndex};
|
||||
use prim_store::{VisibleGradientTile, PrimitiveInstance};
|
||||
use prim_store::{BorderSource, Primitive, PrimitiveDetails};
|
||||
use prim_store::{BrushSegment, BorderSource, Primitive, PrimitiveDetails};
|
||||
use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree};
|
||||
use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
|
||||
use renderer::BLOCKS_PER_UV_RECT;
|
||||
use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties};
|
||||
use scene::FilterOpHelpers;
|
||||
use smallvec::SmallVec;
|
||||
use std::{f32, i32, usize};
|
||||
use tiling::{RenderTargetContext};
|
||||
use util::{MatrixHelpers, TransformedRectKind};
|
||||
@ -419,6 +420,15 @@ impl AlphaBatchContainer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Each segment can optionally specify a per-segment
|
||||
/// texture set and one user data field.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct SegmentInstanceData {
|
||||
textures: BatchTextures,
|
||||
user_data: i32,
|
||||
is_opaque_override: Option<bool>,
|
||||
}
|
||||
|
||||
/// Encapsulates the logic of building batches for items that are blended.
|
||||
pub struct AlphaBatchBuilder {
|
||||
pub batch_list: BatchList,
|
||||
@ -551,7 +561,6 @@ impl AlphaBatchBuilder {
|
||||
let gpu_blocks = [
|
||||
[local_points[0].x, local_points[0].y, local_points[1].x, local_points[1].y].into(),
|
||||
[local_points[2].x, local_points[2].y, local_points[3].x, local_points[3].y].into(),
|
||||
pic_metadata.local_rect.into(),
|
||||
];
|
||||
|
||||
let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
|
||||
@ -1103,37 +1112,29 @@ impl AlphaBatchBuilder {
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
// TODO(gw): As an interim step, just return one value for the
|
||||
// per-segment user data. In the future, this method
|
||||
// will be expanded to optionally return a list of
|
||||
// (BatchTextures, user_data) per segment, which will
|
||||
// allow a different texture / render task to be used
|
||||
// per segment.
|
||||
if let Some((batch_kind, textures, user_data, segment_user_data)) = brush.get_batch_params(
|
||||
ctx.resource_cache,
|
||||
gpu_cache,
|
||||
deferred_resolves,
|
||||
ctx.prim_store.chase_id == Some(prim_instance.prim_index),
|
||||
if let Some(params) = brush.get_batch_params(
|
||||
ctx.resource_cache,
|
||||
gpu_cache,
|
||||
deferred_resolves,
|
||||
ctx.prim_store.chase_id == Some(prim_instance.prim_index),
|
||||
) {
|
||||
let prim_header_index = prim_headers.push(&prim_header, user_data);
|
||||
let prim_header_index = prim_headers.push(&prim_header, params.prim_user_data);
|
||||
if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_instance.prim_index) {
|
||||
println!("\t{:?} {:?}, task relative bounds {:?}",
|
||||
batch_kind, prim_header_index, bounding_rect);
|
||||
params.batch_kind, prim_header_index, bounding_rect);
|
||||
}
|
||||
|
||||
self.add_brush_to_batch(
|
||||
brush,
|
||||
¶ms,
|
||||
prim_instance,
|
||||
batch_kind,
|
||||
specified_blend_mode,
|
||||
non_segmented_blend_mode,
|
||||
textures,
|
||||
prim_header_index,
|
||||
clip_task_address,
|
||||
bounding_rect,
|
||||
transform_kind,
|
||||
render_tasks,
|
||||
segment_user_data,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1270,80 +1271,147 @@ impl AlphaBatchBuilder {
|
||||
);
|
||||
}
|
||||
|
||||
/// Add a single segment instance to a batch.
|
||||
fn add_segment_to_batch(
|
||||
&mut self,
|
||||
segment: &BrushSegment,
|
||||
segment_data: &SegmentInstanceData,
|
||||
segment_index: i32,
|
||||
batch_kind: BrushBatchKind,
|
||||
prim_instance: &PrimitiveInstance,
|
||||
prim_header_index: PrimitiveHeaderIndex,
|
||||
alpha_blend_mode: BlendMode,
|
||||
bounding_rect: &WorldRect,
|
||||
transform_kind: TransformedRectKind,
|
||||
render_tasks: &RenderTaskTree,
|
||||
) {
|
||||
let clip_task_address = match segment.clip_task_id {
|
||||
BrushSegmentTaskId::RenderTaskId(id) =>
|
||||
render_tasks.get_task_address(id),
|
||||
BrushSegmentTaskId::Opaque => OPAQUE_TASK_ADDRESS,
|
||||
BrushSegmentTaskId::Empty => return,
|
||||
};
|
||||
|
||||
// If the segment instance data specifies opacity for that
|
||||
// segment, use it. Otherwise, assume opacity for the segment
|
||||
// from the overall primitive opacity.
|
||||
let is_segment_opaque = match segment_data.is_opaque_override {
|
||||
Some(is_opaque) => is_opaque,
|
||||
None => prim_instance.opacity.is_opaque,
|
||||
};
|
||||
|
||||
let is_inner = segment.edge_flags.is_empty();
|
||||
let needs_blending = !is_segment_opaque ||
|
||||
segment.clip_task_id.needs_blending() ||
|
||||
(!is_inner && transform_kind == TransformedRectKind::Complex);
|
||||
|
||||
let instance = PrimitiveInstanceData::from(BrushInstance {
|
||||
segment_index,
|
||||
edge_flags: segment.edge_flags,
|
||||
clip_task_address,
|
||||
brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION | segment.brush_flags,
|
||||
prim_header_index,
|
||||
user_data: segment_data.user_data,
|
||||
});
|
||||
|
||||
let batch_key = BatchKey {
|
||||
blend_mode: if needs_blending { alpha_blend_mode } else { BlendMode::None },
|
||||
kind: BatchKind::Brush(batch_kind),
|
||||
textures: segment_data.textures,
|
||||
};
|
||||
|
||||
self.batch_list.push_single_instance(
|
||||
batch_key,
|
||||
bounding_rect,
|
||||
prim_instance.prim_index,
|
||||
instance,
|
||||
);
|
||||
}
|
||||
|
||||
/// Add any segment(s) from a brush to batches.
|
||||
fn add_brush_to_batch(
|
||||
&mut self,
|
||||
brush: &BrushPrimitive,
|
||||
params: &BrushBatchParameters,
|
||||
prim_instance: &PrimitiveInstance,
|
||||
batch_kind: BrushBatchKind,
|
||||
alpha_blend_mode: BlendMode,
|
||||
non_segmented_blend_mode: BlendMode,
|
||||
textures: BatchTextures,
|
||||
prim_header_index: PrimitiveHeaderIndex,
|
||||
clip_task_address: RenderTaskAddress,
|
||||
bounding_rect: &WorldRect,
|
||||
transform_kind: TransformedRectKind,
|
||||
render_tasks: &RenderTaskTree,
|
||||
user_data: i32,
|
||||
) {
|
||||
let base_instance = BrushInstance {
|
||||
prim_header_index,
|
||||
clip_task_address,
|
||||
segment_index: 0,
|
||||
edge_flags: EdgeAaSegmentMask::all(),
|
||||
brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
|
||||
user_data,
|
||||
};
|
||||
|
||||
match brush.segment_desc {
|
||||
Some(ref segment_desc) => {
|
||||
for (i, segment) in segment_desc.segments.iter().enumerate() {
|
||||
let is_inner = segment.edge_flags.is_empty();
|
||||
let needs_blending = !prim_instance.opacity.is_opaque ||
|
||||
segment.clip_task_id.needs_blending() ||
|
||||
(!is_inner && transform_kind == TransformedRectKind::Complex);
|
||||
|
||||
let clip_task_address = match segment.clip_task_id {
|
||||
BrushSegmentTaskId::RenderTaskId(id) =>
|
||||
render_tasks.get_task_address(id),
|
||||
BrushSegmentTaskId::Opaque => OPAQUE_TASK_ADDRESS,
|
||||
BrushSegmentTaskId::Empty => continue,
|
||||
};
|
||||
|
||||
let instance = PrimitiveInstanceData::from(BrushInstance {
|
||||
segment_index: i as i32,
|
||||
edge_flags: segment.edge_flags,
|
||||
clip_task_address,
|
||||
brush_flags: base_instance.brush_flags | segment.brush_flags,
|
||||
..base_instance
|
||||
});
|
||||
|
||||
let batch_key = BatchKey {
|
||||
blend_mode: if needs_blending { alpha_blend_mode } else { BlendMode::None },
|
||||
kind: BatchKind::Brush(batch_kind),
|
||||
textures,
|
||||
};
|
||||
|
||||
self.batch_list.push_single_instance(
|
||||
batch_key,
|
||||
match (&brush.segment_desc, ¶ms.segment_data) {
|
||||
(Some(ref segment_desc), SegmentDataKind::Instanced(ref segment_data)) => {
|
||||
// In this case, we have both a list of segments, and a list of
|
||||
// per-segment instance data. Zip them together to build batches.
|
||||
debug_assert_eq!(segment_desc.segments.len(), segment_data.len());
|
||||
for (segment_index, (segment, segment_data)) in segment_desc.segments
|
||||
.iter()
|
||||
.zip(segment_data.iter())
|
||||
.enumerate() {
|
||||
self.add_segment_to_batch(
|
||||
segment,
|
||||
segment_data,
|
||||
segment_index as i32,
|
||||
params.batch_kind,
|
||||
prim_instance,
|
||||
prim_header_index,
|
||||
alpha_blend_mode,
|
||||
bounding_rect,
|
||||
prim_instance.prim_index,
|
||||
instance,
|
||||
transform_kind,
|
||||
render_tasks,
|
||||
);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
(Some(ref segment_desc), SegmentDataKind::Shared(ref segment_data)) => {
|
||||
// A list of segments, but the per-segment data is common
|
||||
// between all segments.
|
||||
for (segment_index, segment) in segment_desc.segments
|
||||
.iter()
|
||||
.enumerate() {
|
||||
self.add_segment_to_batch(
|
||||
segment,
|
||||
segment_data,
|
||||
segment_index as i32,
|
||||
params.batch_kind,
|
||||
prim_instance,
|
||||
prim_header_index,
|
||||
alpha_blend_mode,
|
||||
bounding_rect,
|
||||
transform_kind,
|
||||
render_tasks,
|
||||
);
|
||||
}
|
||||
}
|
||||
(None, SegmentDataKind::Shared(ref segment_data)) => {
|
||||
// No segments, and thus no per-segment instance data.
|
||||
let batch_key = BatchKey {
|
||||
blend_mode: non_segmented_blend_mode,
|
||||
kind: BatchKind::Brush(batch_kind),
|
||||
textures,
|
||||
kind: BatchKind::Brush(params.batch_kind),
|
||||
textures: segment_data.textures,
|
||||
};
|
||||
let instance = PrimitiveInstanceData::from(BrushInstance {
|
||||
segment_index: 0,
|
||||
edge_flags: EdgeAaSegmentMask::all(),
|
||||
clip_task_address,
|
||||
brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
|
||||
prim_header_index,
|
||||
user_data: segment_data.user_data,
|
||||
});
|
||||
self.batch_list.push_single_instance(
|
||||
batch_key,
|
||||
bounding_rect,
|
||||
prim_instance.prim_index,
|
||||
PrimitiveInstanceData::from(base_instance),
|
||||
PrimitiveInstanceData::from(instance),
|
||||
);
|
||||
}
|
||||
(None, SegmentDataKind::Instanced(..)) => {
|
||||
// We should never hit the case where there are no segments,
|
||||
// but a list of segment instance data.
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1426,6 +1494,58 @@ fn get_image_tile_params(
|
||||
}
|
||||
}
|
||||
|
||||
/// Either a single texture / user data for all segments,
|
||||
/// or a list of one per segment.
|
||||
enum SegmentDataKind {
|
||||
Shared(SegmentInstanceData),
|
||||
Instanced(SmallVec<[SegmentInstanceData; 8]>),
|
||||
}
|
||||
|
||||
/// The parameters that are specific to a kind of brush,
|
||||
/// used by the common method to add a brush to batches.
|
||||
struct BrushBatchParameters {
|
||||
batch_kind: BrushBatchKind,
|
||||
prim_user_data: [i32; 3],
|
||||
segment_data: SegmentDataKind,
|
||||
}
|
||||
|
||||
impl BrushBatchParameters {
|
||||
/// This brush instance has a list of per-segment
|
||||
/// instance data.
|
||||
fn instanced(
|
||||
batch_kind: BrushBatchKind,
|
||||
prim_user_data: [i32; 3],
|
||||
segment_data: SmallVec<[SegmentInstanceData; 8]>,
|
||||
) -> Self {
|
||||
BrushBatchParameters {
|
||||
batch_kind,
|
||||
prim_user_data,
|
||||
segment_data: SegmentDataKind::Instanced(segment_data),
|
||||
}
|
||||
}
|
||||
|
||||
/// This brush instance shares the per-segment data
|
||||
/// across all segments.
|
||||
fn shared(
|
||||
batch_kind: BrushBatchKind,
|
||||
textures: BatchTextures,
|
||||
prim_user_data: [i32; 3],
|
||||
segment_user_data: i32,
|
||||
) -> Self {
|
||||
BrushBatchParameters {
|
||||
batch_kind,
|
||||
prim_user_data,
|
||||
segment_data: SegmentDataKind::Shared(
|
||||
SegmentInstanceData {
|
||||
textures,
|
||||
user_data: segment_user_data,
|
||||
is_opaque_override: None,
|
||||
}
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BrushPrimitive {
|
||||
fn get_batch_params(
|
||||
&self,
|
||||
@ -1433,7 +1553,7 @@ impl BrushPrimitive {
|
||||
gpu_cache: &mut GpuCache,
|
||||
deferred_resolves: &mut Vec<DeferredResolve>,
|
||||
is_chased: bool,
|
||||
) -> Option<(BrushBatchKind, BatchTextures, [i32; 3], i32)> {
|
||||
) -> Option<BrushBatchParameters> {
|
||||
match self.kind {
|
||||
BrushKind::Image { request, ref source, .. } => {
|
||||
let cache_item = match *source {
|
||||
@ -1463,7 +1583,7 @@ impl BrushPrimitive {
|
||||
} else {
|
||||
let textures = BatchTextures::color(cache_item.texture_id);
|
||||
|
||||
Some((
|
||||
Some(BrushBatchParameters::shared(
|
||||
BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
|
||||
textures,
|
||||
[
|
||||
@ -1478,7 +1598,7 @@ impl BrushPrimitive {
|
||||
BrushKind::LineDecoration { ref handle, style, .. } => {
|
||||
match style {
|
||||
LineStyle::Solid => {
|
||||
Some((
|
||||
Some(BrushBatchParameters::shared(
|
||||
BrushBatchKind::Solid,
|
||||
BatchTextures::no_texture(),
|
||||
[0; 3],
|
||||
@ -1492,7 +1612,7 @@ impl BrushPrimitive {
|
||||
.get_cached_render_task(handle.as_ref().unwrap());
|
||||
let cache_item = resource_cache.get_texture_cache_item(&rt_cache_entry.handle);
|
||||
let textures = BatchTextures::color(cache_item.texture_id);
|
||||
Some((
|
||||
Some(BrushBatchParameters::shared(
|
||||
BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
|
||||
textures,
|
||||
[
|
||||
@ -1506,48 +1626,69 @@ impl BrushPrimitive {
|
||||
}
|
||||
}
|
||||
BrushKind::Border { ref source, .. } => {
|
||||
let cache_item = match *source {
|
||||
match *source {
|
||||
BorderSource::Image(request) => {
|
||||
resolve_image(
|
||||
let cache_item = resolve_image(
|
||||
request,
|
||||
resource_cache,
|
||||
gpu_cache,
|
||||
deferred_resolves,
|
||||
)
|
||||
}
|
||||
BorderSource::Border { ref handle, .. } => {
|
||||
let rt_handle = match *handle {
|
||||
Some(ref handle) => handle,
|
||||
None => return None,
|
||||
};
|
||||
let rt_cache_entry = resource_cache
|
||||
.get_cached_render_task(rt_handle);
|
||||
resource_cache.get_texture_cache_item(&rt_cache_entry.handle)
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
if cache_item.texture_id == TextureSource::Invalid {
|
||||
None
|
||||
} else {
|
||||
let textures = BatchTextures::color(cache_item.texture_id);
|
||||
if cache_item.texture_id == TextureSource::Invalid {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((
|
||||
BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
|
||||
textures,
|
||||
[
|
||||
ShaderColorMode::Image as i32,
|
||||
RasterizationSpace::Local as i32,
|
||||
0,
|
||||
],
|
||||
cache_item.uv_rect_handle.as_int(gpu_cache),
|
||||
))
|
||||
let textures = BatchTextures::color(cache_item.texture_id);
|
||||
|
||||
Some(BrushBatchParameters::shared(
|
||||
BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
|
||||
textures,
|
||||
[
|
||||
ShaderColorMode::Image as i32,
|
||||
RasterizationSpace::Local as i32,
|
||||
0,
|
||||
],
|
||||
cache_item.uv_rect_handle.as_int(gpu_cache),
|
||||
))
|
||||
}
|
||||
BorderSource::Border { ref segments, .. } => {
|
||||
let mut segment_data = SmallVec::new();
|
||||
|
||||
// Collect the segment instance data from each render
|
||||
// task for each valid edge / corner of the border.
|
||||
|
||||
for segment in segments {
|
||||
let rt_cache_entry = resource_cache
|
||||
.get_cached_render_task(segment.handle.as_ref().unwrap());
|
||||
let cache_item = resource_cache
|
||||
.get_texture_cache_item(&rt_cache_entry.handle);
|
||||
segment_data.push(
|
||||
SegmentInstanceData {
|
||||
textures: BatchTextures::color(cache_item.texture_id),
|
||||
user_data: cache_item.uv_rect_handle.as_int(gpu_cache),
|
||||
is_opaque_override: Some(segment.is_opaque),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Some(BrushBatchParameters::instanced(
|
||||
BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
|
||||
[
|
||||
ShaderColorMode::Image as i32,
|
||||
RasterizationSpace::Local as i32,
|
||||
0,
|
||||
],
|
||||
segment_data,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
BrushKind::Picture { .. } => {
|
||||
panic!("bug: get_batch_key is handled at higher level for pictures");
|
||||
}
|
||||
BrushKind::Solid { .. } => {
|
||||
Some((
|
||||
Some(BrushBatchParameters::shared(
|
||||
BrushBatchKind::Solid,
|
||||
BatchTextures::no_texture(),
|
||||
[0; 3],
|
||||
@ -1555,7 +1696,7 @@ impl BrushPrimitive {
|
||||
))
|
||||
}
|
||||
BrushKind::Clear => {
|
||||
Some((
|
||||
Some(BrushBatchParameters::shared(
|
||||
BrushBatchKind::Solid,
|
||||
BatchTextures::no_texture(),
|
||||
[0; 3],
|
||||
@ -1563,7 +1704,7 @@ impl BrushPrimitive {
|
||||
))
|
||||
}
|
||||
BrushKind::RadialGradient { ref stops_handle, .. } => {
|
||||
Some((
|
||||
Some(BrushBatchParameters::shared(
|
||||
BrushBatchKind::RadialGradient,
|
||||
BatchTextures::no_texture(),
|
||||
[
|
||||
@ -1575,7 +1716,7 @@ impl BrushPrimitive {
|
||||
))
|
||||
}
|
||||
BrushKind::LinearGradient { ref stops_handle, .. } => {
|
||||
Some((
|
||||
Some(BrushBatchParameters::shared(
|
||||
BrushBatchKind::LinearGradient,
|
||||
BatchTextures::no_texture(),
|
||||
[
|
||||
@ -1631,7 +1772,7 @@ impl BrushPrimitive {
|
||||
color_space,
|
||||
);
|
||||
|
||||
Some((
|
||||
Some(BrushBatchParameters::shared(
|
||||
kind,
|
||||
textures,
|
||||
[
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -405,8 +405,8 @@ impl ClipScrollTree {
|
||||
pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
|
||||
pt.add_item(format!("scroll offset: {:?}", scrolling_info.offset));
|
||||
}
|
||||
SpatialNodeType::ReferenceFrame(ref info) => {
|
||||
pt.new_level(format!("ReferenceFrame {:?}", info.resolved_transform));
|
||||
SpatialNodeType::ReferenceFrame(ref _info) => {
|
||||
pt.new_level(format!("ReferenceFrame"));
|
||||
pt.add_item(format!("index: {:?}", index));
|
||||
}
|
||||
}
|
||||
|
@ -3,18 +3,19 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use super::super::shader_source;
|
||||
use api::{ColorF, ImageFormat};
|
||||
use api::{ColorF, ImageFormat, MemoryReport};
|
||||
use api::{DeviceIntPoint, DeviceIntRect, DeviceUintRect, DeviceUintSize};
|
||||
use api::TextureTarget;
|
||||
#[cfg(any(feature = "debug_renderer", feature="capture"))]
|
||||
use api::ImageDescriptor;
|
||||
use euclid::Transform3D;
|
||||
use gleam::gl;
|
||||
use internal_types::{FastHashMap, RenderTargetInfo};
|
||||
use internal_types::{FastHashMap, LayerIndex, RenderTargetInfo};
|
||||
use log::Level;
|
||||
use smallvec::SmallVec;
|
||||
use std::cell::RefCell;
|
||||
use std::cmp;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::marker::PhantomData;
|
||||
@ -442,9 +443,28 @@ pub struct Texture {
|
||||
width: u32,
|
||||
height: u32,
|
||||
filter: TextureFilter,
|
||||
render_target: Option<RenderTargetInfo>,
|
||||
fbo_ids: Vec<FBOId>,
|
||||
depth_rb: Option<RBOId>,
|
||||
/// Framebuffer Objects, one for each layer of the texture, allowing this
|
||||
/// texture to be rendered to. Empty if this texture is not used as a render
|
||||
/// target.
|
||||
fbos: Vec<FBOId>,
|
||||
/// Same as the above, but with a depth buffer attached.
|
||||
///
|
||||
/// FBOs are cheap to create but expensive to reconfigure (since doing so
|
||||
/// invalidates framebuffer completeness caching). Moreover, rendering with
|
||||
/// a depth buffer attached but the depth write+test disabled relies on the
|
||||
/// driver to optimize it out of the rendering pass, which most drivers
|
||||
/// probably do but, according to jgilbert, is best not to rely on.
|
||||
///
|
||||
/// So we lazily generate a second list of FBOs with depth. This list is
|
||||
/// empty if this texture is not used as a render target _or_ if it is, but
|
||||
/// the depth buffer has never been requested.
|
||||
///
|
||||
/// Note that we always fill fbos, and then lazily create fbos_with_depth
|
||||
/// when needed. We could make both lazy (i.e. render targets would have one
|
||||
/// or the other, but not both, unless they were actually used in both
|
||||
/// configurations). But that would complicate a lot of logic in this module,
|
||||
/// and FBOs are cheap enough to create.
|
||||
fbos_with_depth: Vec<FBOId>,
|
||||
last_frame_used: FrameId,
|
||||
}
|
||||
|
||||
@ -453,10 +473,6 @@ impl Texture {
|
||||
DeviceUintSize::new(self.width, self.height)
|
||||
}
|
||||
|
||||
pub fn get_render_target_layer_count(&self) -> usize {
|
||||
self.fbo_ids.len()
|
||||
}
|
||||
|
||||
pub fn get_layer_count(&self) -> i32 {
|
||||
self.layer_count
|
||||
}
|
||||
@ -470,17 +486,8 @@ impl Texture {
|
||||
self.filter
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "debug_renderer", feature = "capture"))]
|
||||
pub fn get_render_target(&self) -> Option<RenderTargetInfo> {
|
||||
self.render_target.clone()
|
||||
}
|
||||
|
||||
pub fn has_depth(&self) -> bool {
|
||||
self.depth_rb.is_some()
|
||||
}
|
||||
|
||||
pub fn get_rt_info(&self) -> Option<&RenderTargetInfo> {
|
||||
self.render_target.as_ref()
|
||||
pub fn supports_depth(&self) -> bool {
|
||||
!self.fbos_with_depth.is_empty()
|
||||
}
|
||||
|
||||
pub fn used_in_frame(&self, frame_id: FrameId) -> bool {
|
||||
@ -713,6 +720,22 @@ pub enum ShaderError {
|
||||
Link(String, String), // name, error message
|
||||
}
|
||||
|
||||
/// A refcounted depth target, which may be shared by multiple textures across
|
||||
/// the device.
|
||||
struct SharedDepthTarget {
|
||||
/// The Render Buffer Object representing the depth target.
|
||||
rbo_id: RBOId,
|
||||
/// Reference count. When this drops to zero, the RBO is deleted.
|
||||
refcount: usize,
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
impl Drop for SharedDepthTarget {
|
||||
fn drop(&mut self) {
|
||||
debug_assert!(thread::panicking() || self.refcount == 0);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Device {
|
||||
gl: Rc<gl::Gl>,
|
||||
// device state
|
||||
@ -735,6 +758,12 @@ pub struct Device {
|
||||
bgra_format_internal: gl::GLuint,
|
||||
bgra_format_external: gl::GLuint,
|
||||
|
||||
/// Map from texture dimensions to shared depth buffers for render targets.
|
||||
///
|
||||
/// Render targets often have the same width/height, so we can save memory
|
||||
/// by sharing these across targets.
|
||||
depth_targets: FastHashMap<DeviceUintSize, SharedDepthTarget>,
|
||||
|
||||
// debug
|
||||
inside_frame: bool,
|
||||
|
||||
@ -759,6 +788,35 @@ pub struct Device {
|
||||
extensions: Vec<String>,
|
||||
}
|
||||
|
||||
/// Contains the parameters necessary to bind a texture-backed draw target.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TextureDrawTarget<'a> {
|
||||
/// The target texture.
|
||||
pub texture: &'a Texture,
|
||||
/// The slice within the texture array to draw to.
|
||||
pub layer: LayerIndex,
|
||||
/// Whether to draw with the texture's associated depth target.
|
||||
pub with_depth: bool,
|
||||
}
|
||||
|
||||
/// Contains the parameters necessary to bind a texture-backed read target.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TextureReadTarget<'a> {
|
||||
/// The source texture.
|
||||
pub texture: &'a Texture,
|
||||
/// The slice within the texture array to read from.
|
||||
pub layer: LayerIndex,
|
||||
}
|
||||
|
||||
impl<'a> From<TextureDrawTarget<'a>> for TextureReadTarget<'a> {
|
||||
fn from(t: TextureDrawTarget<'a>) -> Self {
|
||||
TextureReadTarget {
|
||||
texture: t.texture,
|
||||
layer: t.layer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Device {
|
||||
pub fn new(
|
||||
gl: Rc<gl::Gl>,
|
||||
@ -829,6 +887,8 @@ impl Device {
|
||||
bgra_format_internal,
|
||||
bgra_format_external,
|
||||
|
||||
depth_targets: FastHashMap::default(),
|
||||
|
||||
bound_textures: [0; 16],
|
||||
bound_program: 0,
|
||||
bound_vao: 0,
|
||||
@ -1006,9 +1066,9 @@ impl Device {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_read_target(&mut self, texture_and_layer: Option<(&Texture, i32)>) {
|
||||
let fbo_id = texture_and_layer.map_or(FBOId(self.default_read_fbo), |texture_and_layer| {
|
||||
texture_and_layer.0.fbo_ids[texture_and_layer.1 as usize]
|
||||
pub fn bind_read_target(&mut self, texture_target: Option<TextureReadTarget>) {
|
||||
let fbo_id = texture_target.map_or(FBOId(self.default_read_fbo), |target| {
|
||||
target.texture.fbos[target.layer]
|
||||
});
|
||||
|
||||
self.bind_read_target_impl(fbo_id)
|
||||
@ -1025,11 +1085,15 @@ impl Device {
|
||||
|
||||
pub fn bind_draw_target(
|
||||
&mut self,
|
||||
texture_and_layer: Option<(&Texture, i32)>,
|
||||
texture_target: Option<TextureDrawTarget>,
|
||||
dimensions: Option<DeviceUintSize>,
|
||||
) {
|
||||
let fbo_id = texture_and_layer.map_or(FBOId(self.default_draw_fbo), |texture_and_layer| {
|
||||
texture_and_layer.0.fbo_ids[texture_and_layer.1 as usize]
|
||||
let fbo_id = texture_target.map_or(FBOId(self.default_draw_fbo), |target| {
|
||||
if target.with_depth {
|
||||
target.texture.fbos_with_depth[target.layer]
|
||||
} else {
|
||||
target.texture.fbos[target.layer]
|
||||
}
|
||||
});
|
||||
|
||||
self.bind_draw_target_impl(fbo_id);
|
||||
@ -1232,9 +1296,8 @@ impl Device {
|
||||
layer_count,
|
||||
format,
|
||||
filter,
|
||||
render_target,
|
||||
fbo_ids: vec![],
|
||||
depth_rb: None,
|
||||
fbos: vec![],
|
||||
fbos_with_depth: vec![],
|
||||
last_frame_used: self.frame_id,
|
||||
};
|
||||
self.bind_texture(DEFAULT_TEXTURE, &texture);
|
||||
@ -1309,7 +1372,10 @@ impl Device {
|
||||
|
||||
// Set up FBOs, if required.
|
||||
if let Some(rt_info) = render_target {
|
||||
self.init_fbos(&mut texture, rt_info);
|
||||
self.init_fbos(&mut texture, false);
|
||||
if rt_info.has_depth {
|
||||
self.init_fbos(&mut texture, true);
|
||||
}
|
||||
}
|
||||
|
||||
texture
|
||||
@ -1349,7 +1415,7 @@ impl Device {
|
||||
debug_assert!(dst.height >= src.height);
|
||||
|
||||
let rect = DeviceIntRect::new(DeviceIntPoint::zero(), src.get_dimensions().to_i32());
|
||||
for (read_fbo, draw_fbo) in src.fbo_ids.iter().zip(&dst.fbo_ids) {
|
||||
for (read_fbo, draw_fbo) in src.fbos.iter().zip(&dst.fbos) {
|
||||
self.bind_read_target_impl(*read_fbo);
|
||||
self.bind_draw_target_impl(*draw_fbo);
|
||||
self.blit_render_target(rect, rect);
|
||||
@ -1359,15 +1425,19 @@ impl Device {
|
||||
|
||||
/// Notifies the device that the contents of a render target are no longer
|
||||
/// needed.
|
||||
///
|
||||
/// FIXME(bholley): We could/should invalidate the depth targets earlier
|
||||
/// than the color targets, i.e. immediately after each pass.
|
||||
pub fn invalidate_render_target(&mut self, texture: &Texture) {
|
||||
let attachments: &[gl::GLenum] = if texture.has_depth() {
|
||||
&[gl::COLOR_ATTACHMENT0, gl::DEPTH_ATTACHMENT]
|
||||
let (fbos, attachments) = if texture.supports_depth() {
|
||||
(&texture.fbos_with_depth,
|
||||
&[gl::COLOR_ATTACHMENT0, gl::DEPTH_ATTACHMENT] as &[gl::GLenum])
|
||||
} else {
|
||||
&[gl::COLOR_ATTACHMENT0]
|
||||
(&texture.fbos, &[gl::COLOR_ATTACHMENT0] as &[gl::GLenum])
|
||||
};
|
||||
|
||||
let original_bound_fbo = self.bound_draw_fbo;
|
||||
for fbo_id in texture.fbo_ids.iter() {
|
||||
for fbo_id in fbos.iter() {
|
||||
// Note: The invalidate extension may not be supported, in which
|
||||
// case this is a no-op. That's ok though, because it's just a
|
||||
// hint.
|
||||
@ -1386,42 +1456,28 @@ impl Device {
|
||||
rt_info: RenderTargetInfo,
|
||||
) {
|
||||
texture.last_frame_used = self.frame_id;
|
||||
texture.render_target = Some(rt_info);
|
||||
|
||||
// If the depth target requirements changed, just drop the FBOs and
|
||||
// reinitialize.
|
||||
//
|
||||
// FIXME(bholley): I have a patch to do this better.
|
||||
if rt_info.has_depth != texture.has_depth() {
|
||||
self.deinit_fbos(texture);
|
||||
self.init_fbos(texture, rt_info);
|
||||
// Add depth support if needed.
|
||||
if rt_info.has_depth && !texture.supports_depth() {
|
||||
self.init_fbos(texture, true);
|
||||
}
|
||||
}
|
||||
|
||||
fn init_fbos(&mut self, texture: &mut Texture, rt_info: RenderTargetInfo) {
|
||||
// Generate the FBOs.
|
||||
assert!(texture.fbo_ids.is_empty());
|
||||
texture.fbo_ids.extend(
|
||||
self.gl.gen_framebuffers(texture.layer_count).into_iter().map(FBOId)
|
||||
);
|
||||
fn init_fbos(&mut self, texture: &mut Texture, with_depth: bool) {
|
||||
let (fbos, depth_rb) = if with_depth {
|
||||
let depth_target = self.acquire_depth_target(texture.get_dimensions());
|
||||
(&mut texture.fbos_with_depth, Some(depth_target))
|
||||
} else {
|
||||
(&mut texture.fbos, None)
|
||||
};
|
||||
|
||||
// Optionally generate a depth target.
|
||||
if rt_info.has_depth {
|
||||
let renderbuffer_ids = self.gl.gen_renderbuffers(1);
|
||||
let depth_rb = renderbuffer_ids[0];
|
||||
texture.depth_rb = Some(RBOId(depth_rb));
|
||||
self.gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
|
||||
self.gl.renderbuffer_storage(
|
||||
gl::RENDERBUFFER,
|
||||
gl::DEPTH_COMPONENT24,
|
||||
texture.width as _,
|
||||
texture.height as _,
|
||||
);
|
||||
}
|
||||
// Generate the FBOs.
|
||||
assert!(fbos.is_empty());
|
||||
fbos.extend(self.gl.gen_framebuffers(texture.layer_count).into_iter().map(FBOId));
|
||||
|
||||
// Bind the FBOs.
|
||||
let original_bound_fbo = self.bound_draw_fbo;
|
||||
for (fbo_index, &fbo_id) in texture.fbo_ids.iter().enumerate() {
|
||||
for (fbo_index, &fbo_id) in fbos.iter().enumerate() {
|
||||
self.bind_external_draw_target(fbo_id);
|
||||
match texture.target {
|
||||
gl::TEXTURE_2D_ARRAY => {
|
||||
@ -1445,7 +1501,7 @@ impl Device {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(depth_rb) = texture.depth_rb {
|
||||
if let Some(depth_rb) = depth_rb {
|
||||
self.gl.framebuffer_renderbuffer(
|
||||
gl::DRAW_FRAMEBUFFER,
|
||||
gl::DEPTH_ATTACHMENT,
|
||||
@ -1457,15 +1513,9 @@ impl Device {
|
||||
self.bind_external_draw_target(original_bound_fbo);
|
||||
}
|
||||
|
||||
fn deinit_fbos(&mut self, texture: &mut Texture) {
|
||||
if let Some(RBOId(depth_rb)) = texture.depth_rb.take() {
|
||||
self.gl.delete_renderbuffers(&[depth_rb]);
|
||||
texture.depth_rb = None;
|
||||
}
|
||||
|
||||
if !texture.fbo_ids.is_empty() {
|
||||
let fbo_ids: Vec<_> = texture
|
||||
.fbo_ids
|
||||
fn deinit_fbos(&mut self, fbos: &mut Vec<FBOId>) {
|
||||
if !fbos.is_empty() {
|
||||
let fbo_ids: SmallVec<[gl::GLuint; 8]> = fbos
|
||||
.drain(..)
|
||||
.map(|FBOId(fbo_id)| fbo_id)
|
||||
.collect();
|
||||
@ -1473,6 +1523,40 @@ impl Device {
|
||||
}
|
||||
}
|
||||
|
||||
fn acquire_depth_target(&mut self, dimensions: DeviceUintSize) -> RBOId {
|
||||
let gl = &self.gl;
|
||||
let target = self.depth_targets.entry(dimensions).or_insert_with(|| {
|
||||
let renderbuffer_ids = gl.gen_renderbuffers(1);
|
||||
let depth_rb = renderbuffer_ids[0];
|
||||
gl.bind_renderbuffer(gl::RENDERBUFFER, depth_rb);
|
||||
gl.renderbuffer_storage(
|
||||
gl::RENDERBUFFER,
|
||||
gl::DEPTH_COMPONENT24,
|
||||
dimensions.width as _,
|
||||
dimensions.height as _,
|
||||
);
|
||||
SharedDepthTarget {
|
||||
rbo_id: RBOId(depth_rb),
|
||||
refcount: 0,
|
||||
}
|
||||
});
|
||||
target.refcount += 1;
|
||||
target.rbo_id
|
||||
}
|
||||
|
||||
fn release_depth_target(&mut self, dimensions: DeviceUintSize) {
|
||||
let mut entry = match self.depth_targets.entry(dimensions) {
|
||||
Entry::Occupied(x) => x,
|
||||
Entry::Vacant(..) => panic!("Releasing unknown depth target"),
|
||||
};
|
||||
debug_assert!(entry.get().refcount != 0);
|
||||
entry.get_mut().refcount -= 1;
|
||||
if entry.get().refcount == 0 {
|
||||
let t = entry.remove();
|
||||
self.gl.delete_renderbuffers(&[t.rbo_id.0]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blit_render_target(&mut self, src_rect: DeviceIntRect, dest_rect: DeviceIntRect) {
|
||||
debug_assert!(self.inside_frame);
|
||||
|
||||
@ -1492,7 +1576,13 @@ impl Device {
|
||||
|
||||
pub fn delete_texture(&mut self, mut texture: Texture) {
|
||||
debug_assert!(self.inside_frame);
|
||||
self.deinit_fbos(&mut texture);
|
||||
let had_depth = texture.supports_depth();
|
||||
self.deinit_fbos(&mut texture.fbos);
|
||||
self.deinit_fbos(&mut texture.fbos_with_depth);
|
||||
if had_depth {
|
||||
self.release_depth_target(texture.get_dimensions());
|
||||
}
|
||||
|
||||
self.gl.delete_textures(&[texture.id]);
|
||||
|
||||
for bound_texture in &mut self.bound_textures {
|
||||
@ -2317,6 +2407,18 @@ impl Device {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a memory report for the resources managed by the device layer.
|
||||
pub fn report_memory(&self) -> MemoryReport {
|
||||
let mut report = MemoryReport::default();
|
||||
for dim in self.depth_targets.keys() {
|
||||
// DEPTH24 textures generally reserve 3 bytes for depth and 1 byte
|
||||
// for stencil, so we measure them as 32 bytes.
|
||||
let pixels: u32 = dim.width * dim.height;
|
||||
report.depth_target_textures += (pixels as usize) * 4;
|
||||
}
|
||||
report
|
||||
}
|
||||
}
|
||||
|
||||
struct FormatDesc {
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter, ClipAndScrollInfo};
|
||||
use api::{ClipId, ColorF, ComplexClipRegion, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
|
||||
use api::{DevicePixelScale, DeviceUintRect, DisplayItemRef, ExtendMode, ExternalScrollId};
|
||||
use api::{DisplayItemRef, ExtendMode, ExternalScrollId};
|
||||
use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, RasterSpace, GradientStop};
|
||||
use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint, ColorDepth};
|
||||
use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
|
||||
@ -32,7 +32,7 @@ use render_backend::{DocumentView};
|
||||
use resource_cache::{FontInstanceMap, ImageRequest};
|
||||
use scene::{Scene, ScenePipeline, StackingContextHelpers};
|
||||
use scene_builder::DocumentResources;
|
||||
use spatial_node::{SpatialNodeType, StickyFrameInfo};
|
||||
use spatial_node::{StickyFrameInfo};
|
||||
use std::{f32, mem};
|
||||
use std::collections::vec_deque::VecDeque;
|
||||
use tiling::{CompositeOps};
|
||||
@ -209,7 +209,6 @@ impl<'a> DisplayListFlattener<'a> {
|
||||
&root_pipeline.viewport_size,
|
||||
&root_pipeline.content_size,
|
||||
);
|
||||
flattener.setup_viewport_offset(view.inner_rect, view.accumulated_scale_factor());
|
||||
flattener.flatten_root(root_pipeline, &root_pipeline.viewport_size);
|
||||
|
||||
debug_assert!(flattener.sc_stack.is_empty());
|
||||
@ -1257,20 +1256,6 @@ impl<'a> DisplayListFlattener<'a> {
|
||||
index
|
||||
}
|
||||
|
||||
pub fn setup_viewport_offset(
|
||||
&mut self,
|
||||
inner_rect: DeviceUintRect,
|
||||
device_pixel_scale: DevicePixelScale,
|
||||
) {
|
||||
let viewport_offset = (inner_rect.origin.to_vector().to_f32() / device_pixel_scale).round();
|
||||
let root_id = self.clip_scroll_tree.root_reference_frame_index();
|
||||
let root_node = &mut self.clip_scroll_tree.spatial_nodes[root_id.0];
|
||||
if let SpatialNodeType::ReferenceFrame(ref mut info) = root_node.node_type {
|
||||
info.resolved_transform =
|
||||
LayoutVector2D::new(viewport_offset.x, viewport_offset.y).into();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_root(
|
||||
&mut self,
|
||||
pipeline_id: PipelineId,
|
||||
@ -1484,7 +1469,10 @@ impl<'a> DisplayListFlattener<'a> {
|
||||
&info,
|
||||
pending_primitive.clip_and_scroll.clip_chain_id,
|
||||
pending_primitive.clip_and_scroll.spatial_node_index,
|
||||
pending_primitive.container.create_shadow(&pending_shadow.shadow),
|
||||
pending_primitive.container.create_shadow(
|
||||
&pending_shadow.shadow,
|
||||
&info.rect,
|
||||
),
|
||||
);
|
||||
|
||||
// Add the new primitive to the shadow picture.
|
||||
@ -1687,7 +1675,9 @@ impl<'a> DisplayListFlattener<'a> {
|
||||
|
||||
// Use segment relative interpolation for all
|
||||
// instances in this primitive.
|
||||
let mut brush_flags = BrushFlags::SEGMENT_RELATIVE;
|
||||
let mut brush_flags =
|
||||
BrushFlags::SEGMENT_RELATIVE |
|
||||
BrushFlags::SEGMENT_TEXEL_RECT;
|
||||
|
||||
// Enable repeat modes on the segment.
|
||||
if repeat_horizontal == RepeatMode::Repeat {
|
||||
@ -1845,7 +1835,12 @@ impl<'a> DisplayListFlattener<'a> {
|
||||
self.add_primitive(clip_and_scroll, info, Vec::new(), prim);
|
||||
}
|
||||
BorderDetails::Normal(ref border) => {
|
||||
self.add_normal_border(info, border, &border_item.widths, clip_and_scroll);
|
||||
self.add_normal_border(
|
||||
info,
|
||||
border,
|
||||
border_item.widths,
|
||||
clip_and_scroll,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
use api::{DeviceIntPoint, DeviceIntRect, DeviceUintSize, FontRenderMode};
|
||||
use api::{ImageFormat, TextureTarget};
|
||||
use debug_colors;
|
||||
use device::{Device, Texture, TextureFilter, VAO};
|
||||
use device::{Device, Texture, TextureDrawTarget, TextureFilter, VAO};
|
||||
use euclid::{Point2D, Size2D, Transform3D, TypedVector2D, Vector2D};
|
||||
use internal_types::RenderTargetInfo;
|
||||
use pathfinder_gfx_utils::ShelfBinPacker;
|
||||
@ -194,7 +194,11 @@ impl Renderer {
|
||||
projection,
|
||||
&mut self.renderer_errors);
|
||||
|
||||
self.device.bind_draw_target(Some((¤t_page.texture, 0)), Some(*target_size));
|
||||
self.device.bind_draw_target(Some(TextureDrawTarget {
|
||||
texture: ¤t_page.texture,
|
||||
layer: 0,
|
||||
with_depth: false,
|
||||
}), Some(*target_size));
|
||||
self.device.clear_target(Some([0.0, 0.0, 0.0, 0.0]), None, None);
|
||||
|
||||
self.device.set_blend(true);
|
||||
|
@ -89,7 +89,7 @@ pub struct ScalingInstance {
|
||||
pub src_task_address: RenderTaskAddress,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
#[repr(C)]
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
@ -318,6 +318,8 @@ bitflags! {
|
||||
const SEGMENT_REPEAT_X = 0x4;
|
||||
/// Repeat UVs vertically.
|
||||
const SEGMENT_REPEAT_Y = 0x8;
|
||||
/// The extra segment data is a texel rect.
|
||||
const SEGMENT_TEXEL_RECT = 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,19 @@ pub type FastHashSet<K> = HashSet<K, BuildHasherDefault<FxHasher>>;
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct CacheTextureId(pub u64);
|
||||
|
||||
/// Canonical type for texture layer indices.
|
||||
///
|
||||
/// WebRender is currently not very consistent about layer index types. Some
|
||||
/// places use i32 (since that's the type used in various OpenGL APIs), some
|
||||
/// places use u32 (since having it be signed is non-sensical, but the
|
||||
/// underlying graphics APIs generally operate on 32-bit integers) and some
|
||||
/// places use usize (since that's most natural in Rust).
|
||||
///
|
||||
/// Going forward, we aim to us usize throughout the codebase, since that allows
|
||||
/// operations like indexing without a cast, and convert to the required type in
|
||||
/// the device module when making calls into the platform layer.
|
||||
pub type LayerIndex = usize;
|
||||
|
||||
/// Identifies a render pass target that is persisted until the end of the frame.
|
||||
///
|
||||
/// By default, only the targets of the immediately-preceding pass are bound as
|
||||
|
@ -131,9 +131,20 @@ impl FontContext {
|
||||
}
|
||||
|
||||
let system_fc = dwrote::FontCollection::system();
|
||||
let font = match system_fc.get_font_from_descriptor(&font_handle) {
|
||||
Some(font) => font,
|
||||
None => { panic!("missing descriptor {:?}", font_handle) }
|
||||
// A version of get_font_from_descriptor() that panics early to help with bug 1455848
|
||||
let font = if let Some(family) = system_fc.get_font_family_by_name(&font_handle.family_name) {
|
||||
let font = family.get_first_matching_font(font_handle.weight, font_handle.stretch, font_handle.style);
|
||||
// Exact matches only here
|
||||
if font.weight() == font_handle.weight &&
|
||||
font.stretch() == font_handle.stretch &&
|
||||
font.style() == font_handle.style
|
||||
{
|
||||
font
|
||||
} else {
|
||||
panic!("font mismatch for descriptor {:?} {:?}", font_handle, font.to_descriptor())
|
||||
}
|
||||
} else {
|
||||
panic!("missing font family for descriptor {:?}", font_handle)
|
||||
};
|
||||
let face = font.create_font_face();
|
||||
self.fonts.insert(*font_key, face);
|
||||
|
@ -7,10 +7,10 @@ use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, ExtendMode, DeviceRect
|
||||
use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, TileOffset};
|
||||
use api::{RasterSpace, LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize, LayoutToWorldTransform};
|
||||
use api::{LayoutVector2D, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat};
|
||||
use api::{DeviceIntSideOffsets, WorldPixel, BoxShadowClipMode, LayoutToWorldScale, NormalBorder, WorldRect};
|
||||
use api::{DeviceIntSideOffsets, WorldPixel, BoxShadowClipMode, NormalBorder, WorldRect, LayoutToWorldScale};
|
||||
use api::{PicturePixel, RasterPixel, ColorDepth, LineStyle, LineOrientation, LayoutSizeAu, AuHelpers};
|
||||
use app_units::Au;
|
||||
use border::{BorderCacheKey, BorderRenderTaskInfo};
|
||||
use border::{get_max_scale_for_border, build_border_instances, create_normal_border_prim};
|
||||
use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, SpatialNodeIndex};
|
||||
use clip::{ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector};
|
||||
use euclid::{TypedTransform3D, TypedRect, TypedScale};
|
||||
@ -25,7 +25,7 @@ use intern;
|
||||
use picture::{PictureCompositeMode, PicturePrimitive};
|
||||
#[cfg(debug_assertions)]
|
||||
use render_backend::FrameId;
|
||||
use render_task::{BlitSource, RenderTask, RenderTaskCacheKey};
|
||||
use render_task::{BlitSource, RenderTask, RenderTaskCacheKey, to_cache_size};
|
||||
use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
|
||||
use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
|
||||
use resource_cache::{ImageProperties, ImageRequest, ResourceCache};
|
||||
@ -345,13 +345,21 @@ pub struct VisibleGradientTile {
|
||||
pub local_clip_rect: LayoutRect,
|
||||
}
|
||||
|
||||
/// Information about how to cache a border segment,
|
||||
/// along with the current render task cache entry.
|
||||
#[derive(Debug)]
|
||||
pub struct BorderSegmentInfo {
|
||||
pub handle: Option<RenderTaskCacheEntryHandle>,
|
||||
pub local_task_size: LayoutSize,
|
||||
pub cache_key: RenderTaskCacheKey,
|
||||
pub is_opaque: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BorderSource {
|
||||
Image(ImageRequest),
|
||||
Border {
|
||||
handle: Option<RenderTaskCacheEntryHandle>,
|
||||
cache_key: BorderCacheKey,
|
||||
task_info: Option<BorderRenderTaskInfo>,
|
||||
segments: SmallVec<[BorderSegmentInfo; 8]>,
|
||||
border: NormalBorder,
|
||||
widths: LayoutSideOffsets,
|
||||
},
|
||||
@ -456,18 +464,19 @@ impl BrushKind {
|
||||
|
||||
// Construct a brush that is a border with `border` style and `widths`
|
||||
// dimensions.
|
||||
pub fn new_border(mut border: NormalBorder, widths: LayoutSideOffsets) -> BrushKind {
|
||||
pub fn new_border(
|
||||
mut border: NormalBorder,
|
||||
widths: LayoutSideOffsets,
|
||||
segments: SmallVec<[BorderSegmentInfo; 8]>,
|
||||
) -> BrushKind {
|
||||
// FIXME(emilio): Is this the best place to do this?
|
||||
border.normalize(&widths);
|
||||
|
||||
let cache_key = BorderCacheKey::new(&border, &widths);
|
||||
BrushKind::Border {
|
||||
source: BorderSource::Border {
|
||||
border,
|
||||
widths,
|
||||
cache_key,
|
||||
task_info: None,
|
||||
handle: None,
|
||||
segments,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1412,7 +1421,11 @@ impl PrimitiveContainer {
|
||||
// Create a clone of this PrimitiveContainer, applying whatever
|
||||
// changes are necessary to the primitive to support rendering
|
||||
// it as part of the supplied shadow.
|
||||
pub fn create_shadow(&self, shadow: &Shadow) -> PrimitiveContainer {
|
||||
pub fn create_shadow(
|
||||
&self,
|
||||
shadow: &Shadow,
|
||||
prim_rect: &LayoutRect,
|
||||
) -> PrimitiveContainer {
|
||||
match *self {
|
||||
PrimitiveContainer::TextRun(ref info) => {
|
||||
let mut font = FontInstance {
|
||||
@ -1440,22 +1453,25 @@ impl PrimitiveContainer {
|
||||
))
|
||||
}
|
||||
BrushKind::Border { ref source } => {
|
||||
let source = match *source {
|
||||
let prim = match *source {
|
||||
BorderSource::Image(request) => {
|
||||
BrushKind::Border {
|
||||
source: BorderSource::Image(request)
|
||||
}
|
||||
},
|
||||
BrushPrimitive::new(
|
||||
BrushKind::Border {
|
||||
source: BorderSource::Image(request)
|
||||
},
|
||||
None,
|
||||
)
|
||||
}
|
||||
BorderSource::Border { border, widths, .. } => {
|
||||
let border = border.with_color(shadow.color);
|
||||
BrushKind::new_border(border, widths)
|
||||
|
||||
create_normal_border_prim(
|
||||
prim_rect,
|
||||
border,
|
||||
widths,
|
||||
)
|
||||
}
|
||||
};
|
||||
PrimitiveContainer::Brush(BrushPrimitive::new(
|
||||
source,
|
||||
None,
|
||||
))
|
||||
PrimitiveContainer::Brush(prim)
|
||||
}
|
||||
BrushKind::LineDecoration { style, orientation, wavy_line_thickness, .. } => {
|
||||
PrimitiveContainer::Brush(BrushPrimitive::new_line_decoration(
|
||||
@ -1960,13 +1976,6 @@ impl PrimitiveStore {
|
||||
|
||||
prim_instance.clipped_world_rect = Some(clipped_world_rect);
|
||||
|
||||
prim.build_prim_segments_if_needed(
|
||||
prim_instance,
|
||||
pic_state,
|
||||
frame_state,
|
||||
frame_context,
|
||||
);
|
||||
|
||||
prim.update_clip_task(
|
||||
prim_instance,
|
||||
prim_context,
|
||||
@ -2839,9 +2848,46 @@ impl Primitive {
|
||||
);
|
||||
}
|
||||
}
|
||||
BorderSource::Border { .. } => {
|
||||
// Handled earlier since we need to update the segment
|
||||
// descriptor *before* update_clip_task() is called.
|
||||
BorderSource::Border { ref border, ref widths, ref mut segments, .. } => {
|
||||
// TODO(gw): When drawing in screen raster mode, we should also incorporate a
|
||||
// scale factor from the world transform to get an appropriately
|
||||
// sized border task.
|
||||
let world_scale = LayoutToWorldScale::new(1.0);
|
||||
let mut scale = world_scale * frame_context.device_pixel_scale;
|
||||
let max_scale = get_max_scale_for_border(&border.radius, widths);
|
||||
scale.0 = scale.0.min(max_scale.0);
|
||||
|
||||
// For each edge and corner, request the render task by content key
|
||||
// from the render task cache. This ensures that the render task for
|
||||
// this segment will be available for batching later in the frame.
|
||||
for segment in segments {
|
||||
// Update the cache key device size based on requested scale.
|
||||
segment.cache_key.size = to_cache_size(segment.local_task_size * scale);
|
||||
|
||||
segment.handle = Some(frame_state.resource_cache.request_render_task(
|
||||
segment.cache_key.clone(),
|
||||
frame_state.gpu_cache,
|
||||
frame_state.render_tasks,
|
||||
None,
|
||||
segment.is_opaque,
|
||||
|render_tasks| {
|
||||
let task = RenderTask::new_border_segment(
|
||||
segment.cache_key.size,
|
||||
build_border_instances(
|
||||
&segment.cache_key,
|
||||
border,
|
||||
scale,
|
||||
),
|
||||
);
|
||||
|
||||
let task_id = render_tasks.add(task);
|
||||
|
||||
pic_state.tasks.push(task_id);
|
||||
|
||||
task_id
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3092,95 +3138,6 @@ impl Primitive {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_prim_segments_if_needed(
|
||||
&mut self,
|
||||
prim_instance: &mut PrimitiveInstance,
|
||||
pic_state: &mut PictureState,
|
||||
frame_state: &mut FrameBuildingState,
|
||||
frame_context: &FrameBuildingContext,
|
||||
) {
|
||||
let brush = match self.details {
|
||||
PrimitiveDetails::Brush(ref mut brush) => brush,
|
||||
PrimitiveDetails::TextRun(..) => return,
|
||||
};
|
||||
|
||||
if let BrushKind::Border {
|
||||
source: BorderSource::Border {
|
||||
ref border,
|
||||
ref mut cache_key,
|
||||
ref widths,
|
||||
ref mut handle,
|
||||
ref mut task_info,
|
||||
..
|
||||
}
|
||||
} = brush.kind {
|
||||
// TODO(gw): When drawing in screen raster mode, we should also incorporate a
|
||||
// scale factor from the world transform to get an appropriately
|
||||
// sized border task.
|
||||
let world_scale = LayoutToWorldScale::new(1.0);
|
||||
let mut scale = world_scale * frame_context.device_pixel_scale;
|
||||
let max_scale = BorderRenderTaskInfo::get_max_scale(&border.radius, &widths);
|
||||
scale.0 = scale.0.min(max_scale.0);
|
||||
let scale_au = Au::from_f32_px(scale.0);
|
||||
|
||||
// NOTE(emilio): This `needs_update` relies on the local rect for a
|
||||
// given primitive being immutable. If that changes, this code
|
||||
// should probably handle changes to it as well, retaining the old
|
||||
// size in cache_key.
|
||||
let needs_update = scale_au != cache_key.scale;
|
||||
|
||||
let mut new_segments = BrushSegmentVec::new();
|
||||
|
||||
let local_rect = &self.metadata.local_rect;
|
||||
if needs_update {
|
||||
cache_key.scale = scale_au;
|
||||
|
||||
*task_info = BorderRenderTaskInfo::new(
|
||||
local_rect,
|
||||
border,
|
||||
widths,
|
||||
scale,
|
||||
&mut new_segments,
|
||||
);
|
||||
}
|
||||
|
||||
*handle = task_info.as_ref().map(|task_info| {
|
||||
frame_state.resource_cache.request_render_task(
|
||||
RenderTaskCacheKey {
|
||||
size: task_info.cache_key_size(&local_rect.size, scale),
|
||||
kind: RenderTaskCacheKeyKind::Border(cache_key.clone()),
|
||||
},
|
||||
frame_state.gpu_cache,
|
||||
frame_state.render_tasks,
|
||||
None,
|
||||
false, // todo
|
||||
|render_tasks| {
|
||||
let task = RenderTask::new_border(
|
||||
task_info.size,
|
||||
task_info.build_instances(border),
|
||||
);
|
||||
|
||||
let task_id = render_tasks.add(task);
|
||||
|
||||
pic_state.tasks.push(task_id);
|
||||
|
||||
task_id
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
if needs_update {
|
||||
brush.segment_desc = Some(BrushSegmentDescriptor {
|
||||
segments: new_segments,
|
||||
});
|
||||
|
||||
// The segments have changed, so force the GPU cache to
|
||||
// re-upload the primitive information.
|
||||
frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_raster_rects(
|
||||
|
@ -277,6 +277,12 @@ impl Document {
|
||||
FrameMsg::AppendDynamicProperties(property_bindings) => {
|
||||
self.dynamic_properties.add_properties(property_bindings);
|
||||
}
|
||||
FrameMsg::SetPinchZoom(factor) => {
|
||||
if self.view.pinch_zoom_factor != factor.get() {
|
||||
self.view.pinch_zoom_factor = factor.get();
|
||||
self.frame_is_valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DocumentOps::nop()
|
||||
@ -508,9 +514,6 @@ impl RenderBackend {
|
||||
SceneMsg::SetPageZoom(factor) => {
|
||||
doc.view.page_zoom_factor = factor.get();
|
||||
}
|
||||
SceneMsg::SetPinchZoom(factor) => {
|
||||
doc.view.pinch_zoom_factor = factor.get();
|
||||
}
|
||||
SceneMsg::SetWindowParameters {
|
||||
window_size,
|
||||
inner_rect,
|
||||
|
@ -7,7 +7,7 @@ use api::{DevicePixelScale, ImageDescriptor, ImageFormat};
|
||||
use api::{LineStyle, LineOrientation, LayoutSize};
|
||||
#[cfg(feature = "pathfinder")]
|
||||
use api::FontRenderMode;
|
||||
use border::BorderCacheKey;
|
||||
use border::{BorderCornerCacheKey, BorderEdgeCacheKey};
|
||||
use box_shadow::{BoxShadowCacheKey};
|
||||
use clip::{ClipDataStore, ClipItem, ClipStore, ClipNodeRange};
|
||||
use clip_scroll_tree::SpatialNodeIndex;
|
||||
@ -18,7 +18,7 @@ use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
|
||||
use glyph_rasterizer::GpuGlyphCacheKey;
|
||||
use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
|
||||
use gpu_types::{BorderInstance, ImageSource, UvRectKind};
|
||||
use internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex};
|
||||
use internal_types::{CacheTextureId, FastHashMap, LayerIndex, SavedTargetIndex};
|
||||
#[cfg(feature = "pathfinder")]
|
||||
use pathfinder_partitioner::mesh::Mesh;
|
||||
use picture::PictureCacheKey;
|
||||
@ -113,7 +113,7 @@ impl RenderTaskTree {
|
||||
debug_assert!(pass_index == passes.len() - 1);
|
||||
}
|
||||
RenderTaskLocation::Dynamic(..) |
|
||||
RenderTaskLocation::TextureCache(..) => {
|
||||
RenderTaskLocation::TextureCache { .. } => {
|
||||
debug_assert!(pass_index < passes.len() - 1);
|
||||
}
|
||||
}
|
||||
@ -192,7 +192,14 @@ pub enum RenderTaskLocation {
|
||||
Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
|
||||
/// The output of the `RenderTask` will be persisted beyond this frame, and
|
||||
/// thus should be drawn into the `TextureCache`.
|
||||
TextureCache(CacheTextureId, i32, DeviceIntRect),
|
||||
TextureCache {
|
||||
/// Which texture in the texture cache should be drawn into.
|
||||
texture: CacheTextureId,
|
||||
/// The target layer in the above texture.
|
||||
layer: LayerIndex,
|
||||
/// The target region within the above layer.
|
||||
rect: DeviceIntRect,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -386,7 +393,7 @@ impl RenderTask {
|
||||
let size = match location {
|
||||
RenderTaskLocation::Dynamic(_, size) => size,
|
||||
RenderTaskLocation::Fixed(rect) => rect.size,
|
||||
RenderTaskLocation::TextureCache(_, _, rect) => rect.size,
|
||||
RenderTaskLocation::TextureCache { rect, .. } => rect.size,
|
||||
};
|
||||
|
||||
render_task_sanity_check(&size);
|
||||
@ -655,7 +662,7 @@ impl RenderTask {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_border(
|
||||
pub fn new_border_segment(
|
||||
size: DeviceIntSize,
|
||||
instances: Vec<BorderInstance>,
|
||||
) -> Self {
|
||||
@ -839,7 +846,7 @@ impl RenderTask {
|
||||
match self.location {
|
||||
RenderTaskLocation::Fixed(..) => DeviceIntSize::zero(),
|
||||
RenderTaskLocation::Dynamic(_, size) => size,
|
||||
RenderTaskLocation::TextureCache(_, _, rect) => rect.size,
|
||||
RenderTaskLocation::TextureCache { rect, .. } => rect.size,
|
||||
}
|
||||
}
|
||||
|
||||
@ -868,7 +875,7 @@ impl RenderTask {
|
||||
RenderTaskLocation::Dynamic(None, _) => {
|
||||
(DeviceIntRect::zero(), RenderTargetIndex(0))
|
||||
}
|
||||
RenderTaskLocation::TextureCache(_, layer, rect) => {
|
||||
RenderTaskLocation::TextureCache {layer, rect, .. } => {
|
||||
(rect, RenderTargetIndex(layer as usize))
|
||||
}
|
||||
}
|
||||
@ -1044,7 +1051,7 @@ impl RenderTask {
|
||||
RenderTaskLocation::Dynamic(..) => {
|
||||
self.saved_index = Some(SavedTargetIndex::PENDING);
|
||||
}
|
||||
RenderTaskLocation::TextureCache(..) => {
|
||||
RenderTaskLocation::TextureCache { .. } => {
|
||||
panic!("Unable to mark a permanently cached task for saving!");
|
||||
}
|
||||
}
|
||||
@ -1060,7 +1067,8 @@ pub enum RenderTaskCacheKeyKind {
|
||||
#[allow(dead_code)]
|
||||
Glyph(GpuGlyphCacheKey),
|
||||
Picture(PictureCacheKey),
|
||||
Border(BorderCacheKey),
|
||||
BorderEdge(BorderEdgeCacheKey),
|
||||
BorderCorner(BorderCornerCacheKey),
|
||||
LineDecoration(LineDecorationCacheKey),
|
||||
}
|
||||
|
||||
@ -1161,7 +1169,7 @@ impl RenderTaskCache {
|
||||
// Find out what size to alloc in the texture cache.
|
||||
let size = match render_task.location {
|
||||
RenderTaskLocation::Fixed(..) |
|
||||
RenderTaskLocation::TextureCache(..) => {
|
||||
RenderTaskLocation::TextureCache { .. } => {
|
||||
panic!("BUG: dynamic task was expected");
|
||||
}
|
||||
RenderTaskLocation::Dynamic(_, size) => size,
|
||||
@ -1203,11 +1211,11 @@ impl RenderTaskCache {
|
||||
let (texture_id, texture_layer, uv_rect) =
|
||||
texture_cache.get_cache_location(&entry.handle);
|
||||
|
||||
render_task.location = RenderTaskLocation::TextureCache(
|
||||
texture_id,
|
||||
texture_layer,
|
||||
uv_rect.to_i32()
|
||||
);
|
||||
render_task.location = RenderTaskLocation::TextureCache {
|
||||
texture: texture_id,
|
||||
layer: texture_layer,
|
||||
rect: uv_rect.to_i32(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ use batch::{BatchKind, BatchTextures, BrushBatchKind};
|
||||
use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
|
||||
use debug_colors;
|
||||
use device::{DepthFunction, Device, FrameId, Program, UploadMethod, Texture, PBO};
|
||||
use device::{ExternalTexture, FBOId, TextureSlot};
|
||||
use device::{ExternalTexture, FBOId, TextureDrawTarget, TextureReadTarget, TextureSlot};
|
||||
use device::{ShaderError, TextureFilter,
|
||||
VertexUsageHint, VAO, VBO, CustomVAO};
|
||||
use device::{ProgramCache, ReadPixelsFormat};
|
||||
@ -54,7 +54,7 @@ use gpu_glyph_renderer::GpuGlyphRenderer;
|
||||
use gpu_types::ScalingInstance;
|
||||
use internal_types::{TextureSource, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError};
|
||||
use internal_types::{CacheTextureId, DebugOutput, FastHashMap, RenderedDocument, ResultMsg};
|
||||
use internal_types::{TextureUpdateList, TextureUpdateOp, TextureUpdateSource};
|
||||
use internal_types::{LayerIndex, TextureUpdateList, TextureUpdateOp, TextureUpdateSource};
|
||||
use internal_types::{RenderTargetInfo, SavedTargetIndex};
|
||||
use prim_store::DeferredResolve;
|
||||
use profiler::{BackendProfileCounters, FrameProfileCounters,
|
||||
@ -1337,7 +1337,11 @@ impl GpuCacheTexture {
|
||||
device.bind_program(program);
|
||||
device.bind_custom_vao(vao);
|
||||
device.bind_draw_target(
|
||||
Some((texture, 0)),
|
||||
Some(TextureDrawTarget {
|
||||
texture,
|
||||
layer: 0,
|
||||
with_depth: false,
|
||||
}),
|
||||
Some(texture.get_dimensions()),
|
||||
);
|
||||
device.draw_nonindexed_points(0, count as _);
|
||||
@ -2918,7 +2922,7 @@ impl Renderer {
|
||||
|
||||
fn handle_readback_composite(
|
||||
&mut self,
|
||||
render_target: Option<(&Texture, i32)>,
|
||||
render_target: Option<TextureDrawTarget>,
|
||||
framebuffer_size: DeviceUintSize,
|
||||
scissor_rect: Option<DeviceIntRect>,
|
||||
source: &RenderTask,
|
||||
@ -2951,7 +2955,11 @@ impl Renderer {
|
||||
// Called per-instance in case the layer (and therefore FBO)
|
||||
// changes. The device will skip the GL call if the requested
|
||||
// target is already bound.
|
||||
let cache_draw_target = (cache_texture, readback_layer.0 as i32);
|
||||
let cache_draw_target = TextureDrawTarget {
|
||||
texture: cache_texture,
|
||||
layer: readback_layer.0 as usize,
|
||||
with_depth: false,
|
||||
};
|
||||
self.device.bind_draw_target(Some(cache_draw_target), None);
|
||||
|
||||
let mut src = DeviceIntRect::new(
|
||||
@ -2968,7 +2976,7 @@ impl Renderer {
|
||||
dest.size.height = -dest.size.height;
|
||||
}
|
||||
|
||||
self.device.bind_read_target(render_target);
|
||||
self.device.bind_read_target(render_target.map(|r| r.into()));
|
||||
self.device.blit_render_target(src, dest);
|
||||
|
||||
// Restore draw target to current pass render target + layer.
|
||||
@ -2997,22 +3005,22 @@ impl Renderer {
|
||||
let source_rect = match blit.source {
|
||||
BlitJobSource::Texture(texture_id, layer, source_rect) => {
|
||||
// A blit from a texture into this target.
|
||||
let src_texture = self.texture_resolver
|
||||
let texture = self.texture_resolver
|
||||
.resolve(&texture_id)
|
||||
.expect("BUG: invalid source texture");
|
||||
self.device.bind_read_target(Some((src_texture, layer)));
|
||||
self.device.bind_read_target(Some(TextureReadTarget { texture, layer: layer as usize }));
|
||||
source_rect
|
||||
}
|
||||
BlitJobSource::RenderTask(task_id) => {
|
||||
// A blit from the child render task into this target.
|
||||
// TODO(gw): Support R8 format here once we start
|
||||
// creating mips for alpha masks.
|
||||
let src_texture = self.texture_resolver
|
||||
let texture = self.texture_resolver
|
||||
.resolve(&TextureSource::PrevPassColor)
|
||||
.expect("BUG: invalid source texture");
|
||||
let source = &render_tasks[task_id];
|
||||
let (source_rect, layer) = source.get_target_rect();
|
||||
self.device.bind_read_target(Some((src_texture, layer.0 as i32)));
|
||||
self.device.bind_read_target(Some(TextureReadTarget { texture, layer: layer.0 }));
|
||||
source_rect
|
||||
}
|
||||
};
|
||||
@ -3059,7 +3067,7 @@ impl Renderer {
|
||||
|
||||
fn draw_color_target(
|
||||
&mut self,
|
||||
render_target: Option<(&Texture, i32)>,
|
||||
render_target: Option<TextureDrawTarget>,
|
||||
target: &ColorRenderTarget,
|
||||
framebuffer_target_rect: DeviceUintRect,
|
||||
target_size: DeviceUintSize,
|
||||
@ -3074,8 +3082,8 @@ impl Renderer {
|
||||
let _gm = self.gpu_profile.start_marker("color target");
|
||||
|
||||
// sanity check for the depth buffer
|
||||
if let Some((texture, _)) = render_target {
|
||||
assert!(texture.has_depth() >= target.needs_depth());
|
||||
if let Some(t) = render_target {
|
||||
assert!(t.texture.supports_depth() >= target.needs_depth());
|
||||
}
|
||||
|
||||
let framebuffer_kind = if render_target.is_none() {
|
||||
@ -3372,7 +3380,7 @@ impl Renderer {
|
||||
dest_rect.origin.y += dest_rect.size.height;
|
||||
dest_rect.size.height *= -1;
|
||||
|
||||
self.device.bind_read_target(render_target);
|
||||
self.device.bind_read_target(render_target.map(|r| r.into()));
|
||||
self.device.bind_external_draw_target(fbo_id);
|
||||
self.device.blit_render_target(src_rect, dest_rect);
|
||||
handler.unlock(output.pipeline_id);
|
||||
@ -3382,7 +3390,7 @@ impl Renderer {
|
||||
|
||||
fn draw_alpha_target(
|
||||
&mut self,
|
||||
render_target: (&Texture, i32),
|
||||
render_target: TextureDrawTarget,
|
||||
target: &AlphaRenderTarget,
|
||||
target_size: DeviceUintSize,
|
||||
projection: &Transform3D<f32>,
|
||||
@ -3527,7 +3535,7 @@ impl Renderer {
|
||||
fn draw_texture_cache_target(
|
||||
&mut self,
|
||||
texture: &CacheTextureId,
|
||||
layer: i32,
|
||||
layer: LayerIndex,
|
||||
target: &TextureCacheRenderTarget,
|
||||
render_tasks: &RenderTaskTree,
|
||||
stats: &mut RendererStats,
|
||||
@ -3561,8 +3569,11 @@ impl Renderer {
|
||||
let texture = self.texture_resolver
|
||||
.resolve(&texture_source)
|
||||
.expect("BUG: invalid target texture");
|
||||
self.device
|
||||
.bind_draw_target(Some((texture, layer)), Some(target_size));
|
||||
self.device.bind_draw_target(Some(TextureDrawTarget {
|
||||
texture,
|
||||
layer,
|
||||
with_depth: false,
|
||||
}), Some(target_size));
|
||||
}
|
||||
|
||||
self.device.disable_depth();
|
||||
@ -3801,7 +3812,7 @@ impl Renderer {
|
||||
// create a new texture.
|
||||
let selector = TargetSelector {
|
||||
size: list.max_size,
|
||||
num_layers: list.targets.len() as _,
|
||||
num_layers: list.targets.len(),
|
||||
format: list.format,
|
||||
};
|
||||
let index = self.texture_resolver.render_target_pool
|
||||
@ -3809,7 +3820,7 @@ impl Renderer {
|
||||
.position(|texture| {
|
||||
selector == TargetSelector {
|
||||
size: texture.get_dimensions(),
|
||||
num_layers: texture.get_render_target_layer_count(),
|
||||
num_layers: texture.get_layer_count() as usize,
|
||||
format: texture.get_format(),
|
||||
}
|
||||
});
|
||||
@ -3980,7 +3991,11 @@ impl Renderer {
|
||||
);
|
||||
|
||||
self.draw_alpha_target(
|
||||
(&alpha_tex.as_ref().unwrap().texture, target_index as i32),
|
||||
TextureDrawTarget {
|
||||
texture: &alpha_tex.as_ref().unwrap().texture,
|
||||
layer: target_index,
|
||||
with_depth: false,
|
||||
},
|
||||
target,
|
||||
alpha.max_size,
|
||||
&projection,
|
||||
@ -4002,7 +4017,11 @@ impl Renderer {
|
||||
);
|
||||
|
||||
self.draw_color_target(
|
||||
Some((&color_tex.as_ref().unwrap().texture, target_index as i32)),
|
||||
Some(TextureDrawTarget {
|
||||
texture: &color_tex.as_ref().unwrap().texture,
|
||||
layer: target_index,
|
||||
with_depth: target.needs_depth(),
|
||||
}),
|
||||
target,
|
||||
frame.inner_rect,
|
||||
color.max_size,
|
||||
@ -4106,7 +4125,7 @@ impl Renderer {
|
||||
let fb_width = framebuffer_size.width as i32;
|
||||
let num_layers: i32 = self.texture_resolver.render_target_pool
|
||||
.iter()
|
||||
.map(|texture| texture.get_render_target_layer_count() as i32)
|
||||
.map(|texture| texture.get_layer_count() as i32)
|
||||
.sum();
|
||||
|
||||
if num_layers * (size + spacing) > fb_width {
|
||||
@ -4120,10 +4139,9 @@ impl Renderer {
|
||||
let dimensions = texture.get_dimensions();
|
||||
let src_rect = DeviceIntRect::new(DeviceIntPoint::zero(), dimensions.to_i32());
|
||||
|
||||
let layer_count = texture.get_render_target_layer_count();
|
||||
for layer_index in 0 .. layer_count {
|
||||
self.device
|
||||
.bind_read_target(Some((texture, layer_index as i32)));
|
||||
let layer_count = texture.get_layer_count() as usize;
|
||||
for layer in 0 .. layer_count {
|
||||
self.device.bind_read_target(Some(TextureReadTarget { texture, layer }));
|
||||
let x = fb_width - (spacing + size) * (target_index + 1);
|
||||
let y = spacing;
|
||||
|
||||
@ -4168,9 +4186,9 @@ impl Renderer {
|
||||
DeviceIntSize::new(dimensions.width as i32, dimensions.height as i32),
|
||||
);
|
||||
|
||||
let layer_count = texture.get_layer_count();
|
||||
for layer_index in 0 .. layer_count {
|
||||
self.device.bind_read_target(Some((texture, layer_index)));
|
||||
let layer_count = texture.get_layer_count() as usize;
|
||||
for layer in 0 .. layer_count {
|
||||
self.device.bind_read_target(Some(TextureReadTarget { texture, layer}));
|
||||
|
||||
let x = fb_width - (spacing + size) * (i as i32 + 1);
|
||||
|
||||
@ -4277,7 +4295,7 @@ impl Renderer {
|
||||
let size = texture.get_dimensions();
|
||||
let mut texels = vec![0; (size.width * size.height * 16) as usize];
|
||||
self.device.begin_frame();
|
||||
self.device.bind_read_target(Some((texture, 0)));
|
||||
self.device.bind_read_target(Some(TextureReadTarget { texture, layer: 0 }));
|
||||
self.device.read_pixels_into(
|
||||
DeviceUintRect::new(DeviceUintPoint::zero(), size),
|
||||
ReadPixelsFormat::Standard(ImageFormat::RGBAF32),
|
||||
@ -4362,6 +4380,9 @@ impl Renderer {
|
||||
// Texture cache and render target GPU memory.
|
||||
report += self.texture_resolver.report_memory();
|
||||
|
||||
// Textures held internally within the device layer.
|
||||
report += self.device.report_memory();
|
||||
|
||||
report
|
||||
}
|
||||
|
||||
@ -4631,7 +4652,6 @@ struct PlainTexture {
|
||||
size: (u32, u32, i32),
|
||||
format: ImageFormat,
|
||||
filter: TextureFilter,
|
||||
render_target: Option<RenderTargetInfo>,
|
||||
}
|
||||
|
||||
|
||||
@ -4741,7 +4761,6 @@ impl Renderer {
|
||||
size: (rect.size.width, rect.size.height, texture.get_layer_count()),
|
||||
format: texture.get_format(),
|
||||
filter: texture.get_filter(),
|
||||
render_target: texture.get_render_target(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -4749,6 +4768,7 @@ impl Renderer {
|
||||
fn load_texture(
|
||||
target: TextureTarget,
|
||||
plain: &PlainTexture,
|
||||
rt_info: Option<RenderTargetInfo>,
|
||||
root: &PathBuf,
|
||||
device: &mut Device
|
||||
) -> (Texture, Vec<u8>)
|
||||
@ -4768,7 +4788,7 @@ impl Renderer {
|
||||
plain.size.0,
|
||||
plain.size.1,
|
||||
plain.filter,
|
||||
plain.render_target,
|
||||
rt_info,
|
||||
plain.size.2,
|
||||
);
|
||||
device.upload_texture_immediate(&texture, &texels);
|
||||
@ -4939,6 +4959,7 @@ impl Renderer {
|
||||
let t = Self::load_texture(
|
||||
TextureTarget::Array,
|
||||
&texture,
|
||||
Some(RenderTargetInfo { has_depth: false }),
|
||||
&root,
|
||||
&mut self.device
|
||||
);
|
||||
@ -4952,6 +4973,7 @@ impl Renderer {
|
||||
let (t, gpu_cache_data) = Self::load_texture(
|
||||
TextureTarget::Default,
|
||||
&renderer.gpu_cache,
|
||||
Some(RenderTargetInfo { has_depth: false }),
|
||||
&root,
|
||||
&mut self.device,
|
||||
);
|
||||
@ -4996,11 +5018,11 @@ impl Renderer {
|
||||
size: (descriptor.size.width, descriptor.size.height, layer_count),
|
||||
format: descriptor.format,
|
||||
filter,
|
||||
render_target: None,
|
||||
};
|
||||
let t = Self::load_texture(
|
||||
target,
|
||||
&plain_tex,
|
||||
None,
|
||||
&root,
|
||||
&mut self.device
|
||||
);
|
||||
|
@ -122,7 +122,6 @@ impl SpatialNode {
|
||||
let source_perspective = source_perspective.map_or_else(
|
||||
LayoutFastTransform::identity, |perspective| perspective.into());
|
||||
let info = ReferenceFrameInfo {
|
||||
resolved_transform: LayoutFastTransform::identity(),
|
||||
source_transform: source_transform.unwrap_or(PropertyBinding::Value(identity)),
|
||||
source_perspective,
|
||||
origin_in_parent_reference_frame,
|
||||
@ -256,7 +255,7 @@ impl SpatialNode {
|
||||
let scrolled_perspective = info.source_perspective
|
||||
.pre_translate(&state.parent_accumulated_scroll_offset)
|
||||
.post_translate(-state.parent_accumulated_scroll_offset);
|
||||
info.resolved_transform =
|
||||
let resolved_transform =
|
||||
LayoutFastTransform::with_vector(info.origin_in_parent_reference_frame)
|
||||
.pre_mul(&source_transform.into())
|
||||
.pre_mul(&scrolled_perspective);
|
||||
@ -265,7 +264,7 @@ impl SpatialNode {
|
||||
// our parent reference frame, plus any accumulated scrolling offsets from nodes
|
||||
// between our reference frame and this node. Finally, we also include
|
||||
// whatever local transformation this reference frame provides.
|
||||
let relative_transform = info.resolved_transform
|
||||
let relative_transform = resolved_transform
|
||||
.post_translate(state.parent_accumulated_scroll_offset)
|
||||
.to_transform()
|
||||
.with_destination::<LayoutPixel>();
|
||||
@ -622,10 +621,6 @@ impl ScrollFrameInfo {
|
||||
/// Contains information about reference frames.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ReferenceFrameInfo {
|
||||
/// The transformation that establishes this reference frame, relative to the parent
|
||||
/// reference frame. The origin of the reference frame is included in the transformation.
|
||||
pub resolved_transform: LayoutFastTransform,
|
||||
|
||||
/// The source transform and perspective matrices provided by the stacking context
|
||||
/// that forms this reference frame. We maintain the property binding information
|
||||
/// here so that we can resolve the animated transform and update the tree each
|
||||
|
@ -9,7 +9,7 @@ use device::TextureFilter;
|
||||
use freelist::{FreeList, FreeListHandle, UpsertResult, WeakFreeListHandle};
|
||||
use gpu_cache::{GpuCache, GpuCacheHandle};
|
||||
use gpu_types::{ImageSource, UvRectKind};
|
||||
use internal_types::{CacheTextureId, TextureUpdateList, TextureUpdateSource};
|
||||
use internal_types::{CacheTextureId, LayerIndex, TextureUpdateList, TextureUpdateSource};
|
||||
use internal_types::{RenderTargetInfo, TextureSource, TextureUpdate, TextureUpdateOp};
|
||||
use profiler::{ResourceProfileCounter, TextureCacheProfileCounters};
|
||||
use render_backend::FrameId;
|
||||
@ -541,12 +541,14 @@ impl TextureCache {
|
||||
}
|
||||
}
|
||||
|
||||
// A more detailed version of get(). This allows access to the actual
|
||||
// device rect of the cache allocation.
|
||||
/// A more detailed version of get(). This allows access to the actual
|
||||
/// device rect of the cache allocation.
|
||||
///
|
||||
/// Returns a tuple identifying the texture, the layer, and the region.
|
||||
pub fn get_cache_location(
|
||||
&self,
|
||||
handle: &TextureCacheHandle,
|
||||
) -> (CacheTextureId, i32, DeviceUintRect) {
|
||||
) -> (CacheTextureId, LayerIndex, DeviceUintRect) {
|
||||
let handle = handle
|
||||
.entry
|
||||
.as_ref()
|
||||
@ -567,7 +569,7 @@ impl TextureCache {
|
||||
} => (layer_index, origin),
|
||||
};
|
||||
(entry.texture_id,
|
||||
layer_index as i32,
|
||||
layer_index as usize,
|
||||
DeviceUintRect::new(origin, entry.size))
|
||||
}
|
||||
|
||||
|
@ -285,10 +285,8 @@ impl<T: RenderTarget> RenderTargetList<T> {
|
||||
pub fn check_ready(&self, t: &Texture) {
|
||||
assert_eq!(t.get_dimensions(), self.max_size);
|
||||
assert_eq!(t.get_format(), self.format);
|
||||
assert_eq!(t.get_render_target_layer_count(), self.targets.len());
|
||||
assert_eq!(t.get_layer_count() as usize, self.targets.len());
|
||||
assert_eq!(t.has_depth(), t.get_rt_info().unwrap().has_depth);
|
||||
assert_eq!(t.has_depth(), self.needs_depth());
|
||||
assert!(t.supports_depth() >= self.needs_depth());
|
||||
}
|
||||
}
|
||||
|
||||
@ -821,7 +819,7 @@ pub enum RenderPassKind {
|
||||
OffScreen {
|
||||
alpha: RenderTargetList<AlphaRenderTarget>,
|
||||
color: RenderTargetList<ColorRenderTarget>,
|
||||
texture_cache: FastHashMap<(CacheTextureId, i32), TextureCacheRenderTarget>,
|
||||
texture_cache: FastHashMap<(CacheTextureId, usize), TextureCacheRenderTarget>,
|
||||
},
|
||||
}
|
||||
|
||||
@ -950,8 +948,8 @@ impl RenderPass {
|
||||
// Find a target to assign this task to, or create a new
|
||||
// one if required.
|
||||
let texture_target = match task.location {
|
||||
RenderTaskLocation::TextureCache(texture_id, layer, _) => {
|
||||
Some((texture_id, layer))
|
||||
RenderTaskLocation::TextureCache { texture, layer, .. } => {
|
||||
Some((texture, layer))
|
||||
}
|
||||
RenderTaskLocation::Fixed(..) => {
|
||||
None
|
||||
|
@ -232,7 +232,7 @@ impl Transaction {
|
||||
}
|
||||
|
||||
pub fn set_pinch_zoom(&mut self, pinch_zoom: ZoomFactor) {
|
||||
self.scene_ops.push(SceneMsg::SetPinchZoom(pinch_zoom));
|
||||
self.frame_ops.push(FrameMsg::SetPinchZoom(pinch_zoom));
|
||||
}
|
||||
|
||||
pub fn set_pan(&mut self, pan: DeviceIntPoint) {
|
||||
@ -523,7 +523,6 @@ pub struct AddFontInstance {
|
||||
pub enum SceneMsg {
|
||||
UpdateEpoch(PipelineId, Epoch),
|
||||
SetPageZoom(ZoomFactor),
|
||||
SetPinchZoom(ZoomFactor),
|
||||
SetRootPipeline(PipelineId),
|
||||
RemovePipeline(PipelineId),
|
||||
SetDisplayList {
|
||||
@ -554,6 +553,7 @@ pub enum FrameMsg {
|
||||
GetScrollNodeState(MsgSender<Vec<ScrollNodeState>>),
|
||||
UpdateDynamicProperties(DynamicProperties),
|
||||
AppendDynamicProperties(DynamicProperties),
|
||||
SetPinchZoom(ZoomFactor),
|
||||
}
|
||||
|
||||
impl fmt::Debug for SceneMsg {
|
||||
@ -562,7 +562,6 @@ impl fmt::Debug for SceneMsg {
|
||||
SceneMsg::UpdateEpoch(..) => "SceneMsg::UpdateEpoch",
|
||||
SceneMsg::SetDisplayList { .. } => "SceneMsg::SetDisplayList",
|
||||
SceneMsg::SetPageZoom(..) => "SceneMsg::SetPageZoom",
|
||||
SceneMsg::SetPinchZoom(..) => "SceneMsg::SetPinchZoom",
|
||||
SceneMsg::RemovePipeline(..) => "SceneMsg::RemovePipeline",
|
||||
SceneMsg::SetWindowParameters { .. } => "SceneMsg::SetWindowParameters",
|
||||
SceneMsg::SetRootPipeline(..) => "SceneMsg::SetRootPipeline",
|
||||
@ -582,6 +581,7 @@ impl fmt::Debug for FrameMsg {
|
||||
FrameMsg::EnableFrameOutput(..) => "FrameMsg::EnableFrameOutput",
|
||||
FrameMsg::UpdateDynamicProperties(..) => "FrameMsg::UpdateDynamicProperties",
|
||||
FrameMsg::AppendDynamicProperties(..) => "FrameMsg::AppendDynamicProperties",
|
||||
FrameMsg::SetPinchZoom(..) => "FrameMsg::SetPinchZoom",
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -791,6 +791,7 @@ pub struct MemoryReport {
|
||||
pub vertex_data_textures: usize,
|
||||
pub render_target_textures: usize,
|
||||
pub texture_cache_textures: usize,
|
||||
pub depth_target_textures: usize,
|
||||
}
|
||||
|
||||
impl ::std::ops::AddAssign for MemoryReport {
|
||||
@ -808,6 +809,7 @@ impl ::std::ops::AddAssign for MemoryReport {
|
||||
self.vertex_data_textures += other.vertex_data_textures;
|
||||
self.render_target_textures += other.render_target_textures;
|
||||
self.texture_cache_textures += other.texture_cache_textures;
|
||||
self.depth_target_textures += other.depth_target_textures;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -435,6 +435,29 @@ impl BorderStyle {
|
||||
pub fn is_hidden(&self) -> bool {
|
||||
*self == BorderStyle::Hidden || *self == BorderStyle::None
|
||||
}
|
||||
|
||||
/// Returns true if the border style itself is opaque. Other
|
||||
/// factors (such as color, or border radii) may mean that
|
||||
/// the border segment isn't opaque regardless of this.
|
||||
pub fn is_opaque(&self) -> bool {
|
||||
match *self {
|
||||
BorderStyle::None |
|
||||
BorderStyle::Double |
|
||||
BorderStyle::Dotted |
|
||||
BorderStyle::Dashed |
|
||||
BorderStyle::Hidden => {
|
||||
false
|
||||
}
|
||||
|
||||
BorderStyle::Solid |
|
||||
BorderStyle::Groove |
|
||||
BorderStyle::Ridge |
|
||||
BorderStyle::Inset |
|
||||
BorderStyle::Outset => {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
|
@ -1 +1 @@
|
||||
98d507003c07c003ef0e0297dc4d29ee896a5868
|
||||
74f265e447d2927c27d4320c676779956d39eaf0
|
||||
|
@ -542,6 +542,7 @@ struct MemoryReport {
|
||||
uintptr_t vertex_data_textures;
|
||||
uintptr_t render_target_textures;
|
||||
uintptr_t texture_cache_textures;
|
||||
uintptr_t depth_target_textures;
|
||||
|
||||
bool operator==(const MemoryReport& aOther) const {
|
||||
return primitive_stores == aOther.primitive_stores &&
|
||||
@ -556,7 +557,8 @@ struct MemoryReport {
|
||||
gpu_cache_textures == aOther.gpu_cache_textures &&
|
||||
vertex_data_textures == aOther.vertex_data_textures &&
|
||||
render_target_textures == aOther.render_target_textures &&
|
||||
texture_cache_textures == aOther.texture_cache_textures;
|
||||
texture_cache_textures == aOther.texture_cache_textures &&
|
||||
depth_target_textures == aOther.depth_target_textures;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -18,6 +18,16 @@
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="mozglue"
|
||||
version="1.0.0.0"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<ms_asmv3:security>
|
||||
|
@ -7,7 +7,6 @@
|
||||
#ifndef mozilla_ipc_backgroundchild_h__
|
||||
#define mozilla_ipc_backgroundchild_h__
|
||||
|
||||
#include "base/process.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/ipc/Transport.h"
|
||||
|
||||
@ -47,7 +46,6 @@ class BackgroundChild final
|
||||
friend class mozilla::dom::ContentChild;
|
||||
friend class mozilla::dom::ContentParent;
|
||||
|
||||
typedef base::ProcessId ProcessId;
|
||||
typedef mozilla::ipc::Transport Transport;
|
||||
|
||||
public:
|
||||
|
@ -412,9 +412,6 @@ js::GetElementsWithAdder(JSContext* cx, HandleObject obj, HandleObject receiver,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ObjectMayHaveExtraIndexedProperties(JSObject* obj);
|
||||
|
||||
static inline bool
|
||||
IsPackedArrayOrNoExtraIndexedProperties(JSObject* obj, uint64_t length)
|
||||
{
|
||||
@ -1050,8 +1047,8 @@ ObjectMayHaveExtraIndexedOwnProperties(JSObject* obj)
|
||||
* elements. This includes other indexed properties in its shape hierarchy, and
|
||||
* indexed properties or elements along its prototype chain.
|
||||
*/
|
||||
static bool
|
||||
ObjectMayHaveExtraIndexedProperties(JSObject* obj)
|
||||
bool
|
||||
js::ObjectMayHaveExtraIndexedProperties(JSObject* obj)
|
||||
{
|
||||
MOZ_ASSERT_IF(obj->hasDynamicPrototype(), !obj->isNative());
|
||||
|
||||
|
@ -196,6 +196,9 @@ array_construct(JSContext* cx, unsigned argc, Value* vp);
|
||||
extern bool
|
||||
IsCrossRealmArrayConstructor(JSContext* cx, const Value& v, bool* result);
|
||||
|
||||
extern bool
|
||||
ObjectMayHaveExtraIndexedProperties(JSObject* obj);
|
||||
|
||||
class MOZ_NON_TEMPORARY_CLASS ArraySpeciesLookup final
|
||||
{
|
||||
/*
|
||||
|
125
js/src/jit-test/tests/cacheir/bug1494537.js
Normal file
125
js/src/jit-test/tests/cacheir/bug1494537.js
Normal file
@ -0,0 +1,125 @@
|
||||
setJitCompilerOption("ion.forceinlineCaches", 1);
|
||||
|
||||
let offsets = [213, 559, 255, 515, 30, 507, 252, 329, 487, 7];
|
||||
|
||||
function update_index(i, j) {
|
||||
var offset = offsets[j % offsets.length];
|
||||
return i + offset;
|
||||
}
|
||||
|
||||
function compute_index(initial, count) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
initial = update_index(initial, i);
|
||||
}
|
||||
return initial;
|
||||
}
|
||||
|
||||
// This is written so that the IC added in the bug activates.
|
||||
function mutate_array(array, count, epsilon = 0) {
|
||||
var index = 0;
|
||||
for (var i = 0; i < count; i++) {
|
||||
index = update_index(index, i);
|
||||
array[index] = i + epsilon;
|
||||
}
|
||||
return array[offsets[0]+offsets[1]] === (1 + epsilon) &&
|
||||
array[10] === undefined;
|
||||
}
|
||||
|
||||
// Monomorphizing mutate_array to ensure we get the IC chains we want
|
||||
function create_variant(variant) {
|
||||
var source = mutate_array.toString().replace("mutate_array", "mutate_array_"+variant);
|
||||
return source;
|
||||
}
|
||||
|
||||
function test_basic() {
|
||||
eval(create_variant("basic"));
|
||||
var x = [];
|
||||
|
||||
var count = 100;
|
||||
assertEq(mutate_array_basic(x, count), true);
|
||||
var end = compute_index(0, count);
|
||||
assertEq(x[end], count - 1);
|
||||
assertEq(x[end - 1], undefined);
|
||||
}
|
||||
|
||||
// Ensure the IC respects frozen.
|
||||
function test_frozen() {
|
||||
eval(create_variant("frozen"));
|
||||
var x = [];
|
||||
Object.freeze(x);
|
||||
|
||||
var count = 100;
|
||||
assertEq(mutate_array_frozen(x, count), false);
|
||||
assertEq(x.length, 0);
|
||||
|
||||
var end = compute_index(0, count);
|
||||
|
||||
var y = [];
|
||||
assertEq(mutate_array_frozen(y, count), true);
|
||||
assertEq(y[end], count - 1);
|
||||
Object.freeze(y);
|
||||
|
||||
// After a mutated array is frozen, can't subsequently modify elements
|
||||
assertEq(mutate_array_frozen(x, count, 10), false);
|
||||
assertEq(y[end], count - 1);
|
||||
}
|
||||
|
||||
// Let's make sure updates to the array happen as expected.
|
||||
function test_update() {
|
||||
eval(create_variant("update"));
|
||||
|
||||
var x = [];
|
||||
var count = 100;
|
||||
assertEq(mutate_array_update(x, count), true);
|
||||
var end = compute_index(0, count);
|
||||
assertEq(x[end], count - 1);
|
||||
assertEq(x[end - 1], undefined);
|
||||
|
||||
var epsilon = 2;
|
||||
mutate_array_update(x, 200, epsilon);
|
||||
assertEq(x[end], count -1 + epsilon)
|
||||
}
|
||||
|
||||
// Elements may be non-writable, let us not write them.
|
||||
function test_nonwritable() {
|
||||
eval(create_variant("nonwritable"));
|
||||
var x = [];
|
||||
var count = 100;
|
||||
var index = compute_index(0, 10);
|
||||
Object.defineProperty(x, index, {value: -10, writable: false});
|
||||
mutate_array_nonwritable(x, count);
|
||||
assertEq(x[index], -10);
|
||||
}
|
||||
|
||||
// Random indices can get setters, let's make sure we honour those.
|
||||
function test_setter() {
|
||||
eval(create_variant("setter"));
|
||||
var x = [];
|
||||
var count = 100;
|
||||
var index = compute_index(0, 80);
|
||||
var sigil = 0;
|
||||
Object.defineProperty(x, index, {set(newVal) {sigil++; }});
|
||||
mutate_array_setter(x, count);
|
||||
assertEq(sigil, 1);
|
||||
assertEq(x[index], undefined);
|
||||
}
|
||||
|
||||
// Ensure indexes on the prototype don't break things;
|
||||
//
|
||||
function test_proto_indices() {
|
||||
eval(create_variant("proto_indices"));
|
||||
var x = [];
|
||||
var count = 100;
|
||||
var index = compute_index(0, 80);
|
||||
x.__proto__[index] = "hello";
|
||||
mutate_array_proto_indices(x, count);
|
||||
assertEq(x.__proto__[index], "hello");
|
||||
assertEq(x[index], 79);
|
||||
}
|
||||
|
||||
test_basic();
|
||||
test_frozen();
|
||||
test_update();
|
||||
test_nonwritable();
|
||||
test_setter();
|
||||
test_proto_indices();
|
@ -1879,6 +1879,33 @@ BaselineCacheIRCompiler::emitCallProxySetByValue()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BaselineCacheIRCompiler::emitCallAddOrUpdateSparseElementHelper()
|
||||
{
|
||||
Register obj = allocator.useRegister(masm, reader.objOperandId());
|
||||
Register id = allocator.useRegister(masm, reader.int32OperandId());
|
||||
ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId());
|
||||
bool strict = reader.readBool();
|
||||
AutoScratchRegister scratch(allocator, masm);
|
||||
|
||||
allocator.discardStack(masm);
|
||||
|
||||
AutoStubFrame stubFrame(*this);
|
||||
stubFrame.enter(masm, scratch);
|
||||
|
||||
masm.Push(Imm32(strict));
|
||||
masm.Push(val);
|
||||
masm.Push(id);
|
||||
masm.Push(obj);
|
||||
|
||||
if (!callVM(masm, AddOrUpdateSparseElementHelperInfo)) {
|
||||
return false;
|
||||
}
|
||||
stubFrame.leave(masm);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
BaselineCacheIRCompiler::emitMegamorphicSetElement()
|
||||
{
|
||||
|
@ -3450,6 +3450,9 @@ SetPropIRGenerator::tryAttachStub()
|
||||
if (tryAttachSetTypedElement(obj, objId, index, indexId, rhsValId)) {
|
||||
return true;
|
||||
}
|
||||
if (tryAttachAddOrUpdateSparseElement(obj, objId, index, indexId, rhsValId)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
@ -4066,6 +4069,89 @@ SetPropIRGenerator::tryAttachSetDenseElementHole(HandleObject obj, ObjOperandId
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add an IC for adding or updating a sparse array element.
|
||||
bool
|
||||
SetPropIRGenerator::tryAttachAddOrUpdateSparseElement(HandleObject obj, ObjOperandId objId,
|
||||
uint32_t index, Int32OperandId indexId,
|
||||
ValOperandId rhsId)
|
||||
{
|
||||
JSOp op = JSOp(*pc_);
|
||||
MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op));
|
||||
|
||||
if (op != JSOP_SETELEM && op != JSOP_STRICTSETELEM) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!obj->isNative()) {
|
||||
return false;
|
||||
}
|
||||
RootedNativeObject nobj(cx_, &obj->as<NativeObject>());
|
||||
|
||||
// We cannot attach a stub to a non-extensible object
|
||||
if (!nobj->isExtensible()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Stub doesn't handle negative indices.
|
||||
if (index > INT_MAX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We also need to be past the end of the dense capacity, to ensure sparse.
|
||||
if (index < nobj->getDenseInitializedLength()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only handle Array objects in this stub.
|
||||
if (!nobj->is<ArrayObject>()) {
|
||||
return false;
|
||||
}
|
||||
RootedArrayObject aobj(cx_, &obj->as<ArrayObject>());
|
||||
|
||||
// Don't attach if we're adding to an array with non-writable length.
|
||||
bool isAdd = (index >= aobj->length());
|
||||
if (isAdd && !aobj->lengthIsWritable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Indexed properties on the prototype chain aren't handled by the helper.
|
||||
if (ObjectMayHaveExtraIndexedProperties(aobj->staticPrototype())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure we are still talking about an array class.
|
||||
writer.guardClass(objId, GuardClassKind::Array);
|
||||
|
||||
// The helper we are going to call only applies to non-dense elements.
|
||||
writer.guardIndexGreaterThanDenseInitLength(objId, indexId);
|
||||
|
||||
// Guard extensible: We may be trying to add a new element, and so we'd best
|
||||
// be able to do so safely.
|
||||
writer.guardIsExtensible(objId);
|
||||
|
||||
// Ensures we are able to efficiently able to map to an integral jsid.
|
||||
writer.guardIndexIsNonNegative(indexId);
|
||||
|
||||
// Shape guard the prototype chain to avoid shadowing indexes from appearing.
|
||||
// Dense elements may appear on the prototype chain (and prototypes may
|
||||
// have a different notion of which elements are dense), but they can
|
||||
// only be data properties, so our specialized Set handler is ok to bind
|
||||
// to them.
|
||||
ShapeGuardProtoChain(writer, obj, objId);
|
||||
|
||||
// Ensure that if we're adding an element to the object, the object's
|
||||
// length is writable.
|
||||
writer.guardIndexIsValidUpdateOrAdd(objId, indexId);
|
||||
|
||||
writer.callAddOrUpdateSparseElementHelper(objId, indexId, rhsId,
|
||||
/* strict = */op == JSOP_STRICTSETELEM);
|
||||
writer.returnFromIC();
|
||||
|
||||
trackAttached("AddOrUpdateSparseElement");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
SetPropIRGenerator::tryAttachSetTypedElement(HandleObject obj, ObjOperandId objId,
|
||||
uint32_t index, Int32OperandId indexId,
|
||||
|
@ -201,6 +201,7 @@ extern const char* const CacheKindNames[];
|
||||
_(GuardClass) /* Guard an object class, per GuardClassKind */ \
|
||||
_(GuardAnyClass) /* Guard an arbitrary class for an object */ \
|
||||
_(GuardCompartment) \
|
||||
_(GuardIsExtensible) \
|
||||
_(GuardIsNativeFunction) \
|
||||
_(GuardIsNativeObject) \
|
||||
_(GuardIsProxy) \
|
||||
@ -222,6 +223,9 @@ extern const char* const CacheKindNames[];
|
||||
_(GuardHasGetterSetter) \
|
||||
_(GuardGroupHasUnanalyzedNewScript) \
|
||||
_(GuardIndexIsNonNegative) \
|
||||
_(GuardIndexGreaterThanDenseCapacity) \
|
||||
_(GuardIndexGreaterThanArrayLength) \
|
||||
_(GuardIndexIsValidUpdateOrAdd) \
|
||||
_(GuardIndexGreaterThanDenseInitLength) \
|
||||
_(GuardTagNotEqual) \
|
||||
_(GuardXrayExpandoShapeAndDefaultProto) \
|
||||
@ -267,6 +271,7 @@ extern const char* const CacheKindNames[];
|
||||
_(CallSetArrayLength) \
|
||||
_(CallProxySet) \
|
||||
_(CallProxySetByValue) \
|
||||
_(CallAddOrUpdateSparseElementHelper) \
|
||||
_(CallInt32ToString) \
|
||||
_(CallNumberToString) \
|
||||
\
|
||||
@ -765,6 +770,9 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter
|
||||
// Use RawWord, because compartments never move and it can't be GCed.
|
||||
addStubField(uintptr_t(compartment), StubField::Type::RawWord);
|
||||
}
|
||||
void guardIsExtensible(ObjOperandId obj) {
|
||||
writeOpWithOperandId(CacheOp::GuardIsExtensible, obj);
|
||||
}
|
||||
void guardNoDetachedTypedObjects() {
|
||||
writeOp(CacheOp::GuardNoDetachedTypedObjects);
|
||||
}
|
||||
@ -811,6 +819,18 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter
|
||||
writeOpWithOperandId(CacheOp::GuardIndexGreaterThanDenseInitLength, obj);
|
||||
writeOperandId(index);
|
||||
}
|
||||
void guardIndexGreaterThanDenseCapacity(ObjOperandId obj, Int32OperandId index) {
|
||||
writeOpWithOperandId(CacheOp::GuardIndexGreaterThanDenseCapacity, obj);
|
||||
writeOperandId(index);
|
||||
}
|
||||
void guardIndexGreaterThanArrayLength(ObjOperandId obj, Int32OperandId index) {
|
||||
writeOpWithOperandId(CacheOp::GuardIndexGreaterThanArrayLength, obj);
|
||||
writeOperandId(index);
|
||||
}
|
||||
void guardIndexIsValidUpdateOrAdd(ObjOperandId obj, Int32OperandId index) {
|
||||
writeOpWithOperandId(CacheOp::GuardIndexIsValidUpdateOrAdd, obj);
|
||||
writeOperandId(index);
|
||||
}
|
||||
void guardTagNotEqual(ValueTagOperandId lhs, ValueTagOperandId rhs) {
|
||||
writeOpWithOperandId(CacheOp::GuardTagNotEqual, lhs);
|
||||
writeOperandId(rhs);
|
||||
@ -1041,6 +1061,12 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter
|
||||
writeOperandId(rhs);
|
||||
buffer_.writeByte(uint32_t(strict));
|
||||
}
|
||||
void callAddOrUpdateSparseElementHelper(ObjOperandId obj, Int32OperandId id, ValOperandId rhs, bool strict) {
|
||||
writeOpWithOperandId(CacheOp::CallAddOrUpdateSparseElementHelper, obj);
|
||||
writeOperandId(id);
|
||||
writeOperandId(rhs);
|
||||
buffer_.writeByte(uint32_t(strict));
|
||||
}
|
||||
StringOperandId callInt32ToString(Int32OperandId id) {
|
||||
StringOperandId res(nextOperandId_++);
|
||||
writeOpWithOperandId(CacheOp::CallInt32ToString, id);
|
||||
@ -1753,6 +1779,10 @@ class MOZ_RAII SetPropIRGenerator : public IRGenerator
|
||||
bool tryAttachSetDenseElementHole(HandleObject obj, ObjOperandId objId, uint32_t index,
|
||||
Int32OperandId indexId, ValOperandId rhsId);
|
||||
|
||||
bool tryAttachAddOrUpdateSparseElement(HandleObject obj, ObjOperandId objId, uint32_t index,
|
||||
Int32OperandId indexId, ValOperandId rhsId);
|
||||
|
||||
|
||||
bool tryAttachGenericProxy(HandleObject obj, ObjOperandId objId, HandleId id,
|
||||
ValOperandId rhsId, bool handleDOMProxies);
|
||||
bool tryAttachDOMProxyShadowed(HandleObject obj, ObjOperandId objId, HandleId id,
|
||||
|
@ -1734,6 +1734,35 @@ CacheIRCompiler::emitGuardClass()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
CacheIRCompiler::emitGuardIsExtensible()
|
||||
{
|
||||
Register obj = allocator.useRegister(masm, reader.objOperandId());
|
||||
AutoScratchRegister scratch(allocator, masm);
|
||||
|
||||
FailurePath* failure;
|
||||
if (!addFailurePath(&failure)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Address shape(obj, ShapedObject::offsetOfShape());
|
||||
masm.loadPtr(shape, scratch);
|
||||
|
||||
Address baseShape(scratch, Shape::offsetOfBaseShape());
|
||||
masm.loadPtr(baseShape, scratch);
|
||||
|
||||
Address baseShapeFlags(scratch, BaseShape::offsetOfFlags());
|
||||
masm.loadPtr(baseShapeFlags, scratch);
|
||||
|
||||
masm.and32(Imm32(js::BaseShape::NOT_EXTENSIBLE), scratch);
|
||||
|
||||
// Spectre-style checks are not needed here because we do not
|
||||
// interpret data based on this check.
|
||||
masm.branch32(Assembler::Equal, scratch, Imm32(js::BaseShape::NOT_EXTENSIBLE),
|
||||
failure->label());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
CacheIRCompiler::emitGuardIsNativeFunction()
|
||||
{
|
||||
@ -2836,7 +2865,7 @@ CacheIRCompiler::emitGuardIndexGreaterThanDenseInitLength()
|
||||
// Load obj->elements.
|
||||
masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
|
||||
|
||||
// Ensure index >= capacity.
|
||||
// Ensure index >= initLength.
|
||||
Label outOfBounds;
|
||||
Address capacity(scratch, ObjectElements::offsetOfInitializedLength());
|
||||
masm.spectreBoundsCheck32(index, capacity, scratch2, &outOfBounds);
|
||||
@ -2846,6 +2875,89 @@ CacheIRCompiler::emitGuardIndexGreaterThanDenseInitLength()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
CacheIRCompiler::emitGuardIndexGreaterThanDenseCapacity()
|
||||
{
|
||||
Register obj = allocator.useRegister(masm, reader.objOperandId());
|
||||
Register index = allocator.useRegister(masm, reader.int32OperandId());
|
||||
AutoScratchRegister scratch(allocator, masm);
|
||||
AutoScratchRegister scratch2(allocator, masm);
|
||||
|
||||
FailurePath* failure;
|
||||
if (!addFailurePath(&failure)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load obj->elements.
|
||||
masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
|
||||
|
||||
// Ensure index >= capacity.
|
||||
Label outOfBounds;
|
||||
Address capacity(scratch, ObjectElements::offsetOfCapacity());
|
||||
masm.spectreBoundsCheck32(index, capacity, scratch2, &outOfBounds);
|
||||
masm.jump(failure->label());
|
||||
masm.bind(&outOfBounds);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
CacheIRCompiler::emitGuardIndexGreaterThanArrayLength()
|
||||
{
|
||||
Register obj = allocator.useRegister(masm, reader.objOperandId());
|
||||
Register index = allocator.useRegister(masm, reader.int32OperandId());
|
||||
AutoScratchRegister scratch(allocator, masm);
|
||||
AutoScratchRegister scratch2(allocator, masm);
|
||||
|
||||
FailurePath* failure;
|
||||
if (!addFailurePath(&failure)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load obj->elements.
|
||||
masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
|
||||
|
||||
// Ensure index >= length;
|
||||
Label outOfBounds;
|
||||
Address length(scratch, ObjectElements::offsetOfLength());
|
||||
masm.spectreBoundsCheck32(index, length, scratch2, &outOfBounds);
|
||||
masm.jump(failure->label());
|
||||
masm.bind(&outOfBounds);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
CacheIRCompiler::emitGuardIndexIsValidUpdateOrAdd()
|
||||
{
|
||||
Register obj = allocator.useRegister(masm, reader.objOperandId());
|
||||
Register index = allocator.useRegister(masm, reader.int32OperandId());
|
||||
AutoScratchRegister scratch(allocator, masm);
|
||||
AutoScratchRegister scratch2(allocator, masm);
|
||||
|
||||
FailurePath* failure;
|
||||
if (!addFailurePath(&failure)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load obj->elements.
|
||||
masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
|
||||
|
||||
Label success;
|
||||
|
||||
// If length is writable, branch to &success. All indices are writable.
|
||||
Address flags(scratch, ObjectElements::offsetOfFlags());
|
||||
masm.branchTest32(Assembler::Zero, flags,
|
||||
Imm32(ObjectElements::Flags::NONWRITABLE_ARRAY_LENGTH),
|
||||
&success);
|
||||
|
||||
// Otherwise, ensure index is in bounds.
|
||||
Address length(scratch, ObjectElements::offsetOfLength());
|
||||
masm.spectreBoundsCheck32(index, length, scratch2,
|
||||
/* failure = */ failure->label());
|
||||
masm.bind(&success);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
CacheIRCompiler::emitGuardTagNotEqual()
|
||||
{
|
||||
|
@ -32,6 +32,7 @@ namespace jit {
|
||||
_(GuardType) \
|
||||
_(GuardClass) \
|
||||
_(GuardGroupHasUnanalyzedNewScript) \
|
||||
_(GuardIsExtensible) \
|
||||
_(GuardIsNativeFunction) \
|
||||
_(GuardFunctionPrototype) \
|
||||
_(GuardIsNativeObject) \
|
||||
@ -46,6 +47,9 @@ namespace jit {
|
||||
_(GuardAndGetNumberFromString) \
|
||||
_(GuardAndGetIndexFromString) \
|
||||
_(GuardIndexIsNonNegative) \
|
||||
_(GuardIndexGreaterThanDenseCapacity) \
|
||||
_(GuardIndexGreaterThanArrayLength) \
|
||||
_(GuardIndexIsValidUpdateOrAdd) \
|
||||
_(GuardIndexGreaterThanDenseInitLength) \
|
||||
_(GuardTagNotEqual) \
|
||||
_(GuardXrayExpandoShapeAndDefaultProto)\
|
||||
|
@ -2265,6 +2265,28 @@ IonCacheIRCompiler::emitCallProxySetByValue()
|
||||
return callVM(masm, ProxySetPropertyByValueInfo);
|
||||
}
|
||||
|
||||
bool
|
||||
IonCacheIRCompiler::emitCallAddOrUpdateSparseElementHelper()
|
||||
{
|
||||
AutoSaveLiveRegisters save(*this);
|
||||
|
||||
Register obj = allocator.useRegister(masm, reader.objOperandId());
|
||||
Register id = allocator.useRegister(masm, reader.int32OperandId());
|
||||
ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId());
|
||||
bool strict = reader.readBool();
|
||||
|
||||
Label done;
|
||||
prepareVMCall(masm, save);
|
||||
|
||||
masm.Push(Imm32(strict));
|
||||
masm.Push(val);
|
||||
masm.Push(id);
|
||||
masm.Push(obj);
|
||||
|
||||
return callVM(masm, AddOrUpdateSparseElementHelperInfo);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
IonCacheIRCompiler::emitMegamorphicSetElement()
|
||||
{
|
||||
|
@ -2072,5 +2072,10 @@ typedef bool (*NativeGetElementFn)(JSContext*, HandleNativeObject, HandleValue,
|
||||
const VMFunction NativeGetElementInfo =
|
||||
FunctionInfo<NativeGetElementFn>(NativeGetElement, "NativeGetProperty");
|
||||
|
||||
typedef bool (*AddOrUpdateSparseElementHelperFn)(JSContext* cx, HandleArrayObject obj,
|
||||
int32_t int_id, HandleValue v, bool strict);
|
||||
const VMFunction AddOrUpdateSparseElementHelperInfo =
|
||||
FunctionInfo<AddOrUpdateSparseElementHelperFn>(AddOrUpdateSparseElementHelper, "AddOrUpdateSparseElementHelper");
|
||||
|
||||
} // namespace jit
|
||||
} // namespace js
|
||||
|
@ -982,6 +982,8 @@ extern const VMFunction ProxyHasOwnInfo;
|
||||
|
||||
extern const VMFunction NativeGetElementInfo;
|
||||
|
||||
extern const VMFunction AddOrUpdateSparseElementHelperInfo;
|
||||
|
||||
// TailCall VMFunctions
|
||||
extern const VMFunction DoConcatStringObjectInfo;
|
||||
|
||||
|
@ -459,20 +459,11 @@ class Assembler : public AssemblerX86Shared
|
||||
MOZ_ASSERT(dest.size() == 16);
|
||||
masm.vhaddpd_rr(src.encoding(), dest.encoding());
|
||||
}
|
||||
void vsubpd(const Operand& src1, FloatRegister src0, FloatRegister dest) {
|
||||
void vsubpd(FloatRegister src1, FloatRegister src0, FloatRegister dest) {
|
||||
MOZ_ASSERT(HasSSE2());
|
||||
MOZ_ASSERT(src0.size() == 16);
|
||||
MOZ_ASSERT(dest.size() == 16);
|
||||
switch (src1.kind()) {
|
||||
case Operand::MEM_REG_DISP:
|
||||
masm.vsubpd_mr(src1.disp(), src1.base(), src0.encoding(), dest.encoding());
|
||||
break;
|
||||
case Operand::MEM_ADDRESS32:
|
||||
masm.vsubpd_mr(src1.address(), src0.encoding(), dest.encoding());
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH("unexpected operand kind");
|
||||
}
|
||||
masm.vsubpd_rr(src1.encoding(), src0.encoding(), dest.encoding());
|
||||
}
|
||||
|
||||
void vpunpckldq(FloatRegister src1, FloatRegister src0, FloatRegister dest) {
|
||||
|
@ -176,14 +176,6 @@ class BaseAssemblerX86 : public BaseAssembler
|
||||
{
|
||||
twoByteOpSimd("vsubpd", VEX_PD, OP2_SUBPS_VpsWps, src1, src0, dst);
|
||||
}
|
||||
void vsubpd_mr(int32_t offset, RegisterID base, XMMRegisterID src0, XMMRegisterID dst)
|
||||
{
|
||||
twoByteOpSimd("vsubpd", VEX_PD, OP2_SUBPS_VpsWps, offset, base, src0, dst);
|
||||
}
|
||||
void vsubpd_mr(const void* address, XMMRegisterID src0, XMMRegisterID dst)
|
||||
{
|
||||
twoByteOpSimd("vsubpd", VEX_PD, OP2_SUBPS_VpsWps, address, src0, dst);
|
||||
}
|
||||
|
||||
void vpunpckldq_rr(XMMRegisterID src1, XMMRegisterID src0, XMMRegisterID dst) {
|
||||
twoByteOpSimd("vpunpckldq", VEX_PD, OP2_PUNPCKLDQ, src1, src0, dst);
|
||||
|
@ -1120,15 +1120,6 @@ MacroAssembler::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 outp
|
||||
// ========================================================================
|
||||
// Convert floating point.
|
||||
|
||||
// vpunpckldq requires 16-byte boundary for memory operand.
|
||||
// See convertUInt64ToDouble for the details.
|
||||
MOZ_ALIGNED_DECL(static const uint64_t, 16) TO_DOUBLE[4] = {
|
||||
0x4530000043300000LL,
|
||||
0x0LL,
|
||||
0x4330000000000000LL,
|
||||
0x4530000000000000LL
|
||||
};
|
||||
|
||||
bool
|
||||
MacroAssembler::convertUInt64ToDoubleNeedsTemp()
|
||||
{
|
||||
@ -1187,8 +1178,16 @@ MacroAssembler::convertUInt64ToDouble(Register64 src, FloatRegister dest, Regist
|
||||
// here, each 64-bit part of dest represents following double:
|
||||
// HI(dest) = 0x 1.00000HHHHHHHH * 2**84 == 2**84 + 0x HHHHHHHH 00000000
|
||||
// LO(dest) = 0x 1.00000LLLLLLLL * 2**52 == 2**52 + 0x 00000000 LLLLLLLL
|
||||
movePtr(ImmWord((uintptr_t)TO_DOUBLE), temp);
|
||||
vpunpckldq(Operand(temp, 0), dest128, dest128);
|
||||
// See convertUInt64ToDouble for the details.
|
||||
static const int32_t CST1[4] = {
|
||||
0x43300000,
|
||||
0x45300000,
|
||||
0x0,
|
||||
0x0,
|
||||
};
|
||||
|
||||
loadConstantSimd128Int(SimdConstant::CreateX4(CST1), ScratchSimd128Reg);
|
||||
vpunpckldq(ScratchSimd128Reg, dest128, dest128);
|
||||
|
||||
// Subtract a constant C2 from dest, for each 64-bit part:
|
||||
// C2 = 0x 45300000 00000000 43300000 00000000
|
||||
@ -1198,7 +1197,15 @@ MacroAssembler::convertUInt64ToDouble(Register64 src, FloatRegister dest, Regist
|
||||
// after the operation each 64-bit part of dest represents following:
|
||||
// HI(dest) = double(0x HHHHHHHH 00000000)
|
||||
// LO(dest) = double(0x 00000000 LLLLLLLL)
|
||||
vsubpd(Operand(temp, sizeof(uint64_t) * 2), dest128, dest128);
|
||||
static const int32_t CST2[4] = {
|
||||
0x0,
|
||||
0x43300000,
|
||||
0x0,
|
||||
0x45300000,
|
||||
};
|
||||
|
||||
loadConstantSimd128Int(SimdConstant::CreateX4(CST2), ScratchSimd128Reg);
|
||||
vsubpd(ScratchSimd128Reg, dest128, dest128);
|
||||
|
||||
// Add HI(dest) and LO(dest) in double and store it into LO(dest),
|
||||
// LO(dest) = double(0x HHHHHHHH 00000000) + double(0x 00000000 LLLLLLLL)
|
||||
|
@ -2107,6 +2107,48 @@ DefineNonexistentProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
|
||||
return result.succeed();
|
||||
}
|
||||
|
||||
bool
|
||||
js::AddOrUpdateSparseElementHelper(JSContext* cx, HandleArrayObject obj, int32_t int_id,
|
||||
HandleValue v, bool strict)
|
||||
{
|
||||
MOZ_ASSERT(INT_FITS_IN_JSID(int_id));
|
||||
RootedId id(cx, INT_TO_JSID(int_id));
|
||||
|
||||
// This helper doesn't handle the case where the index may be in the dense elements
|
||||
MOZ_ASSERT(int_id >= 0);
|
||||
MOZ_ASSERT(uint32_t(int_id) >= obj->getDenseInitializedLength());
|
||||
|
||||
// First decide if this is an add or an update. Because the IC guards have
|
||||
// already ensured this exists exterior to the dense array range, and the
|
||||
// prototype checks have ensured there are no indexes on the prototype, we
|
||||
// can use the shape lineage to find the element if it exists:
|
||||
RootedShape shape(cx, obj->lastProperty()->search(cx, id));
|
||||
|
||||
// If we didn't find the shape, we're on the add path: delegate to
|
||||
// AddSparseElement:
|
||||
if (shape == nullptr) {
|
||||
Rooted<PropertyDescriptor> desc(cx);
|
||||
desc.setDataDescriptor(v, JSPROP_ENUMERATE);
|
||||
desc.assertComplete();
|
||||
|
||||
return AddOrChangeProperty<IsAddOrChange::Add>(cx, obj, id, desc);
|
||||
}
|
||||
|
||||
// At this point we're updating a property: See SetExistingProperty
|
||||
if (shape->writable() && shape->isDataProperty()) {
|
||||
// While all JSID_INT properties use a single TI entry,
|
||||
// nothing yet has inspected the updated value so we *must* use setSlotWithType().
|
||||
obj->setSlotWithType(cx, shape, v, /* overwriting = */ true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// We don't know exactly what this object looks like, hit the slowpath.
|
||||
RootedValue receiver(cx, ObjectValue(*obj));
|
||||
JS::ObjectOpResult result;
|
||||
return SetProperty(cx, obj, id, v, receiver, result) &&
|
||||
result.checkStrictErrorOrWarning(cx, obj, id, strict);
|
||||
}
|
||||
|
||||
|
||||
/*** [[HasProperty]] *****************************************************************************/
|
||||
|
||||
|
@ -1622,6 +1622,10 @@ bool
|
||||
SetPropertyOnProto(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
|
||||
HandleValue receiver, ObjectOpResult& result);
|
||||
|
||||
bool
|
||||
AddOrUpdateSparseElementHelper(JSContext* cx, HandleArrayObject obj, int32_t int_id,
|
||||
HandleValue v, bool strict);
|
||||
|
||||
/*
|
||||
* Indicates whether an assignment operation is qualified (`x.y = 0`) or
|
||||
* unqualified (`y = 0`). In strict mode, the latter is an error if no such
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user