diff --git a/browser/components/preferences/in-content-new/findInPage.js b/browser/components/preferences/in-content-new/findInPage.js index eede0eecbc1d..ace3a940063e 100644 --- a/browser/components/preferences/in-content-new/findInPage.js +++ b/browser/components/preferences/in-content-new/findInPage.js @@ -18,6 +18,7 @@ var gSearchResultsPane = { this.searchInput = document.getElementById("searchInput"); this.searchInput.hidden = !Services.prefs.getBoolPref("browser.preferences.search"); if (!this.searchInput.hidden) { + this.searchInput.addEventListener("input", this); this.searchInput.addEventListener("command", this); window.addEventListener("DOMContentLoaded", () => { this.searchInput.focus(); @@ -145,7 +146,8 @@ var gSearchResultsPane = { let nodeStartIndex = null; // Determining the start and end node to highlight from - nodeSizes.forEach(function(lengthNodes, index) { + for (let index = 0; index < nodeSizes.length; index++) { + let lengthNodes = nodeSizes[index]; // Determining the start node if (!startNode && lengthNodes >= startValue) { startNode = textNodes[index]; @@ -164,7 +166,7 @@ var gSearchResultsPane = { endValue -= nodeSizes[index - 1]; } } - }); + } let range = document.createRange(); range.setStart(startNode, startValue); range.setEnd(endNode, endValue); @@ -207,8 +209,15 @@ var gSearchResultsPane = { * @param String event * to search for filted query in */ - searchFunction(event) { - this.query = event.target.value.trim().toLowerCase(); + async searchFunction(event) { + let query = event.target.value.trim().toLowerCase(); + if (this.query == query) { + return; + } + + let subQuery = this.query && query.indexOf(this.query) !== -1; + this.query = query; + this.getFindSelection(window).removeAllRanges(); this.removeAllSearchTooltips(); this.removeAllSearchMenuitemIndicators(); @@ -230,13 +239,40 @@ var gSearchResultsPane = { let rootPreferencesChildren = document .querySelectorAll("#mainPrefPane > *:not([data-hidden-from-search])"); - // Showing all the children to bind JS, Access Keys, etc + if (subQuery) { + // Since the previous query is a subset of the current query, + // there is no need to check elements that is hidden already. + rootPreferencesChildren = + Array.prototype.filter.call(rootPreferencesChildren, el => !el.hidden); + } + + // Mark all the children to check be visible to bind JS, Access Keys, etc, + // but don't really show them by setting their visibility to hidden in CSS. for (let i = 0; i < rootPreferencesChildren.length; i++) { rootPreferencesChildren[i].hidden = false; + rootPreferencesChildren[i].classList.add("visually-hidden"); } + let ts = performance.now(); + let FRAME_THRESHOLD = 1000 / 60; + // Showing or Hiding specific section depending on if words in query are found for (let i = 0; i < rootPreferencesChildren.length; i++) { + if (performance.now() - ts > FRAME_THRESHOLD) { + // Creating tooltips for all the instances found + for (let anchorNode of this.listSearchTooltips) { + this.createSearchTooltip(anchorNode, this.query); + } + // It hides Search Results header so turning it on + srHeader.hidden = false; + srHeader.classList.remove("visually-hidden"); + ts = await new Promise(resolve => window.requestAnimationFrame(resolve)); + if (query !== this.query) { + return; + } + } + + rootPreferencesChildren[i].classList.remove("visually-hidden"); if (!rootPreferencesChildren[i].classList.contains("header") && !rootPreferencesChildren[i].classList.contains("subcategory") && !rootPreferencesChildren[i].classList.contains("no-results-message") && @@ -249,6 +285,7 @@ var gSearchResultsPane = { } // It hides Search Results header so turning it on srHeader.hidden = false; + srHeader.classList.remove("visually-hidden"); if (!resultsFound) { let noResultsEl = document.querySelector(".no-results-message"); @@ -266,7 +303,9 @@ var gSearchResultsPane = { strings.getFormattedString("searchResults.needHelp2", [helpUrl, brandName]); } else { // Creating tooltips for all the instances found - this.listSearchTooltips.forEach((anchorNode) => this.createSearchTooltip(anchorNode, this.query)); + for (let anchorNode of this.listSearchTooltips) { + this.createSearchTooltip(anchorNode, this.query); + } // Implant search telemetry probe after user stops typing for a while if (this.query.length >= 2) { @@ -280,6 +319,8 @@ var gSearchResultsPane = { // Going back to General when cleared gotoPref("paneGeneral"); } + + window.dispatchEvent(new CustomEvent("PreferencesSearchCompleted", { detail: query })); }, /** @@ -409,6 +450,9 @@ var gSearchResultsPane = { * Word or words that are being searched for */ createSearchTooltip(anchorNode, query) { + if (anchorNode.tooltipNode) { + return; + } let searchTooltip = anchorNode.ownerDocument.createElement("span"); searchTooltip.setAttribute("class", "search-tooltip"); searchTooltip.textContent = query; @@ -439,7 +483,10 @@ var gSearchResultsPane = { searchTooltip.parentElement.classList.remove("search-tooltip-parent"); searchTooltip.remove(); } - this.listSearchTooltips.forEach((anchorNode) => anchorNode.tooltipNode.remove()); + for (let anchorNode of this.listSearchTooltips) { + anchorNode.tooltipNode.remove(); + anchorNode.tooltipNode = null; + } this.listSearchTooltips.clear(); }, @@ -447,7 +494,9 @@ var gSearchResultsPane = { * Remove all indicators on menuitem. */ removeAllSearchMenuitemIndicators() { - this.listSearchMenuitemIndicators.forEach((node) => node.removeAttribute("indicator")); + for (let node of this.listSearchMenuitemIndicators) { + node.removeAttribute("indicator"); + } this.listSearchMenuitemIndicators.clear(); } } diff --git a/browser/components/preferences/in-content-new/preferences.js b/browser/components/preferences/in-content-new/preferences.js index 3e335030ce05..43744466612d 100644 --- a/browser/components/preferences/in-content-new/preferences.js +++ b/browser/components/preferences/in-content-new/preferences.js @@ -240,6 +240,7 @@ function search(aQuery, aAttribute, aSubquery, aSubAttribute) { element.hidden = true; } } + element.classList.remove("visually-hidden"); } let keysets = mainPrefPane.getElementsByTagName("keyset"); diff --git a/browser/components/preferences/in-content-new/tests/browser.ini b/browser/components/preferences/in-content-new/tests/browser.ini index f8fe6add719b..38a6b3c13705 100644 --- a/browser/components/preferences/in-content-new/tests/browser.ini +++ b/browser/components/preferences/in-content-new/tests/browser.ini @@ -15,10 +15,15 @@ skip-if = !updater [browser_bug731866.js] [browser_search_within_preferences_1.js] [browser_search_within_preferences_2.js] +[browser_search_within_preferences_command.js] [browser_search_subdialogs_within_preferences_1.js] [browser_search_subdialogs_within_preferences_2.js] [browser_search_subdialogs_within_preferences_3.js] [browser_search_subdialogs_within_preferences_4.js] +[browser_search_subdialogs_within_preferences_5.js] +[browser_search_subdialogs_within_preferences_6.js] +[browser_search_subdialogs_within_preferences_7.js] +[browser_search_subdialogs_within_preferences_8.js] [browser_bug795764_cachedisabled.js] [browser_bug1018066_resetScrollPosition.js] [browser_bug1020245_openPreferences_to_paneContent.js] diff --git a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_1.js b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_1.js index d533581fc414..c209479c04d8 100644 --- a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_1.js +++ b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_1.js @@ -12,7 +12,7 @@ add_task(async function() { */ add_task(async function() { await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("Set Home Page", "startupGroup"); + await evaluateSearchResults("Set Home Page", "startupGroup"); await BrowserTestUtils.removeTab(gBrowser.selectedTab); }); @@ -21,33 +21,6 @@ add_task(async function() { */ add_task(async function() { await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("Choose languages", "languagesGroup"); - await BrowserTestUtils.removeTab(gBrowser.selectedTab); -}); - -/** - * Test for searching for the "Fonts" subdialog. - */ -add_task(async function() { - await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("Text Encoding", "fontsGroup"); - await BrowserTestUtils.removeTab(gBrowser.selectedTab); -}); - -/** - * Test for searching for the "Colors" subdialog. - */ -add_task(async function() { - await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("Link Colors", "fontsGroup"); - await BrowserTestUtils.removeTab(gBrowser.selectedTab); -}); - -/** - * Test for searching for the "Exceptions - Saved Logins" subdialog. - */ -add_task(async function() { - await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("sites will not be saved", "passwordsGroup"); + await evaluateSearchResults("Choose languages", "languagesGroup"); await BrowserTestUtils.removeTab(gBrowser.selectedTab); }); diff --git a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_2.js b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_2.js index 8084fb7ae614..4b88ee541081 100644 --- a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_2.js +++ b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_2.js @@ -15,7 +15,7 @@ add_task(async function() { */ add_task(async function() { await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("sites are stored", "passwordsGroup"); + await evaluateSearchResults("sites are stored", "passwordsGroup"); await BrowserTestUtils.removeTab(gBrowser.selectedTab); }); @@ -24,24 +24,6 @@ add_task(async function() { */ add_task(async function() { await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("disabled Tracking Protection", "trackingGroup"); - await BrowserTestUtils.removeTab(gBrowser.selectedTab); -}); - -/** - * Test for searching for the "Block Lists" subdialog. - */ -add_task(async function() { - await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("block Web elements", "trackingGroup"); - await BrowserTestUtils.removeTab(gBrowser.selectedTab); -}); - -/** - * Test for searching for the "Allowed Sites - Pop-ups" subdialog. - */ -add_task(async function() { - await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("open pop-up windows", "permissionsGroup"); + await evaluateSearchResults("disabled Tracking Protection", "trackingGroup"); await BrowserTestUtils.removeTab(gBrowser.selectedTab); }); diff --git a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_3.js b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_3.js index b8f45aede47d..dabfc84b5c71 100644 --- a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_3.js +++ b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_3.js @@ -15,7 +15,7 @@ add_task(async function() { */ add_task(async function() { await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("allowed to install add-ons", "permissionsGroup"); + await evaluateSearchResults("allowed to install add-ons", "permissionsGroup"); await BrowserTestUtils.removeTab(gBrowser.selectedTab); }); @@ -24,33 +24,6 @@ add_task(async function() { */ add_task(async function() { await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("identify these certificate authorities", "certSelection"); - await BrowserTestUtils.removeTab(gBrowser.selectedTab); -}); - -/** - * Test for searching for the "Device Manager" subdialog. - */ -add_task(async function() { - await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("Security Modules and Devices", "certSelection"); - await BrowserTestUtils.removeTab(gBrowser.selectedTab); -}); - -/** - * Test for searching for the "Connection Settings" subdialog. - */ -add_task(async function() { - await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("Use system proxy settings", "connectionGroup"); - await BrowserTestUtils.removeTab(gBrowser.selectedTab); -}); - -/** - * Test for searching for the "Settings - Site Data" subdialog. - */ -add_task(async function() { - await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("store site data on your computer", "siteDataGroup"); + await evaluateSearchResults("identify these certificate authorities", "certSelection"); await BrowserTestUtils.removeTab(gBrowser.selectedTab); }); diff --git a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_4.js b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_4.js index fa0e85251b21..d1d6922ec05c 100644 --- a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_4.js +++ b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_4.js @@ -12,7 +12,7 @@ add_task(async function() { */ add_task(async function() { await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("updates have been installed", "updateApp"); + await evaluateSearchResults("updates have been installed", "updateApp"); await BrowserTestUtils.removeTab(gBrowser.selectedTab); }); @@ -21,33 +21,6 @@ add_task(async function() { */ add_task(async function() { await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("set location permissions", "permissionsGroup"); - await BrowserTestUtils.removeTab(gBrowser.selectedTab); -}); - -/** - * Test for searching for the "Camera Permissions" subdialog. - */ -add_task(async function() { - await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("set camera permissions", "permissionsGroup"); - await BrowserTestUtils.removeTab(gBrowser.selectedTab); -}); - -/** - * Test for searching for the "Microphone Permissions" subdialog. - */ -add_task(async function() { - await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("set microphone permissions", "permissionsGroup"); - await BrowserTestUtils.removeTab(gBrowser.selectedTab); -}); - -/** - * Test for searching for the "Notification Permissions" subdialog. - */ -add_task(async function() { - await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); - evaluateSearchResults("set notifications permissions", "permissionsGroup"); + await evaluateSearchResults("set location permissions", "permissionsGroup"); await BrowserTestUtils.removeTab(gBrowser.selectedTab); }); diff --git a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_5.js b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_5.js new file mode 100644 index 000000000000..2d68a3616140 --- /dev/null +++ b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_5.js @@ -0,0 +1,35 @@ +/* +* This file contains tests for the Preferences search bar. +*/ + +// Enabling Searching functionatily. Will display search bar form this testcase forward. +add_task(async function() { + await SpecialPowers.pushPrefEnv({"set": [["browser.preferences.search", true]]}); +}); + +/** + * Test for searching for the "Fonts" subdialog. + */ +add_task(async function() { + await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); + await evaluateSearchResults("Text Encoding", "fontsGroup"); + await BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +/** + * Test for searching for the "Colors" subdialog. + */ +add_task(async function() { + await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); + await evaluateSearchResults("Link Colors", "fontsGroup"); + await BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +/** + * Test for searching for the "Exceptions - Saved Logins" subdialog. + */ +add_task(async function() { + await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); + await evaluateSearchResults("sites will not be saved", "passwordsGroup"); + await BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_6.js b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_6.js new file mode 100644 index 000000000000..b2b2d436eba1 --- /dev/null +++ b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_6.js @@ -0,0 +1,29 @@ +/* +* This file contains tests for the Preferences search bar. +*/ + +// Enabling Searching functionatily. Will display search bar form this testcase forward. +add_task(async function() { + await SpecialPowers.pushPrefEnv({"set": [ + ["browser.preferences.search", true], + ["privacy.trackingprotection.ui.enabled", true] + ]}); +}); + +/** + * Test for searching for the "Block Lists" subdialog. + */ +add_task(async function() { + await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); + await evaluateSearchResults("block Web elements", "trackingGroup"); + await BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +/** + * Test for searching for the "Allowed Sites - Pop-ups" subdialog. + */ +add_task(async function() { + await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); + await evaluateSearchResults("open pop-up windows", "permissionsGroup"); + await BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_7.js b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_7.js new file mode 100644 index 000000000000..25f5302d9e17 --- /dev/null +++ b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_7.js @@ -0,0 +1,38 @@ +/* +* This file contains tests for the Preferences search bar. +*/ + +// Enabling Searching functionatily. Will display search bar form this testcase forward. +add_task(async function() { + await SpecialPowers.pushPrefEnv({"set": [ + ["browser.preferences.search", true], + ["browser.storageManager.enabled", true] + ]}); +}); + +/** + * Test for searching for the "Device Manager" subdialog. + */ +add_task(async function() { + await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); + await evaluateSearchResults("Security Modules and Devices", "certSelection"); + await BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +/** + * Test for searching for the "Connection Settings" subdialog. + */ +add_task(async function() { + await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); + await evaluateSearchResults("Use system proxy settings", "connectionGroup"); + await BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +/** + * Test for searching for the "Settings - Site Data" subdialog. + */ +add_task(async function() { + await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); + await evaluateSearchResults("store site data on your computer", "siteDataGroup"); + await BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_8.js b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_8.js new file mode 100644 index 000000000000..a0b7accc35e9 --- /dev/null +++ b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_8.js @@ -0,0 +1,35 @@ +/* +* This file contains tests for the Preferences search bar. +*/ + +// Enabling Searching functionatily. Will display search bar form this testcase forward. +add_task(async function() { + await SpecialPowers.pushPrefEnv({"set": [["browser.preferences.search", true]]}); +}); + +/** + * Test for searching for the "Camera Permissions" subdialog. + */ +add_task(async function() { + await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); + await evaluateSearchResults("set camera permissions", "permissionsGroup"); + await BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +/** + * Test for searching for the "Microphone Permissions" subdialog. + */ +add_task(async function() { + await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); + await evaluateSearchResults("set microphone permissions", "permissionsGroup"); + await BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +/** + * Test for searching for the "Notification Permissions" subdialog. + */ +add_task(async function() { + await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true}); + await evaluateSearchResults("set notifications permissions", "permissionsGroup"); + await BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/components/preferences/in-content-new/tests/browser_search_within_preferences_1.js b/browser/components/preferences/in-content-new/tests/browser_search_within_preferences_1.js index ce24a5ca7b2f..471d9cd10801 100644 --- a/browser/components/preferences/in-content-new/tests/browser_search_within_preferences_1.js +++ b/browser/components/preferences/in-content-new/tests/browser_search_within_preferences_1.js @@ -3,7 +3,7 @@ * This file contains tests for the Preferences search bar. */ -requestLongerTimeout(2); +requestLongerTimeout(6); /** * Tests to see if search bar is being hidden when pref is turned off @@ -49,8 +49,11 @@ add_task(async function() { is(searchInput, gBrowser.contentDocument.activeElement.closest("#searchInput"), "Search input should be focused when visiting preferences"); - searchInput.value = "password"; - searchInput.doCommand(); + let query = "password"; + let searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query); + EventUtils.sendString(query); + await searchCompletedPromise; let categoriesList = gBrowser.contentDocument.getElementById("categories"); @@ -59,8 +62,13 @@ add_task(async function() { is(child.selected, false, "No other panel should be selected"); } // Takes search off - searchInput.value = ""; - searchInput.doCommand(); + searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == ""); + let count = query.length; + while (count--) { + EventUtils.sendKey("BACK_SPACE"); + } + await searchCompletedPromise; // Checks if back to generalPane for (let i = 0; i < categoriesList.childElementCount; i++) { @@ -87,8 +95,11 @@ add_task(async function() { is(searchInput, gBrowser.contentDocument.activeElement.closest("#searchInput"), "Search input should be focused when visiting preferences"); - searchInput.value = "password"; - searchInput.doCommand(); + let query = "password"; + let searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query); + EventUtils.sendString(query); + await searchCompletedPromise; let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane"); @@ -106,8 +117,13 @@ add_task(async function() { } // Takes search off - searchInput.value = ""; - searchInput.doCommand(); + searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == ""); + let count = query.length; + while (count--) { + EventUtils.sendKey("BACK_SPACE"); + } + await searchCompletedPromise; // Checks if back to generalPane for (let i = 0; i < mainPrefTag.childElementCount; i++) { @@ -155,14 +171,22 @@ add_task(async function() { is(searchInput, gBrowser.contentDocument.activeElement.closest("#searchInput"), "Search input should be focused when visiting preferences"); - searchInput.value = "coach"; - searchInput.doCommand(); + let query = "coach"; + let searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query); + EventUtils.sendString(query); + await searchCompletedPromise; is_element_visible(noResultsEl, "Should be in search results"); // Takes search off - searchInput.value = ""; - searchInput.doCommand(); + searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == ""); + let count = query.length; + while (count--) { + EventUtils.sendKey("BACK_SPACE"); + } + await searchCompletedPromise; is_element_hidden(noResultsEl, "Should not be in search results"); @@ -184,12 +208,20 @@ add_task(async function() { is(searchInput, gBrowser.contentDocument.activeElement.closest("#searchInput"), "Search input should be focused when visiting preferences"); - searchInput.value = "password"; - searchInput.doCommand(); + let query = "password"; + let searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query); + EventUtils.sendString(query); + await searchCompletedPromise; // Takes search off - searchInput.value = ""; - searchInput.doCommand(); + searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == ""); + let count = query.length; + while (count--) { + EventUtils.sendKey("BACK_SPACE"); + } + await searchCompletedPromise; // Checks if back to normal is_element_visible(generalPane, "Should be in generalPane"); @@ -214,8 +246,11 @@ add_task(async function() { is(searchInput, gBrowser.contentDocument.activeElement.closest("#searchInput"), "Search input should be focused when visiting preferences"); - searchInput.value = "site data"; - searchInput.doCommand(); + let query = "site data"; + let searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query); + EventUtils.sendString(query); + await searchCompletedPromise; let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane"); @@ -223,8 +258,13 @@ add_task(async function() { is_element_hidden(child, "Should be hidden in search results"); // Takes search off - searchInput.value = ""; - searchInput.doCommand(); + searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == ""); + let count = query.length; + while (count--) { + EventUtils.sendKey("BACK_SPACE"); + } + await searchCompletedPromise; // Checks if back to normal is_element_visible(generalPane, "Should be in generalPane"); @@ -242,8 +282,11 @@ add_task(async function() { is(searchInput, gBrowser.contentDocument.activeElement.closest("#searchInput"), "Search input should be focused when visiting preferences"); - searchInput.value = "password"; - searchInput.doCommand(); + let query = "password"; + let searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query); + EventUtils.sendString(query); + await searchCompletedPromise; let privacyCategory = gBrowser.contentDocument.getElementById("category-privacy"); privacyCategory.click(); diff --git a/browser/components/preferences/in-content-new/tests/browser_search_within_preferences_2.js b/browser/components/preferences/in-content-new/tests/browser_search_within_preferences_2.js index fc7d6119a065..963dc633033e 100644 --- a/browser/components/preferences/in-content-new/tests/browser_search_within_preferences_2.js +++ b/browser/components/preferences/in-content-new/tests/browser_search_within_preferences_2.js @@ -31,8 +31,11 @@ add_task(async function() { is(searchInput, gBrowser.contentDocument.activeElement.closest("#searchInput"), "Search input should be focused when visiting preferences"); - searchInput.value = "Create Account"; - searchInput.doCommand(); + let query = "Create Account"; + let searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query); + EventUtils.sendString(query); + await searchCompletedPromise; let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane"); for (let i = 0; i < mainPrefTag.childElementCount; i++) { @@ -51,8 +54,11 @@ add_task(async function() { // Performs search. searchInput.focus(); - searchInput.value = "Forget this Email"; - searchInput.doCommand(); + query = "Forget this Email"; + searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query); + EventUtils.sendString(query); + await searchCompletedPromise; let noResultsEl = gBrowser.contentDocument.querySelector(".no-results-message"); is_element_visible(noResultsEl, "Should be reporting no results"); diff --git a/browser/components/preferences/in-content-new/tests/browser_search_within_preferences_command.js b/browser/components/preferences/in-content-new/tests/browser_search_within_preferences_command.js new file mode 100644 index 000000000000..0e864b0f7c66 --- /dev/null +++ b/browser/components/preferences/in-content-new/tests/browser_search_within_preferences_command.js @@ -0,0 +1,37 @@ +"use strict"; + +/** + * Test for "command" event on search input (when user clicks the x button) + */ +add_task(async function() { + await SpecialPowers.pushPrefEnv({"set": [["browser.storageManager.enabled", false]]}); + await openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true}); + let generalPane = gBrowser.contentDocument.getElementById("generalCategory"); + + is_element_hidden(generalPane, "Should not be in general"); + + // Performs search + let searchInput = gBrowser.contentDocument.getElementById("searchInput"); + is(searchInput, gBrowser.contentDocument.activeElement.closest("#searchInput"), + "Search input should be focused when visiting preferences"); + + let query = "x"; + let searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query); + EventUtils.sendString(query); + await searchCompletedPromise; + + is_element_hidden(generalPane, "Should not be in generalPane"); + + // Takes search off with "command" + searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == ""); + searchInput.value = ""; + searchInput.doCommand(); + await searchCompletedPromise; + + // Checks if back to normal + is_element_visible(generalPane, "Should be in generalPane"); + + await BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/components/preferences/in-content-new/tests/head.js b/browser/components/preferences/in-content-new/tests/head.js index e3bd6046374f..da2753174ee3 100644 --- a/browser/components/preferences/in-content-new/tests/head.js +++ b/browser/components/preferences/in-content-new/tests/head.js @@ -227,14 +227,16 @@ function assertSitesListed(doc, hosts) { is(removeAllBtn.disabled, false, "Should enable the removeAllBtn button"); } -function evaluateSearchResults(keyword, searchReults) { +async function evaluateSearchResults(keyword, searchReults) { searchReults = Array.isArray(searchReults) ? searchReults : [searchReults]; searchReults.push("header-searchResults"); let searchInput = gBrowser.contentDocument.getElementById("searchInput"); searchInput.focus(); - searchInput.value = keyword; - searchInput.doCommand(); + let searchCompletedPromise = BrowserTestUtils.waitForEvent( + gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == keyword); + EventUtils.sendString(keyword); + await searchCompletedPromise; let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane"); for (let i = 0; i < mainPrefTag.childElementCount; i++) { diff --git a/browser/extensions/onboarding/test/browser/browser.ini b/browser/extensions/onboarding/test/browser/browser.ini index 9195f85230d7..abb5acc91ae6 100644 --- a/browser/extensions/onboarding/test/browser/browser.ini +++ b/browser/extensions/onboarding/test/browser/browser.ini @@ -7,5 +7,6 @@ support-files = [browser_onboarding_notification_2.js] [browser_onboarding_notification_3.js] [browser_onboarding_notification_4.js] +[browser_onboarding_select_default_tour.js] [browser_onboarding_tours.js] [browser_onboarding_tourset.js] diff --git a/browser/extensions/onboarding/test/browser/browser_onboarding_select_default_tour.js b/browser/extensions/onboarding/test/browser/browser_onboarding_select_default_tour.js new file mode 100644 index 000000000000..0ab566764c08 --- /dev/null +++ b/browser/extensions/onboarding/test/browser/browser_onboarding_select_default_tour.js @@ -0,0 +1,114 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const OVERLAY_ICON_ID = "#onboarding-overlay-button"; +const PRIVATE_BROWSING_TOUR_ID = "#onboarding-tour-private-browsing"; +const ADDONS_TOUR_ID = "#onboarding-tour-addons"; +const CUSTOMIZE_TOUR_ID = "#onboarding-tour-customize"; +const CLASS_ACTIVE = "onboarding-active"; + +add_task(async function test_default_tour_open_the_right_page() { + 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 tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter(OVERLAY_ICON_ID, {}, tab.linkedBrowser); + await promiseOnboardingOverlayOpened(tab.linkedBrowser); + + info("Make sure the default tour is active and open the right page"); + let { activeNavItemId, activePageId } = await getCurrentActiveTour(tab.linkedBrowser); + is(`#${activeNavItemId}`, PRIVATE_BROWSING_TOUR_ID, "default tour is active"); + is(activePageId, "onboarding-tour-private-browsing-page", "default tour page is shown"); + + await BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_first_tour_is_active_by_default() { + 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 tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter(OVERLAY_ICON_ID, {}, tab.linkedBrowser); + await promiseOnboardingOverlayOpened(tab.linkedBrowser); + + info("Make sure the default tour is selected"); + let doc = content && content.document; + let dom = doc.querySelector(PRIVATE_BROWSING_TOUR_ID); + ok(dom.classList.contains(CLASS_ACTIVE), "default tour is selected"); + let dom2 = doc.querySelector(ADDONS_TOUR_ID); + ok(!dom2.classList.contains(CLASS_ACTIVE), "none default tour should not be selected"); + let dom3 = doc.querySelector(CUSTOMIZE_TOUR_ID); + ok(!dom3.classList.contains(CLASS_ACTIVE), "none default tour should not be selected"); + + await BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_select_first_uncomplete_tour() { + 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"], + ]}); + setTourCompletedState("onboarding-tour-private-browsing", true); + + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter(OVERLAY_ICON_ID, {}, tab.linkedBrowser); + await promiseOnboardingOverlayOpened(tab.linkedBrowser); + + info("Make sure the first uncomplete tour is selected"); + let doc = content && content.document; + let dom = doc.querySelector(PRIVATE_BROWSING_TOUR_ID); + ok(!dom.classList.contains(CLASS_ACTIVE), "the first tour is set completed and should not be selected"); + let dom2 = doc.querySelector(ADDONS_TOUR_ID); + ok(dom2.classList.contains(CLASS_ACTIVE), "the first uncomplete tour is selected"); + let dom3 = doc.querySelector(CUSTOMIZE_TOUR_ID); + ok(!dom3.classList.contains(CLASS_ACTIVE), "other tour should not be selected"); + + await BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_select_first_tour_when_all_tours_are_complete() { + 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"], + ]}); + setTourCompletedState("onboarding-tour-private-browsing", true); + setTourCompletedState("onboarding-tour-addons", true); + setTourCompletedState("onboarding-tour-customize", true); + + let tab = await openTab(ABOUT_NEWTAB_URL); + await promiseOnboardingOverlayLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter(OVERLAY_ICON_ID, {}, tab.linkedBrowser); + await promiseOnboardingOverlayOpened(tab.linkedBrowser); + + info("Make sure the first tour is selected when all tours are completed"); + let doc = content && content.document; + let dom = doc.querySelector(PRIVATE_BROWSING_TOUR_ID); + ok(dom.classList.contains(CLASS_ACTIVE), "should be selected when all tours are completed"); + let dom2 = doc.querySelector(ADDONS_TOUR_ID); + ok(!dom2.classList.contains(CLASS_ACTIVE), "other tour should not be selected"); + let dom3 = doc.querySelector(CUSTOMIZE_TOUR_ID); + ok(!dom3.classList.contains(CLASS_ACTIVE), "other tour should not be selected"); + + await BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/themes/shared/incontentprefs/preferences.inc.css b/browser/themes/shared/incontentprefs/preferences.inc.css index f1592b2eaa20..70a847bae0d5 100644 --- a/browser/themes/shared/incontentprefs/preferences.inc.css +++ b/browser/themes/shared/incontentprefs/preferences.inc.css @@ -721,6 +721,10 @@ groupbox { position: relative; } +.visually-hidden { + visibility: hidden; +} + menulist[indicator=true] > menupopup menuitem:not([image]) > .menu-iconic-left { display: -moz-box; min-width: auto; /* Override the min-width defined in menu.css */ diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index 0f24de1a9e5f..3ccac47a2a78 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -4143,19 +4143,40 @@ Element::SetCustomElementData(CustomElementData* aData) MOZ_DEFINE_MALLOC_SIZE_OF(ServoElementMallocSizeOf) -size_t -Element::SizeOfExcludingThis(SizeOfState& aState) const +void +Element::AddSizeOfExcludingThis(SizeOfState& aState, nsStyleSizes& aSizes, + size_t* aNodeSize) const { - size_t n = FragmentOrElement::SizeOfExcludingThis(aState); + FragmentOrElement::AddSizeOfExcludingThis(aState, aSizes, aNodeSize); - // Measure mServoData. We use ServoElementMallocSizeOf rather than - // |aState.mMallocSizeOf| to distinguish in DMD's output the memory - // measured within Servo code. - if (mServoData.Get()) { - n += Servo_Element_SizeOfExcludingThis(ServoElementMallocSizeOf, - &aState.mSeenPtrs, this); + if (HasServoData()) { + // Measure mServoData, excluding the ComputedValues. This measurement + // counts towards the element's size. We use ServoElementMallocSizeOf + // rather thang |aState.mMallocSizeOf| to better distinguish in DMD's + // output the memory measured within Servo code. + *aNodeSize += + Servo_Element_SizeOfExcludingThisAndCVs(ServoElementMallocSizeOf, + &aState.mSeenPtrs, this); + + // Now measure just the ComputedValues (and style structs) under + // mServoData. This counts towards the relevant fields in |aSizes|. + RefPtr sc; + if (Servo_Element_HasPrimaryComputedValues(this)) { + sc = Servo_Element_GetPrimaryComputedValues(this).Consume(); + if (!aState.HaveSeenPtr(sc.get())) { + sc->AddSizeOfIncludingThis(aState, aSizes, /* isDOM = */ true); + } + + for (size_t i = 0; i < nsCSSPseudoElements::kEagerPseudoCount; i++) { + if (Servo_Element_HasPseudoComputedValues(this, i)) { + sc = Servo_Element_GetPseudoComputedValues(this, i).Consume(); + if (!aState.HaveSeenPtr(sc.get())) { + sc->AddSizeOfIncludingThis(aState, aSizes, /* isDOM = */ true); + } + } + } + } } - return n; } struct DirtyDescendantsBit { diff --git a/dom/base/Element.h b/dom/base/Element.h index 67bc6656f7b1..8feee9b617ac 100644 --- a/dom/base/Element.h +++ b/dom/base/Element.h @@ -205,7 +205,7 @@ public: NS_DECLARE_STATIC_IID_ACCESSOR(NS_ELEMENT_IID) - NS_DECL_SIZEOF_EXCLUDING_THIS + NS_DECL_ADDSIZEOFEXCLUDINGTHIS NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp index 044fde4cbd0d..1bf6670e3eb8 100644 --- a/dom/base/FragmentOrElement.cpp +++ b/dom/base/FragmentOrElement.cpp @@ -2502,19 +2502,18 @@ FragmentOrElement::FireNodeRemovedForChildren() } } -size_t -FragmentOrElement::SizeOfExcludingThis(SizeOfState& aState) const +void +FragmentOrElement::AddSizeOfExcludingThis(SizeOfState& aState, + nsStyleSizes& aSizes, + size_t* aNodeSize) const { - size_t n = 0; - n += nsIContent::SizeOfExcludingThis(aState); - n += mAttrsAndChildren.SizeOfExcludingThis(aState.mMallocSizeOf); + nsIContent::AddSizeOfExcludingThis(aState, aSizes, aNodeSize); + *aNodeSize += mAttrsAndChildren.SizeOfExcludingThis(aState.mMallocSizeOf); nsDOMSlots* slots = GetExistingDOMSlots(); if (slots) { - n += slots->SizeOfIncludingThis(aState.mMallocSizeOf); + *aNodeSize += slots->SizeOfIncludingThis(aState.mMallocSizeOf); } - - return n; } void diff --git a/dom/base/FragmentOrElement.h b/dom/base/FragmentOrElement.h index f0e6684416e3..9e9520c74f13 100644 --- a/dom/base/FragmentOrElement.h +++ b/dom/base/FragmentOrElement.h @@ -118,7 +118,7 @@ public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_SIZEOF_EXCLUDING_THIS + NS_DECL_ADDSIZEOFEXCLUDINGTHIS // nsINode interface methods virtual uint32_t GetChildCount() const override; diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 79967a1f824e..60c3966a9156 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -12434,26 +12434,25 @@ nsDocument::GetVisibilityState(nsAString& aState) } /* virtual */ void -nsIDocument::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const +nsIDocument::DocAddSizeOfExcludingThis(nsWindowSizes& aSizes) const { - aWindowSizes.mDOMOtherSize += - nsINode::SizeOfExcludingThis(aWindowSizes.mState); + nsINode::AddSizeOfExcludingThis(aSizes.mState, aSizes.mStyleSizes, + &aSizes.mDOMOtherSize); if (mPresShell) { - mPresShell->AddSizeOfIncludingThis(aWindowSizes); + mPresShell->AddSizeOfIncludingThis(aSizes); } - aWindowSizes.mPropertyTablesSize += - mPropertyTable.SizeOfExcludingThis(aWindowSizes.mState.mMallocSizeOf); + aSizes.mPropertyTablesSize += + mPropertyTable.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf); for (uint32_t i = 0, count = mExtraPropertyTables.Length(); i < count; ++i) { - aWindowSizes.mPropertyTablesSize += - mExtraPropertyTables[i]->SizeOfIncludingThis( - aWindowSizes.mState.mMallocSizeOf); + aSizes.mPropertyTablesSize += + mExtraPropertyTables[i]->SizeOfIncludingThis(aSizes.mState.mMallocSizeOf); } if (EventListenerManager* elm = GetExistingListenerManager()) { - aWindowSizes.mDOMEventListenersCount += elm->ListenerCount(); + aSizes.mDOMEventListenersCount += elm->ListenerCount(); } // Measurement of the following members may be added later if DMD finds it @@ -12484,10 +12483,12 @@ SizeOfOwnedSheetArrayExcludingThis(const nsTArray>& aSheets, return n; } -size_t -nsDocument::SizeOfExcludingThis(SizeOfState& aState) const +void +nsDocument::AddSizeOfExcludingThis(SizeOfState& aState, + nsStyleSizes& aSizes, + size_t* aNodeSize) const { - // This SizeOfExcludingThis() overrides the one from nsINode. But + // This AddSizeOfExcludingThis() overrides the one from nsINode. But // nsDocuments can only appear at the top of the DOM tree, and we use the // specialized DocAddSizeOfExcludingThis() in that case. So this should never // be called. @@ -12497,40 +12498,48 @@ nsDocument::SizeOfExcludingThis(SizeOfState& aState) const void nsDocument::DocAddSizeOfExcludingThis(nsWindowSizes& aWindowSizes) const { - nsIDocument::DocAddSizeOfExcludingThis(aWindowSizes); - for (nsIContent* node = nsINode::GetFirstChild(); node; node = node->GetNextNode(this)) { - size_t nodeSize = node->SizeOfIncludingThis(aWindowSizes.mState); - size_t* p; + size_t nodeSize = 0; + node->AddSizeOfIncludingThis(aWindowSizes.mState, aWindowSizes.mStyleSizes, + &nodeSize); + // This is where we transfer the nodeSize obtained from + // nsINode::AddSizeOfIncludingThis() to a value in nsWindowSizes. switch (node->NodeType()) { case nsIDOMNode::ELEMENT_NODE: - p = &aWindowSizes.mDOMElementNodesSize; + aWindowSizes.mDOMElementNodesSize += nodeSize; break; case nsIDOMNode::TEXT_NODE: - p = &aWindowSizes.mDOMTextNodesSize; + aWindowSizes.mDOMTextNodesSize += nodeSize; break; case nsIDOMNode::CDATA_SECTION_NODE: - p = &aWindowSizes.mDOMCDATANodesSize; + aWindowSizes.mDOMCDATANodesSize += nodeSize; break; case nsIDOMNode::COMMENT_NODE: - p = &aWindowSizes.mDOMCommentNodesSize; + aWindowSizes.mDOMCommentNodesSize += nodeSize; break; default: - p = &aWindowSizes.mDOMOtherSize; + aWindowSizes.mDOMOtherSize += nodeSize; break; } - *p += nodeSize; - if (EventListenerManager* elm = node->GetExistingListenerManager()) { aWindowSizes.mDOMEventListenersCount += elm->ListenerCount(); } } + // IMPORTANT: for our ComputedValues measurements, we want to measure + // ComputedValues accessible from DOM elements before ComputedValues not + // accessible from DOM elements (i.e. accessible only from the frame tree). + // + // Therefore, the measurement of the nsIDocument superclass must happen after + // the measurement of DOM nodes (above), because nsIDocument contains the + // PresShell, which contains the frame tree. + nsIDocument::DocAddSizeOfExcludingThis(aWindowSizes); + aWindowSizes.mStyleSheetsSize += SizeOfOwnedSheetArrayExcludingThis(mStyleSheets, aWindowSizes.mState.mMallocSizeOf); diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index ae048579cab4..2ebb658e1ee6 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -367,7 +367,7 @@ public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_SIZEOF_EXCLUDING_THIS + NS_DECL_ADDSIZEOFEXCLUDINGTHIS virtual void Reset(nsIChannel *aChannel, nsILoadGroup *aLoadGroup) override; virtual void ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup, diff --git a/dom/base/nsGenericDOMDataNode.cpp b/dom/base/nsGenericDOMDataNode.cpp index d033c8eeef2a..24fc12b20a1b 100644 --- a/dom/base/nsGenericDOMDataNode.cpp +++ b/dom/base/nsGenericDOMDataNode.cpp @@ -1109,11 +1109,12 @@ nsGenericDOMDataNode::GetAttributeChangeHint(const nsIAtom* aAttribute, return nsChangeHint(0); } -size_t -nsGenericDOMDataNode::SizeOfExcludingThis(SizeOfState& aState) const +void +nsGenericDOMDataNode::AddSizeOfExcludingThis(SizeOfState& aState, + nsStyleSizes& aSizes, + size_t* aNodeSize) const { - size_t n = nsIContent::SizeOfExcludingThis(aState); - n += mText.SizeOfExcludingThis(aState.mMallocSizeOf); - return n; + nsIContent::AddSizeOfExcludingThis(aState, aSizes, aNodeSize); + *aNodeSize += mText.SizeOfExcludingThis(aState.mMallocSizeOf); } diff --git a/dom/base/nsGenericDOMDataNode.h b/dom/base/nsGenericDOMDataNode.h index 6f3194e00df8..763279af2a10 100644 --- a/dom/base/nsGenericDOMDataNode.h +++ b/dom/base/nsGenericDOMDataNode.h @@ -67,7 +67,7 @@ class nsGenericDOMDataNode : public nsIContent public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_SIZEOF_EXCLUDING_THIS + NS_DECL_ADDSIZEOFEXCLUDINGTHIS explicit nsGenericDOMDataNode(already_AddRefed& aNodeInfo); explicit nsGenericDOMDataNode(already_AddRefed&& aNodeInfo); diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp index 7a125d19bd65..9f58a6c069c9 100644 --- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp @@ -2569,13 +2569,13 @@ nsINode::GetAccessibleNode() return nullptr; } -size_t -nsINode::SizeOfExcludingThis(SizeOfState& aState) const +void +nsINode::AddSizeOfExcludingThis(SizeOfState& aState, nsStyleSizes& aSizes, + size_t* aNodeSize) const { - size_t n = 0; EventListenerManager* elm = GetExistingListenerManager(); if (elm) { - n += elm->SizeOfIncludingThis(aState.mMallocSizeOf); + *aNodeSize += elm->SizeOfIncludingThis(aState.mMallocSizeOf); } // Measurement of the following members may be added later if DMD finds it is @@ -2586,7 +2586,6 @@ nsINode::SizeOfExcludingThis(SizeOfState& aState) const // The following members are not measured: // - mParent, mNextSibling, mPreviousSibling, mFirstChild: because they're // non-owning - return n; } #define EVENT(name_, id_, type_, struct_) \ diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h index f3a9f085dbe3..714d2a819cd5 100644 --- a/dom/base/nsINode.h +++ b/dom/base/nsINode.h @@ -17,9 +17,9 @@ #include "nsNodeInfoManager.h" // for use in NodePrincipal() #include "nsPropertyTable.h" // for typedefs #include "nsTObserverArray.h" // for member +#include "nsWindowSizes.h" // for nsStyleSizes #include "mozilla/ErrorResult.h" #include "mozilla/MemoryReporting.h" -#include "mozilla/SizeOfState.h" // for SizeOfState #include "mozilla/dom/EventTarget.h" // for base class #include "js/TypeDecls.h" // for Handle, Value, JSObject, JSContext #include "mozilla/dom/DOMString.h" @@ -264,13 +264,13 @@ private: }; // This should be used for any nsINode sub-class that has fields of its own -// that it needs to measure; any sub-class that doesn't use it will inherit -// SizeOfExcludingThis from its super-class. SizeOfIncludingThis() need not be -// defined, it is inherited from nsINode. -// This macro isn't actually specific to nodes, and bug 956400 will move it into MFBT. -#define NS_DECL_SIZEOF_EXCLUDING_THIS \ - virtual size_t SizeOfExcludingThis(mozilla::SizeOfState& aState) \ - const override; +// that it needs to measure; any sub-class that doesn't use it will inherit +// AddSizeOfExcludingThis from its super-class. AddSizeOfIncludingThis() need +// not be defined, it is inherited from nsINode. +#define NS_DECL_ADDSIZEOFEXCLUDINGTHIS \ + virtual void AddSizeOfExcludingThis(mozilla::SizeOfState& aState, \ + nsStyleSizes& aSizes, \ + size_t* aNodeSize) const override; // Categories of node properties // 0 is global. @@ -305,6 +305,10 @@ public: NS_DECLARE_STATIC_IID_ACCESSOR(NS_INODE_IID) + // The |aNodeSize| outparam on this function is where the actual node size + // value is put. It gets added to the appropriate value within |aSizes| by + // nsDocument::DocAddSizeOfExcludingThis(). + // // Among the sub-classes that inherit (directly or indirectly) from nsINode, // measurement of the following members may be added later if DMD finds it is // worthwhile: @@ -328,15 +332,20 @@ public: // The following members don't need to be measured: // - nsIContent: mPrimaryFrame, because it's non-owning and measured elsewhere // - virtual size_t SizeOfExcludingThis(mozilla::SizeOfState& aState) const; + virtual void AddSizeOfExcludingThis(mozilla::SizeOfState& aState, + nsStyleSizes& aSizes, + size_t* aNodeSize) const; // SizeOfIncludingThis doesn't need to be overridden by sub-classes because // sub-classes of nsINode are guaranteed to be laid out in memory in such a // way that |this| points to the start of the allocated object, even in // methods of nsINode's sub-classes, so aState.mMallocSizeOf(this) is always // safe to call no matter which object it was invoked on. - virtual size_t SizeOfIncludingThis(mozilla::SizeOfState& aState) const { - return aState.mMallocSizeOf(this) + SizeOfExcludingThis(aState); + virtual void AddSizeOfIncludingThis(mozilla::SizeOfState& aState, + nsStyleSizes& aSizes, + size_t* aNodeSize) const { + *aNodeSize += aState.mMallocSizeOf(this); + AddSizeOfExcludingThis(aState, aSizes, aNodeSize); } friend class nsNodeUtils; diff --git a/dom/base/nsWindowMemoryReporter.cpp b/dom/base/nsWindowMemoryReporter.cpp index bf453418ae28..2207360fe555 100644 --- a/dom/base/nsWindowMemoryReporter.cpp +++ b/dom/base/nsWindowMemoryReporter.cpp @@ -384,7 +384,7 @@ CollectWindowReports(nsGlobalWindow *aWindow, aWindowTotalSizes->mArenaSizes.mStyleContexts += windowSizes.mArenaSizes.mStyleContexts; - REPORT_SIZE("/layout/style-structs", windowSizes.mArenaSizes.mStyleStructs, + REPORT_SIZE("/layout/gecko-style-structs", windowSizes.mArenaSizes.mStyleStructs, "Memory used by style structs within a window."); aWindowTotalSizes->mArenaSizes.mStyleStructs += windowSizes.mArenaSizes.mStyleStructs; @@ -429,19 +429,17 @@ CollectWindowReports(nsGlobalWindow *aWindow, js::MemoryReportingSundriesThreshold(); size_t frameSundriesSize = 0; -#define FRAME_ID(classname, ...) \ - { \ - size_t frameSize \ - = windowSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(classname); \ - if (frameSize < FRAME_SUNDRIES_THRESHOLD) { \ - frameSundriesSize += frameSize; \ - } else { \ - REPORT_SIZE("/layout/frames/" # classname, frameSize, \ - "Memory used by frames of " \ - "type " #classname " within a window."); \ - } \ - aWindowTotalSizes->mArenaSizes.NS_ARENA_SIZES_FIELD(classname) \ - += frameSize; \ +#define FRAME_ID(classname, ...) \ + { \ + size_t size = windowSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(classname); \ + if (size < FRAME_SUNDRIES_THRESHOLD) { \ + frameSundriesSize += size; \ + } else { \ + REPORT_SIZE("/layout/frames/" # classname, size, \ + "Memory used by frames of " \ + "type " #classname " within a window."); \ + } \ + aWindowTotalSizes->mArenaSizes.NS_ARENA_SIZES_FIELD(classname) += size; \ } #define ABSTRACT_FRAME_ID(...) #include "nsFrameIdList.h" @@ -454,6 +452,50 @@ CollectWindowReports(nsGlobalWindow *aWindow, "to be shown individually."); } + // There are many different kinds of style structs, but it is likely that + // only a few matter. Implement a cutoff so we don't bloat about:memory with + // many uninteresting entries. + const size_t STYLE_SUNDRIES_THRESHOLD = + js::MemoryReportingSundriesThreshold(); + + size_t styleSundriesSize = 0; +#define STYLE_STRUCT(name_, cb_) \ + { \ + size_t size = windowSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_); \ + if (size < STYLE_SUNDRIES_THRESHOLD) { \ + styleSundriesSize += size; \ + } else { \ + REPORT_SIZE("/layout/servo-style-structs/" # name_, size, \ + "Memory used by the " #name_ " Servo style structs " \ + "within a window."); \ + } \ + aWindowTotalSizes->mStyleSizes.NS_STYLE_SIZES_FIELD(name_) += size; \ + } +#define STYLE_STRUCT_LIST_IGNORE_VARIABLES +#include "nsStyleStructList.h" +#undef STYLE_STRUCT +#undef STYLE_STRUCT_LIST_IGNORE_VARIABLES + + if (styleSundriesSize > 0) { + REPORT_SIZE("/layout/servo-style-structs/sundries", styleSundriesSize, + "The sum of all memory used by Servo style structs which were " + "too small to be shown individually."); + } + + REPORT_SIZE("/layout/computed-values/dom", + windowSizes.mStyleSizes.mComputedValuesDom, + "Memory used by ComputedValues objects accessible from DOM " + "elements."); + aWindowTotalSizes->mStyleSizes.mComputedValuesDom += + windowSizes.mStyleSizes.mComputedValuesDom; + + REPORT_SIZE("/layout/computed-values/non-dom", + windowSizes.mStyleSizes.mComputedValuesNonDom, + "Memory used by ComputedValues objects not accessible from DOM " + "elements."); + aWindowTotalSizes->mStyleSizes.mComputedValuesNonDom += + windowSizes.mStyleSizes.mComputedValuesNonDom; + #undef REPORT_SIZE #undef REPORT_COUNT } @@ -575,9 +617,9 @@ nsWindowMemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport, windowTotalSizes.mArenaSizes.mStyleContexts, "This is the sum of all windows' 'layout/style-contexts' numbers."); - REPORT("window-objects/layout/style-structs", + REPORT("window-objects/layout/gecko-style-structs", windowTotalSizes.mArenaSizes.mStyleStructs, - "This is the sum of all windows' 'layout/style-structs' numbers."); + "This is the sum of all windows' 'layout/gecko-style-structs' numbers."); REPORT("window-objects/layout/style-sets", windowTotalSizes.mLayoutStyleSetsSize, "This is the sum of all windows' 'layout/style-sets' numbers."); @@ -603,6 +645,24 @@ nsWindowMemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport, "Memory used for layout frames within windows. " "This is the sum of all windows' 'layout/frames/' numbers."); + size_t styleTotal = 0; +#define STYLE_STRUCT(name_, cb_) \ + styleTotal += windowTotalSizes.mStyleSizes.NS_STYLE_SIZES_FIELD(name_); +#define STYLE_STRUCT_LIST_IGNORE_VARIABLES +#include "nsStyleStructList.h" +#undef STYLE_STRUCT +#undef STYLE_STRUCT_LIST_IGNORE_VARIABLES + + REPORT("window-objects/layout/servo-style-structs", styleTotal, + "Memory used for style structs within windows. This is the sum of " + "all windows' 'layout/servo-style-structs/' numbers."); + + REPORT("window-objects/layout/computed-values", + windowTotalSizes.mStyleSizes.mComputedValuesDom + + windowTotalSizes.mStyleSizes.mComputedValuesNonDom, + "This is the sum of all windows' 'layout/computed-values/' " + "numbers."); + #undef REPORT return NS_OK; diff --git a/dom/base/nsWindowSizes.h b/dom/base/nsWindowSizes.h index 3dca81048860..a92684eb1e8d 100644 --- a/dom/base/nsWindowSizes.h +++ b/dom/base/nsWindowSizes.h @@ -36,6 +36,11 @@ public: size_t mOther; }; +#define ZERO_SIZE(kind, mSize) mSize(0), +#define ADD_TO_TAB_SIZES(kind, mSize) aSizes->add(nsTabSizes::kind, mSize); +#define ADD_TO_TOTAL_SIZE(kind, mSize) total += mSize; +#define DECL_SIZE(kind, mSize) size_t mSize; + #define NS_ARENA_SIZES_FIELD(classname) mArena##classname struct nsArenaSizes { @@ -47,24 +52,24 @@ struct nsArenaSizes { nsArenaSizes() : - #define ZERO_SIZE(kind, mSize) mSize(0), FOR_EACH_SIZE(ZERO_SIZE) - #undef ZERO_SIZE - #define FRAME_ID(classname, ...) NS_ARENA_SIZES_FIELD(classname)(), + + #define FRAME_ID(classname, ...) \ + NS_ARENA_SIZES_FIELD(classname)(0), #define ABSTRACT_FRAME_ID(...) #include "nsFrameIdList.h" #undef FRAME_ID #undef ABSTRACT_FRAME_ID + dummy() {} - void addToTabSizes(nsTabSizes *sizes) const + void addToTabSizes(nsTabSizes* aSizes) const { - #define ADD_TO_TAB_SIZES(kind, mSize) sizes->add(nsTabSizes::kind, mSize); FOR_EACH_SIZE(ADD_TO_TAB_SIZES) - #undef ADD_TO_TAB_SIZES + #define FRAME_ID(classname, ...) \ - sizes->add(nsTabSizes::Other, NS_ARENA_SIZES_FIELD(classname)); + aSizes->add(nsTabSizes::Other, NS_ARENA_SIZES_FIELD(classname)); #define ABSTRACT_FRAME_ID(...) #include "nsFrameIdList.h" #undef FRAME_ID @@ -74,28 +79,95 @@ struct nsArenaSizes { size_t getTotalSize() const { size_t total = 0; - #define ADD_TO_TOTAL_SIZE(kind, mSize) total += mSize; + FOR_EACH_SIZE(ADD_TO_TOTAL_SIZE) - #undef ADD_TO_TOTAL_SIZE + #define FRAME_ID(classname, ...) \ total += NS_ARENA_SIZES_FIELD(classname); #define ABSTRACT_FRAME_ID(...) #include "nsFrameIdList.h" #undef FRAME_ID #undef ABSTRACT_FRAME_ID + return total; } - #define DECL_SIZE(kind, mSize) size_t mSize; FOR_EACH_SIZE(DECL_SIZE) - #undef DECL_SIZE - #define FRAME_ID(classname, ...) size_t NS_ARENA_SIZES_FIELD(classname); + + #define FRAME_ID(classname, ...) \ + size_t NS_ARENA_SIZES_FIELD(classname); #define ABSTRACT_FRAME_ID(...) #include "nsFrameIdList.h" #undef FRAME_ID #undef ABSTRACT_FRAME_ID - int dummy; // present just to absorb the trailing comma from FRAME_ID in the - // constructor + + // Present just to absorb the trailing comma in the constructor. + int dummy; + +#undef FOR_EACH_SIZE +}; + +#define NS_STYLE_SIZES_FIELD(name_) mStyle##name_ + +struct nsStyleSizes +{ +#define FOR_EACH_SIZE(macro) \ + macro(Style, mComputedValuesDom) \ + macro(Style, mComputedValuesNonDom) + + nsStyleSizes() + : + FOR_EACH_SIZE(ZERO_SIZE) + + #define STYLE_STRUCT(name_, cb_) \ + NS_STYLE_SIZES_FIELD(name_)(0), + #define STYLE_STRUCT_LIST_IGNORE_VARIABLES + #include "nsStyleStructList.h" + #undef STYLE_STRUCT + #undef STYLE_STRUCT_LIST_IGNORE_VARIABLES + + dummy() + {} + + void addToTabSizes(nsTabSizes* aSizes) const + { + FOR_EACH_SIZE(ADD_TO_TAB_SIZES) + + #define STYLE_STRUCT(name_, cb_) \ + aSizes->add(nsTabSizes::Style, NS_STYLE_SIZES_FIELD(name_)); + #define STYLE_STRUCT_LIST_IGNORE_VARIABLES + #include "nsStyleStructList.h" + #undef STYLE_STRUCT + #undef STYLE_STRUCT_LIST_IGNORE_VARIABLES + } + + size_t getTotalSize() const + { + size_t total = 0; + + FOR_EACH_SIZE(ADD_TO_TOTAL_SIZE) + + #define STYLE_STRUCT(name_, cb_) \ + total += NS_STYLE_SIZES_FIELD(name_); + #define STYLE_STRUCT_LIST_IGNORE_VARIABLES + #include "nsStyleStructList.h" + #undef STYLE_STRUCT + #undef STYLE_STRUCT_LIST_IGNORE_VARIABLES + + return total; + } + + FOR_EACH_SIZE(DECL_SIZE) + + #define STYLE_STRUCT(name_, cb_) \ + size_t NS_STYLE_SIZES_FIELD(name_); + #define STYLE_STRUCT_LIST_IGNORE_VARIABLES + #include "nsStyleStructList.h" + #undef STYLE_STRUCT + #undef STYLE_STRUCT_LIST_IGNORE_VARIABLES + + // Present just to absorb the trailing comma in the constructor. + int dummy; #undef FOR_EACH_SIZE }; @@ -122,43 +194,50 @@ class nsWindowSizes public: explicit nsWindowSizes(mozilla::SizeOfState& aState) : - #define ZERO_SIZE(kind, mSize) mSize(0), FOR_EACH_SIZE(ZERO_SIZE) - #undef ZERO_SIZE mDOMEventTargetsCount(0), mDOMEventListenersCount(0), mArenaSizes(), + mStyleSizes(), mState(aState) {} - void addToTabSizes(nsTabSizes *sizes) const { - #define ADD_TO_TAB_SIZES(kind, mSize) sizes->add(nsTabSizes::kind, mSize); + void addToTabSizes(nsTabSizes* aSizes) const { FOR_EACH_SIZE(ADD_TO_TAB_SIZES) - #undef ADD_TO_TAB_SIZES - mArenaSizes.addToTabSizes(sizes); + mArenaSizes.addToTabSizes(aSizes); + mStyleSizes.addToTabSizes(aSizes); } size_t getTotalSize() const { size_t total = 0; - #define ADD_TO_TOTAL_SIZE(kind, mSize) total += mSize; + FOR_EACH_SIZE(ADD_TO_TOTAL_SIZE) - #undef ADD_TO_TOTAL_SIZE total += mArenaSizes.getTotalSize(); + total += mStyleSizes.getTotalSize(); + return total; } - #define DECL_SIZE(kind, mSize) size_t mSize; FOR_EACH_SIZE(DECL_SIZE); - #undef DECL_SIZE uint32_t mDOMEventTargetsCount; uint32_t mDOMEventListenersCount; nsArenaSizes mArenaSizes; + + // This is Stylo-only because in Gecko these style structs are stored in the + // nsPresArena, and so are measured as part of that. + nsStyleSizes mStyleSizes; + mozilla::SizeOfState& mState; #undef FOR_EACH_SIZE }; +#undef ZERO_SIZE +#undef ADD_TO_TAB_SIZES +#undef ADD_TO_TOTAL_SIZE +#undef DECL_SIZE + #endif // nsWindowSizes_h diff --git a/dom/html/HTMLAnchorElement.cpp b/dom/html/HTMLAnchorElement.cpp index 0b5fc35d68bb..e62df45f68cd 100644 --- a/dom/html/HTMLAnchorElement.cpp +++ b/dom/html/HTMLAnchorElement.cpp @@ -405,11 +405,13 @@ HTMLAnchorElement::IntrinsicState() const return Link::LinkState() | nsGenericHTMLElement::IntrinsicState(); } -size_t -HTMLAnchorElement::SizeOfExcludingThis(mozilla::SizeOfState& aState) const +void +HTMLAnchorElement::AddSizeOfExcludingThis(SizeOfState& aState, + nsStyleSizes& aSizes, + size_t* aNodeSize) const { - return nsGenericHTMLElement::SizeOfExcludingThis(aState) + - Link::SizeOfExcludingThis(aState); + nsGenericHTMLElement::AddSizeOfExcludingThis(aState, aSizes, aNodeSize); + *aNodeSize += Link::SizeOfExcludingThis(aState); } } // namespace dom diff --git a/dom/html/HTMLAnchorElement.h b/dom/html/HTMLAnchorElement.h index 966fe7983ea7..b6af1180c14a 100644 --- a/dom/html/HTMLAnchorElement.h +++ b/dom/html/HTMLAnchorElement.h @@ -48,8 +48,7 @@ public: // nsIDOMHTMLAnchorElement NS_DECL_NSIDOMHTMLANCHORELEMENT - // DOM memory reporter participant - NS_DECL_SIZEOF_EXCLUDING_THIS + NS_DECL_ADDSIZEOFEXCLUDINGTHIS virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent, nsIContent* aBindingParent, diff --git a/dom/html/HTMLAreaElement.cpp b/dom/html/HTMLAreaElement.cpp index a8a7824c5ab1..d49fbf62be9f 100644 --- a/dom/html/HTMLAreaElement.cpp +++ b/dom/html/HTMLAreaElement.cpp @@ -219,11 +219,13 @@ HTMLAreaElement::IntrinsicState() const return Link::LinkState() | nsGenericHTMLElement::IntrinsicState(); } -size_t -HTMLAreaElement::SizeOfExcludingThis(mozilla::SizeOfState& aState) const +void +HTMLAreaElement::AddSizeOfExcludingThis(SizeOfState& aState, + nsStyleSizes& aSizes, + size_t* aNodeSize) const { - return nsGenericHTMLElement::SizeOfExcludingThis(aState) + - Link::SizeOfExcludingThis(aState); + nsGenericHTMLElement::AddSizeOfExcludingThis(aState, aSizes, aNodeSize); + *aNodeSize += Link::SizeOfExcludingThis(aState); } JSObject* diff --git a/dom/html/HTMLAreaElement.h b/dom/html/HTMLAreaElement.h index 79d07f5d1418..47d1a962d37d 100644 --- a/dom/html/HTMLAreaElement.h +++ b/dom/html/HTMLAreaElement.h @@ -36,8 +36,7 @@ public: NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLAreaElement, nsGenericHTMLElement) - // DOM memory reporter participant - NS_DECL_SIZEOF_EXCLUDING_THIS + NS_DECL_ADDSIZEOFEXCLUDINGTHIS virtual int32_t TabIndexDefault() override; diff --git a/dom/html/HTMLLinkElement.cpp b/dom/html/HTMLLinkElement.cpp index 43ab36ec3cbe..a426f117f89e 100644 --- a/dom/html/HTMLLinkElement.cpp +++ b/dom/html/HTMLLinkElement.cpp @@ -508,11 +508,13 @@ HTMLLinkElement::IntrinsicState() const return Link::LinkState() | nsGenericHTMLElement::IntrinsicState(); } -size_t -HTMLLinkElement::SizeOfExcludingThis(mozilla::SizeOfState& aState) const +void +HTMLLinkElement::AddSizeOfExcludingThis(SizeOfState& aState, + nsStyleSizes& aSizes, + size_t* aNodeSize) const { - return nsGenericHTMLElement::SizeOfExcludingThis(aState) + - Link::SizeOfExcludingThis(aState); + nsGenericHTMLElement::AddSizeOfExcludingThis(aState, aSizes, aNodeSize); + *aNodeSize += Link::SizeOfExcludingThis(aState); } JSObject* diff --git a/dom/html/HTMLLinkElement.h b/dom/html/HTMLLinkElement.h index 33ae8ea9c769..3ee187856812 100644 --- a/dom/html/HTMLLinkElement.h +++ b/dom/html/HTMLLinkElement.h @@ -36,8 +36,7 @@ public: // nsIDOMHTMLLinkElement NS_DECL_NSIDOMHTMLLINKELEMENT - // DOM memory reporter participant - NS_DECL_SIZEOF_EXCLUDING_THIS + NS_DECL_ADDSIZEOFEXCLUDINGTHIS void LinkAdded(); void LinkRemoved(); diff --git a/dom/media/webaudio/AudioDestinationNode.cpp b/dom/media/webaudio/AudioDestinationNode.cpp index 6a2d6eddc42f..43039eeae41b 100644 --- a/dom/media/webaudio/AudioDestinationNode.cpp +++ b/dom/media/webaudio/AudioDestinationNode.cpp @@ -406,10 +406,11 @@ AudioDestinationNode::DestroyMediaStream() void AudioDestinationNode::NotifyMainThreadStreamFinished() { + MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mStream->IsFinished()); if (mIsOffline) { - NS_DispatchToCurrentThread( + AbstractMainThread()->Dispatch( NewRunnableMethod("dom::AudioDestinationNode::FireOfflineCompletionEvent", this, &AudioDestinationNode::FireOfflineCompletionEvent)); diff --git a/dom/svg/SVGPathElement.cpp b/dom/svg/SVGPathElement.cpp index a028e34c05b6..f99fddd17ce2 100644 --- a/dom/svg/SVGPathElement.cpp +++ b/dom/svg/SVGPathElement.cpp @@ -45,11 +45,13 @@ SVGPathElement::SVGPathElement(already_AddRefed& aNodeIn //---------------------------------------------------------------------- // memory reporting methods -size_t -SVGPathElement::SizeOfExcludingThis(mozilla::SizeOfState& aState) const +void +SVGPathElement::AddSizeOfExcludingThis(SizeOfState& aState, + nsStyleSizes& aSizes, + size_t* aNodeSize) const { - return SVGPathElementBase::SizeOfExcludingThis(aState) + - mD.SizeOfExcludingThis(aState.mMallocSizeOf); + SVGPathElementBase::AddSizeOfExcludingThis(aState, aSizes, aNodeSize); + *aNodeSize += mD.SizeOfExcludingThis(aState.mMallocSizeOf); } //---------------------------------------------------------------------- diff --git a/dom/svg/SVGPathElement.h b/dom/svg/SVGPathElement.h index 05f364443d4a..0c74425af8db 100644 --- a/dom/svg/SVGPathElement.h +++ b/dom/svg/SVGPathElement.h @@ -35,8 +35,7 @@ protected: explicit SVGPathElement(already_AddRefed& aNodeInfo); public: - // DOM memory reporter participant - NS_DECL_SIZEOF_EXCLUDING_THIS + NS_DECL_ADDSIZEOFEXCLUDINGTHIS // nsIContent interface NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* name) const override; diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 63a503f1c73e..f6d3e93d212a 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -2121,16 +2121,6 @@ MOZ_DEFINE_MALLOC_SIZE_OF(OrphanMallocSizeOf) namespace xpc { -static size_t -SizeOfTreeIncludingThis(nsINode* tree, SizeOfState& aState) -{ - size_t n = tree->SizeOfIncludingThis(aState); - for (nsIContent* child = tree->GetFirstChild(); child; child = child->GetNextNode(tree)) - n += child->SizeOfIncludingThis(aState); - - return n; -} - class OrphanReporter : public JS::ObjectPrivateVisitor { public: @@ -2154,12 +2144,28 @@ class OrphanReporter : public JS::ObjectPrivateVisitor // and then record its root so we don't measure it again. nsCOMPtr orphanTree = node->SubtreeRoot(); if (orphanTree && !mState.HaveSeenPtr(orphanTree.get())) { - n += SizeOfTreeIncludingThis(orphanTree, mState); + n += SizeOfTreeIncludingThis(orphanTree); } } return n; } + size_t SizeOfTreeIncludingThis(nsINode* tree) + { + size_t nodeSize = 0; + nsStyleSizes sizes; + tree->AddSizeOfIncludingThis(mState, sizes, &nodeSize); + for (nsIContent* child = tree->GetFirstChild(); child; child = child->GetNextNode(tree)) + child->AddSizeOfIncludingThis(mState, sizes, &nodeSize); + + // We combine the node size with nsStyleSizes here. It's not ideal, but + // it's hard to get the style structs measurements out to + // nsWindowMemoryReporter. Also, we drop mServoData in + // UnbindFromTree(), so in theory any non-in-tree element won't have + // any style data to measure. + return nodeSize + sizes.getTotalSize(); + } + private: SizeOfState mState; }; diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp index 12f7ef2cf15c..7dd794146ae6 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp @@ -11045,8 +11045,7 @@ PresShell::AddSizeOfIncludingThis(nsWindowSizes& aSizes) const nsIFrame* rootFrame = mFrameConstructor->GetRootFrame(); if (rootFrame) { - aSizes.mLayoutFramePropertiesSize += - rootFrame->SizeOfFramePropertiesForTree(mallocSizeOf); + rootFrame->AddSizeOfExcludingThisForTree(aSizes); } } diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index e28aec4354e5..31d71cc7b811 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -10701,22 +10701,29 @@ nsFrame::HasCSSTransitions() return collection && collection->mAnimations.Length() > 0; } -size_t -nsIFrame::SizeOfFramePropertiesForTree(MallocSizeOf aMallocSizeOf) const +void +nsIFrame::AddSizeOfExcludingThisForTree(nsWindowSizes& aSizes) const { - size_t result = 0; + aSizes.mLayoutFramePropertiesSize += + mProperties.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf); - result += mProperties.SizeOfExcludingThis(aMallocSizeOf); + // We don't do this for Gecko because this stuff is stored in the nsPresArena + // and so measured elsewhere. + if (mStyleContext->IsServo()) { + ServoStyleContext* sc = mStyleContext->AsServo(); + if (!aSizes.mState.HaveSeenPtr(sc)) { + sc->AddSizeOfIncludingThis(aSizes.mState, aSizes.mStyleSizes, + /* isDOM = */ false); + } + } FrameChildListIterator iter(this); while (!iter.IsDone()) { for (const nsIFrame* f : iter.CurrentList()) { - result += f->SizeOfFramePropertiesForTree(aMallocSizeOf); + f->AddSizeOfExcludingThisForTree(aSizes); } iter.Next(); } - - return result; } // Box layout debugging diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index af81d83e7d0a..0fc53a103fda 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -88,6 +88,7 @@ class nsIContent; class nsContainerFrame; class nsPlaceholderFrame; class nsStyleChangeList; +class nsWindowSizes; struct nsPeekOffsetStruct; struct nsPoint; @@ -3541,8 +3542,10 @@ public: mProperties.DeleteAll(this); } - // Reports size of the FrameProperties for this frame and its descendants - size_t SizeOfFramePropertiesForTree(mozilla::MallocSizeOf aMallocSizeOf) const; + // nsIFrames themselves are in the nsPresArena, and so are not measured here. + // Instead, this measures heap-allocated things hanging off the nsIFrame, and + // likewise for its descendants. + void AddSizeOfExcludingThisForTree(nsWindowSizes& aWindowSizes) const; /** * Return true if and only if this frame obeys visibility:hidden. diff --git a/layout/style/ServoBindingList.h b/layout/style/ServoBindingList.h index f27ee05167d4..9bd549745682 100644 --- a/layout/style/ServoBindingList.h +++ b/layout/style/ServoBindingList.h @@ -20,8 +20,19 @@ // Element data SERVO_BINDING_FUNC(Servo_Element_ClearData, void, RawGeckoElementBorrowed node) -SERVO_BINDING_FUNC(Servo_Element_SizeOfExcludingThis, size_t, mozilla::MallocSizeOf, - mozilla::SeenPtrs* seen_ptrs, RawGeckoElementBorrowed node) +SERVO_BINDING_FUNC(Servo_Element_SizeOfExcludingThisAndCVs, size_t, + mozilla::MallocSizeOf, mozilla::SeenPtrs* seen_ptrs, + RawGeckoElementBorrowed node) +SERVO_BINDING_FUNC(Servo_Element_HasPrimaryComputedValues, bool, + RawGeckoElementBorrowed node) +SERVO_BINDING_FUNC(Servo_Element_GetPrimaryComputedValues, + ServoStyleContextStrong, + RawGeckoElementBorrowed node) +SERVO_BINDING_FUNC(Servo_Element_HasPseudoComputedValues, bool, + RawGeckoElementBorrowed node, size_t index) +SERVO_BINDING_FUNC(Servo_Element_GetPseudoComputedValues, + ServoStyleContextStrong, + RawGeckoElementBorrowed node, size_t index) // Styleset and Stylesheet management SERVO_BINDING_FUNC(Servo_StyleSheet_FromUTF8Bytes, RawServoStyleSheetContentsStrong, diff --git a/layout/style/ServoBindings.cpp b/layout/style/ServoBindings.cpp index 8324281b2c3b..7059d39e8e3f 100644 --- a/layout/style/ServoBindings.cpp +++ b/layout/style/ServoBindings.cpp @@ -235,6 +235,46 @@ ServoComputedData::GetStyleVariables() const "called"); } +MOZ_DEFINE_MALLOC_SIZE_OF(ServoStyleStructsMallocSizeOf) + +void +ServoComputedData::AddSizeOfExcludingThis(SizeOfState& aState, + nsStyleSizes& aSizes) const +{ + // XXX WARNING: GetStyleFoo() returns an nsStyleFoo pointer. This nsStyleFoo + // sits within a servo_arc::Arc, i.e. it is preceded by a word-sized + // refcount. So this pointer is an interior pointer. To get the start address + // of the heap block we move the pointer back by one word. For this to work, + // two things must be true. + // + // - The layout of servo_arc::Arc must stay the same. + // + // - The alignment of each nsStyleFoo must not be greater than the size of a + // word (otherwise padding might be inserted between the refcount and the + // struct in the servo_arc::Arc). + // + // In the long run a better solution here is for mozjemalloc to provide a + // function that converts an interior pointer to a start pointer (bug + // 1389305), but that's not available right now. + // + // Also, we use ServoStyleStructsMallocSizeOf rather than + // |aState.mMallocSizeOf| to better distinguish in DMD's output the memory + // measured here. +#define STYLE_STRUCT(name_, cb_) \ + static_assert(alignof(nsStyle##name_) <= sizeof(size_t), \ + "alignment will break AddSizeOfExcludingThis()"); \ + const char* p##name_ = reinterpret_cast(GetStyle##name_()); \ + p##name_ -= sizeof(size_t); \ + if (!aState.HaveSeenPtr(p##name_)) { \ + aSizes.NS_STYLE_SIZES_FIELD(name_) += \ + ServoStyleStructsMallocSizeOf(p##name_); \ + } + #define STYLE_STRUCT_LIST_IGNORE_VARIABLES +#include "nsStyleStructList.h" +#undef STYLE_STRUCT +#undef STYLE_STRUCT_LIST_IGNORE_VARIABLES +} + void Gecko_ServoStyleContext_Destroy(ServoStyleContext* aContext) { diff --git a/layout/style/ServoStyleContext.h b/layout/style/ServoStyleContext.h index a969e8ec516a..f2918ff999f6 100644 --- a/layout/style/ServoStyleContext.h +++ b/layout/style/ServoStyleContext.h @@ -7,7 +7,10 @@ #ifndef mozilla_ServoStyleContext_h #define mozilla_ServoStyleContext_h +#include "nsIMemoryReporter.h" #include "nsStyleContext.h" +#include "nsWindowSizes.h" +#include namespace mozilla { @@ -15,6 +18,8 @@ namespace dom { class Element; } // namespace dom +MOZ_DEFINE_MALLOC_SIZE_OF(ServoComputedValuesMallocSizeOf) + class ServoStyleContext final : public nsStyleContext { public: @@ -95,6 +100,29 @@ public: */ inline void ResolveSameStructsAs(const ServoStyleContext* aOther); + void AddSizeOfIncludingThis(SizeOfState& aState, nsStyleSizes& aSizes, + bool aIsDOM) const + { + // XXX WARNING: similar to ServoComputedData::AddSizeOfExcludingThis(), + // but here we need to step back 4 or 8 bytes to get past the servo_arc::Arc + // refcount to the base pointer. + static_assert(alignof(ServoStyleContext) == 4 || + alignof(ServoStyleContext) == 8, + "alignment will break AddSizeOfExcludingThis()"); + const char* p = reinterpret_cast(this); + p -= std::max(sizeof(size_t), alignof(ServoStyleContext)); + + // We use ServoComputedValuesMallocSizeOf rather than + // |aState.mMallocSizeOf| to better distinguish in DMD's output the memory + // measured here. + if (aIsDOM) { + aSizes.mComputedValuesDom += ServoComputedValuesMallocSizeOf(p); + } else { + aSizes.mComputedValuesNonDom += ServoComputedValuesMallocSizeOf(p); + } + mSource.AddSizeOfExcludingThis(aState, aSizes); + } + private: nsPresContext* mPresContext; ServoComputedData mSource; diff --git a/layout/style/ServoTypes.h b/layout/style/ServoTypes.h index c10dcdb03fac..d34ac8c1d5b1 100644 --- a/layout/style/ServoTypes.h +++ b/layout/style/ServoTypes.h @@ -18,9 +18,12 @@ * so don't add significant include dependencies to this file. */ +struct nsStyleSizes; struct ServoNodeData; namespace mozilla { +class SizeOfState; + /* * Replaced types. These get mapped to associated Servo types in bindgen. */ @@ -230,6 +233,9 @@ public: #undef STYLE_STRUCT_LIST_IGNORE_VARIABLES const nsStyleVariables* GetStyleVariables() const; + void AddSizeOfExcludingThis(mozilla::SizeOfState& aState, + nsStyleSizes& aSizes) const; + private: mozilla::ServoCustomPropertiesMap custom_properties; mozilla::ServoWritingMode writing_mode; diff --git a/layout/style/nsCSSPseudoElements.h b/layout/style/nsCSSPseudoElements.h index 6a4170859487..f69d5b138ba8 100644 --- a/layout/style/nsCSSPseudoElements.h +++ b/layout/style/nsCSSPseudoElements.h @@ -87,6 +87,9 @@ public: static bool IsCSS2PseudoElement(nsIAtom *aAtom); + // This must match EAGER_PSEUDO_COUNT in Rust code. + static const size_t kEagerPseudoCount = 4; + static bool IsEagerlyCascadedInServo(const Type aType) { return PseudoElementHasFlags(aType, CSS_PSEUDO_ELEMENT_IS_CSS2); diff --git a/layout/style/test/test_transitions_per_property.html b/layout/style/test/test_transitions_per_property.html index e2b38fd67019..e8ad84e0e2b2 100644 --- a/layout/style/test/test_transitions_per_property.html +++ b/layout/style/test/test_transitions_per_property.html @@ -74,7 +74,6 @@ var skippedProperties = { "box-shadow": true, // Bug 1387973 "caret-color": true, // Bug 1387951 "clip": true, // Bug 1387951 - "clip-path": true, // Bug 1387952 "column-count": true, // Bug 1387939 "-moz-image-region": true, // Bug 1387951 "order": true, // Bug 1387939 @@ -687,11 +686,15 @@ var clipPathTests = [ // none to shape { start: "none", end: "circle(500px at 500px 500px) border-box", - expected: ["circle", ["500px at calc(500px + 0%) calc(500px + 0%)"], "border-box"] + expected: + isServo ? ["circle", ["500px at 500px 500px"], "border-box"] + : ["circle", ["500px at calc(500px + 0%) calc(500px + 0%)"], "border-box"] }, { start: "none", end: "ellipse(500px 500px at 500px 500px) border-box", - expected: ["ellipse", ["500px 500px at calc(500px + 0%) calc(500px + 0%)"], "border-box"] + expected: + isServo ? ["ellipse", ["500px 500px at 500px 500px"], "border-box"] + : ["ellipse", ["500px 500px at calc(500px + 0%) calc(500px + 0%)"], "border-box"] }, { start: "none", end: "polygon(evenodd, 500px 500px, 500px 500px) border-box", @@ -708,11 +711,15 @@ var clipPathTests = [ expected: ["ellipse", ["200px 200px at 50% 50%"]] }, { start: "circle(100px at 100px 100px) border-box", end: "circle(500px at 500px 500px) border-box", - expected: ["circle", ["200px at calc(200px + 0%) calc(200px + 0%)"], "border-box"] + expected: + isServo ? ["circle", ["200px at 200px 200px"], "border-box"] + : ["circle", ["200px at calc(200px + 0%) calc(200px + 0%)"], "border-box"] }, { start: "ellipse(100px 100px at 100px 100px) border-box", end: "ellipse(500px 500px at 500px 500px) border-box", - expected: ["ellipse", ["200px 200px at calc(200px + 0%) calc(200px + 0%)"], "border-box"] + expected: + isServo ? ["ellipse", ["200px 200px at 200px 200px"], "border-box"] + : ["ellipse", ["200px 200px at calc(200px + 0%) calc(200px + 0%)"], "border-box"] }, { start: "polygon(evenodd, 100px 100px, 100px 100px) border-box", end: "polygon(evenodd, 500px 500px, 500px 500px) border-box", @@ -783,11 +790,15 @@ var clipPathTests = [ // mismatching boxes { start: "circle(100px at 100px 100px) border-box", end: "circle(500px at 500px 500px) content-box", - expected: ["circle", ["500px at calc(500px + 0%) calc(500px + 0%)"], "content-box"] + expected: + isServo ? ["circle", ["500px at 500px 500px"], "content-box"] + : ["circle", ["500px at calc(500px + 0%) calc(500px + 0%)"], "content-box"] }, { start: "ellipse(100px 100px at 100px 100px) border-box", end: "ellipse(500px 500px at 500px 500px) content-box", - expected: ["ellipse", ["500px 500px at calc(500px + 0%) calc(500px + 0%)"], "content-box"] + expected: + isServo ? ["ellipse", ["500px 500px at 500px 500px"], "content-box"] + : ["ellipse", ["500px 500px at calc(500px + 0%) calc(500px + 0%)"], "content-box"] }, { start: "polygon(evenodd, 100px 100px, 100px 100px) border-box", end: "polygon(evenodd, 500px 500px, 500px 500px) content-box", @@ -800,7 +811,9 @@ var clipPathTests = [ // mismatching functions { start: "circle(100px at 100px 100px) border-box", end: "ellipse(500px 500px at 500px 500px) border-box", - expected: ["ellipse", ["500px 500px at calc(500px + 0%) calc(500px + 0%)"], "border-box"] + expected: + isServo ? ["ellipse", ["500px 500px at 500px 500px"], "border-box"] + : ["ellipse", ["500px 500px at calc(500px + 0%) calc(500px + 0%)"], "border-box"] }, { start: "inset(0px round 20px)", end: "ellipse(500px 500px)", expected: ["ellipse", ["500px 500px at 50% 50%"]] diff --git a/memory/replace/dmd/DMD.cpp b/memory/replace/dmd/DMD.cpp index 2787b318b4f5..f219ade8f093 100644 --- a/memory/replace/dmd/DMD.cpp +++ b/memory/replace/dmd/DMD.cpp @@ -1677,7 +1677,9 @@ ReportHelper(const void* aPtr, bool aReportedOnAlloc) } else { // We have no record of the block. It must be a bogus pointer. This should // be extremely rare because Report() is almost always called in - // conjunction with a malloc_size_of-style function. + // conjunction with a malloc_size_of-style function. Print a message so + // that we get some feedback. + StatusMsg("Unknown pointer %p\n", aPtr); } } diff --git a/mobile/android/locales/en-US/chrome/browser.properties b/mobile/android/locales/en-US/chrome/browser.properties index 5007b3bcc309..c3dc496ddd42 100644 --- a/mobile/android/locales/en-US/chrome/browser.properties +++ b/mobile/android/locales/en-US/chrome/browser.properties @@ -404,6 +404,26 @@ getUserMedia.blockedCameraAccess = Camera has been blocked. getUserMedia.blockedMicrophoneAccess = Microphone has been blocked. getUserMedia.blockedCameraAndMicrophoneAccess = Camera and microphone have been blocked. +# LOCALIZATION NOTE (userContextPersonal.label, +# userContextWork.label, +# userContextShopping.label, +# userContextBanking.label, +# userContextNone.label): +# These strings specify the four predefined contexts included in support of the +# Contextual Identity / Containers project. Each context is meant to represent +# the context that the user is in when interacting with the site. Different +# contexts will store cookies and other information from those sites in +# different, isolated locations. You can enable the feature by typing +# about:config in the URL bar and changing privacy.userContext.enabled to true. +# Once enabled, you can open a new tab in a specific context by clicking +# File > New Container Tab > (1 of 4 contexts). Once opened, you will see these +# strings on the right-hand side of the URL bar. +# In android this will be only exposed by web extensions +userContextPersonal.label = Personal +userContextWork.label = Work +userContextBanking.label = Banking +userContextShopping.label = Shopping + # LOCALIZATION NOTE (readerMode.toolbarTip): # Tip shown to users the first time we hide the reader mode toolbar. readerMode.toolbarTip=Tap the screen to show reader options diff --git a/servo/components/style/data.rs b/servo/components/style/data.rs index 38099cb6eb65..096f09f00def 100644 --- a/servo/components/style/data.rs +++ b/servo/components/style/data.rs @@ -16,7 +16,7 @@ use shared_lock::StylesheetGuards; use std::fmt; use std::ops::{Deref, DerefMut}; #[cfg(feature = "gecko")] -use stylesheets::{MallocSizeOfWithRepeats, SizeOfState}; +use stylesheets::SizeOfState; bitflags! { flags RestyleFlags: u8 { @@ -261,6 +261,17 @@ impl ElementStyles { pub fn is_display_none(&self) -> bool { self.primary().get_box().clone_display() == display::T::none } + + #[cfg(feature = "gecko")] + fn malloc_size_of_children_excluding_cvs(&self, _state: &mut SizeOfState) -> usize { + // As the method name suggests, we don't measures the ComputedValues + // here, because they are measured on the C++ side. + + // XXX: measure the EagerPseudoArray itself, but not the ComputedValues + // within it. + + 0 + } } // We manually implement Debug for ElementStyles so that we can avoid the @@ -273,20 +284,6 @@ impl fmt::Debug for ElementStyles { } } -#[cfg(feature = "gecko")] -impl MallocSizeOfWithRepeats for ElementStyles { - fn malloc_size_of_children(&self, state: &mut SizeOfState) -> usize { - let mut n = 0; - if let Some(ref primary) = self.primary { - n += primary.malloc_size_of_children(state) - }; - - // We may measure more fields in the future if DMD says it's worth it. - - n - } -} - /// Style system data associated with an Element. /// /// In Gecko, this hangs directly off the Element. Servo, this is embedded @@ -436,12 +433,11 @@ impl ElementData { pub fn clear_restyle_flags_and_damage(&mut self) { self.restyle.clear_restyle_flags_and_damage(); } -} -#[cfg(feature = "gecko")] -impl MallocSizeOfWithRepeats for ElementData { - fn malloc_size_of_children(&self, state: &mut SizeOfState) -> usize { - let n = self.styles.malloc_size_of_children(state); + /// Measures memory usage. + #[cfg(feature = "gecko")] + pub fn malloc_size_of_children_excluding_cvs(&self, state: &mut SizeOfState) -> usize { + let n = self.styles.malloc_size_of_children_excluding_cvs(state); // We may measure more fields in the future if DMD says it's worth it. diff --git a/servo/components/style/gecko/generated/bindings.rs b/servo/components/style/gecko/generated/bindings.rs index 0d6dbabb502c..ecf25e3a6227 100644 --- a/servo/components/style/gecko/generated/bindings.rs +++ b/servo/components/style/gecko/generated/bindings.rs @@ -1915,11 +1915,26 @@ extern "C" { pub fn Servo_Element_ClearData(node: RawGeckoElementBorrowed); } extern "C" { - pub fn Servo_Element_SizeOfExcludingThis(arg1: MallocSizeOf, - seen_ptrs: *mut SeenPtrs, - node: RawGeckoElementBorrowed) + pub fn Servo_Element_SizeOfExcludingThisAndCVs(malloc_size_of: MallocSizeOf, + seen_ptrs: *mut SeenPtrs, + node: RawGeckoElementBorrowed) -> usize; } +extern "C" { + pub fn Servo_Element_HasPrimaryComputedValues(element: RawGeckoElementBorrowed) -> bool; +} +extern "C" { + pub fn Servo_Element_GetPrimaryComputedValues(element: RawGeckoElementBorrowed) + -> ServoStyleContextStrong; +} +extern "C" { + pub fn Servo_Element_HasPseudoComputedValues(element: RawGeckoElementBorrowed, index: usize) + -> bool; +} +extern "C" { + pub fn Servo_Element_GetPseudoComputedValues(element: RawGeckoElementBorrowed, index: usize) + -> ServoStyleContextStrong; +} extern "C" { pub fn Servo_StyleSheet_FromUTF8Bytes(loader: *mut Loader, gecko_stylesheet: diff --git a/servo/components/style/macros.rs b/servo/components/style/macros.rs index d72ecc73c064..8af642f80da6 100644 --- a/servo/components/style/macros.rs +++ b/servo/components/style/macros.rs @@ -84,7 +84,7 @@ macro_rules! define_keyword_type { ($name: ident, $css: expr) => { #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] - #[derive(Clone, Copy, PartialEq, ToCss)] + #[derive(Clone, ComputeSquaredDistance, Copy, PartialEq, ToCss)] pub struct $name; impl $crate::properties::animated_properties::Animatable for $name { diff --git a/servo/components/style/properties/gecko.mako.rs b/servo/components/style/properties/gecko.mako.rs index a62d00be52f3..74a7b157aea3 100644 --- a/servo/components/style/properties/gecko.mako.rs +++ b/servo/components/style/properties/gecko.mako.rs @@ -60,7 +60,6 @@ use selector_parser::PseudoElement; use servo_arc::{Arc, RawOffsetArc}; use std::mem::{forget, uninitialized, transmute, zeroed}; use std::{cmp, ops, ptr}; -use stylesheets::{MallocSizeOfWithRepeats, SizeOfState}; use values::{self, Auto, CustomIdent, Either, KeyframesName}; use values::computed::{NonNegativeAu, ToComputedValue, Percentage}; use values::computed::effects::{BoxShadow, Filter, SimpleShadow}; @@ -369,18 +368,6 @@ impl ComputedValuesInner { } } -impl MallocSizeOfWithRepeats for ComputedValues { - fn malloc_size_of_children(&self, state: &mut SizeOfState) -> usize { - let mut n = 0; - if let Some(ref raw_offset_arc) = *self.get_raw_visited_style() { - n += raw_offset_arc.with_arc(|a: &Arc| { - a.malloc_size_of_children(state) - }) - } - n - } -} - <%def name="declare_style_struct(style_struct)"> pub use ::gecko_bindings::structs::mozilla::Gecko${style_struct.gecko_name} as ${style_struct.gecko_struct_name}; impl ${style_struct.gecko_struct_name} { diff --git a/servo/components/style/properties/helpers.mako.rs b/servo/components/style/properties/helpers.mako.rs index 82eeb653e16b..e82ee073c870 100644 --- a/servo/components/style/properties/helpers.mako.rs +++ b/servo/components/style/properties/helpers.mako.rs @@ -119,8 +119,11 @@ use values::computed::ComputedVecIter; /// The computed value, effectively a list of single values. - #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] + #[derive(Clone, Debug, PartialEq)] + % if need_animatable or animation_value_type == "ComputedValue": + #[derive(ComputeSquaredDistance)] + % endif pub struct T( % if allow_empty and allow_empty != "NotInitial": pub Vec, @@ -142,16 +145,6 @@ fn add(&self, other: &Self) -> Result { self.0.add(&other.0).map(T) } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.0.compute_distance(&other.0) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - self.0.compute_squared_distance(&other.0) - } } impl ToAnimatedZero for T { @@ -956,63 +949,6 @@ %> -/// Macro for defining Animatable trait for tuple struct which has Option, -/// e.g. struct T(pub Option). -<%def name="impl_animatable_for_option_tuple(value_for_none)"> - impl Animatable for T { - #[inline] - fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) - -> Result { - match (self, other) { - (&T(Some(ref this)), &T(Some(ref other))) => { - Ok(T(this.add_weighted(other, self_portion, other_portion).ok())) - }, - (&T(Some(ref this)), &T(None)) => { - Ok(T(this.add_weighted(&${value_for_none}, self_portion, other_portion).ok())) - }, - (&T(None), &T(Some(ref other))) => { - Ok(T(${value_for_none}.add_weighted(other, self_portion, other_portion).ok())) - }, - (&T(None), &T(None)) => { - Ok(T(None)) - }, - } - } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - match (self, other) { - (&T(Some(ref this)), &T(Some(ref other))) => { - this.compute_distance(other) - }, - (&T(Some(ref value)), &T(None)) | - (&T(None), &T(Some(ref value)))=> { - value.compute_distance(&${value_for_none}) - }, - (&T(None), &T(None)) => { - Ok(0.0) - }, - } - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - match (self, other) { - (&T(Some(ref this)), &T(Some(ref other))) => { - this.compute_squared_distance(other) - }, - (&T(Some(ref value)), &T(None)) | - (&T(None), &T(Some(ref value))) => { - value.compute_squared_distance(&${value_for_none}) - }, - (&T(None), &T(None)) => { - Ok(0.0) - }, - } - } - } - - // Define property that supports prefixed intrinsic size keyword values for gecko. // E.g. -moz-max-content, -moz-min-content, etc. <%def name="gecko_size_type(name, length_type, initial_value, logical, **kwargs)"> diff --git a/servo/components/style/properties/helpers/animated_properties.mako.rs b/servo/components/style/properties/helpers/animated_properties.mako.rs index 1b4755b4bb7a..2f11d506f29c 100644 --- a/servo/components/style/properties/helpers/animated_properties.mako.rs +++ b/servo/components/style/properties/helpers/animated_properties.mako.rs @@ -49,6 +49,7 @@ use values::computed::{LengthOrPercentage, MaxLength, MozLength, Percentage, ToC use values::computed::{NonNegativeAu, NonNegativeNumber, PositiveIntegerOrAuto}; use values::computed::length::{NonNegativeLengthOrAuto, NonNegativeLengthOrNormal}; use values::computed::length::NonNegativeLengthOrPercentage; +use values::distance::{ComputeSquaredDistance, SquaredDistance}; use values::generics::{GreaterThanOrEqualToOne, NonNegative}; use values::generics::effects::Filter; use values::generics::position as generic_position; @@ -83,16 +84,6 @@ pub trait Animatable: Sized { fn accumulate(&self, other: &Self, count: u64) -> Result { self.add_weighted(other, count as f64, 1.0) } - - /// Compute distance between a value and another for a given property. - fn compute_distance(&self, _other: &Self) -> Result { Err(()) } - - /// In order to compute the Euclidean distance of a list or property value with multiple - /// components, we need to compute squared distance for each element, so the vector can sum it - /// and then get its squared root as the distance. - fn compute_squared_distance(&self, other: &Self) -> Result { - self.compute_distance(other).map(|d| d * d) - } } /// https://drafts.csswg.org/css-transitions/#animtype-repeatable-list @@ -741,28 +732,31 @@ impl Animatable for AnimationValue { } } } +} - fn compute_distance(&self, other: &Self) -> Result { +impl ComputeSquaredDistance for AnimationValue { + fn compute_squared_distance(&self, other: &Self) -> Result { match (self, other) { % for prop in data.longhands: - % if prop.animatable: - % if prop.animation_value_type != "discrete": - (&AnimationValue::${prop.camel_case}(ref from), - &AnimationValue::${prop.camel_case}(ref to)) => { - from.compute_distance(to) - }, - % else: - (&AnimationValue::${prop.camel_case}(ref _from), - &AnimationValue::${prop.camel_case}(ref _to)) => { - Err(()) - }, - % endif - % endif + % if prop.animatable: + % if prop.animation_value_type != "discrete": + (&AnimationValue::${prop.camel_case}(ref this), &AnimationValue::${prop.camel_case}(ref other)) => { + this.compute_squared_distance(other) + }, + % else: + (&AnimationValue::${prop.camel_case}(_), &AnimationValue::${prop.camel_case}(_)) => { + Err(()) + }, + % endif + % endif % endfor _ => { - panic!("Expected compute_distance of computed values of the same \ - property, got: {:?}, {:?}", self, other); - } + panic!( + "computed values should be of the same property, got: {:?}, {:?}", + self, + other + ); + }, } } } @@ -802,22 +796,21 @@ macro_rules! repeated_vec_impl { me.add_weighted(you, self_portion, other_portion) }).collect() } + } + impl ComputeSquaredDistance for $ty + where + T: ComputeSquaredDistance + RepeatableListAnimatable, + { #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.compute_squared_distance(other).map(|sd| sd.sqrt()) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - // If the length of either list is zero, the least common multiple is undefined. - if cmp::min(self.len(), other.len()) < 1 { + fn compute_squared_distance(&self, other: &Self) -> Result { + if self.is_empty() || other.is_empty() { return Err(()); } use num_integer::lcm; let len = lcm(self.len(), other.len()); - self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(me, you)| { - me.compute_squared_distance(you) + self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(this, other)| { + this.compute_squared_distance(other) }).sum() } })* @@ -832,11 +825,6 @@ impl Animatable for Au { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result { Ok(Au((self.0 as f64 * self_portion + other.0 as f64 * other_portion).round() as i32)) } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.0.compute_distance(&other.0) - } } impl Animatable for Option @@ -852,28 +840,6 @@ impl Animatable for Option _ => Err(()), } } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - match (self, other) { - (&Some(ref this), &Some(ref other)) => { - this.compute_distance(other) - }, - (&None, &None) => Ok(0.0), - _ => Err(()), - } - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - match (self, other) { - (&Some(ref this), &Some(ref other)) => { - this.compute_squared_distance(other) - }, - (&None, &None) => Ok(0.0), - _ => Err(()), - } - } } /// https://drafts.csswg.org/css-transitions/#animtype-number @@ -882,11 +848,6 @@ impl Animatable for f32 { fn add_weighted(&self, other: &f32, self_portion: f64, other_portion: f64) -> Result { Ok((*self as f64 * self_portion + *other as f64 * other_portion) as f32) } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - Ok((*self - *other).abs() as f64) - } } /// https://drafts.csswg.org/css-transitions/#animtype-number @@ -895,11 +856,6 @@ impl Animatable for f64 { fn add_weighted(&self, other: &f64, self_portion: f64, other_portion: f64) -> Result { Ok(*self * self_portion + *other * other_portion) } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - Ok((*self - *other).abs()) - } } /// https://drafts.csswg.org/css-transitions/#animtype-integer @@ -908,11 +864,6 @@ impl Animatable for i32 { fn add_weighted(&self, other: &i32, self_portion: f64, other_portion: f64) -> Result { Ok((*self as f64 * self_portion + *other as f64 * other_portion).round() as i32) } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - Ok((*self - *other).abs() as f64) - } } /// https://drafts.csswg.org/css-transitions/#animtype-number @@ -934,13 +885,6 @@ impl Animatable for Angle { } } } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - // Use the formula for calculating the distance between angles defined in SVG: - // https://www.w3.org/TR/SVG/animate.html#complexDistances - Ok((self.radians64() - other.radians64()).abs()) - } } /// https://drafts.csswg.org/css-transitions/#animtype-percentage @@ -949,11 +893,6 @@ impl Animatable for Percentage { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result { Ok(Percentage((self.0 as f64 * self_portion + other.0 as f64 * other_portion) as f32)) } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - Ok((self.0 as f64 - other.0 as f64).abs()) - } } impl ToAnimatedZero for Percentage { @@ -977,14 +916,12 @@ impl Animatable for Visibility { _ => Err(()), } } +} +impl ComputeSquaredDistance for Visibility { #[inline] - fn compute_distance(&self, other: &Self) -> Result { - if *self == *other { - Ok(0.0) - } else { - Ok(1.0) - } + fn compute_squared_distance(&self, other: &Self) -> Result { + Ok(SquaredDistance::Value(if *self == *other { 0. } else { 1. })) } } @@ -1003,16 +940,6 @@ impl Animatable for Size2D { Ok(Size2D::new(width, height)) } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - Ok(self.compute_squared_distance(other)?.sqrt()) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - Ok(self.width.compute_squared_distance(&other.width)? + self.height.compute_squared_distance(&other.height)?) - } } impl Animatable for Point2D { @@ -1044,15 +971,20 @@ impl Animatable for VerticalAlign { _ => Err(()), } } +} +impl ComputeSquaredDistance for VerticalAlign { #[inline] - fn compute_distance(&self, other: &Self) -> Result { - match (*self, *other) { - (VerticalAlign::LengthOrPercentage(ref this), - VerticalAlign::LengthOrPercentage(ref other)) => { - this.compute_distance(other) + fn compute_squared_distance(&self, other: &Self) -> Result { + match (self, other) { + (&VerticalAlign::LengthOrPercentage(ref this), &VerticalAlign::LengthOrPercentage(ref other)) => { + this.compute_squared_distance(other) + }, + _ => { + // FIXME(nox): Should this return `Ok(SquaredDistance::Value(0.))` + // if `self` and `other` are the same keyword value? + Err(()) }, - _ => Err(()), } } } @@ -1087,18 +1019,6 @@ impl Animatable for CalcLengthOrPercentage { let percentage = add_weighted_half(self.percentage, other.percentage, self_portion, other_portion)?; Ok(CalcLengthOrPercentage::with_clamping_mode(length, percentage, self.clamping_mode)) } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.compute_squared_distance(other).map(|sq| sq.sqrt()) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - let length_diff = (self.unclamped_length().0 - other.unclamped_length().0) as f64; - let percentage_diff = (self.percentage() - other.percentage()) as f64; - Ok(length_diff * length_diff + percentage_diff * percentage_diff) - } } /// https://drafts.csswg.org/css-transitions/#animtype-lpcalc @@ -1131,48 +1051,6 @@ impl Animatable for LengthOrPercentage { } } } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - match (*self, *other) { - (LengthOrPercentage::Length(ref this), - LengthOrPercentage::Length(ref other)) => { - this.compute_distance(other) - }, - (LengthOrPercentage::Percentage(ref this), - LengthOrPercentage::Percentage(ref other)) => { - this.compute_distance(other) - }, - (this, other) => { - let this: CalcLengthOrPercentage = From::from(this); - let other: CalcLengthOrPercentage = From::from(other); - this.compute_distance(&other) - } - } - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - match (*self, *other) { - (LengthOrPercentage::Length(ref this), - LengthOrPercentage::Length(ref other)) => { - let diff = (this.0 - other.0) as f64; - Ok(diff * diff) - }, - (LengthOrPercentage::Percentage(ref this), - LengthOrPercentage::Percentage(ref other)) => { - let diff = this.0 as f64 - other.0 as f64; - Ok(diff * diff) - }, - (this, other) => { - let this: CalcLengthOrPercentage = From::from(this); - let other: CalcLengthOrPercentage = From::from(other); - let length_diff = (this.unclamped_length().0 - other.unclamped_length().0) as f64; - let percentage_diff = (this.percentage() - other.percentage()) as f64; - Ok(length_diff * length_diff + percentage_diff * percentage_diff) - } - } - } } impl ToAnimatedZero for LengthOrPercentage { @@ -1210,53 +1088,6 @@ impl Animatable for LengthOrPercentageOrAuto { } } } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - match (*self, *other) { - (LengthOrPercentageOrAuto::Length(ref this), - LengthOrPercentageOrAuto::Length(ref other)) => { - this.compute_distance(other) - }, - (LengthOrPercentageOrAuto::Percentage(ref this), - LengthOrPercentageOrAuto::Percentage(ref other)) => { - this.compute_distance(other) - }, - (this, other) => { - // If one of the element is Auto, Option<> will be None, and the returned distance is Err(()) - let this: Option = From::from(this); - let other: Option = From::from(other); - this.compute_distance(&other) - } - } - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - match (*self, *other) { - (LengthOrPercentageOrAuto::Length(ref this), - LengthOrPercentageOrAuto::Length(ref other)) => { - let diff = (this.0 - other.0) as f64; - Ok(diff * diff) - }, - (LengthOrPercentageOrAuto::Percentage(ref this), - LengthOrPercentageOrAuto::Percentage(ref other)) => { - let diff = this.0 as f64 - other.0 as f64; - Ok(diff * diff) - }, - (this, other) => { - let this: Option = From::from(this); - let other: Option = From::from(other); - if let (Some(this), Some(other)) = (this, other) { - let length_diff = (this.unclamped_length().0 - other.unclamped_length().0) as f64; - let percentage_diff = (this.percentage() - other.percentage()) as f64; - Ok(length_diff * length_diff + percentage_diff * percentage_diff) - } else { - Err(()) - } - } - } - } } impl ToAnimatedZero for LengthOrPercentageOrAuto { @@ -1301,26 +1132,6 @@ impl Animatable for LengthOrPercentageOrNone { }, } } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - match (*self, *other) { - (LengthOrPercentageOrNone::Length(ref this), - LengthOrPercentageOrNone::Length(ref other)) => { - this.compute_distance(other) - }, - (LengthOrPercentageOrNone::Percentage(ref this), - LengthOrPercentageOrNone::Percentage(ref other)) => { - this.compute_distance(other) - }, - (this, other) => { - // If one of the element is Auto, Option<> will be None, and the returned distance is Err(()) - let this = >::from(this); - let other = >::from(other); - this.compute_distance(&other) - }, - } - } } impl ToAnimatedZero for LengthOrPercentageOrNone { @@ -1350,17 +1161,6 @@ impl Animatable for MozLength { _ => Err(()), } } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - match (*self, *other) { - (MozLength::LengthOrPercentageOrAuto(ref this), - MozLength::LengthOrPercentageOrAuto(ref other)) => { - this.compute_distance(other) - }, - _ => Err(()), - } - } } impl ToAnimatedZero for MozLength { @@ -1388,17 +1188,6 @@ impl Animatable for MaxLength { _ => Err(()), } } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - match (*self, *other) { - (MaxLength::LengthOrPercentageOrNone(ref this), - MaxLength::LengthOrPercentageOrNone(ref other)) => { - this.compute_distance(other) - }, - _ => Err(()), - } - } } impl ToAnimatedZero for MaxLength { @@ -1417,13 +1206,6 @@ impl Animatable for FontWeight { let weight = (weight.max(100.).min(900.) / 100.).round() * 100.; Ok(FontWeight(weight as u16)) } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - let a = self.0 as f64; - let b = other.0 as f64; - a.compute_distance(&b) - } } impl ToAnimatedZero for FontWeight { @@ -1447,12 +1229,12 @@ impl Animatable for FontStretch { Ok(result.into()) } +} +impl ComputeSquaredDistance for FontStretch { #[inline] - fn compute_distance(&self, other: &Self) -> Result { - let from = f64::from(*self); - let to = f64::from(*other); - from.compute_distance(&to) + fn compute_squared_distance(&self, other: &Self) -> Result { + f64::from(*self).compute_squared_distance(&(*other).into()) } } @@ -1500,17 +1282,6 @@ impl Animatable for generic_position::Position Result { - self.compute_squared_distance(other).map(|sd| sd.sqrt()) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - Ok(self.horizontal.compute_squared_distance(&other.horizontal)? + - self.vertical.compute_squared_distance(&other.vertical)?) - } } impl ToAnimatedZero for generic_position::Position @@ -1542,22 +1313,6 @@ impl Animatable for ClipRect { left: self.left.add_weighted(&other.left, self_portion, other_portion)?, }) } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.compute_squared_distance(other).map(|sd| sd.sqrt()) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - let list = [ - self.top.compute_distance(&other.top)?, - self.right.compute_distance(&other.right)?, - self.bottom.compute_distance(&other.bottom)?, - self.left.compute_distance(&other.left)? - ]; - Ok(list.iter().fold(0.0f64, |sum, diff| sum + diff * diff)) - } } impl ToAnimatedZero for ClipRect { @@ -1719,11 +1474,17 @@ fn add_weighted_transform_lists(from_list: &[TransformOperation], } } (&TransformOperation::Perspective(fd), - &TransformOperation::Perspective(_td)) => { + &TransformOperation::Perspective(td)) => { let mut fd_matrix = ComputedMatrix::identity(); let mut td_matrix = ComputedMatrix::identity(); - fd_matrix.m43 = -1. / fd.to_f32_px(); - td_matrix.m43 = -1. / _td.to_f32_px(); + if fd.0 > 0 { + fd_matrix.m34 = -1. / fd.to_f32_px(); + } + + if td.0 > 0 { + td_matrix.m34 = -1. / td.to_f32_px(); + } + let sum = fd_matrix.add_weighted(&td_matrix, self_portion, other_portion) .unwrap(); result.push(TransformOperation::Matrix(sum)); @@ -2641,6 +2402,14 @@ impl Animatable for TransformList { } } +impl ComputeSquaredDistance for TransformList { + #[inline] + fn compute_squared_distance(&self, _other: &Self) -> Result { + // FIXME: This should be implemented. + Err(()) + } +} + impl ToAnimatedZero for TransformList { #[inline] fn to_animated_zero(&self) -> Result { @@ -2666,32 +2435,6 @@ impl Animatable for Either } } } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - match (self, other) { - (&Either::First(ref this), &Either::First(ref other)) => { - this.compute_distance(other) - }, - (&Either::Second(ref this), &Either::Second(ref other)) => { - this.compute_distance(other) - }, - _ => Err(()) - } - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - match (self, other) { - (&Either::First(ref this), &Either::First(ref other)) => { - this.compute_squared_distance(other) - }, - (&Either::Second(ref this), &Either::Second(ref other)) => { - this.compute_squared_distance(other) - }, - _ => Err(()) - } - } } impl ToAnimatedZero for Either @@ -2789,28 +2532,14 @@ impl Animatable for IntermediateRGBA { Ok(IntermediateRGBA::new(red, green, blue, alpha)) } } +} +impl ComputeSquaredDistance for IntermediateRGBA { #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.compute_squared_distance(other).map(|sq| sq.sqrt()) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - let start = [ self.alpha, - self.red * self.alpha, - self.green * self.alpha, - self.blue * self.alpha ]; - let end = [ other.alpha, - other.red * other.alpha, - other.green * other.alpha, - other.blue * other.alpha ]; - let diff = start.iter().zip(&end) - .fold(0.0f64, |n, (&a, &b)| { - let diff = (a - b) as f64; - n + diff * diff - }); - Ok(diff) + fn compute_squared_distance(&self, other: &Self) -> Result { + let start = [ self.alpha, self.red * self.alpha, self.green * self.alpha, self.blue * self.alpha ]; + let end = [ other.alpha, other.red * other.alpha, other.green * other.alpha, other.blue * other.alpha ]; + start.iter().zip(&end).map(|(this, other)| this.compute_squared_distance(other)).sum() } } @@ -2930,31 +2659,35 @@ impl Animatable for IntermediateColor { }) } } +} +impl ComputeSquaredDistance for IntermediateColor { #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.compute_squared_distance(other).map(|sq| sq.sqrt()) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { + fn compute_squared_distance(&self, other: &Self) -> Result { // All comments in add_weighted also applies here. if self.foreground_ratio == other.foreground_ratio { if self.is_currentcolor() { - Ok(0.) + Ok(SquaredDistance::Value(0.)) } else { self.color.compute_squared_distance(&other.color) } } else if self.is_currentcolor() && other.is_numeric() { - Ok(IntermediateRGBA::transparent().compute_squared_distance(&other.color)? + 1.) + Ok( + IntermediateRGBA::transparent().compute_squared_distance(&other.color)? + + SquaredDistance::Value(1.), + ) } else if self.is_numeric() && other.is_currentcolor() { - Ok(self.color.compute_squared_distance(&IntermediateRGBA::transparent())? + 1.) + Ok( + self.color.compute_squared_distance(&IntermediateRGBA::transparent())? + + SquaredDistance::Value(1.), + ) } else { let self_color = self.effective_intermediate_rgba(); let other_color = other.effective_intermediate_rgba(); - let dist = self_color.compute_squared_distance(&other_color)?; - let ratio_diff = (self.foreground_ratio - other.foreground_ratio) as f64; - Ok(dist + ratio_diff * ratio_diff) + Ok( + self_color.compute_squared_distance(&other_color)? + + self.foreground_ratio.compute_squared_distance(&other.foreground_ratio)?, + ) } } } @@ -2978,16 +2711,15 @@ impl Animatable for IntermediateSVGPaint { fallback: self.fallback.add_weighted(&other.fallback, self_portion, other_portion)?, }) } +} +impl ComputeSquaredDistance for IntermediateSVGPaint { #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.compute_squared_distance(other).map(|sq| sq.sqrt()) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - Ok(self.kind.compute_squared_distance(&other.kind)? + - self.fallback.compute_squared_distance(&other.fallback)?) + fn compute_squared_distance(&self, other: &Self) -> Result { + Ok( + self.kind.compute_squared_distance(&other.kind)? + + self.fallback.compute_squared_distance(&other.fallback)?, + ) } } @@ -3016,16 +2748,20 @@ impl Animatable for IntermediateSVGPaintKind { _ => Err(()) } } +} +impl ComputeSquaredDistance for IntermediateSVGPaintKind { #[inline] - fn compute_distance(&self, other: &Self) -> Result { + fn compute_squared_distance(&self, other: &Self) -> Result { match (self, other) { - (&SVGPaintKind::Color(ref self_color), &SVGPaintKind::Color(ref other_color)) => { - self_color.compute_distance(other_color) + (&SVGPaintKind::Color(ref this), &SVGPaintKind::Color(ref other)) => { + this.compute_squared_distance(other) } (&SVGPaintKind::None, &SVGPaintKind::None) | (&SVGPaintKind::ContextFill, &SVGPaintKind::ContextFill) | - (&SVGPaintKind::ContextStroke, &SVGPaintKind::ContextStroke)=> Ok(0.0), + (&SVGPaintKind::ContextStroke, &SVGPaintKind::ContextStroke) => { + Ok(SquaredDistance::Value(0.)) + }, _ => Err(()) } } @@ -3060,16 +2796,6 @@ impl Animatable for SVGLength } } } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - match (self, other) { - (&SVGLength::Length(ref this), &SVGLength::Length(ref other)) => { - this.compute_distance(other) - } - _ => Err(()) - } - } } impl ToAnimatedZero for SVGLength where LengthType : ToAnimatedZero { @@ -3097,16 +2823,6 @@ impl Animatable for SVGStrokeDashArray } } } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - match (self, other) { - (&SVGStrokeDashArray::Values(ref this), &SVGStrokeDashArray::Values(ref other)) => { - this.compute_distance(other) - } - _ => Err(()) - } - } } impl ToAnimatedZero for SVGStrokeDashArray @@ -3138,16 +2854,6 @@ impl Animatable for SVGOpacity } } } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - match (self, other) { - (&SVGOpacity::Opacity(ref this), &SVGOpacity::Opacity(ref other)) => { - this.compute_distance(other) - } - _ => Err(()) - } - } } impl ToAnimatedZero for SVGOpacity @@ -3246,9 +2952,7 @@ fn add_weighted_filter_function(from: Option<<&AnimatedFilter>, } } -fn compute_filter_square_distance(from: &AnimatedFilter, - to: &AnimatedFilter) - -> Result { +fn compute_filter_square_distance(from: &AnimatedFilter, to: &AnimatedFilter) -> Result { match (from, to) { % for func in FILTER_FUNCTIONS : (&Filter::${func}(f), @@ -3296,29 +3000,24 @@ impl Animatable for AnimatedFilterList { fn add(&self, other: &Self) -> Result { Ok(AnimatedFilterList(self.0.iter().chain(other.0.iter()).cloned().collect())) } +} +impl ComputeSquaredDistance for AnimatedFilterList { #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.compute_squared_distance(other).map(|sd| sd.sqrt()) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { + fn compute_squared_distance(&self, other: &Self) -> Result { use itertools::{EitherOrBoth, Itertools}; - let mut square_distance: f64 = 0.0; - for it in self.0.iter().zip_longest(other.0.iter()) { - square_distance += match it { + self.0.iter().zip_longest(other.0.iter()).map(|it| { + match it { EitherOrBoth::Both(from, to) => { - compute_filter_square_distance(&from, &to)? + compute_filter_square_distance(&from, &to) }, EitherOrBoth::Left(list) | EitherOrBoth::Right(list)=> { let none = add_weighted_filter_function(Some(list), Some(list), 0.0, 0.0)?; - compute_filter_square_distance(&none, &list)? + compute_filter_square_distance(&none, &list) }, - }; - } - Ok(square_distance) + } + }).sum() } } @@ -3382,11 +3081,6 @@ impl Animatable for NonNegative fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result { self.0.add_weighted(&other.0, self_portion, other_portion).map(NonNegative::) } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.0.compute_distance(&other.0) - } } impl ToAnimatedZero for NonNegative @@ -3405,11 +3099,6 @@ impl Animatable for GreaterThanOrEqualToOne fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result { self.0.add_weighted(&other.0, self_portion, other_portion).map(GreaterThanOrEqualToOne::) } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.0.compute_distance(&other.0) - } } impl ToAnimatedZero for GreaterThanOrEqualToOne diff --git a/servo/components/style/properties/longhand/font.mako.rs b/servo/components/style/properties/longhand/font.mako.rs index 96ba9199bf25..0ebcf2db9a7b 100644 --- a/servo/components/style/properties/longhand/font.mako.rs +++ b/servo/components/style/properties/longhand/font.mako.rs @@ -485,7 +485,7 @@ ${helpers.single_keyword_system("font-variant-caps", /// /// However, system fonts may provide other values. Pango /// may provide 350, 380, and 1000 (on top of the existing values), for example. - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, ToCss)] + #[derive(Clone, ComputeSquaredDistance, Copy, Debug, PartialEq, Eq, Hash, ToCss)] #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))] pub struct T(pub u16); @@ -1118,6 +1118,7 @@ ${helpers.single_keyword_system("font-variant-caps", use properties::animated_properties::Animatable; use values::CSSFloat; use values::animated::{ToAnimatedValue, ToAnimatedZero}; + use values::distance::{ComputeSquaredDistance, SquaredDistance}; #[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[derive(Copy, Clone, Debug, PartialEq, ToCss)] @@ -1145,12 +1146,13 @@ ${helpers.single_keyword_system("font-variant-caps", _ => Err(()), } } + } + impl ComputeSquaredDistance for T { #[inline] - fn compute_distance(&self, other: &Self) -> Result { - match (*self, *other) { - (T::Number(ref number), T::Number(ref other)) => - number.compute_distance(other), + fn compute_squared_distance(&self, other: &Self) -> Result { + match (self, other) { + (&T::Number(ref this), &T::Number(ref other)) => this.compute_squared_distance(other), _ => Err(()), } } diff --git a/servo/components/style/properties/longhand/inherited_table.mako.rs b/servo/components/style/properties/longhand/inherited_table.mako.rs index 1be9de01bd2c..1d646cc9410e 100644 --- a/servo/components/style/properties/longhand/inherited_table.mako.rs +++ b/servo/components/style/properties/longhand/inherited_table.mako.rs @@ -31,7 +31,7 @@ ${helpers.single_keyword("caption-side", "top bottom", use values::computed::NonNegativeAu; #[cfg_attr(feature = "servo", derive(HeapSizeOf))] - #[derive(Clone, Copy, Debug, PartialEq, ToCss)] + #[derive(Clone, ComputeSquaredDistance, Copy, Debug, PartialEq, ToCss)] pub struct T { pub horizontal: NonNegativeAu, pub vertical: NonNegativeAu, @@ -49,17 +49,6 @@ ${helpers.single_keyword("caption-side", "top bottom", self_portion, other_portion)?, }) } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.compute_squared_distance(other).map(|sd| sd.sqrt()) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - Ok(self.horizontal.compute_squared_distance(&other.horizontal)? + - self.vertical.compute_squared_distance(&other.vertical)?) - } } impl ToAnimatedZero for T { diff --git a/servo/components/style/values/animated/effects.rs b/servo/components/style/values/animated/effects.rs index 9bd3456d0288..37cf926a1d7f 100644 --- a/servo/components/style/values/animated/effects.rs +++ b/servo/components/style/values/animated/effects.rs @@ -14,6 +14,7 @@ use values::Impossible; use values::animated::{ToAnimatedValue, ToAnimatedZero}; use values::computed::{Angle, NonNegativeNumber}; use values::computed::length::{Length, NonNegativeLength}; +use values::distance::{ComputeSquaredDistance, SquaredDistance}; use values::generics::effects::BoxShadow as GenericBoxShadow; use values::generics::effects::Filter as GenericFilter; use values::generics::effects::SimpleShadow as GenericSimpleShadow; @@ -102,6 +103,14 @@ where } } +impl ComputeSquaredDistance for ShadowList { + #[inline] + fn compute_squared_distance(&self, _other: &Self) -> Result { + // FIXME: This should be implemented. + Err(()) + } +} + impl ToAnimatedZero for ShadowList { #[inline] fn to_animated_zero(&self) -> Result { @@ -140,14 +149,11 @@ impl Animatable for BoxShadow { inset: self.inset, }) } +} +impl ComputeSquaredDistance for BoxShadow { #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.compute_squared_distance(other).map(|sd| sd.sqrt()) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { + fn compute_squared_distance(&self, other: &Self) -> Result { if self.inset != other.inset { return Err(()); } @@ -219,21 +225,6 @@ impl Animatable for SimpleShadow { blur: blur, }) } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.compute_squared_distance(other).map(|sd| sd.sqrt()) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - Ok( - self.color.compute_squared_distance(&other.color)? + - self.horizontal.compute_squared_distance(&other.horizontal)? + - self.vertical.compute_squared_distance(&other.vertical)? + - self.blur.compute_squared_distance(&other.blur)? - ) - } } impl ToAnimatedZero for SimpleShadow { diff --git a/servo/components/style/values/computed/background.rs b/servo/components/style/values/computed/background.rs index d2781ac18913..a2111a046e47 100644 --- a/servo/components/style/values/computed/background.rs +++ b/servo/components/style/values/computed/background.rs @@ -30,27 +30,6 @@ impl Animatable for BackgroundSize { _ => Err(()), } } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.compute_squared_distance(other).map(|sd| sd.sqrt()) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - match (self, other) { - ( - &GenericBackgroundSize::Explicit { width: self_width, height: self_height }, - &GenericBackgroundSize::Explicit { width: other_width, height: other_height }, - ) => { - Ok( - self_width.compute_squared_distance(&other_width)? + - self_height.compute_squared_distance(&other_height)? - ) - } - _ => Err(()), - } - } } impl ToAnimatedZero for BackgroundSize { diff --git a/servo/components/style/values/computed/length.rs b/servo/components/style/values/computed/length.rs index ed4a38255d3e..fbe3b56e7248 100644 --- a/servo/components/style/values/computed/length.rs +++ b/servo/components/style/values/computed/length.rs @@ -12,6 +12,7 @@ use style_traits::values::specified::AllowedLengthType; use super::{Number, ToComputedValue, Context, Percentage}; use values::{Auto, CSSFloat, Either, ExtremumLength, None_, Normal, specified}; use values::computed::{NonNegativeAu, NonNegativeNumber}; +use values::distance::{ComputeSquaredDistance, SquaredDistance}; use values::generics::NonNegative; use values::specified::length::{AbsoluteLength, FontBaseSize, FontRelativeLength}; use values::specified::length::ViewportPercentageLength; @@ -71,6 +72,18 @@ pub struct CalcLengthOrPercentage { pub percentage: Option, } +impl ComputeSquaredDistance for CalcLengthOrPercentage { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + // FIXME(nox): This looks incorrect to me, to add a distance between lengths + // with a distance between percentages. + Ok( + self.unclamped_length().compute_squared_distance(&other.unclamped_length())? + + self.percentage().compute_squared_distance(&other.percentage())?, + ) + } +} + impl CalcLengthOrPercentage { /// Returns a new `CalcLengthOrPercentage`. #[inline] @@ -257,6 +270,23 @@ pub enum LengthOrPercentage { Calc(CalcLengthOrPercentage), } +impl ComputeSquaredDistance for LengthOrPercentage { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + match (self, other) { + (&LengthOrPercentage::Length(ref this), &LengthOrPercentage::Length(ref other)) => { + this.compute_squared_distance(other) + }, + (&LengthOrPercentage::Percentage(ref this), &LengthOrPercentage::Percentage(ref other)) => { + this.compute_squared_distance(other) + }, + (this, other) => { + CalcLengthOrPercentage::compute_squared_distance(&(*this).into(), &(*other).into()) + } + } + } +} + impl From for LengthOrPercentage { #[inline] fn from(length: Au) -> Self { @@ -382,6 +412,23 @@ pub enum LengthOrPercentageOrAuto { Calc(CalcLengthOrPercentage), } +impl ComputeSquaredDistance for LengthOrPercentageOrAuto { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + match (self, other) { + (&LengthOrPercentageOrAuto::Length(ref this), &LengthOrPercentageOrAuto::Length(ref other)) => { + this.compute_squared_distance(other) + }, + (&LengthOrPercentageOrAuto::Percentage(ref this), &LengthOrPercentageOrAuto::Percentage(ref other)) => { + this.compute_squared_distance(other) + }, + (this, other) => { + >::compute_squared_distance(&(*this).into(), &(*other).into()) + } + } + } +} + impl LengthOrPercentageOrAuto { /// Returns true if the computed value is absolute 0 or 0%. /// @@ -460,6 +507,23 @@ pub enum LengthOrPercentageOrNone { None, } +impl ComputeSquaredDistance for LengthOrPercentageOrNone { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + match (self, other) { + (&LengthOrPercentageOrNone::Length(ref this), &LengthOrPercentageOrNone::Length(ref other)) => { + this.compute_squared_distance(other) + }, + (&LengthOrPercentageOrNone::Percentage(ref this), &LengthOrPercentageOrNone::Percentage(ref other)) => { + this.compute_squared_distance(other) + }, + (this, other) => { + >::compute_squared_distance(&(*this).into(), &(*other).into()) + } + } + } +} + impl LengthOrPercentageOrNone { /// Returns the used value. pub fn to_used_value(&self, containing_length: Au) -> Option { @@ -607,6 +671,22 @@ pub enum MozLength { ExtremumLength(ExtremumLength), } +impl ComputeSquaredDistance for MozLength { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + match (self, other) { + (&MozLength::LengthOrPercentageOrAuto(ref this), &MozLength::LengthOrPercentageOrAuto(ref other)) => { + this.compute_squared_distance(other) + }, + _ => { + // FIXME(nox): Should this return `Ok(SquaredDistance::Value(1.))` + // when `self` and `other` are the same extremum value? + Err(()) + }, + } + } +} + impl MozLength { /// Returns the `auto` value. pub fn auto() -> Self { @@ -651,6 +731,22 @@ pub enum MaxLength { ExtremumLength(ExtremumLength), } +impl ComputeSquaredDistance for MaxLength { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + match (self, other) { + (&MaxLength::LengthOrPercentageOrNone(ref this), &MaxLength::LengthOrPercentageOrNone(ref other)) => { + this.compute_squared_distance(other) + }, + _ => { + // FIXME(nox): Should this return `Ok(SquaredDistance::Value(1.))` + // when `self` and `other` are the same extremum value? + Err(()) + }, + } + } +} + impl MaxLength { /// Returns the `none` value. pub fn none() -> Self { diff --git a/servo/components/style/values/computed/mod.rs b/servo/components/style/values/computed/mod.rs index 75b9e3bb3545..b963d37fab62 100644 --- a/servo/components/style/values/computed/mod.rs +++ b/servo/components/style/values/computed/mod.rs @@ -22,6 +22,7 @@ use std::fmt; use std::sync::Arc; use style_traits::ToCss; use super::{CSSFloat, CSSInteger}; +use super::distance::{ComputeSquaredDistance, SquaredDistance}; use super::generics::{GreaterThanOrEqualToOne, NonNegative}; use super::generics::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as GenericTrackSize}; use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent; @@ -338,6 +339,15 @@ pub enum Angle { Turn(CSSFloat), } +impl ComputeSquaredDistance for Angle { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + // Use the formula for calculating the distance between angles defined in SVG: + // https://www.w3.org/TR/SVG/animate.html#complexDistances + self.radians64().compute_squared_distance(&other.radians64()) + } +} + impl Angle { /// Construct a computed `Angle` value from a radian amount. pub fn from_radians(radians: CSSFloat) -> Self { @@ -533,9 +543,9 @@ pub type LengthOrPercentageOrNumber = Either; /// NonNegativeLengthOrPercentage | NonNegativeNumber pub type NonNegativeLengthOrPercentageOrNumber = Either; -#[derive(Clone, PartialEq, Eq, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, ComputeSquaredDistance, Copy, Debug, Eq, PartialEq)] /// A computed cliprect for clip and image-region pub struct ClipRect { pub top: Option, @@ -649,7 +659,7 @@ impl From for NonNegativeAu { } /// A computed `` value. -#[derive(Clone, Copy, Debug, Default, PartialEq, HasViewportPercentage)] +#[derive(Clone, ComputeSquaredDistance, Copy, Debug, Default, HasViewportPercentage, PartialEq)] #[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))] pub struct Percentage(pub CSSFloat); diff --git a/servo/components/style/values/computed/text.rs b/servo/components/style/values/computed/text.rs index 7376616ba5ee..fd39b4dd6470 100644 --- a/servo/components/style/values/computed/text.rs +++ b/servo/components/style/values/computed/text.rs @@ -45,22 +45,6 @@ impl Animatable for LineHeight { _ => Err(()), } } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - match (*self, *other) { - (GenericLineHeight::Length(ref this), GenericLineHeight::Length(ref other)) => { - this.compute_distance(other) - }, - (GenericLineHeight::Number(ref this), GenericLineHeight::Number(ref other)) => { - this.compute_distance(other) - }, - (GenericLineHeight::Normal, GenericLineHeight::Normal) => Ok(0.), - #[cfg(feature = "gecko")] - (GenericLineHeight::MozBlockHeight, GenericLineHeight::MozBlockHeight) => Ok(0.), - _ => Err(()), - } - } } impl ToAnimatedZero for LineHeight { diff --git a/servo/components/style/values/computed/transform.rs b/servo/components/style/values/computed/transform.rs index 5bf953d46be9..70c543b9a7ff 100644 --- a/servo/components/style/values/computed/transform.rs +++ b/servo/components/style/values/computed/transform.rs @@ -37,20 +37,6 @@ impl Animatable for TransformOrigin { self.depth.add_weighted(&other.depth, self_portion, other_portion)?, )) } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.compute_squared_distance(other).map(f64::sqrt) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - Ok( - self.horizontal.compute_squared_distance(&other.horizontal)? + - self.vertical.compute_squared_distance(&other.vertical)? + - self.depth.compute_squared_distance(&other.depth)? - ) - } } impl ToAnimatedZero for TransformOrigin { diff --git a/servo/components/style/values/distance.rs b/servo/components/style/values/distance.rs new file mode 100644 index 000000000000..d4eb91f112f8 --- /dev/null +++ b/servo/components/style/values/distance.rs @@ -0,0 +1,124 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Machinery to compute distances between animatable values. + +use app_units::Au; +use euclid::Size2D; +use std::iter::Sum; +use std::ops::Add; + +/// A trait to compute squared distances between two animatable values. +pub trait ComputeSquaredDistance { + /// Computes the squared distance between two animatable values. + fn compute_squared_distance(&self, other: &Self) -> Result; +} + +/// A distance between two animatable values. +#[derive(Clone, Copy, Debug)] +pub enum SquaredDistance { + /// Represented as the square root of the squared distance. + Sqrt(f64), + /// Represented as the squared distance itself. + Value(f64), +} + +impl ComputeSquaredDistance for u16 { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + Ok(SquaredDistance::Sqrt(((*self as f64) - (*other as f64)).abs())) + } +} + +impl ComputeSquaredDistance for i32 { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + Ok(SquaredDistance::Sqrt((*self - *other).abs() as f64)) + } +} + +impl ComputeSquaredDistance for f32 { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + Ok(SquaredDistance::Sqrt((*self - *other).abs() as f64)) + } +} + +impl ComputeSquaredDistance for f64 { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + Ok(SquaredDistance::Sqrt((*self - *other).abs())) + } +} + +impl ComputeSquaredDistance for Au { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + self.0.compute_squared_distance(&other.0) + } +} + +impl ComputeSquaredDistance for Option + where T: ComputeSquaredDistance +{ + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + match (self.as_ref(), other.as_ref()) { + (Some(this), Some(other)) => this.compute_squared_distance(other), + (None, None) => Ok(SquaredDistance::Value(0.)), + _ => Err(()), + } + } +} + +impl ComputeSquaredDistance for Size2D + where T: ComputeSquaredDistance +{ + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + Ok(self.width.compute_squared_distance(&other.width)? + self.height.compute_squared_distance(&other.height)?) + } +} + +impl SquaredDistance { + /// Returns the square root of this squared distance. + pub fn sqrt(self) -> f64 { + match self { + SquaredDistance::Sqrt(this) => this, + SquaredDistance::Value(this) => this.sqrt(), + } + } +} + +impl From for f64 { + #[inline] + fn from(distance: SquaredDistance) -> Self { + match distance { + SquaredDistance::Sqrt(this) => this * this, + SquaredDistance::Value(this) => this, + } + } +} + +impl Add for SquaredDistance { + type Output = Self; + + #[inline] + fn add(self, rhs: Self) -> Self { + SquaredDistance::Value(f64::from(self) + f64::from(rhs)) + } +} + +impl Sum for SquaredDistance { + fn sum(mut iter: I) -> Self + where + I: Iterator, + { + let first = match iter.next() { + Some(first) => first, + None => return SquaredDistance::Value(0.), + }; + iter.fold(first, Add::add) + } +} diff --git a/servo/components/style/values/generics/background.rs b/servo/components/style/values/generics/background.rs index aa24454ef6dd..8a79691f3b98 100644 --- a/servo/components/style/values/generics/background.rs +++ b/servo/components/style/values/generics/background.rs @@ -5,7 +5,7 @@ //! Generic types for CSS values related to backgrounds. /// A generic value for the `background-size` property. -#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue, ToCss)] +#[derive(Clone, ComputeSquaredDistance, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue, ToCss)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub enum BackgroundSize { /// ` ` diff --git a/servo/components/style/values/generics/basic_shape.rs b/servo/components/style/values/generics/basic_shape.rs index f218fd41ba8b..cad038df0f1a 100644 --- a/servo/components/style/values/generics/basic_shape.rs +++ b/servo/components/style/values/generics/basic_shape.rs @@ -10,6 +10,7 @@ use std::fmt; use style_traits::{HasViewportPercentage, ToCss}; use values::animated::ToAnimatedZero; use values::computed::ComputedValueAsSpecified; +use values::distance::{ComputeSquaredDistance, SquaredDistance}; use values::generics::border::BorderRadius; use values::generics::position::Position; use values::generics::rect::Rect; @@ -54,7 +55,7 @@ pub enum ShapeSource { #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Debug, PartialEq, ToComputedValue, ToCss)] +#[derive(Clone, ComputeSquaredDistance, Debug, PartialEq, ToComputedValue, ToCss)] pub enum BasicShape { Inset(InsetRect), Circle(Circle), @@ -65,7 +66,7 @@ pub enum BasicShape { /// https://drafts.csswg.org/css-shapes/#funcdef-inset #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Debug, PartialEq, ToComputedValue)] +#[derive(Clone, ComputeSquaredDistance, Debug, PartialEq, ToComputedValue)] pub struct InsetRect { pub rect: Rect, pub round: Option>, @@ -74,7 +75,7 @@ pub struct InsetRect { /// https://drafts.csswg.org/css-shapes/#funcdef-circle #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Copy, Debug, PartialEq, ToComputedValue)] +#[derive(Clone, ComputeSquaredDistance, Copy, Debug, PartialEq, ToComputedValue)] pub struct Circle { pub position: Position, pub radius: ShapeRadius, @@ -83,7 +84,7 @@ pub struct Circle { /// https://drafts.csswg.org/css-shapes/#funcdef-ellipse #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Copy, Debug, PartialEq, ToComputedValue)] +#[derive(Clone, ComputeSquaredDistance, Copy, Debug, PartialEq, ToComputedValue)] pub struct Ellipse { pub position: Position, pub semiaxis_x: ShapeRadius, @@ -93,7 +94,7 @@ pub struct Ellipse { /// https://drafts.csswg.org/css-shapes/#typedef-shape-radius #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Copy, Debug, PartialEq, ToComputedValue, ToCss)] +#[derive(Clone, ComputeSquaredDistance, Copy, Debug, PartialEq, ToComputedValue, ToCss)] pub enum ShapeRadius { Length(LengthOrPercentage), ClosestSide, @@ -144,20 +145,16 @@ where _ => Err(()), } } +} - fn compute_distance(&self, other: &Self) -> Result { - match (self, other) { - ( - &ShapeSource::Shape(ref this, ref this_box), - &ShapeSource::Shape(ref other, ref other_box), - ) if this_box == other_box => { - this.compute_distance(other) - }, - _ => Err(()), - } - } - - fn compute_squared_distance(&self, other: &Self) -> Result { +// FIXME(nox): Implement ComputeSquaredDistance for T types and stop +// using PartialEq here, this will let us derive this impl. +impl ComputeSquaredDistance for ShapeSource +where + B: ComputeSquaredDistance, + T: PartialEq, +{ + fn compute_squared_distance(&self, other: &Self) -> Result { match (self, other) { ( &ShapeSource::Shape(ref this, ref this_box), @@ -209,42 +206,6 @@ where _ => Err(()), } } - - fn compute_distance(&self, other: &Self) -> Result { - match (self, other) { - (&BasicShape::Circle(ref this), &BasicShape::Circle(ref other)) => { - this.compute_distance(other) - }, - (&BasicShape::Ellipse(ref this), &BasicShape::Ellipse(ref other)) => { - this.compute_distance(other) - }, - (&BasicShape::Inset(ref this), &BasicShape::Inset(ref other)) => { - this.compute_distance(other) - }, - (&BasicShape::Polygon(ref this), &BasicShape::Polygon(ref other)) => { - this.compute_distance(other) - }, - _ => Err(()), - } - } - - fn compute_squared_distance(&self, other: &Self) -> Result { - match (self, other) { - (&BasicShape::Circle(ref this), &BasicShape::Circle(ref other)) => { - this.compute_squared_distance(other) - }, - (&BasicShape::Ellipse(ref this), &BasicShape::Ellipse(ref other)) => { - this.compute_squared_distance(other) - }, - (&BasicShape::Inset(ref this), &BasicShape::Inset(ref other)) => { - this.compute_squared_distance(other) - }, - (&BasicShape::Polygon(ref this), &BasicShape::Polygon(ref other)) => { - this.compute_squared_distance(other) - }, - _ => Err(()), - } - } } impl Animatable for InsetRect @@ -261,17 +222,6 @@ where let round = self.round.add_weighted(&other.round, self_portion, other_portion)?; Ok(InsetRect { rect, round }) } - - fn compute_distance(&self, other: &Self) -> Result { - Ok(self.compute_squared_distance(other)?.sqrt()) - } - - fn compute_squared_distance(&self, other: &Self) -> Result { - Ok( - self.rect.compute_squared_distance(&other.rect)? + - self.round.compute_squared_distance(&other.round)?, - ) - } } impl ToCss for InsetRect @@ -304,17 +254,6 @@ where let radius = self.radius.add_weighted(&other.radius, self_portion, other_portion)?; Ok(Circle { position, radius }) } - - fn compute_distance(&self, other: &Self) -> Result { - Ok(self.compute_squared_distance(other)?.sqrt()) - } - - fn compute_squared_distance(&self, other: &Self) -> Result { - Ok( - self.position.compute_squared_distance(&other.position)? + - self.radius.compute_squared_distance(&other.radius)?, - ) - } } impl Animatable for Ellipse @@ -334,18 +273,6 @@ where let semiaxis_y = self.semiaxis_y.add_weighted(&other.semiaxis_y, self_portion, other_portion)?; Ok(Ellipse { position, semiaxis_x, semiaxis_y }) } - - fn compute_distance(&self, other: &Self) -> Result { - Ok(self.compute_squared_distance(other)?.sqrt()) - } - - fn compute_squared_distance(&self, other: &Self) -> Result { - Ok( - self.position.compute_squared_distance(&other.position)? + - self.semiaxis_x.compute_squared_distance(&other.semiaxis_x)? + - self.semiaxis_y.compute_squared_distance(&other.semiaxis_y)?, - ) - } } impl Animatable for ShapeRadius @@ -365,24 +292,6 @@ where _ => Err(()), } } - - fn compute_distance(&self, other: &Self) -> Result { - match (self, other) { - (&ShapeRadius::Length(ref this), &ShapeRadius::Length(ref other)) => { - this.compute_distance(other) - }, - _ => Err(()), - } - } - - fn compute_squared_distance(&self, other: &Self) -> Result { - match (self, other) { - (&ShapeRadius::Length(ref this), &ShapeRadius::Length(ref other)) => { - this.compute_squared_distance(other) - }, - _ => Err(()), - } - } } impl Default for ShapeRadius { @@ -413,12 +322,13 @@ where }).collect::, _>>()?; Ok(Polygon { fill: self.fill, coordinates }) } +} - fn compute_distance(&self, other: &Self) -> Result { - Ok(self.compute_squared_distance(other)?.sqrt()) - } - - fn compute_squared_distance(&self, other: &Self) -> Result { +impl ComputeSquaredDistance for Polygon +where + L: ComputeSquaredDistance, +{ + fn compute_squared_distance(&self, other: &Self) -> Result { if self.fill != other.fill { return Err(()); } @@ -426,9 +336,10 @@ where return Err(()); } self.coordinates.iter().zip(other.coordinates.iter()).map(|(this, other)| { - let x = this.0.compute_squared_distance(&other.0)?; - let y = this.1.compute_squared_distance(&other.1)?; - Ok(x + y) + Ok( + this.0.compute_squared_distance(&other.0)? + + this.1.compute_squared_distance(&other.1)?, + ) }).sum() } } diff --git a/servo/components/style/values/generics/border.rs b/servo/components/style/values/generics/border.rs index e70504e7e87a..78faf1de050d 100644 --- a/servo/components/style/values/generics/border.rs +++ b/servo/components/style/values/generics/border.rs @@ -36,7 +36,7 @@ pub struct BorderImageSlice { /// /// https://drafts.csswg.org/css-backgrounds-3/#border-radius #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)] +#[derive(Clone, ComputeSquaredDistance, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)] pub struct BorderRadius { /// The top left radius. pub top_left: BorderCornerRadius, @@ -48,9 +48,9 @@ pub struct BorderRadius { pub bottom_left: BorderCornerRadius, } -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)] /// A generic value for `border-*-radius` longhand properties. +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, ComputeSquaredDistance, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)] pub struct BorderCornerRadius(pub Size2D); impl From for BorderImageSlice @@ -129,19 +129,6 @@ where let bl = self.bottom_left.add_weighted(&other.bottom_left, self_portion, other_portion)?; Ok(BorderRadius::new(tl, tr, br, bl)) } - - fn compute_distance(&self, other: &Self) -> Result { - Ok(self.compute_squared_distance(other)?.sqrt()) - } - - fn compute_squared_distance(&self, other: &Self) -> Result { - Ok( - self.top_left.compute_squared_distance(&other.top_left)? + - self.top_right.compute_squared_distance(&other.top_right)? + - self.bottom_right.compute_squared_distance(&other.bottom_right)? + - self.bottom_left.compute_squared_distance(&other.bottom_left)?, - ) - } } impl ToCss for BorderRadius @@ -189,16 +176,6 @@ where ) -> Result { Ok(BorderCornerRadius(self.0.add_weighted(&other.0, self_portion, other_portion)?)) } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.0.compute_distance(&other.0) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - self.0.compute_squared_distance(&other.0) - } } impl ToCss for BorderCornerRadius diff --git a/servo/components/style/values/generics/effects.rs b/servo/components/style/values/generics/effects.rs index 9105123f5ab0..cff8d87c0786 100644 --- a/servo/components/style/values/generics/effects.rs +++ b/servo/components/style/values/generics/effects.rs @@ -65,7 +65,7 @@ pub enum Filter { /// Contrary to the canonical order from the spec, the color is serialised /// first, like in Gecko and Webkit. #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToAnimatedValue, ToCss)] +#[derive(Clone, ComputeSquaredDistance, Debug, HasViewportPercentage, PartialEq, ToAnimatedValue, ToCss)] pub struct SimpleShadow { /// Color. pub color: Color, diff --git a/servo/components/style/values/generics/mod.rs b/servo/components/style/values/generics/mod.rs index 075ce2c4715a..b097b0f85de3 100644 --- a/servo/components/style/values/generics/mod.rs +++ b/servo/components/style/values/generics/mod.rs @@ -267,11 +267,13 @@ impl ToCss for FontSettingTagFloat { } /// A wrapper of Non-negative values. -#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, PartialOrd, ToComputedValue, ToCss)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))] +#[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))] +#[derive(Clone, ComputeSquaredDistance, Copy, Debug, HasViewportPercentage)] +#[derive(PartialEq, PartialOrd, ToComputedValue, ToCss)] pub struct NonNegative(pub T); /// A wrapper of greater-than-or-equal-to-one values. -#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, PartialOrd, ToComputedValue, ToCss)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))] +#[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))] +#[derive(Clone, ComputeSquaredDistance, Copy, Debug, HasViewportPercentage)] +#[derive(PartialEq, PartialOrd, ToComputedValue, ToCss)] pub struct GreaterThanOrEqualToOne(pub T); diff --git a/servo/components/style/values/generics/position.rs b/servo/components/style/values/generics/position.rs index 4ce5b63ab304..503cb9a15630 100644 --- a/servo/components/style/values/generics/position.rs +++ b/servo/components/style/values/generics/position.rs @@ -5,9 +5,9 @@ //! Generic types for CSS handling of specified and computed values of //! [`position`](https://drafts.csswg.org/css-backgrounds-3/#position) -#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] /// A generic type for representing a CSS [position](https://drafts.csswg.org/css-values/#position). +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, ComputeSquaredDistance, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)] pub struct Position { /// The horizontal component of position. pub horizontal: H, diff --git a/servo/components/style/values/generics/rect.rs b/servo/components/style/values/generics/rect.rs index 3dc9e095890d..89ce196267a2 100644 --- a/servo/components/style/values/generics/rect.rs +++ b/servo/components/style/values/generics/rect.rs @@ -12,7 +12,7 @@ use style_traits::{ToCss, ParseError}; /// A CSS value made of four components, where its `ToCss` impl will try to /// serialize as few components as possible, like for example in `border-width`. -#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)] +#[derive(Clone, ComputeSquaredDistance, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct Rect(pub T, pub T, pub T, pub T); diff --git a/servo/components/style/values/generics/svg.rs b/servo/components/style/values/generics/svg.rs index 05e0e66248fa..60f76950aee0 100644 --- a/servo/components/style/values/generics/svg.rs +++ b/servo/components/style/values/generics/svg.rs @@ -97,7 +97,8 @@ impl Parse for SVGPaint { /// ` | | ` Length(LengthType), @@ -107,7 +108,7 @@ pub enum SVGLength { /// Generic value for stroke-dasharray. #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Debug, PartialEq, HasViewportPercentage, ToAnimatedValue, ToComputedValue)] +#[derive(Clone, ComputeSquaredDistance, Debug, PartialEq, HasViewportPercentage, ToAnimatedValue, ToComputedValue)] pub enum SVGStrokeDashArray { /// `[ | | ]#` Values(Vec), @@ -141,7 +142,7 @@ impl ToCss for SVGStrokeDashArray where LengthType: ToCs /// An SVG opacity value accepts `context-{fill,stroke}-opacity` in /// addition to opacity value. #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Copy, Debug, PartialEq, HasViewportPercentage, ToComputedValue, ToCss)] +#[derive(Clone, ComputeSquaredDistance, Copy, Debug, PartialEq, HasViewportPercentage, ToComputedValue, ToCss)] pub enum SVGOpacity { /// `` Opacity(OpacityType), diff --git a/servo/components/style/values/generics/text.rs b/servo/components/style/values/generics/text.rs index 94be4a6db8cb..a3879602bdc9 100644 --- a/servo/components/style/values/generics/text.rs +++ b/servo/components/style/values/generics/text.rs @@ -10,6 +10,7 @@ use parser::ParserContext; use properties::animated_properties::Animatable; use style_traits::ParseError; use values::animated::ToAnimatedZero; +use values::distance::{ComputeSquaredDistance, SquaredDistance}; /// A generic value for the `initial-letter` property. #[cfg_attr(feature = "servo", derive(HeapSizeOf))] @@ -84,13 +85,18 @@ impl Animatable for Spacing let other = other.value().unwrap_or(&zero); this.add_weighted(other, self_portion, other_portion).map(Spacing::Value) } +} +impl ComputeSquaredDistance for Spacing +where + V: ComputeSquaredDistance + From, +{ #[inline] - fn compute_distance(&self, other: &Self) -> Result { - let zero = Value::from(Au(0)); + fn compute_squared_distance(&self, other: &Self) -> Result { + let zero = V::from(Au(0)); let this = self.value().unwrap_or(&zero); let other = other.value().unwrap_or(&zero); - this.compute_distance(other) + this.compute_squared_distance(other) } } @@ -104,7 +110,7 @@ where /// A generic value for the `line-height` property. #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToAnimatedValue, ToCss)] +#[derive(Clone, ComputeSquaredDistance, Copy, Debug, HasViewportPercentage, PartialEq, ToAnimatedValue, ToCss)] pub enum LineHeight { /// `normal` Normal, diff --git a/servo/components/style/values/generics/transform.rs b/servo/components/style/values/generics/transform.rs index e00104f0c43e..32f37caec12a 100644 --- a/servo/components/style/values/generics/transform.rs +++ b/servo/components/style/values/generics/transform.rs @@ -24,7 +24,7 @@ pub struct Matrix { /// A generic transform origin. #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue, ToCss)] +#[derive(Clone, ComputeSquaredDistance, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue, ToCss)] pub struct TransformOrigin { /// The horizontal origin. pub horizontal: H, diff --git a/servo/components/style/values/mod.rs b/servo/components/style/values/mod.rs index 616aa5cb4852..f03ab188a7a5 100644 --- a/servo/components/style/values/mod.rs +++ b/servo/components/style/values/mod.rs @@ -19,6 +19,7 @@ use style_traits::{ToCss, ParseError, StyleParseError}; pub mod animated; pub mod computed; +pub mod distance; pub mod generics; pub mod specified; @@ -51,7 +52,8 @@ impl Parse for Impossible { /// A struct representing one of two kinds of values. #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Copy, HasViewportPercentage, PartialEq, ToAnimatedValue, ToComputedValue, ToCss)] +#[derive(Clone, ComputeSquaredDistance, Copy, HasViewportPercentage, PartialEq)] +#[derive(ToAnimatedValue, ToComputedValue, ToCss)] pub enum Either { /// The first value. First(A), diff --git a/servo/components/style_derive/compute_squared_distance.rs b/servo/components/style_derive/compute_squared_distance.rs new file mode 100644 index 000000000000..46aa376319d9 --- /dev/null +++ b/servo/components/style_derive/compute_squared_distance.rs @@ -0,0 +1,117 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use quote; +use std::borrow::Cow; +use syn; +use synstructure; + +pub fn derive(input: syn::DeriveInput) -> quote::Tokens { + let name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let mut where_clause = where_clause.clone(); + for param in &input.generics.ty_params { + where_clause.predicates.push(where_predicate(syn::Ty::Path(None, param.ident.clone().into()))) + } + + let variants = variants(&input); + let mut match_body = quote!(); + match_body.append_all(variants.iter().map(|variant| { + let name = match input.body { + syn::Body::Struct(_) => Cow::Borrowed(&input.ident), + syn::Body::Enum(_) => { + Cow::Owned(syn::Ident::from(format!("{}::{}", input.ident, variant.ident))) + }, + }; + let (this_pattern, this_info) = synstructure::match_pattern( + &name, + &variant.data, + &synstructure::BindOpts::with_prefix( + synstructure::BindStyle::Ref, + "this".to_owned(), + ), + ); + let (other_pattern, other_info) = synstructure::match_pattern( + &name, + &variant.data, + &synstructure::BindOpts::with_prefix( + synstructure::BindStyle::Ref, + "other".to_owned(), + ), + ); + let sum = if this_info.is_empty() { + quote! { ::values::distance::SquaredDistance::Value(0.) } + } else { + let mut sum = quote!(); + sum.append_separated(this_info.iter().zip(&other_info).map(|(this, other)| { + where_clause.predicates.push(where_predicate(this.field.ty.clone())); + quote! { + ::values::distance::ComputeSquaredDistance::compute_squared_distance(#this, #other)? + } + }), "+"); + sum + }; + quote! { + (&#this_pattern, &#other_pattern) => { + Ok(#sum) + } + } + })); + + if variants.len() > 1 { + match_body = quote! { #match_body, _ => Err(()), }; + } + + quote! { + impl #impl_generics ::values::distance::ComputeSquaredDistance for #name #ty_generics #where_clause { + #[allow(unused_variables, unused_imports)] + #[inline] + fn compute_squared_distance( + &self, + other: &Self, + ) -> Result<::values::distance::SquaredDistance, ()> { + match (self, other) { + #match_body + } + } + } + } +} + +fn variants(input: &syn::DeriveInput) -> Cow<[syn::Variant]> { + match input.body { + syn::Body::Enum(ref variants) => (&**variants).into(), + syn::Body::Struct(ref data) => { + vec![syn::Variant { + ident: input.ident.clone(), + attrs: input.attrs.clone(), + data: data.clone(), + discriminant: None, + }].into() + }, + } +} + +fn where_predicate(ty: syn::Ty) -> syn::WherePredicate { + syn::WherePredicate::BoundPredicate( + syn::WhereBoundPredicate { + bound_lifetimes: vec![], + bounded_ty: ty, + bounds: vec![syn::TyParamBound::Trait( + syn::PolyTraitRef { + bound_lifetimes: vec![], + trait_ref: syn::Path { + global: true, + segments: vec![ + "values".into(), + "distance".into(), + "ComputeSquaredDistance".into(), + ], + }, + }, + syn::TraitBoundModifier::None, + )], + }, + ) +} diff --git a/servo/components/style_derive/lib.rs b/servo/components/style_derive/lib.rs index 683d6c2c5636..13a855eb6bab 100644 --- a/servo/components/style_derive/lib.rs +++ b/servo/components/style_derive/lib.rs @@ -9,11 +9,18 @@ extern crate synstructure; use proc_macro::TokenStream; +mod compute_squared_distance; mod has_viewport_percentage; mod to_animated_value; mod to_computed_value; mod to_css; +#[proc_macro_derive(ComputeSquaredDistance)] +pub fn derive_compute_squared_distance(stream: TokenStream) -> TokenStream { + let input = syn::parse_derive_input(&stream.to_string()).unwrap(); + compute_squared_distance::derive(input).to_string().parse().unwrap() +} + #[proc_macro_derive(HasViewportPercentage)] pub fn derive_has_viewport_percentage(stream: TokenStream) -> TokenStream { let input = syn::parse_derive_input(&stream.to_string()).unwrap(); diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs index b8ab946de88b..ec1f610403e8 100644 --- a/servo/ports/geckolib/glue.rs +++ b/servo/ports/geckolib/glue.rs @@ -115,10 +115,8 @@ use style::string_cache::Atom; use style::style_adjuster::StyleAdjuster; use style::stylesheets::{CssRule, CssRules, CssRuleType, CssRulesHelpers, DocumentRule}; use style::stylesheets::{FontFeatureValuesRule, ImportRule, KeyframesRule, MallocSizeOfWithGuard}; -use style::stylesheets::{MallocSizeOfWithRepeats, MediaRule}; -use style::stylesheets::{NamespaceRule, Origin, PageRule, SizeOfState, StyleRule, SupportsRule}; -use style::stylesheets::StylesheetContents; -use style::stylesheets::StylesheetInDocument; +use style::stylesheets::{MediaRule, NamespaceRule, Origin, PageRule, SizeOfState, StyleRule}; +use style::stylesheets::{StylesheetContents, StylesheetInDocument, SupportsRule}; use style::stylesheets::StylesheetLoader as StyleStylesheetLoader; use style::stylesheets::keyframes_rule::{Keyframe, KeyframeSelector, KeyframesStepValue}; use style::stylesheets::supports_rule::parse_condition_or_declaration; @@ -131,6 +129,7 @@ use style::traversal_flags::{TraversalFlags, self}; use style::values::{CustomIdent, KeyframesName}; use style::values::animated::ToAnimatedZero; use style::values::computed::Context; +use style::values::distance::ComputeSquaredDistance; use style_traits::{PARSING_MODE_DEFAULT, ToCss}; use super::error_reporter::ErrorReporter; use super::stylesheet_loader::StylesheetLoader; @@ -380,7 +379,7 @@ pub extern "C" fn Servo_AnimationValues_ComputeDistance(from: RawServoAnimationV -> f64 { let from_value = AnimationValue::as_arc(&from); let to_value = AnimationValue::as_arc(&to); - from_value.compute_distance(to_value).unwrap_or(0.0) + from_value.compute_squared_distance(to_value).map(|d| d.sqrt()).unwrap_or(0.0) } #[no_mangle] @@ -758,9 +757,9 @@ pub extern "C" fn Servo_Element_ClearData(element: RawGeckoElementBorrowed) { } #[no_mangle] -pub extern "C" fn Servo_Element_SizeOfExcludingThis(malloc_size_of: MallocSizeOf, - seen_ptrs: *mut SeenPtrs, - element: RawGeckoElementBorrowed) -> usize { +pub extern "C" fn Servo_Element_SizeOfExcludingThisAndCVs(malloc_size_of: MallocSizeOf, + seen_ptrs: *mut SeenPtrs, + element: RawGeckoElementBorrowed) -> usize { let malloc_size_of = malloc_size_of.unwrap(); let element = GeckoElement(element); let borrow = element.borrow_data(); @@ -769,12 +768,49 @@ pub extern "C" fn Servo_Element_SizeOfExcludingThis(malloc_size_of: MallocSizeOf malloc_size_of: malloc_size_of, seen_ptrs: seen_ptrs, }; - (*data).malloc_size_of_children(&mut state) + (*data).malloc_size_of_children_excluding_cvs(&mut state) } else { 0 } } +#[no_mangle] +pub extern "C" fn Servo_Element_HasPrimaryComputedValues(element: RawGeckoElementBorrowed) -> bool +{ + let element = GeckoElement(element); + let data = element.borrow_data().expect("Looking for CVs on unstyled element"); + data.has_styles() +} + +#[no_mangle] +pub extern "C" fn Servo_Element_GetPrimaryComputedValues(element: RawGeckoElementBorrowed) + -> ServoStyleContextStrong +{ + let element = GeckoElement(element); + let data = element.borrow_data().expect("Getting CVs on unstyled element"); + assert!(data.has_styles(), "Getting CVs on unstyled element"); + data.styles.primary().clone().into() +} + +#[no_mangle] +pub extern "C" fn Servo_Element_HasPseudoComputedValues(element: RawGeckoElementBorrowed, + index: usize) -> bool +{ + let element = GeckoElement(element); + let data = element.borrow_data().expect("Looking for CVs on unstyled element"); + data.styles.pseudos.as_array()[index].is_some() +} + +#[no_mangle] +pub extern "C" fn Servo_Element_GetPseudoComputedValues(element: RawGeckoElementBorrowed, + index: usize) -> ServoStyleContextStrong +{ + let element = GeckoElement(element); + let data = element.borrow_data().expect("Getting CVs that aren't present"); + data.styles.pseudos.as_array()[index].as_ref().expect("Getting CVs that aren't present") + .clone().into() +} + #[no_mangle] pub extern "C" fn Servo_StyleSheet_Empty(mode: SheetParsingMode) -> RawServoStyleSheetContentsStrong { let global_style_data = &*GLOBAL_STYLE_DATA; diff --git a/testing/web-platform/meta/MANIFEST.json b/testing/web-platform/meta/MANIFEST.json index 3f309b8cf112..ff47478db8c9 100644 --- a/testing/web-platform/meta/MANIFEST.json +++ b/testing/web-platform/meta/MANIFEST.json @@ -330453,6 +330453,12 @@ {} ] ], + "css/css-fonts-3/test_datafont_same_origin.html": [ + [ + "/css/css-fonts-3/test_datafont_same_origin.html", + {} + ] + ], "css/css-fonts-3/test_font_family_parsing.html": [ [ "/css/css-fonts-3/test_font_family_parsing.html", @@ -510454,6 +510460,10 @@ "2a8f7afbb6ccfc4084534c36bd4cfeebcbae4194", "manual" ], + "css/css-fonts-3/test_datafont_same_origin.html": [ + "a793f06cceb92b34dc27728307995774a5d95b63", + "testharness" + ], "css/css-fonts-3/test_font_family_parsing.html": [ "fa926a9e86823cddea6d9f1418c752cf04e341ad", "testharness" @@ -573043,7 +573053,7 @@ "support" ], "cssom/stylesheet-same-origin.sub.html": [ - "719c525b1af3b6b46dfeeb0627034d799bab50b5", + "c8112887da9ee152a9ebe64988fd29cb2c27b9ba", "testharness" ], "cssom/support/1x1-green.png": [ @@ -574339,11 +574349,11 @@ "support" ], "dom/nodes/Document-createEvent.html": [ - "9274ffffe1b08dee78b64ffc70582957f0386bca", + "9d128acd63f3e972334d9a034e0c222495fd927e", "testharness" ], "dom/nodes/Document-createEvent.js": [ - "6e7d9350e2fd9ffad36fddd4d90438d620c7dbf3", + "5a8ecc8e5627e340eb10fdc91675968dc9b8fb98", "support" ], "dom/nodes/Document-createProcessingInstruction-xhtml.xhtml": [ @@ -579755,7 +579765,7 @@ "testharness" ], "html/browsers/browsing-the-web/history-traversal/PopStateEvent.html": [ - "2a7ed0827fc61af7b3bdd238577887aff1902ea7", + "5a9c575a86adbbbca30734992b4d80c22f3973a1", "testharness" ], "html/browsers/browsing-the-web/history-traversal/browsing_context_name-0.html": [ @@ -596407,7 +596417,7 @@ "testharness" ], "intersection-observer/timestamp.html": [ - "a7084b2a0f359115a1862f3c0891ace8143dba83", + "b9bf8d472d7751ec4a1ebee925d12668bedeee7a", "testharness" ], "intersection-observer/unclipped-root.html": [ @@ -625023,7 +625033,7 @@ "wdspec" ], "webdriver/tests/actions/mouse.py": [ - "51ba095d3d754e30154c20b2910830c6d3e3410c", + "d7af66e45c8e306b4258b92c54ed86073fd21c8d", "wdspec" ], "webdriver/tests/actions/sequence.py": [ @@ -625047,7 +625057,7 @@ "support" ], "webdriver/tests/actions/support/test_actions_wdspec.html": [ - "a5a1cbce9f0ef66c19065cb8ba761c8fd27cc4ac", + "94b6f474bb4417cf2c06cb5fc6042c01bde98aa2", "support" ], "webdriver/tests/conftest.py": [ @@ -639415,7 +639425,7 @@ "testharness" ], "workers/Worker_dispatchEvent_ErrorEvent.htm": [ - "a1100df5a79ed7b484a8d5c5746bd646a165242b", + "a27efcba6fcdbb34bb07ac8553a6bbfa04761008", "testharness" ], "workers/Worker_script_mimetype.htm": [ diff --git a/testing/web-platform/meta/quirks-mode/unitless-length.html.ini b/testing/web-platform/meta/quirks-mode/unitless-length.html.ini index 557d5bd269f8..21852a246582 100644 --- a/testing/web-platform/meta/quirks-mode/unitless-length.html.ini +++ b/testing/web-platform/meta/quirks-mode/unitless-length.html.ini @@ -1,8 +1,5 @@ [unitless-length.html] type: testharness - expected: - if debug and stylo and (os == "linux"): TIMEOUT - if debug and stylo and (os == "mac"): TIMEOUT [calc(2 * 2px) (quirks)] expected: if stylo: PASS diff --git a/testing/web-platform/tests/css/css-fonts-3/test_datafont_same_origin.html b/testing/web-platform/tests/css/css-fonts-3/test_datafont_same_origin.html new file mode 100644 index 000000000000..937c8c38e00d --- /dev/null +++ b/testing/web-platform/tests/css/css-fonts-3/test_datafont_same_origin.html @@ -0,0 +1,43 @@ + + + + + data:font same-origin test + + + + + + + + +
+

+
+
+
+
+
diff --git a/testing/web-platform/tests/cssom/stylesheet-same-origin.sub.html b/testing/web-platform/tests/cssom/stylesheet-same-origin.sub.html
index b259e4369ddc..9df0a54b7d85 100644
--- a/testing/web-platform/tests/cssom/stylesheet-same-origin.sub.html
+++ b/testing/web-platform/tests/cssom/stylesheet-same-origin.sub.html
@@ -8,10 +8,12 @@
 
     
     
+    
 
     
 
 
diff --git a/toolkit/components/contextualidentity/ContextualIdentityService.jsm b/toolkit/components/contextualidentity/ContextualIdentityService.jsm
index 8fa2e39f1a33..e73896e9494b 100644
--- a/toolkit/components/contextualidentity/ContextualIdentityService.jsm
+++ b/toolkit/components/contextualidentity/ContextualIdentityService.jsm
@@ -216,6 +216,8 @@ _ContextualIdentityService.prototype = {
 
     this._identities.push(identity);
     this.saveSoon();
+    Services.obs.notifyObservers(this.getIdentityObserverOutput(identity),
+                                 "contextual-identity-created");
 
     return Cu.cloneInto(identity, {});
   },
@@ -232,8 +234,9 @@ _ContextualIdentityService.prototype = {
       delete identity.l10nID;
       delete identity.accessKey;
 
-      Services.obs.notifyObservers(null, "contextual-identity-updated", userContextId);
       this.saveSoon();
+      Services.obs.notifyObservers(this.getIdentityObserverOutput(identity),
+                                   "contextual-identity-updated");
     }
 
     return !!identity;
@@ -250,13 +253,26 @@ _ContextualIdentityService.prototype = {
     Services.obs.notifyObservers(null, "clear-origin-attributes-data",
                                  JSON.stringify({ userContextId }));
 
+    let deletedOutput = this.getIdentityObserverOutput(this.getPublicIdentityFromId(userContextId));
     this._identities.splice(index, 1);
     this._openedIdentities.delete(userContextId);
     this.saveSoon();
+    Services.obs.notifyObservers(deletedOutput, "contextual-identity-deleted");
 
     return true;
   },
 
+  getIdentityObserverOutput(identity) {
+    let wrappedJSObject = {
+      name: this.getUserContextLabel(identity.userContextId),
+      icon: identity.icon,
+      color: identity.color,
+      userContextId: identity.userContextId,
+    };
+
+    return {wrappedJSObject};
+  },
+
   ensureDataReady() {
     if (this._dataReady) {
       return;
diff --git a/toolkit/components/contextualidentity/tests/unit/test_basic.js b/toolkit/components/contextualidentity/tests/unit/test_basic.js
index 209b662f6a04..7baa98157713 100644
--- a/toolkit/components/contextualidentity/tests/unit/test_basic.js
+++ b/toolkit/components/contextualidentity/tests/unit/test_basic.js
@@ -1,12 +1,13 @@
 "use strict";
 
-do_get_profile();
+const profileDir = do_get_profile();
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/ContextualIdentityService.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
 
-const TEST_STORE_FILE_NAME = "test-containers.json";
+const TEST_STORE_FILE_PATH = OS.Path.join(profileDir.path, "test-containers.json");
 
 let cis;
 
@@ -14,7 +15,7 @@ let cis;
 add_task(function() {
   ok(!!ContextualIdentityService, "ContextualIdentityService exists");
 
-  cis = ContextualIdentityService.createNewInstanceForTesting(TEST_STORE_FILE_NAME);
+  cis = ContextualIdentityService.createNewInstanceForTesting(TEST_STORE_FILE_PATH);
   ok(!!cis, "We have our instance of ContextualIdentityService");
 
   equal(cis.getPublicIdentities().length, 4, "By default, 4 containers.");
diff --git a/toolkit/components/contextualidentity/tests/unit/xpcshell.ini b/toolkit/components/contextualidentity/tests/unit/xpcshell.ini
index b45ff2c30f29..a635199538c4 100644
--- a/toolkit/components/contextualidentity/tests/unit/xpcshell.ini
+++ b/toolkit/components/contextualidentity/tests/unit/xpcshell.ini
@@ -1,3 +1,4 @@
 [DEFAULT]
+firefox-appdir = browser
 
 [test_basic.js]
diff --git a/toolkit/components/crashes/CrashService.js b/toolkit/components/crashes/CrashService.js
index c9e6bf72739d..a1f207dfeb73 100644
--- a/toolkit/components/crashes/CrashService.js
+++ b/toolkit/components/crashes/CrashService.js
@@ -24,34 +24,38 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
  */
 function runMinidumpAnalyzer(minidumpPath) {
   return new Promise((resolve, reject) => {
-    const binSuffix = AppConstants.platform === "win" ? ".exe" : "";
-    const exeName = "minidump-analyzer" + binSuffix;
+    try {
+      const binSuffix = AppConstants.platform === "win" ? ".exe" : "";
+      const exeName = "minidump-analyzer" + binSuffix;
 
-    let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile);
+      let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile);
 
-    if (AppConstants.platform === "macosx") {
-        exe.append("crashreporter.app");
-        exe.append("Contents");
-        exe.append("MacOS");
-    }
-
-    exe.append(exeName);
-
-    let args = [ minidumpPath ];
-    let process = Cc["@mozilla.org/process/util;1"]
-                    .createInstance(Ci.nsIProcess);
-    process.init(exe);
-    process.startHidden = true;
-    process.runAsync(args, args.length, (subject, topic, data) => {
-      switch (topic) {
-        case "process-finished":
-          resolve();
-          break;
-        default:
-          reject(topic);
-          break;
+      if (AppConstants.platform === "macosx") {
+          exe.append("crashreporter.app");
+          exe.append("Contents");
+          exe.append("MacOS");
       }
-    });
+
+      exe.append(exeName);
+
+      let args = [ minidumpPath ];
+      let process = Cc["@mozilla.org/process/util;1"]
+                      .createInstance(Ci.nsIProcess);
+      process.init(exe);
+      process.startHidden = true;
+      process.runAsync(args, args.length, (subject, topic, data) => {
+        switch (topic) {
+          case "process-finished":
+            resolve();
+            break;
+          default:
+            reject(new Error("Unexpected topic received " + topic));
+            break;
+        }
+      });
+    } catch (e) {
+      Cu.reportError(e);
+    }
   });
 }
 
@@ -65,21 +69,26 @@ function runMinidumpAnalyzer(minidumpPath) {
  */
 function computeMinidumpHash(minidumpPath) {
   return (async function() {
-    let minidumpData = await OS.File.read(minidumpPath);
-    let hasher = Cc["@mozilla.org/security/hash;1"]
-                   .createInstance(Ci.nsICryptoHash);
-    hasher.init(hasher.SHA256);
-    hasher.update(minidumpData, minidumpData.length);
+    try {
+      let minidumpData = await OS.File.read(minidumpPath);
+      let hasher = Cc["@mozilla.org/security/hash;1"]
+                     .createInstance(Ci.nsICryptoHash);
+      hasher.init(hasher.SHA256);
+      hasher.update(minidumpData, minidumpData.length);
 
-    let hashBin = hasher.finish(false);
-    let hash = "";
+      let hashBin = hasher.finish(false);
+      let hash = "";
 
-    for (let i = 0; i < hashBin.length; i++) {
-      // Every character in the hash string contains a byte of the hash data
-      hash += ("0" + hashBin.charCodeAt(i).toString(16)).slice(-2);
+      for (let i = 0; i < hashBin.length; i++) {
+        // Every character in the hash string contains a byte of the hash data
+        hash += ("0" + hashBin.charCodeAt(i).toString(16)).slice(-2);
+      }
+
+      return hash;
+    } catch (e) {
+      Cu.reportError(e);
+      return null;
     }
-
-    return hash;
   })();
 }
 
@@ -94,10 +103,15 @@ function computeMinidumpHash(minidumpPath) {
  */
 function processExtraFile(extraPath) {
   return (async function() {
-    let decoder = new TextDecoder();
-    let extraData = await OS.File.read(extraPath);
+    try {
+      let decoder = new TextDecoder();
+      let extraData = await OS.File.read(extraPath);
 
-    return parseKeyValuePairs(decoder.decode(extraData));
+      return parseKeyValuePairs(decoder.decode(extraData));
+    } catch (e) {
+      Cu.reportError(e);
+      return {};
+    }
   })();
 }
 
@@ -115,7 +129,7 @@ CrashService.prototype = Object.freeze({
     Ci.nsIObserver,
   ]),
 
-  addCrash(processType, crashType, id) {
+  async addCrash(processType, crashType, id) {
     switch (processType) {
     case Ci.nsICrashService.PROCESS_TYPE_MAIN:
       processType = Services.crashmanager.PROCESS_TYPE_MAIN;
@@ -147,30 +161,23 @@ CrashService.prototype = Object.freeze({
       throw new Error("Unrecognized CRASH_TYPE: " + crashType);
     }
 
-    let blocker = (async function() {
-      let metadata = {};
-      let hash = null;
+    let cr = Cc["@mozilla.org/toolkit/crash-reporter;1"]
+               .getService(Components.interfaces.nsICrashReporter);
+    let minidumpPath = cr.getMinidumpForID(id).path;
+    let extraPath = cr.getExtraFileForID(id).path;
+    let metadata = {};
+    let hash = null;
 
-      try {
-        let cr = Cc["@mozilla.org/toolkit/crash-reporter;1"]
-                   .getService(Components.interfaces.nsICrashReporter);
-        let minidumpPath = cr.getMinidumpForID(id).path;
-        let extraPath = cr.getExtraFileForID(id).path;
+    await runMinidumpAnalyzer(minidumpPath);
+    metadata = await processExtraFile(extraPath);
+    hash = await computeMinidumpHash(minidumpPath);
 
-        await runMinidumpAnalyzer(minidumpPath);
-        metadata = await processExtraFile(extraPath);
-        hash = await computeMinidumpHash(minidumpPath);
-      } catch (e) {
-        Cu.reportError(e);
-      }
+    if (hash) {
+      metadata.MinidumpSha256Hash = hash;
+    }
 
-      if (hash) {
-        metadata.MinidumpSha256Hash = hash;
-      }
-
-      await Services.crashmanager.addCrash(processType, crashType, id,
-                                           new Date(), metadata);
-    })();
+    let blocker = Services.crashmanager.addCrash(processType, crashType, id,
+                                                 new Date(), metadata);
 
     AsyncShutdown.profileBeforeChange.addBlocker(
       "CrashService waiting for content crash ping to be sent", blocker
@@ -178,7 +185,7 @@ CrashService.prototype = Object.freeze({
 
     blocker.then(AsyncShutdown.profileBeforeChange.removeBlocker(blocker));
 
-    return blocker;
+    await blocker;
   },
 
   observe(subject, topic, data) {
diff --git a/toolkit/components/extensions/ext-contextualIdentities.js b/toolkit/components/extensions/ext-contextualIdentities.js
index a8db6944e4f6..b3e6b86beb60 100644
--- a/toolkit/components/extensions/ext-contextualIdentities.js
+++ b/toolkit/components/extensions/ext-contextualIdentities.js
@@ -19,6 +19,18 @@ const convertIdentity = identity => {
   return result;
 };
 
+const convertIdentityFromObserver = wrappedIdentity => {
+  let identity = wrappedIdentity.wrappedJSObject;
+  let result = {
+    name: identity.name,
+    icon: identity.icon,
+    color: identity.color,
+    cookieStoreId: getCookieStoreIdForContainer(identity.userContextId),
+  };
+
+  return result;
+};
+
 this.contextualIdentities = class extends ExtensionAPI {
   getAPI(context) {
     let self = {
@@ -126,6 +138,40 @@ this.contextualIdentities = class extends ExtensionAPI {
 
           return Promise.resolve(convertedIdentity);
         },
+
+        onCreated: new EventManager(context, "contextualIdentities.onCreated", fire => {
+          let observer = (subject, topic) => {
+            fire.async({contextualIdentity: convertIdentityFromObserver(subject)});
+          };
+
+          Services.obs.addObserver(observer, "contextual-identity-created");
+          return () => {
+            Services.obs.removeObserver(observer, "contextual-identity-created");
+          };
+        }).api(),
+
+        onUpdated: new EventManager(context, "contextualIdentities.onUpdated", fire => {
+          let observer = (subject, topic) => {
+            fire.async({contextualIdentity: convertIdentityFromObserver(subject)});
+          };
+
+          Services.obs.addObserver(observer, "contextual-identity-updated");
+          return () => {
+            Services.obs.removeObserver(observer, "contextual-identity-updated");
+          };
+        }).api(),
+
+        onRemoved: new EventManager(context, "contextualIdentities.onRemoved", fire => {
+          let observer = (subject, topic) => {
+            fire.async({contextualIdentity: convertIdentityFromObserver(subject)});
+          };
+
+          Services.obs.addObserver(observer, "contextual-identity-deleted");
+          return () => {
+            Services.obs.removeObserver(observer, "contextual-identity-deleted");
+          };
+        }).api(),
+
       },
     };
 
diff --git a/toolkit/components/extensions/schemas/contextual_identities.json b/toolkit/components/extensions/schemas/contextual_identities.json
index fb6399467245..46ac90f7d8fe 100644
--- a/toolkit/components/extensions/schemas/contextual_identities.json
+++ b/toolkit/components/extensions/schemas/contextual_identities.json
@@ -118,6 +118,50 @@
           }
         ]
       }
+    ],
+    "events": [
+      {
+        "name": "onUpdated",
+        "type": "function",
+        "description": "Fired when a container is updated.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "changeInfo",
+            "properties": {
+              "contextualIdentity": {"$ref": "ContextualIdentity", "description": "Contextual identity that has been updated"}
+            }
+          }
+        ]
+      },
+      {
+        "name": "onCreated",
+        "type": "function",
+        "description": "Fired when a new container is created.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "changeInfo",
+            "properties": {
+              "contextualIdentity": {"$ref": "ContextualIdentity", "description": "Contextual identity that has been created"}
+            }
+          }
+        ]
+      },
+      {
+        "name": "onRemoved",
+        "type": "function",
+        "description": "Fired when a container is removed.",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "changeInfo",
+            "properties": {
+              "contextualIdentity": {"$ref": "ContextualIdentity", "description": "Contextual identity that has been removed"}
+            }
+          }
+        ]
+      }
     ]
   }
 ]
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js b/toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
index 8eb46213fa73..141012babb59 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
@@ -61,6 +61,73 @@ add_task(async function test_contextualIdentity_no_containers() {
   Services.prefs.clearUserPref("privacy.userContext.enabled");
 });
 
+add_task(async function test_contextualIdentity_events() {
+  async function backgroundScript() {
+    function createOneTimeListener(type) {
+      return new Promise((resolve, reject) => {
+        try {
+          browser.test.assertTrue(type in browser.contextualIdentities, `Found API object browser.contextualIdentities.${type}`);
+          const listener = (change) => {
+            browser.test.assertTrue("contextualIdentity" in change, `Found identity in change`);
+            browser.contextualIdentities[type].removeListener(listener);
+            resolve(change);
+          };
+          browser.contextualIdentities[type].addListener(listener);
+        } catch (e) {
+          reject(e);
+        }
+      });
+    }
+
+    function assertExpected(expected, container) {
+      for (let key of Object.keys(container)) {
+        browser.test.assertTrue(key in expected, `found property ${key}`);
+        browser.test.assertEq(expected[key], container[key], `property value for ${key} is correct`);
+      }
+      browser.test.assertEq(Object.keys(expected).length, Object.keys(container).length, "all expected properties found");
+    }
+
+    let onCreatePromise = createOneTimeListener("onCreated");
+
+    let containerObj = {name: "foobar", color: "red", icon: "icon"};
+    let ci = await browser.contextualIdentities.create(containerObj);
+    browser.test.assertTrue(!!ci, "We have an identity");
+    const onCreateListenerResponse = await onCreatePromise;
+    const cookieStoreId = ci.cookieStoreId;
+    assertExpected(onCreateListenerResponse.contextualIdentity, Object.assign(containerObj, {cookieStoreId}));
+
+    let onUpdatedPromise = createOneTimeListener("onUpdated");
+    let updateContainerObj = {name: "testing", color: "blue", icon: "thing"};
+    ci = await browser.contextualIdentities.update(cookieStoreId, updateContainerObj);
+    browser.test.assertTrue(!!ci, "We have an update identity");
+    const onUpdatedListenerResponse = await onUpdatedPromise;
+    assertExpected(onUpdatedListenerResponse.contextualIdentity, Object.assign(updateContainerObj, {cookieStoreId}));
+
+    let onRemovePromise = createOneTimeListener("onRemoved");
+    ci = await browser.contextualIdentities.remove(updateContainerObj.cookieStoreId);
+    browser.test.assertTrue(!!ci, "We have an remove identity");
+    const onRemoveListenerResponse = await onRemovePromise;
+    assertExpected(onRemoveListenerResponse.contextualIdentity, Object.assign(updateContainerObj, {cookieStoreId}));
+
+    browser.test.notifyPass("contextualIdentities_events");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background: `(${backgroundScript})()`,
+    manifest: {
+      permissions: ["contextualIdentities"],
+    },
+  });
+
+  Services.prefs.setBoolPref("privacy.userContext.enabled", true);
+
+  await extension.startup();
+  await extension.awaitFinish("contextualIdentities_events");
+  await extension.unload();
+
+  Services.prefs.clearUserPref("privacy.userContext.enabled");
+});
+
 add_task(async function test_contextualIdentity_with_permissions() {
   async function backgroundScript() {
     let ci = await browser.contextualIdentities.get("foobar");