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

This commit is contained in:
Narcis Beleuzu 2017-11-08 12:57:37 +02:00
Родитель ae1b806daa 218e1676cb
Коммит 82324f1ffe
470 изменённых файлов: 3170 добавлений и 3508 удалений

Просмотреть файл

@ -222,6 +222,31 @@ endif
default all::
$(call BUILDSTATUS,TIERS $(TIERS) $(if $(MOZ_AUTOMATION),$(MOZ_AUTOMATION_TIERS)))
# PGO build target.
profiledbuild::
$(call BUILDSTATUS,TIERS pgo_profile_generate pgo_package pgo_profile pgo_clobber pgo_profile_use)
$(call BUILDSTATUS,TIER_START pgo_profile_generate)
$(MAKE) default MOZ_PROFILE_GENERATE=1 MOZ_PGO_INSTRUMENTED=1
$(call BUILDSTATUS,TIER_FINISH pgo_profile_generate)
$(call BUILDSTATUS,TIER_START pgo_package)
$(MAKE) package MOZ_PGO_INSTRUMENTED=1 MOZ_INTERNAL_SIGNING_FORMAT= MOZ_EXTERNAL_SIGNING_FORMAT=
rm -f jarlog/en-US.log
$(call BUILDSTATUS,TIER_FINISH pgo_package)
$(call BUILDSTATUS,TIER_START pgo_profile)
MOZ_PGO_INSTRUMENTED=1 JARLOG_FILE=jarlog/en-US.log $(PYTHON) $(topsrcdir)/build/pgo/profileserver.py 10
$(call BUILDSTATUS,TIER_FINISH pgo_profile)
$(call BUILDSTATUS,TIER_START pgo_clobber)
$(MAKE) maybe_clobber_profiledbuild
$(call BUILDSTATUS,TIER_FINISH pgo_clobber)
$(call BUILDSTATUS,TIER_START pgo_profile_use)
$(MAKE) default MOZ_PROFILE_USE=1
$(call BUILDSTATUS,TIER_FINISH pgo_profile_use)
# Change default target to PGO build if PGO is enabled.
ifdef MOZ_PGO
OVERRIDE_DEFAULT_GOAL := profiledbuild
endif
include $(topsrcdir)/config/rules.mk
ifdef SCCACHE_VERBOSE_STATS

Просмотреть файл

@ -85,6 +85,7 @@ const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 5
const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 6;
const PREF_BLOCKLIST_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds";
const PREF_BLOCKLIST_LAST_FETCHED = "services.blocklist.last_update_seconds";
const PREF_SSL_IMPACT_ROOTS = ["security.tls.version.", "security.ssl3."];
@ -262,6 +263,24 @@ var AboutNetAndCertErrorListener = {
}
},
_getCertValidityRange() {
let {securityInfo} = docShell.failedChannel;
securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
let certs = securityInfo.failedCertChain.getEnumerator();
let notBefore = 0;
let notAfter = Number.MAX_SAFE_INTEGER;
while (certs.hasMoreElements()) {
let cert = certs.getNext();
cert.QueryInterface(Ci.nsIX509Cert);
notBefore = Math.max(notBefore, cert.validity.notBefore);
notAfter = Math.min(notAfter, cert.validity.notAfter);
}
// nsIX509Cert reports in PR_Date terms, which uses microseconds. Convert:
notBefore /= 1000;
notAfter /= 1000;
return {notBefore, notAfter};
},
onCertErrorDetails(msg) {
let div = content.document.getElementById("certificateErrorText");
div.textContent = msg.data.info;
@ -284,26 +303,30 @@ var AboutNetAndCertErrorListener = {
// We check against Kinto time first if available, because that allows us
// to give the user an approximation of what the correct time is.
let difference = 0;
if (Services.prefs.getPrefType(PREF_BLOCKLIST_CLOCK_SKEW_SECONDS)) {
difference = Services.prefs.getIntPref(PREF_BLOCKLIST_CLOCK_SKEW_SECONDS);
}
let difference = Services.prefs.getIntPref(PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, 0);
let lastFetched = Services.prefs.getIntPref(PREF_BLOCKLIST_LAST_FETCHED, 0) * 1000;
// If the difference is more than a day.
if (Math.abs(difference) > 60 * 60 * 24) {
let now = Date.now();
let certRange = this._getCertValidityRange();
let approximateDate = now - difference * 1000;
// If the difference is more than a day, we last fetched the date in the last 5 days,
// and adjusting the date per the interval would make the cert valid, warn the user:
if (Math.abs(difference) > 60 * 60 * 24 && (now - lastFetched) <= 60 * 60 * 24 * 5 &&
certRange.notBefore < approximateDate && certRange.notAfter > approximateDate) {
let formatter = Services.intl.createDateTimeFormat(undefined, {
dateStyle: "short"
});
let systemDate = formatter.format(new Date());
// negative difference means local time is behind server time
let actualDate = formatter.format(new Date(Date.now() - difference * 1000));
approximateDate = formatter.format(new Date(approximateDate));
content.document.getElementById("wrongSystemTime_URL")
.textContent = content.document.location.hostname;
content.document.getElementById("wrongSystemTime_systemDate")
.textContent = systemDate;
content.document.getElementById("wrongSystemTime_actualDate")
.textContent = actualDate;
.textContent = approximateDate;
content.document.getElementById("errorShortDesc")
.style.display = "none";
@ -322,7 +345,11 @@ var AboutNetAndCertErrorListener = {
let buildDate = new Date(year, month, day);
let systemDate = new Date();
if (buildDate > systemDate) {
// We don't check the notBefore of the cert with the build date,
// as it is of course almost certain that it is now later than the build date,
// so we shouldn't exclude the possibility that the cert has become valid
// since the build date.
if (buildDate > systemDate && new Date(certRange.notAfter) > buildDate) {
let formatter = Services.intl.createDateTimeFormat(undefined, {
dateStyle: "short"
});

Просмотреть файл

@ -217,7 +217,7 @@ add_task(async function checkWrongSystemTimeWarning() {
is(message.divDisplay, "none", "Wrong time message information is not visible");
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
}).skip(); // Skipping because of bug 1414804.
add_task(async function checkAdvancedDetails() {
info("Loading a bad cert page and verifying the main error and advanced details section");

Просмотреть файл

@ -7,7 +7,6 @@ fi
ac_add_options --enable-official-branding
ac_add_options --enable-verify-mar
mk_add_options MOZ_PGO=1
ac_add_options MOZ_PGO=1
. "$topsrcdir/build/mozconfig.common.override"

Просмотреть файл

@ -17,7 +17,7 @@ STRIP_FLAGS="--strip-debug"
ac_add_options --with-branding=browser/branding/aurora
mk_add_options MOZ_PGO=1
ac_add_options MOZ_PGO=1
# Enable MOZ_ALLOW_LEGACY_EXTENSIONS
ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"

Просмотреть файл

@ -10,8 +10,7 @@ fi
ac_add_options --enable-official-branding
ac_add_options --enable-verify-mar
mk_add_options MOZ_PGO=1
ac_add_options MOZ_PGO=1
# safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
# defines.sh during the beta cycle

Просмотреть файл

@ -7,7 +7,6 @@ fi
ac_add_options --enable-official-branding
ac_add_options --enable-verify-mar
mk_add_options MOZ_PGO=1
ac_add_options MOZ_PGO=1
. "$topsrcdir/build/mozconfig.common.override"

Просмотреть файл

@ -17,7 +17,7 @@ STRIP_FLAGS="--strip-debug"
ac_add_options --with-branding=browser/branding/aurora
mk_add_options MOZ_PGO=1
ac_add_options MOZ_PGO=1
# Enable MOZ_ALLOW_LEGACY_EXTENSIONS
ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"

Просмотреть файл

@ -11,7 +11,7 @@ fi
ac_add_options --enable-official-branding
ac_add_options --enable-verify-mar
mk_add_options MOZ_PGO=1
ac_add_options MOZ_PGO=1
# safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
# defines.sh during the beta cycle

Просмотреть файл

@ -42,6 +42,7 @@ whitelist['nightly']['linux64'] += [
whitelist['nightly']['macosx64'] += [
'if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then',
'if test "${MOZ_UPDATE_CHANNEL}" = "nightly-try"; then',
'ac_add_options --with-macbundlename-prefix=Firefox',
'fi',
'mk_add_options MOZ_MAKE_FLAGS="-j12"',
@ -74,19 +75,19 @@ for platform in all_platforms:
'MOZ_AUTOMATION_UPDATE_PACKAGING=1',
'fi',
]
whitelist['release']['win32'] += ['mk_add_options MOZ_PGO=1']
whitelist['release']['win64'] += ['mk_add_options MOZ_PGO=1']
whitelist['release']['win32'] += ['ac_add_options MOZ_PGO=1']
whitelist['release']['win64'] += ['ac_add_options MOZ_PGO=1']
whitelist['release']['linux32'] += [
'export MOZILLA_OFFICIAL=1',
'export MOZ_TELEMETRY_REPORTING=1',
'mk_add_options MOZ_PGO=1',
'ac_add_options MOZ_PGO=1',
"mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
]
whitelist['release']['linux64'] += [
'export MOZILLA_OFFICIAL=1',
'export MOZ_TELEMETRY_REPORTING=1',
'mk_add_options MOZ_PGO=1',
'ac_add_options MOZ_PGO=1',
"mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
]

Просмотреть файл

@ -6,8 +6,7 @@ fi
. "$topsrcdir/build/mozconfig.win-common"
. "$topsrcdir/browser/config/mozconfigs/win32/common-opt"
mk_add_options MOZ_PGO=1
ac_add_options MOZ_PGO=1
ac_add_options --enable-official-branding
ac_add_options --enable-verify-mar

Просмотреть файл

@ -12,8 +12,7 @@ MOZ_REQUIRE_SIGNING=0
ac_add_options --enable-verify-mar
ac_add_options --with-branding=browser/branding/aurora
mk_add_options MOZ_PGO=1
ac_add_options MOZ_PGO=1
# Enable MOZ_ALLOW_LEGACY_EXTENSIONS
ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"

Просмотреть файл

@ -9,8 +9,7 @@ fi
. "$topsrcdir/build/mozconfig.win-common"
. "$topsrcdir/browser/config/mozconfigs/win32/common-opt"
mk_add_options MOZ_PGO=1
ac_add_options MOZ_PGO=1
ac_add_options --enable-official-branding
ac_add_options --enable-verify-mar

Просмотреть файл

@ -7,8 +7,7 @@ fi
. "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
. "$topsrcdir/browser/config/mozconfigs/win64/common-opt"
mk_add_options MOZ_PGO=1
ac_add_options MOZ_PGO=1
ac_add_options --enable-official-branding
ac_add_options --enable-verify-mar

Просмотреть файл

@ -13,8 +13,7 @@ MOZ_REQUIRE_SIGNING=0
ac_add_options --enable-verify-mar
ac_add_options --with-branding=browser/branding/aurora
mk_add_options MOZ_PGO=1
ac_add_options MOZ_PGO=1
# Enable MOZ_ALLOW_LEGACY_EXTENSIONS
ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"

Просмотреть файл

@ -10,8 +10,7 @@ fi
. "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
. "$topsrcdir/browser/config/mozconfigs/win64/common-opt"
mk_add_options MOZ_PGO=1
ac_add_options MOZ_PGO=1
ac_add_options --enable-official-branding
ac_add_options --enable-verify-mar

Просмотреть файл

@ -431,24 +431,44 @@ FormAutofillParent.prototype = {
async _onCreditCardSubmit(creditCard, target, timeStartedFillingMS) {
// We'll show the credit card doorhanger if:
// - User applys autofill and changed
// - User fills form manually
if (creditCard.guid &&
Object.keys(creditCard.record).every(key => creditCard.untouchedFields.includes(key))) {
// Add probe to record credit card autofill(without modification).
Services.telemetry.scalarAdd("formautofill.creditCards.fill_type_autofill", 1);
this._recordFormFillingTime("creditCard", "autofill", timeStartedFillingMS);
return;
}
// Add the probe to record credit card manual filling or autofill but modified case.
// - User fills form manually and the filling data is not duplicated to storage
if (creditCard.guid) {
let originalCCData = this.profileStorage.creditCards.get(creditCard.guid);
let unchanged = Object.keys(creditCard.record).every(field => {
if (creditCard.record[field] === "" && !originalCCData[field]) {
return true;
}
// Avoid updating the fields that users don't modify.
let untouched = creditCard.untouchedFields.includes(field);
if (untouched) {
creditCard.record[field] = originalCCData[field];
}
return untouched;
});
if (unchanged) {
this.profileStorage.creditCards.notifyUsed(creditCard.guid);
// Add probe to record credit card autofill(without modification).
Services.telemetry.scalarAdd("formautofill.creditCards.fill_type_autofill", 1);
this._recordFormFillingTime("creditCard", "autofill", timeStartedFillingMS);
return;
}
// Add the probe to record credit card autofill with modification.
Services.telemetry.scalarAdd("formautofill.creditCards.fill_type_autofill_modified", 1);
this._recordFormFillingTime("creditCard", "autofill-update", timeStartedFillingMS);
} else {
// Add the probe to record credit card manual filling.
Services.telemetry.scalarAdd("formautofill.creditCards.fill_type_manual", 1);
this._recordFormFillingTime("creditCard", "manual", timeStartedFillingMS);
}
// Early return if it's a duplicate data
let dupGuid = this.profileStorage.creditCards.getDuplicateGuid(creditCard.record);
if (dupGuid) {
this.profileStorage.creditCards.notifyUsed(dupGuid);
return;
}
let state = await FormAutofillDoorhanger.show(target, "creditCard");
if (state == "cancel") {
return;
@ -466,7 +486,18 @@ FormAutofillParent.prototype = {
return;
}
this.profileStorage.creditCards.add(creditCard.record);
let changedGUIDs = [];
// TODO: Autofill(with guid) case should show update doorhanger with update/create new.
// It'll be implemented in bug 1403881 and only avoid mergering for now.
if (creditCard.guid) {
changedGUIDs.push(this.profileStorage.creditCards.add(creditCard.record));
} else {
changedGUIDs.push(...this.profileStorage.creditCards.mergeToStorage(creditCard.record));
if (!changedGUIDs.length) {
changedGUIDs.push(this.profileStorage.creditCards.add(creditCard.record));
}
}
changedGUIDs.forEach(guid => this.profileStorage.creditCards.notifyUsed(guid));
},
_onFormSubmit(data, target) {

Просмотреть файл

@ -1625,6 +1625,88 @@ class CreditCards extends AutofillRecords {
delete creditCard["cc-exp"];
}
/**
* Normailze the given record and retrun the first matched guid if storage has the same record.
* @param {Object} targetCreditCard
* The credit card for duplication checking.
* @returns {string|null}
* Return the first guid if storage has the same credit card and null otherwise.
*/
getDuplicateGuid(targetCreditCard) {
let clonedTargetCreditCard = this._clone(targetCreditCard);
this._normalizeRecord(clonedTargetCreditCard);
for (let creditCard of this.data) {
let isDuplicate = this.VALID_FIELDS.every(field => {
if (!clonedTargetCreditCard[field]) {
return !creditCard[field];
}
if (field == "cc-number") {
return this._getMaskedCCNumber(clonedTargetCreditCard[field]) == creditCard[field];
}
return clonedTargetCreditCard[field] == creditCard[field];
});
if (isDuplicate) {
return creditCard.guid;
}
}
return null;
}
/**
* Merge new credit card into the specified record if cc-number is identical.
*
* @param {string} guid
* Indicates which credit card to merge.
* @param {Object} creditCard
* The new credit card used to merge into the old one.
* @returns {boolean}
* Return true if credit card is merged into target with specific guid or false if not.
*/
mergeIfPossible(guid, creditCard) {
this.log.debug("mergeIfPossible:", guid, creditCard);
// Query raw data for comparing the decrypted credit card number
let creditCardFound = this.get(guid, {rawData: true});
if (!creditCardFound) {
throw new Error("No matching credit card.");
}
let creditCardToMerge = this._cloneAndCleanUp(creditCard);
this._normalizeRecord(creditCardToMerge);
for (let field of this.VALID_FIELDS) {
let existingField = creditCardFound[field];
// Make sure credit card field is existed and have value
if (field == "cc-number" && (!existingField || !creditCardToMerge[field])) {
return false;
}
if (!creditCardToMerge[field]) {
creditCardToMerge[field] = existingField;
}
let incomingField = creditCardToMerge[field];
if (incomingField && existingField) {
if (incomingField != existingField) {
this.log.debug("Conflicts: field", field, "has different value.");
return false;
}
}
}
// Early return if the data is the same.
let exactlyMatch = this.VALID_FIELDS.every((field) =>
creditCardFound[field] === creditCardToMerge[field]
);
if (exactlyMatch) {
return true;
}
this.update(guid, creditCardToMerge, true);
return true;
}
}
function ProfileStorage(path) {

Просмотреть файл

@ -45,8 +45,9 @@ add_task(async function test_submit_creditCard_saved() {
name.focus();
name.setUserInput("User 1");
let number = form.querySelector("#cc-number");
number.setUserInput("1111222233334444");
form.querySelector("#cc-number").setUserInput("1111222233334444");
form.querySelector("#cc-exp-month").setUserInput("12");
form.querySelector("#cc-exp-year").setUserInput("2017");
// Wait 1000ms before submission to make sure the input value applied
await new Promise(resolve => setTimeout(resolve, 1000));
@ -61,6 +62,136 @@ add_task(async function test_submit_creditCard_saved() {
let creditCards = await getCreditCards();
is(creditCards.length, 1, "1 credit card in storage");
is(creditCards[0]["cc-name"], "User 1", "Verify the name field");
await removeAllRecords();
});
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");
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 ContentTask.spawn(browser, null, async function() {
let form = content.document.getElementById("form");
// 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 sleep(1000);
is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
}
);
creditCards = await getCreditCards();
is(creditCards.length, 1, "Still 1 credit card");
is(creditCards[0].timesUsed, 1, "timesUsed field set to 1");
await removeAllRecords();
});
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");
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("");
form.querySelector("#cc-number").setUserInput("1234567812345678");
form.querySelector("#cc-exp-month").setUserInput("4");
form.querySelector("#cc-exp-year").setUserInput("2017");
// 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);
}
);
creditCards = await getCreditCards();
is(creditCards.length, 1, "Still 1 credit card in storage");
is(creditCards[0]["cc-name"], TEST_CREDIT_CARD_1["cc-name"], "name field still exists");
await removeAllRecords();
});
add_task(async function test_submit_duplicate_creditCard_form() {
await saveCreditCard(TEST_CREDIT_CARD_1);
let creditCards = await getCreditCards();
is(creditCards.length, 1, "1 credit card in storage");
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
async function(browser) {
await ContentTask.spawn(browser, null, async function() {
let form = content.document.getElementById("form");
let name = form.querySelector("#cc-name");
name.focus();
name.setUserInput("John Doe");
form.querySelector("#cc-number").setUserInput("1234567812345678");
form.querySelector("#cc-exp-month").setUserInput("4");
form.querySelector("#cc-exp-year").setUserInput("2017");
// 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 sleep(1000);
is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
}
);
creditCards = await getCreditCards();
is(creditCards.length, 1, "Still 1 credit card in storage");
is(creditCards[0]["cc-name"], TEST_CREDIT_CARD_1["cc-name"], "Verify the name field");
is(creditCards[0].timesUsed, 1, "timesUsed field set to 1");
await removeAllRecords();
});
add_task(async function test_submit_unnormailzed_creditCard_form() {
await saveCreditCard(TEST_CREDIT_CARD_1);
let creditCards = await getCreditCards();
is(creditCards.length, 1, "1 credit card in storage");
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
async function(browser) {
await ContentTask.spawn(browser, null, async function() {
let form = content.document.getElementById("form");
let name = form.querySelector("#cc-name");
name.focus();
name.setUserInput("John Doe");
form.querySelector("#cc-number").setUserInput("1234567812345678");
form.querySelector("#cc-exp-month").setUserInput("4");
// Set unnormalized year
form.querySelector("#cc-exp-year").setUserInput("17");
// 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 sleep(1000);
is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
}
);
creditCards = await getCreditCards();
is(creditCards.length, 1, "Still 1 credit card in storage");
is(creditCards[0]["cc-exp-year"], "2017", "Verify the expiry year field");
await removeAllRecords();
});
add_task(async function test_submit_creditCard_never_save() {
@ -91,7 +222,7 @@ add_task(async function test_submit_creditCard_never_save() {
await sleep(1000);
let creditCards = await getCreditCards();
let creditCardPref = SpecialPowers.getBoolPref(ENABLED_AUTOFILL_CREDITCARDS_PREF);
is(creditCards.length, 1, "Still 1 credit card in storage");
is(creditCards.length, 0, "No credit card in storage");
is(creditCardPref, false, "Credit card is disabled");
SpecialPowers.clearUserPref(ENABLED_AUTOFILL_CREDITCARDS_PREF);
});
@ -127,10 +258,11 @@ add_task(async function test_submit_creditCard_saved_with_mp_enabled() {
);
let creditCards = await getCreditCards();
is(creditCards.length, 2, "2 credit cards in storage");
is(creditCards[1]["cc-name"], "User 0", "Verify the name field");
is(creditCards[1]["cc-number"], "************1234", "Verify the card number field");
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"], "************1234", "Verify the card number field");
LoginTestUtils.masterPassword.disable();
await removeAllRecords();
});
add_task(async function test_submit_creditCard_saved_with_mp_enabled_but_canceled() {
@ -163,7 +295,7 @@ add_task(async function test_submit_creditCard_saved_with_mp_enabled_but_cancele
await sleep(1000);
let creditCards = await getCreditCards();
is(creditCards.length, 2, "Still 2 credit cards in storage");
is(creditCards.length, 0, "No credit cards in storage");
LoginTestUtils.masterPassword.disable();
});
@ -217,7 +349,7 @@ add_task(async function test_submit_creditCard_with_sync_account() {
"creditCards sync should be disabled after unchecked");
is(secondaryButton.disabled, false, "Not saving button should be enabled again");
is(menuButton.disabled, false, "Never saving menu button should be enabled again");
await clickDoorhangerButton(MAIN_BUTTON);
await clickDoorhangerButton(SECONDARY_BUTTON);
}
);
});
@ -252,7 +384,40 @@ add_task(async function test_submit_creditCard_with_synced_already() {
await promiseShown;
let cb = getDoorhangerCheckbox();
ok(cb.hidden, "Sync checkbox should be hidden");
await clickDoorhangerButton(MAIN_BUTTON);
await clickDoorhangerButton(SECONDARY_BUTTON);
}
);
});
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");
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();
name.setUserInput("User 3");
form.querySelector("#cc-number").setUserInput("9999888877776666");
form.querySelector("#cc-exp-month").setUserInput("1");
form.querySelector("#cc-exp-year").setUserInput("2000");
// 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);
}
);
creditCards = await getCreditCards();
is(creditCards.length, 1, "Still 1 credit card in storage");
is(creditCards[0]["cc-name"], "User 3", "Verify the name field");
await removeAllRecords();
});

Просмотреть файл

@ -6,7 +6,7 @@
sleep, expectPopupOpen, openPopupOn, expectPopupClose, closePopup, clickDoorhangerButton,
getAddresses, saveAddress, removeAddresses, saveCreditCard,
getDisplayedPopupItems, getDoorhangerCheckbox, waitForMasterPasswordDialog,
getNotification, getDoorhangerButton */
getNotification, getDoorhangerButton, removeAllRecords */
"use strict";
@ -19,7 +19,7 @@ const EDIT_CREDIT_CARD_DIALOG_URL = "chrome://formautofill/content/editCreditCar
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 =
"http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_creditcard_basic.html";
"https://example.org/browser/browser/extensions/formautofill/test/browser/autocomplete_creditcard_basic.html";
const FTU_PREF = "extensions.formautofill.firstTimeUse";
const ENABLED_AUTOFILL_ADDRESSES_PREF = "extensions.formautofill.addresses.enabled";
const AUTOFILL_CREDITCARDS_AVAILABLE_PREF = "extensions.formautofill.creditCards.available";
@ -287,7 +287,7 @@ function waitForMasterPasswordDialog(login = false) {
});
}
registerCleanupFunction(async function() {
async function removeAllRecords() {
let addresses = await getAddresses();
if (addresses.length) {
await removeAddresses(addresses.map(address => address.guid));
@ -297,4 +297,6 @@ registerCleanupFunction(async function() {
if (creditCards.length) {
await removeCreditCards(creditCards.map(cc => cc.guid));
}
});
}
registerCleanupFunction(removeAllRecords);

Просмотреть файл

@ -28,6 +28,11 @@ const TEST_CREDIT_CARD_3 = {
"cc-exp-year": 2000,
};
const TEST_CREDIT_CARD_4 = {
"cc-name": "Foo Bar",
"cc-number": "9999888877776666",
};
const TEST_CREDIT_CARD_WITH_EMPTY_FIELD = {
"cc-name": "",
"cc-number": "1234123412341234",
@ -58,6 +63,68 @@ const TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS = {
"cc-number": "1111 2222 3333 4444",
};
const MERGE_TESTCASES = [
{
description: "Merge a superset",
creditCardInStorage: {
"cc-number": "1234567812345678",
"cc-exp-month": 4,
"cc-exp-year": 2017,
},
creditCardToMerge: {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-exp-month": 4,
"cc-exp-year": 2017,
},
expectedCreditCard: {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-exp-month": 4,
"cc-exp-year": 2017,
},
},
{
description: "Merge a subset",
creditCardInStorage: {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-exp-month": 4,
"cc-exp-year": 2017,
},
creditCardToMerge: {
"cc-number": "1234567812345678",
"cc-exp-month": 4,
"cc-exp-year": 2017,
},
expectedCreditCard: {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-exp-month": 4,
"cc-exp-year": 2017,
},
noNeedToUpdate: true,
},
{
description: "Merge an creditCard with partial overlaps",
creditCardInStorage: {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
},
creditCardToMerge: {
"cc-number": "1234567812345678",
"cc-exp-month": 4,
"cc-exp-year": 2017,
},
expectedCreditCard: {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-exp-month": 4,
"cc-exp-year": 2017,
},
},
];
let prepareTestCreditCards = async function(path) {
let profileStorage = new ProfileStorage(path);
await profileStorage.initialize();
@ -315,3 +382,81 @@ add_task(async function test_remove() {
do_check_eq(profileStorage.creditCards.get(guid), null);
});
MERGE_TESTCASES.forEach((testcase) => {
add_task(async function test_merge() {
do_print("Starting testcase: " + testcase.description);
let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
[testcase.creditCardInStorage],
"creditCards");
let creditCards = profileStorage.creditCards.getAll();
let guid = creditCards[0].guid;
let timeLastModified = creditCards[0].timeLastModified;
// Merge creditCard and verify the guid in notifyObservers subject
let onMerged = TestUtils.topicObserved(
"formautofill-storage-changed",
(subject, data) =>
data == "update" && subject.QueryInterface(Ci.nsISupportsString).data == guid
);
// Force to create sync metadata.
profileStorage.creditCards.pullSyncChanges();
do_check_eq(getSyncChangeCounter(profileStorage.creditCards, guid), 1);
Assert.ok(profileStorage.creditCards.mergeIfPossible(guid, testcase.creditCardToMerge));
if (!testcase.noNeedToUpdate) {
await onMerged;
}
creditCards = profileStorage.creditCards.getAll();
Assert.equal(creditCards.length, 1);
do_check_credit_card_matches(creditCards[0], testcase.expectedCreditCard);
if (!testcase.noNeedToUpdate) {
// Record merging should update timeLastModified and bump the change counter.
Assert.notEqual(creditCards[0].timeLastModified, timeLastModified);
do_check_eq(getSyncChangeCounter(profileStorage.creditCards, guid), 2);
} else {
// Subset record merging should not update timeLastModified and the change
// counter is still the same.
Assert.equal(creditCards[0].timeLastModified, timeLastModified);
do_check_eq(getSyncChangeCounter(profileStorage.creditCards, guid), 1);
}
});
});
add_task(async function test_merge_unable_merge() {
let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
[TEST_CREDIT_CARD_1],
"creditCards");
let creditCards = profileStorage.creditCards.getAll();
let guid = creditCards[0].guid;
// Force to create sync metadata.
profileStorage.creditCards.pullSyncChanges();
do_check_eq(getSyncChangeCounter(profileStorage.creditCards, guid), 1);
// Unable to merge because of conflict
let anotherCreditCard = profileStorage.creditCards._clone(TEST_CREDIT_CARD_1);
anotherCreditCard["cc-name"] = "Foo Bar";
do_check_eq(profileStorage.creditCards.mergeIfPossible(guid, anotherCreditCard), false);
// The change counter is unchanged.
do_check_eq(getSyncChangeCounter(profileStorage.creditCards, guid), 1);
// Unable to merge because no credit card number
anotherCreditCard = profileStorage.creditCards._clone(TEST_CREDIT_CARD_1);
anotherCreditCard["cc-number"] = "";
do_check_eq(profileStorage.creditCards.mergeIfPossible(guid, anotherCreditCard), false);
// The change counter is still unchanged.
do_check_eq(getSyncChangeCounter(profileStorage.creditCards, guid), 1);
});
add_task(async function test_mergeToStorage() {
let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
[TEST_CREDIT_CARD_3, TEST_CREDIT_CARD_4],
"creditCards");
// Merge a creditCard to storage
let anotherCreditCard = profileStorage.creditCards._clone(TEST_CREDIT_CARD_3);
anotherCreditCard["cc-name"] = "Foo Bar";
do_check_eq(profileStorage.creditCards.mergeToStorage(anotherCreditCard).length, 2);
do_check_eq(profileStorage.creditCards.getAll()[0]["cc-name"], "Foo Bar");
do_check_eq(profileStorage.creditCards.getAll()[0]["cc-exp"], "2000-01");
do_check_eq(profileStorage.creditCards.getAll()[1]["cc-name"], "Foo Bar");
do_check_eq(profileStorage.creditCards.getAll()[1]["cc-exp"], "2000-01");
});

6
browser/extensions/onboarding/bootstrap.js поставляемый
Просмотреть файл

@ -43,12 +43,12 @@ let waitingForBrowserReady = true;
let startupData;
/**
* Set pref. Why no `getPrefs` function is due to the priviledge level.
* Set pref. Why no `getPrefs` function is due to the privilege level.
* We cannot set prefs inside a framescript but can read.
* For simplicity and effeciency, we still read prefs inside the framescript.
* For simplicity and efficiency, we still read prefs inside the framescript.
*
* @param {Array} prefs the array of prefs to set.
* The array element carrys info to set pref, should contain
* The array element carries info to set pref, should contain
* - {String} name the pref name, such as `browser.onboarding.state`
* - {*} value the value to set
**/

Просмотреть файл

@ -31,7 +31,7 @@ const ICON_STATE_DEFAULT = "default";
* "tourId": { // The short tour id which could be saved in pref
* // The unique tour id
* id: "onboarding-tour-addons",
* // (optional) mark tour as complete instantly when user enters the tour
* // (optional) mark tour as complete instantly when the user enters the tour
* instantComplete: false,
* // The string id of tour name which would be displayed on the navigation bar
* tourNameId: "onboarding.tour-addon",
@ -372,7 +372,7 @@ class Onboarding {
async init(contentWindow) {
this._window = contentWindow;
// session_key is used for telemetry to track the current tab.
// The number will renew after reload the page.
// The number will renew after reloading the page.
this._session_key = Date.now();
this._tours = [];
this._tourType = Services.prefs.getStringPref("browser.onboarding.tour-type", "update");
@ -397,7 +397,7 @@ class Onboarding {
this._window.addEventListener("resize", this);
// Destroy on unload. This is to ensure we remove all the stuff we left.
// Destroy on unloading. This is to ensure we remove all the stuff we left.
// No any leak out there.
this._window.addEventListener("unload", () => this.destroy());
@ -468,7 +468,7 @@ class Onboarding {
let doc = this._window.document;
if (doc.hidden) {
// When the preloaded-browser feature is on,
// it would preload an hidden about:newtab in the background.
// it would preload a hidden about:newtab in the background.
// We don't want to show notification in that hidden state.
let onVisible = () => {
if (!doc.hidden) {
@ -595,9 +595,12 @@ class Onboarding {
}
/**
* Wrap keyboard focus within the dialog and focus on first element after last
* when moving forward or last element after first when moving backwards. Do
* nothing if focus is moving in the middle of the list of dialog's focusable
* Wrap keyboard focus within the dialog.
* When moving forward, focus on the first element when the current focused
* element is the last one.
* When moving backward, focus on the last element when the current focused
* element is the first one.
* Do nothing if focus is moving in the middle of the list of dialog's focusable
* elements.
*
* @param {DOMNode} current currently focused element
@ -633,7 +636,7 @@ class Onboarding {
return;
}
// Current focused item can be tab container if previous navigation was done
// Currently focused item could be tab container if previous navigation was done
// via mouse.
if (target.classList.contains("onboarding-tour-item-container")) {
target = target.firstChild;
@ -770,20 +773,20 @@ class Onboarding {
[...doc.body.children].forEach(
child => child.id !== "onboarding-overlay" &&
child.setAttribute("aria-hidden", true));
// When dialog is opened with the keyboard, focus on the 1st uncomplete tour
// because it will be the selected tour
// When dialog is opened with the keyboard, focus on the first
// uncomplete tour because it will be the selected tour.
if (this._overlayIcon.dataset.keyboardFocus) {
doc.getElementById(this._firstUncompleteTour.id).focus();
} else {
// When dialog is opened with mouse, focus on the dialog itself to avoid
// visible keyboard focus styling.
// When the dialog is opened with the mouse, focus on the dialog
// itself to avoid visible keyboard focus styling.
this._dialog.focus();
}
} else {
// Remove all set aria-hidden attributes.
[...doc.body.children].forEach(
child => child.removeAttribute("aria-hidden"));
// If dialog was opened with a keyboard, set the focus back on the overlay
// If dialog was opened with a keyboard, set the focus back to the overlay
// button.
if (this._overlayIcon.dataset.keyboardFocus) {
delete this._overlayIcon.dataset.keyboardFocus;
@ -814,10 +817,10 @@ class Onboarding {
session_key: this._session_key,
});
// some tours should completed instantly upon showing.
// Some tours should complete instantly upon showing.
if (tab.getAttribute("data-instant-complete")) {
this.setToursCompleted([tourId]);
// also track auto completed tour so we can filter data with the same event
// Also track auto-completed tour so we can filter data with the same event.
telemetry({
event: "overlay-cta-click",
tour_id: tourId,
@ -1006,7 +1009,7 @@ class Onboarding {
if (queue.length > 0 && this._isTimeForNextTourNotification(lastTime)) {
queue.shift();
}
// We don't want to prompt completed tour.
// We don't want to prompt the completed tour.
while (queue.length > 0 && this.isTourCompleted(queue[0])) {
queue.shift();
}
@ -1242,7 +1245,7 @@ class Onboarding {
let element = l10nElements[i];
// We always put brand short name as the first argument for it's the
// only and frequently used arguments in our l10n case. Rewrite it if
// other arguments appears.
// other arguments appear.
element.textContent = this._bundle.formatStringFromName(
element.dataset.l10nId, [BRAND_SHORT_NAME], 1);
}
@ -1299,7 +1302,7 @@ if (Services.prefs.getBoolPref("browser.onboarding.enabled", false)) {
let window = evt.target.defaultView;
let location = window.location.href;
if (location == ABOUT_NEWTAB_URL || location == ABOUT_HOME_URL) {
// We just want to run tests as quick as possible
// We just want to run tests as quickly as possible
// so in the automation test, we don't do `requestIdleCallback`.
if (Cu.isInAutomation) {
new Onboarding(window);

Просмотреть файл

@ -66,15 +66,15 @@ For reference, Onyx is a Mozilla owned service to serve tiles for the current ne
|-----|-------------|:-----:|
| `addon_version` | [Required] The version of the Onboarding addon. | :one:
| `category` | [Required] Either ("overlay-interactions", "notification-interactions") to identify which kind of the interaction | :one:
| `client_id` | [Required] An identifier generated by [ClientID](https://github.com/mozilla/gecko-dev/blob/master/toolkit/modules/ClientID.jsm) module to provide an identifier for this device. Auto append by `ping-centre` module | :one:
| `client_id` | [Required] An identifier generated by [ClientID](https://github.com/mozilla/gecko-dev/blob/master/toolkit/modules/ClientID.jsm) module to provide an identifier for this device. This data is automatically appended by `ping-centre` module | :one:
| `event` | [Required] The type of event. allowed event strings are defined in the below section | :one:
| `impression` | [Optional] An integer to record how many times the current notification tour is shown to the user. Each Notification tour can show not more than 8 times. We put `-1` when this field is not relevant to this event | :one:
| `ip` | [Auto populated by Onyx] The IP address of the client. Onyx does use (with the permission) the IP address to infer user's geo information so that it could prepare the corresponding tiles for the country she lives in. However, Ping-centre will NOT store IP address in the database, where only authorized Mozilla employees can access the telemetry data, and all the raw logs are being strictly managed by the Ops team and will expire according to the Mozilla's data retention policy.| :two:
| `locale` | The browser chrome's language (eg. en-US). | :two:
| `ip` | [Auto populated by Onyx] The IP address of the client. Onyx does use (with the permission) the IP address to infer user's geo-information so that it could prepare the corresponding tiles for the country she lives in. However, Ping-centre will NOT store IP address in the database, where only authorized Mozilla employees can access the telemetry data, and all the raw logs are being strictly managed by the Ops team and will expire according to the Mozilla's data retention policy.| :two:
| `locale` | The browser chrome's language (e.g. en-US). | :two:
| `page` | [Required] One of ["about:newtab", "about:home"]| :one:
| `session_begin` | Timestamp in (integer) milliseconds when onboarding/overlay/notification becoming visible. | :one:
| `session_end` | Timestamp in (integer) milliseconds when onboarding/overlay/notification losing focus. | :one:
| `session_id` | [Required] The unique identifier generated by `gUUIDGenerator` service to identify the specific user session when onboarding is inited/when overlay is opened/when notification is shown. | :one:
| `session_id` | [Required] The unique identifier generated by `gUUIDGenerator` service to identify the specific user session when onboarding is inited/when the overlay is opened/when notification is shown. | :one:
| `timestamp` | Timestamp in (integer) milliseconds when the event triggered | :one:
| `tour_id` | id of the current tour. The number of open from notification can be retrieved via 'notification-cta-click event'. We put ` ` when this field is not relevant to this event | :one:
| `tour_source` | [Required] One of ["default", "watermark"] indicates the overlay is opened while in the default or the watermark state. Open from the notification bar is counted via 'notification-cta-click event'. | :one:
@ -96,27 +96,26 @@ Here are all allowed `event` strings that defined in `OnboardingTelemetry::EVENT
|-----------|---------------------|
| `onboarding-register-session` | internal event triggered to register a new page session. Called when the onboarding script is inited in a browser tab. Will not send out any data. |
| `onboarding-session-begin` | internal event triggered when the onboarding script is inited, will not send out any data. |
| `onboarding-session-end` | internal event triggered when the onboarding script is destoryed. `onboarding-session` event is sent to the server. |
| `onboarding-session` | event is sent when the onboarding script is destoryed |
| `onboarding-session-end` | internal event triggered when the onboarding script is destroyed. `onboarding-session` event is sent to the server. |
| `onboarding-session` | event is sent when the onboarding script is destroyed |
### Overlay events
| EVENT | DESCRIPTION |
|-----------|---------------------|
| `overlay-session-begin` | internal event triggered when user open the overlay, will not send out any data. |
| `overlay-session-end` | internal event is triggered when user close the overlay. `overlay-session` event is sent to the server. |
| `overlay-session-end` | internal event is triggered when user closes the overlay. `overlay-session` event is sent to the server. |
| `overlay-session` | event is sent when user close the overlay |
| `overlay-nav-click` | event is sent when click or auto select the overlay navigate item |
| `overlay-nav-click` | event is sent when clicking or auto select the overlay navigate item |
| `overlay-cta-click` | event is sent when user click the overlay CTA button |
| `overlay-skip-tour` | event is sent when click the overlay `skip tour` button |
| `overlay-skip-tour` | event is sent when clicking the overlay `skip tour` button |
### Notification events
| EVENT | DESCRIPTION |
|-----------|---------------------|
| `notification-session-begin` | internal event triggered when user open the notification, will not send out any data. |
| `notification-session-end` | internal event is triggered when user close the notification. `notification-session` event is sent to the server. |
| `notification-session` | event is sent when user close the notification |
| `notification-close-button-click` | event is sent when click the notification close button |
| `notification-cta-click` | event is sent when click the notification CTA button |
| `notification-session-end` | internal event is triggered when user closes the notification. `notification-session` event is sent to the server. |
| `notification-session` | event is sent when user closes the notification |
| `notification-close-button-click` | event is sent when clicking the notification close button |
| `notification-cta-click` | event is sent when clicking the notification CTA button |

Просмотреть файл

@ -46,21 +46,6 @@ add_task(async function test_clean_up_uitour_after_closing_overlay() {
await highlightClosePromise;
is(highlight.state, "closed", "Should close UITour highlight after closing the overlay by clicking the overlay");
// Trigger UITour showHighlight
highlightOpenPromise = promisePopupChange(highlight, "open");
await triggerUITourHighlight("library", tab);
await highlightOpenPromise;
is(highlight.state, "open", "Should show UITour highlight");
is(highlight.getAttribute("targetName"), "library", "UITour should highlight library");
// Close the overlay by clicking the overlay
// Should not click the close button here since the close button is hovered by appmenu and can't be clicked on win7
highlightClosePromise = promisePopupChange(highlight, "closed");
BrowserTestUtils.synthesizeMouseAtPoint(2, 2, {}, tab.linkedBrowser);
await promiseOnboardingOverlayClosed(tab.linkedBrowser);
await highlightClosePromise;
is(highlight.state, "closed", "Should close UITour highlight after closing the overlay by clicking the overlay");
// Trigger UITour showHighlight again
highlightOpenPromise = promisePopupChange(highlight, "open");
await triggerUITourHighlight("library", tab);

Просмотреть файл

@ -16,6 +16,7 @@
"as": "/builds/worker/workspace/build/src/gcc/bin/gcc",
"patches": [
"llvm-debug-frame.patch",
"hide-gcda-profiling-symbols.patch"
"hide-gcda-profiling-symbols.patch",
"pr_set_ptracer.patch"
]
}

Просмотреть файл

@ -16,6 +16,7 @@
"as": "/builds/worker/workspace/build/src/gcc/bin/gcc",
"patches": [
"llvm-debug-frame-for-5.patch",
"r313872.patch"
"r313872.patch",
"pr_set_ptracer.patch"
]
}

Просмотреть файл

@ -0,0 +1,27 @@
--- a/compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cc
@@ -57,6 +57,14 @@
#include "sanitizer_mutex.h"
#include "sanitizer_placement_new.h"
+// Sufficiently old kernel headers don't provide this value, but we can still
+// call prctl with it. If the runtime kernel is new enough, the prctl call will
+// have the desired effect; if the kernel is too old, the call will error and we
+// can ignore said error.
+#ifndef PR_SET_PTRACER
+#define PR_SET_PTRACER 0x59616d61
+#endif
+
// This module works by spawning a Linux task which then attaches to every
// thread in the caller process with ptrace. This suspends the threads, and
// PTRACE_GETREGS can then be used to obtain their register state. The callback
@@ -433,9 +441,7 @@
ScopedSetTracerPID scoped_set_tracer_pid(tracer_pid);
// On some systems we have to explicitly declare that we want to be traced
// by the tracer thread.
-#ifdef PR_SET_PTRACER
internal_prctl(PR_SET_PTRACER, tracer_pid, 0, 0, 0);
-#endif
// Allow the tracer thread to start.
tracer_thread_argument.mutex.Unlock();
// NOTE: errno is shared between this thread and the tracer thread.

Просмотреть файл

@ -8,7 +8,6 @@ from __future__ import unicode_literals
import logging
import mozunit
import subprocess
import sys
import unittest
from os import path
@ -16,25 +15,21 @@ from buildconfig import substs
log = logging.getLogger(__name__)
def determine_platform():
platform_mapping = {'WINNT': {'x86_64': 'win64',
'i686': 'win32'},
'Darwin': {'x86_64': 'macosx64'},
'Linux': {'x86_64': 'linux64',
'i686': 'linux32'}}
os_type = substs['OS_TARGET']
cpu_type = substs['TARGET_CPU']
return platform_mapping.get(os_type, {}).get(cpu_type, None)
PLATFORMS = (
'linux32',
'linux64',
'macosx64',
'win32',
'win64',
)
class TestCompareMozconfigs(unittest.TestCase):
def test_compare_mozconfigs(self):
""" A wrapper script that calls compare-mozconfig.py
based on the platform that the machine is building for"""
platform = determine_platform()
if platform is not None:
for platform in PLATFORMS:
log.info('Comparing platform %s' % platform)
python_exe = substs['PYTHON']
topsrcdir = substs['top_srcdir']
@ -53,7 +48,6 @@ class TestCompareMozconfigs(unittest.TestCase):
',' + nightly_mozconfig_path])
self.assertEqual(0, ret_code)
log.info("Comparing release against nightly mozconfigs")
ret_code = subprocess.call([python_exe, script_path, '--whitelist',
whitelist_path, '--no-download',

Просмотреть файл

@ -8,8 +8,6 @@
from __future__ import unicode_literals
import logging
import os
import site
import sys
import urllib2
import difflib

Просмотреть файл

@ -12,10 +12,14 @@ faster.
How PGO Builds Work
===================
The supported interface for invoking a PGO build is to invoke the build system
with ``MOZ_PGO`` defined. e.g.::
The supported interface for invoking a PGO build is to add ``MOZ_PGO=1`` to
configure flags and then build. e.g. in your mozconfig::
$ MOZ_PGO=1 ./mach build
ac_add_options MOZ_PGO=1
Then::
$ ./mach build
This is roughly equivalent to::

Просмотреть файл

@ -14,6 +14,7 @@ option(env='DIST', nargs=1, help='DIST directory')
@depends('--help', 'DIST')
@imports(_from='__builtin__', _import='open')
@imports(_from='os.path', _import='exists')
def check_build_environment(help, dist):
topobjdir = os.path.realpath(os.path.abspath('.'))
@ -34,6 +35,21 @@ def check_build_environment(help, dist):
if help:
return result
# This limitation has mostly to do with GNU make. Since make can't represent
# variables with spaces without correct quoting and many paths are used
# without proper quoting, using paths with spaces commonly results in
# targets or dependencies being treated as multiple paths. This, of course,
# undermines the ability for make to perform up-to-date checks and makes
# the build system not work very efficiently. In theory, a non-make build
# backend will make this limitation go away. But there is likely a long tail
# of things that will need fixing due to e.g. lack of proper path quoting.
if len(topsrcdir.split()) > 1:
die('Source directory cannot be located in a path with spaces: %s' %
topsrcdir)
if len(topobjdir.split()) > 1:
die('Object directory cannot be located in a path with spaces: %s' %
topobjdir)
if topsrcdir == topobjdir:
die(' ***\n'
' * Building directly in the main source directory is not allowed.\n'
@ -46,6 +62,29 @@ def check_build_environment(help, dist):
' ***'
)
# Check for CRLF line endings.
with open(os.path.join(topsrcdir, 'configure.py'), 'rb') as fh:
data = fh.read()
if '\r' in data:
die('\n ***\n'
' * The source tree appears to have Windows-style line endings.\n'
' *\n'
' * If using Git, Git is likely configured to use Windows-style\n'
' * line endings.\n'
' *\n'
' * To convert the working copy to UNIX-style line endings, run\n'
' * the following:\n'
' *\n'
' * $ git config core.autocrlf false\n'
' * $ git config core.eof lf\n'
' * $ git rm --cached -r .\n'
' * $ git reset --hard\n'
' *\n'
' * If not using Git, the tool you used to obtain the source\n'
' * code likely converted files to Windows line endings. See\n'
' * usage information for that tool for more.\n'
' ***')
# Check for a couple representative files in the source tree
conflict_files = [
'* %s' % f for f in ('Makefile', 'config/autoconf.mk')

Просмотреть файл

@ -1,11 +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/.
include $(topsrcdir)/config/rules.mk
$(CSRCS): %.c: ../inject.c
cp $< $@
GARBAGE += $(CSRCS)

Просмотреть файл

@ -4,9 +4,6 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
with Files('**'):
BUG_COMPONENT = ('Android Background Services', 'Build & Test')
TEST_DIRS += [
'junit3',
]
def copy(out_file, in_path):
with open(in_path, 'r') as fh:
out_file.write(fh.read())

Просмотреть файл

@ -17,8 +17,16 @@ elif CONFIG['TARGET_CPU'].startswith('arm'):
else:
cpu = CONFIG['TARGET_CPU']
gen_src = '%s.c' % cpu
GENERATED_FILES += [
gen_src,
]
GENERATED_FILES[gen_src].script = 'copy_source.py:copy'
GENERATED_FILES[gen_src].inputs = ['../inject.c']
SOURCES += [
"!%s.c" % cpu,
'!%s' % gen_src,
]
NO_PGO = True

Просмотреть файл

@ -31,9 +31,6 @@ endif
CWD := $(CURDIR)
ifneq (1,$(words $(CWD)))
$(error The mozilla directory cannot be located in a path with spaces.)
endif
ifeq "$(CWD)" "/"
CWD := /.
@ -59,14 +56,6 @@ CONFIG_GUESS := $(shell $(TOPSRCDIR)/build/autoconf/config.guess)
# Windows checks.
ifneq (,$(findstring mingw,$(CONFIG_GUESS)))
# check for CRLF line endings
ifneq (0,$(shell $(PERL) -e 'binmode(STDIN); while (<STDIN>) { if (/\r/) { print "1"; exit } } print "0"' < $(TOPSRCDIR)/client.mk))
$(error This source tree appears to have Windows-style line endings. To \
convert it to Unix-style line endings, check \
"https://developer.mozilla.org/en-US/docs/Developer_Guide/Mozilla_build_FAQ\#Win32-specific_questions" \
for a workaround of this issue.)
endif
# Set this for baseconfig.mk
HOST_OS_ARCH=WINNT
endif
@ -86,8 +75,7 @@ endef
# before evaluation. $(shell) replacing newlines with spaces, || is always
# followed by a space (since sed doesn't remove newlines), except on the
# last line, so replace both '|| ' and '||'.
# Also, make MOZ_PGO available to mozconfig when passed on make command line.
MOZCONFIG_CONTENT := $(subst ||,$(CR),$(subst || ,$(CR),$(shell MOZ_PGO=$(MOZ_PGO) $(TOPSRCDIR)/mach environment --format=client.mk | sed 's/$$/||/')))
MOZCONFIG_CONTENT := $(subst ||,$(CR),$(subst || ,$(CR),$(shell $(TOPSRCDIR)/mach environment --format=client.mk | sed 's/$$/||/')))
$(eval $(MOZCONFIG_CONTENT))
export FOUND_MOZCONFIG
@ -104,9 +92,6 @@ MOZCONFIG_OUT_FILTERED := $(filter-out $(START_COMMENT)%,$(MOZCONFIG_OUT_LINES))
ifdef AUTOCLOBBER
export AUTOCLOBBER=1
endif
ifdef MOZ_PGO
export MOZ_PGO
endif
ifdef MOZ_PARALLEL_BUILD
MOZ_MAKE_FLAGS := $(filter-out -j%,$(MOZ_MAKE_FLAGS))
@ -132,14 +117,13 @@ CONFIGURES := $(TOPSRCDIR)/configure
CONFIGURES += $(TOPSRCDIR)/js/src/configure
# Make targets that are going to be passed to the real build system
OBJDIR_TARGETS = install export libs clean realclean distclean maybe_clobber_profiledbuild upload sdk installer package package-compare stage-package source-package l10n-check automation/build
OBJDIR_TARGETS = install export libs clean realclean distclean upload sdk installer package package-compare stage-package source-package l10n-check automation/build
#######################################################################
# Rules
# The default rule is build
build::
$(MAKE) -f $(TOPSRCDIR)/client.mk $(if $(MOZ_PGO),profiledbuild,realbuild) CREATE_MOZCONFIG_JSON=
# Include baseconfig.mk for its $(MAKE) validation.
include $(TOPSRCDIR)/config/baseconfig.mk
@ -160,47 +144,20 @@ $(OBJDIR)/.mozconfig.mk: $(TOPSRCDIR)/client.mk $(FOUND_MOZCONFIG) $(call mkdir_
include $(OBJDIR)/.mozconfig.mk
# Print out any options loaded from mozconfig.
all realbuild clean distclean export libs install realclean::
all build clean distclean export libs install realclean::
ifneq (,$(strip $(MOZCONFIG_OUT_FILTERED)))
$(info Adding client.mk options from $(FOUND_MOZCONFIG):)
$(foreach line,$(MOZCONFIG_OUT_FILTERED),$(info $(NULL) $(NULL) $(NULL) $(NULL) $(subst ||, ,$(line))))
endif
# Windows equivalents
build_all: build
clobber clobber_all: clean
# helper target for mobile
build_and_deploy: build package install
####################################
# Profile-Guided Optimization
# This is up here so that this is usable in multi-pass builds, where you
# might not have a runnable application until all the build passes have run.
profiledbuild::
$(call BUILDSTATUS,TIERS pgo_profile_generate pgo_package pgo_profile pgo_clobber pgo_profile_use)
$(call BUILDSTATUS,TIER_START pgo_profile_generate)
$(MAKE) -f $(TOPSRCDIR)/client.mk realbuild MOZ_PROFILE_GENERATE=1 MOZ_PGO_INSTRUMENTED=1 CREATE_MOZCONFIG_JSON=
$(call BUILDSTATUS,TIER_FINISH pgo_profile_generate)
$(call BUILDSTATUS,TIER_START pgo_package)
$(MAKE) -C $(OBJDIR) package MOZ_PGO_INSTRUMENTED=1 MOZ_INTERNAL_SIGNING_FORMAT= MOZ_EXTERNAL_SIGNING_FORMAT=
rm -f $(OBJDIR)/jarlog/en-US.log
$(call BUILDSTATUS,TIER_FINISH pgo_package)
$(call BUILDSTATUS,TIER_START pgo_profile)
MOZ_PGO_INSTRUMENTED=1 JARLOG_FILE=jarlog/en-US.log EXTRA_TEST_ARGS=10 $(MAKE) -C $(OBJDIR) pgo-profile-run
$(call BUILDSTATUS,TIER_FINISH pgo_profile)
$(call BUILDSTATUS,TIER_START pgo_clobber)
$(MAKE) -f $(TOPSRCDIR)/client.mk maybe_clobber_profiledbuild CREATE_MOZCONFIG_JSON=
$(call BUILDSTATUS,TIER_FINISH pgo_clobber)
$(call BUILDSTATUS,TIER_START pgo_profile_use)
$(MAKE) -f $(TOPSRCDIR)/client.mk realbuild MOZ_PROFILE_USE=1 CREATE_MOZCONFIG_JSON=
$(call BUILDSTATUS,TIER_FINISH pgo_profile_use)
#####################################################
# Preflight, before building any project
ifdef MOZ_PREFLIGHT_ALL
realbuild preflight_all::
build preflight_all::
set -e; \
for mkfile in $(MOZ_PREFLIGHT_ALL); do \
$(MAKE) -f $(TOPSRCDIR)/$$mkfile preflight_all TOPSRCDIR=$(TOPSRCDIR) OBJDIR=$(OBJDIR) MOZ_OBJDIR=$(MOZ_OBJDIR); \
@ -313,7 +270,7 @@ endif
####################################
# Build it
realbuild:: $(OBJDIR)/Makefile $(OBJDIR)/config.status
build:: $(OBJDIR)/Makefile $(OBJDIR)/config.status
+$(MOZ_MAKE)
####################################
@ -327,12 +284,12 @@ $(OBJDIR_TARGETS):: $(OBJDIR)/Makefile $(OBJDIR)/config.status
# Postflight, after building all projects
ifdef MOZ_AUTOMATION
$(if $(MOZ_PGO),profiledbuild,realbuild)::
build::
$(MAKE) -f $(TOPSRCDIR)/client.mk automation/build
endif
ifdef MOZ_POSTFLIGHT_ALL
realbuild postflight_all::
build postflight_all::
set -e; \
for mkfile in $(MOZ_POSTFLIGHT_ALL); do \
$(MAKE) -f $(TOPSRCDIR)/$$mkfile postflight_all TOPSRCDIR=$(TOPSRCDIR) OBJDIR=$(OBJDIR) MOZ_OBJDIR=$(MOZ_OBJDIR); \
@ -348,12 +305,7 @@ echo-variable-%:
.NOTPARALLEL:
.PHONY: \
realbuild \
build \
profiledbuild \
build_all \
clobber \
clobber_all \
configure \
preflight_all \
postflight_all \

Просмотреть файл

@ -87,7 +87,6 @@ showbuild:
DSO_LDOPTS \
OS_INCLUDES \
OS_LIBS \
EXTRA_LIBS \
BIN_FLAGS \
INCLUDES \
DEFINES \

Просмотреть файл

@ -240,7 +240,7 @@ ALL_TRASH = \
$(filter-out $(ASFILES),$(OBJS:.$(OBJ_SUFFIX)=.s)) $(OBJS:.$(OBJ_SUFFIX)=.ii) \
$(OBJS:.$(OBJ_SUFFIX)=.i) $(OBJS:.$(OBJ_SUFFIX)=.i_o) \
$(HOST_PROGOBJS) $(HOST_OBJS) $(IMPORT_LIBRARY) \
$(EXE_DEF_FILE) so_locations _gen _stubs $(wildcard *.res) $(wildcard *.RES) \
so_locations _gen _stubs $(wildcard *.res) $(wildcard *.RES) \
$(wildcard *.pdb) $(CODFILE) $(IMPORT_LIBRARY) \
$(SHARED_LIBRARY:$(DLL_SUFFIX)=.exp) $(wildcard *.ilk) \
$(PROGRAM:$(BIN_SUFFIX)=.exp) $(SIMPLE_PROGRAMS:$(BIN_SUFFIX)=.exp) \
@ -339,16 +339,6 @@ endif
endif
endif
#
# Linux: add -Bsymbolic flag for components
#
ifeq ($(OS_ARCH),Linux)
ifdef LD_VERSION_SCRIPT
EXTRA_DSO_LDOPTS += -Wl,--version-script,$(LD_VERSION_SCRIPT)
EXTRA_DEPS += $(LD_VERSION_SCRIPT)
endif
endif
ifdef SYMBOLS_FILE
ifeq ($(OS_TARGET),WINNT)
ifndef GNU_CC
@ -560,11 +550,11 @@ endef
# PROGRAM = Foo
# creates OBJS, links with LIBS to create Foo
#
$(PROGRAM): $(PROGOBJS) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(EXE_DEF_FILE) $(RESFILE) $(GLOBAL_DEPS)
$(PROGRAM): $(PROGOBJS) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(RESFILE) $(GLOBAL_DEPS)
$(REPORT_BUILD)
@$(RM) $@.manifest
ifeq (_WINNT,$(GNU_CC)_$(OS_ARCH))
$(EXPAND_LINK) -NOLOGO -OUT:$@ -PDB:$(LINK_PDBFILE) $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(MOZ_PROGRAM_LDFLAGS) $(PROGOBJS) $(RESFILE) $(STATIC_LIBS) $(SHARED_LIBS) $(EXTRA_LIBS) $(OS_LIBS)
$(EXPAND_LINK) -NOLOGO -OUT:$@ -PDB:$(LINK_PDBFILE) $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(MOZ_PROGRAM_LDFLAGS) $(PROGOBJS) $(RESFILE) $(STATIC_LIBS) $(SHARED_LIBS) $(OS_LIBS)
ifdef MSMANIFEST_TOOL
@if test -f $@.manifest; then \
if test -f '$(srcdir)/$@.manifest'; then \
@ -585,7 +575,7 @@ ifdef MOZ_PROFILE_GENERATE
touch -t `date +%Y%m%d%H%M.%S -d 'now+5seconds'` pgo.relink
endif
else # !WINNT || GNU_CC
$(call EXPAND_CC_OR_CXX,$@) -o $@ $(COMPUTED_CXX_LDFLAGS) $(PGO_CFLAGS) $(PROGOBJS) $(RESFILE) $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(WRAP_LDFLAGS) $(STATIC_LIBS) $(MOZ_PROGRAM_LDFLAGS) $(SHARED_LIBS) $(EXTRA_LIBS) $(OS_LIBS) $(BIN_FLAGS) $(EXE_DEF_FILE)
$(call EXPAND_CC_OR_CXX,$@) -o $@ $(COMPUTED_CXX_LDFLAGS) $(PGO_CFLAGS) $(PROGOBJS) $(RESFILE) $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(STATIC_LIBS) $(MOZ_PROGRAM_LDFLAGS) $(SHARED_LIBS) $(OS_LIBS)
$(call CHECK_BINARY,$@)
endif # WINNT && !GNU_CC
@ -636,7 +626,7 @@ endif
$(SIMPLE_PROGRAMS): %$(BIN_SUFFIX): %.$(OBJ_SUFFIX) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(GLOBAL_DEPS)
$(REPORT_BUILD)
ifeq (_WINNT,$(GNU_CC)_$(OS_ARCH))
$(EXPAND_LINK) -nologo -out:$@ -pdb:$(LINK_PDBFILE) $< $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(MOZ_PROGRAM_LDFLAGS) $(STATIC_LIBS) $(SHARED_LIBS) $(EXTRA_LIBS) $(OS_LIBS)
$(EXPAND_LINK) -nologo -out:$@ -pdb:$(LINK_PDBFILE) $< $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(MOZ_PROGRAM_LDFLAGS) $(STATIC_LIBS) $(SHARED_LIBS) $(OS_LIBS)
ifdef MSMANIFEST_TOOL
@if test -f $@.manifest; then \
$(MT) -NOLOGO -MANIFEST $@.manifest -OUTPUTRESOURCE:$@\;1; \
@ -644,7 +634,7 @@ ifdef MSMANIFEST_TOOL
fi
endif # MSVC with manifest tool
else
$(call EXPAND_CC_OR_CXX,$@) $(COMPUTED_CXX_LDFLAGS) $(PGO_CFLAGS) -o $@ $< $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(WRAP_LDFLAGS) $(STATIC_LIBS) $(MOZ_PROGRAM_LDFLAGS) $(SHARED_LIBS) $(EXTRA_LIBS) $(OS_LIBS) $(BIN_FLAGS)
$(call EXPAND_CC_OR_CXX,$@) $(COMPUTED_CXX_LDFLAGS) $(PGO_CFLAGS) -o $@ $< $(WIN32_EXE_LDFLAGS) $(LDFLAGS) $(STATIC_LIBS) $(MOZ_PROGRAM_LDFLAGS) $(SHARED_LIBS) $(OS_LIBS)
$(call CHECK_BINARY,$@)
endif # WINNT && !GNU_CC
@ -670,17 +660,17 @@ ifndef CROSS_COMPILE
$(call CHECK_STDCXX,$@)
endif
$(filter %.$(LIB_SUFFIX),$(LIBRARY)): $(OBJS) $(STATIC_LIBS_DEPS) $(filter %.$(LIB_SUFFIX),$(EXTRA_LIBS)) $(EXTRA_DEPS) $(GLOBAL_DEPS)
$(filter %.$(LIB_SUFFIX),$(LIBRARY)): $(OBJS) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(GLOBAL_DEPS)
$(REPORT_BUILD)
# Always remove both library and library descriptor
$(RM) $(REAL_LIBRARY) $(REAL_LIBRARY).$(LIBS_DESC_SUFFIX)
$(EXPAND_AR) $(AR_FLAGS) $(OBJS) $(STATIC_LIBS) $(filter %.$(LIB_SUFFIX),$(EXTRA_LIBS))
$(EXPAND_AR) $(AR_FLAGS) $(OBJS) $(STATIC_LIBS)
$(filter-out %.$(LIB_SUFFIX),$(LIBRARY)): $(filter %.$(LIB_SUFFIX),$(LIBRARY)) $(OBJS) $(STATIC_LIBS_DEPS) $(filter %.$(LIB_SUFFIX),$(EXTRA_LIBS)) $(EXTRA_DEPS) $(GLOBAL_DEPS)
$(filter-out %.$(LIB_SUFFIX),$(LIBRARY)): $(filter %.$(LIB_SUFFIX),$(LIBRARY)) $(OBJS) $(STATIC_LIBS_DEPS) $(EXTRA_DEPS) $(GLOBAL_DEPS)
# When we only build a library descriptor, blow out any existing library
$(REPORT_BUILD)
$(if $(filter %.$(LIB_SUFFIX),$(LIBRARY)),,$(RM) $(REAL_LIBRARY))
$(EXPAND_LIBS_GEN) -o $@ $(OBJS) $(STATIC_LIBS) $(filter %.$(LIB_SUFFIX),$(EXTRA_LIBS))
$(EXPAND_LIBS_GEN) -o $@ $(OBJS) $(STATIC_LIBS)
ifeq ($(OS_ARCH),WINNT)
# Import libraries are created by the rules creating shared libraries.
@ -708,7 +698,7 @@ $(SHARED_LIBRARY): $(OBJS) $(RESFILE) $(RUST_STATIC_LIB_FOR_SHARED_LIB) $(STATIC
ifndef INCREMENTAL_LINKER
$(RM) $@
endif
$(EXPAND_MKSHLIB) $(SHLIB_LDSTARTFILE) $(OBJS) $(SUB_SHLOBJS) $(RESFILE) $(LDFLAGS) $(WRAP_LDFLAGS) $(STATIC_LIBS) $(RUST_STATIC_LIB_FOR_SHARED_LIB) $(SHARED_LIBS) $(EXTRA_DSO_LDOPTS) $(MOZ_GLUE_LDFLAGS) $(EXTRA_LIBS) $(OS_LIBS) $(SHLIB_LDENDFILE)
$(EXPAND_MKSHLIB) $(OBJS) $(RESFILE) $(LDFLAGS) $(STATIC_LIBS) $(RUST_STATIC_LIB_FOR_SHARED_LIB) $(SHARED_LIBS) $(EXTRA_DSO_LDOPTS) $(MOZ_GLUE_LDFLAGS) $(OS_LIBS)
$(call CHECK_BINARY,$@)
ifeq (_WINNT,$(GNU_CC)_$(OS_ARCH))

Просмотреть файл

@ -10,7 +10,7 @@
const ADDON_PATH = "addon-webext-contentscript.xpi";
const TAB_URL = EXAMPLE_URL + "doc_script_webext_contentscript.html";
let {getExtensionUUID} = Cu.import("resource://gre/modules/Extension.jsm", {});
const {WebExtensionPolicy} = Cu.getGlobalForObject(Cu.import("resource://gre/modules/Extension.jsm", {}));
function test() {
let gPanel, gDebugger;
@ -32,10 +32,10 @@ function test() {
return Task.spawn(function* () {
gAddon = yield addTemporaryAddon(ADDON_PATH);
let uuid = getExtensionUUID(gAddon.id);
let {mozExtensionHostname} = WebExtensionPolicy.getByID(gAddon.id);
let options = {
source: `moz-extension://${uuid}/webext-content-script.js`,
source: `moz-extension://${mozExtensionHostname}/webext-content-script.js`,
line: 1
};
[,, gPanel] = yield initDebugger(TAB_URL, options);

Просмотреть файл

@ -1,3 +1,7 @@
/* 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/. */
:root {
/* Photon color variables used on the aboutdevtools page */
--blue-60: #0060df;
@ -8,7 +12,14 @@
--grey-90-alpha-10: rgba(12, 12, 13, 0.1);
--grey-90-alpha-20: rgba(12, 12, 13, 0.2);
--grey-90-alpha-30: rgba(12, 12, 13, 0.3);
--grey-90-alpha-40: rgba(12, 12, 13, 0.4);
--grey-90-alpha-50: rgba(12, 12, 13, 0.5);
--teal-60: #00c8d7;
--red-50: #ff0039;
--white: #ffffff;
/* Shared variables */
--line-height: 1.5em;
}
html, body {
@ -17,16 +28,15 @@ html, body {
}
p {
line-height: 1.5em;
line-height: var(--line-height);
}
.box {
width: 100%;
max-width: 850px;
display: flex;
align-items: center;
height: 400px;
flex-shrink: 0;
padding: 34px 0 50px 0;
}
.wrapper {
@ -38,19 +48,16 @@ p {
}
.left-pane {
width: 360px;
height: 100%;
width: 300px;
height: 300px;
margin-inline-end: 20px;
background-image: url(images/otter.svg);
background-size: 80%;
background-size: 100%;
background-position: 50%;
background-repeat: no-repeat;
flex-shrink: 0;
}
.right-pane {
height: 250px;
}
.features {
max-width: 980px;
border-top: 1px solid var(--grey-30);
@ -129,7 +136,7 @@ p {
}
button {
margin: 2em 0 0 0;
margin: 20px 0 0 0;
padding: 10px 20px;
border: none;

Просмотреть файл

@ -14,7 +14,9 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>a
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css" type="text/css"/>
<link rel="stylesheet" href="chrome://devtools-shim/content/aboutdevtools/aboutdevtools.css" type="text/css"/>
<link rel="stylesheet" href="chrome://devtools-shim/content/aboutdevtools/subscribe.css" type="text/css"/>
<script type="application/javascript" src="chrome://devtools-shim/content/aboutdevtools/aboutdevtools.js"></script>
<script type="application/javascript" src="chrome://devtools-shim/content/aboutdevtools/subscribe.js"></script>
</head>
<body>
<div id="install-page" class="wrapper" hidden="true">
@ -48,6 +50,33 @@
<div class="right-pane">
<h1 class="title" >&aboutDevtools.welcome.title;</h1>
<p id="welcome-message">&aboutDevtools.welcome.message;</p>
<!-- Form dedicated to the newsletter subscription -->
<div class="newsletter">
<h2 class="newsletter-title">&aboutDevtools.newsletter.title;</h2>
<p>&aboutDevtools.newsletter.message;</p>
<form id="newsletter-form" name="newsletter-form" action="https://www.mozilla.org/en-US/newsletter/" method="post">
<!-- "H" stands for the HTML format (->fmt). Alternative is T for text. -->
<input type="hidden" id="fmt" name="fmt" value="H" />
<!-- "app-dev" is the id of the Mozilla Developper newsletter -->
<input type="hidden" id="newsletters" name="newsletters" value="app-dev" />
<div id="newsletter-errors"></div>
<section id="newsletter-email" class="newsletter-form-section">
<input type="email" id="email" name="email" required="true" placeholder="&aboutDevtools.newsletter.email.placeholder;" />
</section>
<section id="newsletter-privacy" class="newsletter-form-section">
<input type="checkbox" id="privacy" name="privacy" required="true" />
<label for="privacy">&aboutDevtools.newsletter.privacy.label;</label>
</section>
<button type="submit" id="newsletter-submit" class="primary-button">&aboutDevtools.newsletter.subscribe.label;</button>
</form>
<div id="newsletter-thanks">
<h2>&aboutDevtools.newsletter.thanks.title;</h2>
<p>&aboutDevtools.newsletter.thanks.message;</p>
</div>
</div>
</div>
</div>

Просмотреть файл

@ -0,0 +1,100 @@
/* 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/. */
/**
* This file contains the styles for the newsletter subscription form on about:devtools.
* It is largely inspired from https://mozilla.github.io/basket-example/
*/
#newsletter-errors {
/* Hidden by default */
display: none;
margin-bottom: 20px;
padding: 10px;
border-radius: 2px;
background-color: var(--red-50);
color: var(--white);
}
#newsletter-errors.show {
display: block;
}
#newsletter-errors .error {
margin: 0;
margin-bottom: 10px;
}
#newsletter-errors .error:last-child {
margin-bottom: 0;
}
#newsletter-thanks {
/* Hidden by default */
display: none;
}
#newsletter-thanks.show {
display: block;
}
.newsletter-form-section {
display: block;
margin-bottom: 20px;
width: 320px;
}
#newsletter-privacy {
display: flex;
/* The privacy section is hidden by default and only displayed on focus */
height: 0;
margin-bottom: -20px;
overflow: hidden;
}
#newsletter-privacy.animate {
transition: all 0.25s cubic-bezier(.15,.75,.35,.9);
}
#newsletter-privacy label {
line-height: var(--line-height);
}
#privacy {
width: 20px;
height: 20px;
margin: 2px;
margin-inline-end: 10px;
flex-shrink: 0;
}
#email {
width: 100%;
box-sizing: border-box;
padding: 12px 15px;
}
#newsletter-form input {
border-color: var(--grey-90-alpha-30);
}
#newsletter-form input:hover {
border-color: var(--grey-90-alpha-50);
}
#newsletter-form input:focus {
border-color: var(--teal-60);
box-shadow: 0 0 2px 0 var(--teal-60);
}
#newsletter-form::placeholder {
color: var(--grey-90-alpha-40);
}
#newsletter-submit {
display: block;
}

Просмотреть файл

@ -0,0 +1,147 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/**
* This file handles the newsletter subscription form on about:devtools.
* It is largely inspired from https://mozilla.github.io/basket-example/
*/
window.addEventListener("load", function () {
const { utils: Cu } = Components;
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
// Timeout for the subscribe XHR.
const REQUEST_TIMEOUT = 5000;
const ABOUTDEVTOOLS_STRINGS = "chrome://devtools-shim/locale/aboutdevtools.properties";
const aboutDevtoolsBundle = Services.strings.createBundle(ABOUTDEVTOOLS_STRINGS);
let emailInput = document.getElementById("email");
let newsletterErrors = document.getElementById("newsletter-errors");
let newsletterForm = document.getElementById("newsletter-form");
let newsletterPrivacySection = document.getElementById("newsletter-privacy");
let newsletterThanks = document.getElementById("newsletter-thanks");
/**
* Update the error panel to display the provided errors. If the argument is null or
* empty, a default error message will be displayed.
*
* @param {Array} errors
* Array of strings, each item being an error message to display.
*/
function updateErrorPanel(errors) {
clearErrorPanel();
if (!errors || errors.length == 0) {
errors = [aboutDevtoolsBundle.GetStringFromName("newsletter.error.unknown")];
}
// Create errors markup.
let fragment = document.createDocumentFragment();
for (let error of errors) {
let item = document.createElement("p");
item.classList.add("error");
item.appendChild(document.createTextNode(error));
fragment.appendChild(item);
}
newsletterErrors.appendChild(fragment);
newsletterErrors.classList.add("show");
}
/**
* Hide the error panel and remove all errors.
*/
function clearErrorPanel() {
newsletterErrors.classList.remove("show");
newsletterErrors.innerHTML = "";
}
// Show the additional form fields on focus of the email input.
function onEmailInputFocus() {
// Create a hidden measuring container, append it to the parent of the privacy section
let container = document.createElement("div");
container.style.cssText = "visibility: hidden; overflow: hidden; position: absolute";
newsletterPrivacySection.parentNode.appendChild(container);
// Clone the privacy section, append the clone to the measuring container.
let clone = newsletterPrivacySection.cloneNode(true);
container.appendChild(clone);
// Measure the target height of the privacy section.
clone.style.height = "auto";
let height = clone.offsetHeight;
// Cleanup the measuring container.
container.remove();
// Set the animate class and set the height to the measured height.
newsletterPrivacySection.classList.add("animate");
newsletterPrivacySection.style.cssText = `height: ${height}px; margin-bottom: 0;`;
}
// XHR subscribe; handle errors; display thanks message on success.
function onFormSubmit(evt) {
evt.preventDefault();
evt.stopPropagation();
// New submission, clear old errors
clearErrorPanel();
let xhr = new XMLHttpRequest();
xhr.onload = function (r) {
if (r.target.status >= 200 && r.target.status < 300) {
let {response} = r.target;
if (response.success === true) {
// Hide form and show success message.
newsletterForm.style.display = "none";
newsletterThanks.classList.add("show");
} else {
// We trust the error messages from the service to be meaningful for the user.
updateErrorPanel(response.errors);
}
} else {
let {status, statusText} = r.target;
let statusInfo = `${status} - ${statusText}`;
let error = aboutDevtoolsBundle
.formatStringFromName("newsletter.error.common", [statusInfo], 1);
updateErrorPanel([error]);
}
};
xhr.onerror = () => {
updateErrorPanel();
};
xhr.ontimeout = () => {
let error = aboutDevtoolsBundle.GetStringFromName("newsletter.error.timeout");
updateErrorPanel([error]);
};
let url = newsletterForm.getAttribute("action");
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.timeout = REQUEST_TIMEOUT;
xhr.responseType = "json";
// Create form data.
let formData = new FormData(newsletterForm);
formData.append("source_url", document.location.href);
let params = new URLSearchParams(formData);
// Send the request.
xhr.send(params.toString());
}
// Attach event listeners.
newsletterForm.addEventListener("submit", onFormSubmit);
emailInput.addEventListener("focus", onEmailInputFocus);
}, { once: true });

