Bug 1415408 - send 'a11y-consumers-changed' notifcation whenever accessibility services consumers change. r=surkov

MozReview-Commit-ID: 2V4X4AO3JAT
This commit is contained in:
Yura Zenevich 2017-11-07 23:07:24 -05:00
Родитель 446a07beed
Коммит aa6a1a9b17
5 изменённых файлов: 116 добавлений и 7 удалений

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

@ -25,6 +25,7 @@
#include "nsAttrName.h"
#include "nsEventShell.h"
#include "nsIURI.h"
#include "nsTextFormatter.h"
#include "OuterDocAccessible.h"
#include "Role.h"
#ifdef MOZ_ACCESSIBILITY_ATK
@ -1397,8 +1398,7 @@ nsAccessibilityService::Shutdown()
// if someone will try to operate with it.
MOZ_ASSERT(gConsumers, "Accessibility was shutdown already");
gConsumers = 0;
UnsetConsumers(eXPCOM | eMainProcess | ePlatformAPI);
// Remove observers.
nsCOMPtr<nsIObserverService> observerService =
@ -1856,6 +1856,47 @@ nsAccessibilityService::CreateAccessibleForXULTree(nsIContent* aContent,
}
#endif
void
nsAccessibilityService::SetConsumers(uint32_t aConsumers) {
if (gConsumers & aConsumers) {
return;
}
gConsumers |= aConsumers;
NotifyOfConsumersChange();
}
void
nsAccessibilityService::UnsetConsumers(uint32_t aConsumers) {
if (!(gConsumers & aConsumers)) {
return;
}
gConsumers &= ~aConsumers;
NotifyOfConsumersChange();
}
void
nsAccessibilityService::NotifyOfConsumersChange()
{
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (!observerService) {
return;
}
const char16_t* kJSONFmt =
u"{ \"XPCOM\": %s, \"MainProcess\": %s, \"PlatformAPI\": %s }";
nsString json;
nsTextFormatter::ssprintf(json, kJSONFmt,
gConsumers & eXPCOM ? "true" : "false",
gConsumers & eMainProcess ? "true" : "false",
gConsumers & ePlatformAPI ? "true" : "false");
observerService->NotifyObservers(
nullptr, "a11y-consumers-changed", json.get());
}
nsAccessibilityService*
GetOrCreateAccService(uint32_t aNewConsumer)
{
@ -1869,7 +1910,7 @@ GetOrCreateAccService(uint32_t aNewConsumer)
MOZ_ASSERT(nsAccessibilityService::gAccessibilityService,
"Accessible service is not initialized.");
nsAccessibilityService::gConsumers |= aNewConsumer;
nsAccessibilityService::gAccessibilityService->SetConsumers(aNewConsumer);
return nsAccessibilityService::gAccessibilityService;
}
@ -1887,14 +1928,15 @@ MaybeShutdownAccService(uint32_t aFormerConsumer)
xpcAccessibilityService::IsInUse() ||
accService->HasXPCDocuments()) {
// Still used by XPCOM
nsAccessibilityService::gConsumers =
(nsAccessibilityService::gConsumers & ~aFormerConsumer) |
nsAccessibilityService::eXPCOM;
if (aFormerConsumer != nsAccessibilityService::eXPCOM) {
// Only unset non-XPCOM consumers.
accService->UnsetConsumers(aFormerConsumer);
}
return;
}
if (nsAccessibilityService::gConsumers & ~aFormerConsumer) {
nsAccessibilityService::gConsumers &= ~aFormerConsumer;
accService->UnsetConsumers(aFormerConsumer);
} else {
accService->Shutdown(); // Will unset all nsAccessibilityService::gConsumers
}

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

@ -304,6 +304,21 @@ private:
CreateAccessibleByFrameType(nsIFrame* aFrame, nsIContent* aContent,
Accessible* aContext);
/**
* Notify observers about change of the accessibility service's consumers.
*/
void NotifyOfConsumersChange();
/**
* Set accessibility service consumers.
*/
void SetConsumers(uint32_t aConsumers);
/**
* Unset accessibility service consumers.
*/
void UnsetConsumers(uint32_t aConsumers);
#ifdef MOZ_XUL
/**
* Create accessible for XUL tree element.

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

@ -25,22 +25,40 @@ add_task(async function() {
// the a11y service in parent as well.
let parentA11yInit = initPromise();
let contentA11yInit = initPromise(browser);
let parentConsumersChanged = a11yConsumersChangedPromise();
let contentConsumersChanged =
ContentTask.spawn(browser, {}, a11yConsumersChangedPromise);
let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
Ci.nsIAccessibilityService);
ok(accService, "Service initialized in parent");
await Promise.all([parentA11yInit, contentA11yInit]);
await parentConsumersChanged.then(data => Assert.deepEqual(data, {
XPCOM: true, MainProcess: false, PlatformAPI: false
}, "Accessibility service consumers in parent are correct."));
await contentConsumersChanged.then(data => Assert.deepEqual(data, {
XPCOM: false, MainProcess: true, PlatformAPI: false
}, "Accessibility service consumers in content are correct."));
info("Removing a service in parent and waiting for service to be shut " +
"down in content");
// Remove a11y service reference in the main process.
let parentA11yShutdown = shutdownPromise();
let contentA11yShutdown = shutdownPromise(browser);
parentConsumersChanged = a11yConsumersChangedPromise();
contentConsumersChanged =
ContentTask.spawn(browser, {}, a11yConsumersChangedPromise);
accService = null;
ok(!accService, "Service is removed in parent");
// Force garbage collection that should trigger shutdown in both main and
// content process.
forceGC();
await Promise.all([parentA11yShutdown, contentA11yShutdown]);
await parentConsumersChanged.then(data => Assert.deepEqual(data, {
XPCOM: false, MainProcess: false, PlatformAPI: false
}, "Accessibility service consumers are correct."));
await contentConsumersChanged.then(data => Assert.deepEqual(data, {
XPCOM: false, MainProcess: false, PlatformAPI: false
}, "Accessibility service consumers are correct."));
});
// Unsetting e10s related preferences.

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

@ -25,22 +25,34 @@ add_task(async function() {
// the a11y service in parent as well.
let parentA11yInit = initPromise();
let contentA11yInit = initPromise(browser);
let contentConsumersChanged =
ContentTask.spawn(browser, {}, a11yConsumersChangedPromise);
let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
Ci.nsIAccessibilityService);
ok(accService, "Service initialized in parent");
await Promise.all([parentA11yInit, contentA11yInit]);
await contentConsumersChanged.then(data => Assert.deepEqual(data, {
XPCOM: false, MainProcess: true, PlatformAPI: false
}, "Accessibility service consumers in content are correct."));
info("Adding additional reference to accessibility service in content " +
"process");
contentConsumersChanged =
ContentTask.spawn(browser, {}, a11yConsumersChangedPromise);
// Add a new reference to the a11y service inside the content process.
loadFrameScripts(browser, `let accService = Components.classes[
'@mozilla.org/accessibilityService;1'].getService(
Components.interfaces.nsIAccessibilityService);`);
await contentConsumersChanged.then(data => Assert.deepEqual(data, {
XPCOM: true, MainProcess: true, PlatformAPI: false
}, "Accessibility service consumers in content are correct."));
info("Shutting down a service in parent and making sure the one in " +
"content stays alive");
let contentCanShutdown = false;
let parentA11yShutdown = shutdownPromise();
contentConsumersChanged =
ContentTask.spawn(browser, {}, a11yConsumersChangedPromise);
// This promise will resolve only if contentCanShutdown flag is set to true.
// If 'a11y-init-or-shutdown' event with '0' flag (in content) comes before
// it can be shut down, the promise will reject.
@ -57,6 +69,9 @@ add_task(async function() {
forceGC();
loadFrameScripts(browser, `Components.utils.forceGC();`);
await parentA11yShutdown;
await contentConsumersChanged.then(data => Assert.deepEqual(data, {
XPCOM: true, MainProcess: false, PlatformAPI: false
}, "Accessibility service consumers in content are correct."));
// Have some breathing room between a11y service shutdowns.
await new Promise(resolve => executeSoon(resolve));
@ -64,10 +79,15 @@ add_task(async function() {
info("Removing a service in content");
// Now allow a11y service to shutdown in content.
contentCanShutdown = true;
contentConsumersChanged =
ContentTask.spawn(browser, {}, a11yConsumersChangedPromise);
// Remove last reference to a11y service in content and force garbage
// collection that should trigger shutdown.
loadFrameScripts(browser, `accService = null; Components.utils.forceGC();`);
await contentA11yShutdown;
await contentConsumersChanged.then(data => Assert.deepEqual(data, {
XPCOM: false, MainProcess: false, PlatformAPI: false
}, "Accessibility service consumers in content are correct."));
// Unsetting e10s related preferences.
await unsetE10sPrefs();

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

@ -37,6 +37,20 @@ Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
this);
/**
* Returns a promise that resolves when 'a11y-consumers-changed' event is fired.
* @return {Promise} event promise evaluating to event's data
*/
function a11yConsumersChangedPromise() {
return new Promise(resolve => {
let observe = (subject, topic, data) => {
Services.obs.removeObserver(observe, "a11y-consumers-changed");
resolve(JSON.parse(data));
};
Services.obs.addObserver(observe, "a11y-consumers-changed");
});
}
/**
* Returns a promise that resolves when 'a11y-init-or-shutdown' event is fired.
* @return {Promise} event promise evaluating to event's data