Commit 5c87a00f authored by Carsten Schoenert's avatar Carsten Schoenert
Browse files

New upstream version 91.0

parent e28b2f91
......@@ -1820,8 +1820,16 @@ LayoutDeviceIntRect HyperTextAccessible::GetCaretRect(nsIWidget** aWidget) {
// the character. This is important for font size transitions, and is
// necessary because the Gecko caret uses the previous character's size as
// the user moves forward in the text by character.
int32_t caretOffset = CaretOffset();
if (NS_WARN_IF(caretOffset == -1)) {
// The caret offset will be -1 if this Accessible isn't focused. Note that
// the DOM node contaning the caret might be focused, but the Accessible
// might not be; e.g. due to an autocomplete popup suggestion having a11y
// focus.
return LayoutDeviceIntRect();
}
nsIntRect charRect = CharBounds(
CaretOffset(), nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE);
caretOffset, nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE);
if (!charRect.IsEmpty()) {
caretRect.SetTopEdge(charRect.Y());
}
......
......@@ -23,6 +23,7 @@ support-files =
[test_flush.html]
[test_focusable_statechange.html]
[test_focus_aria_activedescendant.html]
[test_focus_autocomplete.html]
[test_focus_autocomplete.xhtml]
# Disabled on Linux and Windows due to frequent failures - bug 695019, bug 890795
skip-if = os == 'win' || os == 'linux'
......
<!doctype html>
<head>
<title>Form Autocomplete Tests</title>
<link rel="stylesheet"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script src="../common.js"></script>
<script src="../promisified-events.js"></script>
<script src="../role.js"></script>
<script type="application/javascript">
const { TestUtils } = ChromeUtils.import(
"resource://testing-common/TestUtils.jsm");
async function waitForFocusOnOptionWithname(name) {
let event = await waitForEvent(
EVENT_FOCUS,
evt => evt.accessible.role == ROLE_COMBOBOX_OPTION
);
if (!event.accessible.name) {
// Sometimes, the name is null for a very short time after the focus
// event.
await waitForEvent(EVENT_NAME_CHANGE, event.accessible);
}
is(event.accessible.name, name, "Got focus on option with name " + name);
}
async function doTests() {
const input = getNode("input");
info("Focusing the input");
let focused = waitForEvent(EVENT_FOCUS, input);
input.focus();
await focused;
let shown = waitForEvent(EVENT_SHOW, event =>
event.accessible.role == ROLE_GROUPING &&
event.accessible.firstChild.role == ROLE_COMBOBOX_LIST);
info("Pressing ArrowDown to open the popup");
synthesizeKey("KEY_ArrowDown");
await shown;
// The popup still doesn't seem to be ready even once it's fired an a11y
// show event!
const controller = Cc["@mozilla.org/autocomplete/controller;1"].
getService(Ci.nsIAutoCompleteController);
info("Waiting for popup to be fully open and ready");
await TestUtils.waitForCondition(() => controller.input.popupOpen);
focused = waitForFocusOnOptionWithname("a");
info("Pressing ArrowDown to focus first item");
synthesizeKey("KEY_ArrowDown");
await focused;
focused = waitForFocusOnOptionWithname("b");
info("Pressing ArrowDown to focus the second item");
synthesizeKey("KEY_ArrowDown");
await focused;
focused = waitForEvent(EVENT_FOCUS, input);
info("Pressing enter to select the second item");
synthesizeKey("KEY_Enter");
await focused;
is(input.value, "b", "input value filled with second item");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTests);
</script>
</head>
<body>
<input id="input" list="list">
<datalist id="list">
<option id="a" value="a">
<option id="b" value="b">
</datalist>
</body>
</html>
......@@ -67,10 +67,10 @@ bool XULColumnItemAccessible::DoAction(uint8_t aIndex) const {
XULListboxAccessible::XULListboxAccessible(nsIContent* aContent,
DocAccessible* aDoc)
: XULSelectControlAccessible(aContent, aDoc) {
nsIContent* parentContent = mContent->GetFlattenedTreeParent();
if (parentContent) {
dom::Element* parentEl = mContent->GetParentElement();
if (parentEl) {
nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm =
parentContent->AsElement()->AsAutoCompletePopup();
parentEl->AsAutoCompletePopup();
if (autoCompletePopupElm) mGenericTypes |= eAutoCompletePopup;
}
......
......@@ -78,10 +78,14 @@ static inline mozilla::LauncherResult<bool> IsSameBinaryAsParentProcess(
FALSE, parentPid.unwrap()));
if (!parentProcess.get()) {
DWORD err = ::GetLastError();
if (err == ERROR_INVALID_PARAMETER) {
// The process identified by parentPid has already exited. This is a
// common case when the parent process is not Firefox, thus we should
// return false instead of erroring out.
if (err == ERROR_INVALID_PARAMETER || err == ERROR_ACCESS_DENIED) {
// In the ERROR_INVALID_PARAMETER case, the process identified by
// parentPid has already exited. This is a common case when the parent
// process is not Firefox, thus we should return false instead of erroring
// out.
// The ERROR_ACCESS_DENIED case can happen when the parent process is
// something that we don't have permission to query. For example, we may
// encounter this when Firefox is launched by the Windows Task Scheduler.
return false;
}
......
......@@ -319,21 +319,23 @@ this.devtools = class extends ExtensionAPI {
/* eslint-disable mozilla/balanced-listeners */
extension.on("add-permissions", (ignoreEvent, permissions) => {
Services.prefs.setBoolPref(
`${getDevToolsPrefBranchName(extension.id)}.enabled`,
true
);
if (permissions.permissions.includes("devtools")) {
Services.prefs.setBoolPref(
`${getDevToolsPrefBranchName(extension.id)}.enabled`,
true
);
this._initialize();
}
});
extension.on("remove-permissions", (ignoreEvent, permissions) => {
Services.prefs.setBoolPref(
`${getDevToolsPrefBranchName(extension.id)}.enabled`,
false
);
if (permissions.permissions.includes("devtools")) {
Services.prefs.setBoolPref(
`${getDevToolsPrefBranchName(extension.id)}.enabled`,
false
);
this._uninitialize();
}
});
......
......@@ -7,10 +7,11 @@ loadTestSubscript("head_devtools.js");
/**
* This test file ensures that:
*
* - the devtools_page property creates a new WebExtensions context
* - the devtools_page can exchange messages with the background page
* - "devtools" permission can be used as an optional permission
* - the extension devtools page and panels are not disabled/enabled on changes
* to unrelated optional permissions.
*/
add_task(async function test_devtools_page_runtime_api_messaging() {
add_task(async function test_devtools_optional_permission() {
Services.prefs.setBoolPref(
"extensions.webextOptionalPermissionPrompts",
false
......@@ -25,8 +26,7 @@ add_task(async function test_devtools_page_runtime_api_messaging() {
);
function background() {
let perm = { permissions: ["devtools"], origins: [] };
browser.test.onMessage.addListener(async (msg, sender) => {
browser.test.onMessage.addListener(async (msg, perm) => {
if (msg === "request") {
let granted = await new Promise(resolve => {
browser.test.withHandlingUserInput(() => {
......@@ -36,7 +36,7 @@ add_task(async function test_devtools_page_runtime_api_messaging() {
browser.test.assertTrue(granted, "permission request succeeded");
browser.test.sendMessage("done");
} else if (msg === "revoke") {
browser.permissions.remove(perm);
await browser.permissions.remove(perm);
browser.test.sendMessage("done");
}
});
......@@ -49,7 +49,7 @@ add_task(async function test_devtools_page_runtime_api_messaging() {
let extension = ExtensionTestUtils.loadExtension({
background,
manifest: {
optional_permissions: ["devtools"],
optional_permissions: ["devtools", "*://mochi.test/*"],
devtools_page: "devtools_page.html",
},
files: {
......@@ -68,25 +68,47 @@ add_task(async function test_devtools_page_runtime_api_messaging() {
await extension.startup();
function checkEnabled(expect = false) {
function checkEnabled(expect = false, { expectIsUserSet = true } = {}) {
const prefName = `devtools.webextensions.${extension.id}.enabled`;
Assert.equal(
expect,
Services.prefs.getBoolPref(
`devtools.webextensions.${extension.id}.enabled`,
false
),
"devtools enabled pref is correct"
Services.prefs.getBoolPref(prefName, false),
`Got the expected value set on pref ${prefName}`
);
Assert.equal(
expectIsUserSet,
Services.prefs.prefHasUserValue(prefName),
`pref "${prefName}" ${
expectIsUserSet ? "should" : "should not"
} be user set`
);
}
checkEnabled(false);
checkEnabled(false, { expectIsUserSet: false });
// Open the devtools first, then request permission
info("Open the developer toolbox");
await openToolboxForTab(tab);
assertDevToolsExtensionEnabled(extension.uuid, false);
extension.sendMessage("request");
info(
"Request unrelated permission, expect devtools page and panel to be disabled"
);
extension.sendMessage("request", {
permissions: [],
origins: ["*://mochi.test/*"],
});
await extension.awaitMessage("done");
checkEnabled(false, { expectIsUserSet: false });
info(
"Request devtools permission, expect devtools page and panel to be enabled"
);
extension.sendMessage("request", {
permissions: ["devtools"],
origins: [],
});
await extension.awaitMessage("done");
checkEnabled(true);
......@@ -94,13 +116,29 @@ add_task(async function test_devtools_page_runtime_api_messaging() {
await extension.awaitMessage("devtools_page_loaded");
assertDevToolsExtensionEnabled(extension.uuid, true);
info(
"Revoke unrelated permission, expect devtools page and panel to stay enabled"
);
extension.sendMessage("revoke", {
permissions: [],
origins: ["*://mochi.test/*"],
});
await extension.awaitMessage("done");
checkEnabled(true);
info(
"Revoke devtools permission, expect devtools page and panel to be destroyed"
);
let policy = WebExtensionPolicy.getByID(extension.id);
let closed = new Promise(resolve => {
// eslint-disable-next-line mozilla/balanced-listeners
policy.extension.on("devtools-page-shutdown", resolve);
});
extension.sendMessage("revoke");
extension.sendMessage("revoke", {
permissions: ["devtools"],
origins: [],
});
await extension.awaitMessage("done");
await closed;
......@@ -110,7 +148,10 @@ add_task(async function test_devtools_page_runtime_api_messaging() {
info("Close the developer toolbox");
await closeToolboxForTab(tab);
extension.sendMessage("request");
extension.sendMessage("request", {
permissions: ["devtools"],
origins: [],
});
await extension.awaitMessage("done");
info("Open the developer toolbox");
......
......@@ -22,6 +22,7 @@ async function testManifest(manifest, expectedError) {
}`
);
}
return normalized.errors;
}
const all_actions = [
......@@ -75,16 +76,7 @@ add_task(async function test_manifest() {
add_task(async function test_action_version() {
// The above test validates these work with the correct version,
// here we verify they fail with the incorrect version.
testManifest(
{
manifest_version: 2,
action: {
default_panel: "foo.html",
},
},
/Property "action" is unsupported in Manifest Version 2/
);
// here we verify they fail with the incorrect version for MV3.
testManifest(
{
manifest_version: 3,
......@@ -94,4 +86,29 @@ add_task(async function test_action_version() {
},
/Property "browser_action" is unsupported in Manifest Version 3/
);
// But we still allow previously ignored keys in MV2, just warn about them.
ExtensionTestUtils.failOnSchemaWarnings(false);
let warnings = await testManifest({
manifest_version: 2,
action: {
default_icon: "",
default_panel: "foo.html",
},
});
equal(warnings.length, 2, "Got exactly two warnings");
equal(
warnings[0],
`Property "action" is unsupported in Manifest Version 2`,
`Manifest v2 with "action" key first warning is clear.`
);
equal(
warnings[1],
"Warning processing action: An unexpected property was found in the WebExtension manifest.",
`Manifest v2 with "action" key second warning has more details.`
);
ExtensionTestUtils.failOnSchemaWarnings(true);
});
......@@ -50,6 +50,8 @@ Please note that some targeting attributes require stricter controls on the tele
* [activeNotifications](#activenotifications)
* [isMajorUpgrade](#ismajorupgrade)
* [hasActiveEnterprisePolicies](#hasactiveenterprisepolicies)
* [mainPingSubmissions](#mainpingsubmissions)
* [userMonthlyActivity](#usermonthlyactivity)
## Detailed usage
......@@ -816,3 +818,23 @@ A boolean. `true` if the browser just updated to a new major version.
### `hasActiveEnterprisePolicies`
A boolean. `true` if any Enterprise Policies are active.
### `mainPingSubmissions`
Filter through the local telemetry pings archive submitted and select the `main`
pings sent at least 24 hours apart. Result is sorted in ascending order.
```javascript
interface MainTelemetryPing {
id: string,
type: "main",
timestampCreated: number,
}
declare const mainPingSubmissions: Promise<MainTelemetryPing[]>
```
### `userMonthlyActivity`
Returns an array of entries in the form `[int, unixTimestamp]` for each day of
user activity where the first entry is the total urls visited for that day.
......@@ -28,6 +28,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
HomePage: "resource:///modules/HomePage.jsm",
AboutNewTab: "resource:///modules/AboutNewTab.jsm",
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
TelemetryArchive: "resource://gre/modules/TelemetryArchive.jsm",
});
XPCOMUtils.defineLazyPreferenceGetter(
......@@ -243,6 +244,7 @@ const QueryCache = {
CheckBrowserNeedsUpdate: new CheckBrowserNeedsUpdate(),
RecentBookmarks: new CachedTargetingGetter("getRecentBookmarks"),
ListAttachedOAuthClients: new CacheListAttachedOAuthClients(),
UserMonthlyActivity: new CachedTargetingGetter("getUserMonthlyActivity"),
},
};
......@@ -634,6 +636,38 @@ const TargetingGetters = {
get hasActiveEnterprisePolicies() {
return Services.policies.status === Services.policies.ACTIVE;
},
get mainPingSubmissions() {
return (
TelemetryArchive.promiseArchivedPingList()
// Filter out non-main pings. Do it before so we compare timestamps
// between pings of same type.
.then(pings => pings.filter(p => p.type === "main"))
.then(pings => {
if (pings.length <= 1) {
return pings;
}
// Pings are returned in ascending order.
return pings.reduce(
(acc, ping) => {
if (
// Keep only main pings sent a day (or more) apart
new Date(ping.timestampCreated).toDateString() !==
new Date(acc[acc.length - 1].timestampCreated).toDateString()
) {
acc.push(ping);
}
return acc;
},
[pings[0]]
);
})
);
},
get userMonthlyActivity() {
return QueryCache.queries.UserMonthlyActivity.get();
},
};
this.ASRouterTargeting = {
......
......@@ -1103,3 +1103,10 @@ add_task(async function check_is_major_upgrade() {
"Should select the message"
);
});
add_task(async function check_userMonthlyActivity() {
ok(
Array.isArray(await ASRouterTargeting.Environment.userMonthlyActivity),
"value is an array"
);
});
......@@ -273,6 +273,103 @@ describe("#CacheListAttachedOAuthClients", () => {
assert.calledOnce(fxAccounts.listAttachedOAuthClients);
});
});
describe("#mainPingSubmissions", () => {
let promiseArchivedPingList;
let globals;
let sandbox;
beforeEach(() => {
sandbox = sinon.createSandbox();
globals = new GlobalOverrider();
});
afterEach(() => {
sandbox.restore();
globals.restore();
});
it("should return an empty list", async () => {
promiseArchivedPingList = sandbox.stub().resolves([]);
globals.set("TelemetryArchive", { promiseArchivedPingList });
assert.typeOf(
await ASRouterTargeting.Environment.mainPingSubmissions,
"array",
"we get back an array"
);
assert.lengthOf(
await ASRouterTargeting.Environment.mainPingSubmissions,
0,
"no pings available"
);
});
it("should filter out bhr pings", async () => {
promiseArchivedPingList = sandbox.stub().resolves([
{
id: "5c8c786b-eca5-734b-a755-7ec0f022aaaf",
timestampCreated: 1622525975674,
type: "bhr",
},
]);
globals.set("TelemetryArchive", { promiseArchivedPingList });
assert.lengthOf(
await ASRouterTargeting.Environment.mainPingSubmissions,
0,
"no `main` pings available"
);
});
it("should filter out pings less than 24hrs apart", async () => {
let startTime = 0;
promiseArchivedPingList = sandbox.stub().resolves([
{
id: "5c8c786b-eca5-734b-a755-7ec0f022aaaf",
timestampCreated: 1622525975674,
type: "bhr",
},
{
id: "5c8c786b-eca5-734b-a755-7ec0f022aaaa",
timestampCreated: startTime,
type: "main",
},
{
id: "5c8c786b-eca5-734b-a755-7ec0f022aaaa",
timestampCreated: startTime + 1000,
type: "main",
},
{
id: "5c8c786b-eca5-734b-a755-7ec0f022aaac",
timestampCreated: startTime + 86400001,
type: "main",
},
]);
globals.set("TelemetryArchive", { promiseArchivedPingList });
assert.lengthOf(
await ASRouterTargeting.Environment.mainPingSubmissions,
2,
"1 main ping is removed"
);
});
it("should allow for pings < 24hrs apart but on different days", async () => {
let startTime = new Date("2020-02-20").getTime();
let oneDay = 86400000;
promiseArchivedPingList = sandbox.stub().resolves([
{
id: "5c8c786b-eca5-734b-a755-7ec0f022aaaa",
// Using oneDay / 2 because timezone of browser running the test
// affects the calculation
timestampCreated: startTime - oneDay / 2,
type: "main",
},
{
id: "5c8c786b-eca5-734b-a755-7ec0f022aaac",
timestampCreated: startTime + 1000,
type: "main",
},
]);
globals.set("TelemetryArchive", { promiseArchivedPingList });
assert.lengthOf(
await ASRouterTargeting.Environment.mainPingSubmissions,
2,
"pings are less day oneDay apart but fall on different days"
);
});
});
describe("ASRouterTargeting", () => {
let evalStub;
let sandbox;
......
......@@ -22,6 +22,7 @@ skip-if =
os == "win" && debug && bits == 32 # fails on win10-32/debug
[browser_netInfo.js]
[browser_performanceAPI.js]
[browser_performanceAPIWorkers.js]
[browser_roundedWindow_dialogWindow.js]
[browser_roundedWindow_newWindow.js]
[browser_roundedWindow_open_max_inner.js]
......