Просмотреть файл

@ -22,4 +22,14 @@
<!ENTITY aboutDevtools.enable.installButton "Enable Developer Tools">
<!ENTITY aboutDevtools.enable.closeButton "Close this page">
<!ENTITY aboutDevtools.welcome.title "Welcome to Firefox Developer Tools!">
<!ENTITY aboutDevtools.welcome.message "Youve successfully enabled DevTools! To get started, explore the Web Developer menu or open the tools with ##INSPECTOR_SHORTCUT##.">
<!ENTITY aboutDevtools.welcome.message "Youve successfully enabled DevTools! To get started, explore the Web Developer menu or open the tools with ##INSPECTOR_SHORTCUT##.">
<!ENTITY aboutDevtools.newsletter.title "Mozilla Developer Newsletter">
<!ENTITY aboutDevtools.newsletter.message "Get developer news, tricks and resources sent straight to your inbox.">
<!ENTITY aboutDevtools.newsletter.email.placeholder "Email">
<!ENTITY aboutDevtools.newsletter.privacy.label "Im okay with Mozilla handling my info as explained in this <a class=&#x0022;external&#x0022; href=&#x0022;https://www.mozilla.org/privacy/&#x0022;>Privacy Policy</a>.">
<!ENTITY aboutDevtools.newsletter.subscribe.label "Subscribe">
<!ENTITY aboutDevtools.newsletter.thanks.title "Thanks!">
<!ENTITY aboutDevtools.newsletter.thanks.message "If you havent previously confirmed a subscription to a Mozilla-related newsletter you may have to do so. Please check your inbox or your spam filter for an email from us.">

Просмотреть файл

@ -7,6 +7,8 @@ devtools-shim.jar:
content/aboutdevtools/aboutdevtools.xhtml (aboutdevtools/aboutdevtools.xhtml)
content/aboutdevtools/aboutdevtools.css (aboutdevtools/aboutdevtools.css)
content/aboutdevtools/aboutdevtools.js (aboutdevtools/aboutdevtools.js)
content/aboutdevtools/subscribe.css (aboutdevtools/subscribe.css)
content/aboutdevtools/subscribe.js (aboutdevtools/subscribe.js)
content/aboutdevtools/images/otter.svg (aboutdevtools/images/otter.svg)

Просмотреть файл

@ -35,3 +35,16 @@ features.performance.desc=Unblock bottlenecks, streamline processes, optimize as
features.memory.title=Memory
features.memory.desc=Find memory leaks and make your application zippy.
# LOCALIZATION NOTE (newsletter.error.common): error text displayed when the newsletter
# subscription failed. The argument will be replaced with request's status code and status
# text (e.g. "404 - Not Found")
newsletter.error.common=Subscription request failed (%S).
# LOCALIZATION NOTE (newsletter.error.unknown): error text displayed when the newsletter
# subscription failed for an unexpected reason.
newsletter.error.unknown=An unexpected error occurred.
# LOCALIZATION NOTE (newsletter.error.timeout): error text displayed when the newsletter
# subscription timed out.
newsletter.error.timeout=Subscription request timed out.

Просмотреть файл

