зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to inbound
This commit is contained in:
Коммит
7e0d52f50e
16
.cron.yml
16
.cron.yml
|
@ -10,14 +10,12 @@ jobs:
|
|||
target-tasks-method: nightly_linux
|
||||
run-on-projects:
|
||||
- mozilla-central
|
||||
- mozilla-aurora
|
||||
- date
|
||||
when:
|
||||
by-project:
|
||||
# Match buildbot starts for now
|
||||
date: [{hour: 15, minute: 0}]
|
||||
mozilla-central: [{hour: 10, minute: 0}]
|
||||
mozilla-aurora: [] # bug 1358976
|
||||
# No default
|
||||
|
||||
- name: nightly-desktop-osx
|
||||
|
@ -51,14 +49,12 @@ jobs:
|
|||
target-tasks-method: nightly_fennec
|
||||
run-on-projects:
|
||||
- mozilla-central
|
||||
- mozilla-aurora
|
||||
- date
|
||||
when:
|
||||
by-project:
|
||||
# Match buildbot starts for now
|
||||
date: [{hour: 15, minute: 0}]
|
||||
mozilla-central: [{hour: 10, minute: 0}]
|
||||
mozilla-aurora: [] # bug 1358976
|
||||
# No default
|
||||
|
||||
- name: nightly-mochitest-valgrind
|
||||
|
@ -71,3 +67,15 @@ jobs:
|
|||
when:
|
||||
- {hour: 16, minute: 0}
|
||||
- {hour: 4, minute: 0}
|
||||
|
||||
- name: nightly-dmd
|
||||
job:
|
||||
type: decision-task
|
||||
treeherder-symbol: Ndmd
|
||||
target-tasks-method: nightly_dmd
|
||||
run-on-projects:
|
||||
- mozilla-central
|
||||
when:
|
||||
by-project:
|
||||
mozilla-central: [{hour: 10, minute: 0}]
|
||||
# No default
|
||||
|
|
|
@ -1704,6 +1704,8 @@ pref("browser.onboarding.hidden", false);
|
|||
// So use `browser.onboarding.notification.finished` to let the AS page know
|
||||
// if our notification is finished and safe to show their snippet.
|
||||
pref("browser.onboarding.notification.finished", false);
|
||||
pref("browser.onboarding.newtour", "private,addons,customize,search,default,sync");
|
||||
pref("browser.onboarding.updatetour", "");
|
||||
|
||||
// Preferences for the Screenshots feature:
|
||||
// Temporarily disable Screenshots in Beta & Release, so that we can gradually
|
||||
|
|
|
@ -23,6 +23,14 @@ registerCleanupFunction(function() {
|
|||
Services.prefs.clearUserPref("browser.rights." + gRightsVersion + ".shown");
|
||||
});
|
||||
|
||||
add_task(async function() {
|
||||
// The onboarding tour's notification would affect tests
|
||||
// and it isn't out test target so make sure disabling it
|
||||
// before any tests start.
|
||||
await promiseDisableOnboardingTours();
|
||||
is(false, Services.prefs.getBoolPref("browser.onboarding.enabled"), "Should disable the onboarding tours");
|
||||
});
|
||||
|
||||
add_task(async function() {
|
||||
info("Check that clearing cookies does not clear storage");
|
||||
|
||||
|
|
|
@ -829,3 +829,7 @@ function getCertExceptionDialog(aLocation) {
|
|||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function promiseDisableOnboardingTours() {
|
||||
return SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", false]]});
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ function promiseTourNotificationOpened(browser) {
|
|||
return ContentTask.spawn(browser, {}, function() {
|
||||
return new Promise(resolve => {
|
||||
let bar = content.document.querySelector("#onboarding-notification-bar");
|
||||
if (bar && bar.classList.contains("onboarding-opened") && bar.dataset.cssTransition == "end") {
|
||||
if (bar && bar.classList.contains("onboarding-opened")) {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -130,8 +130,6 @@ var whitelist = [
|
|||
{file: "chrome://global/content/findUtils.js"},
|
||||
// Bug 1343843
|
||||
{file: "chrome://global/content/url-classifier/unittests.xul"},
|
||||
// Bug 1343839
|
||||
{file: "chrome://global/locale/headsUpDisplay.properties"},
|
||||
// Bug 1348362
|
||||
{file: "chrome://global/skin/icons/warning-64.png", platforms: ["linux", "win"]},
|
||||
// Bug 1348525
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
ac_add_options --enable-dmd
|
||||
|
||||
. "$topsrcdir/browser/config/mozconfigs/linux32/nightly"
|
|
@ -0,0 +1,3 @@
|
|||
ac_add_options --enable-dmd
|
||||
|
||||
. "$topsrcdir/browser/config/mozconfigs/linux64/nightly"
|
|
@ -0,0 +1,3 @@
|
|||
ac_add_options --enable-dmd
|
||||
|
||||
. "$topsrcdir/browser/config/mozconfigs/macosx64/nightly"
|
|
@ -0,0 +1,3 @@
|
|||
ac_add_options --enable-dmd
|
||||
|
||||
. "$topsrcdir/browser/config/mozconfigs/win32/nightly"
|
|
@ -0,0 +1,3 @@
|
|||
ac_add_options --enable-dmd
|
||||
|
||||
. "$topsrcdir/browser/config/mozconfigs/win64/nightly"
|
|
@ -14,6 +14,10 @@ We would apply some rules:
|
|||
* All styles and ids should be formated as `onboarding-*` to avoid conflict with the origin page.
|
||||
* All strings in `locales` should be formated as `onboarding.*` for consistency.
|
||||
|
||||
## How to change the order of tours
|
||||
|
||||
Edit `browser/app/profile/firefox.js` and modify `browser.onboarding.newtour` for the new user tour or `browser.onboarding.updatetour` for the update user tour. You can change the tour list and the order by concate `tourIds` with `,` sign. You can find available `tourId` from `onboardingTourset` in `onboarding.js`.
|
||||
|
||||
## How to pump tour set version after update tours
|
||||
|
||||
The tourset version is used to track the last major tourset change version. The `tourset-version` pref store the major tourset version (ex: `1`) but not the current browser version. When browser update to the next version (ex: 58, 59) the tourset pref is still `1` if we didn't do any major tourset update.
|
||||
|
|
|
@ -353,6 +353,7 @@
|
|||
}
|
||||
|
||||
#onboarding-notification-bar.onboarding-opened {
|
||||
transition: none;
|
||||
transform: translateY(0px);
|
||||
}
|
||||
|
||||
|
@ -373,6 +374,7 @@
|
|||
|
||||
#onboarding-notification-icon::after {
|
||||
--height: 22px;
|
||||
--vpadding: 3px;
|
||||
content: attr(data-tooltip);
|
||||
background: #5ce6e6;
|
||||
position: absolute;
|
||||
|
@ -380,11 +382,11 @@
|
|||
offset-inline-start: 68px;
|
||||
color: #10404a;
|
||||
font-size: 12px;
|
||||
min-height: var(--height);
|
||||
line-height: var(--height);
|
||||
line-height: calc(var(--height) - var(--vpadding) * 2);
|
||||
max-width: 90px;
|
||||
border-radius: calc(var(--height) / 2);
|
||||
border: 1px solid #fff;
|
||||
padding: 0 10px;
|
||||
padding: var(--vpadding) 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
@ -427,7 +429,7 @@
|
|||
}
|
||||
|
||||
#onboarding-notification-tour-icon {
|
||||
width: 64px;
|
||||
min-width: 64px;
|
||||
height: 64px;
|
||||
background-size: 64px;
|
||||
background-repeat: no-repeat;
|
||||
|
|
|
@ -22,7 +22,7 @@ const BRAND_SHORT_NAME = Services.strings
|
|||
|
||||
/**
|
||||
* Add any number of tours, following the format
|
||||
* {
|
||||
* "tourId": { // The short tour id which could be saved in pref
|
||||
* // The unique tour id
|
||||
* id: "onboarding-tour-addons",
|
||||
* // The string id of tour name which would be displayed on the navigation bar
|
||||
|
@ -40,8 +40,8 @@ const BRAND_SHORT_NAME = Services.strings
|
|||
* getPage() {},
|
||||
* },
|
||||
**/
|
||||
var onboardingTours = [
|
||||
{
|
||||
var onboardingTourset = {
|
||||
"private": {
|
||||
id: "onboarding-tour-private-browsing",
|
||||
tourNameId: "onboarding.tour-private-browsing",
|
||||
getNotificationStrings(bundle) {
|
||||
|
@ -68,7 +68,7 @@ var onboardingTours = [
|
|||
return div;
|
||||
},
|
||||
},
|
||||
{
|
||||
"addons": {
|
||||
id: "onboarding-tour-addons",
|
||||
tourNameId: "onboarding.tour-addons",
|
||||
getNotificationStrings(bundle) {
|
||||
|
@ -95,7 +95,7 @@ var onboardingTours = [
|
|||
return div;
|
||||
},
|
||||
},
|
||||
{
|
||||
"customize": {
|
||||
id: "onboarding-tour-customize",
|
||||
tourNameId: "onboarding.tour-customize",
|
||||
getNotificationStrings(bundle) {
|
||||
|
@ -122,7 +122,7 @@ var onboardingTours = [
|
|||
return div;
|
||||
},
|
||||
},
|
||||
{
|
||||
"search": {
|
||||
id: "onboarding-tour-search",
|
||||
tourNameId: "onboarding.tour-search2",
|
||||
getNotificationStrings(bundle) {
|
||||
|
@ -149,7 +149,7 @@ var onboardingTours = [
|
|||
return div;
|
||||
},
|
||||
},
|
||||
{
|
||||
"default": {
|
||||
id: "onboarding-tour-default-browser",
|
||||
tourNameId: "onboarding.tour-default-browser",
|
||||
getNotificationStrings(bundle) {
|
||||
|
@ -179,7 +179,7 @@ var onboardingTours = [
|
|||
return div;
|
||||
},
|
||||
},
|
||||
{
|
||||
"sync": {
|
||||
id: "onboarding-tour-sync",
|
||||
tourNameId: "onboarding.tour-sync2",
|
||||
getNotificationStrings(bundle) {
|
||||
|
@ -212,7 +212,7 @@ var onboardingTours = [
|
|||
return div;
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* The script won't be initialized if we turned off onboarding by
|
||||
|
@ -227,9 +227,16 @@ class Onboarding {
|
|||
this._window = contentWindow;
|
||||
this._tourItems = [];
|
||||
this._tourPages = [];
|
||||
this._tours = [];
|
||||
|
||||
// we only support the new user tour at this moment
|
||||
if (Services.prefs.getStringPref("browser.onboarding.tour-type", "update") !== "new") {
|
||||
let tourIds = this._getTourIDList(Services.prefs.getStringPref("browser.onboarding.tour-type", "update"));
|
||||
tourIds.forEach(tourId => {
|
||||
if (onboardingTourset[tourId]) {
|
||||
this._tours.push(onboardingTourset[tourId]);
|
||||
}
|
||||
});
|
||||
|
||||
if (this._tours.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -256,6 +263,11 @@ class Onboarding {
|
|||
this._initNotification();
|
||||
}
|
||||
|
||||
_getTourIDList(tourType) {
|
||||
let tours = Services.prefs.getStringPref(`browser.onboarding.${tourType}tour`, "");
|
||||
return tours.split(",").filter(tourId => tourId !== "").map(tourId => tourId.trim());
|
||||
}
|
||||
|
||||
_initNotification() {
|
||||
let doc = this._window.document;
|
||||
if (doc.hidden) {
|
||||
|
@ -285,7 +297,7 @@ class Onboarding {
|
|||
this.destroy();
|
||||
}
|
||||
});
|
||||
onboardingTours.forEach(tour => {
|
||||
this._tours.forEach(tour => {
|
||||
let tourId = tour.id;
|
||||
this._prefsObserved.set(`browser.onboarding.tour.${tourId}.completed`, () => {
|
||||
this.markTourCompletionState(tourId);
|
||||
|
@ -355,7 +367,7 @@ class Onboarding {
|
|||
toggleOverlay() {
|
||||
if (this._tourItems.length == 0) {
|
||||
// Lazy loading until first toggle.
|
||||
this._loadTours(onboardingTours);
|
||||
this._loadTours(this._tours);
|
||||
}
|
||||
|
||||
this.hideNotification();
|
||||
|
@ -418,16 +430,16 @@ class Onboarding {
|
|||
|
||||
// Take the last tour as the default last prompted
|
||||
// so below would start from the 1st one if found no the last prompted from the pref.
|
||||
let lastPromptedId = onboardingTours[onboardingTours.length - 1].id;
|
||||
let lastPromptedId = this._tours[this._tours.length - 1].id;
|
||||
lastPromptedId = Preferences.get("browser.onboarding.notification.lastPrompted", lastPromptedId);
|
||||
|
||||
let lastTourIndex = onboardingTours.findIndex(tour => tour.id == lastPromptedId);
|
||||
let lastTourIndex = this._tours.findIndex(tour => tour.id == lastPromptedId);
|
||||
if (lastTourIndex < 0) {
|
||||
// Couldn't find the tour.
|
||||
// This could be because the pref was manually modified into unknown value
|
||||
// or the tour version has been updated so have an new tours set.
|
||||
// Take the last tour as the last prompted so would start from the 1st one below.
|
||||
lastTourIndex = onboardingTours.length - 1;
|
||||
lastTourIndex = this._tours.length - 1;
|
||||
}
|
||||
|
||||
// Form tours to notify into the order we want.
|
||||
|
@ -435,7 +447,7 @@ class Onboarding {
|
|||
// This would form [#4, #5, #0, #1, #2, #3].
|
||||
// So the 1st met incomplete tour in #4 ~ #2 would be the one to show.
|
||||
// Or #3 would be the one to show if #4 ~ #2 are all completed.
|
||||
let toursToNotify = [ ...onboardingTours.slice(lastTourIndex + 1), ...onboardingTours.slice(0, lastTourIndex + 1) ];
|
||||
let toursToNotify = [ ...this._tours.slice(lastTourIndex + 1), ...this._tours.slice(0, lastTourIndex + 1) ];
|
||||
targetTour = toursToNotify.find(tour => !this.isTourCompleted(tour.id));
|
||||
|
||||
|
||||
|
@ -460,16 +472,7 @@ class Onboarding {
|
|||
tourTitle.textContent = notificationStrings.title;
|
||||
let tourMessage = this._notificationBar.querySelector("#onboarding-notification-tour-message");
|
||||
tourMessage.textContent = notificationStrings.message;
|
||||
|
||||
this._notificationBar.addEventListener("transitionend", () => {
|
||||
this._notificationBar.dataset.cssTransition = "end";
|
||||
}, { once: true });
|
||||
this._window.requestAnimationFrame(() => {
|
||||
// Request the 2nd animation frame.
|
||||
// This is to make sure the appending operation above and the css operation happen
|
||||
// in the different layout tick so as to make sure the transition happens.
|
||||
this._window.requestAnimationFrame(() => this._notificationBar.classList.add("onboarding-opened"));
|
||||
});
|
||||
this._notificationBar.classList.add("onboarding-opened");
|
||||
|
||||
this.sendMessageToChrome("set-prefs", [{
|
||||
name: "browser.onboarding.notification.lastPrompted",
|
||||
|
@ -480,7 +483,6 @@ class Onboarding {
|
|||
hideNotification() {
|
||||
if (this._notificationBar) {
|
||||
this._notificationBar.classList.remove("onboarding-opened");
|
||||
delete this._notificationBar.dataset.cssTransition;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -507,7 +509,7 @@ class Onboarding {
|
|||
}
|
||||
|
||||
hide() {
|
||||
this.setToursCompleted(onboardingTours.map(tour => tour.id));
|
||||
this.setToursCompleted(this._tours.map(tour => tour.id));
|
||||
this.sendMessageToChrome("set-prefs", [
|
||||
{
|
||||
name: "browser.onboarding.hidden",
|
||||
|
|
|
@ -4,3 +4,4 @@ support-files =
|
|||
|
||||
[browser_onboarding_notification.js]
|
||||
[browser_onboarding_tours.js]
|
||||
[browser_onboarding_tourset.js]
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
add_task(async function test_show_tour_notifications_in_order() {
|
||||
resetOnboardingDefaultState();
|
||||
await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
|
||||
|
||||
let tourIds = TOUR_IDs;
|
||||
let tab = null;
|
||||
|
@ -43,7 +42,6 @@ add_task(async function test_show_tour_notifications_in_order() {
|
|||
|
||||
add_task(async function test_open_target_tour_from_notification() {
|
||||
resetOnboardingDefaultState();
|
||||
await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
|
||||
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
|
||||
|
@ -61,7 +59,6 @@ add_task(async function test_open_target_tour_from_notification() {
|
|||
|
||||
add_task(async function test_not_show_notification_for_completed_tour() {
|
||||
resetOnboardingDefaultState();
|
||||
await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
|
||||
|
||||
let tourIds = TOUR_IDs;
|
||||
// Make only the last tour uncompleted
|
||||
|
|
|
@ -29,11 +29,10 @@ function assertTourCompletedStyle(tourId, expectComplete, browser) {
|
|||
|
||||
add_task(async function test_hide_onboarding_tours() {
|
||||
resetOnboardingDefaultState();
|
||||
await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
|
||||
|
||||
let tourIds = TOUR_IDs;
|
||||
let tabs = [];
|
||||
for (let url of [ABOUT_NEWTAB_URL, ABOUT_HOME_URL]) {
|
||||
for (let url of URLs) {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
|
@ -61,11 +60,10 @@ add_task(async function test_hide_onboarding_tours() {
|
|||
|
||||
add_task(async function test_click_action_button_to_set_tour_completed() {
|
||||
resetOnboardingDefaultState();
|
||||
await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
|
||||
|
||||
let tourIds = TOUR_IDs;
|
||||
let tabs = [];
|
||||
for (let url of [ABOUT_NEWTAB_URL, ABOUT_HOME_URL]) {
|
||||
for (let url of URLs) {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
|
@ -91,7 +89,6 @@ add_task(async function test_click_action_button_to_set_tour_completed() {
|
|||
|
||||
add_task(async function test_set_right_tour_completed_style_on_overlay() {
|
||||
resetOnboardingDefaultState();
|
||||
await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
|
||||
|
||||
let tourIds = TOUR_IDs;
|
||||
// Make the tours of even number as completed
|
||||
|
@ -100,7 +97,7 @@ add_task(async function test_set_right_tour_completed_style_on_overlay() {
|
|||
}
|
||||
|
||||
let tabs = [];
|
||||
for (let url of [ABOUT_NEWTAB_URL, ABOUT_HOME_URL]) {
|
||||
for (let url of URLs) {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(async function test_onboarding_default_new_tourset() {
|
||||
resetOnboardingDefaultState();
|
||||
let tabs = [];
|
||||
for (let url of URLs) {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-icon", {}, tab.linkedBrowser);
|
||||
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
|
||||
tabs.push(tab);
|
||||
}
|
||||
|
||||
let doc = content && content.document;
|
||||
let doms = doc.querySelectorAll(".onboarding-tour-item");
|
||||
is(doms.length, TOUR_IDs.length, "has exact tour numbers");
|
||||
doms.forEach((dom, idx) => {
|
||||
is(TOUR_IDs[idx], dom.id, "contain defined onboarding id");
|
||||
});
|
||||
|
||||
for (let i = tabs.length - 1; i >= 0; --i) {
|
||||
let tab = tabs[i];
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_onboarding_custom_new_tourset() {
|
||||
const CUSTOM_NEW_TOURs = [
|
||||
"onboarding-tour-private-browsing",
|
||||
"onboarding-tour-addons",
|
||||
"onboarding-tour-customize",
|
||||
];
|
||||
|
||||
resetOnboardingDefaultState();
|
||||
await SpecialPowers.pushPrefEnv({set: [
|
||||
["browser.onboarding.tour-type", "new"],
|
||||
["browser.onboarding.tourset-version", 1],
|
||||
["browser.onboarding.seen-tourset-version", 1],
|
||||
["browser.onboarding.newtour", "private,addons,customize"],
|
||||
]});
|
||||
|
||||
let tabs = [];
|
||||
for (let url of URLs) {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-icon", {}, tab.linkedBrowser);
|
||||
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
|
||||
tabs.push(tab);
|
||||
}
|
||||
|
||||
let doc = content && content.document;
|
||||
let doms = doc.querySelectorAll(".onboarding-tour-item");
|
||||
is(doms.length, CUSTOM_NEW_TOURs.length, "has exact tour numbers");
|
||||
doms.forEach((dom, idx) => {
|
||||
is(CUSTOM_NEW_TOURs[idx], dom.id, "contain defined onboarding id");
|
||||
});
|
||||
|
||||
for (let i = tabs.length - 1; i >= 0; --i) {
|
||||
let tab = tabs[i];
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_onboarding_custom_update_tourset() {
|
||||
const CUSTOM_UPDATE_TOURs = [
|
||||
"onboarding-tour-customize",
|
||||
"onboarding-tour-private-browsing",
|
||||
"onboarding-tour-addons",
|
||||
];
|
||||
resetOnboardingDefaultState();
|
||||
await SpecialPowers.pushPrefEnv({set: [
|
||||
["browser.onboarding.tour-type", "update"],
|
||||
["browser.onboarding.tourset-version", 1],
|
||||
["browser.onboarding.seen-tourset-version", 1],
|
||||
["browser.onboarding.updatetour", "customize,private,addons"],
|
||||
]});
|
||||
|
||||
let tabs = [];
|
||||
for (let url of URLs) {
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||
await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
|
||||
await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-icon", {}, tab.linkedBrowser);
|
||||
await promiseOnboardingOverlayOpened(tab.linkedBrowser);
|
||||
tabs.push(tab);
|
||||
}
|
||||
|
||||
let doc = content && content.document;
|
||||
let doms = doc.querySelectorAll(".onboarding-tour-item");
|
||||
is(doms.length, CUSTOM_UPDATE_TOURs.length, "has exact tour numbers");
|
||||
doms.forEach((dom, idx) => {
|
||||
is(CUSTOM_UPDATE_TOURs[idx], dom.id, "contain defined onboarding id");
|
||||
});
|
||||
|
||||
for (let i = tabs.length - 1; i >= 0; --i) {
|
||||
let tab = tabs[i];
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
}
|
||||
});
|
|
@ -5,6 +5,7 @@ let { Preferences } = Cu.import("resource://gre/modules/Preferences.jsm", {});
|
|||
|
||||
const ABOUT_HOME_URL = "about:home";
|
||||
const ABOUT_NEWTAB_URL = "about:newtab";
|
||||
const URLs = [ABOUT_HOME_URL, ABOUT_NEWTAB_URL];
|
||||
const TOUR_IDs = [
|
||||
"onboarding-tour-private-browsing",
|
||||
"onboarding-tour-addons",
|
||||
|
@ -13,10 +14,12 @@ const TOUR_IDs = [
|
|||
"onboarding-tour-default-browser",
|
||||
"onboarding-tour-sync",
|
||||
];
|
||||
const UPDATE_TOUR_IDs = [];
|
||||
|
||||
function resetOnboardingDefaultState() {
|
||||
// All the prefs should be reset to the default states
|
||||
// and no need to revert back so we don't use `SpecialPowers.pushPrefEnv` here.
|
||||
Preferences.set("browser.onboarding.enabled", true);
|
||||
Preferences.set("browser.onboarding.hidden", false);
|
||||
Preferences.set("browser.onboarding.notification.finished", false);
|
||||
Preferences.set("browser.onboarding.notification.lastPrompted", "");
|
||||
|
@ -87,7 +90,7 @@ function promiseTourNotificationOpened(browser) {
|
|||
return ContentTask.spawn(browser, {}, function() {
|
||||
return new Promise(resolve => {
|
||||
let bar = content.document.querySelector("#onboarding-notification-bar");
|
||||
if (bar && bar.classList.contains("onboarding-opened") && bar.dataset.cssTransition == "end") {
|
||||
if (bar && bar.classList.contains("onboarding-opened")) {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -825,6 +825,14 @@ bin/libfreebl_32int64_3.so
|
|||
@RESPATH@/components/SanityTest.js
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_MULET
|
||||
#include ../../b2g/installer/package-manifest.in
|
||||
#ifdef MOZ_DMD
|
||||
; DMD
|
||||
@RESPATH@/dmd.py
|
||||
@RESPATH@/fix_stack_using_bpsyms.py
|
||||
#ifdef XP_MACOSX
|
||||
@RESPATH@/fix_macosx_stack.py
|
||||
#endif
|
||||
#ifdef XP_LINUX
|
||||
@RESPATH@/fix_linux_stack.py
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -567,7 +567,8 @@ KeyframeUtils::ParseProperty(nsCSSPropertyID aProperty,
|
|||
&value,
|
||||
data,
|
||||
ParsingMode::Default,
|
||||
aDocument->GetCompatibilityMode()).Consume();
|
||||
aDocument->GetCompatibilityMode(),
|
||||
aDocument->CSSLoader()).Consume();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
|
|
@ -1757,7 +1757,8 @@ nsAttrValue::ParseStyleAttribute(const nsAString& aString,
|
|||
RefPtr<URLExtraData> data = new URLExtraData(baseURI, docURI,
|
||||
aElement->NodePrincipal());
|
||||
decl = ServoDeclarationBlock::FromCssText(aString, data,
|
||||
ownerDoc->GetCompatibilityMode());
|
||||
ownerDoc->GetCompatibilityMode(),
|
||||
ownerDoc->CSSLoader());
|
||||
} else {
|
||||
css::Loader* cssLoader = ownerDoc->CSSLoader();
|
||||
nsCSSParser cssParser(cssLoader);
|
||||
|
|
|
@ -2812,7 +2812,8 @@ CreateDeclarationForServo(nsCSSPropertyID aProperty,
|
|||
&value,
|
||||
data,
|
||||
ParsingMode::Default,
|
||||
aDocument->GetCompatibilityMode()).Consume();
|
||||
aDocument->GetCompatibilityMode(),
|
||||
aDocument->CSSLoader()).Consume();
|
||||
|
||||
if (!servoDeclarations) {
|
||||
// We got a syntax error. The spec says this value must be ignored.
|
||||
|
@ -2829,7 +2830,8 @@ CreateDeclarationForServo(nsCSSPropertyID aProperty,
|
|||
false,
|
||||
data,
|
||||
ParsingMode::Default,
|
||||
aDocument->GetCompatibilityMode());
|
||||
aDocument->GetCompatibilityMode(),
|
||||
aDocument->CSSLoader());
|
||||
}
|
||||
|
||||
return servoDeclarations.forget();
|
||||
|
|
|
@ -1326,6 +1326,7 @@ ContentChild::RecvReinitRendering(Endpoint<PCompositorManagerChild>&& aComposito
|
|||
if (!gfx::VRManagerChild::ReinitForContent(Move(aVRBridge))) {
|
||||
return IPC_FAIL_NO_REASON(this);
|
||||
}
|
||||
gfxPlatform::GetPlatform()->CompositorUpdated();
|
||||
|
||||
// Establish new PLayerTransactions.
|
||||
for (const auto& tabChild : tabs) {
|
||||
|
|
|
@ -46,11 +46,6 @@ public:
|
|||
// Can be called on any thread.
|
||||
virtual void NotifyDecodedFrames(const FrameStatisticsData& aStats) = 0;
|
||||
|
||||
virtual AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull()
|
||||
{
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
// Returns an event that will be notified when the owning document changes state
|
||||
// and we might have a new compositor. If this new compositor requires us to
|
||||
// recreate our decoders, then we expect the existing decoderis to return an
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "FileBlockCache.h"
|
||||
#include "MediaCache.h"
|
||||
#include "MediaPrefs.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "prio.h"
|
||||
|
@ -121,6 +123,33 @@ FileBlockCache::Init()
|
|||
return rv;
|
||||
}
|
||||
|
||||
int32_t
|
||||
FileBlockCache::GetMaxBlocks() const
|
||||
{
|
||||
// We look up the cache size every time. This means dynamic changes
|
||||
// to the pref are applied.
|
||||
const uint32_t cacheSizeKb =
|
||||
std::min(MediaPrefs::MediaCacheSizeKb(), uint32_t(INT32_MAX) * 2);
|
||||
// Ensure we can divide BLOCK_SIZE by 1024.
|
||||
static_assert(MediaCacheStream::BLOCK_SIZE % 1024 == 0,
|
||||
"BLOCK_SIZE should be a multiple of 1024");
|
||||
// Ensure BLOCK_SIZE/1024 is at least 2.
|
||||
static_assert(MediaCacheStream::BLOCK_SIZE / 1024 >= 2,
|
||||
"BLOCK_SIZE / 1024 should be at least 2");
|
||||
// Ensure we can convert BLOCK_SIZE/1024 to a uint32_t without truncation.
|
||||
static_assert(MediaCacheStream::BLOCK_SIZE / 1024 <= int64_t(UINT32_MAX),
|
||||
"BLOCK_SIZE / 1024 should be at most UINT32_MAX");
|
||||
// Since BLOCK_SIZE is a strict multiple of 1024,
|
||||
// cacheSizeKb * 1024 / BLOCK_SIZE == cacheSizeKb / (BLOCK_SIZE / 1024),
|
||||
// but the latter formula avoids a potential overflow from `* 1024`.
|
||||
// And because BLOCK_SIZE/1024 is at least 2, the maximum cache size
|
||||
// INT32_MAX*2 will give a maxBlocks that can fit in an int32_t.
|
||||
constexpr uint32_t blockSizeKb =
|
||||
uint32_t(MediaCacheStream::BLOCK_SIZE / 1024);
|
||||
const int32_t maxBlocks = int32_t(cacheSizeKb / blockSizeKb);
|
||||
return std::max(maxBlocks, int32_t(1));
|
||||
}
|
||||
|
||||
FileBlockCache::FileBlockCache()
|
||||
: mFileMutex("MediaCache.Writer.IO.Mutex")
|
||||
, mFD(nullptr)
|
||||
|
|
|
@ -65,6 +65,10 @@ public:
|
|||
// If re-initializing, just discard pending writes if any.
|
||||
nsresult Init() override;
|
||||
|
||||
// Maximum number of blocks allowed in this block cache.
|
||||
// Calculated from "media.cache_size" pref.
|
||||
int32_t GetMaxBlocks() const override;
|
||||
|
||||
// Can be called on any thread. This defers to a non-main thread.
|
||||
nsresult WriteBlock(uint32_t aBlockIndex,
|
||||
Span<const uint8_t> aData1,
|
||||
|
|
|
@ -52,6 +52,10 @@ public:
|
|||
// If called again, re-initialize cache with minimal chance of failure.
|
||||
virtual nsresult Init() = 0;
|
||||
|
||||
// Maximum number of blocks expected in this block cache. (But allow overflow
|
||||
// to accomodate incoming traffic before MediaCache can handle it.)
|
||||
virtual int32_t GetMaxBlocks() const = 0;
|
||||
|
||||
// Can be called on any thread. This defers to a non-main thread.
|
||||
virtual nsresult WriteBlock(uint32_t aBlockIndex,
|
||||
Span<const uint8_t> aData1,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "FileBlockCache.h"
|
||||
#include "MediaBlockCacheBase.h"
|
||||
#include "MediaPrefs.h"
|
||||
#include "MediaResource.h"
|
||||
#include "MemoryBlockCache.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
|
@ -737,31 +738,6 @@ MediaCache::ReadCacheFile(
|
|||
}
|
||||
}
|
||||
|
||||
static int32_t GetMaxBlocks()
|
||||
{
|
||||
// We look up the cache size every time. This means dynamic changes
|
||||
// to the pref are applied.
|
||||
const uint32_t cacheSizeKb =
|
||||
std::min(MediaPrefs::MediaCacheSizeKb(), uint32_t(INT32_MAX) * 2);
|
||||
// Ensure we can divide BLOCK_SIZE by 1024.
|
||||
static_assert(MediaCache::BLOCK_SIZE % 1024 == 0,
|
||||
"BLOCK_SIZE should be a multiple of 1024");
|
||||
// Ensure BLOCK_SIZE/1024 is at least 2.
|
||||
static_assert(MediaCache::BLOCK_SIZE / 1024 >= 2,
|
||||
"BLOCK_SIZE / 1024 should be at least 2");
|
||||
// Ensure we can convert BLOCK_SIZE/1024 to a uint32_t without truncation.
|
||||
static_assert(MediaCache::BLOCK_SIZE / 1024 <= int64_t(UINT32_MAX),
|
||||
"BLOCK_SIZE / 1024 should be at most UINT32_MAX");
|
||||
// Since BLOCK_SIZE is a strict multiple of 1024,
|
||||
// cacheSizeKb * 1024 / BLOCK_SIZE == cacheSizeKb / (BLOCK_SIZE / 1024),
|
||||
// but the latter formula avoids a potential overflow from `* 1024`.
|
||||
// And because BLOCK_SIZE/1024 is at least 2, the maximum cache size
|
||||
// INT32_MAX*2 will give a maxBlocks that can fit in an int32_t.
|
||||
constexpr uint32_t blockSizeKb = uint32_t(MediaCache::BLOCK_SIZE / 1024);
|
||||
const int32_t maxBlocks = int32_t(cacheSizeKb / blockSizeKb);
|
||||
return std::max(maxBlocks, int32_t(1));
|
||||
}
|
||||
|
||||
// Allowed range is whatever can be accessed with an int32_t block index.
|
||||
static bool
|
||||
IsOffsetAllowed(int64_t aOffset)
|
||||
|
@ -818,8 +794,10 @@ MediaCache::FindBlockForIncomingData(TimeStamp aNow,
|
|||
// b) the data we're going to store in the free block is not higher
|
||||
// priority than the data already stored in the free block.
|
||||
// The latter can lead us to go over the cache limit a bit.
|
||||
if ((mIndex.Length() < uint32_t(GetMaxBlocks()) || blockIndex < 0 ||
|
||||
PredictNextUseForIncomingData(aStream) >= PredictNextUse(aNow, blockIndex))) {
|
||||
if ((mIndex.Length() < uint32_t(mBlockCache->GetMaxBlocks()) ||
|
||||
blockIndex < 0 ||
|
||||
PredictNextUseForIncomingData(aStream) >=
|
||||
PredictNextUse(aNow, blockIndex))) {
|
||||
blockIndex = mIndex.Length();
|
||||
if (!mIndex.AppendElement())
|
||||
return -1;
|
||||
|
@ -1163,7 +1141,7 @@ MediaCache::Update()
|
|||
mInUpdate = true;
|
||||
#endif
|
||||
|
||||
int32_t maxBlocks = GetMaxBlocks();
|
||||
int32_t maxBlocks = mBlockCache->GetMaxBlocks();
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
|
||||
int32_t freeBlockCount = mFreeBlocks.GetCount();
|
||||
|
|
|
@ -197,13 +197,6 @@ MediaDecoder::GetDuration()
|
|||
return mDuration;
|
||||
}
|
||||
|
||||
AbstractCanonical<media::NullableTimeUnit>*
|
||||
MediaDecoder::CanonicalDurationOrNull()
|
||||
{
|
||||
MOZ_ASSERT(mDecoderStateMachine);
|
||||
return mDecoderStateMachine->CanonicalDuration();
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoder::SetInfinite(bool aInfinite)
|
||||
{
|
||||
|
|
|
@ -780,7 +780,6 @@ protected:
|
|||
Canonical<int64_t> mDecoderPosition;
|
||||
|
||||
public:
|
||||
AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() override;
|
||||
AbstractCanonical<double>* CanonicalVolume() { return &mVolume; }
|
||||
AbstractCanonical<bool>* CanonicalPreservesPitch()
|
||||
{
|
||||
|
|
|
@ -75,9 +75,7 @@ MediaDecoderReader::MediaDecoderReader(const MediaDecoderReaderInit& aInit)
|
|||
GetMediaThreadPool(MediaThreadType::PLAYBACK),
|
||||
"MediaDecoderReader::mTaskQueue",
|
||||
/* aSupportsTailDispatch = */ true))
|
||||
, mWatchManager(this, mTaskQueue)
|
||||
, mBuffered(mTaskQueue, TimeIntervals(), "MediaDecoderReader::mBuffered (Canonical)")
|
||||
, mDuration(mTaskQueue, NullableTimeUnit(), "MediaDecoderReader::mDuration (Mirror)")
|
||||
, mIgnoreAudioOutputFormat(false)
|
||||
, mHitAudioDecodeError(false)
|
||||
, mShutdown(false)
|
||||
|
@ -90,28 +88,9 @@ MediaDecoderReader::MediaDecoderReader(const MediaDecoderReaderInit& aInit)
|
|||
nsresult
|
||||
MediaDecoderReader::Init()
|
||||
{
|
||||
// Dispatch initialization that needs to happen on that task queue.
|
||||
mTaskQueue->Dispatch(
|
||||
NewRunnableMethod("MediaDecoderReader::InitializationTask",
|
||||
this,
|
||||
&MediaDecoderReader::InitializationTask));
|
||||
return InitInternal();
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoderReader::InitializationTask()
|
||||
{
|
||||
if (!mDecoder) {
|
||||
return;
|
||||
}
|
||||
if (mDecoder->CanonicalDurationOrNull()) {
|
||||
mDuration.Connect(mDecoder->CanonicalDurationOrNull());
|
||||
}
|
||||
|
||||
// Initialize watchers.
|
||||
mWatchManager.Watch(mDuration, &MediaDecoderReader::UpdateBuffered);
|
||||
}
|
||||
|
||||
MediaDecoderReader::~MediaDecoderReader()
|
||||
{
|
||||
MOZ_ASSERT(mShutdown);
|
||||
|
@ -142,6 +121,14 @@ size_t MediaDecoderReader::SizeOfAudioQueueInFrames()
|
|||
return mAudioQueue.GetSize();
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoderReader::UpdateDuration(const media::TimeUnit& aDuration)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
mDuration = Some(aDuration);
|
||||
UpdateBuffered();
|
||||
}
|
||||
|
||||
nsresult MediaDecoderReader::ResetDecode(TrackSet aTracks)
|
||||
{
|
||||
if (aTracks.contains(TrackInfo::kVideoTrack)) {
|
||||
|
@ -203,13 +190,12 @@ MediaDecoderReader::GetBuffered()
|
|||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
||||
AutoPinned<MediaResource> stream(mResource);
|
||||
|
||||
if (!mDuration.Ref().isSome()) {
|
||||
if (mDuration.isNothing()) {
|
||||
return TimeIntervals();
|
||||
}
|
||||
|
||||
return GetEstimatedBufferedTimeRanges(stream, mDuration.Ref().ref().ToMicroseconds());
|
||||
AutoPinned<MediaResource> stream(mResource);
|
||||
return GetEstimatedBufferedTimeRanges(stream, mDuration->ToMicroseconds());
|
||||
}
|
||||
|
||||
RefPtr<MediaDecoderReader::MetadataPromise>
|
||||
|
@ -371,12 +357,8 @@ MediaDecoderReader::Shutdown()
|
|||
mBaseVideoPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
|
||||
|
||||
ReleaseResources();
|
||||
mDuration.DisconnectIfConnected();
|
||||
mBuffered.DisconnectAll();
|
||||
|
||||
// Shut down the watch manager before shutting down our task queue.
|
||||
mWatchManager.Shutdown();
|
||||
|
||||
mDecoder = nullptr;
|
||||
|
||||
return mTaskQueue->BeginShutdown();
|
||||
|
|
|
@ -132,6 +132,8 @@ public:
|
|||
return OwnerThread()->IsCurrentThreadIn();
|
||||
}
|
||||
|
||||
void UpdateDuration(const media::TimeUnit& aDuration);
|
||||
|
||||
// Resets all state related to decoding, emptying all buffers etc.
|
||||
// Cancels all pending Request*Data() request callbacks, rejects any
|
||||
// outstanding seek promises, and flushes the decode pipeline. The
|
||||
|
@ -301,17 +303,13 @@ protected:
|
|||
// Decode task queue.
|
||||
RefPtr<TaskQueue> mTaskQueue;
|
||||
|
||||
// State-watching manager.
|
||||
WatchManager<MediaDecoderReader> mWatchManager;
|
||||
|
||||
// Buffered range.
|
||||
Canonical<media::TimeIntervals> mBuffered;
|
||||
|
||||
// Stores presentation info required for playback.
|
||||
MediaInfo mInfo;
|
||||
|
||||
// Duration, mirrored from the state machine task queue.
|
||||
Mirror<media::NullableTimeUnit> mDuration;
|
||||
media::NullableTimeUnit mDuration;
|
||||
|
||||
// Whether we should accept media that we know we can't play
|
||||
// directly, because they have a number of channel higher than
|
||||
|
@ -339,12 +337,6 @@ protected:
|
|||
private:
|
||||
virtual nsresult InitInternal() { return NS_OK; }
|
||||
|
||||
// Does any spinup that needs to happen on this task queue. This runs on a
|
||||
// different thread than Init, and there should not be ordering dependencies
|
||||
// between the two (even though in practice, Init will always run first right
|
||||
// now thanks to the tail dispatcher).
|
||||
void InitializationTask();
|
||||
|
||||
// Read header data for all bitstreams in the file. Fills aInfo with
|
||||
// the data required to present the media, and optionally fills *aTags
|
||||
// with tag metadata from the file.
|
||||
|
|
|
@ -13,6 +13,10 @@ MediaDecoderReaderWrapper::MediaDecoderReaderWrapper(AbstractThread* aOwnerThrea
|
|||
MediaDecoderReader* aReader)
|
||||
: mOwnerThread(aOwnerThread)
|
||||
, mReader(aReader)
|
||||
, mWatchManager(this, aReader->OwnerThread())
|
||||
, mDuration(aReader->OwnerThread(),
|
||||
NullableTimeUnit(),
|
||||
"MediaDecoderReaderWrapper::mDuration (Mirror)")
|
||||
{
|
||||
// Must support either heuristic buffering or WaitForData().
|
||||
MOZ_ASSERT(mReader->UseBufferingHeuristics() ||
|
||||
|
@ -134,8 +138,12 @@ MediaDecoderReaderWrapper::Shutdown()
|
|||
{
|
||||
MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
|
||||
mShutdown = true;
|
||||
return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
|
||||
&MediaDecoderReader::Shutdown);
|
||||
RefPtr<MediaDecoderReaderWrapper> self = this;
|
||||
return InvokeAsync(mReader->OwnerThread(), __func__, [self]() {
|
||||
self->mDuration.DisconnectIfConnected();
|
||||
self->mWatchManager.Shutdown();
|
||||
return self->mReader->Shutdown();
|
||||
});
|
||||
}
|
||||
|
||||
RefPtr<MediaDecoderReaderWrapper::MetadataPromise>
|
||||
|
@ -171,4 +179,28 @@ MediaDecoderReaderWrapper::SetVideoBlankDecode(bool aIsBlankDecode)
|
|||
mReader->OwnerThread()->Dispatch(r.forget());
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoderReaderWrapper::UpdateDuration()
|
||||
{
|
||||
MOZ_ASSERT(mReader->OwnerThread()->IsCurrentThreadIn());
|
||||
mReader->UpdateDuration(mDuration.Ref().ref());
|
||||
}
|
||||
|
||||
void
|
||||
MediaDecoderReaderWrapper::SetCanonicalDuration(
|
||||
AbstractCanonical<media::NullableTimeUnit>* aCanonical)
|
||||
{
|
||||
using DurationT = AbstractCanonical<media::NullableTimeUnit>;
|
||||
RefPtr<MediaDecoderReaderWrapper> self = this;
|
||||
RefPtr<DurationT> canonical = aCanonical;
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
|
||||
"MediaDecoderReaderWrapper::SetCanonicalDuration",
|
||||
[this, self, canonical]() {
|
||||
mDuration.Connect(canonical);
|
||||
mWatchManager.Watch(mDuration,
|
||||
&MediaDecoderReaderWrapper::UpdateDuration);
|
||||
});
|
||||
mReader->OwnerThread()->Dispatch(r.forget());
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -88,16 +88,26 @@ public:
|
|||
|
||||
void SetVideoBlankDecode(bool aIsBlankDecode);
|
||||
|
||||
void SetCanonicalDuration(
|
||||
AbstractCanonical<media::NullableTimeUnit>* aCanonical);
|
||||
|
||||
private:
|
||||
~MediaDecoderReaderWrapper();
|
||||
RefPtr<MetadataPromise> OnMetadataRead(MetadataHolder&& aMetadata);
|
||||
RefPtr<MetadataPromise> OnMetadataNotRead(const MediaResult& aError);
|
||||
void UpdateDuration();
|
||||
|
||||
const RefPtr<AbstractThread> mOwnerThread;
|
||||
const RefPtr<MediaDecoderReader> mReader;
|
||||
|
||||
bool mShutdown = false;
|
||||
Maybe<media::TimeUnit> mStartTime;
|
||||
|
||||
// State-watching manager.
|
||||
WatchManager<MediaDecoderReaderWrapper> mWatchManager;
|
||||
|
||||
// Duration, mirrored from the state machine task queue.
|
||||
Mirror<media::NullableTimeUnit> mDuration;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -2961,6 +2961,8 @@ nsresult MediaDecoderStateMachine::Init(MediaDecoder* aDecoder)
|
|||
nsresult rv = mReader->Init();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mReader->SetCanonicalDuration(&mDuration);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -137,9 +137,23 @@ enum MemoryBlockCacheTelemetryErrors
|
|||
MoveBlockCannotGrow = 7,
|
||||
};
|
||||
|
||||
static int32_t
|
||||
CalculateMaxBlocks(int64_t aContentLength)
|
||||
{
|
||||
// Note: It doesn't matter if calculations overflow, Init() would later fail.
|
||||
// We want at least enough blocks to contain the original content length.
|
||||
const int32_t requiredBlocks =
|
||||
int32_t((aContentLength - 1) / MediaBlockCacheBase::BLOCK_SIZE + 1);
|
||||
// Allow at least 1s of ultra HD (25Mbps).
|
||||
const int32_t workableBlocks =
|
||||
25 * 1024 * 1024 / 8 / MediaBlockCacheBase::BLOCK_SIZE;
|
||||
return std::max(requiredBlocks, workableBlocks);
|
||||
}
|
||||
|
||||
MemoryBlockCache::MemoryBlockCache(int64_t aContentLength)
|
||||
// Buffer whole blocks.
|
||||
: mInitialContentLength((aContentLength >= 0) ? size_t(aContentLength) : 0)
|
||||
, mMaxBlocks(CalculateMaxBlocks(aContentLength))
|
||||
, mMutex("MemoryBlockCache")
|
||||
, mHasGrown(false)
|
||||
{
|
||||
|
@ -175,31 +189,36 @@ MemoryBlockCache::EnsureBufferCanContain(size_t aContentLength)
|
|||
}
|
||||
// Need larger buffer. If we are allowed more memory, attempt to re-allocate.
|
||||
const size_t extra = desiredLength - initialLength;
|
||||
// Note: There is a small race between testing `atomic + extra > limit` and
|
||||
// committing to it with `atomic += extra` below; but this is acceptable, as
|
||||
// in the worst case it may allow a small number of buffers to go past the
|
||||
// limit.
|
||||
// The alternative would have been to reserve the space first with
|
||||
// `atomic += extra` and then undo it with `atomic -= extra` in case of
|
||||
// failure; but this would have meant potentially preventing other (small but
|
||||
// successful) allocations.
|
||||
static const size_t sysmem =
|
||||
std::max<size_t>(PR_GetPhysicalMemorySize(), 32 * 1024 * 1024);
|
||||
const size_t limit = std::min(
|
||||
size_t(MediaPrefs::MediaMemoryCachesCombinedLimitKb()) * 1024,
|
||||
sysmem * MediaPrefs::MediaMemoryCachesCombinedLimitPcSysmem() / 100);
|
||||
const size_t currentSizes = static_cast<size_t>(gCombinedSizes);
|
||||
if (currentSizes + extra > limit) {
|
||||
LOG("EnsureBufferCanContain(%zu) - buffer size %zu, wanted + %zu = %zu;"
|
||||
" combined sizes %zu + %zu > limit %zu",
|
||||
aContentLength,
|
||||
initialLength,
|
||||
extra,
|
||||
desiredLength,
|
||||
currentSizes,
|
||||
extra,
|
||||
limit);
|
||||
return false;
|
||||
// Only check the very first allocation against the combined MemoryBlockCache
|
||||
// limit. Further growths will always be allowed, assuming MediaCache won't
|
||||
// go over GetMaxBlocks() by too much.
|
||||
if (initialLength == 0) {
|
||||
// Note: There is a small race between testing `atomic + extra > limit` and
|
||||
// committing to it with `atomic += extra` below; but this is acceptable, as
|
||||
// in the worst case it may allow a small number of buffers to go past the
|
||||
// limit.
|
||||
// The alternative would have been to reserve the space first with
|
||||
// `atomic += extra` and then undo it with `atomic -= extra` in case of
|
||||
// failure; but this would have meant potentially preventing other (small
|
||||
// but successful) allocations.
|
||||
static const size_t sysmem =
|
||||
std::max<size_t>(PR_GetPhysicalMemorySize(), 32 * 1024 * 1024);
|
||||
const size_t limit = std::min(
|
||||
size_t(MediaPrefs::MediaMemoryCachesCombinedLimitKb()) * 1024,
|
||||
sysmem * MediaPrefs::MediaMemoryCachesCombinedLimitPcSysmem() / 100);
|
||||
const size_t currentSizes = static_cast<size_t>(gCombinedSizes);
|
||||
if (currentSizes + extra > limit) {
|
||||
LOG("EnsureBufferCanContain(%zu) - buffer size %zu, wanted + %zu = %zu;"
|
||||
" combined sizes %zu + %zu > limit %zu",
|
||||
aContentLength,
|
||||
initialLength,
|
||||
extra,
|
||||
desiredLength,
|
||||
currentSizes,
|
||||
extra,
|
||||
limit);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!mBuffer.SetLength(desiredLength, mozilla::fallible)) {
|
||||
LOG("EnsureBufferCanContain(%zu) - buffer size %zu, wanted + %zu = %zu, "
|
||||
|
|
|
@ -42,6 +42,10 @@ public:
|
|||
// If re-initializing, clear buffer.
|
||||
virtual nsresult Init() override;
|
||||
|
||||
// Maximum number of blocks allowed in this block cache.
|
||||
// Based on initial content length, and minimum usable block cache.
|
||||
int32_t GetMaxBlocks() const override { return mMaxBlocks; }
|
||||
|
||||
// Can be called on any thread.
|
||||
virtual nsresult WriteBlock(uint32_t aBlockIndex,
|
||||
Span<const uint8_t> aData1,
|
||||
|
@ -71,6 +75,9 @@ private:
|
|||
// Initial content length.
|
||||
const size_t mInitialContentLength;
|
||||
|
||||
// Maximum number of blocks that this MemoryBlockCache expects.
|
||||
const int32_t mMaxBlocks;
|
||||
|
||||
// Mutex which controls access to all members below.
|
||||
Mutex mMutex;
|
||||
|
||||
|
|
|
@ -694,7 +694,8 @@ ValueFromStringHelper(nsCSSPropertyID aPropID,
|
|||
data,
|
||||
ParsingMode::AllowUnitlessLength |
|
||||
ParsingMode::AllowAllNumericValues,
|
||||
doc->GetCompatibilityMode()).Consume();
|
||||
doc->GetCompatibilityMode(),
|
||||
doc->CSSLoader()).Consume();
|
||||
if (!servoDeclarationBlock) {
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1193,6 +1193,7 @@ private:
|
|||
// MEMBER DATA
|
||||
// -----------
|
||||
nsCSSParser mParser;
|
||||
css::Loader* mLoader;
|
||||
|
||||
// Arguments for nsCSSParser::ParseProperty
|
||||
nsIURI* mDocURI;
|
||||
|
@ -1212,7 +1213,7 @@ MappedAttrParser::MappedAttrParser(css::Loader* aLoader,
|
|||
already_AddRefed<nsIURI> aBaseURI,
|
||||
nsSVGElement* aElement,
|
||||
StyleBackendType aBackend)
|
||||
: mParser(aLoader), mDocURI(aDocURI), mBaseURI(aBaseURI),
|
||||
: mParser(aLoader), mLoader(aLoader), mDocURI(aDocURI), mBaseURI(aBaseURI),
|
||||
mElement(aElement), mBackend(aBackend)
|
||||
{
|
||||
}
|
||||
|
@ -1253,7 +1254,7 @@ MappedAttrParser::ParseMappedAttrValue(nsIAtom* aMappedAttrName,
|
|||
mElement->NodePrincipal());
|
||||
changed = Servo_DeclarationBlock_SetPropertyById(
|
||||
mDecl->AsServo()->Raw(), propertyID, &value, false, data,
|
||||
ParsingMode::AllowUnitlessLength, mElement->OwnerDoc()->GetCompatibilityMode());
|
||||
ParsingMode::AllowUnitlessLength, mElement->OwnerDoc()->GetCompatibilityMode(), mLoader);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
|
|
|
@ -215,17 +215,6 @@ Factory::Init(const Config& aConfig)
|
|||
MOZ_ASSERT(!sConfig);
|
||||
sConfig = new Config(aConfig);
|
||||
|
||||
// Make sure we don't completely break rendering because of a typo in the
|
||||
// pref or whatnot.
|
||||
const int32_t kMinAllocPref = 10000000;
|
||||
const int32_t kMinSizePref = 2048;
|
||||
if (sConfig->mMaxAllocSize < kMinAllocPref) {
|
||||
sConfig->mMaxAllocSize = kMinAllocPref;
|
||||
}
|
||||
if (sConfig->mMaxTextureSize < kMinSizePref) {
|
||||
sConfig->mMaxTextureSize = kMinSizePref;
|
||||
}
|
||||
|
||||
#ifdef MOZ_ENABLE_FREETYPE
|
||||
mFTLock = new Mutex("Factory::mFTLock");
|
||||
#endif
|
||||
|
|
|
@ -431,6 +431,17 @@ GPUProcessManager::OnRemoteProcessDeviceReset(GPUProcessHost* aHost)
|
|||
DestroyProcess();
|
||||
DisableGPUProcess("GPU processed experienced too many device resets");
|
||||
|
||||
// Reaches the limited TDR attempts, fallback to software solution.
|
||||
gfxConfig::SetFailed(Feature::HW_COMPOSITING,
|
||||
FeatureStatus::Blocked,
|
||||
"Too many attemps of D3D11 creation, fallback to software solution.");
|
||||
gfxConfig::SetFailed(Feature::D3D11_COMPOSITING,
|
||||
FeatureStatus::Blocked,
|
||||
"Too many attemps of D3D11 creation, fallback to software solution.");
|
||||
gfxConfig::SetFailed(Feature::DIRECT2D,
|
||||
FeatureStatus::Blocked,
|
||||
"Too many attemps of D3D11 creation, fallback to software solution.");
|
||||
|
||||
HandleProcessLost();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -237,11 +237,5 @@ FixedSizeSmallShmemSectionAllocator::ShrinkShmemSectionHeap()
|
|||
}
|
||||
}
|
||||
|
||||
int32_t
|
||||
ClientIPCAllocator::GetMaxTextureSize() const
|
||||
{
|
||||
return gfxPrefs::MaxTextureSize();
|
||||
}
|
||||
|
||||
} // namespace layers
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -120,8 +120,6 @@ public:
|
|||
|
||||
virtual MessageLoop * GetMessageLoop() const = 0;
|
||||
|
||||
virtual int32_t GetMaxTextureSize() const;
|
||||
|
||||
virtual void CancelWaitForRecycle(uint64_t aTextureId) = 0;
|
||||
};
|
||||
|
||||
|
|
|
@ -1645,7 +1645,7 @@ private:
|
|||
buf.mGlyphs = mGlyphBuffer;
|
||||
buf.mNumGlyphs = mNumGlyphs;
|
||||
|
||||
gfxContext::AzureState state = mRunParams.context->CurrentState();
|
||||
const gfxContext::AzureState &state = mRunParams.context->CurrentState();
|
||||
if (mRunParams.drawMode & DrawMode::GLYPH_FILL) {
|
||||
if (state.pattern || mFontParams.contextPaint) {
|
||||
Pattern *pat;
|
||||
|
@ -1661,7 +1661,9 @@ private:
|
|||
}
|
||||
if (!fillPattern) {
|
||||
if (state.pattern) {
|
||||
pat = state.pattern->GetPattern(mRunParams.dt,
|
||||
RefPtr<gfxPattern> statePattern =
|
||||
mRunParams.context->CurrentState().pattern;
|
||||
pat = statePattern->GetPattern(mRunParams.dt,
|
||||
state.patternTransformChanged ?
|
||||
&state.patternTransform : nullptr);
|
||||
} else {
|
||||
|
@ -2070,10 +2072,12 @@ gfxFont::Draw(const gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
|
|||
aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
|
||||
|
||||
bool sideways = false;
|
||||
gfxContextMatrixAutoSaveRestore matrixRestore;
|
||||
|
||||
gfxPoint origPt = *aPt;
|
||||
if (aRunParams.isVerticalRun && !fontParams.isVerticalFont) {
|
||||
sideways = true;
|
||||
aRunParams.context->Save();
|
||||
matrixRestore.SetContext(aRunParams.context);
|
||||
gfxPoint p(aPt->x * aRunParams.devPerApp,
|
||||
aPt->y * aRunParams.devPerApp);
|
||||
const Metrics& metrics = GetMetrics(eHorizontal);
|
||||
|
@ -2198,7 +2202,6 @@ gfxFont::Draw(const gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
|
|||
aRunParams.dt->SetPermitSubpixelAA(oldSubpixelAA);
|
||||
|
||||
if (sideways) {
|
||||
aRunParams.context->Restore();
|
||||
// adjust updated aPt to account for the transform we were using
|
||||
gfxFloat advance = aPt->x - origPt.x;
|
||||
if (aOrientation ==
|
||||
|
@ -2222,7 +2225,6 @@ gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint,
|
|||
GetAdjustedSize() / GetFontEntry()->UnitsPerEm();
|
||||
gfxContextMatrixAutoSaveRestore matrixRestore(aContext);
|
||||
|
||||
aContext->Save();
|
||||
aContext->SetMatrix(
|
||||
aContext->CurrentMatrix().PreTranslate(aPoint.x, aPoint.y).
|
||||
PreScale(devUnitsPerSVGUnit, devUnitsPerSVGUnit));
|
||||
|
@ -2230,7 +2232,6 @@ gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint,
|
|||
aContextPaint->InitStrokeGeometry(aContext, devUnitsPerSVGUnit);
|
||||
|
||||
GetFontEntry()->RenderSVGGlyph(aContext, aGlyphId, aContextPaint);
|
||||
aContext->Restore();
|
||||
aContext->NewPath();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ gfxPattern::GetInverseMatrix() const
|
|||
|
||||
Pattern*
|
||||
gfxPattern::GetPattern(const DrawTarget *aTarget,
|
||||
Matrix *aOriginalUserToDevice)
|
||||
const Matrix *aOriginalUserToDevice)
|
||||
{
|
||||
Matrix patternToUser = mPatternToUserSpace;
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ public:
|
|||
* to the current transform.
|
||||
*/
|
||||
mozilla::gfx::Pattern *GetPattern(const mozilla::gfx::DrawTarget *aTarget,
|
||||
mozilla::gfx::Matrix *aOriginalUserToDevice = nullptr);
|
||||
const mozilla::gfx::Matrix *aOriginalUserToDevice = nullptr);
|
||||
bool IsOpaque();
|
||||
|
||||
// clamp, repeat, reflect
|
||||
|
|
|
@ -848,18 +848,36 @@ gfxPlatform::IsDXInterop2Blocked()
|
|||
return status != nsIGfxInfo::FEATURE_STATUS_OK;
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
gfxPlatform::MaxTextureSize()
|
||||
{
|
||||
// Make sure we don't completely break rendering because of a typo in the
|
||||
// pref or whatnot.
|
||||
const int32_t kMinSizePref = 2048;
|
||||
return std::max(kMinSizePref, gfxPrefs::MaxTextureSizeDoNotUseDirectly());
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
gfxPlatform::MaxAllocSize()
|
||||
{
|
||||
// Make sure we don't completely break rendering because of a typo in the
|
||||
// pref or whatnot.
|
||||
const int32_t kMinAllocPref = 10000000;
|
||||
return std::max(kMinAllocPref, gfxPrefs::MaxAllocSizeDoNotUseDirectly());
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
gfxPlatform::InitMoz2DLogging()
|
||||
{
|
||||
auto fwd = new CrashStatsLogForwarder("GraphicsCriticalError");
|
||||
fwd->SetCircularBufferSize(gfxPrefs::GfxLoggingCrashLength());
|
||||
auto fwd = new CrashStatsLogForwarder("GraphicsCriticalError");
|
||||
fwd->SetCircularBufferSize(gfxPrefs::GfxLoggingCrashLength());
|
||||
|
||||
mozilla::gfx::Config cfg;
|
||||
cfg.mLogForwarder = fwd;
|
||||
cfg.mMaxTextureSize = gfxPrefs::MaxTextureSize();
|
||||
cfg.mMaxAllocSize = gfxPrefs::MaxAllocSize();
|
||||
mozilla::gfx::Config cfg;
|
||||
cfg.mLogForwarder = fwd;
|
||||
cfg.mMaxTextureSize = gfxPlatform::MaxTextureSize();
|
||||
cfg.mMaxAllocSize = gfxPlatform::MaxAllocSize();
|
||||
|
||||
gfx::Factory::Init(cfg);
|
||||
gfx::Factory::Init(cfg);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
|
|
|
@ -189,6 +189,8 @@ public:
|
|||
*/
|
||||
static void InitNullMetadata();
|
||||
|
||||
static int32_t MaxTextureSize();
|
||||
static int32_t MaxAllocSize();
|
||||
static void InitMoz2DLogging();
|
||||
|
||||
static bool IsHeadless();
|
||||
|
|
|
@ -448,8 +448,10 @@ private:
|
|||
// The maximums here are quite conservative, we can tighten them if problems show up.
|
||||
DECL_GFX_PREF(Once, "gfx.logging.texture-usage.enabled", GfxLoggingTextureUsageEnabled, bool, false);
|
||||
DECL_GFX_PREF(Once, "gfx.logging.peak-texture-usage.enabled",GfxLoggingPeakTextureUsageEnabled, bool, false);
|
||||
DECL_GFX_PREF(Once, "gfx.max-alloc-size", MaxAllocSize, int32_t, (int32_t)500000000);
|
||||
DECL_GFX_PREF(Once, "gfx.max-texture-size", MaxTextureSize, int32_t, (int32_t)32767);
|
||||
// Use gfxPlatform::MaxAllocSize instead of the pref directly
|
||||
DECL_GFX_PREF(Once, "gfx.max-alloc-size", MaxAllocSizeDoNotUseDirectly, int32_t, (int32_t)500000000);
|
||||
// Use gfxPlatform::MaxTextureSize instead of the pref directly
|
||||
DECL_GFX_PREF(Once, "gfx.max-texture-size", MaxTextureSizeDoNotUseDirectly, int32_t, (int32_t)32767);
|
||||
DECL_GFX_PREF(Live, "gfx.partialpresent.force", PartialPresent, int32_t, 0);
|
||||
DECL_GFX_PREF(Live, "gfx.perf-warnings.enabled", PerfWarnings, bool, false);
|
||||
DECL_GFX_PREF(Live, "gfx.SurfaceTexture.detach.enabled", SurfaceTextureDetachEnabled, bool, true);
|
||||
|
|
|
@ -499,7 +499,6 @@ gfxTextRun::DrawPartialLigature(gfxFont *aFont, Range aRange,
|
|||
(end - start) / mAppUnitsPerDevUnit, clipExtents.Height());
|
||||
MaybeSnapToDevicePixels(clipRect, *aParams.dt, true);
|
||||
|
||||
aParams.context->Save();
|
||||
aParams.context->Clip(clipRect);
|
||||
}
|
||||
|
||||
|
@ -512,7 +511,7 @@ gfxTextRun::DrawPartialLigature(gfxFont *aFont, Range aRange,
|
|||
|
||||
DrawGlyphs(aFont, data.mRange, &pt,
|
||||
aProvider, aRange, aParams, aOrientation);
|
||||
aParams.context->Restore();
|
||||
aParams.context->PopClip();
|
||||
|
||||
if (aParams.isVerticalRun) {
|
||||
aPt->y += aParams.direction * data.mPartWidth;
|
||||
|
|
|
@ -122,12 +122,17 @@ from its prototype:
|
|||
|
||||
This property is writable, so you can change the source map URL by
|
||||
setting it. All Debugger.Source objects referencing the same
|
||||
source will see the change. Setting an empty string has no affect
|
||||
source will see the change. Setting an empty string has no effect
|
||||
and will not change existing value.
|
||||
|
||||
**If the instance refers to WebAssembly code**, `null`. Attempts to write
|
||||
to this property throw a `TypeError`.
|
||||
|
||||
`displayURL`
|
||||
: If the script had a special `//# sourceURL` comment, as described in
|
||||
the source maps specification, then this property's value holds
|
||||
the string that was given. Otherwise, this is `null`.
|
||||
|
||||
`element`
|
||||
: The [`Debugger.Object`][object] instance referring to the DOM element to which
|
||||
this source code belongs, if any, or `undefined` if it belongs to no DOM
|
||||
|
|
|
@ -935,30 +935,41 @@ nsLayoutUtils::GetCurrentAPZResolutionScale(nsIPresShell* aShell) {
|
|||
// Return the maximum displayport size, based on the LayerManager's maximum
|
||||
// supported texture size. The result is in app units.
|
||||
static nscoord
|
||||
GetMaxDisplayPortSize(nsIContent* aContent)
|
||||
GetMaxDisplayPortSize(nsIContent* aContent, nsPresContext* aFallbackPrescontext)
|
||||
{
|
||||
MOZ_ASSERT(!gfxPrefs::LayersTilesEnabled(), "Do not clamp displayports if tiling is enabled");
|
||||
|
||||
// Pick a safe maximum displayport size for sanity purposes. This is the
|
||||
// lowest maximum texture size on tileless-platforms (Windows, D3D10).
|
||||
// If the gfx.max-texture-size pref is set, further restrict the displayport
|
||||
// size to fit within that, because the compositor won't upload stuff larger
|
||||
// than this size.
|
||||
nscoord safeMaximum = aFallbackPrescontext
|
||||
? aFallbackPrescontext->DevPixelsToAppUnits(
|
||||
std::min(8192, gfxPlatform::MaxTextureSize()))
|
||||
: nscoord_MAX;
|
||||
|
||||
nsIFrame* frame = aContent->GetPrimaryFrame();
|
||||
if (!frame) {
|
||||
return nscoord_MAX;
|
||||
return safeMaximum;
|
||||
}
|
||||
frame = nsLayoutUtils::GetDisplayRootFrame(frame);
|
||||
|
||||
nsIWidget* widget = frame->GetNearestWidget();
|
||||
if (!widget) {
|
||||
return nscoord_MAX;
|
||||
return safeMaximum;
|
||||
}
|
||||
LayerManager* lm = widget->GetLayerManager();
|
||||
if (!lm) {
|
||||
return nscoord_MAX;
|
||||
return safeMaximum;
|
||||
}
|
||||
nsPresContext* presContext = frame->PresContext();
|
||||
|
||||
int32_t maxSizeInDevPixels = lm->GetMaxTextureSize();
|
||||
if (maxSizeInDevPixels < 0 || maxSizeInDevPixels == INT_MAX) {
|
||||
return nscoord_MAX;
|
||||
return safeMaximum;
|
||||
}
|
||||
maxSizeInDevPixels = std::min(maxSizeInDevPixels, gfxPlatform::MaxTextureSize());
|
||||
return presContext->DevPixelsToAppUnits(maxSizeInDevPixels);
|
||||
}
|
||||
|
||||
|
@ -1085,12 +1096,8 @@ GetDisplayPortFromMarginsData(nsIContent* aContent,
|
|||
} else {
|
||||
// Calculate the displayport to make sure we fit within the max texture size
|
||||
// when not tiling.
|
||||
nscoord maxSizeAppUnits = GetMaxDisplayPortSize(aContent);
|
||||
if (maxSizeAppUnits == nscoord_MAX) {
|
||||
// Pick a safe maximum displayport size for sanity purposes. This is the
|
||||
// lowest maximum texture size on tileless-platforms (Windows, D3D10).
|
||||
maxSizeAppUnits = presContext->DevPixelsToAppUnits(8192);
|
||||
}
|
||||
nscoord maxSizeAppUnits = GetMaxDisplayPortSize(aContent, presContext);
|
||||
MOZ_ASSERT(maxSizeAppUnits < nscoord_MAX);
|
||||
|
||||
// The alignment code can round up to 3 tiles, we want to make sure
|
||||
// that the displayport can grow by up to 3 tiles without going
|
||||
|
@ -1279,9 +1286,9 @@ GetDisplayPortImpl(nsIContent* aContent, nsRect* aResult, float aMultiplier)
|
|||
if (!gfxPrefs::LayersTilesEnabled()) {
|
||||
// Either we should have gotten a valid rect directly from the displayport
|
||||
// base, or we should have computed a valid rect from the margins.
|
||||
NS_ASSERTION(result.width <= GetMaxDisplayPortSize(aContent),
|
||||
NS_ASSERTION(result.width <= GetMaxDisplayPortSize(aContent, nullptr),
|
||||
"Displayport must be a valid texture size");
|
||||
NS_ASSERTION(result.height <= GetMaxDisplayPortSize(aContent),
|
||||
NS_ASSERTION(result.height <= GetMaxDisplayPortSize(aContent, nullptr),
|
||||
"Displayport must be a valid texture size");
|
||||
}
|
||||
|
||||
|
|
|
@ -5245,16 +5245,15 @@ nsDisplayText::RenderToContext(gfxContext* aCtx, nsDisplayListBuilder* aBuilder,
|
|||
LayoutDeviceRect::FromAppUnits(mVisibleRect, A2D);
|
||||
extraVisible.Inflate(1);
|
||||
|
||||
gfxContextAutoSaveRestore save(aCtx);
|
||||
|
||||
gfxRect pixelVisible(extraVisible.x, extraVisible.y,
|
||||
extraVisible.width, extraVisible.height);
|
||||
pixelVisible.Inflate(2);
|
||||
pixelVisible.RoundOut();
|
||||
|
||||
if (!aBuilder->IsForGenerateGlyphMask() &&
|
||||
!aBuilder->IsForPaintingSelectionBG() &&
|
||||
!aIsRecording) {
|
||||
bool willClip = !aBuilder->IsForGenerateGlyphMask() &&
|
||||
!aBuilder->IsForPaintingSelectionBG() &&
|
||||
!aIsRecording;
|
||||
if (willClip) {
|
||||
aCtx->NewPath();
|
||||
aCtx->Rectangle(pixelVisible);
|
||||
aCtx->Clip();
|
||||
|
@ -5263,17 +5262,20 @@ nsDisplayText::RenderToContext(gfxContext* aCtx, nsDisplayListBuilder* aBuilder,
|
|||
NS_ASSERTION(mVisIStartEdge >= 0, "illegal start edge");
|
||||
NS_ASSERTION(mVisIEndEdge >= 0, "illegal end edge");
|
||||
|
||||
gfxContextMatrixAutoSaveRestore matrixSR;
|
||||
|
||||
nsPoint framePt = ToReferenceFrame();
|
||||
if (f->StyleContext()->IsTextCombined()) {
|
||||
float scaleFactor = GetTextCombineScaleFactor(f);
|
||||
if (scaleFactor != 1.0f) {
|
||||
matrixSR.SetContext(aCtx);
|
||||
// Setup matrix to compress text for text-combine-upright if
|
||||
// necessary. This is done here because we want selection be
|
||||
// compressed at the same time as text.
|
||||
gfxPoint pt = nsLayoutUtils::PointToGfxPoint(framePt, A2D);
|
||||
gfxMatrix mat = aCtx->CurrentMatrix()
|
||||
.PreTranslate(pt).PreScale(scaleFactor, 1.0).PreTranslate(-pt);
|
||||
aCtx->SetMatrix(mat);
|
||||
aCtx->SetMatrix (mat);
|
||||
}
|
||||
}
|
||||
nsTextFrame::PaintTextParams params(aCtx);
|
||||
|
@ -5290,6 +5292,10 @@ nsDisplayText::RenderToContext(gfxContext* aCtx, nsDisplayListBuilder* aBuilder,
|
|||
}
|
||||
|
||||
f->PaintText(params, *this, mOpacity);
|
||||
|
||||
if (willClip) {
|
||||
aCtx->PopClip();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<style>
|
||||
.bar:before {
|
||||
content:'SHOULD NOT BE ORANGE'
|
||||
}
|
||||
.foo:before {
|
||||
content:'SHOULD BE ORANGE'
|
||||
}
|
||||
.foo:before {
|
||||
background-color: orange;
|
||||
}
|
||||
</style>
|
||||
<div class="bar"></div>
|
||||
<div class="foo"></div>
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<style>
|
||||
.bar:before {
|
||||
content:'SHOULD NOT BE ORANGE'
|
||||
}
|
||||
.foo:before {
|
||||
content:'SHOULD BE ORANGE'
|
||||
}
|
||||
:-moz-any(.foo):before {
|
||||
background-color: orange;
|
||||
}
|
||||
</style>
|
||||
<div class="bar"></div>
|
||||
<div class="foo"></div>
|
|
@ -2035,3 +2035,4 @@ fails-if(!stylo||styloVsGecko) == 1365162-1.html 1365162-1-ref.html
|
|||
needs-focus == 1377447-1.html 1377447-1-ref.html
|
||||
needs-focus != 1377447-1.html 1377447-2.html
|
||||
== 1379041.html 1379041-ref.html
|
||||
== 1379696.html 1379696-ref.html
|
||||
|
|
|
@ -228,7 +228,7 @@ asserts(0-10) == grid-fragmentation-015.html grid-fragmentation-015-ref.html # b
|
|||
== grid-fragmentation-dyn4-004.html grid-fragmentation-004-ref.html
|
||||
== grid-fragmentation-dyn4-005.html grid-fragmentation-005-ref.html
|
||||
== grid-fragmentation-dyn5-005.html grid-fragmentation-005-ref.html
|
||||
skip-if(styloVsGecko) == grid-fragmentation-dyn1-006.html grid-fragmentation-006-ref.html
|
||||
== grid-fragmentation-dyn1-006.html grid-fragmentation-006-ref.html
|
||||
== grid-fragmentation-dyn3-007.html grid-fragmentation-007-ref.html
|
||||
== grid-fragmentation-dyn5-007.html grid-fragmentation-007-ref.html
|
||||
== grid-fragmentation-dyn5-008.html grid-fragmentation-008-ref.html
|
||||
|
|
|
@ -519,34 +519,6 @@ CSSStyleSheet::EnabledStateChangedInternal()
|
|||
ClearRuleCascades();
|
||||
}
|
||||
|
||||
uint64_t
|
||||
CSSStyleSheet::FindOwningWindowInnerID() const
|
||||
{
|
||||
uint64_t windowID = 0;
|
||||
if (mDocument) {
|
||||
windowID = mDocument->InnerWindowID();
|
||||
}
|
||||
|
||||
if (windowID == 0 && mOwningNode) {
|
||||
windowID = mOwningNode->OwnerDoc()->InnerWindowID();
|
||||
}
|
||||
|
||||
if (windowID == 0 && mOwnerRule) {
|
||||
RefPtr<StyleSheet> sheet =
|
||||
static_cast<css::Rule*>(mOwnerRule)->GetStyleSheet();
|
||||
if (sheet) {
|
||||
windowID = sheet->AsGecko()->FindOwningWindowInnerID();
|
||||
}
|
||||
}
|
||||
|
||||
if (windowID == 0 && mParent) {
|
||||
CSSStyleSheet* parentAsCSS = mParent->AsGecko();
|
||||
windowID = parentAsCSS->FindOwningWindowInnerID();
|
||||
}
|
||||
|
||||
return windowID;
|
||||
}
|
||||
|
||||
void
|
||||
CSSStyleSheet::AppendStyleRule(css::Rule* aRule)
|
||||
{
|
||||
|
|
|
@ -101,8 +101,6 @@ public:
|
|||
|
||||
bool HasRules() const;
|
||||
|
||||
// Find the ID of the owner inner window.
|
||||
uint64_t FindOwningWindowInnerID() const;
|
||||
#ifdef DEBUG
|
||||
void List(FILE* out = stdout, int32_t aIndent = 0) const override;
|
||||
#endif
|
||||
|
|
|
@ -139,7 +139,7 @@ ErrorReporter::ReleaseGlobals()
|
|||
}
|
||||
|
||||
ErrorReporter::ErrorReporter(const nsCSSScanner& aScanner,
|
||||
const CSSStyleSheet* aSheet,
|
||||
const StyleSheet* aSheet,
|
||||
const Loader* aLoader,
|
||||
nsIURI* aURI)
|
||||
: mScanner(&aScanner), mSheet(aSheet), mLoader(aLoader), mURI(aURI),
|
||||
|
@ -148,6 +148,15 @@ ErrorReporter::ErrorReporter(const nsCSSScanner& aScanner,
|
|||
{
|
||||
}
|
||||
|
||||
ErrorReporter::ErrorReporter(const StyleSheet* aSheet,
|
||||
const Loader* aLoader,
|
||||
nsIURI* aURI)
|
||||
: mScanner(nullptr), mSheet(aSheet), mLoader(aLoader), mURI(aURI),
|
||||
mInnerWindowID(0), mErrorLineNumber(0), mPrevErrorLineNumber(0),
|
||||
mErrorColNumber(0)
|
||||
{
|
||||
}
|
||||
|
||||
ErrorReporter::~ErrorReporter()
|
||||
{
|
||||
// Schedule deferred cleanup for cached data. We want to strike a
|
||||
|
@ -228,13 +237,33 @@ ErrorReporter::OutputError()
|
|||
}
|
||||
|
||||
void
|
||||
ErrorReporter::OutputError(uint32_t aLineNumber, uint32_t aLineOffset)
|
||||
ErrorReporter::OutputError(uint32_t aLineNumber, uint32_t aColNumber)
|
||||
{
|
||||
mErrorLineNumber = aLineNumber;
|
||||
mErrorColNumber = aLineOffset;
|
||||
mErrorColNumber = aColNumber;
|
||||
OutputError();
|
||||
}
|
||||
|
||||
// When Stylo's CSS parser is in use, this reporter does not have access to the CSS parser's
|
||||
// state. The users of ErrorReporter need to provide:
|
||||
// - the line number of the error
|
||||
// - the column number of the error
|
||||
// - the complete source line containing the invalid CSS
|
||||
|
||||
void
|
||||
ErrorReporter::OutputError(uint32_t aLineNumber,
|
||||
uint32_t aColNumber,
|
||||
const nsACString& aSourceLine)
|
||||
{
|
||||
mErrorLine.Truncate();
|
||||
// This could be a really long string for minified CSS; just leave it empty if we OOM.
|
||||
if (!AppendUTF8toUTF16(aSourceLine, mErrorLine, fallible)) {
|
||||
mErrorLine.Truncate();
|
||||
}
|
||||
mPrevErrorLineNumber = aLineNumber;
|
||||
OutputError(aLineNumber, aColNumber);
|
||||
}
|
||||
|
||||
void
|
||||
ErrorReporter::ClearError()
|
||||
{
|
||||
|
@ -248,15 +277,15 @@ ErrorReporter::AddToError(const nsString &aErrorText)
|
|||
|
||||
if (mError.IsEmpty()) {
|
||||
mError = aErrorText;
|
||||
mErrorLineNumber = mScanner->GetLineNumber();
|
||||
mErrorColNumber = mScanner->GetColumnNumber();
|
||||
mErrorLineNumber = mScanner ? mScanner->GetLineNumber() : 0;
|
||||
mErrorColNumber = mScanner ? mScanner->GetColumnNumber() : 0;
|
||||
// Retrieve the error line once per line, and reuse the same nsString
|
||||
// for all errors on that line. That causes the text of the line to
|
||||
// be shared among all the nsIScriptError objects.
|
||||
if (mErrorLine.IsEmpty() || mErrorLineNumber != mPrevErrorLineNumber) {
|
||||
// Be careful here: the error line might be really long and OOM
|
||||
// when we try to make a copy here. If so, just leave it empty.
|
||||
if (!mErrorLine.Assign(mScanner->GetCurrentLine(), fallible)) {
|
||||
if (!mScanner || !mErrorLine.Assign(mScanner->GetCurrentLine(), fallible)) {
|
||||
mErrorLine.Truncate();
|
||||
}
|
||||
mPrevErrorLineNumber = mErrorLineNumber;
|
||||
|
@ -295,6 +324,21 @@ ErrorReporter::ReportUnexpected(const char *aMessage,
|
|||
AddToError(str);
|
||||
}
|
||||
|
||||
void
|
||||
ErrorReporter::ReportUnexpectedUnescaped(const char *aMessage,
|
||||
const nsAutoString& aParam)
|
||||
{
|
||||
if (!ShouldReportErrors()) return;
|
||||
|
||||
const char16_t *params[1] = { aParam.get() };
|
||||
|
||||
nsAutoString str;
|
||||
sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
|
||||
params, ArrayLength(params),
|
||||
getter_Copies(str));
|
||||
AddToError(str);
|
||||
}
|
||||
|
||||
void
|
||||
ErrorReporter::ReportUnexpected(const char *aMessage,
|
||||
const nsCSSToken &aToken)
|
||||
|
@ -303,13 +347,7 @@ ErrorReporter::ReportUnexpected(const char *aMessage,
|
|||
|
||||
nsAutoString tokenString;
|
||||
aToken.AppendToString(tokenString);
|
||||
const char16_t *params[1] = { tokenString.get() };
|
||||
|
||||
nsAutoString str;
|
||||
sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
|
||||
params, ArrayLength(params),
|
||||
getter_Copies(str));
|
||||
AddToError(str);
|
||||
ReportUnexpectedUnescaped(aMessage, tokenString);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -26,10 +26,13 @@ class Loader;
|
|||
|
||||
// If CSS_REPORT_PARSE_ERRORS is not defined, all of this class's
|
||||
// methods become inline stubs.
|
||||
class MOZ_STACK_CLASS ErrorReporter {
|
||||
class ErrorReporter {
|
||||
public:
|
||||
ErrorReporter(const nsCSSScanner &aScanner,
|
||||
const CSSStyleSheet *aSheet,
|
||||
const StyleSheet *aSheet,
|
||||
const Loader *aLoader,
|
||||
nsIURI *aURI);
|
||||
ErrorReporter(const StyleSheet *aSheet,
|
||||
const Loader *aLoader,
|
||||
nsIURI *aURI);
|
||||
~ErrorReporter();
|
||||
|
@ -38,6 +41,7 @@ public:
|
|||
|
||||
void OutputError();
|
||||
void OutputError(uint32_t aLineNumber, uint32_t aLineOffset);
|
||||
void OutputError(uint32_t aLineNumber, uint32_t aLineOffset, const nsACString& aSource);
|
||||
void ClearError();
|
||||
|
||||
// In all overloads of ReportUnexpected, aMessage is a stringbundle
|
||||
|
@ -50,6 +54,9 @@ public:
|
|||
void ReportUnexpected(const char *aMessage, const nsString& aParam);
|
||||
// one parameter, a token
|
||||
void ReportUnexpected(const char *aMessage, const nsCSSToken& aToken);
|
||||
// one parameter which has already been escaped appropriately
|
||||
void ReportUnexpectedUnescaped(const char *aMessage,
|
||||
const nsAutoString& aParam);
|
||||
// two parameters, a token and a character, in that order
|
||||
void ReportUnexpected(const char *aMessage, const nsCSSToken& aToken,
|
||||
char16_t aChar);
|
||||
|
@ -71,7 +78,7 @@ private:
|
|||
nsString mErrorLine;
|
||||
nsString mFileName;
|
||||
const nsCSSScanner *mScanner;
|
||||
const CSSStyleSheet *mSheet;
|
||||
const StyleSheet *mSheet;
|
||||
const Loader *mLoader;
|
||||
nsIURI *mURI;
|
||||
uint64_t mInnerWindowID;
|
||||
|
|
|
@ -215,7 +215,8 @@ SERVO_BINDING_FUNC(Servo_ParseProperty,
|
|||
nsCSSPropertyID property, const nsACString* value,
|
||||
RawGeckoURLExtraData* data,
|
||||
mozilla::ParsingMode parsing_mode,
|
||||
nsCompatibility quirks_mode)
|
||||
nsCompatibility quirks_mode,
|
||||
mozilla::css::Loader* loader)
|
||||
SERVO_BINDING_FUNC(Servo_ParseEasing, bool,
|
||||
const nsAString* easing,
|
||||
RawGeckoURLExtraData* data,
|
||||
|
@ -310,7 +311,8 @@ SERVO_BINDING_FUNC(Servo_AnimationValue_Compute,
|
|||
SERVO_BINDING_FUNC(Servo_ParseStyleAttribute, RawServoDeclarationBlockStrong,
|
||||
const nsACString* data,
|
||||
RawGeckoURLExtraData* extra_data,
|
||||
nsCompatibility quirks_mode)
|
||||
nsCompatibility quirks_mode,
|
||||
mozilla::css::Loader* loader)
|
||||
SERVO_BINDING_FUNC(Servo_DeclarationBlock_CreateEmpty,
|
||||
RawServoDeclarationBlockStrong)
|
||||
SERVO_BINDING_FUNC(Servo_DeclarationBlock_Clone, RawServoDeclarationBlockStrong,
|
||||
|
@ -344,14 +346,16 @@ SERVO_BINDING_FUNC(Servo_DeclarationBlock_SetProperty, bool,
|
|||
const nsACString* value, bool is_important,
|
||||
RawGeckoURLExtraData* data,
|
||||
mozilla::ParsingMode parsing_mode,
|
||||
nsCompatibility quirks_mode)
|
||||
nsCompatibility quirks_mode,
|
||||
mozilla::css::Loader* loader)
|
||||
SERVO_BINDING_FUNC(Servo_DeclarationBlock_SetPropertyById, bool,
|
||||
RawServoDeclarationBlockBorrowed declarations,
|
||||
nsCSSPropertyID property,
|
||||
const nsACString* value, bool is_important,
|
||||
RawGeckoURLExtraData* data,
|
||||
mozilla::ParsingMode parsing_mode,
|
||||
nsCompatibility quirks_mode)
|
||||
nsCompatibility quirks_mode,
|
||||
mozilla::css::Loader* loader)
|
||||
SERVO_BINDING_FUNC(Servo_DeclarationBlock_RemoveProperty, void,
|
||||
RawServoDeclarationBlockBorrowed declarations,
|
||||
const nsACString* property)
|
||||
|
@ -485,8 +489,7 @@ SERVO_BINDING_FUNC(Servo_NoteExplicitHints, void, RawGeckoElementBorrowed elemen
|
|||
SERVO_BINDING_FUNC(Servo_TakeChangeHint, nsChangeHint, RawGeckoElementBorrowed element)
|
||||
SERVO_BINDING_FUNC(Servo_ResolveStyle, ServoComputedValuesStrong,
|
||||
RawGeckoElementBorrowed element,
|
||||
RawServoStyleSetBorrowed set,
|
||||
bool allow_stale)
|
||||
RawServoStyleSetBorrowed set)
|
||||
SERVO_BINDING_FUNC(Servo_ResolvePseudoStyle, ServoComputedValuesStrong,
|
||||
RawGeckoElementBorrowed element,
|
||||
mozilla::CSSPseudoElementType pseudo_type,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "mozilla/ServoBindings.h"
|
||||
|
||||
#include "ChildIterator.h"
|
||||
#include "ErrorReporter.h"
|
||||
#include "GeckoProfiler.h"
|
||||
#include "gfxFontFamilyList.h"
|
||||
#include "nsAnimationManager.h"
|
||||
|
@ -70,6 +71,7 @@
|
|||
#endif
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::css;
|
||||
using namespace mozilla::dom;
|
||||
|
||||
#define SERVO_ARC_TYPE(name_, type_) \
|
||||
|
@ -2631,3 +2633,38 @@ Gecko_SetJemallocThreadLocalArena(bool enabled)
|
|||
#include "ServoBindingList.h"
|
||||
#undef SERVO_BINDING_FUNC
|
||||
#endif
|
||||
|
||||
ErrorReporter*
|
||||
Gecko_CreateCSSErrorReporter(ServoStyleSheet* sheet,
|
||||
Loader* loader,
|
||||
nsIURI* uri)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return new ErrorReporter(sheet, loader, uri);
|
||||
}
|
||||
|
||||
void
|
||||
Gecko_DestroyCSSErrorReporter(ErrorReporter* reporter)
|
||||
{
|
||||
delete reporter;
|
||||
}
|
||||
|
||||
void
|
||||
Gecko_ReportUnexpectedCSSError(ErrorReporter* reporter,
|
||||
const char* message,
|
||||
const char* param,
|
||||
uint32_t paramLen,
|
||||
const char* source,
|
||||
uint32_t sourceLen,
|
||||
uint32_t lineNumber,
|
||||
uint32_t colNumber,
|
||||
nsIURI* uri)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsDependentCSubstring paramValue(param, paramLen);
|
||||
nsAutoString wideParam = NS_ConvertUTF8toUTF16(paramValue);
|
||||
reporter->ReportUnexpectedUnescaped(message, wideParam);
|
||||
nsDependentCSubstring sourceValue(source, sourceLen);
|
||||
reporter->OutputError(lineNumber, colNumber, sourceValue);
|
||||
}
|
||||
|
|
|
@ -639,6 +639,20 @@ void Gecko_SetJemallocThreadLocalArena(bool enabled);
|
|||
#include "mozilla/ServoBindingList.h"
|
||||
#undef SERVO_BINDING_FUNC
|
||||
|
||||
mozilla::css::ErrorReporter* Gecko_CreateCSSErrorReporter(mozilla::ServoStyleSheet* sheet,
|
||||
mozilla::css::Loader* loader,
|
||||
nsIURI* uri);
|
||||
void Gecko_DestroyCSSErrorReporter(mozilla::css::ErrorReporter* reporter);
|
||||
void Gecko_ReportUnexpectedCSSError(mozilla::css::ErrorReporter* reporter,
|
||||
const char* message,
|
||||
const char* param,
|
||||
uint32_t paramLen,
|
||||
const char* source,
|
||||
uint32_t sourceLen,
|
||||
uint32_t lineNumber,
|
||||
uint32_t colNumber,
|
||||
nsIURI* aURI);
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // mozilla_ServoBindings_h
|
||||
|
|
|
@ -56,6 +56,7 @@ headers = [
|
|||
"mozilla/Keyframe.h",
|
||||
"mozilla/ServoElementSnapshot.h",
|
||||
"mozilla/ServoElementSnapshotTable.h",
|
||||
"mozilla/css/ErrorReporter.h",
|
||||
"mozilla/dom/Element.h",
|
||||
"mozilla/dom/ChildIterator.h",
|
||||
"mozilla/dom/NameSpaceConstants.h",
|
||||
|
@ -122,6 +123,7 @@ whitelist-types = [
|
|||
"mozilla::ServoElementSnapshot.*",
|
||||
"mozilla::ServoStyleSheetInner",
|
||||
"mozilla::CSSPseudoClassType",
|
||||
"mozilla::css::ErrorReporter",
|
||||
"mozilla::css::SheetParsingMode",
|
||||
"mozilla::css::URLMatchingFunction",
|
||||
"mozilla::dom::IterationCompositeOperation",
|
||||
|
@ -329,6 +331,7 @@ raw-lines = [
|
|||
whitelist-functions = ["Servo_.*", "Gecko_.*"]
|
||||
structs-types = [
|
||||
"mozilla::css::GridTemplateAreasValue",
|
||||
"mozilla::css::ErrorReporter",
|
||||
"mozilla::css::ImageValue",
|
||||
"mozilla::css::URLValue",
|
||||
"mozilla::css::URLValueData",
|
||||
|
@ -394,6 +397,7 @@ structs-types = [
|
|||
"nsCursorImage",
|
||||
"nsFont",
|
||||
"nsIAtom",
|
||||
"nsIURI",
|
||||
"nsCompatibility",
|
||||
"nsMediaFeature",
|
||||
"nsRestyleHint",
|
||||
|
|
|
@ -14,12 +14,13 @@ namespace mozilla {
|
|||
/* static */ already_AddRefed<ServoDeclarationBlock>
|
||||
ServoDeclarationBlock::FromCssText(const nsAString& aCssText,
|
||||
URLExtraData* aExtraData,
|
||||
nsCompatibility aMode)
|
||||
nsCompatibility aMode,
|
||||
css::Loader* aLoader)
|
||||
{
|
||||
NS_ConvertUTF16toUTF8 value(aCssText);
|
||||
// FIXME (bug 1343964): Figure out a better solution for sending the base uri to servo
|
||||
RefPtr<RawServoDeclarationBlock>
|
||||
raw = Servo_ParseStyleAttribute(&value, aExtraData, aMode).Consume();
|
||||
raw = Servo_ParseStyleAttribute(&value, aExtraData, aMode, aLoader).Consume();
|
||||
RefPtr<ServoDeclarationBlock> decl = new ServoDeclarationBlock(raw.forget());
|
||||
return decl.forget();
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ public:
|
|||
|
||||
static already_AddRefed<ServoDeclarationBlock>
|
||||
FromCssText(const nsAString& aCssText, URLExtraData* aExtraData,
|
||||
nsCompatibility aMode);
|
||||
nsCompatibility aMode, css::Loader* aLoader);
|
||||
|
||||
RawServoDeclarationBlock* Raw() const { return mRaw; }
|
||||
RawServoDeclarationBlock* const* RefRaw() const {
|
||||
|
|
|
@ -37,7 +37,6 @@ using namespace mozilla::dom;
|
|||
|
||||
ServoStyleSet::ServoStyleSet()
|
||||
: mPresContext(nullptr)
|
||||
, mAllowResolveStaleStyles(false)
|
||||
, mAuthorStyleDisabled(false)
|
||||
, mStylistState(StylistState::NotDirty)
|
||||
, mUserFontSetUpdateGeneration(0)
|
||||
|
@ -496,8 +495,8 @@ ServoStyleSet::ResolvePseudoElementStyle(Element* aOriginatingElement,
|
|||
RefPtr<ServoComputedValues> computedValues;
|
||||
if (aPseudoElement) {
|
||||
MOZ_ASSERT(aType == aPseudoElement->GetPseudoElementType());
|
||||
computedValues = Servo_ResolveStyle(aPseudoElement, mRawSet.get(),
|
||||
mAllowResolveStaleStyles).Consume();
|
||||
computedValues = Servo_ResolveStyle(aPseudoElement,
|
||||
mRawSet.get()).Consume();
|
||||
} else {
|
||||
const ServoComputedValues* parentStyle =
|
||||
aParentContext ? aParentContext->ComputedValues() : nullptr;
|
||||
|
@ -1152,8 +1151,7 @@ already_AddRefed<ServoComputedValues>
|
|||
ServoStyleSet::ResolveServoStyle(Element* aElement)
|
||||
{
|
||||
UpdateStylistIfNeeded();
|
||||
return Servo_ResolveStyle(aElement, mRawSet.get(),
|
||||
mAllowResolveStaleStyles).Consume();
|
||||
return Servo_ResolveStyle(aElement, mRawSet.get()).Consume();
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -77,29 +77,6 @@ class ServoStyleSet
|
|||
typedef ServoElementSnapshotTable SnapshotTable;
|
||||
|
||||
public:
|
||||
class AutoAllowStaleStyles
|
||||
{
|
||||
public:
|
||||
explicit AutoAllowStaleStyles(ServoStyleSet* aStyleSet)
|
||||
: mStyleSet(aStyleSet)
|
||||
{
|
||||
if (mStyleSet) {
|
||||
MOZ_ASSERT(!mStyleSet->mAllowResolveStaleStyles);
|
||||
mStyleSet->mAllowResolveStaleStyles = true;
|
||||
}
|
||||
}
|
||||
|
||||
~AutoAllowStaleStyles()
|
||||
{
|
||||
if (mStyleSet) {
|
||||
mStyleSet->mAllowResolveStaleStyles = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ServoStyleSet* mStyleSet;
|
||||
};
|
||||
|
||||
static bool IsInServoTraversal()
|
||||
{
|
||||
// The callers of this function are generally main-thread-only _except_
|
||||
|
@ -603,7 +580,6 @@ private:
|
|||
UniquePtr<RawServoStyleSet> mRawSet;
|
||||
EnumeratedArray<SheetType, SheetType::Count,
|
||||
nsTArray<RefPtr<ServoStyleSheet>>> mSheets;
|
||||
bool mAllowResolveStaleStyles;
|
||||
bool mAuthorStyleDisabled;
|
||||
StylistState mStylistState;
|
||||
uint64_t mUserFontSetUpdateGeneration;
|
||||
|
|
|
@ -611,6 +611,33 @@ StyleSheet::InsertRuleIntoGroup(const nsAString& aRule,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
StyleSheet::FindOwningWindowInnerID() const
|
||||
{
|
||||
uint64_t windowID = 0;
|
||||
if (mDocument) {
|
||||
windowID = mDocument->InnerWindowID();
|
||||
}
|
||||
|
||||
if (windowID == 0 && mOwningNode) {
|
||||
windowID = mOwningNode->OwnerDoc()->InnerWindowID();
|
||||
}
|
||||
|
||||
RefPtr<css::Rule> ownerRule;
|
||||
if (windowID == 0 && (ownerRule = GetDOMOwnerRule())) {
|
||||
RefPtr<StyleSheet> sheet = ownerRule->GetStyleSheet();
|
||||
if (sheet) {
|
||||
windowID = sheet->FindOwningWindowInnerID();
|
||||
}
|
||||
}
|
||||
|
||||
if (windowID == 0 && mParent) {
|
||||
windowID = mParent->FindOwningWindowInnerID();
|
||||
}
|
||||
|
||||
return windowID;
|
||||
}
|
||||
|
||||
void
|
||||
StyleSheet::EnabledStateChanged()
|
||||
{
|
||||
|
|
|
@ -264,6 +264,9 @@ public:
|
|||
nsresult InsertRuleIntoGroup(const nsAString& aRule,
|
||||
css::GroupRule* aGroup, uint32_t aIndex);
|
||||
|
||||
// Find the ID of the owner inner window.
|
||||
uint64_t FindOwningWindowInnerID() const;
|
||||
|
||||
template<typename Func>
|
||||
void EnumerateChildSheets(Func aCallback) {
|
||||
for (StyleSheet* child = GetFirstChild(); child; child = child->mNext) {
|
||||
|
|
|
@ -178,3 +178,4 @@ load 1375812-1.html
|
|||
load 1377053-1.html
|
||||
load 1377256-1.html
|
||||
load 1378814.html
|
||||
load link-transition-before.html
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html class="reftest-wait">
|
||||
<style type="text/css">
|
||||
a {
|
||||
border-bottom: 1px solid transparent;
|
||||
transition: all 2s linear;
|
||||
}
|
||||
a.start {
|
||||
border-bottom: 1px solid #000000;
|
||||
}
|
||||
/* Can be anything, just need to ensure pseudos cascade */
|
||||
:before {
|
||||
color: blue;
|
||||
}
|
||||
</style>
|
||||
<a href="http://www.example.com/">example</a>
|
||||
<script>
|
||||
let a0 = document.querySelectorAll("a")[0];
|
||||
a0.classList.add("start");
|
||||
requestIdleCallback(() => {
|
||||
a0.classList.remove("start");
|
||||
requestIdleCallback(() => {
|
||||
a0.classList.add("start");
|
||||
document.documentElement.removeAttribute("class");
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -25,8 +25,10 @@
|
|||
// OUTPUT_CLASS=nsCSSPseudoElements
|
||||
// MACRO_NAME=CSS_PSEUDO_ELEMENT
|
||||
|
||||
CSS_PSEUDO_ELEMENT(after, ":after", CSS_PSEUDO_ELEMENT_IS_CSS2)
|
||||
CSS_PSEUDO_ELEMENT(before, ":before", CSS_PSEUDO_ELEMENT_IS_CSS2)
|
||||
CSS_PSEUDO_ELEMENT(after, ":after", CSS_PSEUDO_ELEMENT_IS_CSS2 |
|
||||
CSS_PSEUDO_ELEMENT_IS_FLEX_OR_GRID_ITEM)
|
||||
CSS_PSEUDO_ELEMENT(before, ":before", CSS_PSEUDO_ELEMENT_IS_CSS2 |
|
||||
CSS_PSEUDO_ELEMENT_IS_FLEX_OR_GRID_ITEM)
|
||||
|
||||
CSS_PSEUDO_ELEMENT(backdrop, ":backdrop", 0)
|
||||
|
||||
|
|
|
@ -40,6 +40,9 @@
|
|||
// API for creating pseudo-implementing native anonymous content in JS with this
|
||||
// pseudo-element?
|
||||
#define CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC (1<<5)
|
||||
// Does this pseudo-element act like an item for containers (such as flex and
|
||||
// grid containers) and thus needs parent display-based style fixup?
|
||||
#define CSS_PSEUDO_ELEMENT_IS_FLEX_OR_GRID_ITEM (1<<6)
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -112,6 +115,12 @@ public:
|
|||
return PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_IS_JS_CREATED_NAC);
|
||||
}
|
||||
|
||||
static bool PseudoElementIsFlexOrGridItem(const Type aType)
|
||||
{
|
||||
return PseudoElementHasFlags(aType,
|
||||
CSS_PSEUDO_ELEMENT_IS_FLEX_OR_GRID_ITEM);
|
||||
}
|
||||
|
||||
static bool IsEnabled(Type aType, EnabledState aEnabledState)
|
||||
{
|
||||
return !PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_UA_SHEET_ONLY) ||
|
||||
|
|
|
@ -180,6 +180,7 @@ nsDOMCSSAttributeDeclaration::GetServoCSSParsingEnvironment() const
|
|||
return {
|
||||
mElement->GetURLDataForStyleAttr(),
|
||||
mElement->OwnerDoc()->GetCompatibilityMode(),
|
||||
mElement->OwnerDoc()->CSSLoader(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -130,7 +130,7 @@ nsDOMCSSDeclaration::SetCssText(const nsAString& aCssText)
|
|||
}
|
||||
|
||||
newdecl = ServoDeclarationBlock::FromCssText(aCssText, servoEnv.mUrlExtraData,
|
||||
servoEnv.mCompatMode);
|
||||
servoEnv.mCompatMode, servoEnv.mLoader);
|
||||
} else {
|
||||
CSSParsingEnvironment geckoEnv;
|
||||
GetCSSParsingEnvironment(geckoEnv);
|
||||
|
@ -284,19 +284,21 @@ nsDOMCSSDeclaration::GetServoCSSParsingEnvironmentForRule(const css::Rule* aRule
|
|||
{
|
||||
StyleSheet* sheet = aRule ? aRule->GetStyleSheet() : nullptr;
|
||||
if (!sheet) {
|
||||
return { nullptr, eCompatibility_FullStandards };
|
||||
return { nullptr, eCompatibility_FullStandards, nullptr };
|
||||
}
|
||||
|
||||
if (nsIDocument* document = aRule->GetDocument()) {
|
||||
return {
|
||||
sheet->AsServo()->URLData(),
|
||||
document->GetCompatibilityMode(),
|
||||
document->CSSLoader(),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
sheet->AsServo()->URLData(),
|
||||
eCompatibility_FullStandards,
|
||||
nullptr,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -369,7 +371,7 @@ nsDOMCSSDeclaration::ParsePropertyValue(const nsCSSPropertyID aPropID,
|
|||
NS_ConvertUTF16toUTF8 value(aPropValue);
|
||||
return Servo_DeclarationBlock_SetPropertyById(
|
||||
decl->Raw(), aPropID, &value, aIsImportant, env.mUrlExtraData,
|
||||
ParsingMode::Default, env.mCompatMode);
|
||||
ParsingMode::Default, env.mCompatMode, env.mLoader);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -392,7 +394,7 @@ nsDOMCSSDeclaration::ParseCustomPropertyValue(const nsAString& aPropertyName,
|
|||
NS_ConvertUTF16toUTF8 value(aPropValue);
|
||||
return Servo_DeclarationBlock_SetProperty(
|
||||
decl->Raw(), &property, &value, aIsImportant, env.mUrlExtraData,
|
||||
ParsingMode::Default, env.mCompatMode);
|
||||
ParsingMode::Default, env.mCompatMode, env.mLoader);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -153,11 +153,14 @@ protected:
|
|||
{
|
||||
mozilla::URLExtraData* mUrlExtraData;
|
||||
nsCompatibility mCompatMode;
|
||||
mozilla::css::Loader* mLoader;
|
||||
|
||||
ServoCSSParsingEnvironment(mozilla::URLExtraData* aUrlData,
|
||||
nsCompatibility aCompatMode)
|
||||
nsCompatibility aCompatMode,
|
||||
mozilla::css::Loader* aLoader)
|
||||
: mUrlExtraData(aUrlData)
|
||||
, mCompatMode(aCompatMode)
|
||||
, mLoader(aLoader)
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
|
@ -1699,6 +1699,23 @@ nsStyleSet::RuleNodeWithReplacement(Element* aElement,
|
|||
return ruleWalker.CurrentNode();
|
||||
}
|
||||
|
||||
static bool
|
||||
SkipsParentDisplayBasedStyleFixup(nsStyleContext* aStyleContext)
|
||||
{
|
||||
CSSPseudoElementType type = aStyleContext->GetPseudoType();
|
||||
switch (type) {
|
||||
case CSSPseudoElementType::InheritingAnonBox:
|
||||
return nsCSSAnonBoxes::AnonBoxSkipsParentDisplayBasedStyleFixup(
|
||||
aStyleContext->GetPseudo());
|
||||
case CSSPseudoElementType::NonInheritingAnonBox:
|
||||
return true;
|
||||
case CSSPseudoElementType::NotPseudo:
|
||||
return false;
|
||||
default:
|
||||
return !nsCSSPseudoElements::PseudoElementIsFlexOrGridItem(type);
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<nsStyleContext>
|
||||
nsStyleSet::ResolveStyleWithReplacement(Element* aElement,
|
||||
Element* aPseudoElement,
|
||||
|
@ -1769,11 +1786,13 @@ nsStyleSet::ResolveStyleWithReplacement(Element* aElement,
|
|||
#endif
|
||||
}
|
||||
|
||||
if (aElement && aElement->IsRootOfAnonymousSubtree()) {
|
||||
if ((aElement && aElement->IsRootOfAnonymousSubtree()) ||
|
||||
SkipsParentDisplayBasedStyleFixup(aOldStyleContext)) {
|
||||
// For anonymous subtree roots, don't tweak "display" value based on whether
|
||||
// or not the parent is styled as a flex/grid container. (If the parent
|
||||
// has anonymous-subtree kids, then we know it's not actually going to get
|
||||
// a flex/grid container frame, anyway.)
|
||||
// a flex/grid container frame, anyway.) Same for certain anonymous boxes
|
||||
// and most pseudos.
|
||||
flags |= eSkipParentDisplayBasedStyleFixup;
|
||||
}
|
||||
|
||||
|
@ -1945,11 +1964,11 @@ nsStyleSet::ResolvePseudoElementStyleInternal(
|
|||
if (aAnimationFlag == eWithAnimation) {
|
||||
flags |= eDoAnimation;
|
||||
}
|
||||
} else {
|
||||
// Flex and grid containers don't expect to have any pseudo-element children
|
||||
// aside from ::before and ::after. So if we have such a child, we're not
|
||||
// actually in a flex/grid container, and we should skip flex/grid item
|
||||
// style fixup.
|
||||
}
|
||||
|
||||
if (!nsCSSPseudoElements::PseudoElementIsFlexOrGridItem(aType)) {
|
||||
// Only pseudo-elements that act as items in flex and grid containers
|
||||
// have parent display-based style fixup.
|
||||
flags |= eSkipParentDisplayBasedStyleFixup;
|
||||
}
|
||||
|
||||
|
@ -2043,11 +2062,11 @@ nsStyleSet::ProbePseudoElementStyle(Element* aParentElement,
|
|||
if (aType == CSSPseudoElementType::before ||
|
||||
aType == CSSPseudoElementType::after) {
|
||||
flags |= eDoAnimation;
|
||||
} else {
|
||||
// Flex and grid containers don't expect to have any pseudo-element children
|
||||
// aside from ::before and ::after. So if we have such a child, we're not
|
||||
// actually in a flex/grid container, and we should skip flex/grid item
|
||||
// style fixup.
|
||||
}
|
||||
|
||||
if (!nsCSSPseudoElements::PseudoElementIsFlexOrGridItem(aType)) {
|
||||
// Only pseudo-elements that act as items in flex and grid containers
|
||||
// have parent display-based style fixup.
|
||||
flags |= eSkipParentDisplayBasedStyleFixup;
|
||||
}
|
||||
|
||||
|
@ -2502,13 +2521,12 @@ nsStyleSet::ReparentStyleContext(nsStyleContext* aStyleContext,
|
|||
}
|
||||
|
||||
if ((aElement && aElement->IsRootOfAnonymousSubtree()) ||
|
||||
(aStyleContext->IsAnonBox() &&
|
||||
nsCSSAnonBoxes::AnonBoxSkipsParentDisplayBasedStyleFixup(
|
||||
aStyleContext->GetPseudo()))) {
|
||||
SkipsParentDisplayBasedStyleFixup(aStyleContext)) {
|
||||
// For anonymous subtree roots, don't tweak "display" value based on whether
|
||||
// or not the parent is styled as a flex/grid container. (If the parent
|
||||
// has anonymous-subtree kids, then we know it's not actually going to get
|
||||
// a flex/grid container frame, anyway.) Same for certain anonymous boxes.
|
||||
// a flex/grid container frame, anyway.) Same for certain anonymous boxes
|
||||
// and most pseudos.
|
||||
flags |= eSkipParentDisplayBasedStyleFixup;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,9 +38,6 @@ to mochitest command.
|
|||
* Animation support:
|
||||
* SMIL Animation
|
||||
* test_restyles_in_smil_animation.html [2]
|
||||
* console support bug 1352669
|
||||
* test_bug413958.html `monitorConsole` [3]
|
||||
* test_parser_diagnostics_unprintables.html [550]
|
||||
* @namespace support:
|
||||
* test_namespace_rule.html: bug 1355715 [6]
|
||||
* test_font_feature_values_parsing.html: \@font-feature-values support bug 1355721 [107]
|
||||
|
|
|
@ -39,18 +39,19 @@ var tests = [
|
|||
s.color = "black";
|
||||
},
|
||||
];
|
||||
const isStylo = SpecialPowers.getBoolPref('layout.css.servo.enabled', false);
|
||||
var results = [
|
||||
[ { errorMessage: /Unknown property \u2018nosuchprop\u2019/,
|
||||
lineNumber: 1, columnNumber: 14,
|
||||
lineNumber: 1, columnNumber: isStylo ? 4 : 14,
|
||||
sourceLine: "#s1{nosuchprop:auto; color:black}" },
|
||||
{ errorMessage: /Unknown property \u2018nosuchprop\u2019/,
|
||||
lineNumber: 2, columnNumber: 14, sourceLine:
|
||||
lineNumber: 2, columnNumber: isStylo ? 4 : 14, sourceLine:
|
||||
"#s2{nosuchprop:auto; color:black}invalid?sel{}#s3{color:black}" },
|
||||
{ errorMessage: /Ruleset ignored due to bad selector/,
|
||||
lineNumber: 2, columnNumber: 40, sourceLine:
|
||||
lineNumber: 2, columnNumber: isStylo ? 33 : 40, sourceLine:
|
||||
"#s2{nosuchprop:auto; color:black}invalid?sel{}#s3{color:black}" } ],
|
||||
[ { errorMessage: /parsing value for \u2018width\u2019/,
|
||||
lineNumber: 0, columnNumber: 6,
|
||||
lineNumber: 0, columnNumber: isStylo ? 0 : 6,
|
||||
sourceLine: "width:200;color:black" } ],
|
||||
[ { errorMessage: /parsing value for \u2018width\u2019/,
|
||||
lineNumber: 0, columnNumber: 0,
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
// Each "pattern" is tested once with each of the "substitution"s below:
|
||||
// <t>, <i>, and <s> are replaced by the t:, i:, and s: fields of
|
||||
// each substitution object in turn.
|
||||
const patterns = [
|
||||
let patterns = [
|
||||
// REPORT_UNEXPECTED_P (only ever used in contexts where identifier-like
|
||||
// escaping is appropriate)
|
||||
{ i: "<t>|x{}", o: "prefix \u2018<i>\u2019" },
|
||||
|
@ -42,12 +42,20 @@ const patterns = [
|
|||
{ i: "x{ '<t>'}" , o: "declaration but found \u2018'<s>'\u2019." },
|
||||
// _Bad_String
|
||||
{ i: "x{ '<t>\n}", o: "declaration but found \u2018'<s>\u2019." },
|
||||
// _URL
|
||||
{ i: "x{ url('<t>')}", o: "declaration but found \u2018url('<s>')\u2019." },
|
||||
// _Bad_URL
|
||||
{ i: "x{ url('<t>'.)}" , o: "declaration but found \u2018url('<s>'\u2019." }
|
||||
];
|
||||
|
||||
const isStylo = SpecialPowers.getBoolPref('layout.css.servo.enabled', false);
|
||||
|
||||
// Stylo's CSS parser only reports the 'url(' token, not the actual bad URL.
|
||||
if (!isStylo) {
|
||||
patterns.push(
|
||||
// _URL
|
||||
{ i: "x{ url('<t>')}", o: "declaration but found \u2018url('<s>')\u2019." })
|
||||
patterns.push(
|
||||
// _Bad_URL
|
||||
{ i: "x{ url('<t>'.)}" , o: "declaration but found \u2018url('<s>'\u2019." });
|
||||
}
|
||||
|
||||
// Blocks of characters to test, and how they should be escaped when
|
||||
// they appear in identifiers and string constants.
|
||||
const substitutions = [
|
||||
|
|
|
@ -443,24 +443,6 @@ static malloc_mutex_t init_lock = PTHREAD_MUTEX_INITIALIZER;
|
|||
|
||||
typedef struct malloc_bin_stats_s malloc_bin_stats_t;
|
||||
struct malloc_bin_stats_s {
|
||||
/*
|
||||
* Number of allocation requests that corresponded to the size of this
|
||||
* bin.
|
||||
*/
|
||||
uint64_t nrequests;
|
||||
|
||||
/* Total number of runs created for this bin's size class. */
|
||||
uint64_t nruns;
|
||||
|
||||
/*
|
||||
* Total number of runs reused by extracting them from the runs tree for
|
||||
* this bin's size class.
|
||||
*/
|
||||
uint64_t reruns;
|
||||
|
||||
/* High-water mark for this bin. */
|
||||
unsigned long highruns;
|
||||
|
||||
/* Current number of runs in this bin. */
|
||||
unsigned long curruns;
|
||||
};
|
||||
|
@ -470,35 +452,13 @@ struct arena_stats_s {
|
|||
/* Number of bytes currently mapped. */
|
||||
size_t mapped;
|
||||
|
||||
/*
|
||||
* Total number of purge sweeps, total number of madvise calls made,
|
||||
* and total pages purged in order to keep dirty unused memory under
|
||||
* control.
|
||||
*/
|
||||
uint64_t npurge;
|
||||
uint64_t nmadvise;
|
||||
uint64_t purged;
|
||||
#ifdef MALLOC_DECOMMIT
|
||||
/*
|
||||
* Total number of decommit/commit operations, and total number of
|
||||
* pages decommitted.
|
||||
*/
|
||||
uint64_t ndecommit;
|
||||
uint64_t ncommit;
|
||||
uint64_t decommitted;
|
||||
#endif
|
||||
|
||||
/* Current number of committed pages. */
|
||||
size_t committed;
|
||||
|
||||
/* Per-size-category statistics. */
|
||||
size_t allocated_small;
|
||||
uint64_t nmalloc_small;
|
||||
uint64_t ndalloc_small;
|
||||
|
||||
size_t allocated_large;
|
||||
uint64_t nmalloc_large;
|
||||
uint64_t ndalloc_large;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -506,6 +466,14 @@ struct arena_stats_s {
|
|||
* Extent data structures.
|
||||
*/
|
||||
|
||||
enum ChunkType {
|
||||
UNKNOWN_CHUNK,
|
||||
ZEROED_CHUNK, // chunk only contains zeroes
|
||||
ARENA_CHUNK, // used to back arena runs created by arena_run_alloc
|
||||
HUGE_CHUNK, // used to back huge allocations (e.g. huge_malloc)
|
||||
RECYCLED_CHUNK, // chunk has been stored for future use by chunk_recycle
|
||||
};
|
||||
|
||||
/* Tree of extents. */
|
||||
typedef struct extent_node_s extent_node_t;
|
||||
struct extent_node_s {
|
||||
|
@ -521,8 +489,8 @@ struct extent_node_s {
|
|||
/* Total region size. */
|
||||
size_t size;
|
||||
|
||||
/* True if zero-filled; used by chunk recycling code. */
|
||||
bool zeroed;
|
||||
/* What type of chunk is there; used by chunk recycling code. */
|
||||
ChunkType chunk_type;
|
||||
};
|
||||
typedef rb_tree(extent_node_t) extent_tree_t;
|
||||
|
||||
|
@ -795,13 +763,6 @@ struct arena_s {
|
|||
arena_bin_t bins[1]; /* Dynamically sized. */
|
||||
};
|
||||
|
||||
enum ChunkType {
|
||||
UNKNOWN_CHUNK,
|
||||
ARENA_CHUNK, // used to back arena runs created by arena_run_alloc
|
||||
HUGE_CHUNK, // used to back huge allocations (e.g. huge_malloc)
|
||||
RECYCLED_CHUNK, // chunk has been stored for future use by chunk_recycle
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/*
|
||||
* Data.
|
||||
|
@ -1033,8 +994,9 @@ static size_t opt_chunk_2pow = CHUNK_2POW_DEFAULT;
|
|||
* Begin forward declarations.
|
||||
*/
|
||||
|
||||
static void *chunk_alloc(size_t size, size_t alignment, bool base, bool zero);
|
||||
static void chunk_dealloc(void *chunk, size_t size, enum ChunkType type);
|
||||
static void *chunk_alloc(size_t size, size_t alignment, bool base, bool *zeroed=nullptr);
|
||||
static void chunk_dealloc(void *chunk, size_t size, ChunkType chunk_type);
|
||||
static void chunk_ensure_zero(void* ptr, size_t size, bool zeroed);
|
||||
static arena_t *arenas_extend();
|
||||
static void *huge_malloc(size_t size, bool zero);
|
||||
static void *huge_palloc(size_t size, size_t alignment, bool zero);
|
||||
|
@ -1079,43 +1041,24 @@ load_acquire_z(size_t *p)
|
|||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* umax2s() provides minimal integer printing functionality, which is
|
||||
* especially useful for situations where allocation in vsnprintf() calls would
|
||||
* potentially cause deadlock.
|
||||
*/
|
||||
#define UMAX2S_BUFSIZE 65
|
||||
char *
|
||||
umax2s(uintmax_t x, char *s)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
i = UMAX2S_BUFSIZE - 1;
|
||||
s[i] = '\0';
|
||||
do {
|
||||
i--;
|
||||
s[i] = "0123456789"[x % 10];
|
||||
x /= 10;
|
||||
} while (x > 0);
|
||||
return (&s[i]);
|
||||
}
|
||||
|
||||
static void
|
||||
_malloc_message(const char *p1, const char *p2, const char *p3, const char *p4)
|
||||
_malloc_message(const char *p)
|
||||
{
|
||||
#if !defined(MOZ_MEMORY_WINDOWS)
|
||||
#define _write write
|
||||
#endif
|
||||
// Pretend to check _write() errors to suppress gcc warnings about
|
||||
// warn_unused_result annotations in some versions of glibc headers.
|
||||
if (_write(STDERR_FILENO, p1, (unsigned int) strlen(p1)) < 0)
|
||||
return;
|
||||
if (_write(STDERR_FILENO, p2, (unsigned int) strlen(p2)) < 0)
|
||||
return;
|
||||
if (_write(STDERR_FILENO, p3, (unsigned int) strlen(p3)) < 0)
|
||||
return;
|
||||
if (_write(STDERR_FILENO, p4, (unsigned int) strlen(p4)) < 0)
|
||||
return;
|
||||
// Pretend to check _write() errors to suppress gcc warnings about
|
||||
// warn_unused_result annotations in some versions of glibc headers.
|
||||
if (_write(STDERR_FILENO, p, (unsigned int) strlen(p)) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static void
|
||||
_malloc_message(const char *p, Args... args)
|
||||
{
|
||||
_malloc_message(p);
|
||||
_malloc_message(args...);
|
||||
}
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
|
@ -1380,7 +1323,7 @@ base_pages_alloc(size_t minsize)
|
|||
|
||||
MOZ_ASSERT(minsize != 0);
|
||||
csize = CHUNK_CEILING(minsize);
|
||||
base_pages = chunk_alloc(csize, chunksize, true, false);
|
||||
base_pages = chunk_alloc(csize, chunksize, true);
|
||||
if (!base_pages)
|
||||
return (true);
|
||||
base_next_addr = base_pages;
|
||||
|
@ -1545,7 +1488,7 @@ pages_unmap(void *addr, size_t size)
|
|||
{
|
||||
if (VirtualFree(addr, 0, MEM_RELEASE) == 0) {
|
||||
_malloc_message(_getprogname(),
|
||||
": (malloc) Error in VirtualFree()\n", "", "");
|
||||
": (malloc) Error in VirtualFree()\n");
|
||||
if (opt_abort)
|
||||
moz_abort();
|
||||
}
|
||||
|
@ -1923,15 +1866,24 @@ chunk_alloc_mmap(size_t size, size_t alignment)
|
|||
return (ret);
|
||||
}
|
||||
|
||||
bool
|
||||
pages_purge(void *addr, size_t length)
|
||||
/* Purge and release the pages in the chunk of length `length` at `addr` to
|
||||
* the OS.
|
||||
* Returns whether the pages are guaranteed to be full of zeroes when the
|
||||
* function returns.
|
||||
* The force_zero argument explicitly requests that the memory is guaranteed
|
||||
* to be full of zeroes when the function returns.
|
||||
*/
|
||||
static bool
|
||||
pages_purge(void *addr, size_t length, bool force_zero)
|
||||
{
|
||||
bool unzeroed;
|
||||
|
||||
#ifdef MALLOC_DECOMMIT
|
||||
pages_decommit(addr, length);
|
||||
unzeroed = false;
|
||||
return true;
|
||||
#else
|
||||
# ifndef MOZ_MEMORY_LINUX
|
||||
if (force_zero)
|
||||
memset(addr, 0, length);
|
||||
# endif
|
||||
# ifdef MOZ_MEMORY_WINDOWS
|
||||
/*
|
||||
* The region starting at addr may have been allocated in multiple calls
|
||||
|
@ -1947,33 +1899,32 @@ pages_purge(void *addr, size_t length)
|
|||
length -= pages_size;
|
||||
pages_size = std::min(length, chunksize);
|
||||
}
|
||||
unzeroed = true;
|
||||
return force_zero;
|
||||
# else
|
||||
# ifdef MOZ_MEMORY_LINUX
|
||||
# define JEMALLOC_MADV_PURGE MADV_DONTNEED
|
||||
# define JEMALLOC_MADV_ZEROS true
|
||||
# else /* FreeBSD and Darwin. */
|
||||
# define JEMALLOC_MADV_PURGE MADV_FREE
|
||||
# define JEMALLOC_MADV_ZEROS false
|
||||
# define JEMALLOC_MADV_ZEROS force_zero
|
||||
# endif
|
||||
int err = madvise(addr, length, JEMALLOC_MADV_PURGE);
|
||||
unzeroed = (JEMALLOC_MADV_ZEROS == false || err != 0);
|
||||
return JEMALLOC_MADV_ZEROS && err == 0;
|
||||
# undef JEMALLOC_MADV_PURGE
|
||||
# undef JEMALLOC_MADV_ZEROS
|
||||
# endif
|
||||
#endif
|
||||
return (unzeroed);
|
||||
}
|
||||
|
||||
static void *
|
||||
chunk_recycle(extent_tree_t *chunks_szad, extent_tree_t *chunks_ad, size_t size,
|
||||
size_t alignment, bool base, bool *zero)
|
||||
size_t alignment, bool base, bool *zeroed)
|
||||
{
|
||||
void *ret;
|
||||
extent_node_t *node;
|
||||
extent_node_t key;
|
||||
size_t alloc_size, leadsize, trailsize;
|
||||
bool zeroed;
|
||||
ChunkType chunk_type;
|
||||
|
||||
if (base) {
|
||||
/*
|
||||
|
@ -2002,9 +1953,10 @@ chunk_recycle(extent_tree_t *chunks_szad, extent_tree_t *chunks_ad, size_t size,
|
|||
MOZ_ASSERT(node->size >= leadsize + size);
|
||||
trailsize = node->size - leadsize - size;
|
||||
ret = (void *)((uintptr_t)node->addr + leadsize);
|
||||
zeroed = node->zeroed;
|
||||
if (zeroed)
|
||||
*zero = true;
|
||||
chunk_type = node->chunk_type;
|
||||
if (zeroed) {
|
||||
*zeroed = (chunk_type == ZEROED_CHUNK);
|
||||
}
|
||||
/* Remove node from the tree. */
|
||||
extent_tree_szad_remove(chunks_szad, node);
|
||||
extent_tree_ad_remove(chunks_ad, node);
|
||||
|
@ -2028,14 +1980,14 @@ chunk_recycle(extent_tree_t *chunks_szad, extent_tree_t *chunks_ad, size_t size,
|
|||
malloc_mutex_unlock(&chunks_mtx);
|
||||
node = base_node_alloc();
|
||||
if (!node) {
|
||||
chunk_dealloc(ret, size, RECYCLED_CHUNK);
|
||||
chunk_dealloc(ret, size, chunk_type);
|
||||
return nullptr;
|
||||
}
|
||||
malloc_mutex_lock(&chunks_mtx);
|
||||
}
|
||||
node->addr = (void *)((uintptr_t)(ret) + size);
|
||||
node->size = trailsize;
|
||||
node->zeroed = zeroed;
|
||||
node->chunk_type = chunk_type;
|
||||
extent_tree_szad_insert(chunks_szad, node);
|
||||
extent_tree_ad_insert(chunks_ad, node);
|
||||
node = nullptr;
|
||||
|
@ -2049,20 +2001,11 @@ chunk_recycle(extent_tree_t *chunks_szad, extent_tree_t *chunks_ad, size_t size,
|
|||
base_node_dealloc(node);
|
||||
#ifdef MALLOC_DECOMMIT
|
||||
pages_commit(ret, size);
|
||||
#endif
|
||||
if (*zero) {
|
||||
if (zeroed == false)
|
||||
memset(ret, 0, size);
|
||||
#ifdef DEBUG
|
||||
else {
|
||||
size_t i;
|
||||
size_t *p = (size_t *)(uintptr_t)ret;
|
||||
|
||||
for (i = 0; i < size / sizeof(size_t); i++)
|
||||
MOZ_ASSERT(p[i] == 0);
|
||||
}
|
||||
#endif
|
||||
// pages_commit is guaranteed to zero the chunk.
|
||||
if (zeroed) {
|
||||
*zeroed = true;
|
||||
}
|
||||
#endif
|
||||
return (ret);
|
||||
}
|
||||
|
||||
|
@ -2078,8 +2021,15 @@ chunk_recycle(extent_tree_t *chunks_szad, extent_tree_t *chunks_ad, size_t size,
|
|||
#define CAN_RECYCLE(size) true
|
||||
#endif
|
||||
|
||||
/* Allocates `size` bytes of system memory aligned for `alignment`.
|
||||
* `base` indicates whether the memory will be used for the base allocator
|
||||
* (e.g. base_alloc).
|
||||
* `zeroed` is an outvalue that returns whether the allocated memory is
|
||||
* guaranteed to be full of zeroes. It can be omitted when the caller doesn't
|
||||
* care about the result.
|
||||
*/
|
||||
static void *
|
||||
chunk_alloc(size_t size, size_t alignment, bool base, bool zero)
|
||||
chunk_alloc(size_t size, size_t alignment, bool base, bool *zeroed)
|
||||
{
|
||||
void *ret;
|
||||
|
||||
|
@ -2090,11 +2040,13 @@ chunk_alloc(size_t size, size_t alignment, bool base, bool zero)
|
|||
|
||||
if (CAN_RECYCLE(size)) {
|
||||
ret = chunk_recycle(&chunks_szad_mmap, &chunks_ad_mmap,
|
||||
size, alignment, base, &zero);
|
||||
size, alignment, base, zeroed);
|
||||
if (ret)
|
||||
goto RETURN;
|
||||
}
|
||||
ret = chunk_alloc_mmap(size, alignment);
|
||||
if (zeroed)
|
||||
*zeroed = true;
|
||||
if (ret) {
|
||||
goto RETURN;
|
||||
}
|
||||
|
@ -2115,18 +2067,31 @@ RETURN:
|
|||
}
|
||||
|
||||
static void
|
||||
chunk_record(extent_tree_t *chunks_szad, extent_tree_t *chunks_ad, void *chunk,
|
||||
size_t size, enum ChunkType type)
|
||||
chunk_ensure_zero(void* ptr, size_t size, bool zeroed)
|
||||
{
|
||||
if (zeroed == false)
|
||||
memset(ptr, 0, size);
|
||||
#ifdef MOZ_DEBUG
|
||||
else {
|
||||
size_t i;
|
||||
size_t *p = (size_t *)(uintptr_t)ret;
|
||||
|
||||
for (i = 0; i < size / sizeof(size_t); i++)
|
||||
MOZ_ASSERT(p[i] == 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
chunk_record(extent_tree_t *chunks_szad, extent_tree_t *chunks_ad, void *chunk,
|
||||
size_t size, ChunkType chunk_type)
|
||||
{
|
||||
bool unzeroed;
|
||||
extent_node_t *xnode, *node, *prev, *xprev, key;
|
||||
|
||||
unzeroed = pages_purge(chunk, size);
|
||||
|
||||
/* If purge doesn't zero the chunk, only record arena chunks or
|
||||
* previously recycled chunks. */
|
||||
if (unzeroed && type != ARENA_CHUNK && type != RECYCLED_CHUNK) {
|
||||
return;
|
||||
if (chunk_type != ZEROED_CHUNK) {
|
||||
if (pages_purge(chunk, size, chunk_type == HUGE_CHUNK)) {
|
||||
chunk_type = ZEROED_CHUNK;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -2152,7 +2117,9 @@ chunk_record(extent_tree_t *chunks_szad, extent_tree_t *chunks_ad, void *chunk,
|
|||
extent_tree_szad_remove(chunks_szad, node);
|
||||
node->addr = chunk;
|
||||
node->size += size;
|
||||
node->zeroed = (node->zeroed && (unzeroed == false));
|
||||
if (node->chunk_type != chunk_type) {
|
||||
node->chunk_type = RECYCLED_CHUNK;
|
||||
}
|
||||
extent_tree_szad_insert(chunks_szad, node);
|
||||
} else {
|
||||
/* Coalescing forward failed, so insert a new node. */
|
||||
|
@ -2169,7 +2136,7 @@ chunk_record(extent_tree_t *chunks_szad, extent_tree_t *chunks_ad, void *chunk,
|
|||
xnode = nullptr; /* Prevent deallocation below. */
|
||||
node->addr = chunk;
|
||||
node->size = size;
|
||||
node->zeroed = (unzeroed == false);
|
||||
node->chunk_type = chunk_type;
|
||||
extent_tree_ad_insert(chunks_ad, node);
|
||||
extent_tree_szad_insert(chunks_szad, node);
|
||||
}
|
||||
|
@ -2189,7 +2156,9 @@ chunk_record(extent_tree_t *chunks_szad, extent_tree_t *chunks_ad, void *chunk,
|
|||
extent_tree_szad_remove(chunks_szad, node);
|
||||
node->addr = prev->addr;
|
||||
node->size += prev->size;
|
||||
node->zeroed = (node->zeroed && prev->zeroed);
|
||||
if (node->chunk_type != prev->chunk_type) {
|
||||
node->chunk_type = RECYCLED_CHUNK;
|
||||
}
|
||||
extent_tree_szad_insert(chunks_szad, node);
|
||||
|
||||
xprev = prev;
|
||||
|
@ -2222,7 +2191,7 @@ chunk_dalloc_mmap(void *chunk, size_t size)
|
|||
#undef CAN_RECYCLE
|
||||
|
||||
static void
|
||||
chunk_dealloc(void *chunk, size_t size, enum ChunkType type)
|
||||
chunk_dealloc(void *chunk, size_t size, ChunkType type)
|
||||
{
|
||||
|
||||
MOZ_ASSERT(chunk);
|
||||
|
@ -2601,7 +2570,6 @@ arena_run_split(arena_t *arena, arena_run_t *run, size_t size, bool large,
|
|||
# ifdef MALLOC_DECOMMIT
|
||||
pages_commit((void *)((uintptr_t)chunk + ((run_ind + i)
|
||||
<< pagesize_2pow)), (j << pagesize_2pow));
|
||||
arena->stats.ncommit++;
|
||||
# endif
|
||||
|
||||
arena->stats.committed += j;
|
||||
|
@ -2653,9 +2621,21 @@ arena_run_split(arena_t *arena, arena_run_t *run, size_t size, bool large,
|
|||
}
|
||||
|
||||
static void
|
||||
arena_chunk_init(arena_t *arena, arena_chunk_t *chunk)
|
||||
arena_chunk_init(arena_t *arena, arena_chunk_t *chunk, bool zeroed)
|
||||
{
|
||||
size_t i;
|
||||
/* WARNING: The following relies on !zeroed meaning "used to be an arena
|
||||
* chunk".
|
||||
* When the chunk we're initializating as an arena chunk is zeroed, we
|
||||
* mark all runs are decommitted and zeroed.
|
||||
* When it is not, which we can assume means it's a recycled arena chunk,
|
||||
* all it can contain is an arena chunk header (which we're overwriting),
|
||||
* and zeroed or poisoned memory (because a recycled arena chunk will
|
||||
* have been emptied before being recycled). In that case, we can get
|
||||
* away with reusing the chunk as-is, marking all runs as madvised.
|
||||
*/
|
||||
size_t flags = zeroed ? CHUNK_MAP_DECOMMITTED | CHUNK_MAP_ZEROED
|
||||
: CHUNK_MAP_MADVISED;
|
||||
|
||||
arena->stats.mapped += chunksize;
|
||||
|
||||
|
@ -2674,11 +2654,11 @@ arena_chunk_init(arena_t *arena, arena_chunk_t *chunk)
|
|||
|
||||
for (i = 0; i < arena_chunk_header_npages; i++)
|
||||
chunk->map[i].bits = 0;
|
||||
chunk->map[i].bits = arena_maxclass | CHUNK_MAP_DECOMMITTED | CHUNK_MAP_ZEROED;
|
||||
chunk->map[i].bits = arena_maxclass | flags;
|
||||
for (i++; i < chunk_npages-1; i++) {
|
||||
chunk->map[i].bits = CHUNK_MAP_DECOMMITTED | CHUNK_MAP_ZEROED;
|
||||
chunk->map[i].bits = flags;
|
||||
}
|
||||
chunk->map[chunk_npages-1].bits = arena_maxclass | CHUNK_MAP_DECOMMITTED | CHUNK_MAP_ZEROED;
|
||||
chunk->map[chunk_npages-1].bits = arena_maxclass | flags;
|
||||
|
||||
#ifdef MALLOC_DECOMMIT
|
||||
/*
|
||||
|
@ -2686,8 +2666,6 @@ arena_chunk_init(arena_t *arena, arena_chunk_t *chunk)
|
|||
* between dirty pages and committed untouched pages.
|
||||
*/
|
||||
pages_decommit(run, arena_maxclass);
|
||||
arena->stats.ndecommit++;
|
||||
arena->stats.decommitted += (chunk_npages - arena_chunk_header_npages);
|
||||
#endif
|
||||
arena->stats.committed += arena_chunk_header_npages;
|
||||
|
||||
|
@ -2777,12 +2755,13 @@ arena_run_alloc(arena_t *arena, arena_bin_t *bin, size_t size, bool large,
|
|||
* the run.
|
||||
*/
|
||||
{
|
||||
bool zeroed;
|
||||
arena_chunk_t *chunk = (arena_chunk_t *)
|
||||
chunk_alloc(chunksize, chunksize, false, true);
|
||||
chunk_alloc(chunksize, chunksize, false, &zeroed);
|
||||
if (!chunk)
|
||||
return nullptr;
|
||||
|
||||
arena_chunk_init(arena, chunk);
|
||||
arena_chunk_init(arena, chunk, zeroed);
|
||||
run = (arena_run_t *)((uintptr_t)chunk +
|
||||
(arena_chunk_header_npages << pagesize_2pow));
|
||||
}
|
||||
|
@ -2808,8 +2787,6 @@ arena_purge(arena_t *arena, bool all)
|
|||
#endif
|
||||
MOZ_DIAGNOSTIC_ASSERT(all || (arena->ndirty > opt_dirty_max));
|
||||
|
||||
arena->stats.npurge++;
|
||||
|
||||
/*
|
||||
* Iterate downward through chunks until enough dirty memory has been
|
||||
* purged. Terminate as soon as possible in order to minimize the
|
||||
|
@ -2852,8 +2829,6 @@ arena_purge(arena_t *arena, bool all)
|
|||
pages_decommit((void *)((uintptr_t)
|
||||
chunk + (i << pagesize_2pow)),
|
||||
(npages << pagesize_2pow));
|
||||
arena->stats.ndecommit++;
|
||||
arena->stats.decommitted += npages;
|
||||
#endif
|
||||
arena->stats.committed -= npages;
|
||||
|
||||
|
@ -2865,8 +2840,6 @@ arena_purge(arena_t *arena, bool all)
|
|||
madvised = true;
|
||||
# endif
|
||||
#endif
|
||||
arena->stats.nmadvise++;
|
||||
arena->stats.purged += npages;
|
||||
if (arena->ndirty <= (dirty_max >> 1))
|
||||
break;
|
||||
}
|
||||
|
@ -3051,7 +3024,6 @@ arena_bin_nonfull_run_get(arena_t *arena, arena_bin_t *bin)
|
|||
/* run is guaranteed to have available space. */
|
||||
arena_run_tree_remove(&bin->runs, mapelm);
|
||||
run = (arena_run_t *)(mapelm->bits & ~pagesize_mask);
|
||||
bin->stats.reruns++;
|
||||
return (run);
|
||||
}
|
||||
/* No existing runs have any space available. */
|
||||
|
@ -3088,10 +3060,7 @@ arena_bin_nonfull_run_get(arena_t *arena, arena_bin_t *bin)
|
|||
run->magic = ARENA_RUN_MAGIC;
|
||||
#endif
|
||||
|
||||
bin->stats.nruns++;
|
||||
bin->stats.curruns++;
|
||||
if (bin->stats.curruns > bin->stats.highruns)
|
||||
bin->stats.highruns = bin->stats.curruns;
|
||||
return (run);
|
||||
}
|
||||
|
||||
|
@ -3250,8 +3219,6 @@ arena_malloc_small(arena_t *arena, size_t size, bool zero)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bin->stats.nrequests++;
|
||||
arena->stats.nmalloc_small++;
|
||||
arena->stats.allocated_small += size;
|
||||
malloc_spin_unlock(&arena->lock);
|
||||
|
||||
|
@ -3279,7 +3246,6 @@ arena_malloc_large(arena_t *arena, size_t size, bool zero)
|
|||
malloc_spin_unlock(&arena->lock);
|
||||
return nullptr;
|
||||
}
|
||||
arena->stats.nmalloc_large++;
|
||||
arena->stats.allocated_large += size;
|
||||
malloc_spin_unlock(&arena->lock);
|
||||
|
||||
|
@ -3374,7 +3340,6 @@ arena_palloc(arena_t *arena, size_t alignment, size_t size, size_t alloc_size)
|
|||
}
|
||||
}
|
||||
|
||||
arena->stats.nmalloc_large++;
|
||||
arena->stats.allocated_large += size;
|
||||
malloc_spin_unlock(&arena->lock);
|
||||
|
||||
|
@ -3660,7 +3625,6 @@ arena_dalloc_small(arena_t *arena, arena_chunk_t *chunk, void *ptr,
|
|||
}
|
||||
}
|
||||
arena->stats.allocated_small -= size;
|
||||
arena->stats.ndalloc_small++;
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -3672,7 +3636,6 @@ arena_dalloc_large(arena_t *arena, arena_chunk_t *chunk, void *ptr)
|
|||
|
||||
memset(ptr, kAllocPoison, size);
|
||||
arena->stats.allocated_large -= size;
|
||||
arena->stats.ndalloc_large++;
|
||||
|
||||
arena_run_dalloc(arena, (arena_run_t *)ptr, true);
|
||||
}
|
||||
|
@ -3976,7 +3939,7 @@ arenas_fallback()
|
|||
* In practice, this is an extremely unlikely failure.
|
||||
*/
|
||||
_malloc_message(_getprogname(),
|
||||
": (malloc) Error initializing arena\n", "", "");
|
||||
": (malloc) Error initializing arena\n");
|
||||
if (opt_abort)
|
||||
moz_abort();
|
||||
|
||||
|
@ -4053,6 +4016,7 @@ huge_palloc(size_t size, size_t alignment, bool zero)
|
|||
size_t csize;
|
||||
size_t psize;
|
||||
extent_node_t *node;
|
||||
bool zeroed;
|
||||
|
||||
/* Allocate one or more contiguous chunks for this request. */
|
||||
|
||||
|
@ -4067,11 +4031,14 @@ huge_palloc(size_t size, size_t alignment, bool zero)
|
|||
if (!node)
|
||||
return nullptr;
|
||||
|
||||
ret = chunk_alloc(csize, alignment, false, zero);
|
||||
ret = chunk_alloc(csize, alignment, false, &zeroed);
|
||||
if (!ret) {
|
||||
base_node_dealloc(node);
|
||||
return nullptr;
|
||||
}
|
||||
if (zero) {
|
||||
chunk_ensure_zero(ret, csize, zeroed);
|
||||
}
|
||||
|
||||
/* Insert node into huge. */
|
||||
node->addr = ret;
|
||||
|
@ -4314,8 +4281,7 @@ malloc_init_hard(void)
|
|||
#ifdef MALLOC_STATIC_SIZES
|
||||
if (pagesize % (size_t) result) {
|
||||
_malloc_message(_getprogname(),
|
||||
"Compile-time page size does not divide the runtime one.\n",
|
||||
"", "");
|
||||
"Compile-time page size does not divide the runtime one.\n");
|
||||
moz_abort();
|
||||
}
|
||||
#else
|
||||
|
|
|
@ -578,6 +578,7 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
|
|||
// fall through
|
||||
case BOOKMARKS: {
|
||||
trace("Deleting bookmarks: " + uri);
|
||||
// Since we touch multiple records for most deletions, 'deleted' here really means 'affected'.
|
||||
deleted = deleteBookmarks(uri, selection, selectionArgs);
|
||||
deleteUnusedImages(uri);
|
||||
break;
|
||||
|
@ -1679,7 +1680,7 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
|
|||
|
||||
beginWrite(db);
|
||||
|
||||
final int updated = db.update(TABLE_BOOKMARKS, values, inClause, null);
|
||||
int updated = db.update(TABLE_BOOKMARKS, values, inClause, null);
|
||||
if (updated == 0) {
|
||||
trace("No update on URI: " + uri);
|
||||
return updated;
|
||||
|
@ -1702,7 +1703,7 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
|
|||
parentValues.put(Bookmarks.DATE_MODIFIED, lastModified);
|
||||
|
||||
// Bump old/new parent's lastModified timestamps.
|
||||
db.update(TABLE_BOOKMARKS, parentValues,
|
||||
updated += db.update(TABLE_BOOKMARKS, parentValues,
|
||||
Bookmarks._ID + " in (?, ?)",
|
||||
new String[] { String.valueOf(oldParentId), String.valueOf(newParentId) });
|
||||
|
||||
|
@ -2289,28 +2290,27 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
|
|||
|
||||
debug("Marking bookmarks as deleted for URI: " + uri);
|
||||
|
||||
// Bump parent's lastModified timestamp before record deleted.
|
||||
// Deletions of bookmarks almost always affect more than one record, and so we keep track of
|
||||
// number of records changed as opposed to number of records deleted. It's highly unusual,
|
||||
// but not impossible, for a "delete" operation to modify some of the records without
|
||||
// actually marking any as "deleted".
|
||||
// We do this to ensure we correctly fire 'notifyChanged' events whenever > 0 records are touched.
|
||||
int changed = 0;
|
||||
|
||||
// First, bump parents' lastModified timestamp. Running this 'update' query first ensures our
|
||||
// transaction will start off as a 'writer'. Deletion code below performs a SELECT first,
|
||||
// requiring transaction to be upgraded from a reader to a writer, which might result in SQL_BUSY.
|
||||
// NB: this code allows for multi-parent bookmarks.
|
||||
final ContentValues parentValues = new ContentValues();
|
||||
parentValues.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
|
||||
updateBookmarkParents(db, parentValues, selection, selectionArgs);
|
||||
changed += updateBookmarkParents(db, parentValues, selection, selectionArgs);
|
||||
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(Bookmarks.IS_DELETED, 1);
|
||||
values.put(Bookmarks.POSITION, 0);
|
||||
// updateBookmarks() and getBookmarkDescendantGUIDs() both use use selection as a SQL filter,
|
||||
// so we don't set column to null here, or the filtering result might be different because of record changed.
|
||||
// We move all values.putNull() operations into bulkDeleteByBookmarkGUIDs().
|
||||
|
||||
// Doing this UPDATE (or the DELETE above) first ensures that the
|
||||
// first operation within this transaction is a write.
|
||||
// The cleanup call below will do a SELECT first, and thus would
|
||||
// require the transaction to be upgraded from a reader to a writer.
|
||||
updateBookmarks(uri, values, selection, selectionArgs);
|
||||
|
||||
// When deleting bookmarks/folders, we have to delete their all descendant bookmarks/folders as well.
|
||||
// We concatenate all IDs into a clause, and delete them at once.
|
||||
// Finally, deleted everything that needs to be deleted all at once.
|
||||
// We need to compute list of all bookmarks and their descendants first.
|
||||
// We calculate our deletion tree based on 'selection', and so above queries do not null-out
|
||||
// any of the bookmark fields. This will be done in `bulkDeleteByBookmarkGUIDs`.
|
||||
final List<String> guids = getBookmarkDescendantGUIDs(db, selection, selectionArgs);
|
||||
final int updated = bulkDeleteByBookmarkGUIDs(db, guids, TABLE_BOOKMARKS, Bookmarks.GUID);
|
||||
changed += bulkDeleteByBookmarkGUIDs(db, guids, TABLE_BOOKMARKS, Bookmarks.GUID);
|
||||
|
||||
try {
|
||||
cleanUpSomeDeletedRecords(uri, TABLE_BOOKMARKS);
|
||||
|
@ -2318,7 +2318,7 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
|
|||
// We don't care.
|
||||
Log.e(LOGTAG, "Unable to clean up deleted bookmark records: ", e);
|
||||
}
|
||||
return updated;
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,7 +18,12 @@ import org.mozilla.gecko.background.testhelpers.TestRunner;
|
|||
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
|
||||
import org.robolectric.shadows.ShadowContentResolver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
@ -32,7 +37,6 @@ public class BrowserProviderBookmarksTest {
|
|||
|
||||
private static final long INVALID_ID = -1;
|
||||
|
||||
private ShadowContentResolver contentResolver;
|
||||
private ContentProviderClient bookmarksClient;
|
||||
private Uri bookmarksTestUri;
|
||||
private BrowserProvider provider;
|
||||
|
@ -43,7 +47,7 @@ public class BrowserProviderBookmarksTest {
|
|||
provider.onCreate();
|
||||
ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
|
||||
|
||||
contentResolver = new ShadowContentResolver();
|
||||
ShadowContentResolver contentResolver = new ShadowContentResolver();
|
||||
bookmarksClient = contentResolver.acquireContentProviderClient(BrowserContractHelpers.BOOKMARKS_CONTENT_URI);
|
||||
|
||||
bookmarksTestUri = testUri(BrowserContract.Bookmarks.CONTENT_URI);
|
||||
|
@ -55,6 +59,88 @@ public class BrowserProviderBookmarksTest {
|
|||
provider.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBookmarkChangedCountsOnUpdates() throws RemoteException {
|
||||
final long rootId = getBookmarkIdFromGuid(BrowserContract.Bookmarks.MOBILE_FOLDER_GUID);
|
||||
insertBookmark("bookmark-1", "https://www.mozilla-1.org", "guid-1",
|
||||
rootId, BrowserContract.Bookmarks.TYPE_BOOKMARK);
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(BrowserContract.Bookmarks.TITLE, "bookmark-1-2");
|
||||
int changed = bookmarksClient.update(
|
||||
bookmarksTestUri, values, BrowserContract.Bookmarks.GUID + " = ?",
|
||||
new String[] { "guid-1" });
|
||||
|
||||
// Only record itself is changed.
|
||||
assertEquals(1, changed);
|
||||
|
||||
// Test that changing a parent of a record affects three records - record itself, new parent, old parent.
|
||||
insertBookmark("bookmark-1", null, "parent", rootId, BrowserContract.Bookmarks.TYPE_FOLDER);
|
||||
long parentId = getBookmarkIdFromGuid("parent");
|
||||
values = new ContentValues();
|
||||
values.put(BrowserContract.Bookmarks.PARENT, parentId);
|
||||
|
||||
changed = bookmarksClient.update(
|
||||
bookmarksTestUri.buildUpon()
|
||||
.appendQueryParameter(
|
||||
BrowserContract.PARAM_OLD_BOOKMARK_PARENT,
|
||||
String.valueOf(rootId)
|
||||
).build(),
|
||||
values,
|
||||
BrowserContract.Bookmarks.GUID + " = ?",
|
||||
new String[] { "guid-1" }
|
||||
);
|
||||
|
||||
assertEquals(3, changed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBookmarkChangedCountsOnDeletions() throws RemoteException {
|
||||
final long rootId = getBookmarkIdFromGuid(BrowserContract.Bookmarks.MOBILE_FOLDER_GUID);
|
||||
insertBookmark("bookmark-1", "https://www.mozilla-1.org", "guid-1",
|
||||
rootId, BrowserContract.Bookmarks.TYPE_BOOKMARK);
|
||||
|
||||
int changed = bookmarksClient.delete(bookmarksTestUri,
|
||||
BrowserContract.Bookmarks.URL + " = ?",
|
||||
new String[] { "https://www.mozilla-1.org" });
|
||||
|
||||
// Note that we also modify parent folder, and so changed counts must reflect that.
|
||||
assertEquals(2, changed);
|
||||
|
||||
insertBookmark("bookmark-2", "https://www.mozilla-2.org", "guid-2",
|
||||
rootId, BrowserContract.Bookmarks.TYPE_BOOKMARK);
|
||||
insertBookmark("bookmark-3", "https://www.mozilla-3.org", "guid-3",
|
||||
rootId, BrowserContract.Bookmarks.TYPE_BOOKMARK);
|
||||
insertBookmark("bookmark-4", "https://www.mozilla-4.org", "guid-4",
|
||||
rootId, BrowserContract.Bookmarks.TYPE_BOOKMARK);
|
||||
|
||||
changed = bookmarksClient.delete(bookmarksTestUri,
|
||||
BrowserContract.Bookmarks.PARENT + " = ?",
|
||||
new String[] { String.valueOf(rootId) });
|
||||
|
||||
assertEquals(4, changed);
|
||||
|
||||
// Test that we correctly count affected records during deletions of subtrees.
|
||||
final Uri parentUri = insertBookmark("parent", null, "parent", rootId,
|
||||
BrowserContract.Bookmarks.TYPE_FOLDER);
|
||||
final long parentId = Long.parseLong(parentUri.getLastPathSegment());
|
||||
|
||||
insertBookmark("bookmark-5", "https://www.mozilla-2.org", "guid-5",
|
||||
parentId, BrowserContract.Bookmarks.TYPE_BOOKMARK);
|
||||
insertBookmark("bookmark-6", "https://www.mozilla-3.org", "guid-6",
|
||||
parentId, BrowserContract.Bookmarks.TYPE_BOOKMARK);
|
||||
// Directly under the root.
|
||||
insertBookmark("bookmark-7", "https://www.mozilla-4.org", "guid-7",
|
||||
rootId, BrowserContract.Bookmarks.TYPE_BOOKMARK);
|
||||
|
||||
changed = bookmarksClient.delete(bookmarksTestUri,
|
||||
BrowserContract.Bookmarks.GUID + " = ?",
|
||||
new String[] { "parent" });
|
||||
|
||||
// 4 = parent, its two children, mobile root.
|
||||
assertEquals(4, changed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteBookmarkFolder() throws RemoteException {
|
||||
final long rootId = getBookmarkIdFromGuid(BrowserContract.Bookmarks.MOBILE_FOLDER_GUID);
|
||||
|
@ -64,7 +150,7 @@ public class BrowserProviderBookmarksTest {
|
|||
BrowserContract.Bookmarks.TYPE_FOLDER);
|
||||
final long parentId = Long.parseLong(parentUri.getLastPathSegment());
|
||||
|
||||
// Insert 10 bookmarks into the parent
|
||||
// Insert 10 bookmarks into the parent.
|
||||
for (int i = 0; i < 10; i++) {
|
||||
insertBookmark("bookmark-" + i, "https://www.mozilla-" + i + ".org", "guid-" + i,
|
||||
parentId, BrowserContract.Bookmarks.TYPE_BOOKMARK);
|
||||
|
@ -74,15 +160,16 @@ public class BrowserProviderBookmarksTest {
|
|||
BrowserContract.Bookmarks.PARENT + " = ?";
|
||||
final String[] selectionArgs = { String.valueOf(parentId),
|
||||
String.valueOf(parentId) };
|
||||
// Check insertions. We should have 11 records(1 parent and 10 children).
|
||||
// Check insertions. We should have 11 records (1 parent and 10 children).
|
||||
final int inserted = getRowCount(selection, selectionArgs);
|
||||
assertEquals(11, inserted);
|
||||
|
||||
final int deleted = bookmarksClient.delete(bookmarksTestUri,
|
||||
final int changed = bookmarksClient.delete(bookmarksTestUri,
|
||||
BrowserContract.Bookmarks._ID + " = ?",
|
||||
new String[] { String.valueOf(parentId) });
|
||||
// Check if parent and all its descendants are deleted.
|
||||
assertEquals(11, deleted);
|
||||
// We also modify parent's parent, and so that is counted as well.
|
||||
assertEquals(12, changed);
|
||||
|
||||
// Check records are marked as deleted and column are reset.
|
||||
final StringBuilder sb = new StringBuilder(BrowserContract.Bookmarks._ID + " = ? OR ");
|
||||
|
@ -103,11 +190,19 @@ public class BrowserProviderBookmarksTest {
|
|||
where, new String[] { String.valueOf(parentId) }, null);
|
||||
assertNotNull(cursor);
|
||||
assertEquals(11, cursor.getCount());
|
||||
List<String> guids = Arrays.asList("parent", "guid-0", "guid-1", "guid-2", "guid-3", "guid-4", "guid-5", "guid-6", "guid-7", "guid-8", "guid-9");
|
||||
List<String> seenGuids = new ArrayList<>();
|
||||
try {
|
||||
while (cursor.moveToNext()) {
|
||||
final int isDeleted = cursor.getInt(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.IS_DELETED));
|
||||
assertEquals(1, isDeleted);
|
||||
|
||||
final String guid = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.GUID));
|
||||
// Don't depend on iteration order while checking that every guid is correctly preserved.
|
||||
assertTrue(guids.contains(guid));
|
||||
assertFalse(seenGuids.contains(guid));
|
||||
seenGuids.add(guid);
|
||||
|
||||
final int position = cursor.getInt(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.POSITION));
|
||||
assertEquals(0, position);
|
||||
|
||||
|
|
|
@ -656,11 +656,12 @@ public class testBrowserProvider extends ContentProviderTest {
|
|||
public void test() throws Exception {
|
||||
long id = insertOneBookmark();
|
||||
|
||||
int deleted = mProvider.delete(BrowserContract.Bookmarks.CONTENT_URI,
|
||||
int changed = mProvider.delete(BrowserContract.Bookmarks.CONTENT_URI,
|
||||
BrowserContract.Bookmarks._ID + " = ?",
|
||||
new String[] { String.valueOf(id) });
|
||||
|
||||
mAsserter.is((deleted == 1), true, "Inserted bookmark was deleted");
|
||||
// Deletions also affect parents of folders, and so that must be accounted for.
|
||||
mAsserter.is((changed == 2), true, "Inserted bookmark was deleted");
|
||||
|
||||
Cursor c = getBookmarkById(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), id);
|
||||
mAsserter.is(c.moveToFirst(), true, "Deleted bookmark was only marked as deleted");
|
||||
|
@ -686,11 +687,12 @@ public class testBrowserProvider extends ContentProviderTest {
|
|||
"Deleted bookmark GUID is not null");
|
||||
c.close();
|
||||
|
||||
deleted = mProvider.delete(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"),
|
||||
changed = mProvider.delete(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"),
|
||||
BrowserContract.Bookmarks._ID + " = ?",
|
||||
new String[] { String.valueOf(id) });
|
||||
|
||||
mAsserter.is((deleted == 1), true, "Inserted bookmark was deleted");
|
||||
// Deletions from sync skip bumping timestamps of parents.
|
||||
mAsserter.is((changed == 1), true, "Inserted bookmark was deleted");
|
||||
|
||||
c = getBookmarkById(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), id);
|
||||
mAsserter.is(c.moveToFirst(), false, "Inserted bookmark is now actually deleted");
|
||||
|
@ -698,8 +700,8 @@ public class testBrowserProvider extends ContentProviderTest {
|
|||
|
||||
id = insertOneBookmark();
|
||||
|
||||
deleted = mProvider.delete(ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id), null, null);
|
||||
mAsserter.is((deleted == 1), true,
|
||||
changed = mProvider.delete(ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id), null, null);
|
||||
mAsserter.is((changed == 2), true,
|
||||
"Inserted bookmark was deleted using URI with id");
|
||||
|
||||
c = getBookmarkById(id);
|
||||
|
@ -724,13 +726,13 @@ public class testBrowserProvider extends ContentProviderTest {
|
|||
mAsserter.is(c.moveToFirst(), true, "Inserted bookmark found");
|
||||
c.close();
|
||||
|
||||
deleted = 0;
|
||||
changed = 0;
|
||||
try {
|
||||
Uri uri = ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, parentId);
|
||||
deleted = mProvider.delete(appendUriParam(uri, BrowserContract.PARAM_IS_SYNC, "1"), null, null);
|
||||
changed = mProvider.delete(appendUriParam(uri, BrowserContract.PARAM_IS_SYNC, "1"), null, null);
|
||||
} catch(Exception e) {}
|
||||
|
||||
mAsserter.is((deleted == 0), true,
|
||||
mAsserter.is((changed == 0), true,
|
||||
"Should not be able to delete folder that causes orphan bookmarks");
|
||||
}
|
||||
}
|
||||
|
@ -1802,11 +1804,12 @@ public class testBrowserProvider extends ContentProviderTest {
|
|||
mAsserter.is(c.getLong(c.getColumnIndex(BrowserContract.Combined.BOOKMARK_ID)), combinedBookmarkId,
|
||||
"Bookmark id should be set correctly on combined entry");
|
||||
|
||||
int deleted = mProvider.delete(BrowserContract.Bookmarks.CONTENT_URI,
|
||||
int changed = mProvider.delete(BrowserContract.Bookmarks.CONTENT_URI,
|
||||
BrowserContract.Bookmarks._ID + " = ?",
|
||||
new String[] { String.valueOf(combinedBookmarkId) });
|
||||
|
||||
mAsserter.is((deleted == 1), true, "Inserted combined bookmark was deleted");
|
||||
// Deletion of a bookmark also affects its parent, and that must be reflected in the count.
|
||||
mAsserter.is((changed == 2), true, "Inserted combined bookmark was deleted");
|
||||
c.close();
|
||||
|
||||
c = mProvider.query(BrowserContract.Combined.CONTENT_URI, null, "", null, null);
|
||||
|
|
|
@ -56,11 +56,14 @@ class OptionValue(tuple):
|
|||
def __eq__(self, other):
|
||||
# This is to catch naive comparisons against strings and other
|
||||
# types in moz.configure files, as it is really easy to write
|
||||
# value == 'foo'.
|
||||
if not isinstance(other, tuple):
|
||||
raise TypeError('cannot compare a %s against an %s; OptionValue '
|
||||
'instances are tuples - did you mean to compare '
|
||||
'against member elements using [x]?' % (
|
||||
# value == 'foo'. We only raise a TypeError for instances that
|
||||
# have content, because value-less instances (like PositiveOptionValue
|
||||
# and NegativeOptionValue) are common and it is trivial to
|
||||
# compare these.
|
||||
if not isinstance(other, tuple) and len(self):
|
||||
raise TypeError('cannot compare a populated %s against an %s; '
|
||||
'OptionValue instances are tuples - did you mean to '
|
||||
'compare against member elements using [x]?' % (
|
||||
type(other).__name__, type(self).__name__))
|
||||
|
||||
# Allow explicit tuples to be compared.
|
||||
|
|
|
@ -297,6 +297,20 @@ class TestOption(unittest.TestCase):
|
|||
with self.assertRaisesRegexp(TypeError, 'cannot compare a'):
|
||||
val == 'foo'
|
||||
|
||||
# But we allow empty option values to compare otherwise we can't
|
||||
# easily compare value-less types like PositiveOptionValue and
|
||||
# NegativeOptionValue.
|
||||
empty_positive = PositiveOptionValue()
|
||||
empty_negative = NegativeOptionValue()
|
||||
self.assertEqual(empty_positive, ())
|
||||
self.assertEqual(empty_positive, PositiveOptionValue())
|
||||
self.assertEqual(empty_negative, ())
|
||||
self.assertEqual(empty_negative, NegativeOptionValue())
|
||||
self.assertNotEqual(empty_positive, 'foo')
|
||||
self.assertNotEqual(empty_positive, ('foo',))
|
||||
self.assertNotEqual(empty_negative, 'foo')
|
||||
self.assertNotEqual(empty_negative, ('foo',))
|
||||
|
||||
def test_option_value_format(self):
|
||||
val = PositiveOptionValue()
|
||||
self.assertEquals('--with-value', val.format('--with-value'))
|
||||
|
|
|
@ -275,7 +275,7 @@ static const char contentSandboxRules[] = R"(
|
|||
|
||||
; bug 1303987
|
||||
(if (string? debugWriteDir)
|
||||
(allow file-write* (subpath debugWriteDir)))
|
||||
(allow file-write-create file-write-data (subpath debugWriteDir)))
|
||||
|
||||
; bug 1324610
|
||||
(allow network-outbound file-read*
|
||||
|
@ -359,7 +359,7 @@ static const char contentSandboxRules[] = R"(
|
|||
(iokit-user-client-class "Gen6DVDContext"))
|
||||
|
||||
; bug 1237847
|
||||
(allow file-read* file-write*
|
||||
(allow file-read* file-write-create file-write-data
|
||||
(subpath appTempDir))
|
||||
)";
|
||||
|
||||
|
|
|
@ -215,7 +215,15 @@ async function createTempFile() {
|
|||
ok(fileCreated == true, "creating a file in content temp is permitted");
|
||||
// now delete the file
|
||||
let fileDeleted = await ContentTask.spawn(browser, path, deleteFile);
|
||||
ok(fileDeleted == true, "deleting a file in content temp is permitted");
|
||||
if (isMac()) {
|
||||
// On macOS we do not allow file deletion - it is not needed by the content
|
||||
// process itself, and macOS uses a different permission to control access
|
||||
// to revoking it is easy.
|
||||
ok(fileDeleted == false,
|
||||
"deleting a file in the content temp is not permitted");
|
||||
} else {
|
||||
ok(fileDeleted == true, "deleting a file in content temp is permitted");
|
||||
}
|
||||
}
|
||||
|
||||
// Test reading files and dirs from web and file content processes.
|
||||
|
|
|
@ -325,7 +325,7 @@ version = "0.0.1"
|
|||
dependencies = [
|
||||
"azure 0.19.0 (git+https://github.com/servo/rust-azure)",
|
||||
"canvas_traits 0.0.1",
|
||||
"cssparser 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gleam 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ipc-channel 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -340,7 +340,7 @@ dependencies = [
|
|||
name = "canvas_traits"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"cssparser 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -585,7 +585,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cssparser"
|
||||
version = "0.16.1"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cssparser-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1017,7 +1017,7 @@ name = "geckoservo"
|
|||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1083,7 +1083,7 @@ dependencies = [
|
|||
name = "gfx_tests"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"cssparser 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gfx 0.0.1",
|
||||
"ipc-channel 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"style 0.0.1",
|
||||
|
@ -2418,7 +2418,7 @@ dependencies = [
|
|||
"caseless 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cmake 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cookie 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"deny_public_fields 0.0.1",
|
||||
"devtools_traits 0.0.1",
|
||||
"dom_struct 0.0.1",
|
||||
|
@ -2491,7 +2491,7 @@ dependencies = [
|
|||
"app_units 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"canvas_traits 0.0.1",
|
||||
"cssparser 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gfx_traits 0.0.1",
|
||||
"heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2562,7 +2562,7 @@ name = "selectors"
|
|||
version = "0.19.0"
|
||||
dependencies = [
|
||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2954,7 +2954,7 @@ dependencies = [
|
|||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -3007,7 +3007,7 @@ version = "0.0.1"
|
|||
dependencies = [
|
||||
"app_units 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"html5ever 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -3028,7 +3028,7 @@ version = "0.0.1"
|
|||
dependencies = [
|
||||
"app_units 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -3041,7 +3041,7 @@ name = "stylo_tests"
|
|||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cssparser 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"euclid 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"geckoservo 0.0.1",
|
||||
|
@ -3624,7 +3624,7 @@ dependencies = [
|
|||
"checksum core-foundation-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "41115a6aa5d3e1e5ef98148373f25971d1fad53818553f216495f9e67e90a624"
|
||||
"checksum core-graphics 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a9f841e9637adec70838c537cae52cb4c751cc6514ad05669b51d107c2021c79"
|
||||
"checksum core-text 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "74ba2a7abdccb94fb6c00822addef48504182b285aa45a30e78286487888fcb4"
|
||||
"checksum cssparser 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2842253baded8e712e9d8d80ebfe5ea8e95c5b27071e6a6db6080ca1e81c07d1"
|
||||
"checksum cssparser 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e7063452c60432cb306ed54d538178c20792d47fa960c240ce6c083239ee55ec"
|
||||
"checksum cssparser-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "079adec4af52bb5275eadd004292028c79eb3c5f5b4ee8086a36d4197032f6df"
|
||||
"checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850"
|
||||
"checksum dbus 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4aee01fb76ada3e5e7ca642ea6664ebf7308a810739ca2aca44909a1191ac254"
|
||||
|
|
|
@ -12,7 +12,7 @@ path = "lib.rs"
|
|||
[dependencies]
|
||||
azure = {git = "https://github.com/servo/rust-azure"}
|
||||
canvas_traits = {path = "../canvas_traits"}
|
||||
cssparser = "0.16.1"
|
||||
cssparser = "0.17.0"
|
||||
euclid = "0.15"
|
||||
gleam = "0.4"
|
||||
ipc-channel = "0.8"
|
||||
|
|
|
@ -10,7 +10,7 @@ name = "canvas_traits"
|
|||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
cssparser = "0.16.1"
|
||||
cssparser = "0.17.0"
|
||||
euclid = "0.15"
|
||||
heapsize = "0.4"
|
||||
heapsize_derive = "0.1"
|
||||
|
|
|
@ -219,9 +219,6 @@ pub struct LayoutThread {
|
|||
/// All the other elements of this struct are read-only.
|
||||
rw_data: Arc<Mutex<LayoutThreadData>>,
|
||||
|
||||
/// The CSS error reporter for all CSS loaded in this layout thread
|
||||
error_reporter: CSSErrorReporter,
|
||||
|
||||
webrender_image_cache: Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder),
|
||||
WebRenderImageInfo>>>,
|
||||
/// The executor for paint worklets.
|
||||
|
@ -532,10 +529,6 @@ impl LayoutThread {
|
|||
text_index_response: TextIndexResponse(None),
|
||||
nodes_from_point_response: vec![],
|
||||
})),
|
||||
error_reporter: CSSErrorReporter {
|
||||
pipelineid: id,
|
||||
script_chan: Arc::new(Mutex::new(script_chan)),
|
||||
},
|
||||
webrender_image_cache:
|
||||
Arc::new(RwLock::new(FnvHashMap::default())),
|
||||
timer:
|
||||
|
@ -580,7 +573,6 @@ impl LayoutThread {
|
|||
guards: guards,
|
||||
running_animations: self.running_animations.clone(),
|
||||
expired_animations: self.expired_animations.clone(),
|
||||
error_reporter: &self.error_reporter,
|
||||
local_context_creation_data: Mutex::new(thread_local_style_context_creation_data),
|
||||
timer: self.timer.clone(),
|
||||
quirks_mode: self.quirks_mode.unwrap(),
|
||||
|
|
|
@ -35,7 +35,7 @@ byteorder = "1.0"
|
|||
canvas_traits = {path = "../canvas_traits"}
|
||||
caseless = "0.1.0"
|
||||
cookie = "0.6"
|
||||
cssparser = "0.16.1"
|
||||
cssparser = "0.17.0"
|
||||
deny_public_fields = {path = "../deny_public_fields"}
|
||||
devtools_traits = {path = "../devtools_traits"}
|
||||
dom_struct = {path = "../dom_struct"}
|
||||
|
|
|
@ -13,7 +13,7 @@ path = "lib.rs"
|
|||
app_units = "0.5"
|
||||
atomic_refcell = "0.1"
|
||||
canvas_traits = {path = "../canvas_traits"}
|
||||
cssparser = "0.16.1"
|
||||
cssparser = "0.17.0"
|
||||
euclid = "0.15"
|
||||
gfx_traits = {path = "../gfx_traits"}
|
||||
heapsize = "0.4"
|
||||
|
|
|
@ -25,7 +25,7 @@ unstable = []
|
|||
[dependencies]
|
||||
bitflags = "0.7"
|
||||
matches = "0.1"
|
||||
cssparser = "0.16.1"
|
||||
cssparser = "0.17.0"
|
||||
log = "0.3"
|
||||
fnv = "1.0"
|
||||
phf = "0.7.18"
|
||||
|
|
|
@ -53,24 +53,25 @@ impl ElementSelectorFlags {
|
|||
}
|
||||
}
|
||||
|
||||
/// Holds per-element data alongside a pointer to MatchingContext.
|
||||
/// Holds per-selector data alongside a pointer to MatchingContext.
|
||||
pub struct LocalMatchingContext<'a, 'b: 'a, Impl: SelectorImpl> {
|
||||
/// Shared `MatchingContext`.
|
||||
pub shared: &'a mut MatchingContext<'b>,
|
||||
/// A reference to the base selector we're matching against.
|
||||
pub selector: &'a Selector<Impl>,
|
||||
/// The offset of the current compound selector being matched, kept up to date by
|
||||
/// the callees when the iterator is advanced. This, in conjunction with the selector
|
||||
/// reference above, allows callees to synthesize an iterator for the current compound
|
||||
/// selector on-demand. This is necessary because the primary iterator may already have
|
||||
/// been advanced partway through the current compound selector, and the callee may need
|
||||
/// the whole thing.
|
||||
/// The offset of the current compound selector being matched, kept up to
|
||||
/// date by the callees when the iterator is advanced. This, in conjunction
|
||||
/// with the selector reference above, allows callees to synthesize an
|
||||
/// iterator for the current compound selector on-demand. This is necessary
|
||||
/// because the primary iterator may already have been advanced partway
|
||||
/// through the current compound selector, and the callee may need the whole
|
||||
/// thing.
|
||||
offset: usize,
|
||||
/// The level of nesting for the selector being matched.
|
||||
pub nesting_level: usize,
|
||||
/// Holds a bool flag to see whether :active and :hover quirk should try to
|
||||
/// match or not. This flag can only be true in these two cases:
|
||||
/// - LocalMatchingContext is currently within a functional pseudo class
|
||||
/// like `:-moz-any` or `:not`.
|
||||
/// - PseudoElements are encountered when matching mode is ForStatelessPseudoElement.
|
||||
/// match or not. This flag can only be true in the case PseudoElements are
|
||||
/// encountered when matching mode is ForStatelessPseudoElement.
|
||||
pub hover_active_quirk_disabled: bool,
|
||||
}
|
||||
|
||||
|
@ -84,6 +85,7 @@ impl<'a, 'b, Impl> LocalMatchingContext<'a, 'b, Impl>
|
|||
shared: shared,
|
||||
selector: selector,
|
||||
offset: 0,
|
||||
nesting_level: 0,
|
||||
// We flip this off once third sequence is reached.
|
||||
hover_active_quirk_disabled: selector.has_pseudo_element(),
|
||||
}
|
||||
|
@ -107,8 +109,16 @@ impl<'a, 'b, Impl> LocalMatchingContext<'a, 'b, Impl>
|
|||
/// Returns true if current compound selector matches :active and :hover quirk.
|
||||
/// https://quirks.spec.whatwg.org/#the-active-and-hover-quirk
|
||||
pub fn active_hover_quirk_matches(&mut self) -> bool {
|
||||
if self.shared.quirks_mode() != QuirksMode::Quirks ||
|
||||
self.hover_active_quirk_disabled {
|
||||
if self.shared.quirks_mode() != QuirksMode::Quirks {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow it in recursive selectors such as :not and :-moz-any.
|
||||
if self.nesting_level != 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.hover_active_quirk_disabled {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -453,7 +463,8 @@ pub fn matches_complex_selector<E, F>(mut iter: SelectorIter<E::Impl>,
|
|||
F: FnMut(&E, ElementSelectorFlags),
|
||||
{
|
||||
if cfg!(debug_assertions) {
|
||||
if context.shared.matching_mode == MatchingMode::ForStatelessPseudoElement {
|
||||
if context.nesting_level == 0 &&
|
||||
context.shared.matching_mode == MatchingMode::ForStatelessPseudoElement {
|
||||
assert!(iter.clone().any(|c| {
|
||||
matches!(*c, Component::PseudoElement(..))
|
||||
}));
|
||||
|
@ -462,7 +473,8 @@ pub fn matches_complex_selector<E, F>(mut iter: SelectorIter<E::Impl>,
|
|||
|
||||
// If this is the special pseudo-element mode, consume the ::pseudo-element
|
||||
// before proceeding, since the caller has already handled that part.
|
||||
if context.shared.matching_mode == MatchingMode::ForStatelessPseudoElement {
|
||||
if context.nesting_level == 0 &&
|
||||
context.shared.matching_mode == MatchingMode::ForStatelessPseudoElement {
|
||||
// Consume the pseudo.
|
||||
let pseudo = iter.next().unwrap();
|
||||
debug_assert!(matches!(*pseudo, Component::PseudoElement(..)),
|
||||
|
@ -733,13 +745,12 @@ fn matches_simple_selector<E, F>(
|
|||
matches_generic_nth_child(element, 0, 1, true, true, flags_setter)
|
||||
}
|
||||
Component::Negation(ref negated) => {
|
||||
let old_value = context.hover_active_quirk_disabled;
|
||||
context.hover_active_quirk_disabled = true;
|
||||
context.nesting_level += 1;
|
||||
let result = !negated.iter().all(|ss| {
|
||||
matches_simple_selector(ss, element, context,
|
||||
relevant_link, flags_setter)
|
||||
});
|
||||
context.hover_active_quirk_disabled = old_value;
|
||||
context.nesting_level -= 1;
|
||||
result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ pub enum SelectorParseError<'i, T> {
|
|||
PseudoElementExpectedIdent,
|
||||
UnsupportedPseudoClass,
|
||||
UnexpectedIdent(CompactCowStr<'i>),
|
||||
ExpectedNamespace,
|
||||
ExpectedNamespace(CompactCowStr<'i>),
|
||||
Custom(T),
|
||||
}
|
||||
|
||||
|
@ -1105,9 +1105,10 @@ fn parse_qualified_name<'i, 't, P, E, Impl>
|
|||
let position = input.position();
|
||||
match input.next_including_whitespace() {
|
||||
Ok(Token::Delim('|')) => {
|
||||
let prefix = from_cow_str(value.into());
|
||||
let prefix = from_cow_str(value.clone().into());
|
||||
let result = parser.namespace_for_prefix(&prefix);
|
||||
let url = result.ok_or(ParseError::Custom(SelectorParseError::ExpectedNamespace))?;
|
||||
let url = result.ok_or(ParseError::Custom(
|
||||
SelectorParseError::ExpectedNamespace(value.into())))?;
|
||||
explicit_namespace(input, QNamePrefix::ExplicitNamespace(prefix, url))
|
||||
},
|
||||
_ => {
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче