зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-inbound to mozilla-central. a=merge
This commit is contained in:
Коммит
55006aaaec
|
@ -63,11 +63,18 @@
|
|||
padding-inline-start: 30px;
|
||||
}
|
||||
|
||||
#prefs > tr.deleted > td.cell-name {
|
||||
font-weight: bold;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.cell-value {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
td.cell-value > form > input {
|
||||
td.cell-value > form > input[type="text"],
|
||||
td.cell-value > form > input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
</div>
|
||||
|
||||
<div class="button-container">
|
||||
<button class="primary" onclick="alterWarningState(); loadPrefs();"
|
||||
<button class="primary" onclick="onWarningButtonClick();"
|
||||
data-l10n-id="about-config-warning-button"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,30 +2,64 @@
|
|||
* 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/. */
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/DeferredTask.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Preferences.jsm");
|
||||
|
||||
const SEARCH_TIMEOUT_MS = 500;
|
||||
|
||||
let gDefaultBranch = Services.prefs.getDefaultBranch("");
|
||||
let gPrefArray;
|
||||
let gFilterPrefsTask = new DeferredTask(() => filterPrefs(), SEARCH_TIMEOUT_MS);
|
||||
|
||||
/**
|
||||
* Maps the name of each preference in the back-end to its PrefRow object,
|
||||
* separating the preferences that actually exist. This is as an optimization to
|
||||
* avoid querying the preferences service each time the list is filtered.
|
||||
*/
|
||||
let gExistingPrefs = new Map();
|
||||
let gDeletedPrefs = new Map();
|
||||
|
||||
/**
|
||||
* Maps each row element currently in the table to its PrefRow object.
|
||||
*/
|
||||
let gElementToPrefMap = new WeakMap();
|
||||
|
||||
/**
|
||||
* Reference to the PrefRow currently being edited, if any.
|
||||
*/
|
||||
let gPrefInEdit = null;
|
||||
|
||||
/**
|
||||
* Lowercase substring that should be contained in the preference name.
|
||||
*/
|
||||
let gFilterString = null;
|
||||
|
||||
class PrefRow {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this.value = true;
|
||||
this.refreshValue();
|
||||
|
||||
this.editing = false;
|
||||
this.element = document.createElement("tr");
|
||||
this.element.setAttribute("aria-label", this.name);
|
||||
this._setupElement();
|
||||
gElementToPrefMap.set(this.element, this);
|
||||
}
|
||||
|
||||
refreshValue() {
|
||||
this.hasDefaultValue = prefHasDefaultValue(this.name);
|
||||
this.hasUserValue = Services.prefs.prefHasUserValue(this.name);
|
||||
this.hasDefaultValue = this.hasUserValue ? prefHasDefaultValue(this.name)
|
||||
: true;
|
||||
this.isLocked = Services.prefs.prefIsLocked(this.name);
|
||||
|
||||
// If this preference has been deleted, we keep its last known value.
|
||||
if (!this.exists) {
|
||||
gExistingPrefs.delete(this.name);
|
||||
gDeletedPrefs.set(this.name, this);
|
||||
return;
|
||||
}
|
||||
gExistingPrefs.set(this.name, this);
|
||||
gDeletedPrefs.delete(this.name);
|
||||
|
||||
try {
|
||||
// This can throw for locked preferences without a default value.
|
||||
this.value = Preferences.get(this.name);
|
||||
|
@ -42,6 +76,18 @@ class PrefRow {
|
|||
}
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this.value.constructor.name;
|
||||
}
|
||||
|
||||
get exists() {
|
||||
return this.hasDefaultValue || this.hasUserValue;
|
||||
}
|
||||
|
||||
get matchesFilter() {
|
||||
return !gFilterString || this.name.toLowerCase().includes(gFilterString);
|
||||
}
|
||||
|
||||
_setupElement() {
|
||||
this.element.textContent = "";
|
||||
let nameCell = document.createElement("td");
|
||||
|
@ -73,9 +119,15 @@ class PrefRow {
|
|||
refreshElement() {
|
||||
this.element.classList.toggle("has-user-value", !!this.hasUserValue);
|
||||
this.element.classList.toggle("locked", !!this.isLocked);
|
||||
if (!this.editing) {
|
||||
this.valueCell.textContent = this.value;
|
||||
if (this.value.constructor.name == "Boolean") {
|
||||
this.element.classList.toggle("deleted", !this.exists);
|
||||
if (this.exists && !this.editing) {
|
||||
// We need to place the text inside a "span" element to ensure that the
|
||||
// text copied to the clipboard includes all whitespace.
|
||||
let span = document.createElement("span");
|
||||
span.textContent = this.value;
|
||||
this.valueCell.textContent = "";
|
||||
this.valueCell.append(span);
|
||||
if (this.type == "Boolean") {
|
||||
document.l10n.setAttributes(this.editButton, "about-config-pref-toggle");
|
||||
this.editButton.className = "button-toggle";
|
||||
} else {
|
||||
|
@ -91,20 +143,52 @@ class PrefRow {
|
|||
let form = document.createElement("form");
|
||||
form.addEventListener("submit", event => event.preventDefault());
|
||||
form.id = "form-edit";
|
||||
this.inputField = document.createElement("input");
|
||||
this.inputField.value = this.value;
|
||||
if (this.value.constructor.name == "Number") {
|
||||
this.inputField.type = "number";
|
||||
this.inputField.required = true;
|
||||
this.inputField.min = -2147483648;
|
||||
this.inputField.max = 2147483647;
|
||||
if (this.editing) {
|
||||
this.inputField = document.createElement("input");
|
||||
this.inputField.value = this.value;
|
||||
if (this.type == "Number") {
|
||||
this.inputField.type = "number";
|
||||
this.inputField.required = true;
|
||||
this.inputField.min = -2147483648;
|
||||
this.inputField.max = 2147483647;
|
||||
} else {
|
||||
this.inputField.type = "text";
|
||||
}
|
||||
form.appendChild(this.inputField);
|
||||
document.l10n.setAttributes(this.editButton, "about-config-pref-save");
|
||||
this.editButton.className = "primary button-save";
|
||||
} else {
|
||||
this.inputField.type = "text";
|
||||
delete this.inputField;
|
||||
for (let type of ["Boolean", "Number", "String"]) {
|
||||
let radio = document.createElement("input");
|
||||
radio.type = "radio";
|
||||
radio.name = "type";
|
||||
radio.value = type;
|
||||
radio.checked = this.type == type;
|
||||
form.appendChild(radio);
|
||||
let radioLabel = document.createElement("span");
|
||||
radioLabel.textContent = type;
|
||||
form.appendChild(radioLabel);
|
||||
}
|
||||
form.addEventListener("click", event => {
|
||||
if (event.target.name != "type") {
|
||||
return;
|
||||
}
|
||||
let type = event.target.value;
|
||||
if (this.type != type) {
|
||||
if (type == "Boolean") {
|
||||
this.value = true;
|
||||
} else if (type == "Number") {
|
||||
this.value = 0;
|
||||
} else {
|
||||
this.value = "";
|
||||
}
|
||||
}
|
||||
});
|
||||
document.l10n.setAttributes(this.editButton, "about-config-pref-add");
|
||||
this.editButton.className = "button-add";
|
||||
}
|
||||
form.appendChild(this.inputField);
|
||||
this.valueCell.appendChild(form);
|
||||
document.l10n.setAttributes(this.editButton, "about-config-pref-save");
|
||||
this.editButton.className = "primary button-save";
|
||||
this.editButton.setAttribute("form", "form-edit");
|
||||
}
|
||||
this.editButton.disabled = this.isLocked;
|
||||
|
@ -139,7 +223,7 @@ class PrefRow {
|
|||
}
|
||||
|
||||
save() {
|
||||
if (this.value.constructor.name == "Number") {
|
||||
if (this.type == "Number") {
|
||||
if (!this.inputField.reportValidity()) {
|
||||
return;
|
||||
}
|
||||
|
@ -159,19 +243,37 @@ class PrefRow {
|
|||
}
|
||||
}
|
||||
|
||||
function getPrefName(prefRow) {
|
||||
return prefRow.getAttribute("aria-label");
|
||||
let gPrefObserver = {
|
||||
observe(subject, topic, data) {
|
||||
let pref = gExistingPrefs.get(data) || gDeletedPrefs.get(data);
|
||||
if (pref) {
|
||||
pref.refreshValue();
|
||||
if (!pref.editing) {
|
||||
pref.refreshElement();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let newPref = new PrefRow(data);
|
||||
if (newPref.matchesFilter) {
|
||||
document.getElementById("prefs").appendChild(newPref.element);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if (!Preferences.get("browser.aboutConfig.showWarning")) {
|
||||
// When showing the filtered preferences directly, remove the warning elements
|
||||
// immediately to prevent flickering, but wait to filter the preferences until
|
||||
// the value of the textbox has been restored from previous sessions.
|
||||
document.addEventListener("DOMContentLoaded", loadPrefs, { once: true });
|
||||
window.addEventListener("load", filterPrefs, { once: true });
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (!Preferences.get("browser.aboutConfig.showWarning")) {
|
||||
loadPrefs();
|
||||
}
|
||||
}, { once: true });
|
||||
|
||||
function alterWarningState() {
|
||||
function onWarningButtonClick() {
|
||||
Services.prefs.setBoolPref("browser.aboutConfig.showWarning",
|
||||
document.getElementById("showWarningNextTime").checked);
|
||||
loadPrefs();
|
||||
filterPrefs();
|
||||
}
|
||||
|
||||
function loadPrefs() {
|
||||
|
@ -187,110 +289,82 @@ function loadPrefs() {
|
|||
prefs.id = "prefs";
|
||||
document.body.appendChild(prefs);
|
||||
|
||||
gPrefArray = Services.prefs.getChildList("").map(name => new PrefRow(name));
|
||||
for (let name of Services.prefs.getChildList("")) {
|
||||
new PrefRow(name);
|
||||
}
|
||||
|
||||
gPrefArray.sort((a, b) => a.name > b.name);
|
||||
|
||||
search.addEventListener("keypress", e => {
|
||||
if (e.key == "Enter") {
|
||||
filterPrefs();
|
||||
search.addEventListener("keypress", event => {
|
||||
switch (event.key) {
|
||||
case "Escape":
|
||||
search.value = "";
|
||||
// Fall through.
|
||||
case "Enter":
|
||||
gFilterPrefsTask.disarm();
|
||||
filterPrefs();
|
||||
}
|
||||
});
|
||||
|
||||
search.addEventListener("input", () => {
|
||||
// We call "disarm" to restart the timer at every input.
|
||||
gFilterPrefsTask.disarm();
|
||||
gFilterPrefsTask.arm();
|
||||
});
|
||||
|
||||
prefs.addEventListener("click", event => {
|
||||
if (event.target.localName != "button") {
|
||||
return;
|
||||
}
|
||||
let prefRow = event.target.closest("tr");
|
||||
let prefName = getPrefName(prefRow);
|
||||
let pref = gPrefArray.find(p => p.name == prefName);
|
||||
let pref = gElementToPrefMap.get(event.target.closest("tr"));
|
||||
let button = event.target.closest("button");
|
||||
if (button.classList.contains("button-reset")) {
|
||||
// Reset pref and update gPrefArray.
|
||||
Services.prefs.clearUserPref(prefName);
|
||||
pref.refreshValue();
|
||||
pref.refreshElement();
|
||||
pref.editButton.focus();
|
||||
} else if (button.classList.contains("add-true")) {
|
||||
addNewPref(prefRow.firstChild.innerHTML, true);
|
||||
} else if (button.classList.contains("add-false")) {
|
||||
addNewPref(prefRow.firstChild.innerHTML, false);
|
||||
} else if (button.classList.contains("add-Number") ||
|
||||
button.classList.contains("add-String")) {
|
||||
addNewPref(prefRow.firstChild.innerHTML,
|
||||
button.classList.contains("add-Number") ? 0 : "").edit();
|
||||
if (button.classList.contains("button-add")) {
|
||||
Preferences.set(pref.name, pref.value);
|
||||
if (pref.type != "Boolean") {
|
||||
pref.edit();
|
||||
}
|
||||
} else if (button.classList.contains("button-toggle")) {
|
||||
// Toggle the pref and update gPrefArray.
|
||||
Services.prefs.setBoolPref(prefName, !pref.value);
|
||||
pref.refreshValue();
|
||||
pref.refreshElement();
|
||||
Services.prefs.setBoolPref(pref.name, !pref.value);
|
||||
} else if (button.classList.contains("button-edit")) {
|
||||
pref.edit();
|
||||
} else if (button.classList.contains("button-save")) {
|
||||
pref.save();
|
||||
} else {
|
||||
Services.prefs.clearUserPref(prefName);
|
||||
gPrefArray.splice(gPrefArray.findIndex(p => p.name == prefName), 1);
|
||||
prefRow.remove();
|
||||
// This is "button-reset" or "button-delete".
|
||||
pref.editing = false;
|
||||
Services.prefs.clearUserPref(pref.name);
|
||||
pref.editButton.focus();
|
||||
}
|
||||
});
|
||||
|
||||
filterPrefs();
|
||||
Services.prefs.addObserver("", gPrefObserver);
|
||||
window.addEventListener("unload", () => {
|
||||
Services.prefs.removeObserver("", gPrefObserver);
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
function filterPrefs() {
|
||||
if (gPrefInEdit) {
|
||||
gPrefInEdit.endEdit();
|
||||
}
|
||||
gDeletedPrefs.clear();
|
||||
|
||||
let substring = document.getElementById("search").value.trim();
|
||||
document.getElementById("prefs").textContent = "";
|
||||
if (substring && !gPrefArray.some(pref => pref.name == substring)) {
|
||||
document.getElementById("prefs").appendChild(createNewPrefFragment(substring));
|
||||
let searchName = document.getElementById("search").value.trim();
|
||||
gFilterString = searchName.toLowerCase();
|
||||
let prefArray = [...gExistingPrefs.values()];
|
||||
if (gFilterString) {
|
||||
prefArray = prefArray.filter(pref => pref.matchesFilter);
|
||||
}
|
||||
prefArray.sort((a, b) => a.name > b.name);
|
||||
if (searchName && !gExistingPrefs.has(searchName)) {
|
||||
prefArray.push(new PrefRow(searchName));
|
||||
}
|
||||
let fragment = createPrefsFragment(gPrefArray.filter(pref => pref.name.includes(substring)));
|
||||
document.getElementById("prefs").appendChild(fragment);
|
||||
}
|
||||
|
||||
function createPrefsFragment(prefArray) {
|
||||
let prefsElement = document.getElementById("prefs");
|
||||
prefsElement.textContent = "";
|
||||
let fragment = document.createDocumentFragment();
|
||||
for (let pref of prefArray) {
|
||||
fragment.appendChild(pref.element);
|
||||
}
|
||||
return fragment;
|
||||
}
|
||||
|
||||
function createNewPrefFragment(name) {
|
||||
let fragment = document.createDocumentFragment();
|
||||
let row = document.createElement("tr");
|
||||
row.classList.add("has-user-value");
|
||||
row.setAttribute("aria-label", name);
|
||||
let nameCell = document.createElement("td");
|
||||
nameCell.className = "cell-name";
|
||||
nameCell.append(name);
|
||||
row.appendChild(nameCell);
|
||||
|
||||
let valueCell = document.createElement("td");
|
||||
valueCell.classList.add("cell-value");
|
||||
let guideText = document.createElement("span");
|
||||
document.l10n.setAttributes(guideText, "about-config-pref-add");
|
||||
valueCell.appendChild(guideText);
|
||||
for (let item of ["true", "false", "Number", "String"]) {
|
||||
let optionBtn = document.createElement("button");
|
||||
optionBtn.textContent = item;
|
||||
optionBtn.classList.add("add-" + item);
|
||||
valueCell.appendChild(optionBtn);
|
||||
}
|
||||
row.appendChild(valueCell);
|
||||
|
||||
let editCell = document.createElement("td");
|
||||
row.appendChild(editCell);
|
||||
|
||||
let buttonCell = document.createElement("td");
|
||||
row.appendChild(buttonCell);
|
||||
|
||||
fragment.appendChild(row);
|
||||
return fragment;
|
||||
prefsElement.appendChild(fragment);
|
||||
}
|
||||
|
||||
function prefHasDefaultValue(name) {
|
||||
|
@ -309,12 +383,3 @@ function prefHasDefaultValue(name) {
|
|||
} catch (ex) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
function addNewPref(name, value) {
|
||||
Preferences.set(name, value);
|
||||
let pref = new PrefRow(name);
|
||||
gPrefArray.push(pref);
|
||||
gPrefArray.sort((a, b) => a.name > b.name);
|
||||
filterPrefs();
|
||||
return pref;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ about-config-title = about:config
|
|||
about-config-search =
|
||||
.placeholder = Search
|
||||
|
||||
about-config-pref-add = Add as:
|
||||
about-config-pref-add = Add
|
||||
about-config-pref-toggle = Toggle
|
||||
about-config-pref-edit = Edit
|
||||
about-config-pref-save = Save
|
||||
|
|
|
@ -3,10 +3,14 @@ support-files =
|
|||
head.js
|
||||
|
||||
[browser_basic.js]
|
||||
[browser_warning.js]
|
||||
[browser_clipboard.js]
|
||||
skip-if = debug # Bug 1507747
|
||||
subsuite = clipboard
|
||||
[browser_edit.js]
|
||||
skip-if = debug # Bug 1507747
|
||||
[browser_locked.js]
|
||||
[browser_observe.js]
|
||||
skip-if = debug # Bug 1507747
|
||||
[browser_search.js]
|
||||
skip-if = debug # Bug 1507747
|
||||
[browser_locked.js]
|
||||
|
||||
[browser_warning.js]
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["test.aboutconfig.copy.false", false],
|
||||
["test.aboutconfig.copy.number", 10],
|
||||
["test.aboutconfig.copy.spaces.1", " "],
|
||||
["test.aboutconfig.copy.spaces.2", " "],
|
||||
["test.aboutconfig.copy.spaces.3", " "],
|
||||
["test.aboutconfig.copy.string", "010.5"],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_copy() {
|
||||
await AboutConfigTest.withNewTab(async function() {
|
||||
for (let [name, expectedString] of [
|
||||
[PREF_BOOLEAN_DEFAULT_TRUE, "true"],
|
||||
[PREF_BOOLEAN_USERVALUE_TRUE, "true"],
|
||||
[PREF_STRING_DEFAULT_EMPTY, ""],
|
||||
["test.aboutconfig.copy.false", "false"],
|
||||
["test.aboutconfig.copy.number", "10"],
|
||||
["test.aboutconfig.copy.spaces.1", " "],
|
||||
["test.aboutconfig.copy.spaces.2", " "],
|
||||
["test.aboutconfig.copy.spaces.3", " "],
|
||||
["test.aboutconfig.copy.string", "010.5"],
|
||||
]) {
|
||||
// Limit the number of preferences shown so all the rows are visible.
|
||||
this.search(name);
|
||||
let row = this.getRow(name);
|
||||
|
||||
// Triple click at any location in the name cell should select the name.
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter(row.nameCell,
|
||||
{ clickCount: 3 },
|
||||
this.browser);
|
||||
Assert.ok(row.nameCell.contains(this.window.getSelection().anchorNode));
|
||||
await SimpleTest.promiseClipboardChange(name, async () => {
|
||||
await BrowserTestUtils.synthesizeKey("c", { accelKey: true },
|
||||
this.browser);
|
||||
});
|
||||
|
||||
// Triple click at any location in the value cell should select the value.
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter(row.valueCell,
|
||||
{ clickCount: 3 },
|
||||
this.browser);
|
||||
let selection = this.window.getSelection();
|
||||
Assert.ok(row.valueCell.contains(selection.anchorNode));
|
||||
|
||||
// The selection is never collapsed because of the <span> element, and
|
||||
// this makes sure that an empty string can be copied.
|
||||
Assert.ok(!selection.isCollapsed);
|
||||
await SimpleTest.promiseClipboardChange(expectedString, async () => {
|
||||
await BrowserTestUtils.synthesizeKey("c", { accelKey: true },
|
||||
this.browser);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_copy_multiple() {
|
||||
await AboutConfigTest.withNewTab(async function() {
|
||||
// Lines are separated by a single LF character on all platforms.
|
||||
let expectedString = "test.aboutconfig.copy.false\tfalse\t\n" +
|
||||
"test.aboutconfig.copy.number\t10\t\n" +
|
||||
"test.aboutconfig.copy.spaces.1\t \t\n" +
|
||||
"test.aboutconfig.copy.spaces.2\t \t\n" +
|
||||
"test.aboutconfig.copy.spaces.3\t \t\n" +
|
||||
"test.aboutconfig.copy.string\t010.5";
|
||||
|
||||
this.search("test.aboutconfig.copy.");
|
||||
let startRow = this.getRow("test.aboutconfig.copy.false");
|
||||
let endRow = this.getRow("test.aboutconfig.copy.string");
|
||||
let { width, height } = endRow.valueCell.getBoundingClientRect();
|
||||
|
||||
// Drag from the top left of the first row to the bottom right of the last.
|
||||
await BrowserTestUtils.synthesizeMouse(startRow.nameCell, 1, 1,
|
||||
{ type: "mousedown" }, this.browser);
|
||||
await BrowserTestUtils.synthesizeMouse(endRow.valueCell,
|
||||
width - 1, height - 1,
|
||||
{ type: "mousemove" }, this.browser);
|
||||
await BrowserTestUtils.synthesizeMouse(endRow.valueCell,
|
||||
width - 1, height - 1,
|
||||
{ type: "mouseup" }, this.browser);
|
||||
|
||||
await SimpleTest.promiseClipboardChange(expectedString, async () => {
|
||||
await BrowserTestUtils.synthesizeKey("c", { accelKey: true },
|
||||
this.browser);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -18,36 +18,71 @@ add_task(async function setup() {
|
|||
});
|
||||
|
||||
add_task(async function test_add_user_pref() {
|
||||
await AboutConfigTest.withNewTab(async function() {
|
||||
Assert.ok(!Services.prefs.getChildList("").find(pref => pref == "testPref"));
|
||||
Assert.equal(Services.prefs.getPrefType(PREF_NEW),
|
||||
Ci.nsIPrefBranch.PREF_INVALID);
|
||||
|
||||
for (let [buttonSelector, expectedValue] of [
|
||||
[".add-true", true],
|
||||
[".add-false", false],
|
||||
[".add-Number", 0],
|
||||
[".add-String", ""],
|
||||
await AboutConfigTest.withNewTab(async function() {
|
||||
// The row for a new preference appears when searching for its name.
|
||||
Assert.ok(!this.getRow(PREF_NEW));
|
||||
this.search(PREF_NEW);
|
||||
let row = this.getRow(PREF_NEW);
|
||||
Assert.ok(row.hasClass("deleted"));
|
||||
|
||||
for (let [radioIndex, expectedValue, expectedEditingMode] of [
|
||||
[0, true, false],
|
||||
[1, 0, true],
|
||||
[2, "", true],
|
||||
]) {
|
||||
this.search("testPref");
|
||||
this.document.querySelector("#prefs button" + buttonSelector).click();
|
||||
Assert.ok(Services.prefs.getChildList("").find(pref => pref == "testPref"));
|
||||
Assert.ok(Preferences.get("testPref") === expectedValue);
|
||||
this.document.querySelector("#prefs button[data-l10n-id='about-config-pref-delete']").click();
|
||||
// Adding the preference should set the default for the data type.
|
||||
row.element.querySelectorAll("input")[radioIndex].click();
|
||||
row.editColumnButton.click();
|
||||
Assert.ok(!row.hasClass("deleted"));
|
||||
Assert.ok(Preferences.get(PREF_NEW) === expectedValue);
|
||||
|
||||
// Number and String preferences should be in edit mode.
|
||||
Assert.equal(!!row.valueInput, expectedEditingMode);
|
||||
|
||||
// Repeat the search to verify that the preference remains.
|
||||
this.search(PREF_NEW);
|
||||
row = this.getRow(PREF_NEW);
|
||||
Assert.ok(!row.hasClass("deleted"));
|
||||
Assert.ok(!row.valueInput);
|
||||
|
||||
// Reset the preference, then continue by adding a different type.
|
||||
row.resetColumnButton.click();
|
||||
Assert.equal(Services.prefs.getPrefType(PREF_NEW),
|
||||
Ci.nsIPrefBranch.PREF_INVALID);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_delete_user_pref() {
|
||||
Services.prefs.setBoolPref("userAddedPref", true);
|
||||
await AboutConfigTest.withNewTab(async function() {
|
||||
let row = this.getRow("userAddedPref");
|
||||
row.resetColumnButton.click();
|
||||
Assert.ok(!this.getRow("userAddedPref"));
|
||||
Assert.ok(!Services.prefs.getChildList("").includes("userAddedPref"));
|
||||
for (let [radioIndex, testValue] of [
|
||||
[0, false],
|
||||
[1, -1],
|
||||
[2, "value"],
|
||||
]) {
|
||||
Preferences.set(PREF_NEW, testValue);
|
||||
await AboutConfigTest.withNewTab(async function() {
|
||||
// Deleting the preference should keep the row.
|
||||
let row = this.getRow(PREF_NEW);
|
||||
row.resetColumnButton.click();
|
||||
Assert.ok(row.hasClass("deleted"));
|
||||
Assert.equal(Services.prefs.getPrefType(PREF_NEW),
|
||||
Ci.nsIPrefBranch.PREF_INVALID);
|
||||
|
||||
// Search for nothing to test gPrefArray
|
||||
this.search();
|
||||
Assert.ok(!this.getRow("userAddedPref"));
|
||||
});
|
||||
// Re-adding the preference should keep the same value.
|
||||
Assert.ok(row.element.querySelectorAll("input")[radioIndex].checked);
|
||||
row.editColumnButton.click();
|
||||
Assert.ok(!row.hasClass("deleted"));
|
||||
Assert.ok(Preferences.get(PREF_NEW) === testValue);
|
||||
|
||||
// Searching again after deleting should remove the row.
|
||||
row.resetColumnButton.click();
|
||||
this.search();
|
||||
Assert.ok(!this.getRow(PREF_NEW));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_reset_user_pref() {
|
||||
|
@ -148,11 +183,8 @@ add_task(async function test_modify() {
|
|||
row.editColumnButton.click();
|
||||
Assert.equal(row.valueInput.value, Preferences.get(prefName));
|
||||
row.resetColumnButton.click();
|
||||
if (willDelete) {
|
||||
Assert.ok(!this.getRow(prefName));
|
||||
} else {
|
||||
Assert.ok(!row.hasClass("has-user-value"));
|
||||
}
|
||||
Assert.ok(!row.hasClass("has-user-value"));
|
||||
Assert.equal(row.hasClass("deleted"), willDelete);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["test.aboutconfig.modify.boolean", true],
|
||||
["test.aboutconfig.modify.number", 1337],
|
||||
["test.aboutconfig.modify.string", "the answer to the life the universe and everything"],
|
||||
],
|
||||
});
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref(PREF_BOOLEAN_DEFAULT_TRUE);
|
||||
Services.prefs.clearUserPref(PREF_NUMBER_DEFAULT_ZERO);
|
||||
Services.prefs.clearUserPref(PREF_STRING_DEFAULT_EMPTY);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_observe_add_user_pref() {
|
||||
Assert.equal(Services.prefs.getPrefType(PREF_NEW),
|
||||
Ci.nsIPrefBranch.PREF_INVALID);
|
||||
|
||||
await AboutConfigTest.withNewTab(async function() {
|
||||
for (let value of [false, true, "", "value", 0, -10]) {
|
||||
// A row should be added when a new preference is added.
|
||||
Assert.ok(!this.getRow(PREF_NEW));
|
||||
Preferences.set(PREF_NEW, value);
|
||||
let row = this.getRow(PREF_NEW);
|
||||
Assert.equal(row.value, "" + value);
|
||||
|
||||
// The row should stay when the preference is removed.
|
||||
Preferences.reset(PREF_NEW);
|
||||
Assert.ok(row.hasClass("deleted"));
|
||||
|
||||
// Re-adding the preference from the interface should restore its value.
|
||||
row.editColumnButton.click();
|
||||
if (value.constructor.name != "Boolean") {
|
||||
row.editColumnButton.click();
|
||||
}
|
||||
Assert.equal(row.value, "" + value);
|
||||
Assert.ok(Preferences.get(PREF_NEW) === value);
|
||||
|
||||
// Searching again after deleting should remove the row.
|
||||
Preferences.reset(PREF_NEW);
|
||||
this.search();
|
||||
Assert.ok(!this.getRow(PREF_NEW));
|
||||
|
||||
// Searching for the preference name should give the ability to add it.
|
||||
Preferences.reset(PREF_NEW);
|
||||
this.search(PREF_NEW);
|
||||
row = this.getRow(PREF_NEW);
|
||||
Assert.ok(row.hasClass("deleted"));
|
||||
|
||||
// The row for adding should be reused if the new preference is added.
|
||||
Preferences.set(PREF_NEW, value);
|
||||
Assert.equal(row.value, "" + value);
|
||||
|
||||
// If a new preference does not match the filter it is not displayed.
|
||||
Preferences.reset(PREF_NEW);
|
||||
this.search(PREF_NEW + ".extra");
|
||||
Assert.ok(!this.getRow(PREF_NEW));
|
||||
Preferences.set(PREF_NEW, value);
|
||||
Assert.ok(!this.getRow(PREF_NEW));
|
||||
|
||||
// Resetting the filter should display the new preference.
|
||||
this.search("");
|
||||
Assert.equal(this.getRow(PREF_NEW).value, "" + value);
|
||||
|
||||
// Reset the preference, then continue by adding a different value.
|
||||
Preferences.reset(PREF_NEW);
|
||||
this.search("");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_observe_delete_user_pref() {
|
||||
for (let value of [true, "value", -10]) {
|
||||
Preferences.set(PREF_NEW, value);
|
||||
await AboutConfigTest.withNewTab(async function() {
|
||||
// Deleting the preference should keep the row.
|
||||
let row = this.getRow(PREF_NEW);
|
||||
Preferences.reset(PREF_NEW);
|
||||
Assert.ok(row.hasClass("deleted"));
|
||||
|
||||
// Searching again should remove the row.
|
||||
this.search();
|
||||
Assert.ok(!this.getRow(PREF_NEW));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_observe_reset_user_pref() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
[PREF_BOOLEAN_DEFAULT_TRUE, false],
|
||||
],
|
||||
});
|
||||
|
||||
await AboutConfigTest.withNewTab(async function() {
|
||||
let row = this.getRow(PREF_BOOLEAN_DEFAULT_TRUE);
|
||||
Preferences.reset(PREF_BOOLEAN_DEFAULT_TRUE);
|
||||
Assert.ok(!row.hasClass("has-user-value"));
|
||||
Assert.equal(row.value, "true");
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_observe_modify() {
|
||||
await AboutConfigTest.withNewTab(async function() {
|
||||
for (let [name, value] of [
|
||||
["test.aboutconfig.modify.boolean", false],
|
||||
["test.aboutconfig.modify.number", -10],
|
||||
["test.aboutconfig.modify.string", "value"],
|
||||
[PREF_BOOLEAN_DEFAULT_TRUE, false],
|
||||
[PREF_NUMBER_DEFAULT_ZERO, 1],
|
||||
[PREF_STRING_DEFAULT_EMPTY, "string"],
|
||||
]) {
|
||||
let row = this.getRow(name);
|
||||
Assert.notEqual(row.value, "" + value);
|
||||
Preferences.set(name, value);
|
||||
Assert.equal(row.value, "" + value);
|
||||
|
||||
if (value.constructor.name == "Boolean") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Changing the value or removing while editing should not take effect.
|
||||
row.editColumnButton.click();
|
||||
row.valueInput.value = "42";
|
||||
Preferences.reset(name);
|
||||
Assert.equal(row.element, this.getRow(name).element);
|
||||
Assert.equal(row.valueInput.value, "42");
|
||||
|
||||
// Saving should store the value even if the preference was modified.
|
||||
row.editColumnButton.click();
|
||||
Assert.equal(row.value, "42");
|
||||
Assert.equal(Preferences.get(name), "42");
|
||||
}
|
||||
});
|
||||
});
|
|
@ -25,7 +25,7 @@ add_task(async function test_search() {
|
|||
// Filter a subset of preferences. The "browser.download." branch is
|
||||
// chosen because it is very unlikely that its preferences would be
|
||||
// modified by other code during the execution of this test.
|
||||
this.search("wser.down ");
|
||||
this.search("Wser.down ");
|
||||
|
||||
let filteredPrefArray =
|
||||
prefArray.filter(pref => pref.includes("wser.down"));
|
||||
|
@ -49,8 +49,41 @@ add_task(async function test_search() {
|
|||
this.search("aJunkValueasdf");
|
||||
Assert.equal(this.rows.length, 1);
|
||||
|
||||
// Test added preferences search returns 2 preferences.
|
||||
// Two preferences match this filter, and one of those matches exactly.
|
||||
this.search("test.aboutconfig.a");
|
||||
Assert.equal(this.rows.length, 2);
|
||||
|
||||
// When searching case insensitively, there is an additional row to add a
|
||||
// new preference with the same name but a different case.
|
||||
this.search("TEST.aboutconfig.a");
|
||||
Assert.equal(this.rows.length, 3);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_search_delayed() {
|
||||
await AboutConfigTest.withNewTab(async function() {
|
||||
let prefs = this.document.getElementById("prefs");
|
||||
|
||||
// Prepare the table and the search field for the test.
|
||||
this.search("test.aboutconfig.a");
|
||||
Assert.equal(this.rows.length, 2);
|
||||
|
||||
// The table is updated in a single microtask, so we don't need to wait for
|
||||
// specific mutations, we can just continue when the children are updated.
|
||||
let prefsTableChanged = new Promise(resolve => {
|
||||
let observer = new MutationObserver(() => {
|
||||
observer.disconnect();
|
||||
resolve();
|
||||
});
|
||||
observer.observe(prefs, { childList: true });
|
||||
});
|
||||
|
||||
// Add a character and test that the table is not updated immediately.
|
||||
EventUtils.synthesizeKey("b");
|
||||
Assert.equal(this.rows.length, 2);
|
||||
|
||||
// The table will eventually be updated after a delay.
|
||||
await prefsTableChanged;
|
||||
Assert.equal(this.rows.length, 1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,6 +17,9 @@ const PREF_STRING_DEFAULT_NOTEMPTY = "accessibility.typeaheadfind.soundURL";
|
|||
const PREF_STRING_DEFAULT_NOTEMPTY_VALUE = "beep";
|
||||
const PREF_STRING_LOCALIZED_MISSING = "gecko.handlerService.schemes.irc.1.name";
|
||||
|
||||
// Other preference names used in tests.
|
||||
const PREF_NEW = "test.aboutconfig.new";
|
||||
|
||||
class AboutConfigRowTest {
|
||||
constructor(element) {
|
||||
this.element = element;
|
||||
|
@ -26,24 +29,32 @@ class AboutConfigRowTest {
|
|||
return this.element.querySelector(selector);
|
||||
}
|
||||
|
||||
get nameCell() {
|
||||
return this.querySelector("td");
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.querySelector("td").textContent;
|
||||
return this.nameCell.textContent;
|
||||
}
|
||||
|
||||
get valueCell() {
|
||||
return this.querySelector("td.cell-value");
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.querySelector("td.cell-value").textContent;
|
||||
return this.valueCell.textContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Text input field when the row is in edit mode.
|
||||
*/
|
||||
get valueInput() {
|
||||
return this.querySelector("td.cell-value input");
|
||||
return this.valueCell.querySelector("input");
|
||||
}
|
||||
|
||||
/**
|
||||
* This is normally "edit" or "toggle" based on the preference type, or "save"
|
||||
* when the row is in edit mode.
|
||||
* This is normally "edit" or "toggle" based on the preference type, "save"
|
||||
* when the row is in edit mode, or "add" when the preference does not exist.
|
||||
*/
|
||||
get editColumnButton() {
|
||||
return this.querySelector("td.cell-edit > button");
|
||||
|
@ -74,7 +85,9 @@ class AboutConfigTest {
|
|||
}
|
||||
|
||||
constructor(browser) {
|
||||
this.browser = browser;
|
||||
this.document = browser.contentDocument;
|
||||
this.window = browser.contentWindow;
|
||||
}
|
||||
|
||||
async setupNewTab(options) {
|
||||
|
|
|
@ -54,7 +54,7 @@ skip-if = true || !healthreport # Bug 1185403 for the "true"
|
|||
[browser_homepages_filter_aboutpreferences.js]
|
||||
[browser_homepages_use_bookmark.js]
|
||||
[browser_extension_controlled.js]
|
||||
skipif = ccov && os == 'win' # bug 1437051
|
||||
skip-if = ccov && os == 'win' # bug 1437051
|
||||
[browser_languages_subdialog.js]
|
||||
[browser_browser_languages_subdialog.js]
|
||||
[browser_layersacceleration.js]
|
||||
|
|
|
@ -25,6 +25,14 @@
|
|||
|
||||
using namespace mozilla;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
LazyLogModule gPageLoadLog("PageLoad");
|
||||
#define PAGELOAD_LOG(args) MOZ_LOG(gPageLoadLog, LogLevel::Debug, args)
|
||||
#define PAGELOAD_LOG_ENABLED() MOZ_LOG_TEST(gPageLoadLog, LogLevel::Error)
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
nsDOMNavigationTiming::nsDOMNavigationTiming(nsDocShell* aDocShell) {
|
||||
Clear();
|
||||
|
||||
|
@ -140,6 +148,25 @@ void nsDOMNavigationTiming::NotifyLoadEventEnd() {
|
|||
mDocShell);
|
||||
|
||||
if (IsTopLevelContentDocumentInContentProcess()) {
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
if (profiler_is_active() || PAGELOAD_LOG_ENABLED()) {
|
||||
TimeDuration elapsed = mLoadEventEnd - mNavigationStart;
|
||||
TimeDuration duration = mLoadEventEnd - mLoadEventStart;
|
||||
nsAutoCString spec;
|
||||
if (mLoadedURI) {
|
||||
mLoadedURI->GetSpec(spec);
|
||||
}
|
||||
nsPrintfCString marker(
|
||||
"Document %s loaded after %dms, load event duration %dms", spec.get(),
|
||||
int(elapsed.ToMilliseconds()), int(duration.ToMilliseconds()));
|
||||
DECLARE_DOCSHELL_AND_HISTORY_ID(mDocShell);
|
||||
PAGELOAD_LOG(("%s", marker.get()));
|
||||
profiler_add_marker(
|
||||
"DocumentLoad",
|
||||
MakeUnique<TextMarkerPayload>(marker, mNavigationStart, mLoadEventEnd,
|
||||
docShellId, docShellHistoryId));
|
||||
}
|
||||
#endif
|
||||
Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_LOAD_EVENT_END_MS,
|
||||
mNavigationStart);
|
||||
}
|
||||
|
@ -240,36 +267,6 @@ void nsDOMNavigationTiming::TTITimeoutCallback(nsITimer* aTimer,
|
|||
self->TTITimeout(aTimer);
|
||||
}
|
||||
|
||||
// Return the max of aT1 and aT2, or the lower of the two if there's more
|
||||
// than Nms (the window size) between them. In other words, the window
|
||||
// starts at the lower of aT1 and aT2, and we only want to respect
|
||||
// timestamps within the window (and pick the max of those).
|
||||
//
|
||||
// This approach handles the edge case of a late wakeup: where there was
|
||||
// more than Nms after one (of aT1 or aT2) without the other, but the other
|
||||
// happened after Nms and before we woke up. For example, if aT1 was 10
|
||||
// seconds after aT2, but we woke up late (after aT1) we don't want to
|
||||
// return aT1 if the window is 5 seconds.
|
||||
static const TimeStamp& MaxWithinWindowBeginningAtMin(
|
||||
const TimeStamp& aT1, const TimeStamp& aT2,
|
||||
const TimeDuration& aWindowSize) {
|
||||
if (aT2.IsNull()) {
|
||||
return aT1;
|
||||
} else if (aT1.IsNull()) {
|
||||
return aT2;
|
||||
}
|
||||
if (aT1 > aT2) {
|
||||
if ((aT1 - aT2) > aWindowSize) {
|
||||
return aT2;
|
||||
}
|
||||
return aT1;
|
||||
}
|
||||
if ((aT2 - aT1) > aWindowSize) {
|
||||
return aT1;
|
||||
}
|
||||
return aT2;
|
||||
}
|
||||
|
||||
#define TTI_WINDOW_SIZE_MS (5 * 1000)
|
||||
|
||||
void nsDOMNavigationTiming::TTITimeout(nsITimer* aTimer) {
|
||||
|
@ -281,21 +278,36 @@ void nsDOMNavigationTiming::TTITimeout(nsITimer* aTimer) {
|
|||
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
|
||||
TimeStamp lastLongTaskEnded;
|
||||
mainThread->GetLastLongNonIdleTaskEnd(&lastLongTaskEnded);
|
||||
if (!lastLongTaskEnded.IsNull()) {
|
||||
TimeDuration delta = now - lastLongTaskEnded;
|
||||
if (delta.ToMilliseconds() < TTI_WINDOW_SIZE_MS) {
|
||||
// Less than 5 seconds since the last long task. Schedule another check
|
||||
aTimer->InitWithNamedFuncCallback(TTITimeoutCallback, this,
|
||||
TTI_WINDOW_SIZE_MS,
|
||||
nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
|
||||
"nsDOMNavigationTiming::TTITimeout");
|
||||
return;
|
||||
}
|
||||
// Window starts at mContentfulPaint; any long task before that is ignored
|
||||
if (lastLongTaskEnded.IsNull() || lastLongTaskEnded < mContentfulPaint) {
|
||||
PAGELOAD_LOG(
|
||||
("no longtask (last was %g ms before ContentfulPaint)",
|
||||
lastLongTaskEnded.IsNull()
|
||||
? 0
|
||||
: (mContentfulPaint - lastLongTaskEnded).ToMilliseconds()));
|
||||
lastLongTaskEnded = mContentfulPaint;
|
||||
}
|
||||
TimeDuration delta = now - lastLongTaskEnded;
|
||||
PAGELOAD_LOG(("TTI delta: %g ms", delta.ToMilliseconds()));
|
||||
if (delta.ToMilliseconds() < TTI_WINDOW_SIZE_MS) {
|
||||
// Less than 5 seconds since the last long task or start of the window.
|
||||
// Schedule another check.
|
||||
PAGELOAD_LOG(("TTI: waiting additional %g ms",
|
||||
(TTI_WINDOW_SIZE_MS + 100) - delta.ToMilliseconds()));
|
||||
aTimer->InitWithNamedFuncCallback(
|
||||
TTITimeoutCallback, this,
|
||||
(TTI_WINDOW_SIZE_MS + 100) -
|
||||
delta.ToMilliseconds(), // slightly after the window ends
|
||||
nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
|
||||
"nsDOMNavigationTiming::TTITimeout");
|
||||
return;
|
||||
}
|
||||
|
||||
// To correctly implement TTI/TTFI as proposed, we'd need to not
|
||||
// fire it until there are no more than 2 network loads. By the
|
||||
// proposed definition, without that we're closer to
|
||||
// TimeToFirstInteractive.
|
||||
// TimeToFirstInteractive. There are also arguments about what sort
|
||||
// of loads should qualify.
|
||||
|
||||
// XXX check number of network loads, and if > 2 mark to check if loads
|
||||
// decreases to 2 (or record that point and let the normal timer here
|
||||
|
@ -303,15 +315,25 @@ void nsDOMNavigationTiming::TTITimeout(nsITimer* aTimer) {
|
|||
|
||||
// TTI has occurred! TTI is either FCP (if there are no longtasks and no
|
||||
// DCLEnd in the window that starts at FCP), or at the end of the last
|
||||
// Long Task or DOMContentLoadedEnd (whichever is later).
|
||||
// Long Task or DOMContentLoadedEnd (whichever is later). lastLongTaskEnded
|
||||
// is >= FCP here.
|
||||
|
||||
if (mTTFI.IsNull()) {
|
||||
mTTFI = MaxWithinWindowBeginningAtMin(
|
||||
lastLongTaskEnded, mDOMContentLoadedEventEnd,
|
||||
TimeDuration::FromMilliseconds(TTI_WINDOW_SIZE_MS));
|
||||
if (mTTFI.IsNull()) {
|
||||
mTTFI = mContentfulPaint;
|
||||
}
|
||||
// lastLongTaskEnded is >= mContentfulPaint
|
||||
mTTFI = (mDOMContentLoadedEventEnd.IsNull() ||
|
||||
lastLongTaskEnded > mDOMContentLoadedEventEnd)
|
||||
? lastLongTaskEnded
|
||||
: mDOMContentLoadedEventEnd;
|
||||
PAGELOAD_LOG(
|
||||
("TTFI after %dms (LongTask was at %dms, DCL was %dms)",
|
||||
int((mTTFI - mNavigationStart).ToMilliseconds()),
|
||||
lastLongTaskEnded.IsNull()
|
||||
? 0
|
||||
: int((lastLongTaskEnded - mNavigationStart).ToMilliseconds()),
|
||||
mDOMContentLoadedEventEnd.IsNull()
|
||||
? 0
|
||||
: int((mDOMContentLoadedEventEnd - mNavigationStart)
|
||||
.ToMilliseconds())));
|
||||
}
|
||||
// XXX Implement TTI via check number of network loads, and if > 2 mark
|
||||
// to check if loads decreases to 2 (or record that point and let the
|
||||
|
@ -320,22 +342,23 @@ void nsDOMNavigationTiming::TTITimeout(nsITimer* aTimer) {
|
|||
mTTITimer = nullptr;
|
||||
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
if (profiler_is_active()) {
|
||||
if (profiler_is_active() || PAGELOAD_LOG_ENABLED()) {
|
||||
TimeDuration elapsed = mTTFI - mNavigationStart;
|
||||
MOZ_ASSERT(elapsed.ToMilliseconds() > 0);
|
||||
TimeDuration elapsedLongTask =
|
||||
lastLongTaskEnded.IsNull() ? 0 : lastLongTaskEnded - mNavigationStart;
|
||||
nsAutoCString spec;
|
||||
if (mLoadedURI) {
|
||||
mLoadedURI->GetSpec(spec);
|
||||
}
|
||||
nsPrintfCString marker("TTFI after %dms (LongTask after %dms) for URL %s",
|
||||
nsPrintfCString marker("TTFI after %dms (LongTask was at %dms) for URL %s",
|
||||
int(elapsed.ToMilliseconds()),
|
||||
int(elapsedLongTask.ToMilliseconds()), spec.get());
|
||||
|
||||
DECLARE_DOCSHELL_AND_HISTORY_ID(mDocShell);
|
||||
profiler_add_marker("TTI", MakeUnique<UserTimingMarkerPayload>(
|
||||
NS_ConvertASCIItoUTF16(marker), mTTFI,
|
||||
docShellId, docShellHistoryId));
|
||||
profiler_add_marker(
|
||||
"TTFI", MakeUnique<TextMarkerPayload>(marker, mNavigationStart, mTTFI,
|
||||
docShellId, docShellHistoryId));
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
|
@ -352,7 +375,7 @@ void nsDOMNavigationTiming::NotifyNonBlankPaintForRootContentDocument() {
|
|||
mNonBlankPaint = TimeStamp::Now();
|
||||
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
if (profiler_thread_is_being_profiled()) {
|
||||
if (profiler_thread_is_being_profiled() || PAGELOAD_LOG_ENABLED()) {
|
||||
TimeDuration elapsed = mNonBlankPaint - mNavigationStart;
|
||||
nsAutoCString spec;
|
||||
if (mLoadedURI) {
|
||||
|
@ -365,7 +388,12 @@ void nsDOMNavigationTiming::NotifyNonBlankPaintForRootContentDocument() {
|
|||
? "foreground tab"
|
||||
: "this tab was inactive some of the time between navigation start "
|
||||
"and first non-blank paint");
|
||||
profiler_add_marker(marker.get());
|
||||
PAGELOAD_LOG(("%s", marker.get()));
|
||||
DECLARE_DOCSHELL_AND_HISTORY_ID(mDocShell);
|
||||
profiler_add_marker(
|
||||
"FirstNonBlankPaint",
|
||||
MakeUnique<TextMarkerPayload>(marker, mNavigationStart, mNonBlankPaint,
|
||||
docShellId, docShellHistoryId));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -396,7 +424,7 @@ void nsDOMNavigationTiming::NotifyContentfulPaintForRootContentDocument() {
|
|||
mContentfulPaint = TimeStamp::Now();
|
||||
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
if (profiler_is_active()) {
|
||||
if (profiler_is_active() || PAGELOAD_LOG_ENABLED()) {
|
||||
TimeDuration elapsed = mContentfulPaint - mNavigationStart;
|
||||
nsAutoCString spec;
|
||||
if (mLoadedURI) {
|
||||
|
@ -409,7 +437,12 @@ void nsDOMNavigationTiming::NotifyContentfulPaintForRootContentDocument() {
|
|||
? "foreground tab"
|
||||
: "this tab was inactive some of the time between navigation start "
|
||||
"and first non-blank paint");
|
||||
profiler_add_marker(marker.get());
|
||||
DECLARE_DOCSHELL_AND_HISTORY_ID(mDocShell);
|
||||
PAGELOAD_LOG(("%s", marker.get()));
|
||||
profiler_add_marker("FirstContentfulPaint",
|
||||
MakeUnique<TextMarkerPayload>(
|
||||
marker, mNavigationStart, mContentfulPaint,
|
||||
docShellId, docShellHistoryId));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -436,7 +469,7 @@ void nsDOMNavigationTiming::NotifyDOMContentFlushedForRootContentDocument() {
|
|||
mDOMContentFlushed = TimeStamp::Now();
|
||||
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
if (profiler_thread_is_being_profiled()) {
|
||||
if (profiler_thread_is_being_profiled() || PAGELOAD_LOG_ENABLED()) {
|
||||
TimeDuration elapsed = mDOMContentFlushed - mNavigationStart;
|
||||
nsAutoCString spec;
|
||||
if (mLoadedURI) {
|
||||
|
@ -449,7 +482,12 @@ void nsDOMNavigationTiming::NotifyDOMContentFlushedForRootContentDocument() {
|
|||
? "foreground tab"
|
||||
: "this tab was inactive some of the time between navigation start "
|
||||
"and DOMContentFlushed");
|
||||
profiler_add_marker(marker.get());
|
||||
DECLARE_DOCSHELL_AND_HISTORY_ID(mDocShell);
|
||||
PAGELOAD_LOG(("%s", marker.get()));
|
||||
profiler_add_marker("DOMContentFlushed",
|
||||
MakeUnique<TextMarkerPayload>(
|
||||
marker, mNavigationStart, mDOMContentFlushed,
|
||||
docShellId, docShellHistoryId));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -55,10 +55,6 @@ class MOZ_RAII StackingContextHelper {
|
|||
// Export the inherited scale
|
||||
gfx::Size GetInheritedScale() const { return mScale; }
|
||||
|
||||
const gfx::Matrix& GetInheritedTransform() const {
|
||||
return mInheritedTransform;
|
||||
}
|
||||
|
||||
const gfx::Matrix& GetSnappingSurfaceTransform() const {
|
||||
return mSnappingSurfaceTransform;
|
||||
}
|
||||
|
|
|
@ -262,7 +262,7 @@ ImgDrawResult ImageResource::GetImageContainerImpl(
|
|||
return drawResult;
|
||||
}
|
||||
|
||||
void ImageResource::UpdateImageContainer(const Maybe<IntRect>& aDirtyRect) {
|
||||
bool ImageResource::UpdateImageContainer(const Maybe<IntRect>& aDirtyRect) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
for (int i = mImageContainers.Length() - 1; i >= 0; --i) {
|
||||
|
@ -289,6 +289,8 @@ void ImageResource::UpdateImageContainer(const Maybe<IntRect>& aDirtyRect) {
|
|||
mImageContainers.RemoveElementAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
return !mImageContainers.IsEmpty();
|
||||
}
|
||||
|
||||
void ImageResource::ReleaseImageContainer() {
|
||||
|
|
|
@ -368,7 +368,12 @@ class ImageResource : public Image {
|
|||
uint32_t aFlags,
|
||||
layers::ImageContainer** aContainer);
|
||||
|
||||
void UpdateImageContainer(const Maybe<gfx::IntRect>& aDirtyRect);
|
||||
/**
|
||||
* Re-requests the appropriate frames for each image container using
|
||||
* GetFrameInternal.
|
||||
* @returns True if any image containers were updated, else false.
|
||||
*/
|
||||
bool UpdateImageContainer(const Maybe<gfx::IntRect>& aDirtyRect);
|
||||
|
||||
void ReleaseImageContainer();
|
||||
|
||||
|
|
|
@ -542,7 +542,6 @@ VectorImage::RequestRefresh(const TimeStamp& aTime) {
|
|||
mSVGDocumentWrapper->TickRefreshDriver();
|
||||
|
||||
if (mHasPendingInvalidation) {
|
||||
mHasPendingInvalidation = false;
|
||||
SendInvalidationNotifications();
|
||||
}
|
||||
}
|
||||
|
@ -555,21 +554,30 @@ void VectorImage::SendInvalidationNotifications() {
|
|||
// notifications there to ensure that there is actually a document observing
|
||||
// us. Otherwise, the notifications are just wasted effort.
|
||||
//
|
||||
// Non-animated images call this method directly from
|
||||
// Non-animated images post an event to call this method from
|
||||
// InvalidateObserversOnNextRefreshDriverTick, because RequestRefresh is never
|
||||
// called for them. Ordinarily this isn't needed, since we send out
|
||||
// invalidation notifications in OnSVGDocumentLoaded, but in rare cases the
|
||||
// SVG document may not be 100% ready to render at that time. In those cases
|
||||
// we would miss the subsequent invalidations if we didn't send out the
|
||||
// notifications directly in |InvalidateObservers...|.
|
||||
// notifications indirectly in |InvalidateObservers...|.
|
||||
|
||||
MOZ_ASSERT(mHasPendingInvalidation);
|
||||
mHasPendingInvalidation = false;
|
||||
SurfaceCache::RemoveImage(ImageKey(this));
|
||||
|
||||
if (UpdateImageContainer(Nothing())) {
|
||||
// If we have image containers, that means we probably won't get a Draw call
|
||||
// from the owner since they are using the container. We must assume all
|
||||
// invalidations need to be handled.
|
||||
MOZ_ASSERT(mRenderingObserver, "Should have a rendering observer by now");
|
||||
mRenderingObserver->ResumeHonoringInvalidations();
|
||||
}
|
||||
|
||||
if (mProgressTracker) {
|
||||
SurfaceCache::RemoveImage(ImageKey(this));
|
||||
mProgressTracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE,
|
||||
GetMaxSizedIntRect());
|
||||
}
|
||||
|
||||
UpdateImageContainer(Nothing());
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(IntRect)
|
||||
|
@ -1465,11 +1473,37 @@ VectorImage::OnDataAvailable(nsIRequest* aRequest, nsISupports* aCtxt,
|
|||
// Invalidation helper method
|
||||
|
||||
void VectorImage::InvalidateObserversOnNextRefreshDriverTick() {
|
||||
if (mHaveAnimations) {
|
||||
mHasPendingInvalidation = true;
|
||||
} else {
|
||||
SendInvalidationNotifications();
|
||||
if (mHasPendingInvalidation) {
|
||||
return;
|
||||
}
|
||||
|
||||
mHasPendingInvalidation = true;
|
||||
|
||||
// Animated images can wait for the refresh tick.
|
||||
if (mHaveAnimations) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-animated images won't get the refresh tick, so we should just send an
|
||||
// invalidation outside the current execution context. We need to defer
|
||||
// because the layout tree is in the middle of invalidation, and the tree
|
||||
// state needs to be consistent. Specifically only some of the frames have
|
||||
// had the NS_FRAME_DESCENDANT_NEEDS_PAINT and/or NS_FRAME_NEEDS_PAINT bits
|
||||
// set by InvalidateFrameInternal in layout/generic/nsFrame.cpp. These bits
|
||||
// get cleared when we repaint the SVG into a surface by
|
||||
// nsIFrame::ClearInvalidationStateBits in nsDisplayList::PaintRoot.
|
||||
nsCOMPtr<nsIEventTarget> eventTarget;
|
||||
if (mProgressTracker) {
|
||||
eventTarget = mProgressTracker->GetEventTarget();
|
||||
} else {
|
||||
eventTarget = do_GetMainThread();
|
||||
}
|
||||
|
||||
RefPtr<VectorImage> self(this);
|
||||
nsCOMPtr<nsIRunnable> ev(NS_NewRunnableFunction(
|
||||
"VectorImage::SendInvalidationNotifications",
|
||||
[=]() -> void { self->SendInvalidationNotifications(); }));
|
||||
eventTarget->Dispatch(ev.forget(), NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
void VectorImage::PropagateUseCounters(Document* aParentDocument) {
|
||||
|
|
|
@ -4796,6 +4796,10 @@ AbortReasonOr<Ok> IonBuilder::inlineCalls(CallInfo& callInfo,
|
|||
inlineInfo.popCallStack(inlineBlock);
|
||||
inlineInfo.setFun(funcDef);
|
||||
|
||||
if (callInfo.constructing() && callInfo.getNewTarget() == callInfo.fun()) {
|
||||
inlineInfo.setNewTarget(funcDef);
|
||||
}
|
||||
|
||||
if (maybeCache) {
|
||||
// Assign the 'this' value a TypeSet specialized to the groups that
|
||||
// can generate this inlining target.
|
||||
|
|
|
@ -1456,7 +1456,7 @@ void CodeGenerator::visitBitAndAndBranch(LBitAndAndBranch* baab) {
|
|||
} else {
|
||||
masm.Tst(toWRegister(baab->left()), toWRegister(baab->right()));
|
||||
}
|
||||
emitBranch(Assembler::NonZero, baab->ifTrue(), baab->ifFalse());
|
||||
emitBranch(baab->cond(), baab->ifTrue(), baab->ifFalse());
|
||||
}
|
||||
|
||||
void CodeGenerator::visitWasmUint32ToDouble(LWasmUint32ToDouble* lir) {
|
||||
|
|
|
@ -66,8 +66,8 @@ class WptreportHandler(object):
|
|||
"time": start_time,
|
||||
})
|
||||
|
||||
for result in result["subtests"]:
|
||||
self.formatter.test_status(result)
|
||||
for subtest in result["subtests"]:
|
||||
self.formatter.test_status(subtest)
|
||||
|
||||
self.formatter.test_end({
|
||||
"test": testname,
|
||||
|
|
|
@ -413,7 +413,6 @@ void CompilerEnvironment::computeParameters(Decoder& d,
|
|||
gcFeatureOptIn == HasGcTypes::True;
|
||||
bool argBaselineEnabled = args_->baselineEnabled || gcEnabled;
|
||||
bool argIonEnabled = args_->ionEnabled && !gcEnabled;
|
||||
bool argTestTiering = args_->testTiering && !gcEnabled;
|
||||
bool argDebugEnabled = args_->debugEnabled;
|
||||
|
||||
uint32_t codeSectionSize = 0;
|
||||
|
@ -424,11 +423,9 @@ void CompilerEnvironment::computeParameters(Decoder& d,
|
|||
}
|
||||
|
||||
// Attempt to default to ion if baseline is disabled.
|
||||
bool baselineEnabled =
|
||||
BaselineCanCompile() && (argBaselineEnabled || argTestTiering);
|
||||
bool baselineEnabled = BaselineCanCompile() && argBaselineEnabled;
|
||||
bool debugEnabled = BaselineCanCompile() && argDebugEnabled;
|
||||
bool ionEnabled =
|
||||
IonCanCompile() && (argIonEnabled || !baselineEnabled || argTestTiering);
|
||||
bool ionEnabled = IonCanCompile() && (argIonEnabled || !baselineEnabled);
|
||||
#ifdef ENABLE_WASM_CRANELIFT
|
||||
bool forceCranelift = args_->forceCranelift;
|
||||
#endif
|
||||
|
@ -437,7 +434,7 @@ void CompilerEnvironment::computeParameters(Decoder& d,
|
|||
MOZ_RELEASE_ASSERT(baselineEnabled || ionEnabled);
|
||||
|
||||
if (baselineEnabled && ionEnabled && !debugEnabled && CanUseExtraThreads() &&
|
||||
(TieringBeneficial(codeSectionSize) || argTestTiering)) {
|
||||
(TieringBeneficial(codeSectionSize) || args_->testTiering)) {
|
||||
mode_ = CompileMode::Tier1;
|
||||
tier_ = Tier::Baseline;
|
||||
} else {
|
||||
|
|
|
@ -6361,8 +6361,6 @@ static SnappedImageDrawingParameters ComputeSnappedImageDrawingParameters(
|
|||
// Snap even if we have a scale in the context. But don't snap if
|
||||
// we have something that's not translation+scale, or if the scale flips in
|
||||
// the X or Y direction, because snapped image drawing can't handle that yet.
|
||||
// Any changes to this algorithm will need to be reflected in
|
||||
// ComputeImageContainerDrawingParameters.
|
||||
if (!currentMatrix.HasNonAxisAlignedTransform() && currentMatrix._11 > 0.0 &&
|
||||
currentMatrix._22 > 0.0 && aCtx->UserToDevicePixelSnapped(fill, true) &&
|
||||
aCtx->UserToDevicePixelSnapped(dest, true)) {
|
||||
|
@ -6752,46 +6750,17 @@ static ImgDrawResult DrawImageInternal(
|
|||
}
|
||||
}
|
||||
|
||||
// Attempt to snap pixels, the same as ComputeSnappedImageDrawingParameters.
|
||||
// Any changes to the algorithm here will need to be reflected there.
|
||||
bool snapped = false;
|
||||
gfxSize gfxLayerSize;
|
||||
const gfx::Matrix& itm = aSc.GetInheritedTransform();
|
||||
if (!itm.HasNonAxisAlignedTransform() && itm._11 > 0.0 && itm._22 > 0.0) {
|
||||
gfxRect rect(gfxPoint(aDestRect.X(), aDestRect.Y()),
|
||||
gfxSize(aDestRect.Width(), aDestRect.Height()));
|
||||
// Compute our size in layer pixels. We may need to revisit this for Android
|
||||
// because mobile websites are rarely displayed at a 1:1
|
||||
// LayoutPixel:ScreenPixel ratio and the snapping here may be insufficient.
|
||||
const LayerIntSize layerSize =
|
||||
RoundedToInt(LayerSize(aDestRect.Width() * scaleFactors.width,
|
||||
aDestRect.Height() * scaleFactors.height));
|
||||
|
||||
gfxPoint p1 = ThebesPoint(itm.TransformPoint(ToPoint(rect.TopLeft())));
|
||||
gfxPoint p2 = ThebesPoint(itm.TransformPoint(ToPoint(rect.TopRight())));
|
||||
gfxPoint p3 = ThebesPoint(itm.TransformPoint(ToPoint(rect.BottomRight())));
|
||||
|
||||
if (p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y)) {
|
||||
p1.Round();
|
||||
p3.Round();
|
||||
|
||||
rect.MoveTo(gfxPoint(std::min(p1.x, p3.x), std::min(p1.y, p3.y)));
|
||||
rect.SizeTo(gfxSize(std::max(p1.x, p3.x) - rect.X(),
|
||||
std::max(p1.y, p3.y) - rect.Y()));
|
||||
|
||||
// An empty size is unacceptable so we ensure our suggested size is at
|
||||
// least 1 pixel wide/tall.
|
||||
gfxLayerSize =
|
||||
gfxSize(std::max(rect.Width(), 1.0), std::max(rect.Height(), 1.0));
|
||||
snapped = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!snapped) {
|
||||
// Compute our size in layer pixels.
|
||||
const LayerIntSize layerSize =
|
||||
RoundedToInt(LayerSize(aDestRect.Width() * scaleFactors.width,
|
||||
aDestRect.Height() * scaleFactors.height));
|
||||
|
||||
// An empty size is unacceptable so we ensure our suggested size is at least
|
||||
// 1 pixel wide/tall.
|
||||
gfxLayerSize =
|
||||
gfxSize(std::max(layerSize.width, 1), std::max(layerSize.height, 1));
|
||||
}
|
||||
// An empty size is unacceptable so we ensure our suggested size is at least
|
||||
// 1 pixel wide/tall.
|
||||
gfxSize gfxLayerSize =
|
||||
gfxSize(std::max(layerSize.width, 1), std::max(layerSize.height, 1));
|
||||
|
||||
return aImage->OptimalImageSizeForDest(
|
||||
gfxLayerSize, imgIContainer::FRAME_CURRENT, samplingFilter, aFlags);
|
||||
|
|
|
@ -102,6 +102,14 @@ static nsRect FindScrollAnchoringBoundingRect(const nsIFrame* aScrollFrame,
|
|||
}
|
||||
|
||||
nsRect localRect = aCandidate->GetScrollableOverflowRectRelativeToSelf();
|
||||
|
||||
// XXX this isn't correct with non-vertical-tb writing-mode, see bug 1520344
|
||||
if (localRect.X() < 0) {
|
||||
localRect.SetBoxX(0, localRect.XMost());
|
||||
}
|
||||
if (localRect.Y() < 0) {
|
||||
localRect.SetBoxY(0, localRect.YMost());
|
||||
}
|
||||
nsRect transformed = nsLayoutUtils::TransformFrameRectToAncestor(
|
||||
aCandidate, localRect, aScrollFrame);
|
||||
return transformed;
|
||||
|
|
|
@ -432,6 +432,8 @@ ImgDrawResult BulletRenderer::CreateWebRenderCommandsForImage(
|
|||
aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
|
||||
LayoutDeviceRect destRect =
|
||||
LayoutDeviceRect::FromAppUnits(mDest, appUnitsPerDevPixel);
|
||||
destRect.Round();
|
||||
|
||||
Maybe<SVGImageContext> svgContext;
|
||||
gfx::IntSize decodeSize =
|
||||
nsLayoutUtils::ComputeImageContainerDrawingParameters(
|
||||
|
@ -454,7 +456,7 @@ ImgDrawResult BulletRenderer::CreateWebRenderCommandsForImage(
|
|||
return drawResult;
|
||||
}
|
||||
|
||||
wr::LayoutRect dest = wr::ToRoundedLayoutRect(destRect);
|
||||
wr::LayoutRect dest = wr::ToLayoutRect(destRect);
|
||||
|
||||
aBuilder.PushImage(dest, dest, !aItem->BackfaceIsHidden(), rendering,
|
||||
key.value());
|
||||
|
|
|
@ -1618,8 +1618,10 @@ ImgDrawResult nsImageFrame::DisplayAltFeedbackWithoutLayer(
|
|||
size);
|
||||
|
||||
const int32_t factor = PresContext()->AppUnitsPerDevPixel();
|
||||
const LayoutDeviceRect destRect(
|
||||
LayoutDeviceRect destRect(
|
||||
LayoutDeviceRect::FromAppUnits(dest, factor));
|
||||
destRect.Round();
|
||||
|
||||
Maybe<SVGImageContext> svgContext;
|
||||
IntSize decodeSize =
|
||||
nsLayoutUtils::ComputeImageContainerDrawingParameters(
|
||||
|
@ -1896,8 +1898,10 @@ bool nsDisplayImage::CreateWebRenderCommands(
|
|||
}
|
||||
|
||||
const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
|
||||
const LayoutDeviceRect destRect(
|
||||
LayoutDeviceRect destRect(
|
||||
LayoutDeviceRect::FromAppUnits(GetDestRect(), factor));
|
||||
destRect.Round();
|
||||
|
||||
Maybe<SVGImageContext> svgContext;
|
||||
IntSize decodeSize = nsLayoutUtils::ComputeImageContainerDrawingParameters(
|
||||
mImage, mFrame, destRect, aSc, flags, svgContext);
|
||||
|
|
|
@ -3553,7 +3553,8 @@ ImgDrawResult nsCSSBorderImageRenderer::CreateWebRenderCommands(
|
|||
|
||||
LayoutDeviceRect destRect =
|
||||
LayoutDeviceRect::FromAppUnits(mArea, appUnitsPerDevPixel);
|
||||
wr::LayoutRect dest = wr::ToRoundedLayoutRect(destRect);
|
||||
destRect.Round();
|
||||
wr::LayoutRect dest = wr::ToLayoutRect(destRect);
|
||||
|
||||
wr::LayoutRect clip = dest;
|
||||
if (!mClip.IsEmpty()) {
|
||||
|
|
|
@ -567,6 +567,9 @@ ImgDrawResult nsImageRenderer::BuildWebRenderDisplayItems(
|
|||
mForFrame->PresContext()->AppUnitsPerDevPixel();
|
||||
LayoutDeviceRect destRect =
|
||||
LayoutDeviceRect::FromAppUnits(aDest, appUnitsPerDevPixel);
|
||||
auto stretchSize = wr::ToLayoutSize(destRect.Size());
|
||||
destRect.Round();
|
||||
|
||||
gfx::IntSize decodeSize =
|
||||
nsLayoutUtils::ComputeImageContainerDrawingParameters(
|
||||
mImageContainer, mForFrame, destRect, aSc, containerFlags,
|
||||
|
@ -600,8 +603,7 @@ ImgDrawResult nsImageRenderer::BuildWebRenderDisplayItems(
|
|||
appUnitsPerDevPixel);
|
||||
wr::LayoutRect fill = wr::ToRoundedLayoutRect(fillRect);
|
||||
|
||||
wr::LayoutRect roundedDest = wr::ToRoundedLayoutRect(destRect);
|
||||
auto stretchSize = wr::ToLayoutSize(destRect.Size());
|
||||
wr::LayoutRect roundedDest = wr::ToLayoutRect(destRect);
|
||||
|
||||
// WebRender special cases situations where stretchSize == fillSize to
|
||||
// infer that it shouldn't use repeat sampling. This makes sure
|
||||
|
|
|
@ -398,6 +398,8 @@ ImgDrawResult nsImageBoxFrame::CreateWebRenderCommands(
|
|||
const int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
|
||||
LayoutDeviceRect fillRect =
|
||||
LayoutDeviceRect::FromAppUnits(dest, appUnitsPerDevPixel);
|
||||
fillRect.Round();
|
||||
|
||||
Maybe<SVGImageContext> svgContext;
|
||||
gfx::IntSize decodeSize =
|
||||
nsLayoutUtils::ComputeImageContainerDrawingParameters(
|
||||
|
@ -420,7 +422,7 @@ ImgDrawResult nsImageBoxFrame::CreateWebRenderCommands(
|
|||
if (key.isNothing()) {
|
||||
return result;
|
||||
}
|
||||
wr::LayoutRect fill = wr::ToRoundedLayoutRect(fillRect);
|
||||
wr::LayoutRect fill = wr::ToLayoutRect(fillRect);
|
||||
|
||||
LayoutDeviceSize gapSize(0, 0);
|
||||
aBuilder.PushImage(fill, fill, !BackfaceIsHidden(),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
fails-if(Android) == textbox-multiline-noresize.xul textbox-multiline-ref.xul # reference is blank on Android (due to no native theme support?)
|
||||
!= textbox-multiline-resize.xul textbox-multiline-ref.xul
|
||||
== popup-explicit-size.xul popup-explicit-size-ref.xul
|
||||
random-if(Android) fuzzy-if(webrender,128-128,168-168) == image-size.xul image-size-ref.xul
|
||||
random-if(Android) == image-size.xul image-size-ref.xul
|
||||
== image-scaling-min-height-1.xul image-scaling-min-height-1-ref.xul
|
||||
== textbox-text-transform.xul textbox-text-transform-ref.xul
|
||||
|
||||
|
|
|
@ -3338,7 +3338,7 @@ pref("dom.ipc.plugins.asyncdrawing.enabled", true);
|
|||
pref("dom.ipc.plugins.forcedirect.enabled", true);
|
||||
|
||||
// Enable multi by default.
|
||||
#if defined(NIGHTLY_BUILD) && !defined(MOZ_ASAN)
|
||||
#if !defined(MOZ_ASAN)
|
||||
pref("dom.ipc.processCount", 8);
|
||||
#else
|
||||
pref("dom.ipc.processCount", 4);
|
||||
|
|
|
@ -24,14 +24,15 @@ XCODE_LEGACY = ('https://developer.apple.com/downloads/download.action?path=Deve
|
|||
'xcode_3.2.6_and_ios_sdk_4.3__final/xcode_3.2.6_and_ios_sdk_4.3.dmg')
|
||||
|
||||
MACPORTS_URL = {
|
||||
'13': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.3-10.13-HighSierra.pkg',
|
||||
'12': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.3-10.12-Sierra.pkg',
|
||||
'11': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.3-10.11-ElCapitan.pkg',
|
||||
'10': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.3-10.10-Yosemite.pkg',
|
||||
'9': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.3-10.9-Mavericks.pkg',
|
||||
'8': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.3-10.8-MountainLion.pkg',
|
||||
'7': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.3-10.7-Lion.pkg',
|
||||
'6': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.3-10.6-SnowLeopard.pkg', }
|
||||
'14': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.4-10.14-Mojave.pkg',
|
||||
'13': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.4-10.13-HighSierra.pkg',
|
||||
'12': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.4-10.12-Sierra.pkg',
|
||||
'11': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.4-10.11-ElCapitan.pkg',
|
||||
'10': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.4-10.10-Yosemite.pkg',
|
||||
'9': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.4-10.9-Mavericks.pkg',
|
||||
'8': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.4-10.8-MountainLion.pkg',
|
||||
'7': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.4-10.7-Lion.pkg',
|
||||
'6': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.4-10.6-SnowLeopard.pkg', }
|
||||
|
||||
RE_CLANG_VERSION = re.compile('Apple (?:clang|LLVM) version (\d+\.\d+)')
|
||||
|
||||
|
|
|
@ -10,12 +10,13 @@ import concurrent.futures as futures
|
|||
import logging
|
||||
import os
|
||||
|
||||
from taskgraph.util.taskcluster import list_task_group, cancel_task
|
||||
from taskgraph.util.taskcluster import (
|
||||
list_task_group_incomplete_tasks,
|
||||
cancel_task,
|
||||
CONCURRENCY,
|
||||
)
|
||||
from .registry import register_callback_action
|
||||
|
||||
# the maximum number of parallel cancelTask calls to make
|
||||
CONCURRENCY = 50
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -33,11 +34,14 @@ logger = logging.getLogger(__name__)
|
|||
context=[]
|
||||
)
|
||||
def cancel_all_action(parameters, graph_config, input, task_group_id, task_id, task):
|
||||
def do_cancel_task(task_id):
|
||||
logger.info('Cancelling task {}'.format(task_id))
|
||||
cancel_task(task_id, use_proxy=True)
|
||||
|
||||
own_task_id = os.environ.get('TASK_ID', '')
|
||||
to_cancel = [t for t in list_task_group_incomplete_tasks(task_group_id) if t != own_task_id]
|
||||
logger.info("Cancelling {} tasks".format(len(to_cancel)))
|
||||
with futures.ThreadPoolExecutor(CONCURRENCY) as e:
|
||||
cancels_jobs = [
|
||||
e.submit(cancel_task, t, use_proxy=True)
|
||||
for t in list_task_group(task_group_id) if t != own_task_id
|
||||
]
|
||||
for job in cancels_jobs:
|
||||
job.result()
|
||||
cancel_futs = [e.submit(do_cancel_task, t) for t in to_cancel]
|
||||
for f in futures.as_completed(cancel_futs):
|
||||
f.result()
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import concurrent.futures as futures
|
||||
import requests
|
||||
import requests.adapters
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
@ -15,12 +13,10 @@ import logging
|
|||
from slugid import nice as slugid
|
||||
from taskgraph.util.parameterization import resolve_timestamps
|
||||
from taskgraph.util.time import current_json_time
|
||||
from taskgraph.util.taskcluster import get_session, CONCURRENCY
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# the maximum number of parallel createTask calls to make
|
||||
CONCURRENCY = 50
|
||||
|
||||
# this is set to true for `mach taskgraph action-callback --test`
|
||||
testing = False
|
||||
|
||||
|
@ -28,16 +24,6 @@ testing = False
|
|||
def create_tasks(taskgraph, label_to_taskid, params, decision_task_id=None):
|
||||
taskid_to_label = {t: l for l, t in label_to_taskid.iteritems()}
|
||||
|
||||
session = requests.Session()
|
||||
|
||||
# Default HTTPAdapter uses 10 connections. Mount custom adapter to increase
|
||||
# that limit. Connections are established as needed, so using a large value
|
||||
# should not negatively impact performance.
|
||||
http_adapter = requests.adapters.HTTPAdapter(pool_connections=CONCURRENCY,
|
||||
pool_maxsize=CONCURRENCY)
|
||||
session.mount('https://', http_adapter)
|
||||
session.mount('http://', http_adapter)
|
||||
|
||||
decision_task_id = decision_task_id or os.environ.get('TASK_ID')
|
||||
|
||||
# when running as an actual decision task, we use the decision task's
|
||||
|
@ -66,6 +52,7 @@ def create_tasks(taskgraph, label_to_taskid, params, decision_task_id=None):
|
|||
|
||||
# If `testing` is True, then run without parallelization
|
||||
concurrency = CONCURRENCY if not testing else 1
|
||||
session = get_session()
|
||||
with futures.ThreadPoolExecutor(concurrency) as e:
|
||||
fs = {}
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ import logging
|
|||
import taskcluster_urls as liburls
|
||||
from mozbuild.util import memoize
|
||||
from requests.packages.urllib3.util.retry import Retry
|
||||
from requests.adapters import HTTPAdapter
|
||||
from taskgraph.task import Task
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -27,6 +26,9 @@ testing = False
|
|||
# to the production Taskcluster deployment used for CI.
|
||||
PRODUCTION_TASKCLUSTER_ROOT_URL = 'https://taskcluster.net'
|
||||
|
||||
# the maximum number of parallel Taskcluster API calls to make
|
||||
CONCURRENCY = 50
|
||||
|
||||
|
||||
@memoize
|
||||
def get_root_url():
|
||||
|
@ -48,10 +50,20 @@ def get_root_url():
|
|||
@memoize
|
||||
def get_session():
|
||||
session = requests.Session()
|
||||
|
||||
retry = Retry(total=5, backoff_factor=0.1,
|
||||
status_forcelist=[500, 502, 503, 504])
|
||||
session.mount('http://', HTTPAdapter(max_retries=retry))
|
||||
session.mount('https://', HTTPAdapter(max_retries=retry))
|
||||
|
||||
# Default HTTPAdapter uses 10 connections. Mount custom adapter to increase
|
||||
# that limit. Connections are established as needed, so using a large value
|
||||
# should not negatively impact performance.
|
||||
http_adapter = requests.adapters.HTTPAdapter(
|
||||
pool_connections=CONCURRENCY,
|
||||
pool_maxsize=CONCURRENCY,
|
||||
max_retries=retry)
|
||||
session.mount('https://', http_adapter)
|
||||
session.mount('http://', http_adapter)
|
||||
|
||||
return session
|
||||
|
||||
|
||||
|
@ -60,7 +72,7 @@ def _do_request(url, force_get=False, **kwargs):
|
|||
if kwargs and not force_get:
|
||||
response = session.post(url, **kwargs)
|
||||
else:
|
||||
response = session.get(url, stream=True)
|
||||
response = session.get(url, stream=True, **kwargs)
|
||||
if response.status_code >= 400:
|
||||
# Consume content before raise_for_status, so that the connection can be
|
||||
# reused.
|
||||
|
@ -268,8 +280,8 @@ def send_email(address, subject, content, link, use_proxy=False):
|
|||
})
|
||||
|
||||
|
||||
def list_task_group(task_group_id):
|
||||
"""Generate the tasks in a task group"""
|
||||
def list_task_group_incomplete_tasks(task_group_id):
|
||||
"""Generate the incomplete tasks in a task group"""
|
||||
params = {}
|
||||
while True:
|
||||
url = liburls.api(get_root_url(), 'queue', 'v1',
|
||||
|
|
|
@ -990,7 +990,7 @@ SimpleTest.promiseClipboardChange = async function(aExpectedStringOrValidatorFn,
|
|||
let maxPolls = aTimeout ? aTimeout / 100 : 50;
|
||||
|
||||
async function putAndVerify(operationFn, validatorFn, flavor) {
|
||||
operationFn();
|
||||
await operationFn();
|
||||
|
||||
let data;
|
||||
for (let i = 0; i < maxPolls; i++) {
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
[timeouts.py]
|
||||
disabled: if ccov and (os == "win") and (bits == 64) and (version == "10.0.15063"): https://bugzilla.mozilla.org/show_bug.cgi?id=1495002
|
|
@ -10,6 +10,16 @@ MozElements.RichListBox = class RichListBox extends MozElements.BaseControl {
|
|||
constructor() {
|
||||
super();
|
||||
|
||||
this.selectedItems = new ChromeNodeList();
|
||||
this._currentIndex = null;
|
||||
this._lastKeyTime = 0;
|
||||
this._incrementalString = "";
|
||||
this._suppressOnSelect = false;
|
||||
this._userSelecting = false;
|
||||
this._selectTimeout = null;
|
||||
this._currentItem = null;
|
||||
this._selectionStart = null;
|
||||
|
||||
this.addEventListener("keypress", event => {
|
||||
if (event.altKey || event.metaKey) {
|
||||
return;
|
||||
|
@ -135,17 +145,6 @@ MozElements.RichListBox = class RichListBox extends MozElements.BaseControl {
|
|||
}
|
||||
|
||||
this.setAttribute("allowevents", "true");
|
||||
|
||||
this.selectedItems = new ChromeNodeList();
|
||||
this._currentIndex = null;
|
||||
this._lastKeyTime = 0;
|
||||
this._incrementalString = "";
|
||||
this._suppressOnSelect = false;
|
||||
this._userSelecting = false;
|
||||
this._selectTimeout = null;
|
||||
this._currentItem = null;
|
||||
this._selectionStart = null;
|
||||
|
||||
this._refreshSelection();
|
||||
}
|
||||
|
||||
|
|
|
@ -281,8 +281,7 @@ extensions-updates-update-selected =
|
|||
shortcuts-manage =
|
||||
.label = Keyboard Shortcuts
|
||||
shortcuts-empty-message = There are no shortcuts for this extension.
|
||||
# TODO: Confirm this copy.
|
||||
shortcuts-no-addons = You don't have any active add-ons.
|
||||
shortcuts-no-addons = You don’t have any extensions enabled.
|
||||
shortcuts-input =
|
||||
.placeholder = Type a shortcut
|
||||
|
||||
|
|
|
@ -97,6 +97,21 @@ void UserTimingMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter,
|
|||
}
|
||||
}
|
||||
|
||||
void TextMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
const TimeStamp& aProcessStartTime,
|
||||
UniqueStacks& aUniqueStacks) {
|
||||
StreamCommonProps("Text", aWriter, aProcessStartTime, aUniqueStacks);
|
||||
aWriter.StringProperty("name", mText.get());
|
||||
}
|
||||
|
||||
void LogMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
const TimeStamp& aProcessStartTime,
|
||||
UniqueStacks& aUniqueStacks) {
|
||||
StreamCommonProps("Log", aWriter, aProcessStartTime, aUniqueStacks);
|
||||
aWriter.StringProperty("name", mText.get());
|
||||
aWriter.StringProperty("module", mModule.get());
|
||||
}
|
||||
|
||||
void DOMEventMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
const TimeStamp& aProcessStartTime,
|
||||
UniqueStacks& aUniqueStacks) {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/UniquePtrExtensions.h"
|
||||
#include "mozilla/net/TimingStruct.h"
|
||||
|
||||
|
@ -359,4 +360,53 @@ class LongTaskMarkerPayload : public ProfilerMarkerPayload {
|
|||
DECL_STREAM_PAYLOAD
|
||||
};
|
||||
|
||||
class TextMarkerPayload : public ProfilerMarkerPayload {
|
||||
public:
|
||||
TextMarkerPayload(const nsACString& aText,
|
||||
const mozilla::TimeStamp& aStartTime)
|
||||
: ProfilerMarkerPayload(aStartTime, aStartTime), mText(aText) {}
|
||||
|
||||
TextMarkerPayload(const nsACString& aText,
|
||||
const mozilla::TimeStamp& aStartTime,
|
||||
const mozilla::TimeStamp& aEndTime)
|
||||
: ProfilerMarkerPayload(aStartTime, aEndTime), mText(aText) {}
|
||||
|
||||
TextMarkerPayload(const nsACString& aText,
|
||||
const mozilla::TimeStamp& aStartTime,
|
||||
const mozilla::Maybe<nsID>& aDocShellId,
|
||||
const mozilla::Maybe<uint32_t>& aDocShellHistoryId)
|
||||
: ProfilerMarkerPayload(aStartTime, aStartTime, aDocShellId,
|
||||
aDocShellHistoryId),
|
||||
mText(aText) {}
|
||||
|
||||
TextMarkerPayload(const nsACString& aText,
|
||||
const mozilla::TimeStamp& aStartTime,
|
||||
const mozilla::TimeStamp& aEndTime,
|
||||
const mozilla::Maybe<nsID>& aDocShellId,
|
||||
const mozilla::Maybe<uint32_t>& aDocShellHistoryId)
|
||||
: ProfilerMarkerPayload(aStartTime, aEndTime, aDocShellId,
|
||||
aDocShellHistoryId),
|
||||
mText(aText) {}
|
||||
|
||||
DECL_STREAM_PAYLOAD
|
||||
|
||||
private:
|
||||
nsCString mText;
|
||||
};
|
||||
|
||||
class LogMarkerPayload : public ProfilerMarkerPayload {
|
||||
public:
|
||||
LogMarkerPayload(const char* aModule, const char* aText,
|
||||
const mozilla::TimeStamp& aStartTime)
|
||||
: ProfilerMarkerPayload(aStartTime, aStartTime),
|
||||
mModule(aModule),
|
||||
mText(aText) {}
|
||||
|
||||
DECL_STREAM_PAYLOAD
|
||||
|
||||
private:
|
||||
nsAutoCStringN<32> mModule; // longest known LazyLogModule name is ~24
|
||||
nsCString mText;
|
||||
};
|
||||
|
||||
#endif // ProfilerMarkerPayload_h
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
#include "nsDebugImpl.h"
|
||||
#include "NSPRLogModulesParser.h"
|
||||
#include "LogCommandLineHandler.h"
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
#include "ProfilerMarkerPayload.h"
|
||||
#endif
|
||||
|
||||
#include "prenv.h"
|
||||
#ifdef XP_WIN
|
||||
|
@ -166,6 +169,7 @@ class LogModuleManager {
|
|||
mMainThread(PR_GetCurrentThread()),
|
||||
mSetFromEnv(false),
|
||||
mAddTimestamp(false),
|
||||
mAddProfilerMarker(false),
|
||||
mIsRaw(false),
|
||||
mIsSync(false),
|
||||
mRotate(0),
|
||||
|
@ -208,6 +212,7 @@ class LogModuleManager {
|
|||
bool addTimestamp = false;
|
||||
bool isSync = false;
|
||||
bool isRaw = false;
|
||||
bool isMarkers = false;
|
||||
int32_t rotate = 0;
|
||||
const char* modules = PR_GetEnv("MOZ_LOG");
|
||||
if (!modules || !modules[0]) {
|
||||
|
@ -230,9 +235,9 @@ class LogModuleManager {
|
|||
// Need to capture `this` since `sLogModuleManager` is not set until after
|
||||
// initialization is complete.
|
||||
NSPRLogModulesParser(
|
||||
modules,
|
||||
[this, &shouldAppend, &addTimestamp, &isSync, &isRaw, &rotate](
|
||||
const char* aName, LogLevel aLevel, int32_t aValue) mutable {
|
||||
modules, [this, &shouldAppend, &addTimestamp, &isSync, &isRaw, &rotate,
|
||||
&isMarkers](const char* aName, LogLevel aLevel,
|
||||
int32_t aValue) mutable {
|
||||
if (strcmp(aName, "append") == 0) {
|
||||
shouldAppend = true;
|
||||
} else if (strcmp(aName, "timestamp") == 0) {
|
||||
|
@ -243,6 +248,8 @@ class LogModuleManager {
|
|||
isRaw = true;
|
||||
} else if (strcmp(aName, "rotate") == 0) {
|
||||
rotate = (aValue << 20) / kRotateFilesNumber;
|
||||
} else if (strcmp(aName, "profilermarkers") == 0) {
|
||||
isMarkers = true;
|
||||
} else {
|
||||
this->CreateOrGetModule(aName)->SetLevel(aLevel);
|
||||
}
|
||||
|
@ -253,6 +260,7 @@ class LogModuleManager {
|
|||
mIsSync = isSync;
|
||||
mIsRaw = isRaw;
|
||||
mRotate = rotate;
|
||||
mAddProfilerMarker = isMarkers;
|
||||
|
||||
if (rotate > 0 && shouldAppend) {
|
||||
NS_WARNING("MOZ_LOG: when you rotate the log, you cannot use append!");
|
||||
|
@ -401,6 +409,14 @@ class LogModuleManager {
|
|||
charsWritten = strlen(buffToWrite);
|
||||
}
|
||||
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
if (mAddProfilerMarker && profiler_is_active()) {
|
||||
profiler_add_marker(
|
||||
"LogMessages",
|
||||
MakeUnique<LogMarkerPayload>(aName, buffToWrite, TimeStamp::Now()));
|
||||
}
|
||||
#endif
|
||||
|
||||
// Determine if a newline needs to be appended to the message.
|
||||
const char* newline = "";
|
||||
if (charsWritten == 0 || buffToWrite[charsWritten - 1] != '\n') {
|
||||
|
@ -524,6 +540,7 @@ class LogModuleManager {
|
|||
PRThread* mMainThread;
|
||||
bool mSetFromEnv;
|
||||
Atomic<bool, Relaxed> mAddTimestamp;
|
||||
Atomic<bool, Relaxed> mAddProfilerMarker;
|
||||
Atomic<bool, Relaxed> mIsRaw;
|
||||
Atomic<bool, Relaxed> mIsSync;
|
||||
int32_t mRotate;
|
||||
|
|
Загрузка…
Ссылка в новой задаче