@ -34,7 +34,7 @@ function AnimationEventHandler(target) {
this.animationcancel = evt.elapsedTime;
};
}
AnimationEventHandler.prototype.clear = function() {
AnimationEventHandler.prototype.clear = () => {
this.animationstart = undefined;
this.animationiteration = undefined;
this.animationend = undefined;
@ -42,208 +42,205 @@ AnimationEventHandler.prototype.clear = function() {
}
function setupAnimation(t, animationStyle) {
var div = addDiv(t, { style: "animation: " + animationStyle });
var watcher = new EventWatcher(t, div, [ 'animationstart',
const div = addDiv(t, { style: 'animation: ' + animationStyle });
// Note that this AnimationEventHandler should be created before EventWatcher
// to capture all events in the handler prior to the EventWatcher since
// testharness.js proceeds when the EventWatcher received watching events.
const handler = new AnimationEventHandler(div);
const watcher = new EventWatcher(t, div, [ 'animationstart',
'animationiteration',
'animationend',
'animationcancel' ]);
var animation = div.getAnimations()[0];
const animation = div.getAnimations()[0];
return [animation, watcher, div];
return { animation, watcher, div, handler };
}
promise_test(function(t) {
promise_test(t => {
// Add 1ms delay to ensure that the delay is not included in the elapsedTime.
const [animation, watcher] = setupAnimation(t, 'anim 100s 1ms');
const { animation, watcher } = setupAnimation(t, 'anim 100s 1ms');
return watcher.wait_for('animationstart').then(function(evt) {
return watcher.wait_for('animationstart').then(evt => {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Idle -> Active');
promise_test(function(t) {
const [animation, watcher, div] = setupAnimation(t, 'anim 100s');
const handler = new AnimationEventHandler(div);
promise_test(t => {
const { animation, watcher, div, handler } = setupAnimation(t, 'anim 100s');
// Seek to After phase.
animation.finish();
return watcher.wait_for([ 'animationstart',
'animationend' ]).then(function() {
'animationend' ]).then(() => {
assert_equals(handler.animationstart, 0.0);
assert_equals(handler.animationend, 100);
});
}, 'Idle -> After');
promise_test(function(t) {
const [animation, watcher] =
promise_test(t => {
const { animation, watcher } =
setupAnimation(t, 'anim 100s 100s paused');
return animation.ready.then(function() {
return animation.ready.then(() => {
// Seek to Active phase.
animation.currentTime = 100 * MS_PER_SEC;
return watcher.wait_for('animationstart');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Before -> Active');
promise_test(function(t) {
const [animation, watcher, div] =
promise_test(t => {
const { animation, watcher, div, handler } =
setupAnimation(t, 'anim 100s 100s paused');
const handler = new AnimationEventHandler(div);
return animation.ready.then(function() {
return animation.ready.then(() => {
// Seek to After phase.
animation.finish();
return watcher.wait_for([ 'animationstart', 'animationend' ]);
}).then(function(evt) {
}).then(evt => {
assert_equals(handler.animationstart, 0.0);
assert_equals(handler.animationend, 100.0);
});
}, 'Before -> After');
promise_test(function(t) {
const [animation, watcher, div] = setupAnimation(t, 'anim 100s paused');
promise_test(t => {
const { animation, watcher, div } = setupAnimation(t, 'anim 100s paused');
return watcher.wait_for('animationstart').then(function(evt) {
return watcher.wait_for('animationstart').then(evt => {
// Make idle
div.style.display = 'none';
return watcher.wait_for('animationcancel');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Active -> Idle, display: none');
promise_test(function(t) {
const [animation, watcher, div] = setupAnimation(t, 'anim 100s');
promise_test(t => {
const { animation, watcher, div } = setupAnimation(t, 'anim 100s');
return watcher.wait_for('animationstart').then(function(evt) {
return watcher.wait_for('animationstart').then(evt => {
animation.currentTime = 100.0;
// Make idle
animation.timeline = null;
return watcher.wait_for('animationcancel');
}).then(function(evt) {
}).then(evt => {
assert_times_equal(evt.elapsedTime, 0.1);
});
}, 'Active -> Idle, setting Animation.timeline = null');
promise_test(function(t) {
promise_test(t => {
// we should NOT pause animation since calling cancel synchronously.
const [animation, watcher, div] = setupAnimation(t, 'anim 100s');
const { animation, watcher, div } = setupAnimation(t, 'anim 100s');
return watcher.wait_for('animationstart').then(function(evt) {
return watcher.wait_for('animationstart').then(evt => {
animation.currentTime = 50.0;
animation.cancel();
return watcher.wait_for('animationcancel');
}).then(function(evt) {
}).then(evt => {
assert_times_equal(evt.elapsedTime, 0.05);
});
}, 'Active -> Idle, calling Animation.cancel()');
promise_test(function(t) {
const [animation, watcher] =
promise_test(t => {
const { animation, watcher } =
setupAnimation(t, 'anim 100s 100s paused');
// Seek to Active phase.
animation.currentTime = 100 * MS_PER_SEC;
return watcher.wait_for('animationstart').then(function() {
return watcher.wait_for('animationstart').then(() => {
// Seek to Before phase.
animation.currentTime = 0;
return watcher.wait_for('animationend');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Active -> Before');
promise_test(function(t) {
const [animation, watcher] = setupAnimation(t, 'anim 100s paused');
promise_test(t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s paused');
return watcher.wait_for('animationstart').then(function(evt) {
return watcher.wait_for('animationstart').then(evt => {
// Seek to After phase.
animation.finish();
return watcher.wait_for('animationend');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 100.0);
});
}, 'Active -> After');
promise_test(function(t) {
const [animation, watcher, div] =
promise_test(t => {
const { animation, watcher, div, handler } =
setupAnimation(t, 'anim 100s 100s paused');
const handler = new AnimationEventHandler(div);
// Seek to After phase.
animation.finish();
return watcher.wait_for([ 'animationstart',
'animationend' ]).then(function() {
'animationend' ]).then(() => {
// Seek to Before phase.
animation.currentTime = 0;
handler.clear();
return watcher.wait_for([ 'animationstart', 'animationend' ]);
}).then(function() {
}).then(() => {
assert_equals(handler.animationstart, 100.0);
assert_equals(handler.animationend, 0.0);
});
}, 'After -> Before');
promise_test(function(t) {
const [animation, watcher, div] =
promise_test(t => {
const { animation, watcher, div } =
setupAnimation(t, 'anim 100s 100s paused');
const handler = new AnimationEventHandler(div);
// Seek to After phase.
animation.finish();
return watcher.wait_for([ 'animationstart',
'animationend' ]).then(function() {
'animationend' ]).then(() => {
// Seek to Active phase.
animation.currentTime = 100 * MS_PER_SEC;
handler.clear();
return watcher.wait_for('animationstart');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 100.0);
});
}, 'After -> Active');
promise_test(function(t) {
const [animation, watcher, div]
promise_test(t => {
const { animation, watcher, div }
= setupAnimation(t, 'anim 100s 100s 3 paused');
const handler = new AnimationEventHandler(div);
return animation.ready.then(function() {
return animation.ready.then(() => {
// Seek to iteration 0 (no animationiteration event should be dispatched)
animation.currentTime = 100 * MS_PER_SEC;
return watcher.wait_for('animationstart');
}).then(function(evt) {
}).then(evt => {
// Seek to iteration 2
animation.currentTime = 300 * MS_PER_SEC;
handler.clear();
return watcher.wait_for('animationiteration');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 200);
// Seek to After phase (no animationiteration event should be dispatched)
animation.currentTime = 400 * MS_PER_SEC;
return watcher.wait_for('animationend');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 300);
});
}, 'Active -> Active (forwards)');
promise_test(function(t) {
const [animation, watcher] = setupAnimation(t, 'anim 100s 100s 3');
promise_test(t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s 100s 3');
// Seek to After phase.
animation.finish();
return watcher.wait_for([ 'animationstart',
'animationend' ]).then(function() {
'animationend' ]).then(() => {
// Seek to iteration 2 (no animationiteration event should be dispatched)
animation.pause();
animation.currentTime = 300 * MS_PER_SEC;
return watcher.wait_for('animationstart');
}).then(function() {
}).then(() => {
// Seek to mid of iteration 0 phase.
animation.currentTime = 200 * MS_PER_SEC;
return watcher.wait_for('animationiteration');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 200.0);
// Seek to before phase (no animationiteration event should be dispatched)
animation.currentTime = 0;
@ -251,66 +248,66 @@ promise_test(function(t) {
});
}, 'Active -> Active (backwards)');
promise_test(function(t) {
const [animation, watcher, div] =
promise_test(t => {
const { animation, watcher, div } =
setupAnimation(t, 'anim 100s paused');
return watcher.wait_for('animationstart').then(function(evt) {
return watcher.wait_for('animationstart').then(evt => {
// Seek to Idle phase.
div.style.display = 'none';
flushComputedStyle(div);
return watcher.wait_for('animationcancel');
}).then(function() {
}).then(() => {
// Restart this animation.
div.style.display = '';
return watcher.wait_for('animationstart');
});
}, 'Active -> Idle -> Active: animationstart is fired by restarting animation');
promise_test(function(t) {
const [animation, watcher] =
promise_test(t => {
const { animation, watcher } =
setupAnimation(t, 'anim 100s 100s 2 paused');
// Make After.
animation.finish();
return watcher.wait_for([ 'animationstart',
'animationend' ]).then(function(evt) {
'animationend' ]).then(evt => {
animation.playbackRate = -1;
return watcher.wait_for('animationstart');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 200);
// Seek to 1st iteration
animation.currentTime = 200 * MS_PER_SEC - 1;
return watcher.wait_for('animationiteration');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 100);
// Seek to before
animation.currentTime = 100 * MS_PER_SEC - 1;
return watcher.wait_for('animationend');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 0);
assert_equals(animation.playState, 'running'); // delay
});
}, 'Negative playbackRate sanity test(Before -> Active -> Before)');
promise_test(function(t) {
const [animation, watcher] = setupAnimation(t, 'anim 100s');
promise_test(t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s');
return watcher.wait_for('animationstart').then(function(evt) {
return watcher.wait_for('animationstart').then(evt => {
// Make idle
animation.cancel();
return watcher.wait_for('animationcancel');
}).then(function(evt) {
}).then(evt => {
animation.cancel();
// Then wait a couple of frames and check that no event was dispatched.
return waitForAnimationFrames(2);
});
}, 'Call Animation.cancel after cancelling animation.');
promise_test(function(t) {
const [animation, watcher] = setupAnimation(t, 'anim 100s');
promise_test(t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s');
return watcher.wait_for('animationstart').then(function(evt) {
return watcher.wait_for('animationstart').then(evt => {
// Make idle
animation.cancel();
animation.play();
@ -319,56 +316,56 @@ promise_test(function(t) {
});
}, 'Restart animation after cancelling animation immediately.');
promise_test(function(t) {
const [animation, watcher] = setupAnimation(t, 'anim 100s');
promise_test(t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s');
return watcher.wait_for('animationstart').then(function(evt) {
return watcher.wait_for('animationstart').then(evt => {
// Make idle
animation.cancel();
animation.play();
animation.cancel();
return watcher.wait_for('animationcancel');
}).then(function(evt) {
}).then(evt => {
// Then wait a couple of frames and check that no event was dispatched.
return waitForAnimationFrames(2);
});
}, 'Call Animation.cancel after restarting animation immediately.');
promise_test(function(t) {
const [animation, watcher] = setupAnimation(t, 'anim 100s');
promise_test(t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s');
return watcher.wait_for('animationstart').then(function(evt) {
return watcher.wait_for('animationstart').then(evt => {
// Make idle
animation.timeline = null;
return watcher.wait_for('animationcancel');
}).then(function(evt) {
}).then(evt => {
animation.timeline = document.timeline;
animation.play();
return watcher.wait_for('animationstart');
});
}, 'Set timeline and play transition after clearing the timeline.');
promise_test(function(t) {
const [animation, watcher] = setupAnimation(t, 'anim 100s');
promise_test(t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s');
return watcher.wait_for('animationstart').then(function(evt) {
return watcher.wait_for('animationstart').then(evt => {
// Make idle
animation.cancel();
return watcher.wait_for('animationcancel');
}).then(function(evt) {
}).then(evt => {
animation.effect = null;
// Then wait a couple of frames and check that no event was dispatched.
return waitForAnimationFrames(2);
});
}, 'Set null target effect after cancelling the animation.');
promise_test(function(t) {
const [animation, watcher] = setupAnimation(t, 'anim 100s');
promise_test(t => {
const { animation, watcher } = setupAnimation(t, 'anim 100s');
return watcher.wait_for('animationstart').then(function(evt) {
return watcher.wait_for('animationstart').then(evt => {
animation.effect = null;
return watcher.wait_for('animationend');
}).then(function(evt) {
}).then(evt => {
animation.cancel();
// Then wait a couple of frames and check that no event was dispatched.
return waitForAnimationFrames(2);

Просмотреть файл

@ -29,7 +29,7 @@ function TransitionEventHandler(target) {
};
}
TransitionEventHandler.prototype.clear = function() {
TransitionEventHandler.prototype.clear = () => {
this.transitionrun = undefined;
this.transitionstart = undefined;
this.transitionend = undefined;
@ -37,162 +37,163 @@ TransitionEventHandler.prototype.clear = function() {
};
function setupTransition(t, transitionStyle) {
var div = addDiv(t, { style: 'transition: ' + transitionStyle });
var watcher = new EventWatcher(t, div, [ 'transitionrun',
const div = addDiv(t, { style: 'transition: ' + transitionStyle });
// Note that this TransitionEventHandler should be created before EventWatcher
// to capture all events in the handler prior to the EventWatcher since
// testharness.js proceeds when the EventWatcher received watching events.
const handler = new TransitionEventHandler(div);
const watcher = new EventWatcher(t, div, [ 'transitionrun',
'transitionstart',
'transitionend',
'transitioncancel' ]);
flushComputedStyle(div);
div.style.marginLeft = '100px';
var transition = div.getAnimations()[0];
const transition = div.getAnimations()[0];
return [transition, watcher, div];
return { transition, watcher, div, handler };
}
// On the next frame (i.e. when events are queued), whether or not the
// transition is still pending depends on the implementation.
promise_test(function(t) {
var [transition, watcher] =
promise_test(t => {
const { transition, watcher } =
setupTransition(t, 'margin-left 100s 100s');
return watcher.wait_for('transitionrun').then(function(evt) {
return watcher.wait_for('transitionrun').then(evt => {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Idle -> Pending or Before');
promise_test(function(t) {
var [transition, watcher] =
promise_test(t => {
const { transition, watcher } =
setupTransition(t, 'margin-left 100s 100s');
// Force the transition to leave the idle phase
transition.startTime = document.timeline.currentTime;
return watcher.wait_for('transitionrun').then(function(evt) {
return watcher.wait_for('transitionrun').then(evt => {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Idle -> Before');
promise_test(function(t) {
var [transition, watcher, div] =
promise_test(t => {
const { transition, watcher, div, handler } =
setupTransition(t, 'margin-left 100s 100s');
var handler = new TransitionEventHandler(div);
// Seek to Active phase.
transition.currentTime = 100 * MS_PER_SEC;
transition.pause();
return watcher.wait_for([ 'transitionrun',
'transitionstart' ]).then(function(evt) {
'transitionstart' ]).then(evt => {
assert_equals(handler.transitionrun, 0.0);
assert_equals(handler.transitionstart, 0.0);
});
}, 'Idle or Pending -> Active');
promise_test(function(t) {
var [transition, watcher, div] =
promise_test(t => {
const { transition, watcher, div, handler } =
setupTransition(t, 'margin-left 100s 100s');
var handler = new TransitionEventHandler(div);
// Seek to After phase.
transition.finish();
return watcher.wait_for([ 'transitionrun',
'transitionstart',
'transitionend' ]).then(function(evt) {
'transitionend' ]).then(evt => {
assert_equals(handler.transitionrun, 0.0);
assert_equals(handler.transitionstart, 0.0);
assert_equals(handler.transitionend, 100.0);
});
}, 'Idle or Pending -> After');
promise_test(function(t) {
var [transition, watcher, div] =
promise_test(t => {
const { transition, watcher, div } =
setupTransition(t, 'margin-left 100s 100s');
return Promise.all([ watcher.wait_for('transitionrun'),
transition.ready ]).then(function() {
transition.ready ]).then(() => {
// Make idle
div.style.display = 'none';
flushComputedStyle(div);
return watcher.wait_for('transitioncancel');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Before -> Idle (display: none)');
promise_test(function(t) {
var [transition, watcher] =
promise_test(t => {
const { transition, watcher } =
setupTransition(t, 'margin-left 100s 100s');
return Promise.all([ watcher.wait_for('transitionrun'),
transition.ready ]).then(function() {
transition.ready ]).then(() => {
// Make idle
transition.timeline = null;
return watcher.wait_for('transitioncancel');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Before -> Idle (Animation.timeline = null)');
promise_test(function(t) {
var [transition, watcher] =
promise_test(t => {
const { transition, watcher } =
setupTransition(t, 'margin-left 100s 100s');
return Promise.all([ watcher.wait_for('transitionrun'),
transition.ready ]).then(function() {
transition.ready ]).then(() => {
transition.currentTime = 100 * MS_PER_SEC;
return watcher.wait_for('transitionstart');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Before -> Active');
promise_test(function(t) {
var [transition, watcher, div] =
promise_test(t => {
const { transition, watcher, div, handler } =
setupTransition(t, 'margin-left 100s 100s');
var handler = new TransitionEventHandler(div);
return Promise.all([ watcher.wait_for('transitionrun'),
transition.ready ]).then(function() {
transition.ready ]).then(() => {
// Seek to After phase.
transition.currentTime = 200 * MS_PER_SEC;
return watcher.wait_for([ 'transitionstart', 'transitionend' ]);
}).then(function(evt) {
}).then(evt => {
assert_equals(handler.transitionstart, 0.0);
assert_equals(handler.transitionend, 100.0);
});
}, 'Before -> After');
promise_test(function(t) {
var [transition, watcher, div] =
promise_test(t => {
const { transition, watcher, div } =
setupTransition(t, 'margin-left 100s');
// Seek to Active start position.
transition.pause();
return watcher.wait_for([ 'transitionrun',
'transitionstart' ]).then(function(evt) {
'transitionstart' ]).then(evt => {
// Make idle
div.style.display = 'none';
flushComputedStyle(div);
return watcher.wait_for('transitioncancel');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Active -> Idle, no delay (display: none)');
promise_test(function(t) {
var [transition, watcher] =
promise_test(t => {
const { transition, watcher } =
setupTransition(t, 'margin-left 100s');
return watcher.wait_for([ 'transitionrun',
'transitionstart' ]).then(function(evt) {
'transitionstart' ]).then(evt => {
// Make idle
transition.currentTime = 0;
transition.timeline = null;
return watcher.wait_for('transitioncancel');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Active -> Idle, no delay (Animation.timeline = null)');
promise_test(function(t) {
var [transition, watcher, div] =
promise_test(t => {
const { transition, watcher, div } =
setupTransition(t, 'margin-left 100s 100s');
// Pause so the currentTime is fixed and we can accurately compare the event
// time in transition cancel events.
@ -201,35 +202,35 @@ promise_test(function(t) {
// Seek to Active phase.
transition.currentTime = 100 * MS_PER_SEC;
return watcher.wait_for([ 'transitionrun',
'transitionstart' ]).then(function(evt) {
'transitionstart' ]).then(evt => {
// Make idle
div.style.display = 'none';
flushComputedStyle(div);
return watcher.wait_for('transitioncancel');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Active -> Idle, with positive delay (display: none)');
promise_test(function(t) {
var [transition, watcher] =
promise_test(t => {
const { transition, watcher } =
setupTransition(t, 'margin-left 100s 100s');
// Seek to Active phase.
transition.currentTime = 100 * MS_PER_SEC;
return watcher.wait_for([ 'transitionrun',
'transitionstart' ]).then(function(evt) {
'transitionstart' ]).then(evt => {
// Make idle
transition.currentTime = 100 * MS_PER_SEC;
transition.timeline = null;
return watcher.wait_for('transitioncancel');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Active -> Idle, with positive delay (Animation.timeline = null)');
promise_test(function(t) {
var [transition, watcher, div] =
promise_test(t => {
const { transition, watcher, div } =
setupTransition(t, 'margin-left 100s -50s');
// Pause so the currentTime is fixed and we can accurately compare the event
@ -237,118 +238,115 @@ promise_test(function(t) {
transition.pause();
return watcher.wait_for([ 'transitionrun',
'transitionstart' ]).then(function(evt) {
'transitionstart' ]).then(evt => {
// Make idle
div.style.display = 'none';
flushComputedStyle(div);
return watcher.wait_for('transitioncancel');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 50.0);
});
}, 'Active -> Idle, with negative delay (display: none)');
promise_test(function(t) {
var [transition, watcher] =
promise_test(t => {
const { transition, watcher } =
setupTransition(t, 'margin-left 100s -50s');
return watcher.wait_for([ 'transitionrun',
'transitionstart' ]).then(function(evt) {
'transitionstart' ]).then(evt => {
// Make idle
transition.currentTime = 50 * MS_PER_SEC;
transition.timeline = null;
return watcher.wait_for('transitioncancel');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Active -> Idle, with negative delay (Animation.timeline = null)');
promise_test(function(t) {
var [transition, watcher] =
promise_test(t => {
const { transition, watcher } =
setupTransition(t, 'margin-left 100s 100s');
// Seek to Active phase.
transition.currentTime = 100 * MS_PER_SEC;
return watcher.wait_for([ 'transitionrun',
'transitionstart' ]).then(function(evt) {
'transitionstart' ]).then(evt => {
// Seek to Before phase.
transition.currentTime = 0;
return watcher.wait_for('transitionend');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 0.0);
});
}, 'Active -> Before');
promise_test(function(t) {
var [transition, watcher] =
promise_test(t => {
const { transition, watcher } =
setupTransition(t, 'margin-left 100s 100s');
// Seek to Active phase.
transition.currentTime = 100 * MS_PER_SEC;
return watcher.wait_for([ 'transitionrun',
'transitionstart' ]).then(function(evt) {
'transitionstart' ]).then(evt => {
// Seek to After phase.
transition.currentTime = 200 * MS_PER_SEC;
return watcher.wait_for('transitionend');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 100.0);
});
}, 'Active -> After');
promise_test(function(t) {
var [transition, watcher, div] =
promise_test(t => {
const { transition, watcher, div, handler } =
setupTransition(t, 'margin-left 100s 100s');
var handler = new TransitionEventHandler(div);
// Seek to After phase.
transition.finish();
return watcher.wait_for([ 'transitionrun',
'transitionstart',
'transitionend' ]).then(function(evt) {
'transitionend' ]).then(evt => {
// Seek to Before phase.
transition.currentTime = 0;
return watcher.wait_for([ 'transitionstart', 'transitionend' ]);
}).then(function(evt) {
}).then(evt => {
assert_equals(handler.transitionstart, 100.0);
assert_equals(handler.transitionend, 0.0);
});
}, 'After -> Before');
promise_test(function(t) {
var [transition, watcher] =
promise_test(t => {
const { transition, watcher } =
setupTransition(t, 'margin-left 100s 100s');
// Seek to After phase.
transition.finish();
return watcher.wait_for([ 'transitionrun',
'transitionstart',
'transitionend' ]).then(function(evt) {
'transitionend' ]).then(evt => {
// Seek to Active phase.
transition.currentTime = 100 * MS_PER_SEC;
return watcher.wait_for('transitionstart');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 100.0);
});
}, 'After -> Active');
promise_test(function(t) {
var [transition, watcher, div] =
promise_test(t => {
const { transition, watcher, div, handler } =
setupTransition(t, 'margin-left 100s -50s');
var handler = new TransitionEventHandler(div);
return watcher.wait_for([ 'transitionrun',
'transitionstart' ]).then(function() {
'transitionstart' ]).then(() => {
assert_equals(handler.transitionrun, 50.0);
assert_equals(handler.transitionstart, 50.0);
transition.finish();
return watcher.wait_for('transitionend');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 100.0);
});
}, 'Calculating the interval start and end time with negative start delay.');
promise_test(function(t) {
var [transition, watcher, div] =
promise_test(t => {
const { transition, watcher, div, handler } =
setupTransition(t, 'margin-left 100s 100s');
var handler = new TransitionEventHandler(div);
return watcher.wait_for('transitionrun').then(function(evt) {
return watcher.wait_for('transitionrun').then(evt => {
// We can't set the end delay via generated effect timing.
// Because CSS-Transition use the AnimationEffectTimingReadOnly.
transition.effect = new KeyframeEffect(div,
@ -361,38 +359,38 @@ promise_test(function(t) {
return watcher.wait_for([ 'transitioncancel',
'transitionrun',
'transitionstart' ]);
}).then(function() {
}).then(() => {
assert_equals(handler.transitionstart, 0.0);
// Seek to After phase.
transition.finish();
return watcher.wait_for('transitionend');
}).then(function(evt) {
}).then(evt => {
assert_equals(evt.elapsedTime, 50.0);
});
}, 'Calculating the interval start and end time with negative end delay.');
promise_test(function(t) {
var [transition, watcher, div] =
promise_test(t => {
const { transition, watcher, div } =
setupTransition(t, 'margin-left 100s 100s');
return watcher.wait_for('transitionrun').then(function() {
return watcher.wait_for('transitionrun').then(() => {
// Make idle
div.style.display = 'none';
flushComputedStyle(div);
return watcher.wait_for('transitioncancel');
}).then(function() {
}).then(() => {
transition.cancel();
// Then wait a couple of frames and check that no event was dispatched
return waitForAnimationFrames(2);
});
}, 'Call Animation.cancel after cancelling transition.');
promise_test(function(t) {
var [transition, watcher, div] =
promise_test(t => {
const { transition, watcher, div } =
setupTransition(t, 'margin-left 100s 100s');
return watcher.wait_for('transitionrun').then(function(evt) {
return watcher.wait_for('transitionrun').then(evt => {
// Make idle
div.style.display = 'none';
flushComputedStyle(div);
@ -403,33 +401,33 @@ promise_test(function(t) {
});
}, 'Restart transition after cancelling transition immediately');
promise_test(function(t) {
var [transition, watcher, div] =
promise_test(t => {
const { transition, watcher, div } =
setupTransition(t, 'margin-left 100s 100s');
return watcher.wait_for('transitionrun').then(function(evt) {
return watcher.wait_for('transitionrun').then(evt => {
// Make idle
div.style.display = 'none';
flushComputedStyle(div);
transition.play();
transition.cancel();
return watcher.wait_for('transitioncancel');
}).then(function(evt) {
}).then(evt => {
// Then wait a couple of frames and check that no event was dispatched
return waitForAnimationFrames(2);
});
}, 'Call Animation.cancel after restarting transition immediately');
promise_test(function(t) {
var [transition, watcher] =
promise_test(t => {
const { transition, watcher } =
setupTransition(t, 'margin-left 100s');
return watcher.wait_for([ 'transitionrun',
'transitionstart' ]).then(function(evt) {
'transitionstart' ]).then(evt => {
// Make idle
transition.timeline = null;
return watcher.wait_for('transitioncancel');
}).then(function(evt) {
}).then(evt => {
transition.timeline = document.timeline;
transition.play();
@ -437,15 +435,15 @@ promise_test(function(t) {
});
}, 'Set timeline and play transition after clear the timeline');
promise_test(function(t) {
var [transition, watcher, div] =
promise_test(t => {
const { transition, watcher, div } =
setupTransition(t, 'margin-left 100s');
return watcher.wait_for([ 'transitionrun',
'transitionstart' ]).then(function() {
'transitionstart' ]).then(() => {
transition.cancel();
return watcher.wait_for('transitioncancel');
}).then(function() {
}).then(() => {
// Make After phase
transition.effect = null;
@ -454,15 +452,15 @@ promise_test(function(t) {
});
}, 'Set null target effect after cancel the transition');
promise_test(function(t) {
var [transition, watcher, div] =
promise_test(t => {
const { transition, watcher, div } =
setupTransition(t, 'margin-left 100s');
return watcher.wait_for([ 'transitionrun',
'transitionstart' ]).then(function(evt) {
'transitionstart' ]).then(evt => {
transition.effect = null;
return watcher.wait_for('transitionend');
}).then(function(evt) {
}).then(evt => {
transition.cancel();
// Then wait a couple of frames and check that no event was dispatched

Просмотреть файл

@ -1485,9 +1485,11 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTM
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingMediaKeys)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedVideoStreamTrack)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPlayPromises)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSeekDOMPromise)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSetMediaKeysDOMPromise)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
@ -1514,9 +1516,11 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLE
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingMediaKeys)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPlayPromises)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSeekDOMPromise)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSetMediaKeysDOMPromise)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLMediaElement)
@ -1722,6 +1726,7 @@ void HTMLMediaElement::ShutdownDecoder()
{
RemoveMediaElementFromURITable();
NS_ASSERTION(mDecoder, "Must have decoder to shut down");
mSetCDMRequest.DisconnectIfExists();
mWaitingForKeyListener.DisconnectIfExists();
if (mMediaSource) {
mMediaSource->CompletePendingTransactions();
@ -3985,6 +3990,7 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNo
mPlaybackRate(1.0),
mPreservesPitch(true),
mPlayed(new TimeRanges(ToSupports(OwnerDoc()))),
mAttachingMediaKey(false),
mCurrentPlayRangeStart(-1.0),
mLoadedDataFired(false),
mAutoplaying(true),
@ -7015,6 +7021,180 @@ HTMLMediaElement::ContainsRestrictedContent()
return GetMediaKeys() != nullptr;
}
void
HTMLMediaElement::SetCDMProxyFailure(const MediaResult& aResult)
{
LOG(LogLevel::Debug, ("%s", __func__));
MOZ_ASSERT(mSetMediaKeysDOMPromise);
ResetSetMediaKeysTempVariables();
mSetMediaKeysDOMPromise->MaybeReject(aResult.Code(), aResult.Message());
}
void
HTMLMediaElement::RemoveMediaKeys()
{
LOG(LogLevel::Debug, ("%s", __func__));
// 5.2.3 Stop using the CDM instance represented by the mediaKeys attribute
// to decrypt media data and remove the association with the media element.
mMediaKeys->Unbind();
mMediaKeys = nullptr;
}
bool
HTMLMediaElement::TryRemoveMediaKeysAssociation()
{
MOZ_ASSERT(mMediaKeys);
LOG(LogLevel::Debug, ("%s", __func__));
// 5.2.1 If the user agent or CDM do not support removing the association,
// let this object's attaching media keys value be false and reject promise
// with a new DOMException whose name is NotSupportedError.
// 5.2.2 If the association cannot currently be removed, let this object's
// attaching media keys value be false and reject promise with a new
// DOMException whose name is InvalidStateError.
if (mDecoder) {
RefPtr<HTMLMediaElement> self = this;
mDecoder->SetCDMProxy(nullptr)
->Then(mAbstractMainThread,
__func__,
[self]() {
self->mSetCDMRequest.Complete();
self->RemoveMediaKeys();
if (self->AttachNewMediaKeys()) {
// No incoming MediaKeys object or MediaDecoder is not created yet.
self->MakeAssociationWithCDMResolved();
}
},
[self](const MediaResult& aResult) {
self->mSetCDMRequest.Complete();
// 5.2.4 If the preceding step failed, let this object's attaching media
// keys value be false and reject promise with a new DOMException whose
// name is the appropriate error name.
self->SetCDMProxyFailure(aResult);
})
->Track(mSetCDMRequest);
return false;
}
RemoveMediaKeys();
return true;
}
bool
HTMLMediaElement::DetachExistingMediaKeys()
{
LOG(LogLevel::Debug, ("%s", __func__));
MOZ_ASSERT(mSetMediaKeysDOMPromise);
// 5.1 If mediaKeys is not null, CDM instance represented by mediaKeys is
// already in use by another media element, and the user agent is unable
// to use it with this element, let this object's attaching media keys
// value be false and reject promise with a new DOMException whose name
// is QuotaExceededError.
if (mIncomingMediaKeys && mIncomingMediaKeys->IsBoundToMediaElement()) {
SetCDMProxyFailure(MediaResult(
NS_ERROR_DOM_QUOTA_EXCEEDED_ERR,
"MediaKeys object is already bound to another HTMLMediaElement"));
return false;
}
// 5.2 If the mediaKeys attribute is not null, run the following steps:
if (mMediaKeys) {
return TryRemoveMediaKeysAssociation();
}
return true;
}
void
HTMLMediaElement::MakeAssociationWithCDMResolved()
{
LOG(LogLevel::Debug, ("%s", __func__));
MOZ_ASSERT(mSetMediaKeysDOMPromise);
// 5.4 Set the mediaKeys attribute to mediaKeys.
mMediaKeys = mIncomingMediaKeys;
// 5.5 Let this object's attaching media keys value be false.
ResetSetMediaKeysTempVariables();
// 5.6 Resolve promise.
mSetMediaKeysDOMPromise->MaybeResolveWithUndefined();
mSetMediaKeysDOMPromise = nullptr;
}
bool
HTMLMediaElement::TryMakeAssociationWithCDM(CDMProxy* aProxy)
{
LOG(LogLevel::Debug, ("%s", __func__));
MOZ_ASSERT(aProxy);
// 5.3.3 Queue a task to run the "Attempt to Resume Playback If Necessary"
// algorithm on the media element.
// Note: Setting the CDMProxy on the MediaDecoder will unblock playback.
if (mDecoder) {
// CDMProxy is set asynchronously in MediaFormatReader, once it's done,
// HTMLMediaElement should resolve or reject the DOM promise.
RefPtr<HTMLMediaElement> self = this;
mDecoder->SetCDMProxy(aProxy)
->Then(mAbstractMainThread,
__func__,
[self]() {
self->mSetCDMRequest.Complete();
self->MakeAssociationWithCDMResolved();
},
[self](const MediaResult& aResult) {
self->mSetCDMRequest.Complete();
self->SetCDMProxyFailure(aResult);
})
->Track(mSetCDMRequest);
return false;
}
return true;
}
bool
HTMLMediaElement::AttachNewMediaKeys()
{
LOG(LogLevel::Debug,
("%s incoming MediaKeys(%p)", __func__, mIncomingMediaKeys.get()));
MOZ_ASSERT(mSetMediaKeysDOMPromise);
// 5.3. If mediaKeys is not null, run the following steps:
if (mIncomingMediaKeys) {
auto cdmProxy = mIncomingMediaKeys->GetCDMProxy();
if (!cdmProxy) {
SetCDMProxyFailure(MediaResult(
NS_ERROR_DOM_INVALID_STATE_ERR,
"CDM crashed before binding MediaKeys object to HTMLMediaElement"));
return false;
}
// 5.3.1 Associate the CDM instance represented by mediaKeys with the
// media element for decrypting media data.
if (NS_FAILED(mIncomingMediaKeys->Bind(this))) {
// 5.3.2 If the preceding step failed, run the following steps:
// 5.3.2.1 Set the mediaKeys attribute to null.
mMediaKeys = nullptr;
// 5.3.2.2 Let this object's attaching media keys value be false.
// 5.3.2.3 Reject promise with a new DOMException whose name is
// the appropriate error name.
SetCDMProxyFailure(
MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
"Failed to bind MediaKeys object to HTMLMediaElement"));
return false;
}
return TryMakeAssociationWithCDM(cdmProxy);
}
return true;
}
void
HTMLMediaElement::ResetSetMediaKeysTempVariables()
{
mAttachingMediaKey = false;
mIncomingMediaKeys = nullptr;
}
already_AddRefed<Promise>
HTMLMediaElement::SetMediaKeys(mozilla::dom::MediaKeys* aMediaKeys,
ErrorResult& aRv)
@ -7046,89 +7226,31 @@ HTMLMediaElement::SetMediaKeys(mozilla::dom::MediaKeys* aMediaKeys,
return promise.forget();
}
// Note: Our attaching code is synchronous, so we can skip the following steps.
// 2. If this object's attaching media keys value is true, return a
// promise rejected with a new DOMException whose name is InvalidStateError.
// 3. Let this object's attaching media keys value be true.
// 4. Let promise be a new promise.
// 5. Run the following steps in parallel:
// 5.1 If mediaKeys is not null, CDM instance represented by mediaKeys is
// already in use by another media element, and the user agent is unable
// to use it with this element, let this object's attaching media keys
// value be false and reject promise with a new DOMException whose name
// is QuotaExceededError.
if (aMediaKeys && aMediaKeys->IsBoundToMediaElement()) {
promise->MaybeReject(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR,
NS_LITERAL_CSTRING("MediaKeys object is already bound to another HTMLMediaElement"));
if (mAttachingMediaKey) {
promise->MaybeReject(
NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("A MediaKeys object is in attaching operation."));
return promise.forget();
}
// 5.2 If the mediaKeys attribute is not null, run the following steps:
if (mMediaKeys) {
// 5.2.1 If the user agent or CDM do not support removing the association,
// let this object's attaching media keys value be false and reject promise
// with a new DOMException whose name is NotSupportedError.
// 3. Let this object's attaching media keys value be true.
mAttachingMediaKey = true;
mIncomingMediaKeys = aMediaKeys;
// 5.2.2 If the association cannot currently be removed, let this object's
// attaching media keys value be false and reject promise with a new
// DOMException whose name is InvalidStateError.
if (mDecoder) {
// We don't support swapping out the MediaKeys once we've started to
// setup the playback pipeline. Note this also means we don't need to worry
// about handling disassociating the MediaKeys from the MediaDecoder.
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Can't change MediaKeys on HTMLMediaElement after load has started"));
return promise.forget();
}
// 4. Let promise be a new promise.
mSetMediaKeysDOMPromise = promise;
// 5.2.3 Stop using the CDM instance represented by the mediaKeys attribute
// to decrypt media data and remove the association with the media element.
mMediaKeys->Unbind();
mMediaKeys = nullptr;
// 5. Run the following steps in parallel:
// 5.2.4 If the preceding step failed, let this object's attaching media
// keys value be false and reject promise with a new DOMException whose
// name is the appropriate error name.
// 5.1 & 5.2 & 5.3
if (!DetachExistingMediaKeys() || !AttachNewMediaKeys()) {
return promise.forget();
}
// 5.3. If mediaKeys is not null, run the following steps:
if (aMediaKeys) {
if (!aMediaKeys->GetCDMProxy()) {
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("CDM crashed before binding MediaKeys object to HTMLMediaElement"));
return promise.forget();
}
// 5.3.1 Associate the CDM instance represented by mediaKeys with the
// media element for decrypting media data.
if (NS_FAILED(aMediaKeys->Bind(this))) {
// 5.3.2 If the preceding step failed, run the following steps:
// 5.3.2.1 Set the mediaKeys attribute to null.
mMediaKeys = nullptr;
// 5.3.2.2 Let this object's attaching media keys value be false.
// 5.3.2.3 Reject promise with a new DOMException whose name is
// the appropriate error name.
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
NS_LITERAL_CSTRING("Failed to bind MediaKeys object to HTMLMediaElement"));
return promise.forget();
}
// 5.3.3 Queue a task to run the "Attempt to Resume Playback If Necessary"
// algorithm on the media element.
// Note: Setting the CDMProxy on the MediaDecoder will unblock playback.
if (mDecoder) {
mDecoder->SetCDMProxy(aMediaKeys->GetCDMProxy());
}
}
// 5.4 Set the mediaKeys attribute to mediaKeys.
mMediaKeys = aMediaKeys;
// 5.5 Let this object's attaching media keys value be false.
// 5.6 Resolve promise.
promise->MaybeResolveWithUndefined();
// 5.4, 5.5, 5.6
MakeAssociationWithCDMResolved();
// 6. Return promise.
return promise.forget();

Просмотреть файл

@ -12,6 +12,7 @@
#include "MediaEventSource.h"
#include "SeekTarget.h"
#include "MediaDecoderOwner.h"
#include "MediaPromiseDefs.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIObserver.h"
#include "mozilla/CORSMode.h"
@ -1332,6 +1333,15 @@ protected:
const nsAttrValueOrString& aValue,
bool aNotify) override;
bool DetachExistingMediaKeys();
bool TryRemoveMediaKeysAssociation();
void RemoveMediaKeys();
bool AttachNewMediaKeys();
bool TryMakeAssociationWithCDM(CDMProxy* aProxy);
void MakeAssociationWithCDMResolved();
void SetCDMProxyFailure(const MediaResult& aResult);
void ResetSetMediaKeysTempVariables();
// The current decoder. Load() has been called on this decoder.
// At most one of mDecoder and mSrcStream can be non-null.
RefPtr<MediaDecoder> mDecoder;
@ -1534,6 +1544,12 @@ protected:
// Encrypted Media Extension media keys.
RefPtr<MediaKeys> mMediaKeys;
RefPtr<MediaKeys> mIncomingMediaKeys;
// The dom promise is used for HTMLMediaElement::SetMediaKeys.
RefPtr<DetailedPromise> mSetMediaKeysDOMPromise;
// Used to indicate if the MediaKeys attaching operation is on-going or not.
bool mAttachingMediaKey;
MozPromiseRequestHolder<SetCDMPromise> mSetCDMRequest;
// Stores the time at the start of the current 'played' range.
double mCurrentPlayRangeStart;

Просмотреть файл

@ -14,6 +14,7 @@
#include "MemoryBlockCache.h"
#include "mozilla/Attributes.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/ReentrantMonitor.h"
@ -35,9 +36,11 @@ namespace mozilla {
#undef LOG
#undef LOGI
#undef LOGE
LazyLogModule gMediaCacheLog("MediaCache");
#define LOG(...) MOZ_LOG(gMediaCacheLog, LogLevel::Debug, (__VA_ARGS__))
#define LOGI(...) MOZ_LOG(gMediaCacheLog, LogLevel::Info, (__VA_ARGS__))
#define LOGE(...) NS_DebugBreak(NS_DEBUG_WARNING, nsPrintfCString(__VA_ARGS__).get(), nullptr, __FILE__, __LINE__)
// For HTTP seeking, if number of bytes needing to be
// seeked forward is less than this value then a read is
@ -2015,6 +2018,9 @@ MediaCacheStream::NotifyDataReceived(uint32_t aLoadID,
return;
}
// True if we commit any blocks to the cache.
bool cacheUpdated = false;
auto source = MakeSpan<const uint8_t>(aData, aCount);
// We process the data one block (or part of a block) at a time
@ -2042,6 +2048,7 @@ MediaCacheStream::NotifyDataReceived(uint32_t aLoadID,
source.First(remaining));
source = source.From(remaining);
mChannelOffset += remaining;
cacheUpdated = true;
} else {
// The buffer to be filled in the partial block.
auto buf = MakeSpan<uint8_t>(mPartialBlockBuffer.get() + partial.Length(),
@ -2061,10 +2068,12 @@ MediaCacheStream::NotifyDataReceived(uint32_t aLoadID,
stream->mClient->CacheClientNotifyDataReceived();
}
// Notify in case there's a waiting reader
// XXX it would be fairly easy to optimize things a lot more to
// avoid waking up reader threads unnecessarily
mon.NotifyAll();
if (cacheUpdated) {
// Wake up the reader who is waiting for the committed blocks.
mon.NotifyAll();
}
}
void
@ -2462,6 +2471,72 @@ MediaCacheStream::ThrottleReadahead(bool bThrottle)
}
}
uint32_t
MediaCacheStream::ReadPartialBlock(int64_t aOffset, Span<char> aBuffer)
{
mMediaCache->GetReentrantMonitor().AssertCurrentThreadIn();
MOZ_ASSERT(IsOffsetAllowed(aOffset));
if (OffsetToBlockIndexUnchecked(mChannelOffset) !=
OffsetToBlockIndexUnchecked(aOffset) ||
aOffset >= mChannelOffset) {
// Not in the partial block or no data to read.
return 0;
}
auto source = MakeSpan<const uint8_t>(
mPartialBlockBuffer.get() + OffsetInBlock(aOffset),
OffsetInBlock(mChannelOffset) - OffsetInBlock(aOffset));
// We have |source.Length() <= BLOCK_SIZE < INT32_MAX| to guarantee
// that |bytesToRead| can fit into a uint32_t.
uint32_t bytesToRead = std::min(aBuffer.Length(), source.Length());
memcpy(aBuffer.Elements(), source.Elements(), bytesToRead);
return bytesToRead;
}
Result<uint32_t, nsresult>
MediaCacheStream::ReadBlockFromCache(int64_t aOffset, Span<char> aBuffer)
{
mMediaCache->GetReentrantMonitor().AssertCurrentThreadIn();
MOZ_ASSERT(IsOffsetAllowed(aOffset));
// OffsetToBlockIndexUnchecked() is always non-negative.
uint32_t index = OffsetToBlockIndexUnchecked(aOffset);
int32_t cacheBlock = index < mBlocks.Length() ? mBlocks[index] : -1;
if (cacheBlock < 0) {
// Not in the cache.
return 0;
}
if (aBuffer.Length() > size_t(BLOCK_SIZE)) {
// Clamp the buffer to avoid overflow below since we will read at most
// BLOCK_SIZE bytes.
aBuffer = aBuffer.First(BLOCK_SIZE);
}
// |BLOCK_SIZE - OffsetInBlock(aOffset)| <= BLOCK_SIZE
int32_t bytesToRead =
std::min<int32_t>(BLOCK_SIZE - OffsetInBlock(aOffset), aBuffer.Length());
int32_t bytesRead = 0;
nsresult rv =
mMediaCache->ReadCacheFile(cacheBlock * BLOCK_SIZE + OffsetInBlock(aOffset),
aBuffer.Elements(),
bytesToRead,
&bytesRead);
// Ensure |cacheBlock * BLOCK_SIZE + OffsetInBlock(aOffset)| won't overflow.
static_assert(INT64_MAX >= BLOCK_SIZE * (uint32_t(INT32_MAX) + 1),
"BLOCK_SIZE too large!");
if (NS_FAILED(rv)) {
nsCString name;
GetErrorName(rv, name);
LOGE("Stream %p ReadCacheFile failed, rv=%s", this, name.Data());
return mozilla::Err(rv);
}
return bytesRead;
}
int64_t
MediaCacheStream::Tell()
{
@ -2475,8 +2550,6 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
if (mClosed)
return NS_ERROR_FAILURE;
// Cache the offset in case it is changed again when we are waiting for the
// monitor to be notified to avoid reading at the wrong position.
@ -2485,10 +2558,16 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
uint32_t count = 0;
// Read one block (or part of a block) at a time
while (count < aCount) {
if (mClosed) {
return NS_ERROR_ABORT;
}
int32_t streamBlock = OffsetToBlockIndex(streamOffset);
if (streamBlock < 0) {
break;
LOGE("Stream %p invalid offset=%" PRId64, this, streamOffset);
return NS_ERROR_ILLEGAL_VALUE;
}
uint32_t offsetInStreamBlock = uint32_t(streamOffset - streamBlock*BLOCK_SIZE);
int64_t size = std::min<int64_t>(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
@ -2509,12 +2588,6 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
if (cacheBlock < 0) {
// We don't have a complete cached block here.
if (count > 0) {
// Some data has been read, so return what we've got instead of
// blocking or trying to find a stream with a partial block.
break;
}
// See if the data is available in the partial cache block of any
// stream reading this resource. We need to do this in case there is
// another stream with this resource that has all the data to the end of
@ -2524,7 +2597,8 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
while (MediaCacheStream* stream = iter.Next()) {
if (OffsetToBlockIndexUnchecked(stream->mChannelOffset) ==
streamBlock &&
streamOffset < stream->mChannelOffset) {
streamOffset < stream->mChannelOffset &&
stream->mChannelOffset == stream->mStreamLength) {
streamWithPartialBlock = stream;
break;
}
@ -2537,13 +2611,16 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
// Clamp bytes until 64-bit file size issues are fixed.
bytes = std::min(bytes, int64_t(INT32_MAX));
MOZ_ASSERT(bytes >= 0 && bytes <= aCount, "Bytes out of range.");
memcpy(aBuffer,
streamWithPartialBlock->mPartialBlockBuffer.get() + offsetInStreamBlock, bytes);
memcpy(aBuffer + count,
streamWithPartialBlock->mPartialBlockBuffer.get() +
offsetInStreamBlock,
bytes);
if (mCurrentMode == MODE_METADATA) {
streamWithPartialBlock->mMetadataInPartialBlockBuffer = true;
}
streamOffset += bytes;
count = bytes;
count += bytes;
// Break for we've reached EOS and have nothing more to read.
break;
}
@ -2556,11 +2633,6 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
// No data has been read yet, so block
mon.Wait();
if (mClosed) {
// We may have successfully read some data, but let's just throw
// that out.
return NS_ERROR_FAILURE;
}
continue;
}
@ -2573,22 +2645,25 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
nsresult rv = mMediaCache->ReadCacheFile(
offset, aBuffer + count, int32_t(size), &bytes);
if (NS_FAILED(rv)) {
if (count == 0)
return rv;
// If we did successfully read some data, may as well return it
break;
nsCString name;
GetErrorName(rv, name);
LOGE("Stream %p ReadCacheFile failed, rv=%s", this, name.Data());
return rv;
}
streamOffset += bytes;
count += bytes;
}
if (count > 0) {
// Some data was read, so queue an update since block priorities may
// have changed
mMediaCache->QueueUpdate();
}
LOG("Stream %p Read at %" PRId64 " count=%d", this, streamOffset-count, count);
*aBytes = count;
if (count == 0) {
return NS_OK;
}
// Some data was read, so queue an update since block priorities may
// have changed
mMediaCache->QueueUpdate();
LOG("Stream %p Read at %" PRId64 " count=%d", this, streamOffset-count, count);
mStreamOffset = streamOffset;
return NS_OK;
}
@ -2606,67 +2681,49 @@ MediaCacheStream::ReadAt(int64_t aOffset, char* aBuffer,
}
nsresult
MediaCacheStream::ReadFromCache(char* aBuffer, int64_t aOffset, int64_t aCount)
MediaCacheStream::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount)
{
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
// The buffer we are about to fill.
auto buffer = MakeSpan<char>(aBuffer, aCount);
// Read one block (or part of a block) at a time
uint32_t count = 0;
int64_t streamOffset = aOffset;
while (count < aCount) {
while (!buffer.IsEmpty()) {
if (mClosed) {
// We need to check |mClosed| in each iteration which might be changed
// after calling |mMediaCache->ReadCacheFile|.
return NS_ERROR_FAILURE;
}
int32_t streamBlock = OffsetToBlockIndex(streamOffset);
if (streamBlock < 0) {
break;
}
uint32_t offsetInStreamBlock =
uint32_t(streamOffset - streamBlock*BLOCK_SIZE);
int64_t size = std::min<int64_t>(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
if (mStreamLength >= 0) {
// Don't try to read beyond the end of the stream
int64_t bytesRemaining = mStreamLength - streamOffset;
if (bytesRemaining <= 0) {
return NS_ERROR_FAILURE;
}
size = std::min(size, bytesRemaining);
// Clamp size until 64-bit file size issues are fixed.
size = std::min(size, int64_t(INT32_MAX));
if (!IsOffsetAllowed(streamOffset)) {
LOGE("Stream %p invalid offset=%" PRId64, this, streamOffset);
return NS_ERROR_ILLEGAL_VALUE;
}
int32_t bytes;
int32_t channelBlock = OffsetToBlockIndexUnchecked(mChannelOffset);
int32_t cacheBlock =
size_t(streamBlock) < mBlocks.Length() ? mBlocks[streamBlock] : -1;
if (channelBlock == streamBlock && streamOffset < mChannelOffset) {
// We can just use the data in mPartialBlockBuffer. In fact we should
// use it rather than waiting for the block to fill and land in
// the cache.
// Clamp bytes until 64-bit file size issues are fixed.
int64_t toCopy = std::min<int64_t>(size, mChannelOffset - streamOffset);
bytes = std::min(toCopy, int64_t(INT32_MAX));
MOZ_ASSERT(bytes >= 0 && bytes <= toCopy, "Bytes out of range.");
memcpy(aBuffer + count,
mPartialBlockBuffer.get() + offsetInStreamBlock, bytes);
} else {
if (cacheBlock < 0) {
// We expect all blocks to be cached! Fail!
return NS_ERROR_FAILURE;
}
int64_t offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
MOZ_ASSERT(size >= 0 && size <= INT32_MAX, "Size out of range.");
nsresult rv = mMediaCache->ReadCacheFile(
offset, aBuffer + count, int32_t(size), &bytes);
if (NS_FAILED(rv)) {
return rv;
}
Result<uint32_t, nsresult> rv = ReadBlockFromCache(streamOffset, buffer);
if (rv.isErr()) {
return rv.unwrapErr();
}
streamOffset += bytes;
count += bytes;
uint32_t bytes = rv.unwrap();
if (bytes > 0) {
// Read data from the cache successfully. Let's try next block.
streamOffset += bytes;
buffer = buffer.From(bytes);
continue;
}
// The partial block is our last chance to get data.
bytes = ReadPartialBlock(streamOffset, buffer);
if (bytes < buffer.Length()) {
// Not enough data to read.
return NS_ERROR_FAILURE;
}
// Return for we've got all the requested bytes.
return NS_OK;
}
return NS_OK;

Просмотреть файл

@ -8,6 +8,7 @@
#define MediaCache_h_
#include "Intervals.h"
#include "mozilla/Result.h"
#include "mozilla/UniquePtr.h"
#include "nsCOMPtr.h"
#include "nsHashKeys.h"
@ -314,9 +315,7 @@ public:
// in the cache. Will not mark blocks as read. Can be called from the main
// thread. It's the caller's responsibility to wrap the call in a pin/unpin,
// and also to check that the range they want is cached before calling this.
nsresult ReadFromCache(char* aBuffer,
int64_t aOffset,
int64_t aCount);
nsresult ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCount);
// IsDataCachedToEndOfStream returns true if all the data from
// aOffset to the end of the stream (the server-reported end, if the
@ -426,6 +425,16 @@ private:
int32_t mCount;
};
// Read data from the partial block and return the number of bytes read
// successfully. 0 if aOffset is not an offset in the partial block or there
// is nothing to read.
uint32_t ReadPartialBlock(int64_t aOffset, Span<char> aBuffer);
// Read data from the cache block specified by aOffset. Return the number of
// bytes read successfully or an error code if any failure.
Result<uint32_t, nsresult> ReadBlockFromCache(int64_t aOffset,
Span<char> aBuffer);
// Returns the end of the bytes starting at the given offset
// which are in cache.
// This method assumes that the cache monitor is held and can be called on

Просмотреть файл

@ -1423,18 +1423,15 @@ MediaDecoder::CanPlayThrough()
return val;
}
void
RefPtr<SetCDMPromise>
MediaDecoder::SetCDMProxy(CDMProxy* aProxy)
{
MOZ_ASSERT(NS_IsMainThread());
RefPtr<CDMProxy> proxy = aProxy;
RefPtr<MediaFormatReader> reader = mReader;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
"MediaFormatReader::SetCDMProxy",
[reader, proxy]() {
reader->SetCDMProxy(proxy);
});
mReader->OwnerThread()->Dispatch(r.forget());
return InvokeAsync<RefPtr<CDMProxy>>(mReader->OwnerThread(),
mReader.get(),
__func__,
&MediaFormatReader::SetCDMProxy,
aProxy);
}
bool

Просмотреть файл

@ -12,6 +12,7 @@
#include "MediaDecoderOwner.h"
#include "MediaEventSource.h"
#include "MediaMetadataManager.h"
#include "MediaPromiseDefs.h"
#include "MediaResource.h"
#include "MediaStatistics.h"
#include "MediaStreamGraph.h"
@ -355,7 +356,7 @@ private:
return mAbstractMainThread;
}
void SetCDMProxy(CDMProxy* aProxy);
RefPtr<SetCDMPromise> SetCDMProxy(CDMProxy* aProxy);
void EnsureTelemetryReported();

Просмотреть файл

@ -1228,6 +1228,10 @@ MediaFormatReader::Shutdown()
mMetadataPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
mSeekPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
mSkipRequest.DisconnectIfExists();
mSetCDMPromise.RejectIfExists(
MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
"MediaFormatReader is shutting down"),
__func__);
if (mAudio.HasPromise()) {
mAudio.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
@ -1318,23 +1322,101 @@ MediaFormatReader::Init()
return NS_OK;
}
bool
MediaFormatReader::ResolveSetCDMPromiseIfDone(TrackType aTrack)
{
// When a CDM proxy is set, MFR would shutdown the existing MediaDataDecoder
// and would create new one for specific track in the next Update.
MOZ_ASSERT(OnTaskQueue());
if (mSetCDMPromise.IsEmpty()) {
return true;
}
MOZ_ASSERT(mCDMProxy);
if (mSetCDMForTracks.contains(aTrack)) {
mSetCDMForTracks -= aTrack;
}
if (mSetCDMForTracks.isEmpty()) {
LOGV("%s : Done ", __func__);
mSetCDMPromise.Resolve(/* aIgnored = */ true, __func__);
ScheduleUpdate(TrackInfo::kAudioTrack);
ScheduleUpdate(TrackInfo::kVideoTrack);
return true;
}
LOGV("%s : %s track is ready.", __func__, TrackTypeToStr(aTrack));
return false;
}
void
MediaFormatReader::PrepareToSetCDMForTrack(TrackType aTrack)
{
MOZ_ASSERT(OnTaskQueue());
LOGV("%s : %s", __func__, TrackTypeToStr(aTrack));
mSetCDMForTracks += aTrack;
if (mCDMProxy) {
// An old cdm proxy exists, so detaching old cdm proxy by shutting down
// MediaDataDecoder.
ShutdownDecoder(aTrack);
}
ScheduleUpdate(aTrack);
}
bool
MediaFormatReader::IsDecoderWaitingForCDM(TrackType aTrack)
{
MOZ_ASSERT(OnTaskQueue());
return IsEncrypted() && mSetCDMForTracks.contains(aTrack) && !mCDMProxy;
}
RefPtr<SetCDMPromise>
MediaFormatReader::SetCDMProxy(CDMProxy* aProxy)
{
RefPtr<CDMProxy> proxy = aProxy;
RefPtr<MediaFormatReader> self = this;
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableFunction("MediaFormatReader::SetCDMProxy", [=]() {
MOZ_ASSERT(self->OnTaskQueue());
self->mCDMProxy = proxy;
if (HasAudio()) {
self->ScheduleUpdate(TrackInfo::kAudioTrack);
}
if (HasVideo()) {
self->ScheduleUpdate(TrackInfo::kVideoTrack);
}
});
OwnerThread()->Dispatch(r.forget());
MOZ_ASSERT(OnTaskQueue());
LOGV("SetCDMProxy (%p)", aProxy);
if (mShutdown) {
return SetCDMPromise::CreateAndReject(
MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
"MediaFormatReader is shutting down"),
__func__);
}
mSetCDMPromise.RejectIfExists(
MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
"Another new CDM proxy is being set."),
__func__);
// Shutdown all decoders as switching CDM proxy indicates that it's
// inappropriate for the existing decoders to continue decoding via the old
// CDM proxy.
if (HasAudio()) {
PrepareToSetCDMForTrack(TrackInfo::kAudioTrack);
}
if (HasVideo()) {
PrepareToSetCDMForTrack(TrackInfo::kVideoTrack);
}
mCDMProxy = aProxy;
if (IsEncrypted() && !mCDMProxy) {
// Release old PDMFactory which contains an EMEDecoderModule.
mPlatform = nullptr;
}
if (!mInitDone || mSetCDMForTracks.isEmpty() || !mCDMProxy) {
// 1) MFR is not initialized yet or
// 2) Demuxer is initialized without active audio and video or
// 3) A null cdm proxy is set
// the promise can be resolved directly.
mSetCDMForTracks.clear();
return SetCDMPromise::CreateAndResolve(/* aIgnored = */ true, __func__);
}
RefPtr<SetCDMPromise> p = mSetCDMPromise.Ensure(__func__);
return p;
}
bool
@ -2391,6 +2473,13 @@ MediaFormatReader::Update(TrackType aTrack)
LOG("Rejecting %s promise: WAITING_FOR_DATA due to waiting for key",
TrackTypeToStr(aTrack));
decoder.RejectPromise(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__);
} else if (IsDecoderWaitingForCDM(aTrack)) {
// Rejecting the promise could lead to entering buffering state for MDSM,
// once a qualified(with the same key system and sessions created by the
// same InitData) new cdm proxy is set, decoding can be resumed.
LOG("Rejecting %s promise: WAITING_FOR_DATA due to waiting for CDM",
TrackTypeToStr(aTrack));
decoder.RejectPromise(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__);
}
}
@ -2448,7 +2537,7 @@ MediaFormatReader::Update(TrackType aTrack)
LOGV("Update(%s) ni=%d no=%d in:%" PRIu64 " out:%" PRIu64
" qs=%u decoding:%d flushing:%d desc:%s pending:%u waiting:%d eos:%d "
"ds:%d sid:%u",
"ds:%d sid:%u waitcdm:%d",
TrackTypeToStr(aTrack),
needInput,
needOutput,
@ -2462,9 +2551,10 @@ MediaFormatReader::Update(TrackType aTrack)
decoder.mWaitingForData,
decoder.mDemuxEOS,
int32_t(decoder.mDrainState),
decoder.mLastStreamSourceID);
decoder.mLastStreamSourceID,
IsDecoderWaitingForCDM(aTrack));
if (IsWaitingOnCDMResource()) {
if (IsWaitingOnCDMResource() || !ResolveSetCDMPromiseIfDone(aTrack)) {
// If the content is encrypted, MFR won't start to create decoder until
// CDMProxy is set.
return;
@ -2474,7 +2564,9 @@ MediaFormatReader::Update(TrackType aTrack)
(!decoder.mTimeThreshold || decoder.mTimeThreshold.ref().mWaiting)) ||
(decoder.mWaitingForKey && decoder.mDecodeRequest.Exists())) {
// Nothing more we can do at present.
LOGV("Still waiting for data or key.");
LOGV("Still waiting for data or key. data(%d)/key(%d)",
decoder.mWaitingForData,
decoder.mWaitingForKey);
return;
}

Просмотреть файл

@ -18,6 +18,7 @@
#include "MediaDataDemuxer.h"
#include "MediaMetadataManager.h"
#include "MediaPrefs.h"
#include "MediaPromiseDefs.h"
#include "nsAutoPtr.h"
#include "PDMFactory.h"
#include "SeekTarget.h"
@ -91,7 +92,6 @@ class MediaFormatReader final
static const bool IsExclusive = true;
typedef TrackInfo::TrackType TrackType;
typedef MozPromise<bool, MediaResult, IsExclusive> NotifyDataArrivedPromise;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaFormatReader)
public:
@ -194,7 +194,7 @@ public:
// cases like MSE.
bool UseBufferingHeuristics() const { return mTrackDemuxersMayBlock; }
void SetCDMProxy(CDMProxy* aProxy);
RefPtr<SetCDMPromise> SetCDMProxy(CDMProxy* aProxy);
// Returns a string describing the state of the decoder data.
// Used for debugging purposes.
@ -792,6 +792,12 @@ private:
// Used in bug 1393399 for telemetry.
const MediaDecoderOwnerID mMediaDecoderOwnerID;
bool ResolveSetCDMPromiseIfDone(TrackType aTrack);
void PrepareToSetCDMForTrack(TrackType aTrack);
MozPromiseHolder<SetCDMPromise> mSetCDMPromise;
TrackSet mSetCDMForTracks{};
bool IsDecoderWaitingForCDM(TrackType aTrack);
};
} // namespace mozilla

Просмотреть файл

@ -0,0 +1,19 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#ifndef MediaPromiseDefs_h_
#define MediaPromiseDefs_h_
#include "MediaResult.h"
#include "mozilla/MozPromise.h"
namespace mozilla {
using SetCDMPromise =
MozPromise<bool /* aIgnored */, MediaResult, /* IsExclusive */ true>;
} // namespace mozilla
#endif

Просмотреть файл

@ -414,34 +414,14 @@ MediaResourceIndex::UncachedReadAt(int64_t aOffset,
uint32_t aCount,
uint32_t* aBytes) const
{
*aBytes = 0;
if (aOffset < 0) {
return NS_ERROR_ILLEGAL_VALUE;
}
if (aCount != 0) {
for (;;) {
uint32_t bytesRead = 0;
nsresult rv = mResource->ReadAt(aOffset, aBuffer, aCount, &bytesRead);
if (NS_FAILED(rv)) {
return rv;
}
if (bytesRead == 0) {
break;
}
*aBytes += bytesRead;
aCount -= bytesRead;
if (aCount == 0) {
break;
}
aOffset += bytesRead;
if (aOffset < 0) {
// Very unlikely overflow.
return NS_ERROR_FAILURE;
}
aBuffer += bytesRead;
}
if (aCount == 0) {
*aBytes = 0;
return NS_OK;
}
return NS_OK;
return mResource->ReadAt(aOffset, aBuffer, aCount, aBytes);
}
nsresult
@ -451,36 +431,15 @@ MediaResourceIndex::UncachedRangedReadAt(int64_t aOffset,
uint32_t aExtraCount,
uint32_t* aBytes) const
{
*aBytes = 0;
uint32_t count = aRequestedCount + aExtraCount;
if (aOffset < 0 || count < aRequestedCount) {
return NS_ERROR_ILLEGAL_VALUE;
}
if (count != 0) {
for (;;) {
uint32_t bytesRead = 0;
nsresult rv = mResource->ReadAt(aOffset, aBuffer, count, &bytesRead);
if (NS_FAILED(rv)) {
return rv;
}
if (bytesRead == 0) {
break;
}
*aBytes += bytesRead;
count -= bytesRead;
if (count <= aExtraCount) {
// We have read at least aRequestedCount, don't loop anymore.
break;
}
aOffset += bytesRead;
if (aOffset < 0) {
// Very unlikely overflow.
return NS_ERROR_FAILURE;
}
aBuffer += bytesRead;
}
if (count == 0) {
*aBytes = 0;
return NS_OK;
}
return NS_OK;
return mResource->ReadAt(aOffset, aBuffer, count, aBytes);
}
nsresult
@ -516,30 +475,17 @@ MediaResourceIndex::Seek(int32_t aWhence, int64_t aOffset)
already_AddRefed<MediaByteBuffer>
MediaResourceIndex::MediaReadAt(int64_t aOffset, uint32_t aCount) const
{
NS_ENSURE_TRUE(aOffset >= 0, nullptr);
RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
if (aOffset < 0) {
return bytes.forget();
}
bool ok = bytes->SetLength(aCount, fallible);
NS_ENSURE_TRUE(ok, nullptr);
char* curr = reinterpret_cast<char*>(bytes->Elements());
const char* start = curr;
while (aCount > 0) {
uint32_t bytesRead;
nsresult rv = mResource->ReadAt(aOffset, curr, aCount, &bytesRead);
NS_ENSURE_SUCCESS(rv, nullptr);
if (!bytesRead) {
break;
}
aOffset += bytesRead;
if (aOffset < 0) {
// Very unlikely overflow.
break;
}
aCount -= bytesRead;
curr += bytesRead;
}
bytes->SetLength(curr - start);
uint32_t bytesRead = 0;
nsresult rv = mResource->ReadAt(
aOffset, reinterpret_cast<char*>(bytes->Elements()), aCount, &bytesRead);
NS_ENSURE_SUCCESS(rv, nullptr);
bytes->SetLength(bytesRead);
return bytes.forget();
}

Просмотреть файл

@ -121,6 +121,7 @@ EXPORTS += [
'MediaMetadataManager.h',
'MediaMIMETypes.h',
'MediaPrefs.h',
'MediaPromiseDefs.h',
'MediaQueue.h',
'MediaRecorder.h',
'MediaResource.h',

Просмотреть файл

@ -0,0 +1 @@
Cache-Control: no-store

Просмотреть файл

@ -74,6 +74,8 @@ support-files =
bipbop-cenc-videoinit.mp4^headers^
bipbop-cenc-video-10s.mp4
bipbop-cenc-video-10s.mp4^headers^
bipbop_225w_175kbps.mp4
bipbop_225w_175kbps.mp4^headers^
bipbop_225w_175kbps-cenc-audio-key1-1.m4s
bipbop_225w_175kbps-cenc-audio-key1-1.m4s^headers^
bipbop_225w_175kbps-cenc-audio-key1-2.m4s
@ -758,6 +760,8 @@ skip-if = toolkit == 'android' # bug 1149374
skip-if = toolkit == 'android' # bug 1149374
[test_eme_detach_media_keys.html]
skip-if = toolkit == 'android' # bug 1149374
[test_eme_detach_reattach_same_mediakeys_during_playback.html]
skip-if = toolkit == 'android' # bug 1149374
[test_eme_initDataTypes.html]
skip-if = toolkit == 'android' # bug 1149374
[test_eme_missing_pssh.html]
@ -783,6 +787,8 @@ skip-if = toolkit == 'android' # bug 1149374
[test_eme_stream_capture_blocked_case3.html]
tags=msg capturestream
skip-if = toolkit == 'android' # bug 1149374
[test_eme_unsetMediaKeys_then_capture.html]
skip-if = toolkit == 'android' # bug 1149374
[test_eme_waitingforkey.html]
skip-if = toolkit == 'android' # bug 1149374
[test_empty_resource.html]

Просмотреть файл

@ -0,0 +1,144 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test Encrypted Media Extensions</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="manifest.js"></script>
<script type="text/javascript" src="http://test1.mochi.test:8888/tests/dom/media/test/eme.js"></script>
</head>
<body>
<pre id="test">
<video id="v" controls></video>
<script class="testbody" type="text/javascript">
var manager = new MediaTestManager;
var EMEmanifest = [
{
name:"bipbop 10s",
tracks: [
{
name:"video",
type:"video/mp4; codecs=\"avc1.4d4015\"",
fragments:[ "bipbop-cenc-video-10s.mp4",
]
}
],
keys: {
"7e571d037e571d037e571d037e571d11" : "7e5733337e5733337e5733337e573311",
},
sessionType:"temporary",
sessionCount:1,
duration:10.01
},
];
function sleep(time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
// To check if playback can be blocked and resumed correctly after
// detaching original mediakeys and reattach it back.
function startTest(test, token)
{
manager.started(token);
var mk_ori;
let finish = new EMEPromise;
let v = document.getElementById("v");
let sessions = [];
function onSessionCreated(session) {
sessions.push(session);
}
function closeSessions() {
let p = new EMEPromise;
Promise.all(sessions.map(s => s.close()))
.then(p.resolve, p.reject);
return p.promise;
}
function setMediaKeysToElement(mk, solve, reject) {
v.setMediaKeys(mk).then(solve, reject);
}
function ReattachOriMediaKeys() {
function onOriMediaKeysSetOK() {
ok(true, TimeStamp(token) + " (ENCRYPTED) Set original MediaKeys back OK!");
}
function onOriMediaKeysSetFailed() {
ok(false, " Failed to set original mediakeys back.");
}
function onCanPlayAgain(ev) {
Promise.all([closeSessions()])
.then(() => {
ok(true, " (ENCRYPTED) Playback can be resumed.");
manager.finished(token);
}, () => {
ok(false, TimeStamp(token) + " Sessions are closed incorrectly.");
manager.finished(token);
});
}
once(v, "canplay", onCanPlayAgain);
setMediaKeysToElement(mk_ori, onOriMediaKeysSetOK, onOriMediaKeysSetFailed)
}
function triggerSeek() {
v.currentTime = v.duration / 2;
}
function onCanPlay(ev) {
function onSetMediaKeysToNullOK() {
ok(true, TimeStamp(token) + " Set MediaKeys to null. OK!");
triggerSeek();
SimpleTest.requestFlakyTimeout("To reattach mediakeys back again in 5s.");
sleep(5000).then(ReattachOriMediaKeys);
}
function onSetMediaKeysToNullFailed() {
ok(false, TimeStamp(token) + " Set MediaKeys to null. FAILED!");
}
SimpleTest.requestFlakyTimeout("To detach mediakeys after receiving 'canplay' event in 2s");
sleep(2000).then(() => {
setMediaKeysToElement(null, onSetMediaKeysToNullOK, onSetMediaKeysToNullFailed);
});
}
once(v, "canplay", onCanPlay);
var p1 = LoadInitData(v, test, token);
var p2 = CreateAndSetMediaKeys(v, test, token);
var p3 = LoadTest(test, v, token);
Promise.all([p1, p2, p3])
.then(values => {
let initData = values[0];
// stash the mediakeys
mk_ori = v.mediaKeys;
initData.map(ev => {
let session = v.mediaKeys.createSession();
onSessionCreated(session);
MakeRequest(test, token, ev, session);
});
})
.then(() => {
return finish.promise;
})
.catch(reason => ok(false, reason))
}
function beginTest() {
manager.runTests(EMEmanifest, startTest);
}
SimpleTest.waitForExplicitFinish();
SetupEMEPref(beginTest);
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,117 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test Encrypted Media Extensions</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="text/javascript" src="manifest.js"></script>
<script type="text/javascript" src="http://test1.mochi.test:8888/tests/dom/media/test/eme.js"></script>
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
var manager = new MediaTestManager;
// Test that if we can capture a video frame while playing clear content after
// removing the MediaKeys object which was used for a previous encrypted content
// playback on the same video element
function startTest(test, token)
{
manager.started(token);
var sessions = [];
function onSessionCreated(session) {
sessions.push(session);
}
function closeSessions() {
let p = new EMEPromise;
Promise.all(sessions.map(s => s.close()))
.then(p.resolve, p.reject);
return p.promise;
}
let v = document.createElement("video");
document.body.appendChild(v);
let finish = new EMEPromise;
function onVideoEnded(ev) {
ok(true, TimeStamp(token) + " (ENCRYPTED) content playback ended.");
v.removeEventListener("ended", onVideoEnded);
function playClearVideo() {
var p1 = once(v, 'ended', (e) => {
ok(true, TimeStamp(token) + " (CLEAR) content playback ended.");
console.log(" bipbop.mp4 playback ended !!");
});
var p2 = once(v, 'loadeddata', (e) => {
ok(true, TimeStamp(token) + " Receiving event 'loadeddata' for (CLEAR) content.");
canvasElem = document.createElement('canvas');
document.body.appendChild(canvasElem);
ctx2d = canvasElem.getContext('2d');
var gotTypeError = false;
try {
ctx2d.drawImage(v, 0, 0);
} catch (e) {
if (e instanceof TypeError) {
gotTypeError = true;
}
}
ok(!gotTypeError, TimeStamp(token) + " Canvas2D context drawImage succeed.")
});
v.src = 'bipbop_225w_175kbps.mp4';
v.play();
Promise.all([p1, p2, closeSessions()]).then(() => {
manager.finished(token);
}, () => {
ok(false, TimeStamp(token) + " Something wrong.");
manager.finished(token);
});
}
Promise.all(sessions.map(s => s.close()))
.then(() => {
v.setMediaKeys(null)
.then(() => {
ok(true, TimeStamp(token) + " Setting MediaKeys to null.");
playClearVideo();
}, () => {
ok(false, TimeStamp(token) + " Setting MediaKeys to null.");
});;
});
}
v.addEventListener("ended", onVideoEnded);
// Create a MediaKeys object and set to HTMLMediaElement then start the playback.
Promise.all([
LoadInitData(v, test, token),
CreateAndSetMediaKeys(v, test, token),
LoadTest(test, v, token)])
.then(values => {
let initData = values[0];
v.play();
initData.map(ev => {
let session = v.mediaKeys.createSession();
onSessionCreated(session);
MakeRequest(test, token, ev, session);
});
})
.then(() => {
return finish.promise;
})
.catch(reason => ok(false, reason))
}
function beginTest() {
manager.runTests(gEMETests, startTest);
}
SimpleTest.waitForExplicitFinish();
SetupEMEPref(beginTest);
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -14,6 +14,7 @@ support-files =
turnConfig.js
sdpUtils.js
addTurnsSelfsignedCert.js
parser_rtp.js
!/dom/canvas/test/captureStream_common.js
!/dom/canvas/test/webgl-mochitest/webgl-util.js
!/dom/media/test/manifest.js

Просмотреть файл

@ -0,0 +1,131 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/*
* Parses an RTP packet
* @param buffer an ArrayBuffer that contains the packet
* @return { type: "rtp", header: {...}, payload: a DataView }
*/
var ParseRtpPacket = (buffer) => {
// DataView.getFooInt returns big endian numbers by default
let view = new DataView(buffer);
// Standard Header Fields
// https://tools.ietf.org/html/rfc3550#section-5.1
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |V=2|P|X| CC |M| PT | sequence number |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | timestamp |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | synchronization source (SSRC) identifier |
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
// | contributing source (CSRC) identifiers |
// | .... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
let header = {};
let offset = 0;
// Note that incrementing the offset happens as close to reading the data as
// possible. This simplifies ensuring that the number of read bytes and the
// offset increment match. Data may be manipulated between when the offset is
// incremented and before the next read.
let byte = view.getUint8(offset);
offset++;
// Version 2 Bit
header.version = (0xC0 & byte) >> 6;
// Padding 1 Bit
header.padding = (0x30 & byte) >> 5
// Extension 1 Bit
header.extensionsPresent = ((0x10 & byte) >> 4) == 1;
// CSRC count 4 Bit
header.csrcCount = (0xF & byte);
byte = view.getUint8(offset);
offset++;
// Marker 1 Bit
header.marker = (0x80 & byte) >> 7;
// Payload Type 7 Bit
header.payloadType = (0x7F & byte);
// Sequence Number 16 Bit
header.sequenceNumber = view.getUint16(offset);
offset += 2;
// Timestamp 32 Bit
header.timestamp = view.getUint32(offset);
offset += 4;
// SSRC 32 Bit
header.ssrc = view.getUint32(offset);
offset += 4;
// CSRC 32 Bit
header.csrcs = [];
for (let c = 0; c < header.csrcCount; c++) {
header.csrcs.push(view.getUint32(offset));
offset += 4;
}
// Extensions
header.extensions = [];
header.extensionPaddingBytes = 0;
header.extensionsTotalLength = 0;
if ( header.extensionsPresent ) {
// https://tools.ietf.org/html/rfc3550#section-5.3.1
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | defined by profile | length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | header extension |
// | .... |
let addExtension = (id, len) => header.extensions.push({
id: id,
data: new DataView(buffer, offset, len),
});
let extensionId = view.getUint16(offset);
offset += 2;
// len is in 32 bit units, not bytes
header.extensionsTotalLength = view.getUint16(offset) * 4;
offset += 2;
// Check for https://tools.ietf.org/html/rfc5285
if (extensionId != 0xBEDE) {
// No rfc5285
addExtension(extensionId, header.extensionsTotalLength);
offset += header.extensionsTotalLength;
} else {
let expectedEnd = offset + header.extensionsTotalLength;
while (offset < expectedEnd) {
// We only support "one-byte" extension headers ATM
// https://tools.ietf.org/html/rfc5285#section-4.2
// 0
// 0 1 2 3 4 5 6 7
// +-+-+-+-+-+-+-+-+
// | ID | len |
// +-+-+-+-+-+-+-+-+
byte = view.getUint8(offset);
offset++;
// Check for padding which can occur between extensions or at the end
if (byte == 0) {
header.extensionPaddingBytes++;
continue;
}
let id = (byte & 0xF0) >> 4;
// Check for the FORBIDDEN id (15), dun dun dun
if (id == 15) {
// Ignore bytes until until the end of extensions
offset = expectedEnd;
break;
}
// the length of the extention is len + 1
let len = (byte & 0x0F) + 1;
addExtension(id, len);
offset += len;
}
}
}
return { type: "rtp", header: header, payload: new DataView(buffer, offset) };
}

Просмотреть файл

@ -2,6 +2,7 @@
<html>
<head>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="parser_rtp.js"></script>
<script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
</head>
<body>
@ -77,6 +78,9 @@
}
]);
// For storing the rid extension so it can be checked in the RTP
let ridExtensionId = 0;
// do this after set local description so the MediaPipeline
// has been created.
test.chain.insertAfter('PC_REMOTE_SET_LOCAL_DESCRIPTION',[
@ -84,6 +88,7 @@
const extmap_id = test.originalOffer.sdp.match(
"a=extmap:([0-9+])/sendonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id");
ok(extmap_id, "Original offer has extmap id for simulcast: " + extmap_id[1]);
ridExtensionId = extmap_id[1];
// Cause pcRemote to filter out everything but RID "foo", only
// allowing one of the simulcast streams through.
addRIDExtension(test.pcRemote, extmap_id[1]);
@ -91,6 +96,30 @@
}
]);
let getRtpPacket = (pc) => {
pc.mozEnablePacketDump(0, "rtp", false);
return new Promise((res, rej) =>
pc.mozSetPacketCallback((...args) => {
res([...args]);
pc.mozSetPacketCallback(() => {});
pc.mozDisablePacketDump(0, "rtp", false);
})
);
}
test.chain.insertBefore('PC_REMOTE_WAIT_FOR_MEDIA_FLOW', [
async function PC_REMOTE_CHECK_RID_IN_RTP() {
let pc = SpecialPowers.wrap(test.pcRemote._pc);
let [level, type, sending, data] = await getRtpPacket(pc);
let extensions = ParseRtpPacket(data).header.extensions;
ok(ridExtensionId, "RID extension ID has been extracted from SDP");
let ridExt = extensions.find(e => e.id == ridExtensionId);
ok(ridExt, "RID is present in RTP.");
is(new TextDecoder('utf-8').decode(ridExt.data), "foo",
"RID is 'foo'.");
}
]);
test.chain.append([
async function PC_REMOTE_WAIT_FOR_FRAMES() {
const vremote = test.pcRemote.remoteMediaElements[0];

Просмотреть файл

@ -85,6 +85,7 @@ AudioTimelineEvent::AudioTimelineEvent(MediaStream* aStream)
#ifdef DEBUG
, mTimeIsInTicks(false)
#endif
, mTime(0.0)
{
}

Просмотреть файл

@ -4361,8 +4361,12 @@ HTMLEditRules::WillOutdent(Selection& aSelection,
OwningNonNull<nsIContent> curNode = *arrayOfNodes[i]->AsContent();
// Here's where we actually figure out what to do
nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
int32_t offset = curParent ? curParent->IndexOf(curNode) : -1;
int32_t offset;
nsCOMPtr<nsINode> curParent =
EditorBase::GetNodeLocation(curNode, &offset);
if (!curParent) {
continue;
}
// Is it a blockquote?
if (curNode->IsHTMLElement(nsGkAtoms::blockquote)) {

Просмотреть файл

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<script>
function jsfuzzer() {
document.execCommand("outdent", false);
}
function eventhandler2() {
document.execCommand("styleWithCSS", false, true);
}
function eventhandler3() {
document.execCommand("delete", false);
var element1 = document.getElementById("element1");
document.getSelection().setPosition(element1, 0);
var element1 = document.getElementById("element2");
element2.ownerDocument.execCommand("insertOrderedList", false);
var element1 = document.getElementById("element3");
element3.addEventListener("DOMSubtreeModified", eventhandler3);
document.activeElement.setAttribute("contenteditable", "true");
}
</script>
</head>
<body onload=jsfuzzer()>
<i id="element1">
<audio i src="x"></audio>
<da id="element2">
<br id="element3" style="">
<svg onload="eventhandler3()">
<animate onbegin="eventhandler2()">
<body>
</html>

Просмотреть файл

@ -93,3 +93,4 @@ load 1402469.html
load 1402904.html
load 1405747.html
load 1408170.html
load 1414581.html

Просмотреть файл

@ -75,5 +75,14 @@ AppendToString(std::stringstream& aStream, wr::ImageRendering aTextureFilter,
aStream << sfx;
}
void
AppendToString(std::stringstream& aStream, wr::LayoutVector2D aVector,
const char* pfx, const char* sfx)
{
aStream << pfx;
aStream << nsPrintfCString("(x=%f, y=%f)", aVector.x, aVector.y).get();
aStream << sfx;
}
} // namespace layers
} // namespace mozilla

Просмотреть файл

@ -21,6 +21,10 @@ void
AppendToString(std::stringstream& aStream, wr::ImageRendering aTextureFilter,
const char* pfx="", const char* sfx="");
void
AppendToString(std::stringstream& aStream, wr::LayoutVector2D aVector,
const char* pfx="", const char* sfx="");
} // namespace layers
} // namespace mozilla

Просмотреть файл

@ -811,11 +811,13 @@ DisplayListBuilder::DefineStickyFrame(const wr::LayoutRect& aContentRect,
const float* aBottomMargin,
const float* aLeftMargin,
const StickyOffsetBounds& aVerticalBounds,
const StickyOffsetBounds& aHorizontalBounds)
const StickyOffsetBounds& aHorizontalBounds,
const wr::LayoutVector2D& aAppliedOffset)
{
uint64_t id = wr_dp_define_sticky_frame(mWrState, aContentRect, aTopMargin,
aRightMargin, aBottomMargin, aLeftMargin, aVerticalBounds, aHorizontalBounds);
WRDL_LOG("DefineSticky id=%" PRIu64 " c=%s t=%s r=%s b=%s l=%s v=%s h=%s\n",
aRightMargin, aBottomMargin, aLeftMargin, aVerticalBounds, aHorizontalBounds,
aAppliedOffset);
WRDL_LOG("DefineSticky id=%" PRIu64 " c=%s t=%s r=%s b=%s l=%s v=%s h=%s a=%s\n",
mWrState, id,
Stringify(aContentRect).c_str(),
aTopMargin ? Stringify(*aTopMargin).c_str() : "none",
@ -823,7 +825,8 @@ DisplayListBuilder::DefineStickyFrame(const wr::LayoutRect& aContentRect,
aBottomMargin ? Stringify(*aBottomMargin).c_str() : "none",
aLeftMargin ? Stringify(*aLeftMargin).c_str() : "none",
Stringify(aVerticalBounds).c_str(),
Stringify(aHorizontalBounds).c_str());
Stringify(aHorizontalBounds).c_str(),
Stringify(aAppliedOffset).c_str());
return wr::WrStickyId { id };
}

Просмотреть файл

@ -247,7 +247,8 @@ public:
const float* aBottomMargin,
const float* aLeftMargin,
const StickyOffsetBounds& aVerticalBounds,
const StickyOffsetBounds& aHorizontalBounds);
const StickyOffsetBounds& aHorizontalBounds,
const wr::LayoutVector2D& aAppliedOffset);
void PushStickyFrame(const wr::WrStickyId& aStickyId,
const DisplayItemClipChain* aParent);
void PopStickyFrame(const DisplayItemClipChain* aParent);

Просмотреть файл

@ -1331,7 +1331,8 @@ pub extern "C" fn wr_dp_define_sticky_frame(state: &mut WrState,
bottom_margin: *const f32,
left_margin: *const f32,
vertical_bounds: StickyOffsetBounds,
horizontal_bounds: StickyOffsetBounds)
horizontal_bounds: StickyOffsetBounds,
applied_offset: LayoutVector2D)
-> u64 {
assert!(unsafe { is_in_main_thread() });
let clip_id = state.frame_builder.dl_builder.define_sticky_frame(
@ -1341,8 +1342,7 @@ pub extern "C" fn wr_dp_define_sticky_frame(state: &mut WrState,
unsafe { bottom_margin.as_ref() }.cloned(),
unsafe { left_margin.as_ref() }.cloned()
),
vertical_bounds, horizontal_bounds,
LayoutVector2D::new(0.0, 0.0));
vertical_bounds, horizontal_bounds, applied_offset);
match clip_id {
ClipId::Clip(id, pipeline_id) => {
assert!(pipeline_id == state.pipeline_id);

Просмотреть файл

@ -546,6 +546,21 @@ struct StickyOffsetBounds {
}
};
// A 2d Vector tagged with a unit.
struct TypedVector2D_f32__LayerPixel {
float x;
float y;
bool operator==(const TypedVector2D_f32__LayerPixel& aOther) const {
return x == aOther.x &&
y == aOther.y;
}
};
typedef TypedVector2D_f32__LayerPixel LayerVector2D;
typedef LayerVector2D LayoutVector2D;
struct BorderWidths {
float left;
float top;
@ -626,21 +641,6 @@ struct NinePatchDescriptor {
}
};
// A 2d Vector tagged with a unit.
struct TypedVector2D_f32__LayerPixel {
float x;
float y;
bool operator==(const TypedVector2D_f32__LayerPixel& aOther) const {
return x == aOther.x &&
y == aOther.y;
}
};
typedef TypedVector2D_f32__LayerPixel LayerVector2D;
typedef LayerVector2D LayoutVector2D;
struct Shadow {
LayoutVector2D offset;
ColorF color;
@ -1078,7 +1078,8 @@ uint64_t wr_dp_define_sticky_frame(WrState *aState,
const float *aBottomMargin,
const float *aLeftMargin,
StickyOffsetBounds aVerticalBounds,
StickyOffsetBounds aHorizontalBounds)
StickyOffsetBounds aHorizontalBounds,
LayoutVector2D aAppliedOffset)
WR_FUNC;
WR_INLINE

Просмотреть файл

@ -43,7 +43,6 @@ if os_win:
'src/base/platform_file_win.cc',
'src/base/platform_thread_win.cc',
'src/base/process_util_win.cc',
'src/base/process_win.cc',
'src/base/rand_util_win.cc',
'src/base/shared_memory_win.cc',
'src/base/sys_info_win.cc',
@ -70,7 +69,6 @@ if os_posix:
'src/base/message_pump_libevent.cc',
'src/base/platform_file_posix.cc',
'src/base/platform_thread_posix.cc',
'src/base/process_posix.cc',
'src/base/process_util_posix.cc',
'src/base/rand_util_posix.cc',
'src/base/shared_memory_posix.cc',

Просмотреть файл

@ -28,54 +28,6 @@ typedef pid_t ProcessHandle;
typedef pid_t ProcessId;
#endif
class Process {
public:
Process() : process_(0), last_working_set_size_(0) {}
explicit Process(ProcessHandle aHandle) :
process_(aHandle), last_working_set_size_(0) {}
// A handle to the current process.
static Process Current();
// Get/Set the handle for this process. The handle will be 0 if the process
// is no longer running.
ProcessHandle handle() const { return process_; }
void set_handle(ProcessHandle aHandle) { process_ = aHandle; }
// Get the PID for this process.
ProcessId pid() const;
// Is the this process the current process.
bool is_current() const;
// Close the process handle. This will not terminate the process.
void Close();
// Terminates the process with extreme prejudice. The given result code will
// be the exit code of the process. If the process has already exited, this
// will do nothing.
void Terminate(int result_code);
// A process is backgrounded when it's priority is lower than normal.
// Return true if this process is backgrounded, false otherwise.
bool IsProcessBackgrounded() const;
// Set a prcess as backgrounded. If value is true, the priority
// of the process will be lowered. If value is false, the priority
// of the process will be made "normal" - equivalent to default
// process priority.
// Returns true if the priority was changed, false otherwise.
bool SetProcessBackgrounded(bool value);
// Releases as much of the working set back to the OS as possible.
// Returns true if successful, false otherwise.
bool EmptyWorkingSet();
private:
ProcessHandle process_;
size_t last_working_set_size_;
};
} // namespace base
#endif // BASE_PROCESS_H_

Просмотреть файл

@ -1,62 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
// Copyright (c) 2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/process.h"
#include "base/process_util.h"
namespace base {
void Process::Close() {
process_ = 0;
// if the process wasn't termiated (so we waited) or the state
// wasn't already collected w/ a wait from process_utils, we're gonna
// end up w/ a zombie when it does finally exit.
}
void Process::Terminate(int result_code) {
// result_code isn't supportable.
if (!process_)
return;
// We don't wait here. It's the responsibility of other code to reap the
// child.
KillProcess(process_, result_code, false);
}
bool Process::IsProcessBackgrounded() const {
// http://code.google.com/p/chromium/issues/detail?id=8083
return false;
}
bool Process::SetProcessBackgrounded(bool value) {
// http://code.google.com/p/chromium/issues/detail?id=8083
// Just say we did it to keep renderer happy at the moment. Need to finish
// cleaning this up w/in higher layers since windows is probably the only
// one that can raise priorities w/o privileges.
return true;
}
bool Process::EmptyWorkingSet() {
// http://code.google.com/p/chromium/issues/detail?id=8083
return false;
}
ProcessId Process::pid() const {
if (process_ == 0)
return 0;
return GetProcId(process_);
}
bool Process::is_current() const {
return process_ == GetCurrentProcessHandle();
}
// static
Process Process::Current() {
return Process(GetCurrentProcessHandle());
}
} // namspace base

Просмотреть файл

@ -1,64 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/process.h"
#include "base/logging.h"
#include "base/process_util.h"
namespace base {
void Process::Close() {
if (!process_)
return;
CloseProcessHandle(process_);
process_ = NULL;
}
void Process::Terminate(int result_code) {
if (!process_)
return;
::TerminateProcess(process_, result_code);
}
bool Process::IsProcessBackgrounded() const {
DCHECK(process_);
DWORD priority = GetPriorityClass(process_);
if (priority == 0)
return false; // Failure case.
return priority == BELOW_NORMAL_PRIORITY_CLASS;
}
bool Process::SetProcessBackgrounded(bool value) {
DCHECK(process_);
DWORD priority = value ? BELOW_NORMAL_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS;
return (SetPriorityClass(process_, priority) != 0);
}
bool Process::EmptyWorkingSet() {
if (!process_)
return false;
BOOL rv = SetProcessWorkingSetSize(process_, -1, -1);
return rv == TRUE;
}
ProcessId Process::pid() const {
if (process_ == 0)
return 0;
return GetProcId(process_);
}
bool Process::is_current() const {
return process_ == GetCurrentProcess();
}
// static
Process Process::Current() {
return Process(GetCurrentProcess());
}
} // namespace base

Просмотреть файл

@ -628,7 +628,7 @@ GeckoChildProcessHost::PerformAsyncLaunchInternal(std::vector<std::string>& aExt
// send the child the PID so that it can open a ProcessHandle back to us.
// probably don't want to do this in the long run
char pidstring[32];
SprintfLiteral(pidstring,"%d", base::Process::Current().pid());
SprintfLiteral(pidstring, "%d", base::GetCurrentProcId());
const char* const childProcessType =
XRE_ChildProcessTypeToString(mProcessType);

Просмотреть файл

@ -834,7 +834,7 @@ case "$target" in
if $CC -E - -dM </dev/null | grep __ELF__ >/dev/null; then
DSO_PIC_CFLAGS='-fPIC -DPIC'
DSO_LDOPTS='-shared'
BIN_FLAGS='-Wl,--export-dynamic'
MOZ_PROGRAM_LDFLAGS="$MOZ_PROGRAM_LDFLAGS -Wl,--export-dynamic"
else
DSO_PIC_CFLAGS='-fPIC -DPIC'
DSO_LDOPTS='-shared'
@ -1959,7 +1959,6 @@ AC_SUBST(RCFLAGS)
AC_SUBST(WINDRES)
AC_SUBST(IMPLIB)
AC_SUBST(FILTER)
AC_SUBST(BIN_FLAGS)
AC_SUBST_LIST(MOZ_DEBUG_LDFLAGS)
AC_SUBST(WARNINGS_AS_ERRORS)
AC_SUBST(LIBICONV)
@ -2042,7 +2041,6 @@ AC_SUBST(HOST_BIN_SUFFIX)
AC_SUBST(TARGET_XPCOM_ABI)
AC_SUBST(WRAP_LDFLAGS)
AC_SUBST(MKSHLIB)
AC_SUBST(MKCSHLIB)
AC_SUBST_LIST(DSO_CFLAGS)

Просмотреть файл

@ -7072,8 +7072,7 @@ nsCSSFrameConstructor::GetInsertionPrevSibling(InsertionPoint* aInsertion,
iter.Seek(aEndSkipChild);
iter.GetPreviousChild();
}
nsIFrame* nextSibling = FindNextSibling(iter, childDisplay);
if (nextSibling) {
if (nsIFrame* nextSibling = FindNextSibling(iter, childDisplay)) {
aInsertion->mParentFrame = nextSibling->GetParent()->GetContentInsertionFrame();
} else {
// No previous or next sibling, so treat this like an appended frame.
@ -7085,19 +7084,13 @@ nsCSSFrameConstructor::GetInsertionPrevSibling(InsertionPoint* aInsertion,
aInsertion->mParentFrame =
GetLastIBSplitSibling(aInsertion->mParentFrame, false);
}
// Get continuation that parents the last child. This MUST be done
// before the AdjustAppendParentForAfterContent call.
// Get continuation that parents the last child.
aInsertion->mParentFrame =
nsLayoutUtils::LastContinuationWithChild(aInsertion->mParentFrame);
// Deal with fieldsets
aInsertion->mParentFrame =
::GetAdjustedParentFrame(aInsertion->mParentFrame, aChild);
nsIFrame* appendAfterFrame;
aInsertion->mParentFrame =
::AdjustAppendParentForAfterContent(this, aInsertion->mContainer,
aInsertion->mParentFrame,
aChild, &appendAfterFrame);
prevSibling = ::FindAppendPrevSibling(aInsertion->mParentFrame, appendAfterFrame);
prevSibling = ::FindAppendPrevSibling(aInsertion->mParentFrame, nullptr);
}
}

Просмотреть файл

@ -179,7 +179,7 @@ load 398332-1.html
load 398332-2.html
asserts(0-2) load 398332-3.html # bug 436123 and bug 457397
load 399407-1.xhtml
load 399412-1.html
asserts(1) load 399412-1.html # bug 574889
load 399843-1.html
load 400078-1.html
load 400190.html
@ -575,7 +575,7 @@ load 1003441.xul
pref(layout.css.grid.enabled,true) load 1015562.html
asserts(1-2) load 1015563-1.html
asserts(1-2) load 1015563-2.html
asserts(543) asserts-if(stylo&&Android,274) load 1015844.html # bug 574889, bug 1374479
asserts(11) asserts-if(stylo&&Android,274) load 1015844.html # bug 574889, bug 1374479
pref(font.size.inflation.minTwips,200) load 1032450.html
load 1032613-1.svg
load 1032613-2.html

Просмотреть файл

@ -7492,10 +7492,9 @@ nsBlockFrame::ComputeFinalBSize(const ReflowInput& aReflowInput,
aBorderPadding.BEnd(wm));
if (aStatus->IsIncomplete() &&
aFinalSize.BSize(wm) < aReflowInput.AvailableBSize()) {
// We fit in the available space - change status to OVERFLOW_INCOMPLETE.
// XXXmats why didn't Reflow report OVERFLOW_INCOMPLETE in the first place?
// XXXmats and why exclude the case when our size == AvailableBSize?
aFinalSize.BSize(wm) <= aReflowInput.AvailableBSize()) {
// We ran out of height on this page but we're incomplete.
// Set status to complete except for overflow.
aStatus->SetOverflowIncomplete();
}

Просмотреть файл

@ -7317,6 +7317,23 @@ nsDisplayStickyPosition::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder
nsDisplayListBuilder* aDisplayListBuilder)
{
StickyScrollContainer* stickyScrollContainer = StickyScrollContainer::GetStickyScrollContainerForFrame(mFrame);
if (stickyScrollContainer) {
// If there's no ASR for the scrollframe that this sticky item is attached
// to, then don't create a WR sticky item for it either. Trying to do so
// will end in sadness because WR will interpret some coordinates as
// relative to the nearest enclosing scrollframe, which will correspond
// to the nearest ancestor ASR on the gecko side. That ASR will not be the
// same as the scrollframe this sticky item is actually supposed to be
// attached to, thus the sadness.
// Not sending WR the sticky item is ok, because the enclosing scrollframe
// will never be asynchronously scrolled. Instead we will always position
// the sticky items correctly on the gecko side and WR will never need to
// adjust their position itself.
if (!stickyScrollContainer->ScrollFrame()->MayBeAsynchronouslyScrolled()) {
stickyScrollContainer = nullptr;
}
}
if (stickyScrollContainer) {
float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
@ -7329,6 +7346,7 @@ nsDisplayStickyPosition::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder
Maybe<float> leftMargin;
wr::StickyOffsetBounds vBounds = { 0.0, 0.0 };
wr::StickyOffsetBounds hBounds = { 0.0, 0.0 };
nsPoint appliedOffset;
nsRect outer;
nsRect inner;
@ -7364,17 +7382,24 @@ nsDisplayStickyPosition::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder
// -distance works.
nscoord distance = DistanceToRange(inner.YMost(), outer.YMost());
topMargin = Some(NSAppUnitsToFloatPixels(itemBounds.y - scrollPort.y - distance, auPerDevPixel));
// Question: Given the current state, what is the range during which
// WR will have to apply an adjustment to the item (in order to prevent
// the item from visually moving) as a result of async scrolling?
// Answer: [inner.YMost(), outer.YMost()]. But right now the WR API
// doesn't allow us to provide the whole range; it just takes one side
// of the range and assumes it has a particular sign. Bug 1411627 will
// fix this more completely but for now we do the best we can. Note that
// this value also needs to be converted from being relative to the
// scrollframe to being relative to the reference frame, so we have to
// adjust it by |offset|.
vBounds.max = NSAppUnitsToFloatPixels(std::max(0, outer.YMost() + offset.y), auPerDevPixel);
// Question: What is the maximum positive ("downward") offset that WR
// will have to apply to this item in order to prevent the item from
// visually moving?
// Answer: Since the item is "sticky" in the range [inner.YMost(), outer.YMost()],
// the maximum offset will be the size of the range, which is
// outer.YMost() - inner.YMost().
vBounds.max = NSAppUnitsToFloatPixels(outer.YMost() - inner.YMost(), auPerDevPixel);
// Question: how much of an offset has layout already applied to the item?
// Answer: if we are
// (a) inside the sticky range (inner.YMost() < 0 <= outer.YMost()), or
// (b) past the sticky range (inner.YMost() < outer.YMost() < 0)
// then layout has already applied some offset to the position of the
// item. The amount of the adjustment is |0 - inner.YMost()| in case (a)
// and |outer.YMost() - inner.YMost()| in case (b).
if (inner.YMost() < 0) {
appliedOffset.y = std::min(0, outer.YMost()) - inner.YMost();
MOZ_ASSERT(appliedOffset.y > 0);
}
}
if (outer.y != inner.y) {
// Similar logic as in the previous section, but this time we care about
@ -7383,25 +7408,44 @@ nsDisplayStickyPosition::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder
bottomMargin = Some(NSAppUnitsToFloatPixels(scrollPort.YMost() - itemBounds.YMost() + distance, auPerDevPixel));
// And here WR will be moving the item upwards rather than downwards so
// again things are inverted from the previous block.
vBounds.min = NSAppUnitsToFloatPixels(std::min(0, outer.y + offset.y), auPerDevPixel);
vBounds.min = NSAppUnitsToFloatPixels(outer.y - inner.y, auPerDevPixel);
// We can't have appliedOffset be both positive and negative, and the top
// adjustment takes priority. So here we only update appliedOffset.y if
// it wasn't set by the top-sticky case above.
if (appliedOffset.y == 0 && inner.y > 0) {
appliedOffset.y = std::max(0, outer.y) - inner.y;
MOZ_ASSERT(appliedOffset.y < 0);
}
}
// Same as above, but for the x-axis
if (outer.XMost() != inner.XMost()) {
nscoord distance = DistanceToRange(inner.XMost(), outer.XMost());
leftMargin = Some(NSAppUnitsToFloatPixels(itemBounds.x - scrollPort.x - distance, auPerDevPixel));
hBounds.max = NSAppUnitsToFloatPixels(std::max(0, outer.XMost() + offset.x), auPerDevPixel);
hBounds.max = NSAppUnitsToFloatPixels(outer.XMost() - inner.XMost(), auPerDevPixel);
if (inner.XMost() < 0) {
appliedOffset.x = std::min(0, outer.XMost()) - inner.XMost();
MOZ_ASSERT(appliedOffset.x > 0);
}
}
if (outer.x != inner.x) {
nscoord distance = DistanceToRange(outer.x, inner.x);
rightMargin = Some(NSAppUnitsToFloatPixels(scrollPort.XMost() - itemBounds.XMost() + distance, auPerDevPixel));
hBounds.min = NSAppUnitsToFloatPixels(std::min(0, outer.x + offset.x), auPerDevPixel);
hBounds.min = NSAppUnitsToFloatPixels(outer.x - inner.x, auPerDevPixel);
if (appliedOffset.x == 0 && inner.x > 0) {
appliedOffset.x = std::max(0, outer.x) - inner.x;
MOZ_ASSERT(appliedOffset.x < 0);
}
}
LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(itemBounds, auPerDevPixel);
wr::LayoutVector2D applied = {
NSAppUnitsToFloatPixels(appliedOffset.x, auPerDevPixel),
NSAppUnitsToFloatPixels(appliedOffset.y, auPerDevPixel)
};
wr::WrStickyId id = aBuilder.DefineStickyFrame(aSc.ToRelativeLayoutRect(bounds),
topMargin.ptrOr(nullptr), rightMargin.ptrOr(nullptr),
bottomMargin.ptrOr(nullptr), leftMargin.ptrOr(nullptr),
vBounds, hBounds);
vBounds, hBounds, applied);
aBuilder.PushStickyFrame(id, GetClipChain());
}

Просмотреть файл

@ -28,6 +28,9 @@ fuzzy-if(skiaContent,2,240000) fuzzy-if(browserIsRemote&&!skiaContent&&(cocoaWid
skip-if(!asyncPan) == sticky-pos-scrollable-1.html sticky-pos-scrollable-1-ref.html
skip-if(!asyncPan) == sticky-pos-scrollable-2.html sticky-pos-scrollable-2-ref.html
skip-if(!asyncPan) == sticky-pos-scrollable-3.html sticky-pos-scrollable-3-ref.html
skip-if(!asyncPan) == sticky-pos-scrollable-4.html sticky-pos-scrollable-4-ref.html
skip-if(!asyncPan) == sticky-pos-scrollable-5.html sticky-pos-scrollable-5-ref.html
skip-if(!asyncPan) == sticky-pos-scrollable-6.html sticky-pos-scrollable-6-ref.html
skip-if(!asyncPan) == fixed-pos-scrollable-1.html fixed-pos-scrollable-1-ref.html
skip-if(!asyncPan) == culling-1.html culling-1-ref.html
skip-if(!asyncPan) == position-fixed-iframe-1.html position-fixed-iframe-1-ref.html

Просмотреть файл

@ -0,0 +1,7 @@
<!DOCTYPE HTML>
<html>
<body>
<div style="width:400px; height:300px; overflow:hidden; border:2px solid black">
<div style="height: 150px"></div>
<div style="width: 100px; height: 100px; background-color: green"></div>
</div>

Просмотреть файл

@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<html reftest-async-scroll>
<body>
<div style="width:400px; height:300px; overflow:hidden; border:2px solid black"
reftest-displayport-x="0" reftest-displayport-y="0"
reftest-displayport-w="800" reftest-displayport-h="2000"
reftest-async-scroll-x="0" reftest-async-scroll-y="100">
<!-- In this test the position:sticky element has already been adjusted by
the main thread paint, and the async scroll causes a reduction in that
adjustment. -->
<div style="height: 1000px"></div>
<div style="position:sticky; width: 100px; height: 100px; bottom: 50px; background-color: green"></div>
<div style="height: 1000px"></div>
</div>

Просмотреть файл

@ -0,0 +1,7 @@
<!DOCTYPE HTML>
<html>
<body>
<div style="width:400px; height:300px; overflow:hidden; border:2px solid black">
<div style="height: 150px"></div>
<div style="width: 100px; height: 100px; background-color: green"></div>
</div>

Просмотреть файл

@ -0,0 +1,16 @@
<!DOCTYPE HTML>
<html reftest-async-scroll>
<body>
<div style="width:400px; height:300px; overflow:hidden; border:2px solid black"
reftest-displayport-x="0" reftest-displayport-y="0"
reftest-displayport-w="800" reftest-displayport-h="2000"
reftest-async-scroll-x="0" reftest-async-scroll-y="150">
<!-- In this test the position:sticky element has already been adjusted by
the main thread paint, and then clamped by the bounding container. The
async scroll causes a reduction in that adjustment, but by less than
the amount of the scroll (because of the clamping). -->
<div style="height: 500px; margin-top: 250px; margin-bottom: 250px">
<div style="width: 100px; height: 300px"></div>
<div style="position:sticky; width: 100px; height: 100px; bottom: 50px; background-color: green"></div>
</div>
</div>

Просмотреть файл

@ -0,0 +1,7 @@
<!DOCTYPE HTML>
<html>
<body>
<div style="width:400px; height:300px; overflow:hidden; border:2px solid black">
<div style="height: 151px"></div>
<div style="width: 100px; height: 100px; background-color: green"></div>
</div>

Просмотреть файл

@ -0,0 +1,13 @@
<!DOCTYPE HTML>
<html reftest-async-scroll>
<body>
<div style="width:400px; height:300px; overflow:hidden; border:2px solid black"
reftest-displayport-x="0" reftest-displayport-y="0"
reftest-displayport-w="800" reftest-displayport-h="2000"
reftest-async-scroll-x="0" reftest-async-scroll-y="300">
<!-- In this test the position:sticky element is over-constrained with the
top-positioning taking priority over the bottom-positioning. -->
<div style="height: 300px"></div>
<div style="position:sticky; width: 100px; height: 100px; top: 151px; bottom: 50px; background-color: green"></div>
<div style="height: 1000px"></div>
</div>

Просмотреть файл

@ -68,12 +68,14 @@ i { display:block; height:10px; margin-top:7px; background:blue; }
</div>
</div></div>
<!-- bug 1415301
<div class="columns" style="height: 140px/*trigger IsInlineBreakBefore() for the item*/">
<div style="padding-top:30px; background:grey">
<div class="grid">
<span style="grid-row:span 3"><x></x></span>
<span style="grid-row:span 2"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><x></x></span></div>
</div></div>
-->
<div class="columns" style="height: 100px/*trigger IsInlineBreakBefore() for the item*/">
<div style="padding-top:1px; background:grey">

Просмотреть файл

@ -67,12 +67,14 @@ i { display:block; height:10px; margin-top:7px; background:blue; }
</div>
</div></div>
<!-- bug 1415301
<div class="columns" style="height: 140px/*trigger IsInlineBreakBefore() for the item*/">
<div style="padding-top:30px; background:grey">
<div class="grid">
<span style="grid-row:span 3"><x></x></span>
<span class="avoid-break" style="grid-row:span 2"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><x></x></span></div>
</div></div>
-->
<div class="columns" style="height: 100px/*trigger IsInlineBreakBefore() for the item*/">
<div style="padding-top:1px; background:grey">

Просмотреть файл

@ -142,7 +142,6 @@ i { display:block; height:10px; margin-top:7px; background:blue; }
</div>
<div class="grid" style="grid-template-rows: 0; grid-gap:0; border-width:0 5px">
<span class=m style="grid-column:2; border-width:0 1px; height:0"><i style="height:6px;margin:0"></i></span>
<span class=b style="grid-column:3; height:0"></span>
</div>
</div></div>

Просмотреть файл

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html class="reftest-paged">
<title>
Reference
</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
position: absolute;
left: 0; right: 0;
height: 100%;
}
div {
border: solid orange 10px;
}
div + div {
border: solid transparent 20px;
}
div > div {
border: solid gray 10px;
height: 300%;
}
</style>
<div></div>
<div>
<div></div>
</div>

Просмотреть файл

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html class="reftest-paged">
<title>
Overflowing content does not affect whether a fixed-height box fits on a page,
but does get printed on the next page.
</title>
<meta name="flags" content="may print">
<link rel="match" href="moz-block-fragmentation-001-ref.html">
<link rel="help" href="https://www.w3.org/TR/css-break-3/#parallel-flows">
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
height: 100%;
}
body {
border: solid orange 10px;
padding: 10px;
}
div {
border: solid gray 10px;
height: 300%;
}
</style>
<div></div>

Просмотреть файл

@ -0,0 +1 @@
== moz-block-fragmentation-001.html moz-block-fragmentation-001-ref.html

Просмотреть файл

@ -16,6 +16,9 @@ include css21/reftest.list
# Backgrounds and Borders
include background/reftest.list
# Fragmentation
include break3/reftest.list
# Color Level 4
include color4/reftest.list

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше