зеркало из https://github.com/mozilla/gecko-dev.git
Merge inbound to mozilla-central. a=merge
This commit is contained in:
Коммит
210797c4c1
|
@ -172,7 +172,6 @@ var gEMEHandler = {
|
|||
};
|
||||
PopupNotifications.show(browser, "drmContentPlaying", message, anchorId, mainAction, null, options);
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener])
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(gEMEHandler, "_brandShortName", function() {
|
||||
|
|
|
@ -79,17 +79,6 @@ if (!Services.prefs.getBoolPref("full-screen-api.unprefix.enabled")) {
|
|||
});
|
||||
}
|
||||
|
||||
// Platform can be "linux", "macosx" or "win". If omitted, the exception applies to all platforms.
|
||||
let allowedImageReferences = [
|
||||
// Bug 1302691
|
||||
{file: "chrome://devtools/skin/images/dock-bottom-minimize@2x.png",
|
||||
from: "chrome://devtools/skin/toolbox.css",
|
||||
isFromDevTools: true},
|
||||
{file: "chrome://devtools/skin/images/dock-bottom-maximize@2x.png",
|
||||
from: "chrome://devtools/skin/toolbox.css",
|
||||
isFromDevTools: true},
|
||||
];
|
||||
|
||||
let propNameWhitelist = [
|
||||
// These are CSS custom properties that we found a definition of but
|
||||
// no reference to.
|
||||
|
@ -419,18 +408,7 @@ add_task(async function checkAllTheCSS() {
|
|||
for (let [image, references] of imageURIsToReferencesMap) {
|
||||
if (!chromeFileExists(image)) {
|
||||
for (let ref of references) {
|
||||
let ignored = false;
|
||||
for (let item of allowedImageReferences) {
|
||||
if (image.endsWith(item.file) && ref.endsWith(item.from) &&
|
||||
isDevtools == item.isFromDevTools &&
|
||||
(!item.platforms || item.platforms.includes(AppConstants.platform))) {
|
||||
item.used = true;
|
||||
ignored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ignored)
|
||||
ok(false, "missing " + image + " referenced from " + ref);
|
||||
ok(false, "missing " + image + " referenced from " + ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -472,7 +450,6 @@ add_task(async function checkAllTheCSS() {
|
|||
}
|
||||
}
|
||||
checkWhitelist(whitelist);
|
||||
checkWhitelist(allowedImageReferences);
|
||||
checkWhitelist(propNameWhitelist);
|
||||
|
||||
// Clean up to avoid leaks:
|
||||
|
|
|
@ -804,6 +804,7 @@ if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
|
|||
id: "panic-button",
|
||||
type: "view",
|
||||
viewId: "PanelUI-panicView",
|
||||
disabled: !Services.policies.isAllowed("panicButton"),
|
||||
|
||||
forgetButtonCalled(aEvent) {
|
||||
let doc = aEvent.target.ownerDocument;
|
||||
|
|
|
@ -51,8 +51,7 @@ function EnterprisePoliciesManagerContent() {
|
|||
EnterprisePoliciesManagerContent.prototype = {
|
||||
// for XPCOM
|
||||
classID: Components.ID("{dc6358f8-d167-4566-bf5b-4350b5e6a7a2}"),
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
|
||||
Ci.nsIEnterprisePolicies]),
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIEnterprisePolicies]),
|
||||
|
||||
// redefine the default factory for XPCOMUtils
|
||||
_xpcom_factory: EnterprisePoliciesFactory,
|
||||
|
|
|
@ -257,6 +257,14 @@ var Policies = {
|
|||
}
|
||||
},
|
||||
|
||||
"DisableForgetButton": {
|
||||
onProfileAfterChange(manager, param) {
|
||||
if (param) {
|
||||
manager.disallowFeature("panicButton");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"DisableFormHistory": {
|
||||
onBeforeUIStartup(manager, param) {
|
||||
if (param) {
|
||||
|
@ -291,6 +299,14 @@ var Policies = {
|
|||
}
|
||||
},
|
||||
|
||||
"DisableSecurityBypass": {
|
||||
onBeforeUIStartup(manager, param) {
|
||||
if ("SafeBrowsing" in param) {
|
||||
setAndLockPref("browser.safebrowsing.allowOverride", !param.SafeBrowsing);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"DisableSysAddonUpdate": {
|
||||
onBeforeAddons(manager, param) {
|
||||
if (param) {
|
||||
|
@ -489,7 +505,12 @@ var Policies = {
|
|||
|
||||
"InstallAddons": {
|
||||
onBeforeUIStartup(manager, param) {
|
||||
addAllowDenyPermissions("install", param.Allow, null);
|
||||
if ("Allow" in param) {
|
||||
addAllowDenyPermissions("install", param.Allow, null);
|
||||
}
|
||||
if ("Default" in param) {
|
||||
setAndLockPref("xpinstall.enabled", param.Default);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -508,6 +529,17 @@ var Policies = {
|
|||
}
|
||||
},
|
||||
|
||||
"OverridePostUpdatePage": {
|
||||
onProfileAfterChange(manager, param) {
|
||||
let url = param ? param.spec : "";
|
||||
setAndLockPref("startup.homepage_override_url", url);
|
||||
// The pref startup.homepage_override_url is only used
|
||||
// as a fallback when the update.xml file hasn't provided
|
||||
// a specific post-update URL.
|
||||
manager.disallowFeature("postUpdateCustomPage");
|
||||
}
|
||||
},
|
||||
|
||||
"PopupBlocking": {
|
||||
onBeforeUIStartup(manager, param) {
|
||||
addAllowDenyPermissions("popup", param.Allow, null);
|
||||
|
|
|
@ -206,6 +206,13 @@
|
|||
"type": "boolean"
|
||||
},
|
||||
|
||||
"DisableForgetButton": {
|
||||
"description": "Prevents access to the \"Forget\" button.",
|
||||
"first_available": "60.0",
|
||||
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
"DisableFormHistory": {
|
||||
"description": "Don't remember search and form history.",
|
||||
"first_available": "60.0",
|
||||
|
@ -234,6 +241,18 @@
|
|||
"type": "boolean"
|
||||
},
|
||||
|
||||
"DisableSecurityBypass": {
|
||||
"description": "Prevents the user from bypassing certain security warnings.",
|
||||
"first_available": "60.0",
|
||||
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"SafeBrowsing": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"DisableSysAddonUpdate": {
|
||||
"description": "Prevent the browser from installing and updating system addons.",
|
||||
"first_available": "60.0",
|
||||
|
@ -379,6 +398,9 @@
|
|||
"items": {
|
||||
"type": "origin"
|
||||
}
|
||||
},
|
||||
"Default": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -398,6 +420,14 @@
|
|||
"type": "URLorEmpty"
|
||||
},
|
||||
|
||||
"OverridePostUpdatePage": {
|
||||
"description": "Override the post-update \"What's New\" page. Set this policy to blank if you want to disable the post-update page.",
|
||||
"first_available": "60.0",
|
||||
"enterprise_only": true,
|
||||
|
||||
"type": "URLorEmpty"
|
||||
},
|
||||
|
||||
"PopupBlocking": {
|
||||
"description": "Allow or deny popup usage.",
|
||||
"first_available": "60.0",
|
||||
|
|
|
@ -43,6 +43,7 @@ support-files =
|
|||
[browser_policy_display_bookmarks.js]
|
||||
[browser_policy_display_menu.js]
|
||||
[browser_policy_extensions.js]
|
||||
[browser_policy_override_postupdatepage.js]
|
||||
[browser_policy_proxy.js]
|
||||
[browser_policy_search_engine.js]
|
||||
[browser_policy_searchbar.js]
|
||||
|
|
|
@ -39,6 +39,17 @@ const POLICIES_TESTS = [
|
|||
lockedPrefs: { "signon.rememberSignons": true },
|
||||
},
|
||||
|
||||
// POLICY: DisableSecurityBypass
|
||||
{
|
||||
policies: {
|
||||
"DisableSecurityBypass": {
|
||||
"SafeBrowsing": true
|
||||
}
|
||||
},
|
||||
lockedPrefs: { "browser.safebrowsing.allowOverride": false },
|
||||
},
|
||||
|
||||
|
||||
// POLICY: DisableFormHistory
|
||||
{
|
||||
policies: { "DisableFormHistory": true },
|
||||
|
@ -91,6 +102,8 @@ const POLICIES_TESTS = [
|
|||
"network.automatic-ntlm-auth.trusted-uris": "a.com, b.com",
|
||||
}
|
||||
},
|
||||
|
||||
// POLICY: Certificates
|
||||
{
|
||||
policies: {
|
||||
"Certificates": {
|
||||
|
@ -101,6 +114,18 @@ const POLICIES_TESTS = [
|
|||
"security.enterprise_roots.enabled": true,
|
||||
}
|
||||
},
|
||||
|
||||
// POLICY: InstallAddons.Default (block addon installs)
|
||||
{
|
||||
policies: {
|
||||
"InstallAddons": {
|
||||
"Default": false,
|
||||
}
|
||||
},
|
||||
lockedPrefs: {
|
||||
"xpinstall.enabled": false,
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function test_policy_remember_passwords() {
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// This test was based on the test browser_bug538331.js
|
||||
|
||||
const UPDATE_PROVIDED_PAGE = "https://default.example.com/";
|
||||
const POLICY_PROVIDED_PAGE = "https://policy.example.com/";
|
||||
|
||||
const PREF_MSTONE = "browser.startup.homepage_override.mstone";
|
||||
const PREF_POSTUPDATE = "app.update.postupdate";
|
||||
|
||||
/*
|
||||
* The important parts for this test are:
|
||||
* - actions="showURL"
|
||||
* - openURL="${UPDATE_PROVIDED_PAGE}"
|
||||
*/
|
||||
const XML_UPDATE = `<?xml version="1.0"?>
|
||||
<updates xmlns="http://www.mozilla.org/2005/app-update">
|
||||
<update appVersion="1.0" buildID="20080811053724" channel="nightly"
|
||||
displayVersion="Version 1.0" installDate="1238441400314"
|
||||
isCompleteUpdate="true" name="Update Test 1.0" type="minor"
|
||||
detailsURL="http://example.com/" previousAppVersion="1.0"
|
||||
serviceURL="https://example.com/" statusText="The Update was successfully installed"
|
||||
foregroundDownload="true"
|
||||
actions="showURL"
|
||||
openURL="${UPDATE_PROVIDED_PAGE}">
|
||||
<patch type="complete" URL="http://example.com/" size="775" selected="true" state="succeeded"/>
|
||||
</update>
|
||||
</updates>`;
|
||||
|
||||
const XML_EMPTY = `<?xml version="1.0"?>
|
||||
<updates xmlns="http://www.mozilla.org/2005/app-update">
|
||||
</updates>`;
|
||||
|
||||
let gOriginalMStone = null;
|
||||
|
||||
add_task(async function test_override_postupdate_page() {
|
||||
// Remember this to clean-up afterwards
|
||||
if (Services.prefs.prefHasUserValue(PREF_MSTONE)) {
|
||||
gOriginalMStone = Services.prefs.getCharPref(PREF_MSTONE);
|
||||
}
|
||||
Services.prefs.setBoolPref(PREF_POSTUPDATE, true);
|
||||
|
||||
writeUpdatesToXMLFile(XML_UPDATE);
|
||||
reloadUpdateManagerData();
|
||||
|
||||
is(getPostUpdatePage(), UPDATE_PROVIDED_PAGE, "Post-update page was provided by update.xml.");
|
||||
|
||||
// Now perform the same action but set the policy to override this page
|
||||
await setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"OverridePostUpdatePage": POLICY_PROVIDED_PAGE
|
||||
}
|
||||
});
|
||||
|
||||
is(getPostUpdatePage(), POLICY_PROVIDED_PAGE, "Post-update page was provided by policy.");
|
||||
|
||||
// Clean-up
|
||||
writeUpdatesToXMLFile(XML_EMPTY);
|
||||
if (gOriginalMStone) {
|
||||
Services.prefs.setCharPref(PREF_MSTONE, gOriginalMStone);
|
||||
}
|
||||
Services.prefs.clearUserPref(PREF_POSTUPDATE);
|
||||
reloadUpdateManagerData();
|
||||
});
|
||||
|
||||
|
||||
function getPostUpdatePage() {
|
||||
Services.prefs.setCharPref(PREF_MSTONE, "PreviousMilestone");
|
||||
return Cc["@mozilla.org/browser/clh;1"]
|
||||
.getService(Ci.nsIBrowserHandler)
|
||||
.defaultArgs;
|
||||
}
|
||||
|
||||
function reloadUpdateManagerData() {
|
||||
// Reloads the update metadata from disk
|
||||
Cc["@mozilla.org/updates/update-manager;1"].getService(Ci.nsIUpdateManager).
|
||||
QueryInterface(Ci.nsIObserver).observe(null, "um-reload-update-data", "");
|
||||
}
|
||||
|
||||
|
||||
function writeUpdatesToXMLFile(aText) {
|
||||
const PERMS_FILE = 0o644;
|
||||
|
||||
const MODE_WRONLY = 0x02;
|
||||
const MODE_CREATE = 0x08;
|
||||
const MODE_TRUNCATE = 0x20;
|
||||
|
||||
let file = Services.dirsvc.get("UpdRootD", Ci.nsIFile);
|
||||
file.append("updates.xml");
|
||||
let fos = Cc["@mozilla.org/network/file-output-stream;1"].
|
||||
createInstance(Ci.nsIFileOutputStream);
|
||||
if (!file.exists()) {
|
||||
file.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
|
||||
}
|
||||
fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, PERMS_FILE, 0);
|
||||
fos.write(aText, aText.length);
|
||||
fos.close();
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
[DEFAULT]
|
||||
prefs =
|
||||
browser.policies.alternatePath='<test-root>/browser/components/enterprisepolicies/tests/browser/disable_forget_button/forget_button.json'
|
||||
support-files =
|
||||
forget_button.json
|
||||
|
||||
[browser_policy_disable_forgetbutton.js]
|
|
@ -0,0 +1,9 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(async function test_policy_disable_forget_button() {
|
||||
let widget = CustomizableUI.getWidget("panic-button");
|
||||
is(widget.disabled, true, "Forget Button is disabled");
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"policies": {
|
||||
"DisableForgetButton": true
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ BROWSER_CHROME_MANIFESTS += [
|
|||
'browser/disable_app_update/browser.ini',
|
||||
'browser/disable_default_bookmarks/browser.ini',
|
||||
'browser/disable_developer_tools/browser.ini',
|
||||
'browser/disable_forget_button/browser.ini',
|
||||
'browser/disable_fxscreenshots/browser.ini',
|
||||
]
|
||||
|
||||
|
|
|
@ -145,6 +145,13 @@ function getPostUpdateOverridePage(defaultOverridePage) {
|
|||
if (actions.includes("silent") || !actions.includes("showURL"))
|
||||
return "";
|
||||
|
||||
// If a policy was set to not allow the update.xml-provided
|
||||
// URL to be used, use the default fallback (which will also
|
||||
// be provided by the policy).
|
||||
if (!Services.policies.isAllowed("postUpdateCustomPage")) {
|
||||
return defaultOverridePage;
|
||||
}
|
||||
|
||||
return update.getProperty("openURL") || defaultOverridePage;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,225 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { AccessibilityFront } = require("devtools/shared/fronts/accessibility");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const { Picker } = require("./picker");
|
||||
|
||||
// The panel's window global is an EventEmitter firing the following events:
|
||||
const EVENTS = {
|
||||
// When the accessibility inspector has a new accessible front selected.
|
||||
NEW_ACCESSIBLE_FRONT_SELECTED: "Accessibility:NewAccessibleFrontSelected",
|
||||
// When the accessibility inspector has a new accessible front highlighted.
|
||||
NEW_ACCESSIBLE_FRONT_HIGHLIGHTED: "Accessibility:NewAccessibleFrontHighlighted",
|
||||
// When the accessibility inspector has a new accessible front inspected.
|
||||
NEW_ACCESSIBLE_FRONT_INSPECTED: "Accessibility:NewAccessibleFrontInspected",
|
||||
// When the accessibility inspector is updated.
|
||||
ACCESSIBILITY_INSPECTOR_UPDATED: "Accessibility:AccessibilityInspectorUpdated"
|
||||
};
|
||||
|
||||
/**
|
||||
* This object represents Accessibility panel. It's responsibility is to
|
||||
* render Accessibility Tree of the current debugger target and the sidebar that
|
||||
* displays current relevant accessible details.
|
||||
*/
|
||||
function AccessibilityPanel(iframeWindow, toolbox) {
|
||||
this.panelWin = iframeWindow;
|
||||
this._toolbox = toolbox;
|
||||
|
||||
this.onTabNavigated = this.onTabNavigated.bind(this);
|
||||
this.onPanelVisibilityChange = this.onPanelVisibilityChange.bind(this);
|
||||
this.onNewAccessibleFrontSelected =
|
||||
this.onNewAccessibleFrontSelected.bind(this);
|
||||
this.onAccessibilityInspectorUpdated =
|
||||
this.onAccessibilityInspectorUpdated.bind(this);
|
||||
this.updatePickerButton = this.updatePickerButton.bind(this);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
AccessibilityPanel.prototype = {
|
||||
/**
|
||||
* Open is effectively an asynchronous constructor.
|
||||
*/
|
||||
async open() {
|
||||
if (this._opening) {
|
||||
await this._opening;
|
||||
return this._opening;
|
||||
}
|
||||
|
||||
let resolver;
|
||||
this._opening = new Promise(resolve => {
|
||||
resolver = resolve;
|
||||
});
|
||||
|
||||
// Local monitoring needs to make the target remote.
|
||||
if (!this.target.isRemote) {
|
||||
await this.target.makeRemote();
|
||||
}
|
||||
|
||||
this.target.on("navigate", this.onTabNavigated);
|
||||
this._toolbox.on("select", this.onPanelVisibilityChange);
|
||||
|
||||
this.panelWin.EVENTS = EVENTS;
|
||||
EventEmitter.decorate(this.panelWin);
|
||||
this.panelWin.on(EVENTS.NEW_ACCESSIBLE_FRONT_SELECTED,
|
||||
this.onNewAccessibleFrontSelected);
|
||||
this.panelWin.on(EVENTS.ACCESSIBILITY_INSPECTOR_UPDATED,
|
||||
this.onAccessibilityInspectorUpdated);
|
||||
|
||||
this.shouldRefresh = true;
|
||||
this.panelWin.gToolbox = this._toolbox;
|
||||
|
||||
await this._toolbox.initInspector();
|
||||
this._front = new AccessibilityFront(this.target.client,
|
||||
this.target.form);
|
||||
this._walker = await this._front.getWalker();
|
||||
|
||||
this._isOldVersion = !(await this.target.actorHasMethod("accessibility", "enable"));
|
||||
if (!this._isOldVersion) {
|
||||
await this._front.bootstrap();
|
||||
this.picker = new Picker(this);
|
||||
}
|
||||
|
||||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
resolver(this);
|
||||
return this._opening;
|
||||
},
|
||||
|
||||
onNewAccessibleFrontSelected(selected) {
|
||||
this.emit("new-accessible-front-selected", selected);
|
||||
},
|
||||
|
||||
onAccessibilityInspectorUpdated() {
|
||||
this.emit("accessibility-inspector-updated");
|
||||
},
|
||||
|
||||
/**
|
||||
* Make sure the panel is refreshed when the page is reloaded. The panel is
|
||||
* refreshed immediatelly if it's currently selected or lazily when the user
|
||||
* actually selects it.
|
||||
*/
|
||||
onTabNavigated() {
|
||||
this.shouldRefresh = true;
|
||||
this._opening.then(() => this.refresh());
|
||||
},
|
||||
|
||||
/**
|
||||
* Make sure the panel is refreshed (if needed) when it's selected.
|
||||
*/
|
||||
onPanelVisibilityChange() {
|
||||
this._opening.then(() => this.refresh());
|
||||
},
|
||||
|
||||
refresh() {
|
||||
this.cancelPicker();
|
||||
|
||||
if (this.isVisible) {
|
||||
this._front.on("init", this.updatePickerButton);
|
||||
this._front.on("shutdown", this.updatePickerButton);
|
||||
} else {
|
||||
this._front.off("init", this.updatePickerButton);
|
||||
this._front.off("shutdown", this.updatePickerButton);
|
||||
// Do not refresh if the panel isn't visible.
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not refresh if it isn't necessary.
|
||||
if (!this.shouldRefresh) {
|
||||
return;
|
||||
}
|
||||
// Alright reset the flag we are about to refresh the panel.
|
||||
this.shouldRefresh = false;
|
||||
this.postContentMessage("initialize", this._front, this._walker, this._isOldVersion);
|
||||
},
|
||||
|
||||
selectAccessible(accessibleFront) {
|
||||
this.postContentMessage("selectAccessible", this._walker, accessibleFront);
|
||||
},
|
||||
|
||||
highlightAccessible(accessibleFront) {
|
||||
this.postContentMessage("highlightAccessible", this._walker, accessibleFront);
|
||||
},
|
||||
|
||||
postContentMessage(type, ...args) {
|
||||
const event = new this.panelWin.MessageEvent("devtools/chrome/message", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
data: { type, args }
|
||||
});
|
||||
|
||||
this.panelWin.dispatchEvent(event);
|
||||
},
|
||||
|
||||
updatePickerButton() {
|
||||
this.picker && this.picker.updateButton();
|
||||
},
|
||||
|
||||
togglePicker(focus) {
|
||||
this.picker && this.picker.toggle();
|
||||
},
|
||||
|
||||
cancelPicker() {
|
||||
this.picker && this.picker.cancel();
|
||||
},
|
||||
|
||||
stopPicker() {
|
||||
this.picker && this.picker.stop();
|
||||
},
|
||||
|
||||
get walker() {
|
||||
return this._walker;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return true if the Accessibility panel is currently selected.
|
||||
*/
|
||||
get isVisible() {
|
||||
return this._toolbox.currentToolId === "accessibility";
|
||||
},
|
||||
|
||||
get target() {
|
||||
return this._toolbox.target;
|
||||
},
|
||||
|
||||
async destroy() {
|
||||
if (this._destroying) {
|
||||
await this._destroying;
|
||||
return;
|
||||
}
|
||||
|
||||
let resolver;
|
||||
this._destroying = new Promise(resolve => {
|
||||
resolver = resolve;
|
||||
});
|
||||
|
||||
this.target.off("navigate", this.onTabNavigated);
|
||||
this._toolbox.off("select", this.onPanelVisibilityChange);
|
||||
|
||||
this.panelWin.off(EVENTS.NEW_ACCESSIBLE_FRONT_SELECTED,
|
||||
this.onNewAccessibleFrontSelected);
|
||||
this.panelWin.off(EVENTS.ACCESSIBILITY_INSPECTOR_UPDATED,
|
||||
this.onAccessibilityInspectorUpdated);
|
||||
|
||||
this.picker.release();
|
||||
this.picker = null;
|
||||
|
||||
if (this._front) {
|
||||
await this._front.destroy();
|
||||
}
|
||||
|
||||
this._front = null;
|
||||
this.panelWin.gToolbox = null;
|
||||
|
||||
this.emit("destroyed");
|
||||
|
||||
resolver();
|
||||
}
|
||||
};
|
||||
|
||||
// Exports from this module
|
||||
exports.AccessibilityPanel = AccessibilityPanel;
|
|
@ -0,0 +1,97 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/* global EVENTS */
|
||||
|
||||
// React & Redux
|
||||
const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
const { Provider } = require("devtools/client/shared/vendor/react-redux");
|
||||
const { combineReducers } = require("devtools/client/shared/vendor/redux");
|
||||
|
||||
// Accessibility Panel
|
||||
const MainFrame = createFactory(require("./components/MainFrame"));
|
||||
const OldVersionDescription =
|
||||
createFactory(require("./components/Description").OldVersionDescription);
|
||||
|
||||
// Store
|
||||
const createStore = require("devtools/client/shared/redux/create-store")();
|
||||
|
||||
// Reducers
|
||||
const { reducers } = require("./reducers/index");
|
||||
const store = createStore(combineReducers(reducers));
|
||||
|
||||
// Actions
|
||||
const { reset } = require("./actions/ui");
|
||||
const { select, highlight } = require("./actions/accessibles");
|
||||
|
||||
/**
|
||||
* This object represents view of the Accessibility panel and is responsible
|
||||
* for rendering the content. It renders the top level ReactJS
|
||||
* component: the MainFrame.
|
||||
*/
|
||||
function AccessibilityView(localStore) {
|
||||
addEventListener("devtools/chrome/message", this.onMessage.bind(this), true);
|
||||
this.store = localStore;
|
||||
}
|
||||
|
||||
AccessibilityView.prototype = {
|
||||
/**
|
||||
* Initialize accessibility view, create its top level component and set the
|
||||
* data store.
|
||||
*
|
||||
* @param {Object} accessibility front that can initialize accessibility
|
||||
* walker and enable/disable accessibility
|
||||
* services.
|
||||
*/
|
||||
async initialize(accessibility, walker, isOldVersion) {
|
||||
// Make sure state is reset every time accessibility panel is initialized.
|
||||
await this.store.dispatch(reset(accessibility));
|
||||
const container = document.getElementById("content");
|
||||
|
||||
if (isOldVersion) {
|
||||
ReactDOM.render(OldVersionDescription(), container);
|
||||
return;
|
||||
}
|
||||
|
||||
const mainFrame = MainFrame({ accessibility, walker });
|
||||
// Render top level component
|
||||
const provider = createElement(Provider, { store: this.store }, mainFrame);
|
||||
this.mainFrame = ReactDOM.render(provider, container);
|
||||
},
|
||||
|
||||
async selectAccessible(walker, accessible) {
|
||||
await this.store.dispatch(select(walker, accessible));
|
||||
window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED);
|
||||
},
|
||||
|
||||
async highlightAccessible(walker, accessible) {
|
||||
await this.store.dispatch(highlight(walker, accessible));
|
||||
window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_HIGHLIGHTED);
|
||||
},
|
||||
|
||||
async selectNodeAccessible(walker, node) {
|
||||
const accessible = await walker.getAccessibleFor(node);
|
||||
|
||||
await this.store.dispatch(select(walker, accessible));
|
||||
window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_HIGHLIGHTED);
|
||||
},
|
||||
|
||||
/**
|
||||
* Process message from accessibility panel.
|
||||
*
|
||||
* @param {Object} event message type and data.
|
||||
*/
|
||||
onMessage(event) {
|
||||
const data = event.data;
|
||||
const method = data.type;
|
||||
|
||||
if (typeof this[method] === "function") {
|
||||
this[method](...data.args);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
window.view = new AccessibilityView(store);
|
|
@ -0,0 +1,332 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
:root {
|
||||
--accessibility-font-size: 12px;
|
||||
--accessibility-toolbar-height: 24px;
|
||||
--accessibility-toolbar-height-tall: 35px;
|
||||
--accessibility-toolbar-focus: var(--blue-50);
|
||||
--accessibility-toolbar-focus-alpha30: rgba(10, 132, 255, 0.3);
|
||||
--accessibility-full-length-minus-splitter: calc(100% - 1px);
|
||||
--accessibility-horizontal-padding: 5px;
|
||||
--accessibility-arrow-horizontal-padding: 4px;
|
||||
--accessibility-tree-row-height: 21px;
|
||||
}
|
||||
|
||||
/* General */
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:root .flash-out {
|
||||
animation: flash-out 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes flash-out {
|
||||
from {
|
||||
background: var(--theme-contrast-background);
|
||||
}
|
||||
}
|
||||
|
||||
.accessible .tree .node.focused .theme-twisty,
|
||||
.treeTable .treeRow.selected .theme-twisty {
|
||||
background-position: -28px -14px;
|
||||
}
|
||||
|
||||
.accessible .tree .node.focused .theme-twisty.open,
|
||||
.treeTable .treeRow.selected .theme-twisty.open {
|
||||
background-position: -42px -14px;
|
||||
}
|
||||
|
||||
.mainFrame .main-panel {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.mainFrame {
|
||||
height: 100%;
|
||||
color: var(--theme-toolbar-color);
|
||||
}
|
||||
|
||||
.split-box.horz {
|
||||
height: calc(100vh - var(--accessibility-toolbar-height));
|
||||
}
|
||||
|
||||
.mainFrame .devtools-button,
|
||||
.description .devtools-button {
|
||||
padding: unset;
|
||||
}
|
||||
|
||||
.mainFrame .devtools-button > .btn-content {
|
||||
padding: 2px var(--accessibility-horizontal-padding);
|
||||
}
|
||||
|
||||
.description .devtools-button > .btn-content {
|
||||
padding: 7px var(--accessibility-horizontal-padding);
|
||||
}
|
||||
|
||||
.devtools-button:focus,
|
||||
.devtools-button > .btn-content:focus,
|
||||
.devtools-button::-moz-focus-inner {
|
||||
border: 1px solid transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.devtools-button:focus > .btn-content:not(.devtools-throbber) {
|
||||
outline: 2px solid var(--accessibility-toolbar-focus);
|
||||
outline-offset: -2px;
|
||||
box-shadow: 0 0 0 2px var(--accessibility-toolbar-focus-alpha30);
|
||||
border-radius: 2px;
|
||||
-moz-outline-radius: 2px;
|
||||
}
|
||||
|
||||
/* Description */
|
||||
.description {
|
||||
color: var(--theme-toolbar-color);
|
||||
font: message-box;
|
||||
font-size: calc(var(--accessibility-font-size) + 1px);
|
||||
margin: auto;
|
||||
padding-top: 15vh;
|
||||
width: 50vw;
|
||||
}
|
||||
|
||||
.description .general {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1.7em;
|
||||
}
|
||||
|
||||
.description img {
|
||||
margin-right: 12px;
|
||||
flex-basis: 42px;
|
||||
-moz-context-properties: fill;
|
||||
fill: var(--grey-40);
|
||||
}
|
||||
|
||||
.description .devtools-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
/* TreeView Customization */
|
||||
.split-box:not(.horz) .main-panel {
|
||||
height: calc(100vh - var(--accessibility-toolbar-height));
|
||||
}
|
||||
|
||||
.treeTable > thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.split-box:not(.horz) .treeTable {
|
||||
/* To compenstate for 1px splitter between the tree and sidebar. */
|
||||
width: var(--accessibility-full-length-minus-splitter);
|
||||
}
|
||||
|
||||
.split-box.horz .treeTable {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.treeTable .treeRow.highlighted:not(.selected) {
|
||||
background-color: var(--theme-selection-background-hover);
|
||||
}
|
||||
|
||||
.treeTable .treeLabelCell {
|
||||
min-width: 50%;
|
||||
}
|
||||
|
||||
.treeTable:focus,
|
||||
.treeTable > tbody:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.treeTable::-moz-focus-inner,
|
||||
.treeTable > tbody::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.treeTable:focus > tbody {
|
||||
outline: var(--theme-focus-outline);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.treeTable > thead {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.treeTable > tbody tr {
|
||||
height: var(--accessibility-tree-row-height);
|
||||
}
|
||||
|
||||
.treeTable > tbody td {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.treeTable > tbody td > span {
|
||||
-moz-user-select: text;
|
||||
}
|
||||
|
||||
.mainFrame .treeTable .treeRow.hasChildren > .treeLabelCell > .treeLabel:hover {
|
||||
cursor: unset;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.mainFrame .treeTable .treeHeaderRow > .treeHeaderCell:first-child > .treeHeaderCellBox,
|
||||
.mainFrame .treeTable .treeHeaderRow > .treeHeaderCell > .treeHeaderCellBox {
|
||||
padding: 0;
|
||||
padding-inline-start: var(--accessibility-arrow-horizontal-padding);
|
||||
}
|
||||
|
||||
.mainFrame .treeTable .treeHeaderCell {
|
||||
border-bottom: 1px solid var(--theme-splitter-color);
|
||||
background: var(--theme-toolbar-background);
|
||||
font: message-box;
|
||||
font-size: var(--accessibility-font-size);
|
||||
height: var(--accessibility-toolbar-height);
|
||||
color: var(--theme-toolbar-color);
|
||||
}
|
||||
|
||||
/* Right Sidebar */
|
||||
.right-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
font: message-box;
|
||||
font-size: var(--accessibility-font-size);
|
||||
}
|
||||
|
||||
.split-box:not(.horz) .right-sidebar {
|
||||
position: fixed;
|
||||
width: inherit;
|
||||
height: calc(100vh - (var(--accessibility-toolbar-height)));
|
||||
}
|
||||
|
||||
.right-sidebar ._header {
|
||||
background-color: var(--theme-toolbar-background);
|
||||
border-bottom: 1px solid var(--theme-splitter-color);
|
||||
height: var(--accessibility-toolbar-height);
|
||||
line-height: var(--accessibility-toolbar-height);
|
||||
padding-inline-start: 14px;
|
||||
padding-inline-end: var(--accessibility-arrow-horizontal-padding);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.right-sidebar ._content {
|
||||
font-size: var(--accessibility-font-size);
|
||||
flex: 2 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* Tree customization */
|
||||
.accessible .tree {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.split-box.horz .accessible .tree {
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.accessible .tree button {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* NOTE: total height of the node (height + padding + border + margin) must
|
||||
be exactly the same as the value of TREE_ROW_HEIGHT constant in
|
||||
devtools/client/accessibility/constants.js */
|
||||
.accessible .tree .node {
|
||||
padding: 0 var(--accessibility-horizontal-padding);
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: var(--accessibility-tree-row-height);
|
||||
width: calc(100% - var(--accessibility-horizontal-padding));
|
||||
cursor: default;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.accessible .tree .node.focused {
|
||||
background-color: var(--theme-selection-background);
|
||||
}
|
||||
|
||||
.accessible .tree .tree-node:hover:not(.focused) {
|
||||
background-color: var(--theme-selection-background-hover);
|
||||
}
|
||||
|
||||
.accessible .tree .node.focused * {
|
||||
color: var(--theme-selection-color);
|
||||
}
|
||||
|
||||
.accessible .tree .node.focused .open-inspector {
|
||||
background-color: var(--grey-30);
|
||||
}
|
||||
|
||||
.accessible .tree .node.focused:hover .open-inspector {
|
||||
background-color: var(--theme-selection-color);
|
||||
}
|
||||
|
||||
.accessible .tree .arrow {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.accessible .tree .object-value {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.accessible .tree .object-delimiter {
|
||||
padding-inline-end: var(--accessibility-arrow-horizontal-padding);
|
||||
}
|
||||
|
||||
.accessible .tree .object-label {
|
||||
color: var(--theme-highlight-blue);
|
||||
}
|
||||
|
||||
.accessible .tree .objectBox-node {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.accessible .tree .objectBox-node .attrName {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.accessible .tree .objectBox-node .open-inspector{
|
||||
width: 17px;
|
||||
}
|
||||
|
||||
.accessible .tree .objectBox-object,
|
||||
.accessible .tree .objectBox-string,
|
||||
.accessible .tree .objectBox-text,
|
||||
.accessible .tree .objectBox-table,
|
||||
.accessible .tree .objectLink-textNode,
|
||||
.accessible .tree .objectLink-event,
|
||||
.accessible .tree .objectLink-eventLog,
|
||||
.accessible .tree .objectLink-regexp,
|
||||
.accessible .tree .objectLink-object,
|
||||
.accessible .tree .objectLink-Date,
|
||||
.theme-dark .accessible .tree .objectBox-object,
|
||||
.theme-light .accessible .tree .objectBox-object {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Styling for accessible details panel when accessible is not available */
|
||||
.accessible .info {
|
||||
color: var(--theme-body-color);
|
||||
font-size: 110%;
|
||||
padding-inline-start: var(--accessibility-arrow-horizontal-padding);
|
||||
height: var(--accessibility-toolbar-height-tall);
|
||||
line-height: var(--accessibility-toolbar-height-tall);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<!DOCTYPE html>
|
||||
<html dir="">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
|
||||
<link href="resource://devtools/client/accessibility/accessibility.css" rel="stylesheet"/>
|
||||
<link href="resource://devtools/client/shared/components/splitter/SplitBox.css" rel="stylesheet" />
|
||||
<link href="resource://devtools/client/shared/components/tree/TreeView.css" rel="stylesheet" />
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://devtools/content/shared/theme-switching.js"></script>
|
||||
</head>
|
||||
<body class="theme-body devtools-monospace" role="application">
|
||||
<div id="content" role="presentation"></div>
|
||||
<script type="text/javascript" src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,28 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { FETCH_CHILDREN, SELECT, HIGHLIGHT, UNHIGHLIGHT } = require("../constants");
|
||||
|
||||
/**
|
||||
* Fetch child accessibles for a given accessible object.
|
||||
* @param {Object} accessible front
|
||||
*/
|
||||
exports.fetchChildren = accessible =>
|
||||
dispatch => accessible.children()
|
||||
.then(response => dispatch({ accessible, type: FETCH_CHILDREN, response }))
|
||||
.catch(error => dispatch({ accessible, type: FETCH_CHILDREN, error }));
|
||||
|
||||
exports.select = (walker, accessible) =>
|
||||
dispatch => walker.getAncestry(accessible)
|
||||
.then(response => dispatch({ accessible, type: SELECT, response }))
|
||||
.catch(error => dispatch({ accessible, type: SELECT, error }));
|
||||
|
||||
exports.highlight = (walker, accessible) =>
|
||||
dispatch => walker.getAncestry(accessible)
|
||||
.then(response => dispatch({ accessible, type: HIGHLIGHT, response }))
|
||||
.catch(error => dispatch({ accessible, type: HIGHLIGHT, error }));
|
||||
|
||||
exports.unhighlight = () =>
|
||||
dispatch => dispatch({ type: UNHIGHLIGHT });
|
|
@ -0,0 +1,18 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { UPDATE_DETAILS } = require("../constants");
|
||||
|
||||
/**
|
||||
* Update details with the given accessible object.
|
||||
*
|
||||
* @param {Object} dom walker front
|
||||
* @param {Object} accessible front
|
||||
*/
|
||||
exports.updateDetails = (domWalker, accessible) =>
|
||||
dispatch =>
|
||||
domWalker.getNodeFromActor(accessible.actorID, ["rawAccessible", "DOMNode"])
|
||||
.then(response => dispatch({ accessible, type: UPDATE_DETAILS, response }))
|
||||
.catch(error => dispatch({ accessible, type: UPDATE_DETAILS, error }));
|
|
@ -0,0 +1,9 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'accessibles.js',
|
||||
'details.js',
|
||||
'ui.js'
|
||||
)
|
|
@ -0,0 +1,46 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
ENABLE,
|
||||
DISABLE,
|
||||
RESET,
|
||||
UPDATE_CAN_BE_DISABLED,
|
||||
UPDATE_CAN_BE_ENABLED
|
||||
} = require("../constants");
|
||||
|
||||
/**
|
||||
* Reset accessibility panel UI.
|
||||
*/
|
||||
exports.reset = accessibility =>
|
||||
dispatch => dispatch({ accessibility, type: RESET });
|
||||
|
||||
/**
|
||||
* Update a "canBeDisabled" flag for accessibility service.
|
||||
*/
|
||||
exports.updateCanBeDisabled = canBeDisabled =>
|
||||
dispatch => dispatch({ canBeDisabled, type: UPDATE_CAN_BE_DISABLED });
|
||||
|
||||
/**
|
||||
* Update a "canBeEnabled" flag for accessibility service.
|
||||
*/
|
||||
exports.updateCanBeEnabled = canBeEnabled =>
|
||||
dispatch => dispatch({ canBeEnabled, type: UPDATE_CAN_BE_ENABLED });
|
||||
|
||||
/**
|
||||
* Enable accessibility services in order to view accessible tree.
|
||||
*/
|
||||
exports.enable = accessibility =>
|
||||
dispatch => accessibility.enable()
|
||||
.then(() => dispatch({ type: ENABLE }))
|
||||
.catch(error => dispatch({ error, type: ENABLE }));
|
||||
|
||||
/**
|
||||
* Enable accessibility services in order to view accessible tree.
|
||||
*/
|
||||
exports.disable = accessibility =>
|
||||
dispatch => accessibility.disable()
|
||||
.then(() => dispatch({ type: DISABLE }))
|
||||
.catch(error => dispatch({ type: DISABLE, error }));
|
|
@ -0,0 +1,156 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/* global gToolbox, EVENTS */
|
||||
|
||||
// React & Redux
|
||||
const { Component, createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const TreeRow = require("devtools/client/shared/components/tree/TreeRow");
|
||||
|
||||
// Utils
|
||||
const {flashElementOn, flashElementOff} =
|
||||
require("devtools/client/inspector/markup/utils");
|
||||
const { VALUE_FLASHING_DURATION, VALUE_HIGHLIGHT_DURATION } = require("../constants");
|
||||
|
||||
// Actions
|
||||
const { updateDetails } = require("../actions/details");
|
||||
const { unhighlight } = require("../actions/accessibles");
|
||||
|
||||
class HighlightableTreeRowClass extends TreeRow {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
const props = ["name", "open", "value", "loading", "selected", "hasChildren"];
|
||||
|
||||
for (const p of props) {
|
||||
if (nextProps.member[p] !== this.props.member[p]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextProps.highlighted !== this.props.highlighted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const HighlightableTreeRow = createFactory(HighlightableTreeRowClass);
|
||||
|
||||
// Component that expands TreeView's own TreeRow and is responsible for
|
||||
// rendering an accessible object.
|
||||
class AccessibilityRow extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
...TreeRow.propTypes,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
walker: PropTypes.object
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { selected, object } = this.props.member;
|
||||
if (selected) {
|
||||
this.updateAndScrollIntoViewIfNeeded();
|
||||
this.highlight(object, { duration: VALUE_HIGHLIGHT_DURATION });
|
||||
}
|
||||
|
||||
if (this.props.highlighted) {
|
||||
this.scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update accessible object details that are going to be rendered inside the
|
||||
* accessible panel sidebar.
|
||||
*/
|
||||
componentDidUpdate(prevProps) {
|
||||
const { selected, object } = this.props.member;
|
||||
// If row is selected, update corresponding accessible details.
|
||||
if (!prevProps.member.selected && selected) {
|
||||
this.updateAndScrollIntoViewIfNeeded();
|
||||
this.highlight(object, { duration: VALUE_HIGHLIGHT_DURATION });
|
||||
}
|
||||
|
||||
if (this.props.highlighted) {
|
||||
this.scrollIntoView();
|
||||
}
|
||||
|
||||
if (!selected && prevProps.member.value !== this.props.member.value) {
|
||||
this.flashValue();
|
||||
}
|
||||
}
|
||||
|
||||
scrollIntoView() {
|
||||
const row = findDOMNode(this);
|
||||
row.scrollIntoView({ block: "center" });
|
||||
}
|
||||
|
||||
updateAndScrollIntoViewIfNeeded() {
|
||||
const { dispatch, member } = this.props;
|
||||
if (gToolbox) {
|
||||
dispatch(updateDetails(gToolbox.walker, member.object));
|
||||
}
|
||||
|
||||
this.scrollIntoView();
|
||||
window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_SELECTED, member.object);
|
||||
}
|
||||
|
||||
flashValue() {
|
||||
const row = findDOMNode(this);
|
||||
const value = row.querySelector(".objectBox");
|
||||
|
||||
flashElementOn(value);
|
||||
if (this._flashMutationTimer) {
|
||||
clearTimeout(this._flashMutationTimer);
|
||||
this._flashMutationTimer = null;
|
||||
}
|
||||
this._flashMutationTimer = setTimeout(() => {
|
||||
flashElementOff(value);
|
||||
}, VALUE_FLASHING_DURATION);
|
||||
}
|
||||
|
||||
highlight(accessible, options) {
|
||||
const { walker, dispatch } = this.props;
|
||||
dispatch(unhighlight());
|
||||
|
||||
if (!accessible || !walker) {
|
||||
return;
|
||||
}
|
||||
|
||||
walker.highlightAccessible(accessible, options).catch(error =>
|
||||
console.warn(error));
|
||||
}
|
||||
|
||||
unhighlight() {
|
||||
const { walker, dispatch } = this.props;
|
||||
dispatch(unhighlight());
|
||||
|
||||
if (!walker) {
|
||||
return;
|
||||
}
|
||||
|
||||
walker.unhighlight().catch(error => console.warn(error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render accessible row component.
|
||||
* @returns acecssible-row React component.
|
||||
*/
|
||||
render() {
|
||||
const { object } = this.props.member;
|
||||
const props = Object.assign({}, this.props, {
|
||||
onMouseOver: () => this.highlight(object),
|
||||
onMouseOut: () => this.unhighlight()
|
||||
});
|
||||
|
||||
return (HighlightableTreeRow(props));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = connect()(AccessibilityRow);
|
|
@ -0,0 +1,195 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/* global EVENTS */
|
||||
|
||||
// React & Redux
|
||||
const { Component, createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const TreeView = createFactory(require("devtools/client/shared/components/tree/TreeView"));
|
||||
// Reps
|
||||
const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
|
||||
const Rep = createFactory(REPS.Rep);
|
||||
const Grip = REPS.Grip;
|
||||
|
||||
const { fetchChildren } = require("../actions/accessibles");
|
||||
|
||||
const { L10N } = require("../utils/l10n");
|
||||
const AccessibilityRow = createFactory(require("./AccessibilityRow"));
|
||||
const { Provider } = require("../provider");
|
||||
|
||||
/**
|
||||
* Renders Accessibility panel tree.
|
||||
*/
|
||||
class AccessibilityTree extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
walker: PropTypes.object,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
accessibles: PropTypes.object,
|
||||
expanded: PropTypes.object,
|
||||
selected: PropTypes.string,
|
||||
highlighted: PropTypes.object
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onNameChange = this.onNameChange.bind(this);
|
||||
this.onReorder = this.onReorder.bind(this);
|
||||
this.onTextChange = this.onTextChange.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add accessibility walker front event listeners that affect tree rendering
|
||||
* and updates.
|
||||
*/
|
||||
componentWillMount() {
|
||||
let { walker } = this.props;
|
||||
walker.on("reorder", this.onReorder);
|
||||
walker.on("name-change", this.onNameChange);
|
||||
walker.on("text-change", this.onTextChange);
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
window.emit(EVENTS.ACCESSIBILITY_INSPECTOR_UPDATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove accessible walker front event listeners.
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
let { walker } = this.props;
|
||||
walker.off("reorder", this.onReorder);
|
||||
walker.off("name-change", this.onNameChange);
|
||||
walker.off("text-change", this.onTextChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle accessible reorder event. If the accessible is cached and rendered
|
||||
* within the accessibility tree, re-fetch its children and re-render the
|
||||
* corresponding subtree.
|
||||
* @param {Object} accessible accessible object that had its subtree
|
||||
* reordered.
|
||||
*/
|
||||
onReorder(accessible) {
|
||||
if (this.props.accessibles.has(accessible.actorID)) {
|
||||
this.props.dispatch(fetchChildren(accessible));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle accessible name change event. If the name of an accessible changes
|
||||
* and that accessible is cached and rendered within the accessibility tree,
|
||||
* re-fetch its parent's children and re-render the corresponding subtree.
|
||||
* @param {Object} accessible accessible object that had its name changed.
|
||||
* @param {Object} parent optional parent accessible object. Note: if it
|
||||
* parent is not present, we assume that the top
|
||||
* level document's name has changed and use
|
||||
* accessible walker as a parent.
|
||||
*/
|
||||
onNameChange(accessible, parent) {
|
||||
let { accessibles, walker, dispatch } = this.props;
|
||||
parent = parent || walker;
|
||||
|
||||
if (accessibles.has(accessible.actorID) ||
|
||||
accessibles.has(parent.actorID)) {
|
||||
dispatch(fetchChildren(parent));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle accessible text change (change/insert/remove) event. If the text of
|
||||
* an accessible changes and that accessible is cached and rendered within the
|
||||
* accessibility tree, re-fetch its children and re-render the corresponding
|
||||
* subtree.
|
||||
* @param {Object} accessible accessible object that had its child text
|
||||
* changed.
|
||||
*/
|
||||
onTextChange(accessible) {
|
||||
let { accessibles, dispatch } = this.props;
|
||||
if (accessibles.has(accessible.actorID)) {
|
||||
dispatch(fetchChildren(accessible));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Accessibility panel content
|
||||
*/
|
||||
render() {
|
||||
let columns = [{
|
||||
"id": "default",
|
||||
"title": L10N.getStr("accessibility.role")
|
||||
}, {
|
||||
"id": "value",
|
||||
"title": L10N.getStr("accessibility.name")
|
||||
}];
|
||||
|
||||
let {
|
||||
accessibles,
|
||||
dispatch,
|
||||
expanded,
|
||||
selected,
|
||||
highlighted: highlightedItem,
|
||||
walker
|
||||
} = this.props;
|
||||
|
||||
let renderValue = props => {
|
||||
return Rep(Object.assign({}, props, {
|
||||
defaultRep: Grip,
|
||||
cropLimit: 50,
|
||||
}));
|
||||
};
|
||||
|
||||
let renderRow = rowProps => {
|
||||
let { object } = rowProps.member;
|
||||
let highlighted = object === highlightedItem;
|
||||
return AccessibilityRow(Object.assign({}, rowProps, {
|
||||
walker,
|
||||
highlighted,
|
||||
decorator: {
|
||||
getRowClass: function() {
|
||||
return highlighted ? ["highlighted"] : [];
|
||||
}
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
TreeView({
|
||||
object: walker,
|
||||
mode: MODE.SHORT,
|
||||
provider: new Provider(accessibles, dispatch),
|
||||
columns: columns,
|
||||
renderValue,
|
||||
renderRow,
|
||||
label: L10N.getStr("accessibility.treeName"),
|
||||
header: true,
|
||||
expandedNodes: expanded,
|
||||
selected,
|
||||
onClickRow(nodePath, event) {
|
||||
event.stopPropagation();
|
||||
if (event.target.classList.contains("theme-twisty")) {
|
||||
this.toggle(nodePath);
|
||||
}
|
||||
this.selectRow(event.currentTarget);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ accessibles, ui }) => ({
|
||||
accessibles,
|
||||
expanded: ui.expanded,
|
||||
selected: ui.selected,
|
||||
highlighted: ui.highlighted
|
||||
});
|
||||
// Exports from this module
|
||||
module.exports = connect(mapStateToProps)(AccessibilityTree);
|
|
@ -0,0 +1,355 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/* global EVENTS, gToolbox */
|
||||
|
||||
// React & Redux
|
||||
const { createFactory, Component } = require("devtools/client/shared/vendor/react");
|
||||
const { div, span } = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const { TREE_ROW_HEIGHT, ORDERED_PROPS, ACCESSIBLE_EVENTS, VALUE_FLASHING_DURATION } =
|
||||
require("../constants");
|
||||
const { L10N } = require("../utils/l10n");
|
||||
const {flashElementOn, flashElementOff} =
|
||||
require("devtools/client/inspector/markup/utils");
|
||||
const { updateDetails } = require("../actions/details");
|
||||
|
||||
const Tree = createFactory(require("devtools/client/shared/components/VirtualizedTree"));
|
||||
// Reps
|
||||
const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
|
||||
const { Rep, ElementNode } = REPS;
|
||||
|
||||
class AccessiblePropertyClass extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
accessible: PropTypes.string,
|
||||
object: PropTypes.any,
|
||||
focused: PropTypes.bool,
|
||||
children: PropTypes.func
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate({ object: prevObject, accessible: prevAccessible }) {
|
||||
let { accessible, object, focused } = this.props;
|
||||
// Fast check if row is focused or if the value did not update.
|
||||
if (focused || accessible !== prevAccessible || prevObject === object ||
|
||||
(object && prevObject && typeof object === "object")) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.flashRow();
|
||||
}
|
||||
|
||||
flashRow() {
|
||||
let row = findDOMNode(this);
|
||||
flashElementOn(row);
|
||||
if (this._flashMutationTimer) {
|
||||
clearTimeout(this._flashMutationTimer);
|
||||
this._flashMutationTimer = null;
|
||||
}
|
||||
this._flashMutationTimer = setTimeout(() => {
|
||||
flashElementOff(row);
|
||||
}, VALUE_FLASHING_DURATION);
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.props.children();
|
||||
}
|
||||
}
|
||||
|
||||
const AccessibleProperty = createFactory(AccessiblePropertyClass);
|
||||
|
||||
class Accessible extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
accessible: PropTypes.object,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
DOMNode: PropTypes.object,
|
||||
items: PropTypes.array,
|
||||
labelledby: PropTypes.string.isRequired,
|
||||
parents: PropTypes.object
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
expanded: new Set(),
|
||||
focused: null
|
||||
};
|
||||
|
||||
this.onAccessibleInspected = this.onAccessibleInspected.bind(this);
|
||||
this.renderItem = this.renderItem.bind(this);
|
||||
this.update = this.update.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
window.on(EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED, this.onAccessibleInspected);
|
||||
}
|
||||
|
||||
componentWillReceiveProps({ accessible }) {
|
||||
let oldAccessible = this.props.accessible;
|
||||
|
||||
if (oldAccessible) {
|
||||
if (accessible && accessible.actorID === oldAccessible.actorID) {
|
||||
return;
|
||||
}
|
||||
ACCESSIBLE_EVENTS.forEach(event => oldAccessible.off(event, this.update));
|
||||
}
|
||||
|
||||
if (accessible) {
|
||||
ACCESSIBLE_EVENTS.forEach(event => accessible.on(event, this.update));
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.off(EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED, this.onAccessibleInspected);
|
||||
|
||||
let { accessible } = this.props;
|
||||
if (accessible) {
|
||||
ACCESSIBLE_EVENTS.forEach(event => accessible.off(event, this.update));
|
||||
}
|
||||
}
|
||||
|
||||
onAccessibleInspected() {
|
||||
let { props } = this.refs;
|
||||
if (props) {
|
||||
props.refs.tree.focus();
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
let { dispatch, accessible } = this.props;
|
||||
if (gToolbox) {
|
||||
dispatch(updateDetails(gToolbox.walker, accessible));
|
||||
}
|
||||
}
|
||||
|
||||
setExpanded(item, isExpanded) {
|
||||
const { expanded } = this.state;
|
||||
|
||||
if (isExpanded) {
|
||||
expanded.add(item.path);
|
||||
} else {
|
||||
expanded.delete(item.path);
|
||||
}
|
||||
|
||||
this.setState({ expanded });
|
||||
}
|
||||
|
||||
showHighlighter(nodeFront) {
|
||||
if (!gToolbox) {
|
||||
return;
|
||||
}
|
||||
|
||||
gToolbox.highlighterUtils.highlightNodeFront(nodeFront);
|
||||
}
|
||||
|
||||
hideHighlighter() {
|
||||
if (!gToolbox) {
|
||||
return;
|
||||
}
|
||||
|
||||
gToolbox.highlighterUtils.unhighlight();
|
||||
}
|
||||
|
||||
selectNode(nodeFront, reason = "accessibility") {
|
||||
if (!gToolbox) {
|
||||
return;
|
||||
}
|
||||
|
||||
gToolbox.selectTool("inspector").then(() =>
|
||||
gToolbox.selection.setNodeFront(nodeFront, reason));
|
||||
}
|
||||
|
||||
renderItem(item, depth, focused, arrow, expanded) {
|
||||
const object = item.contents;
|
||||
let valueProps = { object, mode: MODE.TINY, title: "Object" };
|
||||
if (isNode(object)) {
|
||||
valueProps.defaultRep = ElementNode;
|
||||
valueProps.onDOMNodeMouseOut = () => this.hideHighlighter();
|
||||
valueProps.onDOMNodeMouseOver = () => this.showHighlighter(this.props.DOMNode);
|
||||
valueProps.onInspectIconClick = () => this.selectNode(this.props.DOMNode);
|
||||
}
|
||||
|
||||
let classList = [ "node", "object-node" ];
|
||||
if (focused) {
|
||||
classList.push("focused");
|
||||
}
|
||||
|
||||
return AccessibleProperty(
|
||||
{ object, focused, accessible: this.props.accessible.actorID },
|
||||
() => div({
|
||||
className: classList.join(" "),
|
||||
style: { paddingInlineStart: depth * 15 },
|
||||
onClick: e => {
|
||||
if (e.target.classList.contains("theme-twisty")) {
|
||||
this.setExpanded(item, !expanded);
|
||||
}
|
||||
}
|
||||
},
|
||||
arrow,
|
||||
span({ className: "object-label" }, item.name),
|
||||
span({ className: "object-delimiter" }, ":"),
|
||||
span({ className: "object-value" }, Rep(valueProps) || "")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { expanded, focused } = this.state;
|
||||
const { items, parents, accessible, labelledby } = this.props;
|
||||
|
||||
if (accessible) {
|
||||
return Tree({
|
||||
ref: "props",
|
||||
key: "accessible-properties",
|
||||
itemHeight: TREE_ROW_HEIGHT,
|
||||
getRoots: () => items,
|
||||
getKey: item => item.path,
|
||||
getParent: item => parents.get(item),
|
||||
getChildren: item => item.children,
|
||||
isExpanded: item => expanded.has(item.path),
|
||||
onExpand: item => this.setExpanded(item, true),
|
||||
onCollapse: item => this.setExpanded(item, false),
|
||||
onFocus: item => {
|
||||
if (this.state.focused !== item.path) {
|
||||
this.setState({ focused: item.path });
|
||||
}
|
||||
},
|
||||
onActivate: ({ contents }) => {
|
||||
if (isNode(contents)) {
|
||||
this.selectNode(this.props.DOMNode, "accessibility-keyboard");
|
||||
}
|
||||
},
|
||||
focused: findFocused(focused, items),
|
||||
renderItem: this.renderItem,
|
||||
labelledby
|
||||
});
|
||||
}
|
||||
|
||||
return div({ className: "info" },
|
||||
L10N.getStr("accessibility.accessible.notAvailable"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find currently focused item.
|
||||
* @param {String} focused Key of the currently focused item.
|
||||
* @param {Array} items Accessibility properties array.
|
||||
* @return {Object?} Possibly found focused item.
|
||||
*/
|
||||
const findFocused = (focused, items) => {
|
||||
for (let item of items) {
|
||||
if (item.path === focused) {
|
||||
return item;
|
||||
}
|
||||
let found = findFocused(focused, item.children);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a given property is a DOMNode actor.
|
||||
* @param {Object?} value A property to check for being a DOMNode.
|
||||
* @return {Boolean} A flag that indicates whether a property is a DOMNode.
|
||||
*/
|
||||
const isNode = value => value && value.typeName === "domnode";
|
||||
|
||||
/**
|
||||
* While waiting for a reps fix in https://github.com/devtools-html/reps/issues/92,
|
||||
* translate nodeFront to a grip-like object that can be used with an ElementNode rep.
|
||||
*
|
||||
* @params {NodeFront} nodeFront
|
||||
* The NodeFront for which we want to create a grip-like object.
|
||||
* @returns {Object} a grip-like object that can be used with Reps.
|
||||
*/
|
||||
const translateNodeFrontToGrip = nodeFront => {
|
||||
let { attributes, actorID, typeName, nodeName, nodeType } = nodeFront;
|
||||
|
||||
// The main difference between NodeFront and grips is that attributes are treated as
|
||||
// a map in grips and as an array in NodeFronts.
|
||||
let attributesMap = {};
|
||||
for (let { name, value } of attributes) {
|
||||
attributesMap[name] = value;
|
||||
}
|
||||
|
||||
return {
|
||||
actor: actorID,
|
||||
typeName,
|
||||
preview: {
|
||||
attributes: attributesMap,
|
||||
attributesLength: attributes.length,
|
||||
// All the grid containers are assumed to be in the DOM tree.
|
||||
isConnected: true,
|
||||
// nodeName is already lowerCased in Node grips
|
||||
nodeName: nodeName.toLowerCase(),
|
||||
nodeType
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Build props ingestible by Tree component.
|
||||
* @param {Object} props Component properties to be processed.
|
||||
* @param {String} parentPath Unique path that is used to identify a Tree Node.
|
||||
* @return {Object} Processed properties.
|
||||
*/
|
||||
const makeItemsForDetails = (props, parentPath) =>
|
||||
Object.getOwnPropertyNames(props).map(name => {
|
||||
let children = [];
|
||||
let path = `${parentPath}/${name}`;
|
||||
let contents = props[name];
|
||||
|
||||
if (contents) {
|
||||
if (isNode(contents)) {
|
||||
contents = translateNodeFrontToGrip(contents);
|
||||
} else if (Array.isArray(contents) || typeof contents === "object") {
|
||||
children = makeItemsForDetails(contents, path);
|
||||
}
|
||||
}
|
||||
|
||||
return { name, path, contents, children };
|
||||
});
|
||||
|
||||
const makeParentMap = (items) => {
|
||||
const map = new WeakMap();
|
||||
|
||||
function _traverse(item) {
|
||||
if (item.children.length > 0) {
|
||||
for (const child of item.children) {
|
||||
map.set(child, item);
|
||||
_traverse(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items.forEach(_traverse);
|
||||
return map;
|
||||
};
|
||||
|
||||
const mapStateToProps = ({ details }) => {
|
||||
let { accessible, DOMNode } = details;
|
||||
if (!accessible || !DOMNode) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let items = makeItemsForDetails(ORDERED_PROPS.reduce((props, key) => {
|
||||
props[key] = key === "DOMNode" ? DOMNode : accessible[key];
|
||||
return props;
|
||||
}, {}), "");
|
||||
let parents = makeParentMap(items);
|
||||
|
||||
return { accessible, DOMNode, items, parents };
|
||||
};
|
||||
|
||||
module.exports = connect(mapStateToProps)(Accessible);
|
|
@ -0,0 +1,63 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Component } = require("devtools/client/shared/vendor/react");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const { button, span } = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
|
||||
const defaultProps = {
|
||||
disabled: false,
|
||||
busy: false,
|
||||
title: null,
|
||||
children: null,
|
||||
className: ""
|
||||
};
|
||||
|
||||
/**
|
||||
* Button component that handles keyboard in an accessible way. When user
|
||||
* uses the mouse to hover/click on the button, there is no focus
|
||||
* highlighting. However if the user uses a keyboard to focus on the button,
|
||||
* it will have focus highlighting in the form of an outline.
|
||||
*/
|
||||
class Button extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
disabled: PropTypes.bool,
|
||||
busy: PropTypes.bool,
|
||||
title: PropTypes.string,
|
||||
children: PropTypes.string,
|
||||
className: PropTypes.string
|
||||
};
|
||||
}
|
||||
|
||||
static get defaultProps() {
|
||||
return defaultProps;
|
||||
}
|
||||
|
||||
render() {
|
||||
let className = [
|
||||
...this.props.className.split(" "),
|
||||
"devtools-button"
|
||||
].join(" ");
|
||||
let props = Object.assign({}, this.props, {
|
||||
className,
|
||||
"aria-busy": this.props.busy
|
||||
});
|
||||
|
||||
let classList = ["btn-content"];
|
||||
if (this.props.busy) {
|
||||
classList.push("devtools-throbber");
|
||||
}
|
||||
|
||||
return (button(props, span({
|
||||
className: classList.join(" "),
|
||||
tabIndex: -1
|
||||
}, this.props.children)));
|
||||
}
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
module.exports = Button;
|
|
@ -0,0 +1,119 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
// React & Redux
|
||||
const { createFactory, Component } = require("devtools/client/shared/vendor/react");
|
||||
const { div, p, img } = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
const Button = createFactory(require("./Button"));
|
||||
const { enable, updateCanBeEnabled } = require("../actions/ui");
|
||||
|
||||
// Localization
|
||||
const { L10N } = require("../utils/l10n");
|
||||
|
||||
class OldVersionDescription extends Component {
|
||||
render() {
|
||||
return (
|
||||
div({ className: "description" },
|
||||
p({ className: "general" },
|
||||
img({
|
||||
src: "chrome://devtools/skin/images/accessibility.svg",
|
||||
alt: L10N.getStr("accessibility.logo")
|
||||
}), L10N.getStr("accessibility.description.oldVersion")))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Landing UI for the accessibility panel when Accessibility features are
|
||||
* deactivated.
|
||||
*/
|
||||
class Description extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
accessibility: PropTypes.object.isRequired,
|
||||
canBeEnabled: PropTypes.bool,
|
||||
dispatch: PropTypes.func.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
enabling: false
|
||||
};
|
||||
|
||||
this.onEnable = this.onEnable.bind(this);
|
||||
this.onCanBeEnabledChange = this.onCanBeEnabledChange.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.props.accessibility.on("can-be-enabled-change",
|
||||
this.onCanBeEnabledChange);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.accessibility.off("can-be-enabled-change",
|
||||
this.onCanBeEnabledChange);
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
let { accessibility, dispatch } = this.props;
|
||||
this.setState({ enabling: true });
|
||||
dispatch(enable(accessibility))
|
||||
.then(() => this.setState({ enabling: false }))
|
||||
.catch(() => this.setState({ enabling: false }));
|
||||
}
|
||||
|
||||
onCanBeEnabledChange(canBeEnabled) {
|
||||
this.props.dispatch(updateCanBeEnabled(canBeEnabled));
|
||||
}
|
||||
|
||||
render() {
|
||||
let { canBeEnabled } = this.props;
|
||||
let { enabling } = this.state;
|
||||
let enableButtonStr = enabling ? "accessibility.enabling" : "accessibility.enable";
|
||||
|
||||
let title;
|
||||
let disableButton = false;
|
||||
|
||||
if (canBeEnabled) {
|
||||
title = L10N.getStr("accessibility.enable.enabledTitle");
|
||||
} else {
|
||||
disableButton = true;
|
||||
title = L10N.getStr("accessibility.enable.disabledTitle");
|
||||
}
|
||||
|
||||
return (
|
||||
div({ className: "description" },
|
||||
p({ className: "general" },
|
||||
img({
|
||||
src: "chrome://devtools/skin/images/accessibility.svg",
|
||||
alt: L10N.getStr("accessibility.logo")
|
||||
}),
|
||||
L10N.getStr("accessibility.description.general")),
|
||||
Button({
|
||||
id: "accessibility-enable-button",
|
||||
onClick: this.onEnable,
|
||||
disabled: enabling || disableButton,
|
||||
busy: enabling,
|
||||
"data-standalone": true,
|
||||
title
|
||||
}, L10N.getStr(enableButtonStr))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ ui }) => ({
|
||||
canBeEnabled: ui.canBeEnabled
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.Description = connect(mapStateToProps)(Description);
|
||||
exports.OldVersionDescription = OldVersionDescription;
|
|
@ -0,0 +1,126 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/* global gToolbox */
|
||||
|
||||
// React & Redux
|
||||
const { Component, createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const { div } = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
const { reset } = require("../actions/ui");
|
||||
|
||||
// Constants
|
||||
const { SIDEBAR_WIDTH, PORTRAIT_MODE_WIDTH } = require("../constants");
|
||||
|
||||
// Accessibility Panel
|
||||
const AccessibilityTree = createFactory(require("./AccessibilityTree"));
|
||||
const Description = createFactory(require("./Description").Description);
|
||||
const RightSidebar = createFactory(require("./RightSidebar"));
|
||||
const Toolbar = createFactory(require("./Toolbar"));
|
||||
const SplitBox = createFactory(require("devtools/client/shared/components/splitter/SplitBox"));
|
||||
|
||||
/**
|
||||
* Renders basic layout of the Accessibility panel. The Accessibility panel
|
||||
* content consists of two main parts: tree and sidebar.
|
||||
*/
|
||||
class MainFrame extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
accessibility: PropTypes.object.isRequired,
|
||||
walker: PropTypes.object.isRequired,
|
||||
enabled: PropTypes.bool.isRequired,
|
||||
dispatch: PropTypes.func.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.resetAccessibility = this.resetAccessibility.bind(this);
|
||||
this.onPanelWindowResize = this.onPanelWindowResize.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// Need inspector for many things such as dom node preview etc.
|
||||
gToolbox.loadTool("inspector");
|
||||
this.props.accessibility.on("init", this.resetAccessibility);
|
||||
this.props.accessibility.on("shutdown", this.resetAccessibility);
|
||||
this.props.walker.on("document-ready", this.resetAccessibility);
|
||||
|
||||
window.addEventListener("resize", this.onPanelWindowResize, true);
|
||||
}
|
||||
|
||||
componentWillReceiveProps({ enabled }) {
|
||||
if (this.props.enabled && !enabled) {
|
||||
this.resetAccessibility();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.accessibility.off("init", this.resetAccessibility);
|
||||
this.props.accessibility.off("shutdown", this.resetAccessibility);
|
||||
this.props.walker.off("document-ready", this.resetAccessibility);
|
||||
|
||||
window.removeEventListener("resize", this.onPanelWindowResize, true);
|
||||
}
|
||||
|
||||
resetAccessibility() {
|
||||
let { dispatch, accessibility } = this.props;
|
||||
dispatch(reset(accessibility));
|
||||
}
|
||||
|
||||
get useLandscapeMode() {
|
||||
let { clientWidth } = document.getElementById("content");
|
||||
return clientWidth > PORTRAIT_MODE_WIDTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* If panel width is less than PORTRAIT_MODE_WIDTH px, the splitter changes
|
||||
* its mode to `horizontal` to support portrait view.
|
||||
*/
|
||||
onPanelWindowResize() {
|
||||
if (this.refs.splitBox) {
|
||||
this.refs.splitBox.setState({ vert: this.useLandscapeMode });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Accessibility panel content
|
||||
*/
|
||||
render() {
|
||||
let { accessibility, walker, enabled } = this.props;
|
||||
|
||||
if (!enabled) {
|
||||
return Description({ accessibility });
|
||||
}
|
||||
|
||||
return (
|
||||
div({ className: "mainFrame", role: "presentation" },
|
||||
Toolbar({ accessibility, walker }),
|
||||
SplitBox({
|
||||
ref: "splitBox",
|
||||
initialSize: SIDEBAR_WIDTH,
|
||||
minSize: "10px",
|
||||
maxSize: "80%",
|
||||
splitterSize: 1,
|
||||
endPanelControl: true,
|
||||
startPanel: div({
|
||||
className: "main-panel",
|
||||
role: "presentation"
|
||||
}, AccessibilityTree({ walker })),
|
||||
endPanel: RightSidebar(),
|
||||
vert: this.useLandscapeMode,
|
||||
})
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ ui }) => ({
|
||||
enabled: ui.enabled
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
module.exports = connect(mapStateToProps)(MainFrame);
|
|
@ -0,0 +1,40 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
// React
|
||||
const { Component, createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const { div } = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
|
||||
const { L10N } = require("../utils/l10n");
|
||||
const Accessible = createFactory(require("./Accessible"));
|
||||
|
||||
// Component that is responsible for rendering accessible panel's sidebar.
|
||||
class RightSidebar extends Component {
|
||||
/**
|
||||
* Render the sidebar component.
|
||||
* @returns Sidebar React component.
|
||||
*/
|
||||
render() {
|
||||
const headerID = "accessibility-right-sidebar-header";
|
||||
return (
|
||||
div({
|
||||
className: "right-sidebar",
|
||||
role: "presentation"
|
||||
},
|
||||
div({
|
||||
className: "_header",
|
||||
id: headerID,
|
||||
role: "heading"
|
||||
}, L10N.getStr("accessibility.properties")),
|
||||
div({
|
||||
className: "_content accessible",
|
||||
role: "presentation"
|
||||
}, Accessible({ labelledby: headerID }))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RightSidebar;
|
|
@ -0,0 +1,94 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
// React
|
||||
const { createFactory, Component } = require("devtools/client/shared/vendor/react");
|
||||
const { div } = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const { L10N } = require("../utils/l10n");
|
||||
const Button = createFactory(require("./Button"));
|
||||
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
const { disable, updateCanBeDisabled } = require("../actions/ui");
|
||||
|
||||
class Toolbar extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
accessibility: PropTypes.object.isRequired,
|
||||
canBeDisabled: PropTypes.bool.isRequired
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
disabling: false
|
||||
};
|
||||
|
||||
this.onDisable = this.onDisable.bind(this);
|
||||
this.onCanBeDisabledChange = this.onCanBeDisabledChange.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.props.accessibility.on("can-be-disabled-change",
|
||||
this.onCanBeDisabledChange);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.accessibility.off("can-be-disabled-change",
|
||||
this.onCanBeDisabledChange);
|
||||
}
|
||||
|
||||
onCanBeDisabledChange(canBeDisabled) {
|
||||
this.props.dispatch(updateCanBeDisabled(canBeDisabled));
|
||||
}
|
||||
|
||||
onDisable() {
|
||||
let { accessibility, dispatch } = this.props;
|
||||
this.setState({ disabling: true });
|
||||
dispatch(disable(accessibility))
|
||||
.then(() => this.setState({ disabling: false }))
|
||||
.catch(() => this.setState({ disabling: false }));
|
||||
}
|
||||
|
||||
render() {
|
||||
let { canBeDisabled } = this.props;
|
||||
let { disabling } = this.state;
|
||||
let disableButtonStr = disabling ?
|
||||
"accessibility.disabling" : "accessibility.disable";
|
||||
let title;
|
||||
let isDisabled = false;
|
||||
|
||||
if (canBeDisabled) {
|
||||
title = L10N.getStr("accessibility.disable.enabledTitle");
|
||||
} else {
|
||||
isDisabled = true;
|
||||
title = L10N.getStr("accessibility.disable.disabledTitle");
|
||||
}
|
||||
|
||||
return (
|
||||
div({
|
||||
className: "devtools-toolbar",
|
||||
role: "toolbar"
|
||||
}, Button({
|
||||
className: "disable",
|
||||
id: "accessibility-disable-button",
|
||||
onClick: this.onDisable,
|
||||
disabled: disabling || isDisabled,
|
||||
busy: disabling,
|
||||
title
|
||||
}, L10N.getStr(disableButtonStr)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ ui }) => ({
|
||||
canBeDisabled: ui.canBeDisabled
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
module.exports = connect(mapStateToProps)(Toolbar);
|
|
@ -0,0 +1,14 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'AccessibilityRow.js',
|
||||
'AccessibilityTree.js',
|
||||
'Accessible.js',
|
||||
'Button.js',
|
||||
'Description.js',
|
||||
'MainFrame.js',
|
||||
'RightSidebar.js',
|
||||
'Toolbar.js'
|
||||
)
|
|
@ -0,0 +1,63 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
// Used in accessible component for properties tree rendering.
|
||||
exports.TREE_ROW_HEIGHT = 21;
|
||||
|
||||
// Initial sidebar width.
|
||||
exports.SIDEBAR_WIDTH = "350px";
|
||||
|
||||
// When value is updated either in the tree or sidebar.
|
||||
exports.VALUE_FLASHING_DURATION = 500;
|
||||
// When new row is selected, flash highlighter.
|
||||
exports.VALUE_HIGHLIGHT_DURATION = 1000;
|
||||
|
||||
// If the panel width is smaller than given amount of pixels,
|
||||
// the sidebar automatically switches from 'landscape' to 'portrait' mode.
|
||||
exports.PORTRAIT_MODE_WIDTH = 700;
|
||||
|
||||
// Names for Redux actions.
|
||||
exports.FETCH_CHILDREN = "FETCH_CHILDREN";
|
||||
exports.UPDATE_DETAILS = "UPDATE_DETAILS";
|
||||
exports.RESET = "RESET";
|
||||
exports.SELECT = "SELECT";
|
||||
exports.HIGHLIGHT = "HIGHLIGHT";
|
||||
exports.UNHIGHLIGHT = "UNHIGHLIGHT";
|
||||
exports.ENABLE = "ENABLE";
|
||||
exports.DISABLE = "DISABLE";
|
||||
exports.UPDATE_CAN_BE_DISABLED = "UPDATE_CAN_BE_DISABLED";
|
||||
exports.UPDATE_CAN_BE_ENABLED = "UPDATE_CAN_BE_ENABLED";
|
||||
|
||||
// Ordered accessible properties to be displayed by the accessible component.
|
||||
exports.ORDERED_PROPS = [
|
||||
"name",
|
||||
"role",
|
||||
"actions",
|
||||
"value",
|
||||
"DOMNode",
|
||||
"description",
|
||||
"help",
|
||||
"keyboardShortcut",
|
||||
"childCount",
|
||||
"indexInParent",
|
||||
"states",
|
||||
"attributes"
|
||||
];
|
||||
|
||||
// Accessible events (emitted by accessible front) that the accessible component
|
||||
// listens to for a current accessible.
|
||||
exports.ACCESSIBLE_EVENTS = [
|
||||
"actions-change",
|
||||
"attributes-change",
|
||||
"description-change",
|
||||
"help-change",
|
||||
"name-change",
|
||||
"reorder",
|
||||
"shortcut-change",
|
||||
"states-change",
|
||||
"text-change",
|
||||
"value-change",
|
||||
"index-in-parent-change"
|
||||
];
|
|
@ -0,0 +1,16 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { utils: Cu } = Components;
|
||||
const { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
|
||||
|
||||
// Module Loader
|
||||
const require = BrowserLoader({
|
||||
baseURI: "resource://devtools/client/accessibility/",
|
||||
window
|
||||
}).require;
|
||||
|
||||
// Load accessibility panel content
|
||||
require("./accessibility-view.js");
|
|
@ -0,0 +1,24 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
|
||||
DIRS += [
|
||||
'actions',
|
||||
'components',
|
||||
'reducers',
|
||||
'utils'
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
'accessibility-panel.js',
|
||||
'accessibility-view.js',
|
||||
'accessibility.css',
|
||||
'constants.js',
|
||||
'picker.js',
|
||||
'provider.js',
|
||||
)
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Firefox', 'Developer Tools: Accessibility')
|
|
@ -0,0 +1,184 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { L10N } = require("./utils/l10n");
|
||||
|
||||
class Picker {
|
||||
constructor(panel) {
|
||||
this._panel = panel;
|
||||
|
||||
this.isPicking = false;
|
||||
|
||||
this.onPickerAccessibleHovered = this.onPickerAccessibleHovered.bind(this);
|
||||
this.onPickerAccessiblePicked = this.onPickerAccessiblePicked.bind(this);
|
||||
this.onPickerAccessiblePreviewed = this.onPickerAccessiblePreviewed.bind(this);
|
||||
this.onPickerAccessibleCanceled = this.onPickerAccessibleCanceled.bind(this);
|
||||
}
|
||||
|
||||
get toolbox() {
|
||||
return this._panel._toolbox;
|
||||
}
|
||||
|
||||
get walker() {
|
||||
return this._panel._walker;
|
||||
}
|
||||
|
||||
get pickerButton() {
|
||||
return this.toolbox.pickerButton;
|
||||
}
|
||||
|
||||
release() {
|
||||
this._panel = null;
|
||||
}
|
||||
|
||||
emit(event, data) {
|
||||
this.toolbox.emit(event, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select accessible object in the tree.
|
||||
* @param {Object} accessible
|
||||
* Accessiblle object to be selected in the inspector tree.
|
||||
*/
|
||||
select(accessible) {
|
||||
this._panel.selectAccessible(accessible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight accessible object in the tree.
|
||||
* @param {Object} accessible
|
||||
* Accessiblle object to be selected in the inspector tree.
|
||||
*/
|
||||
highlight(accessible) {
|
||||
this._panel.highlightAccessible(accessible);
|
||||
}
|
||||
|
||||
getStr(key) {
|
||||
return L10N.getStr(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default presentation of the picker button in toolbox's top
|
||||
* level toolbar.
|
||||
*/
|
||||
updateButton() {
|
||||
this.pickerButton.description = this.getStr("accessibility.pick");
|
||||
this.pickerButton.className = "accessibility";
|
||||
this.pickerButton.disabled = !this._panel._front.enabled;
|
||||
if (!this._panel._front.enabled && this.isPicking) {
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an event when a new accessible object is hovered over.
|
||||
* @param {Object} accessible
|
||||
* newly hovered accessible object
|
||||
*/
|
||||
onPickerAccessibleHovered(accessible) {
|
||||
if (accessible) {
|
||||
this.emit("picker-accessible-hovered", accessible);
|
||||
this.highlight(accessible);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an event when a new accessible is picked by the user.
|
||||
* @param {Object} accessible
|
||||
* newly picked accessible object
|
||||
*/
|
||||
onPickerAccessiblePicked(accessible) {
|
||||
if (accessible) {
|
||||
this.select(accessible);
|
||||
}
|
||||
this.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an event when a new accessible is previewed by the user.
|
||||
* @param {Object} accessible
|
||||
* newly previewed accessible object
|
||||
*/
|
||||
onPickerAccessiblePreviewed(accessible) {
|
||||
if (accessible) {
|
||||
this.select(accessible);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an event when picking is cancelled by the user.s
|
||||
*/
|
||||
onPickerAccessibleCanceled() {
|
||||
this.cancel();
|
||||
this.emit("picker-accessible-canceled");
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel picking.
|
||||
*/
|
||||
async cancel() {
|
||||
await this.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop picking and remove all walker listeners.
|
||||
*/
|
||||
async stop() {
|
||||
if (!this.isPicking) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isPicking = false;
|
||||
|
||||
this.pickerButton.isChecked = false;
|
||||
|
||||
await this.walker.cancelPick();
|
||||
this.walker.off("picker-accessible-hovered", this.onPickerAccessibleHovered);
|
||||
this.walker.off("picker-accessible-picked", this.onPickerAccessiblePicked);
|
||||
this.walker.off("picker-accessible-previewed", this.onPickerAccessiblePreviewed);
|
||||
this.walker.off("picker-accessible-canceled", this.onPickerAccessibleCanceled);
|
||||
|
||||
this.emit("picker-stopped");
|
||||
}
|
||||
|
||||
/**
|
||||
* Start picking and add walker listeners.
|
||||
* @param {Boolean} doFocus
|
||||
* If true, move keyboard focus into content.
|
||||
*/
|
||||
async start(doFocus = true) {
|
||||
if (this.isPicking) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isPicking = true;
|
||||
|
||||
this.pickerButton.isChecked = true;
|
||||
|
||||
this.walker.on("picker-accessible-hovered", this.onPickerAccessibleHovered);
|
||||
this.walker.on("picker-accessible-picked", this.onPickerAccessiblePicked);
|
||||
this.walker.on("picker-accessible-previewed", this.onPickerAccessiblePreviewed);
|
||||
this.walker.on("picker-accessible-canceled", this.onPickerAccessibleCanceled);
|
||||
|
||||
await this.walker.pick(doFocus);
|
||||
this.emit("picker-started");
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle between starting and canceling the picker.
|
||||
* @param {Boolean} doFocus
|
||||
* If true, move keyboard focus into content.
|
||||
*/
|
||||
toggle(doFocus) {
|
||||
if (this.isPicking) {
|
||||
return this.cancel();
|
||||
}
|
||||
|
||||
return this.start(doFocus);
|
||||
}
|
||||
}
|
||||
|
||||
exports.Picker = Picker;
|
|
@ -0,0 +1,92 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { fetchChildren } = require("./actions/accessibles");
|
||||
|
||||
/**
|
||||
* Data provider that is responsible for mapping of an accessibles cache to the
|
||||
* data format that is supported by the TreeView component.
|
||||
* @param {Map} accessibles accessibles object cache
|
||||
* @param {Function} dispatch react dispatch function that triggers a redux
|
||||
* action.
|
||||
*/
|
||||
|
||||
class Provider {
|
||||
constructor(accessibles, dispatch) {
|
||||
this.accessibles = accessibles;
|
||||
this.dispatch = dispatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get accessible's cached children if available, if not fetch them from
|
||||
* backend.
|
||||
* @param {Object} accessible accessible object whose children to get.
|
||||
* @returns {Array} arraof of accessible children.
|
||||
*/
|
||||
getChildren(accessible) {
|
||||
if (!accessible || !accessible.actor || accessible.childCount === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let obj = this.accessibles.get(accessible.actorID);
|
||||
if (!obj || !obj.children) {
|
||||
return this.dispatch(fetchChildren(accessible));
|
||||
}
|
||||
|
||||
return obj.children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a flag indicating if an accessible object has any children.
|
||||
* @param {Object} accessible accessible object whose children to get.
|
||||
* @returns {Boolean} idicator of whether accessible object has children.
|
||||
*/
|
||||
hasChildren(accessible) {
|
||||
return accessible.childCount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value for an accessible object. Used to render the second (value)
|
||||
* column of the accessible tree. Corresponds to an accesible object name, if
|
||||
* available.
|
||||
* @param {Object} accessible accessible object
|
||||
* @returns {String} accessible object value.
|
||||
*/
|
||||
getValue(accessible) {
|
||||
return accessible.name || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a label for an accessible object. Used to render the first column of
|
||||
* the accessible tree. Corresponds to an accessible object role.
|
||||
* @param {Object} accessible accessible object
|
||||
* @returns {String} accessible object label.
|
||||
*/
|
||||
getLabel(accessible) {
|
||||
return accessible.role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a unique key for an accessible object. Corresponds to an accessible
|
||||
* front's actorID.
|
||||
* @param {Object} accessible accessible object
|
||||
* @returns {String} a key for an accessible object.
|
||||
*/
|
||||
getKey(accessible) {
|
||||
return accessible.actorID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a type of an accesible object. Corresponds to the type of an accessible
|
||||
* front.
|
||||
* @param {Object} accessible accessible object
|
||||
* @returns {String} accessible object type
|
||||
*/
|
||||
getType(accessible) {
|
||||
return accessible.typeName;
|
||||
}
|
||||
}
|
||||
|
||||
exports.Provider = Provider;
|
|
@ -0,0 +1,124 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { FETCH_CHILDREN, RESET, SELECT, HIGHLIGHT } = require("../constants");
|
||||
|
||||
/**
|
||||
* Initial state definition
|
||||
*/
|
||||
function getInitialState() {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maintain a cache of received accessibles responses from the backend.
|
||||
*/
|
||||
function accessibles(state = getInitialState(), action) {
|
||||
switch (action.type) {
|
||||
case FETCH_CHILDREN:
|
||||
return onReceiveChildren(state, action);
|
||||
case HIGHLIGHT:
|
||||
case SELECT:
|
||||
return onReceiveAncestry(state, action);
|
||||
case RESET:
|
||||
return getInitialState();
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If accessible is cached recursively remove all its children and remove itself
|
||||
* from cache.
|
||||
* @param {Map} cache Previous state maintaining a cache of previously
|
||||
* fetched accessibles.
|
||||
* @param {Object} accessible Accessible object to remove from cache.
|
||||
*/
|
||||
function cleanupChild(cache, accessible) {
|
||||
let cached = cache.get(accessible.actorID);
|
||||
if (!cached) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let child of cached.children) {
|
||||
cleanupChild(cache, child);
|
||||
}
|
||||
|
||||
cache.delete(accessible.actorID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if accessible in cache is stale. Accessible object is stale if its
|
||||
* cached children array has the size other than the value of its childCount
|
||||
* property that updates on accessible actor event.
|
||||
* @param {Map} cache Previous state maintaining a cache of previously
|
||||
* fetched accessibles.
|
||||
* @param {Object} accessible Accessible object to test for staleness.
|
||||
*/
|
||||
function staleChildren(cache, accessible) {
|
||||
let cached = cache.get(accessible.actorID);
|
||||
if (!cached) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return cached.children.length !== accessible.childCount;
|
||||
}
|
||||
|
||||
function updateChildrenCache(cache, accessible, children) {
|
||||
let { actorID } = accessible;
|
||||
|
||||
if (cache.has(actorID)) {
|
||||
let cached = cache.get(actorID);
|
||||
for (let child of cached.children) {
|
||||
// If exhisting children cache includes an accessible that is not present
|
||||
// any more or if child accessible is stale remove it and all its children
|
||||
// from cache.
|
||||
if (!children.includes(child) || staleChildren(cache, child)) {
|
||||
cleanupChild(cache, child);
|
||||
}
|
||||
}
|
||||
cached.children = children;
|
||||
cache.set(actorID, cached);
|
||||
} else {
|
||||
cache.set(actorID, { children });
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles fetching of accessible children.
|
||||
* @param {Map} cache Previous state maintaining a cache of previously
|
||||
* fetched accessibles.
|
||||
* @param {Object} action Redux action object.
|
||||
* @return {Object} updated state
|
||||
*/
|
||||
function onReceiveChildren(cache, action) {
|
||||
let { accessible, response: children, error } = action;
|
||||
|
||||
if (error) {
|
||||
console.warn("Error fetching children", accessible, error);
|
||||
return cache;
|
||||
}
|
||||
|
||||
return updateChildrenCache(new Map(cache), accessible, children);
|
||||
}
|
||||
|
||||
function onReceiveAncestry(cache, action) {
|
||||
let { accessible: acc, response: ancestry, error } = action;
|
||||
|
||||
if (error) {
|
||||
console.warn("Error fetching ancestry", acc, error);
|
||||
return cache;
|
||||
}
|
||||
|
||||
let newCache = new Map(cache);
|
||||
ancestry.forEach(({ accessible, children }) =>
|
||||
updateChildrenCache(newCache, accessible, children));
|
||||
|
||||
return newCache;
|
||||
}
|
||||
|
||||
exports.accessibles = accessibles;
|
|
@ -0,0 +1,45 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { UPDATE_DETAILS, RESET } = require("../constants");
|
||||
|
||||
/**
|
||||
* Initial state definition
|
||||
*/
|
||||
function getInitialState() {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maintain details of a current relevant accessible.
|
||||
*/
|
||||
function details(state = getInitialState(), action) {
|
||||
switch (action.type) {
|
||||
case UPDATE_DETAILS:
|
||||
return onUpdateDetails(state, action);
|
||||
case RESET:
|
||||
return getInitialState();
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle details update for an accessible object
|
||||
* @param {Object} state Current accessible object details.
|
||||
* @param {Object} action Redux action object
|
||||
* @return {Object} updated state
|
||||
*/
|
||||
function onUpdateDetails(state, action) {
|
||||
let { accessible, response: DOMNode, error } = action;
|
||||
if (error) {
|
||||
console.warn("Error fetching DOMNode for accessible", accessible, error);
|
||||
return state;
|
||||
}
|
||||
|
||||
return { accessible, DOMNode };
|
||||
}
|
||||
|
||||
exports.details = details;
|
|
@ -0,0 +1,14 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { accessibles } = require("./accessibles");
|
||||
const { details } = require("./details");
|
||||
const { ui } = require("./ui");
|
||||
|
||||
exports.reducers = {
|
||||
accessibles,
|
||||
details,
|
||||
ui
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'accessibles.js',
|
||||
'details.js',
|
||||
'index.js',
|
||||
'ui.js'
|
||||
)
|
|
@ -0,0 +1,168 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/* global gToolbox */
|
||||
|
||||
const {
|
||||
ENABLE,
|
||||
DISABLE,
|
||||
RESET,
|
||||
SELECT,
|
||||
HIGHLIGHT,
|
||||
UNHIGHLIGHT,
|
||||
UPDATE_CAN_BE_DISABLED,
|
||||
UPDATE_CAN_BE_ENABLED,
|
||||
UPDATE_DETAILS
|
||||
} = require("../constants");
|
||||
|
||||
const TreeView = require("devtools/client/shared/components/tree/TreeView");
|
||||
|
||||
/**
|
||||
* Initial state definition
|
||||
*/
|
||||
function getInitialState() {
|
||||
return {
|
||||
enabled: false,
|
||||
canBeDisabled: true,
|
||||
canBeEnabled: true,
|
||||
selected: null,
|
||||
highlighted: null,
|
||||
expanded: new Set()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maintain ui components of the accessibility panel.
|
||||
*/
|
||||
function ui(state = getInitialState(), action) {
|
||||
switch (action.type) {
|
||||
case ENABLE:
|
||||
return onToggle(state, action, true);
|
||||
case DISABLE:
|
||||
return onToggle(state, action, false);
|
||||
case UPDATE_CAN_BE_DISABLED:
|
||||
return onCanBeDisabledChange(state, action);
|
||||
case UPDATE_CAN_BE_ENABLED:
|
||||
return onCanBeEnabledChange(state, action);
|
||||
case UPDATE_DETAILS:
|
||||
return onUpdateDetails(state, action);
|
||||
case HIGHLIGHT:
|
||||
return onHighlight(state, action);
|
||||
case UNHIGHLIGHT:
|
||||
return onUnhighlight(state, action);
|
||||
case SELECT:
|
||||
return onSelect(state, action);
|
||||
case RESET:
|
||||
return onReset(state, action);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
function onUpdateDetails(state) {
|
||||
if (!state.selected) {
|
||||
return state;
|
||||
}
|
||||
|
||||
// Clear selected state that should only be set when select action is
|
||||
// performed.
|
||||
return Object.assign({}, state, { selected: null });
|
||||
}
|
||||
|
||||
function onUnhighlight(state) {
|
||||
return Object.assign({}, state, { highlighted: null });
|
||||
}
|
||||
|
||||
function updateExpandedNodes(state, ancestry) {
|
||||
let expanded = new Set(state.expanded);
|
||||
let path = ancestry.reduceRight((accPath, { accessible }) => {
|
||||
accPath = TreeView.subPath(accPath, accessible.actorID);
|
||||
expanded.add(accPath);
|
||||
return accPath;
|
||||
}, "");
|
||||
|
||||
return { path, expanded };
|
||||
}
|
||||
|
||||
function onHighlight(state, { accessible, response: ancestry, error }) {
|
||||
if (error) {
|
||||
console.warn("Error fetching ancestry", accessible, error);
|
||||
return state;
|
||||
}
|
||||
|
||||
let { expanded } = updateExpandedNodes(state, ancestry);
|
||||
return Object.assign({}, state, { expanded, highlighted: accessible });
|
||||
}
|
||||
|
||||
function onSelect(state, { accessible, response: ancestry, error }) {
|
||||
if (error) {
|
||||
console.warn("Error fetching ancestry", accessible, error);
|
||||
return state;
|
||||
}
|
||||
|
||||
let { path, expanded } = updateExpandedNodes(state, ancestry);
|
||||
let selected = TreeView.subPath(path, accessible.actorID);
|
||||
|
||||
return Object.assign({}, state, { expanded, selected });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "canBeDisabled" flag update for accessibility service
|
||||
* @param {Object} state Current ui state
|
||||
* @param {Object} action Redux action object
|
||||
* @return {Object} updated state
|
||||
*/
|
||||
function onCanBeDisabledChange(state, { canBeDisabled }) {
|
||||
return Object.assign({}, state, { canBeDisabled });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "canBeEnabled" flag update for accessibility service
|
||||
* @param {Object} state Current ui state.
|
||||
* @param {Object} action Redux action object
|
||||
* @return {Object} updated state
|
||||
*/
|
||||
function onCanBeEnabledChange(state, { canBeEnabled }) {
|
||||
return Object.assign({}, state, { canBeEnabled });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle reset action for the accessibility panel UI.
|
||||
* @param {Object} state Current ui state.
|
||||
* @param {Object} action Redux action object
|
||||
* @return {Object} updated state
|
||||
*/
|
||||
function onReset(state, { accessibility }) {
|
||||
let { enabled, canBeDisabled, canBeEnabled } = accessibility;
|
||||
toggleHighlightTool(enabled);
|
||||
return Object.assign({}, state, { enabled, canBeDisabled, canBeEnabled });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle accessibilty service enabling/disabling.
|
||||
* @param {Object} state Current accessibility services enabled state.
|
||||
* @param {Object} action Redux action object
|
||||
* @param {Boolean} enabled New enabled state.
|
||||
* @return {Object} updated state
|
||||
*/
|
||||
function onToggle(state, { error }, enabled) {
|
||||
if (error) {
|
||||
console.warn("Error enabling accessibility service: ", error);
|
||||
return state;
|
||||
}
|
||||
|
||||
toggleHighlightTool(enabled);
|
||||
return Object.assign({}, state, { enabled });
|
||||
}
|
||||
|
||||
function toggleHighlightTool(enabled) {
|
||||
if (enabled) {
|
||||
gToolbox.highlightTool("accessibility");
|
||||
} else {
|
||||
gToolbox.unhighlightTool("accessibility");
|
||||
}
|
||||
}
|
||||
|
||||
exports.ui = ui;
|
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../../.eslintrc.mochitests.js"
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
[DEFAULT]
|
||||
tags = devtools
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
head.js
|
||||
!/devtools/client/shared/test/shared-head.js
|
||||
!/devtools/client/inspector/test/shared-head.js
|
||||
!/devtools/client/shared/test/shared-redux-head.js
|
||||
|
||||
[browser_accessibility_mutations.js]
|
||||
[browser_accessibility_reload.js]
|
||||
[browser_accessibility_sidebar.js]
|
||||
[browser_accessibility_tree.js]
|
||||
[browser_accessibility_tree_nagivation.js]
|
|
@ -0,0 +1,118 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = `<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Accessibility Panel Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="h1">Top level header</h1>
|
||||
<p id="p">This is a paragraph.</p>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
/**
|
||||
* Test data has the format of:
|
||||
* {
|
||||
* desc {String} description for better logging
|
||||
* action {Function} An optional action that needs to be performed before
|
||||
* the state of the tree and the sidebar can be checked.
|
||||
* expected {JSON} An expected states for the tree and the sidebar.
|
||||
* }
|
||||
*/
|
||||
const tests = [{
|
||||
desc: "Expand first and second rows, select third row.",
|
||||
action: async ({ doc }) => {
|
||||
await toggleRow(doc, 0);
|
||||
await toggleRow(doc, 1);
|
||||
selectRow(doc, 3);
|
||||
},
|
||||
expected: {
|
||||
tree: [{
|
||||
role: "document",
|
||||
name: `"Accessibility Panel Test"`
|
||||
}, {
|
||||
role: "heading",
|
||||
name: `"Top level header"`
|
||||
}, {
|
||||
role: "text leaf",
|
||||
name: `"Top level header"`
|
||||
}, {
|
||||
role: "paragraph",
|
||||
name: `""`
|
||||
}],
|
||||
sidebar: {
|
||||
name: null,
|
||||
role: "paragraph",
|
||||
actions: [],
|
||||
value: "",
|
||||
description: "",
|
||||
help: "",
|
||||
keyboardShortcut: "",
|
||||
childCount: 1,
|
||||
indexInParent: 1,
|
||||
states: ["selectable text", "opaque", "enabled", "sensitive"]
|
||||
}
|
||||
}
|
||||
}, {
|
||||
desc: "Remove a child from a document.",
|
||||
action: async ({ doc, browser }) => {
|
||||
is(doc.querySelectorAll(".treeRow").length, 4, "Tree size is correct.");
|
||||
await ContentTask.spawn(browser, {}, () =>
|
||||
content.document.getElementById("p").remove());
|
||||
await BrowserTestUtils.waitForCondition(() =>
|
||||
doc.querySelectorAll(".treeRow").length === 3, "Tree updated.");
|
||||
},
|
||||
expected: {
|
||||
tree: [{
|
||||
role: "document",
|
||||
name: `"Accessibility Panel Test"`
|
||||
}, {
|
||||
role: "heading",
|
||||
name: `"Top level header"`
|
||||
}, {
|
||||
role: "text leaf",
|
||||
name: `"Top level header"`
|
||||
}],
|
||||
sidebar: {
|
||||
name: "Top level header",
|
||||
role: "text leaf"
|
||||
}
|
||||
}
|
||||
}, {
|
||||
desc: "Update child's text content.",
|
||||
action: async ({ browser }) => {
|
||||
await ContentTask.spawn(browser, {}, () => {
|
||||
content.document.getElementById("h1").textContent = "New Header";
|
||||
});
|
||||
},
|
||||
expected: {
|
||||
tree: [{
|
||||
role: "document",
|
||||
name: `"Accessibility Panel Test"`
|
||||
}, {
|
||||
role: "heading",
|
||||
name: `"New Header"`
|
||||
}, {
|
||||
role: "text leaf",
|
||||
name: `"New Header"`
|
||||
}]
|
||||
}
|
||||
}, {
|
||||
desc: "Select third row in the tree.",
|
||||
action: ({ doc }) => selectRow(doc, 1),
|
||||
expected: {
|
||||
sidebar: {
|
||||
name: "New Header"
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
/**
|
||||
* Test that checks the Accessibility panel after DOM tree mutations.
|
||||
*/
|
||||
addA11yPanelTestsTask(tests, TEST_URI,
|
||||
"Test Accessibility panel after DOM tree mutations.");
|
|
@ -0,0 +1,84 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI_1 = `<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Accessibility Panel Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Top level header</h1>
|
||||
<p>This is a paragraph.</p>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
const TEST_URI_2 = `<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Navigation Accessibility Panel</title>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>`;
|
||||
|
||||
/**
|
||||
* Test data has the format of:
|
||||
* {
|
||||
* desc {String} description for better logging
|
||||
* action {Function} An optional action that needs to be performed before
|
||||
* the state of the tree and the sidebar can be checked.
|
||||
* expected {JSON} An expected states for the tree and the sidebar.
|
||||
* }
|
||||
*/
|
||||
const tests = [{
|
||||
desc: "Test the initial accessibility tree state after first row is expanded.",
|
||||
action: async ({ doc }) => toggleRow(doc, 0),
|
||||
expected: {
|
||||
tree: [{
|
||||
role: "document",
|
||||
name: `"Accessibility Panel Test"`
|
||||
}, {
|
||||
role: "heading",
|
||||
name: `"Top level header"`
|
||||
}, {
|
||||
role: "paragraph",
|
||||
name: `""`
|
||||
}],
|
||||
sidebar: {
|
||||
name: "Accessibility Panel Test",
|
||||
role: "document"
|
||||
}
|
||||
}
|
||||
}, {
|
||||
desc: "Reload the page.",
|
||||
action: async ({ panel }) => reload(panel.target),
|
||||
expected: {
|
||||
tree: [{
|
||||
role: "document",
|
||||
name: `"Accessibility Panel Test"`
|
||||
}],
|
||||
sidebar: {
|
||||
name: "Accessibility Panel Test",
|
||||
role: "document"
|
||||
}
|
||||
}
|
||||
}, {
|
||||
desc: "Navigate to a new page.",
|
||||
action: async ({ panel }) => navigate(panel.target, buildURL(TEST_URI_2)),
|
||||
expected: {
|
||||
tree: [{
|
||||
role: "document",
|
||||
name: `"Navigation Accessibility Panel"`
|
||||
}],
|
||||
sidebar: {
|
||||
name: "Navigation Accessibility Panel",
|
||||
role: "document"
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
/**
|
||||
* Simple test that checks content of the Accessibility panel tree on reload.
|
||||
*/
|
||||
addA11yPanelTestsTask(tests, TEST_URI_1, "Test Accessibility panel tree on reload.");
|
|
@ -0,0 +1,66 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = `<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Accessibility Panel Test</title>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>`;
|
||||
|
||||
/**
|
||||
* Test data has the format of:
|
||||
* {
|
||||
* desc {String} description for better logging
|
||||
* action {Function} An optional action that needs to be performed before
|
||||
* the state of the tree and the sidebar can be checked.
|
||||
* expected {JSON} An expected states for the tree and the sidebar.
|
||||
* }
|
||||
*/
|
||||
const tests = [{
|
||||
desc: "Test the initial accessibility sidebar state.",
|
||||
expected: {
|
||||
sidebar: {
|
||||
name: "Accessibility Panel Test",
|
||||
role: "document",
|
||||
actions: [],
|
||||
value: "",
|
||||
description: "",
|
||||
help: "",
|
||||
keyboardShortcut: "",
|
||||
childCount: 0,
|
||||
indexInParent: 0,
|
||||
states: ["readonly", "focusable", "opaque", "enabled", "sensitive"]
|
||||
}
|
||||
}
|
||||
}, {
|
||||
desc: "Mark document as disabled for accessibility.",
|
||||
action: async ({ browser }) => ContentTask.spawn(browser, {}, () =>
|
||||
content.document.body.setAttribute("aria-disabled", true)),
|
||||
expected: {
|
||||
sidebar: {
|
||||
states: ["unavailable", "readonly", "focusable", "opaque"]
|
||||
}
|
||||
}
|
||||
}, {
|
||||
desc: "Append a new child to the document.",
|
||||
action: async ({ browser }) => ContentTask.spawn(browser, {}, () => {
|
||||
let doc = content.document;
|
||||
let button = doc.createElement("button");
|
||||
button.textContent = "Press Me!";
|
||||
doc.body.appendChild(button);
|
||||
}),
|
||||
expected: {
|
||||
sidebar: {
|
||||
childCount: 1
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
/**
|
||||
* Test that checks the Accessibility panel sidebar.
|
||||
*/
|
||||
addA11yPanelTestsTask(tests, TEST_URI, "Test Accessibility panel sidebar.");
|
|
@ -0,0 +1,63 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = `<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Accessibility Panel Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Top level header</h1>
|
||||
<p>This is a paragraph.</p>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
/**
|
||||
* Test data has the format of:
|
||||
* {
|
||||
* desc {String} description for better logging
|
||||
* action {Function} An optional action that needs to be performed before
|
||||
* the state of the tree and the sidebar can be checked.
|
||||
* expected {JSON} An expected states for the tree and the sidebar.
|
||||
* }
|
||||
*/
|
||||
const tests = [{
|
||||
desc: "Test the initial accessibility tree state.",
|
||||
expected: {
|
||||
tree: [{
|
||||
role: "document",
|
||||
name: `"Accessibility Panel Test"`
|
||||
}]
|
||||
}
|
||||
}, {
|
||||
desc: "Expand first tree node.",
|
||||
action: async ({ doc }) => toggleRow(doc, 0),
|
||||
expected: {
|
||||
tree: [{
|
||||
role: "document",
|
||||
name: `"Accessibility Panel Test"`
|
||||
}, {
|
||||
role: "heading",
|
||||
name: `"Top level header"`
|
||||
}, {
|
||||
role: "paragraph",
|
||||
name: `""`
|
||||
}]
|
||||
}
|
||||
}, {
|
||||
desc: "Collapse first tree node.",
|
||||
action: async ({ doc }) => toggleRow(doc, 0),
|
||||
expected: {
|
||||
tree: [{
|
||||
role: "document",
|
||||
name: `"Accessibility Panel Test"`
|
||||
}]
|
||||
}
|
||||
}];
|
||||
|
||||
/**
|
||||
* Simple test that checks content of the Accessibility panel tree.
|
||||
*/
|
||||
addA11yPanelTestsTask(tests, TEST_URI, "Test Accessibility panel tree.");
|
|
@ -0,0 +1,152 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = `<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Accessibility Panel Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Top level header</h1>
|
||||
<p>This is a paragraph.</p>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
/**
|
||||
* Test data has the format of:
|
||||
* {
|
||||
* desc {String} description for better logging
|
||||
* action {Function} An optional action that needs to be performed before
|
||||
* the state of the tree and the sidebar can be checked.
|
||||
* expected {JSON} An expected states for the tree and the sidebar.
|
||||
* }
|
||||
*/
|
||||
const tests = [{
|
||||
desc: "Test the initial accessibility tree and sidebar states.",
|
||||
expected: {
|
||||
tree: [{
|
||||
role: "document",
|
||||
name: `"Accessibility Panel Test"`
|
||||
}],
|
||||
sidebar: {
|
||||
name: "Accessibility Panel Test",
|
||||
role: "document",
|
||||
actions: [],
|
||||
value: "",
|
||||
description: "",
|
||||
help: "",
|
||||
keyboardShortcut: "",
|
||||
childCount: 2,
|
||||
indexInParent: 0,
|
||||
states: ["readonly", "focusable", "opaque", "enabled", "sensitive"]
|
||||
}
|
||||
}
|
||||
}, {
|
||||
desc: "Expand first tree node.",
|
||||
action: async ({ doc }) => toggleRow(doc, 0),
|
||||
expected: {
|
||||
tree: [{
|
||||
role: "document",
|
||||
name: `"Accessibility Panel Test"`
|
||||
}, {
|
||||
role: "heading",
|
||||
name: `"Top level header"`
|
||||
}, {
|
||||
role: "paragraph",
|
||||
name: `""`
|
||||
} ]
|
||||
}
|
||||
}, {
|
||||
desc: "Expand second tree node.",
|
||||
action: async ({ doc }) => toggleRow(doc, 1),
|
||||
expected: {
|
||||
tree: [{
|
||||
role: "document",
|
||||
name: `"Accessibility Panel Test"`
|
||||
}, {
|
||||
role: "heading",
|
||||
name: `"Top level header"`
|
||||
}, {
|
||||
role: "text leaf",
|
||||
name: `"Top level header"`
|
||||
}, {
|
||||
role: "paragraph",
|
||||
name: `""`
|
||||
}],
|
||||
sidebar: {
|
||||
name: "Top level header",
|
||||
role: "heading",
|
||||
actions: [],
|
||||
value: "",
|
||||
description: "",
|
||||
help: "",
|
||||
keyboardShortcut: "",
|
||||
childCount: 1,
|
||||
indexInParent: 0,
|
||||
states: ["selectable text", "opaque", "enabled", "sensitive"]
|
||||
}
|
||||
}
|
||||
}, {
|
||||
desc: "Select third tree node.",
|
||||
action: ({ doc }) => selectRow(doc, 2),
|
||||
expected: {
|
||||
sidebar: {
|
||||
name: "Top level header",
|
||||
role: "text leaf",
|
||||
actions: [],
|
||||
value: "",
|
||||
description: "",
|
||||
help: "",
|
||||
keyboardShortcut: "",
|
||||
childCount: 0,
|
||||
indexInParent: 0,
|
||||
states: ["opaque", "enabled", "sensitive"]
|
||||
}
|
||||
}
|
||||
}, {
|
||||
desc: "Collapse first tree node.",
|
||||
action: async ({ doc }) => toggleRow(doc, 0),
|
||||
expected: {
|
||||
tree: [{
|
||||
role: "document",
|
||||
name: `"Accessibility Panel Test"`
|
||||
}],
|
||||
sidebar: {
|
||||
name: "Accessibility Panel Test",
|
||||
role: "document",
|
||||
actions: [],
|
||||
value: "",
|
||||
description: "",
|
||||
help: "",
|
||||
keyboardShortcut: "",
|
||||
childCount: 2,
|
||||
indexInParent: 0,
|
||||
states: ["readonly", "focusable", "opaque", "enabled", "sensitive"]
|
||||
}
|
||||
}
|
||||
}, {
|
||||
desc: "Expand first tree node again.",
|
||||
action: async ({ doc }) => toggleRow(doc, 0),
|
||||
expected: {
|
||||
tree: [{
|
||||
role: "document",
|
||||
name: `"Accessibility Panel Test"`
|
||||
}, {
|
||||
role: "heading",
|
||||
name: `"Top level header"`
|
||||
}, {
|
||||
role: "text leaf",
|
||||
name: `"Top level header"`
|
||||
}, {
|
||||
role: "paragraph",
|
||||
name: `""`
|
||||
}]
|
||||
}
|
||||
}];
|
||||
|
||||
/**
|
||||
* Check navigation within the tree.
|
||||
*/
|
||||
addA11yPanelTestsTask(tests, TEST_URI, "Test Accessibility panel tree navigation.");
|
|
@ -0,0 +1,294 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/* import-globals-from ../../shared/test/shared-head.js */
|
||||
/* import-globals-from ../../inspector/test/shared-head.js */
|
||||
|
||||
/* global waitUntilState, gBrowser */
|
||||
/* exported addTestTab, checkTreeState, checkSidebarState, selectRow,
|
||||
toggleRow, addA11yPanelTestsTask, reload, navigate */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Import framework's shared head.
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
|
||||
this);
|
||||
|
||||
// Import inspector's shared head.
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js",
|
||||
this);
|
||||
|
||||
// Load the shared Redux helpers into this compartment.
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/devtools/client/shared/test/shared-redux-head.js",
|
||||
this);
|
||||
|
||||
const { ORDERED_PROPS } = require("devtools/client/accessibility/constants");
|
||||
|
||||
// Enable the Accessibility panel
|
||||
Services.prefs.setBoolPref("devtools.accessibility.enabled", true);
|
||||
|
||||
/**
|
||||
* Wait for accessibility service to shut down. We consider it shut down when
|
||||
* an "a11y-init-or-shutdown" event is received with a value of "0".
|
||||
*/
|
||||
function shutdownA11y() {
|
||||
if (!Services.appinfo.accessibilityEnabled) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Force collections to speed up accessibility service shutdown.
|
||||
Cu.forceGC();
|
||||
Cu.forceCC();
|
||||
Cu.forceShrinkingGC();
|
||||
|
||||
return new Promise(resolve => {
|
||||
let observe = (subject, topic, data) => {
|
||||
if (data === "0") {
|
||||
Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
// This event is coming from Gecko accessibility module when the
|
||||
// accessibility service is shutdown or initialzied. We attempt to shutdown
|
||||
// accessibility service naturally if there are no more XPCOM references to
|
||||
// a11y related objects (after GC/CC).
|
||||
Services.obs.addObserver(observe, "a11y-init-or-shutdown");
|
||||
});
|
||||
}
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
info("Cleaning up...");
|
||||
await shutdownA11y();
|
||||
Services.prefs.clearUserPref("devtools.accessibility.enabled");
|
||||
});
|
||||
|
||||
const EXPANDABLE_PROPS = ["actions", "states", "attributes"];
|
||||
|
||||
/**
|
||||
* Add a new test tab in the browser and load the given url.
|
||||
* @param {String} url
|
||||
* The url to be loaded in the new tab
|
||||
* @return a promise that resolves to the tab object when
|
||||
* the url is loaded
|
||||
*/
|
||||
async function addTestTab(url) {
|
||||
info("Adding a new test tab with URL: '" + url + "'");
|
||||
|
||||
let tab = await addTab(url);
|
||||
let panel = await initAccessibilityPanel(tab);
|
||||
let win = panel.panelWin;
|
||||
let doc = win.document;
|
||||
let store = win.view.store;
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
doc.getElementById("accessibility-enable-button"), win);
|
||||
|
||||
await waitUntilState(store, state =>
|
||||
state.accessibles.size === 1 && state.details.accessible &&
|
||||
state.details.accessible.role === "document");
|
||||
|
||||
// Wait for inspector load here to avoid protocol errors on shutdown, since
|
||||
// accessibility panel test can be too fast.
|
||||
await win.gToolbox.loadTool("inspector");
|
||||
|
||||
return {
|
||||
tab,
|
||||
browser: tab.linkedBrowser,
|
||||
panel,
|
||||
win,
|
||||
doc,
|
||||
store
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn off accessibility features from within the panel. We call it before the
|
||||
* cleanup function to make sure that the panel is still present.
|
||||
*/
|
||||
function disableAccessibilityInspector(env) {
|
||||
let { doc, win } = env;
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
doc.getElementById("accessibility-disable-button"), win);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the Accessibility panel for the given tab.
|
||||
*
|
||||
* @param {nsIDOMElement} tab
|
||||
* Optional tab element for which you want open the Accessibility panel.
|
||||
* The default tab is taken from the global variable |tab|.
|
||||
* @return a promise that is resolved once the panel is open.
|
||||
*/
|
||||
async function initAccessibilityPanel(tab = gBrowser.selectedTab) {
|
||||
let target = TargetFactory.forTab(tab);
|
||||
let toolbox = await gDevTools.showToolbox(target, "accessibility");
|
||||
return toolbox.getCurrentPanel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the state of the accessibility tree.
|
||||
* @param {document} doc panel documnent.
|
||||
* @param {Array} expected an array that represents an expected row list.
|
||||
*/
|
||||
async function checkTreeState(doc, expected) {
|
||||
info("Checking tree state.");
|
||||
let hasExpectedStructure = await BrowserTestUtils.waitForCondition(() =>
|
||||
[...doc.querySelectorAll(".treeRow")].every((row, i) =>
|
||||
row.querySelector(".treeLabelCell").textContent === expected[i].role &&
|
||||
row.querySelector(".treeValueCell").textContent === expected[i].name),
|
||||
"Wait for the right tree update.");
|
||||
|
||||
ok(hasExpectedStructure, "Tree structure is correct.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the state of the accessibility sidebar.
|
||||
* @param {Object} store React store for the panel (includes store for
|
||||
* the sidebar).
|
||||
* @param {Object} expectedState Expected state of the sidebar.
|
||||
*/
|
||||
async function checkSidebarState(store, expectedState) {
|
||||
info("Checking sidebar state.");
|
||||
await waitUntilState(store, ({ details }) => {
|
||||
for (let key of ORDERED_PROPS) {
|
||||
let expected = expectedState[key];
|
||||
if (expected === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (EXPANDABLE_PROPS.includes(key)) {
|
||||
if (JSON.stringify(details.accessible[key]) !== JSON.stringify(expected)) {
|
||||
return false;
|
||||
}
|
||||
} else if (details.accessible && details.accessible[key] !== expected) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ok(true, "Sidebar state is correct.");
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Select tree row.
|
||||
* @param {document} doc panel documnent.
|
||||
* @param {Number} rowNumber number of the row/tree node to be selected.
|
||||
*/
|
||||
function selectRow(doc, rowNumber) {
|
||||
info(`Selecting row ${rowNumber}.`);
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
doc.querySelectorAll(".treeRow")[rowNumber], doc.defaultView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle an expandable tree row.
|
||||
* @param {document} doc panel documnent.
|
||||
* @param {Number} rowNumber number of the row/tree node to be toggled.
|
||||
*/
|
||||
async function toggleRow(doc, rowNumber) {
|
||||
let win = doc.defaultView;
|
||||
let twisty = doc.querySelectorAll(".theme-twisty")[rowNumber];
|
||||
let expected = !twisty.classList.contains("open");
|
||||
|
||||
info(`${expected ? "Expanding" : "Collapsing"} row ${rowNumber}.`);
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "click" }, twisty, win);
|
||||
await BrowserTestUtils.waitForCondition(() =>
|
||||
!twisty.classList.contains("devtools-throbber") &&
|
||||
expected === twisty.classList.contains("open"), "Twisty updated.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over actions/tests structure and test the state of the
|
||||
* accessibility panel.
|
||||
* @param {JSON} tests test data that has the format of:
|
||||
* {
|
||||
* desc {String} description for better logging
|
||||
* action {Function} An optional action that needs to be
|
||||
* performed before the state of the
|
||||
* tree and the sidebar can be checked
|
||||
* expected {JSON} An expected states for the tree and
|
||||
* the sidebar
|
||||
* }
|
||||
* @param {Object} env contains all relevant environment objects (same
|
||||
* structure as the return value of 'addTestTab' funciton)
|
||||
*/
|
||||
async function runA11yPanelTests(tests, env) {
|
||||
for (let { desc, action, expected } of tests) {
|
||||
info(desc);
|
||||
|
||||
if (action) {
|
||||
await action(env);
|
||||
}
|
||||
|
||||
let { tree, sidebar } = expected;
|
||||
if (tree) {
|
||||
await checkTreeState(env.doc, tree);
|
||||
}
|
||||
|
||||
if (sidebar) {
|
||||
await checkSidebarState(env.store, sidebar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a valid URL from an HTML snippet.
|
||||
* @param {String} uri HTML snippet
|
||||
* @return {String} built URL
|
||||
*/
|
||||
function buildURL(uri) {
|
||||
return `data:text/html,${encodeURIComponent(uri)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a test task based on the test structure and a test URL.
|
||||
* @param {JSON} tests test data that has the format of:
|
||||
* {
|
||||
* desc {String} description for better logging
|
||||
* action {Function} An optional action that needs to be
|
||||
* performed before the state of the
|
||||
* tree and the sidebar can be checked
|
||||
* expected {JSON} An expected states for the tree and
|
||||
* the sidebar
|
||||
* }
|
||||
* @param {String} uri test URL
|
||||
* @param {String} msg a message that is printed for the test
|
||||
*/
|
||||
function addA11yPanelTestsTask(tests, uri, msg) {
|
||||
tests.push({
|
||||
desc: "Disable accessibility inspector.",
|
||||
action: env => disableAccessibilityInspector(env),
|
||||
expected: {}
|
||||
});
|
||||
add_task(async function a11yPanelTests() {
|
||||
info(msg);
|
||||
let env = await addTestTab(buildURL(uri));
|
||||
await runA11yPanelTests(tests, env);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload panel target.
|
||||
* @param {Object} target Panel target.
|
||||
* @param {String} waitForTargetEvent Event to wait for after reload.
|
||||
*/
|
||||
function reload(target, waitForTargetEvent = "navigate") {
|
||||
executeSoon(() => target.activeTab.reload());
|
||||
return once(target, waitForTargetEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a new URL within the panel target.
|
||||
* @param {Object} target Panel target.
|
||||
* @param {Srting} url URL to navigate to.
|
||||
* @param {String} waitForTargetEvent Event to wait for after reload.
|
||||
*/
|
||||
function navigate(target, url, waitForTargetEvent = "navigate") {
|
||||
executeSoon(() => target.activeTab.navigateTo(url));
|
||||
return once(target, waitForTargetEvent);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
|
||||
const A11Y_STRINGS_URI = "devtools/client/locales/accessibility.properties";
|
||||
|
||||
exports.L10N = new LocalizationHelper(A11Y_STRINGS_URI);
|
|
@ -0,0 +1,7 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'l10n.js'
|
||||
)
|
|
@ -24,6 +24,7 @@ loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/client/netmon
|
|||
loader.lazyGetter(this, "StoragePanel", () => require("devtools/client/storage/panel").StoragePanel);
|
||||
loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/client/scratchpad/scratchpad-panel").ScratchpadPanel);
|
||||
loader.lazyGetter(this, "DomPanel", () => require("devtools/client/dom/dom-panel").DomPanel);
|
||||
loader.lazyGetter(this, "AccessibilityPanel", () => require("devtools/client/accessibility/accessibility-panel").AccessibilityPanel);
|
||||
|
||||
// Other dependencies
|
||||
loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
|
||||
|
@ -438,6 +439,32 @@ Tools.dom = {
|
|||
}
|
||||
};
|
||||
|
||||
Tools.accessibility = {
|
||||
id: "accessibility",
|
||||
accesskey: l10n("accessibility.accesskey"),
|
||||
ordinal: 14,
|
||||
modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
|
||||
visibilityswitch: "devtools.accessibility.enabled",
|
||||
icon: "chrome://devtools/skin/images/tool-accessibility.svg",
|
||||
url: "chrome://devtools/content/accessibility/accessibility.html",
|
||||
label: l10n("accessibility.label"),
|
||||
panelLabel: l10n("accessibility.panelLabel"),
|
||||
get tooltip() {
|
||||
return l10n("accessibility.tooltip",
|
||||
(osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") +
|
||||
l10n("accessibility.commandkey"));
|
||||
},
|
||||
inMenu: true,
|
||||
|
||||
isTargetSupported(target) {
|
||||
return target.hasActor("accessibility");
|
||||
},
|
||||
|
||||
build(iframeWindow, toolbox) {
|
||||
return new AccessibilityPanel(iframeWindow, toolbox);
|
||||
}
|
||||
};
|
||||
|
||||
var defaultTools = [
|
||||
Tools.options,
|
||||
Tools.webConsole,
|
||||
|
@ -453,6 +480,7 @@ var defaultTools = [
|
|||
Tools.scratchpad,
|
||||
Tools.memory,
|
||||
Tools.dom,
|
||||
Tools.accessibility,
|
||||
];
|
||||
|
||||
exports.defaultTools = defaultTools;
|
||||
|
|
|
@ -48,7 +48,6 @@ class ToolboxController extends Component {
|
|||
this.setCanCloseToolbox = this.setCanCloseToolbox.bind(this);
|
||||
this.setPanelDefinitions = this.setPanelDefinitions.bind(this);
|
||||
this.setToolboxButtons = this.setToolboxButtons.bind(this);
|
||||
this.setCanMinimize = this.setCanMinimize.bind(this);
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
|
@ -162,13 +161,6 @@ class ToolboxController extends Component {
|
|||
this.setState({ toolboxButtons }, this.updateButtonIds);
|
||||
}
|
||||
|
||||
setCanMinimize(canMinimize) {
|
||||
/* Bug 1177463 - The minimize button is currently hidden until we agree on
|
||||
the UI for it, and until bug 1173849 is fixed too. */
|
||||
|
||||
// this.setState({ canMinimize });
|
||||
}
|
||||
|
||||
render() {
|
||||
return ToolboxToolbar(Object.assign({}, this.props, this.state));
|
||||
}
|
||||
|
|
|
@ -81,8 +81,6 @@ skip-if = os == 'win' || debug # Bug 1282269, 1448084
|
|||
[browser_toolbox_hosts_telemetry.js]
|
||||
[browser_toolbox_keyboard_navigation.js]
|
||||
skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
|
||||
[browser_toolbox_minimize.js]
|
||||
skip-if = true # Bug 1177463 - Temporarily hide the minimize button
|
||||
[browser_toolbox_options.js]
|
||||
[browser_toolbox_options_disable_buttons.js]
|
||||
[browser_toolbox_options_disable_cache-01.js]
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that when the toolbox is displayed in a bottom host, that host can be
|
||||
// minimized to just the tabbar height, and maximized again.
|
||||
// Also test that while minimized, switching to a tool, clicking on the
|
||||
// settings, or clicking on the selected tool's tab maximizes the toolbox again.
|
||||
// Finally test that the minimize button doesn't exist in other host types.
|
||||
|
||||
const URL = "data:text/html;charset=utf8,test page";
|
||||
const {Toolbox} = require("devtools/client/framework/toolbox");
|
||||
|
||||
add_task(async function () {
|
||||
info("Create a test tab and open the toolbox");
|
||||
let tab = await addTab(URL);
|
||||
let target = TargetFactory.forTab(tab);
|
||||
let toolbox = await gDevTools.showToolbox(target, "webconsole");
|
||||
|
||||
let button = toolbox.doc.querySelector("#toolbox-dock-bottom-minimize");
|
||||
ok(button, "The minimize button exists in the default bottom host");
|
||||
|
||||
info("Try to minimize the toolbox");
|
||||
await minimize(toolbox);
|
||||
ok(parseInt(toolbox._host.frame.style.marginBottom, 10) < 0,
|
||||
"The toolbox host has been hidden away with a negative-margin");
|
||||
|
||||
info("Try to maximize again the toolbox");
|
||||
await maximize(toolbox);
|
||||
ok(parseInt(toolbox._host.frame.style.marginBottom, 10) == 0,
|
||||
"The toolbox host is shown again");
|
||||
|
||||
info("Try to minimize again using the keyboard shortcut");
|
||||
await minimizeWithShortcut(toolbox);
|
||||
ok(parseInt(toolbox._host.frame.style.marginBottom, 10) < 0,
|
||||
"The toolbox host has been hidden away with a negative-margin");
|
||||
|
||||
info("Try to maximize again using the keyboard shortcut");
|
||||
await maximizeWithShortcut(toolbox);
|
||||
ok(parseInt(toolbox._host.frame.style.marginBottom, 10) == 0,
|
||||
"The toolbox host is shown again");
|
||||
|
||||
info("Minimize again and switch to another tool");
|
||||
await minimize(toolbox);
|
||||
let onMaximized = toolbox._host.once("maximized");
|
||||
await toolbox.selectTool("inspector");
|
||||
await onMaximized;
|
||||
|
||||
info("Minimize again and click on the tab of the current tool");
|
||||
await minimize(toolbox);
|
||||
onMaximized = toolbox._host.once("maximized");
|
||||
let tabButton = toolbox.doc.querySelector("#toolbox-tab-inspector");
|
||||
EventUtils.synthesizeMouseAtCenter(tabButton, {}, toolbox.win);
|
||||
await onMaximized;
|
||||
|
||||
info("Minimize again and click on the settings tab");
|
||||
await minimize(toolbox);
|
||||
onMaximized = toolbox._host.once("maximized");
|
||||
let settingsButton = toolbox.doc.querySelector("#toolbox-tab-options");
|
||||
EventUtils.synthesizeMouseAtCenter(settingsButton, {}, toolbox.win);
|
||||
await onMaximized;
|
||||
|
||||
info("Switch to a different host");
|
||||
await toolbox.switchHost(Toolbox.HostType.SIDE);
|
||||
button = toolbox.doc.querySelector("#toolbox-dock-bottom-minimize");
|
||||
ok(!button, "The minimize button doesn't exist in the side host");
|
||||
|
||||
Services.prefs.clearUserPref("devtools.toolbox.host");
|
||||
await toolbox.destroy();
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
async function minimize(toolbox) {
|
||||
let button = toolbox.doc.querySelector("#toolbox-dock-bottom-minimize");
|
||||
let onMinimized = toolbox._host.once("minimized");
|
||||
EventUtils.synthesizeMouseAtCenter(button, {}, toolbox.win);
|
||||
await onMinimized;
|
||||
}
|
||||
|
||||
async function minimizeWithShortcut(toolbox) {
|
||||
let key = toolbox.doc.getElementById("toolbox-minimize-key")
|
||||
.getAttribute("key");
|
||||
let onMinimized = toolbox._host.once("minimized");
|
||||
EventUtils.synthesizeKey(key, {accelKey: true, shiftKey: true},
|
||||
toolbox.win);
|
||||
await onMinimized;
|
||||
}
|
||||
|
||||
async function maximize(toolbox) {
|
||||
let button = toolbox.doc.querySelector("#toolbox-dock-bottom-minimize");
|
||||
let onMaximized = toolbox._host.once("maximized");
|
||||
EventUtils.synthesizeMouseAtCenter(button, {}, toolbox.win);
|
||||
await onMaximized;
|
||||
}
|
||||
|
||||
async function maximizeWithShortcut(toolbox) {
|
||||
let key = toolbox.doc.getElementById("toolbox-minimize-key")
|
||||
.getAttribute("key");
|
||||
let onMaximized = toolbox._host.once("maximized");
|
||||
EventUtils.synthesizeKey(key, {accelKey: true, shiftKey: true},
|
||||
toolbox.win);
|
||||
await onMaximized;
|
||||
}
|
|
@ -23,23 +23,12 @@ loader.lazyRequireGetter(this, "Hosts", "devtools/client/framework/toolbox-hosts
|
|||
* - switch-host:
|
||||
* Order to display the toolbox in another host (side, bottom, window, or the
|
||||
* previously used one)
|
||||
* - toggle-minimize-mode:
|
||||
* When using the bottom host, the toolbox can be miximized to only display
|
||||
* the tool titles
|
||||
* - maximize-host:
|
||||
* When using the bottom host in minimized mode, revert back to regular mode
|
||||
* in order to see tool titles and the tools
|
||||
* - raise-host:
|
||||
* Focus the tools
|
||||
* - set-host-title:
|
||||
* When using the window host, update the window title
|
||||
*
|
||||
* Messages sent by the chrome to the toolbox:
|
||||
* - host-minimized:
|
||||
* The bottom host is done minimizing (after animation end)
|
||||
* - host-maximized:
|
||||
* The bottom host is done switching back to regular mode (after animation
|
||||
* end)
|
||||
* - switched-host:
|
||||
* The `switch-host` command sent by the toolbox is done
|
||||
*/
|
||||
|
@ -56,8 +45,6 @@ function ToolboxHostManager(target, hostType, hostOptions) {
|
|||
if (!hostType) {
|
||||
hostType = Services.prefs.getCharPref(LAST_HOST);
|
||||
}
|
||||
this.onHostMinimized = this.onHostMinimized.bind(this);
|
||||
this.onHostMaximized = this.onHostMaximized.bind(this);
|
||||
this.host = this.createHost(hostType, hostOptions);
|
||||
this.hostType = hostType;
|
||||
}
|
||||
|
@ -121,15 +108,9 @@ ToolboxHostManager.prototype = {
|
|||
case "switch-host":
|
||||
this.switchHost(event.data.hostType);
|
||||
break;
|
||||
case "maximize-host":
|
||||
this.host.maximize();
|
||||
break;
|
||||
case "raise-host":
|
||||
this.host.raise();
|
||||
break;
|
||||
case "toggle-minimize-mode":
|
||||
this.host.toggleMinimizeMode(event.data.toolbarHeight);
|
||||
break;
|
||||
case "set-host-title":
|
||||
this.host.setTitle(event.data.title);
|
||||
break;
|
||||
|
@ -167,24 +148,9 @@ ToolboxHostManager.prototype = {
|
|||
}
|
||||
|
||||
let newHost = new Hosts[hostType](this.target.tab, options);
|
||||
// Update the label and icon when the state changes.
|
||||
newHost.on("minimized", this.onHostMinimized);
|
||||
newHost.on("maximized", this.onHostMaximized);
|
||||
return newHost;
|
||||
},
|
||||
|
||||
onHostMinimized() {
|
||||
this.postMessage({
|
||||
name: "host-minimized"
|
||||
});
|
||||
},
|
||||
|
||||
onHostMaximized() {
|
||||
this.postMessage({
|
||||
name: "host-maximized"
|
||||
});
|
||||
},
|
||||
|
||||
async switchHost(hostType) {
|
||||
if (hostType == "previous") {
|
||||
// Switch to the last used host for the toolbox UI.
|
||||
|
@ -243,8 +209,6 @@ ToolboxHostManager.prototype = {
|
|||
}
|
||||
this.host.frame.removeEventListener("unload", this, true);
|
||||
|
||||
this.host.off("minimized", this.onHostMinimized);
|
||||
this.host.off("maximized", this.onHostMaximized);
|
||||
return this.host.destroy();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -101,63 +101,6 @@ BottomHost.prototype = {
|
|||
focusTab(this.hostTab);
|
||||
},
|
||||
|
||||
/**
|
||||
* Minimize this host so that only the toolbox tabbar remains visible.
|
||||
* @param {Number} height The height to minimize to. Defaults to 0, which
|
||||
* means that the toolbox won't be visible at all once minimized.
|
||||
*/
|
||||
minimize: function(height = 0) {
|
||||
if (this.isMinimized) {
|
||||
return;
|
||||
}
|
||||
this.isMinimized = true;
|
||||
|
||||
let onTransitionEnd = event => {
|
||||
if (event.propertyName !== "margin-bottom") {
|
||||
// Ignore transitionend on unrelated properties.
|
||||
return;
|
||||
}
|
||||
|
||||
this.frame.removeEventListener("transitionend", onTransitionEnd);
|
||||
this.emit("minimized");
|
||||
};
|
||||
this.frame.addEventListener("transitionend", onTransitionEnd);
|
||||
this.frame.style.marginBottom = -this.frame.height + height + "px";
|
||||
this._splitter.classList.add("disabled");
|
||||
},
|
||||
|
||||
/**
|
||||
* If the host was minimized before, maximize it again (the host will be
|
||||
* maximized to the height it previously had).
|
||||
*/
|
||||
maximize: function() {
|
||||
if (!this.isMinimized) {
|
||||
return;
|
||||
}
|
||||
this.isMinimized = false;
|
||||
|
||||
let onTransitionEnd = event => {
|
||||
if (event.propertyName !== "margin-bottom") {
|
||||
// Ignore transitionend on unrelated properties.
|
||||
return;
|
||||
}
|
||||
|
||||
this.frame.removeEventListener("transitionend", onTransitionEnd);
|
||||
this.emit("maximized");
|
||||
};
|
||||
this.frame.addEventListener("transitionend", onTransitionEnd);
|
||||
this.frame.style.marginBottom = "0";
|
||||
this._splitter.classList.remove("disabled");
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle the minimize mode.
|
||||
* @param {Number} minHeight The height to minimize to.
|
||||
*/
|
||||
toggleMinimizeMode: function(minHeight) {
|
||||
this.isMinimized ? this.maximize() : this.minimize(minHeight);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the toolbox title.
|
||||
* Nothing to do for this host type.
|
||||
|
|
|
@ -141,12 +141,7 @@ function Toolbox(target, selectedTool, hostType, contentWindow, frameId) {
|
|||
this._onBrowserMessage = this._onBrowserMessage.bind(this);
|
||||
this._showDevEditionPromo = this._showDevEditionPromo.bind(this);
|
||||
this._updateTextBoxMenuItems = this._updateTextBoxMenuItems.bind(this);
|
||||
this._onBottomHostMinimized = this._onBottomHostMinimized.bind(this);
|
||||
this._onBottomHostMaximized = this._onBottomHostMaximized.bind(this);
|
||||
this._onToolSelectWhileMinimized = this._onToolSelectWhileMinimized.bind(this);
|
||||
this._onPerformanceFrontEvent = this._onPerformanceFrontEvent.bind(this);
|
||||
this._onBottomHostWillChange = this._onBottomHostWillChange.bind(this);
|
||||
this._toggleMinimizeMode = this._toggleMinimizeMode.bind(this);
|
||||
this._onToolbarFocus = this._onToolbarFocus.bind(this);
|
||||
this._onToolbarArrowKeypress = this._onToolbarArrowKeypress.bind(this);
|
||||
this._onPickerClick = this._onPickerClick.bind(this);
|
||||
|
@ -891,11 +886,6 @@ Toolbox.prototype = {
|
|||
this.selectPreviousTool();
|
||||
event.preventDefault();
|
||||
});
|
||||
this.shortcuts.on(L10N.getStr("toolbox.minimize.key"),
|
||||
event => {
|
||||
this._toggleMinimizeMode();
|
||||
event.preventDefault();
|
||||
});
|
||||
this.shortcuts.on(L10N.getStr("toolbox.toggleHost.key"),
|
||||
event => {
|
||||
this.switchToPreviousHost();
|
||||
|
@ -920,23 +910,8 @@ Toolbox.prototype = {
|
|||
|
||||
// Called whenever the chrome send a message
|
||||
_onBrowserMessage: function(event) {
|
||||
if (!event.data) {
|
||||
return;
|
||||
}
|
||||
switch (event.data.name) {
|
||||
case "switched-host":
|
||||
this._onSwitchedHost(event.data);
|
||||
break;
|
||||
case "host-minimized":
|
||||
if (this.hostType == Toolbox.HostType.BOTTOM) {
|
||||
this._onBottomHostMinimized();
|
||||
}
|
||||
break;
|
||||
case "host-maximized":
|
||||
if (this.hostType == Toolbox.HostType.BOTTOM) {
|
||||
this._onBottomHostMaximized();
|
||||
}
|
||||
break;
|
||||
if (event.data && event.data.name === "switched-host") {
|
||||
this._onSwitchedHost(event.data);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1082,16 +1057,6 @@ Toolbox.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
// Bottom-type host can be minimized, add a button for this.
|
||||
if (this.hostType == Toolbox.HostType.BOTTOM) {
|
||||
this.component.setCanMinimize(true);
|
||||
|
||||
// Maximize again when a tool gets selected.
|
||||
this.on("before-select", this._onToolSelectWhileMinimized);
|
||||
// Maximize and stop listening before the host type changes.
|
||||
this.once("host-will-change", this._onBottomHostWillChange);
|
||||
}
|
||||
|
||||
this.component.setDockButtonsEnabled(true);
|
||||
this.component.setCanCloseToolbox(this.hostType !== Toolbox.HostType.WINDOW);
|
||||
|
||||
|
@ -1115,20 +1080,6 @@ Toolbox.prototype = {
|
|||
this.component.setHostTypes(hostTypes);
|
||||
},
|
||||
|
||||
_onBottomHostMinimized: function() {
|
||||
this.component.setMinimizeState("minimized");
|
||||
},
|
||||
|
||||
_onBottomHostMaximized: function() {
|
||||
this.component.setMinimizeState("maximized");
|
||||
},
|
||||
|
||||
_onToolSelectWhileMinimized: function() {
|
||||
this.postMessage({
|
||||
name: "maximize-host"
|
||||
});
|
||||
},
|
||||
|
||||
postMessage: function(msg) {
|
||||
// We sometime try to send messages in middle of destroy(), where the
|
||||
// toolbox iframe may already be detached and no longer have a parent.
|
||||
|
@ -1140,29 +1091,6 @@ Toolbox.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_onBottomHostWillChange: function() {
|
||||
this.postMessage({
|
||||
name: "maximize-host"
|
||||
});
|
||||
|
||||
this.off("before-select", this._onToolSelectWhileMinimized);
|
||||
},
|
||||
|
||||
_toggleMinimizeMode: function() {
|
||||
if (this.hostType !== Toolbox.HostType.BOTTOM) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the height to which the host should be minimized so the
|
||||
// tabbar is still visible.
|
||||
let toolbarHeight = this._componentMount.getBoxQuads({box: "content"})[0].bounds
|
||||
.height;
|
||||
this.postMessage({
|
||||
name: "toggle-minimize-mode",
|
||||
toolbarHeight
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Initiate ToolboxTabs React component and all it's properties. Do the initial render.
|
||||
*/
|
||||
|
@ -1189,7 +1117,6 @@ Toolbox.prototype = {
|
|||
selectTool: this.selectTool,
|
||||
closeToolbox: this.destroy,
|
||||
focusButton: this._onToolbarFocus,
|
||||
toggleMinimizeMode: this._toggleMinimizeMode,
|
||||
toolbox: this
|
||||
});
|
||||
|
||||
|
@ -1871,8 +1798,6 @@ Toolbox.prototype = {
|
|||
* The id of the tool to switch to
|
||||
*/
|
||||
selectTool: function(id) {
|
||||
this.emit("before-select", id);
|
||||
|
||||
if (this.currentToolId == id) {
|
||||
let panel = this._toolPanels.get(id);
|
||||
if (panel) {
|
||||
|
|
|
@ -106,6 +106,8 @@ devtools.jar:
|
|||
content/responsive.html/index.js (responsive.html/index.js)
|
||||
content/dom/dom.html (dom/dom.html)
|
||||
content/dom/main.js (dom/main.js)
|
||||
content/accessibility/accessibility.html (accessibility/accessibility.html)
|
||||
content/accessibility/main.js (accessibility/main.js)
|
||||
% skin devtools classic/1.0 %skin/
|
||||
skin/devtools-browser.css (themes/devtools-browser.css)
|
||||
skin/dark-theme.css (themes/dark-theme.css)
|
||||
|
@ -114,6 +116,7 @@ devtools.jar:
|
|||
skin/toolbars.css (themes/toolbars.css)
|
||||
skin/toolbox.css (themes/toolbox.css)
|
||||
skin/tooltips.css (themes/tooltips.css)
|
||||
skin/images/accessibility.svg (themes/images/accessibility.svg)
|
||||
skin/images/add.svg (themes/images/add.svg)
|
||||
skin/images/breadcrumbs-divider.svg (themes/images/breadcrumbs-divider.svg)
|
||||
skin/images/filters.svg (themes/images/filters.svg)
|
||||
|
@ -145,6 +148,7 @@ devtools.jar:
|
|||
skin/images/command-screenshot.svg (themes/images/command-screenshot.svg)
|
||||
skin/images/command-responsivemode.svg (themes/images/command-responsivemode.svg)
|
||||
skin/images/command-pick.svg (themes/images/command-pick.svg)
|
||||
skin/images/command-pick-accessibility.svg (themes/images/command-pick-accessibility.svg)
|
||||
skin/images/command-frames.svg (themes/images/command-frames.svg)
|
||||
skin/images/command-console.svg (themes/images/command-console.svg)
|
||||
skin/images/command-eyedropper.svg (themes/images/command-eyedropper.svg)
|
||||
|
@ -224,6 +228,7 @@ devtools.jar:
|
|||
skin/images/tool-webaudio.svg (themes/images/tool-webaudio.svg)
|
||||
skin/images/tool-memory.svg (themes/images/tool-memory.svg)
|
||||
skin/images/tool-dom.svg (themes/images/tool-dom.svg)
|
||||
skin/images/tool-accessibility.svg (themes/images/tool-accessibility.svg)
|
||||
skin/images/close.svg (themes/images/close.svg)
|
||||
skin/images/clear.svg (themes/images/clear.svg)
|
||||
skin/images/vview-delete.png (themes/images/vview-delete.png)
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# LOCALIZATION NOTE These strings are used inside the Accessibility panel
|
||||
# which is available from the Web Developer sub-menu -> 'Accessibility'.
|
||||
# The correct localization of this file might be to keep it in
|
||||
# English, or another language commonly spoken among web developers.
|
||||
# You want to make that choice consistent across the developer tools.
|
||||
# A good criteria is the language in which you'd find the best
|
||||
# documentation on web development on the web.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.role): A title text used for Accessibility
|
||||
# tree header column that represents accessible element role.
|
||||
accessibility.role=Role
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.name): A title text used for Accessibility
|
||||
# tree header column that represents accessible element name.
|
||||
accessibility.name=Name
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.logo): A title text used for Accessibility
|
||||
# logo used on the accessibility panel landing page.
|
||||
accessibility.logo=Accessibility Logo
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.properties): A title text used for header
|
||||
# for Accessibility details sidebar.
|
||||
accessibility.properties=Properties
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.treeName): A title text used for
|
||||
# Accessibility tree (that represents accessible element name) container.
|
||||
accessibility.treeName=Accessibility Tree
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.accessible.notAvailable): A title text
|
||||
# displayed when accessible sidebar panel does not have an accessible object to
|
||||
# display.
|
||||
accessibility.accessible.notAvailable=Accessible Information Unavailable
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.enable): A title text for Enable
|
||||
# accessibility button used to enable accessibility service.
|
||||
accessibility.enable=Turn On Accessibility Features
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.enabling): A title text for Enable
|
||||
# accessibility button used when accessibility service is being enabled.
|
||||
accessibility.enabling=Turning on accessibility features…
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.disable): A title text for Disable
|
||||
# accessibility button used to disable accessibility service.
|
||||
accessibility.disable=Turn Off Accessibility Features
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.disabling): A title text for Disable
|
||||
# accessibility button used when accessibility service is being
|
||||
# disabled.
|
||||
accessibility.disabling=Turning off accessibility features…
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.pick): A title text for Picker button
|
||||
# button used to pick accessible objects from the page.
|
||||
accessibility.pick=Pick accessible object from the page
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.disable.disabledTitle): A title text used for
|
||||
# a tooltip for Disable accessibility button when accessibility service can not
|
||||
# be disabled. It is the case when a user is using a 3rd party accessibility
|
||||
# tool such as screen reader.
|
||||
accessibility.disable.disabledTitle=Accessibility service can not be turned off. It is used outside Developer Tools.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.disable.enabledTitle): A title text used for
|
||||
# a tooltip for Disable accessibility button when accessibility service can be
|
||||
# disabled.
|
||||
accessibility.disable.enabledTitle=Accessibility service will be turned off for all tabs and windows.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.enable.disabledTitle): A title text used for
|
||||
# a tooltip for Enabled accessibility button when accessibility service can not
|
||||
# be enabled.
|
||||
accessibility.enable.disabledTitle=Accessibility service can not be turned on. It is turned off via accessibility services privacy preference.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.enable.enabledTitle): A title text used for
|
||||
# a tooltip for Enabled accessibility button when accessibility service can be
|
||||
# enabled.
|
||||
accessibility.enable.enabledTitle=Accessibility service will be turned on for all tabs and windows.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.description.general): A title text used when
|
||||
# accessibility service description is provided before accessibility inspector
|
||||
# is enabled.
|
||||
accessibility.description.general=Accessibility features are deactivated by default because they negatively impact performance. Consider turning off accessibility features before using other Developer Tools panels.
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.description.oldVersion): A title text used
|
||||
# when accessibility service description is provided when a client is connected
|
||||
# to an older version of accessibility actor.
|
||||
accessibility.description.oldVersion=You are connected to a debugger server that is too old. To use Accessibility panel, please connect to the latest debugger server version.
|
|
@ -247,6 +247,25 @@ dom.accesskey=D
|
|||
# Keyboard shortcut for DOM panel will be shown inside the brackets.
|
||||
dom.tooltip=DOM (%S)
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.label):
|
||||
# This string is displayed in the title of the tab when the Accessibility panel
|
||||
# is displayed inside the developer tools window and in the Developer Tools Menu.
|
||||
accessibility.label=Accessibility
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.panelLabel):
|
||||
# This is used as the label for the toolbox panel.
|
||||
accessibility.panelLabel=Accessibility Panel
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.accesskey)
|
||||
# Used for the menuitem in the tool menu
|
||||
accessibility.accesskey=y
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.tooltip):
|
||||
# This string is displayed in the tooltip of the tab when the Accessibility is
|
||||
# displayed inside the developer tools window.
|
||||
# Keyboard shortcut for Accessibility panel will be shown inside the brackets.
|
||||
accessibility.tooltip=Accessibility (%S)
|
||||
|
||||
# LOCALIZATION NOTE (toolbox.buttons.splitconsole):
|
||||
# This is the tooltip of the button in the toolbox toolbar used to toggle
|
||||
# the split console.
|
||||
|
|
|
@ -6,20 +6,6 @@ toolboxDockButtons.bottom.tooltip=Dock to bottom of browser window
|
|||
toolboxDockButtons.side.tooltip=Dock to side of browser window
|
||||
toolboxDockButtons.window.tooltip=Show in separate window
|
||||
|
||||
# LOCALIZATION NOTE (toolboxDockButtons.bottom.minimize): This string is shown
|
||||
# as a tooltip that appears in the toolbox when it is in "bottom host" mode and
|
||||
# when hovering over the minimize button in the toolbar. When clicked, the
|
||||
# button minimizes the toolbox so that just the toolbar is visible at the
|
||||
# bottom.
|
||||
toolboxDockButtons.bottom.minimize=Minimize the toolbox
|
||||
|
||||
# LOCALIZATION NOTE (toolboxDockButtons.bottom.maximize): This string is shown
|
||||
# as a tooltip that appears in the toolbox when it is in "bottom host" mode and
|
||||
# when hovering over the maximize button in the toolbar. When clicked, the
|
||||
# button maximizes the toolbox again (if it had been minimized before) so that
|
||||
# the whole toolbox is visible again.
|
||||
toolboxDockButtons.bottom.maximize=Maximize the toolbox
|
||||
|
||||
# LOCALIZATION NOTE (toolboxToggleButton.errors): Semi-colon list of plural
|
||||
# forms.
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
|
@ -151,10 +137,6 @@ toolbox.reload2.key=F5
|
|||
toolbox.forceReload.key=CmdOrCtrl+Shift+R
|
||||
toolbox.forceReload2.key=CmdOrCtrl+F5
|
||||
|
||||
# LOCALIZATION NOTE (toolbox.minimize.key)
|
||||
# Key shortcut used to minimize the toolbox
|
||||
toolbox.minimize.key=CmdOrCtrl+Shift+U
|
||||
|
||||
# LOCALIZATION NOTE (toolbox.toggleHost.key)
|
||||
# Key shortcut used to move the toolbox in bottom or side of the browser window
|
||||
toolbox.toggleHost.key=CmdOrCtrl+Shift+D
|
||||
|
|
|
@ -8,6 +8,7 @@ include('../templates.mozbuild')
|
|||
|
||||
DIRS += [
|
||||
'aboutdebugging',
|
||||
'accessibility',
|
||||
'animationinspector',
|
||||
'canvasdebugger',
|
||||
'commandline',
|
||||
|
|
|
@ -230,6 +230,9 @@ pref("devtools.scratchpad.enabled", false);
|
|||
// Make sure the DOM panel is hidden by default
|
||||
pref("devtools.dom.enabled", false);
|
||||
|
||||
// Make sure the Accessibility panel is hidden by default
|
||||
pref("devtools.accessibility.enabled", false);
|
||||
|
||||
// Web Audio Editor Inspector Width should be a preference
|
||||
pref("devtools.webaudioeditor.inspectorWidth", 300);
|
||||
|
||||
|
|
|
@ -254,10 +254,16 @@ AutocompletePopup.prototype = {
|
|||
}
|
||||
|
||||
let max = 0;
|
||||
for (let {label, count} of this.items) {
|
||||
|
||||
for (let {label, postLabel, count} of this.items) {
|
||||
if (count) {
|
||||
label += count + "";
|
||||
}
|
||||
|
||||
if (postLabel) {
|
||||
label += postLabel;
|
||||
}
|
||||
|
||||
max = Math.max(label.length, max);
|
||||
}
|
||||
|
||||
|
@ -422,6 +428,9 @@ AutocompletePopup.prototype = {
|
|||
* that will be auto completed. When this property is
|
||||
* present, |preLabel.length| starting characters will be
|
||||
* removed from label.
|
||||
* - postLabel {String} [Optional] The string that will be displayed
|
||||
* after the label. Currently used to display the value of
|
||||
* a desired variable.
|
||||
* - count {Number} [Optional] The number to represent the count of
|
||||
* autocompleted label.
|
||||
*/
|
||||
|
@ -431,12 +440,15 @@ AutocompletePopup.prototype = {
|
|||
listItem.setAttribute("id", "autocomplete-item-" + itemIdCounter++);
|
||||
listItem.className = "autocomplete-item";
|
||||
listItem.setAttribute("data-index", this.items.length);
|
||||
|
||||
if (this.direction) {
|
||||
listItem.setAttribute("dir", this.direction);
|
||||
}
|
||||
|
||||
let label = this._document.createElementNS(HTML_NS, "span");
|
||||
label.textContent = item.label;
|
||||
label.className = "autocomplete-value";
|
||||
|
||||
if (item.preLabel) {
|
||||
let preDesc = this._document.createElementNS(HTML_NS, "span");
|
||||
preDesc.textContent = item.preLabel;
|
||||
|
@ -444,7 +456,16 @@ AutocompletePopup.prototype = {
|
|||
listItem.appendChild(preDesc);
|
||||
label.textContent = item.label.slice(item.preLabel.length);
|
||||
}
|
||||
|
||||
listItem.appendChild(label);
|
||||
|
||||
if (item.postLabel) {
|
||||
let postDesc = this._document.createElementNS(HTML_NS, "span");
|
||||
postDesc.textContent = item.postLabel;
|
||||
postDesc.className = "autocomplete-postlabel";
|
||||
listItem.appendChild(postDesc);
|
||||
}
|
||||
|
||||
if (item.count && item.count > 1) {
|
||||
let countDesc = this._document.createElementNS(HTML_NS, "span");
|
||||
countDesc.textContent = item.count;
|
||||
|
|
|
@ -1332,7 +1332,10 @@ InplaceEditor.prototype = {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let list = [];
|
||||
let postLabelValues = [];
|
||||
|
||||
if (this.contentType == CONTENT_TYPES.CSS_PROPERTY) {
|
||||
list = this._getCSSPropertyList();
|
||||
} else if (this.contentType == CONTENT_TYPES.CSS_VALUE) {
|
||||
|
@ -1350,6 +1353,7 @@ InplaceEditor.prototype = {
|
|||
if (varMatch && varMatch.length == 2) {
|
||||
startCheckQuery = varMatch[1];
|
||||
list = this._getCSSVariableNames();
|
||||
postLabelValues = list.map(varName => this._getCSSVariableValue(varName));
|
||||
} else {
|
||||
list = ["!important",
|
||||
...this._getCSSValuesForPropertyName(this.property.name)];
|
||||
|
@ -1415,7 +1419,8 @@ InplaceEditor.prototype = {
|
|||
count++;
|
||||
finalList.push({
|
||||
preLabel: startCheckQuery,
|
||||
label: list[i]
|
||||
label: list[i],
|
||||
postLabel: postLabelValues[i] ? postLabelValues[i] : ""
|
||||
});
|
||||
} else if (count > 0) {
|
||||
// Since count was incremented, we had already crossed the entries
|
||||
|
@ -1518,6 +1523,17 @@ InplaceEditor.prototype = {
|
|||
_getCSSVariableNames: function() {
|
||||
return Array.from(this.cssVariables.keys()).sort();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the variable's value for the given CSS variable name.
|
||||
*
|
||||
* @param {String} varName
|
||||
* The variable name to retrieve the value of
|
||||
* @return {String} the variable value to the given CSS variable name
|
||||
*/
|
||||
_getCSSVariableValue: function(varName) {
|
||||
return this.cssVariables.get(varName);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,34 +21,37 @@ loadHelperScript("helper_inplace_editor.js");
|
|||
// expected input box value after keypress,
|
||||
// selected suggestion index (-1 if popup is hidden),
|
||||
// number of suggestions in the popup (0 if popup is hidden),
|
||||
// expected post label corresponding with the input box value,
|
||||
// ]
|
||||
const testData = [
|
||||
["v", "v", -1, 0],
|
||||
["a", "va", -1, 0],
|
||||
["r", "var", -1, 0],
|
||||
["(", "var(", -1, 0],
|
||||
["-", "var(--abc", 0, 2],
|
||||
["VK_BACK_SPACE", "var(-", -1, 0],
|
||||
["-", "var(--abc", 0, 2],
|
||||
["VK_DOWN", "var(--def", 1, 2],
|
||||
["VK_DOWN", "var(--abc", 0, 2],
|
||||
["VK_LEFT", "var(--abc", -1, 0],
|
||||
["v", "v", -1, 0, null],
|
||||
["a", "va", -1, 0, null],
|
||||
["r", "var", -1, 0, null],
|
||||
["(", "var(", -1, 0, null],
|
||||
["-", "var(--abc", 0, 4, "blue"],
|
||||
["VK_BACK_SPACE", "var(-", -1, 0, null],
|
||||
["-", "var(--abc", 0, 4, "blue"],
|
||||
["VK_DOWN", "var(--def", 1, 4, "red"],
|
||||
["VK_DOWN", "var(--ghi", 2, 4, "green"],
|
||||
["VK_DOWN", "var(--jkl", 3, 4, "yellow"],
|
||||
["VK_DOWN", "var(--abc", 0, 4, "blue"],
|
||||
["VK_DOWN", "var(--def", 1, 4, "red"],
|
||||
["VK_LEFT", "var(--def", -1, 0, null],
|
||||
];
|
||||
|
||||
const CSS_VARIABLES = [
|
||||
["--abc", "blue"],
|
||||
["--def", "red"],
|
||||
["--ghi", "green"],
|
||||
["--jkl", "yellow"]
|
||||
];
|
||||
|
||||
const mockGetCSSValuesForPropertyName = function(propertyName) {
|
||||
return [];
|
||||
};
|
||||
|
||||
const mockGetCSSVariableNames = function() {
|
||||
return [
|
||||
"--abc",
|
||||
"--def",
|
||||
];
|
||||
};
|
||||
|
||||
add_task(async function() {
|
||||
await addTab("data:text/html;charset=utf-8," +
|
||||
"inplace editor CSS variable autocomplete");
|
||||
await addTab("data:text/html;charset=utf-8,inplace editor CSS variable autocomplete");
|
||||
let [host, win, doc] = await createHost();
|
||||
|
||||
let xulDocument = win.top.document;
|
||||
|
@ -61,6 +64,7 @@ add_task(async function() {
|
|||
property: {
|
||||
name: "color"
|
||||
},
|
||||
cssVariables: new Map(CSS_VARIABLES),
|
||||
done: resolve,
|
||||
popup: popup
|
||||
}, doc);
|
||||
|
@ -74,7 +78,6 @@ add_task(async function() {
|
|||
let runAutocompletionTest = async function(editor) {
|
||||
info("Starting to test for css variable completion");
|
||||
editor._getCSSValuesForPropertyName = mockGetCSSValuesForPropertyName;
|
||||
editor._getCSSVariableNames = mockGetCSSVariableNames;
|
||||
|
||||
for (let data of testData) {
|
||||
await testCompletion(data, editor);
|
||||
|
|
|
@ -72,10 +72,11 @@ function createSpan(doc) {
|
|||
* - {String} completion, the expected value of the auto-completion
|
||||
* - {Number} index, the index of the selected suggestion in the popup
|
||||
* - {Number} total, the total number of suggestions in the popup
|
||||
* - {String} postLabel, the expected post label for the selected suggestion
|
||||
* @param {InplaceEditor} editor
|
||||
* The InplaceEditor instance being tested
|
||||
*/
|
||||
async function testCompletion([key, completion, index, total], editor) {
|
||||
async function testCompletion([key, completion, index, total, postLabel], editor) {
|
||||
info("Pressing key " + key);
|
||||
info("Expecting " + completion);
|
||||
|
||||
|
@ -105,6 +106,14 @@ async function testCompletion([key, completion, index, total], editor) {
|
|||
if (completion !== null) {
|
||||
is(editor.input.value, completion, "Correct value is autocompleted");
|
||||
}
|
||||
|
||||
if (postLabel) {
|
||||
let selectedItem = editor.popup.getItems()[index];
|
||||
let selectedElement = editor.popup.elements.get(selectedItem);
|
||||
ok(selectedElement.textContent.includes(postLabel),
|
||||
"Selected popup element contains the expected post-label");
|
||||
}
|
||||
|
||||
if (total === 0) {
|
||||
ok(!(editor.popup && editor.popup.isOpen), "Popup is closed");
|
||||
} else {
|
||||
|
|
|
@ -101,6 +101,12 @@ html|button, html|select {
|
|||
.devtools-autocomplete-listbox .autocomplete-item > .autocomplete-value {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.devtools-autocomplete-listbox .autocomplete-item > .autocomplete-postlabel {
|
||||
font-style: italic;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.devtools-autocomplete-listbox .autocomplete-item > .autocomplete-count {
|
||||
|
|
|
@ -5,11 +5,6 @@
|
|||
@import url("resource://devtools/client/themes/splitters.css");
|
||||
@import url("resource://devtools/client/themes/commandline-browser.css");
|
||||
|
||||
/* Bottom-docked toolbox minimize transition */
|
||||
.devtools-toolbox-bottom-iframe {
|
||||
transition: margin-bottom .1s;
|
||||
}
|
||||
|
||||
.devtools-toolbox-side-iframe {
|
||||
min-width: 465px;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="context-fill">
|
||||
<path d="M8,0C3.6,0,0,3.6,0,8s3.6,8,8,8s8-3.6,8-8S12.4,0,8,0z M8,2.7c0.7,0,1.3,0.6,1.3,1.3S8.7,5.3,8,5.3
|
||||
S6.7,4.7,6.7,4S7.3,2.7,8,2.7z M12,6.9H9.7v3v2.7c0,0.4-0.3,0.7-0.7,0.7S8.3,13,8.3,12.6V9.9H7.7v2.7c0,0.4-0.3,0.7-0.7,0.7
|
||||
S6.3,13,6.3,12.6V7.4V7H4C3.6,7,3.3,6.7,3.3,6.3c0-0.4,0.3-0.7,0.7-0.7h2.3h3.3h2.4c0.3,0,0.6,0.2,0.6,0.5C12.7,6.6,12.4,6.9,12,6.9
|
||||
z"/>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 661 B |
|
@ -0,0 +1,9 @@
|
|||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="context-fill #0b0b0b">
|
||||
<path d="M15,7.5V2.8c0-0.6-0.5-1-1-1H2c-0.6,0-1,0.4-1,1v10c0,0.5,0.5,1,1,1h6.8l-0.4-1H2v-10h12v4.3L15,7.5z"/>
|
||||
<circle cx="11.5" cy="7.1" r="1"/>
|
||||
<path d="M14.6,8.4H8.5C8.2,8.4,8,8.6,8,8.9s0.2,0.5,0.5,0.5h1.8v4.2c0,0.3,0.2,0.6,0.5,0.6s0.5-0.3,0.5-0.6v-2.1
|
||||
h0.5v2.1c0,0.3,0.2,0.6,0.5,0.6s0.5-0.3,0.5-0.6V9.3h1.8c0.3,0,0.5-0.2,0.5-0.5C15,8.5,14.8,8.4,14.6,8.4z"/>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 701 B |
|
@ -0,0 +1,8 @@
|
|||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" fill="none" stroke="context-fill #0b0b0b" stroke-width="0.8">
|
||||
<path d="M8,2.7c0.7,0,1.3,0.6,1.3,1.3S8.7,5.3,8,5.3S6.7,4.7,6.7,4S7.3,2.7,8,2.7z"/>
|
||||
<path d="M12,6.9H9.7v3v2.7c0,0.4-0.3,0.7-0.7,0.7S8.3,13,8.3,12.6V9.9H7.7v2.7c0,0.4-0.3,0.7-0.7,0.7S6.3,13,6.3,12.6
|
||||
V7.4V7H4C3.6,7,3.3,6.7,3.3,6.3c0-0.4,0.3-0.7,0.7-0.7h2.3h3.3H12c0.3,0,0.6,0.2,0.6,0.5C12.7,6.6,12.4,6.9,12,6.9z"/>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 669 B |
|
@ -14,6 +14,7 @@
|
|||
--command-responsive-image: url(images/command-responsivemode.svg);
|
||||
--command-scratchpad-image: url(images/tool-scratchpad.svg);
|
||||
--command-pick-image: url(images/command-pick.svg);
|
||||
--command-pick-accessibility-image: url(images/command-pick-accessibility.svg);
|
||||
--command-frames-image: url(images/command-frames.svg);
|
||||
--command-splitconsole-image: url(images/command-console.svg);
|
||||
--command-noautohide-image: url(images/command-noautohide.svg);
|
||||
|
@ -231,14 +232,6 @@
|
|||
background-image: var(--dock-undock-image);
|
||||
}
|
||||
|
||||
#toolbox-dock-bottom-minimize::before {
|
||||
background-image: url("chrome://devtools/skin/images/dock-bottom-minimize@2x.png");
|
||||
}
|
||||
|
||||
#toolbox-dock-bottom-minimize.minimized::before {
|
||||
background-image: url("chrome://devtools/skin/images/dock-bottom-maximize@2x.png");
|
||||
}
|
||||
|
||||
/* Command buttons */
|
||||
|
||||
.command-button,
|
||||
|
@ -279,6 +272,10 @@
|
|||
background-image: var(--command-pick-image);
|
||||
}
|
||||
|
||||
#command-button-pick.accessibility::before {
|
||||
background-image: var(--command-pick-accessibility-image);
|
||||
}
|
||||
|
||||
#command-button-splitconsole::before {
|
||||
background-image: var(--command-splitconsole-image);
|
||||
}
|
||||
|
|
|
@ -167,6 +167,12 @@ XPCOMUtils.defineLazyGetter(this, "KeyShortcuts", function() {
|
|||
shortcut: KeyShortcutsBundle.GetStringFromName("dom.commandkey"),
|
||||
modifiers
|
||||
},
|
||||
// Key for opening the Accessibility Panel
|
||||
{
|
||||
toolId: "accessibility",
|
||||
shortcut: KeyShortcutsBundle.GetStringFromName("accessibility.commandkey"),
|
||||
modifiers
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
|
|
|
@ -65,3 +65,7 @@ storage.commandkey=VK_F9
|
|||
# LOCALIZATION NOTE (dom.commandkey):
|
||||
# Key pressed to open a toolbox with the DOM panel selected
|
||||
dom.commandkey=W
|
||||
|
||||
# LOCALIZATION NOTE (accessibility.commandkey):
|
||||
# Key pressed to open a toolbox with the accessibility panel selected
|
||||
accessibility.commandkey=Y
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
#include "mozilla/dom/ChromeMessageBroadcaster.h"
|
||||
#include "AccessCheck.h"
|
||||
#include "mozilla/HoldDropJSObjects.h"
|
||||
#include "mozilla/dom/ContentParent.h"
|
||||
#include "mozilla/dom/MessageManagerBinding.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
ChromeMessageBroadcaster::ChromeMessageBroadcaster(nsFrameMessageManager* aParentManager,
|
||||
ChromeMessageBroadcaster::ChromeMessageBroadcaster(ChromeMessageBroadcaster* aParentManager,
|
||||
MessageManagerFlags aFlags)
|
||||
: MessageListenerManager(nullptr, aParentManager,
|
||||
aFlags |
|
||||
|
@ -43,5 +44,28 @@ ChromeMessageBroadcaster::WrapObject(JSContext* aCx,
|
|||
return ChromeMessageBroadcasterBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
void
|
||||
ChromeMessageBroadcaster::ReleaseCachedProcesses()
|
||||
{
|
||||
ContentParent::ReleaseCachedProcesses();
|
||||
}
|
||||
|
||||
void
|
||||
ChromeMessageBroadcaster::AddChildManager(MessageListenerManager* aManager)
|
||||
{
|
||||
mChildManagers.AppendElement(aManager);
|
||||
|
||||
RefPtr<nsFrameMessageManager> kungfuDeathGrip = this;
|
||||
RefPtr<nsFrameMessageManager> kungfuDeathGrip2 = aManager;
|
||||
|
||||
LoadPendingScripts(this, aManager);
|
||||
}
|
||||
|
||||
void
|
||||
ChromeMessageBroadcaster::RemoveChildManager(MessageListenerManager* aManager)
|
||||
{
|
||||
mChildManagers.RemoveElement(aManager);
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -22,14 +22,21 @@ public:
|
|||
MessageManagerFlags::MM_PROCESSMANAGER |
|
||||
MessageManagerFlags::MM_OWNSCALLBACK)));
|
||||
}
|
||||
explicit ChromeMessageBroadcaster(nsFrameMessageManager* aParentManager)
|
||||
explicit ChromeMessageBroadcaster(ChromeMessageBroadcaster* aParentManager)
|
||||
: ChromeMessageBroadcaster(aParentManager, MessageManagerFlags::MM_NONE)
|
||||
{}
|
||||
|
||||
static ChromeMessageBroadcaster* From(nsFrameMessageManager* aManager)
|
||||
{
|
||||
if (aManager->IsBroadcaster() && aManager->IsChrome()) {
|
||||
return static_cast<ChromeMessageBroadcaster*>(aManager);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
using nsFrameMessageManager::BroadcastAsyncMessage;
|
||||
void BroadcastAsyncMessage(JSContext* aCx, const nsAString& aMessageName,
|
||||
JS::Handle<JS::Value> aObj,
|
||||
JS::Handle<JSObject*> aObjects,
|
||||
|
@ -42,22 +49,22 @@ public:
|
|||
{
|
||||
return mChildManagers.Length();
|
||||
}
|
||||
using nsFrameMessageManager::GetChildAt;
|
||||
MessageListenerManager* GetChildAt(uint32_t aIndex)
|
||||
{
|
||||
return mChildManagers.SafeElementAt(aIndex);
|
||||
}
|
||||
// XPCOM ReleaseCachedProcesses is OK
|
||||
void ReleaseCachedProcesses();
|
||||
|
||||
// ProcessScriptLoader
|
||||
using nsFrameMessageManager::LoadProcessScript;
|
||||
void LoadProcessScript(const nsAString& aUrl, bool aAllowDelayedLoad,
|
||||
mozilla::ErrorResult& aError)
|
||||
{
|
||||
LoadScript(aUrl, aAllowDelayedLoad, false, aError);
|
||||
}
|
||||
// XPCOM RemoveDelayedProcessScript is OK
|
||||
using nsFrameMessageManager::GetDelayedProcessScripts;
|
||||
void RemoveDelayedProcessScript(const nsAString& aURL)
|
||||
{
|
||||
RemoveDelayedScript(aURL);
|
||||
}
|
||||
void GetDelayedProcessScripts(JSContext* aCx,
|
||||
nsTArray<nsTArray<JS::Value>>& aScripts,
|
||||
mozilla::ErrorResult& aError)
|
||||
|
@ -66,16 +73,18 @@ public:
|
|||
}
|
||||
|
||||
// GlobalProcessScriptLoader
|
||||
// XPCOM GetInitialProcessData is OK
|
||||
using nsFrameMessageManager::GetInitialProcessData;
|
||||
|
||||
// FrameScriptLoader
|
||||
using nsFrameMessageManager::LoadFrameScript;
|
||||
void LoadFrameScript(const nsAString& aUrl, bool aAllowDelayedLoad,
|
||||
bool aRunInGlobalScope, mozilla::ErrorResult& aError)
|
||||
{
|
||||
LoadScript(aUrl, aAllowDelayedLoad, aRunInGlobalScope, aError);
|
||||
}
|
||||
using nsFrameMessageManager::GetDelayedFrameScripts;
|
||||
void RemoveDelayedFrameScript(const nsAString& aURL)
|
||||
{
|
||||
RemoveDelayedScript(aURL);
|
||||
}
|
||||
void GetDelayedFrameScripts(JSContext* aCx,
|
||||
nsTArray<nsTArray<JS::Value>>& aScripts,
|
||||
mozilla::ErrorResult& aError)
|
||||
|
@ -83,8 +92,11 @@ public:
|
|||
GetDelayedScripts(aCx, aScripts, aError);
|
||||
}
|
||||
|
||||
void AddChildManager(MessageListenerManager* aManager);
|
||||
void RemoveChildManager(MessageListenerManager* aManager);
|
||||
|
||||
private:
|
||||
ChromeMessageBroadcaster(nsFrameMessageManager* aParentManager,
|
||||
ChromeMessageBroadcaster(ChromeMessageBroadcaster* aParentManager,
|
||||
MessageManagerFlags aFlags);
|
||||
virtual ~ChromeMessageBroadcaster();
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace mozilla {
|
|||
namespace dom {
|
||||
|
||||
ChromeMessageSender::ChromeMessageSender(ipc::MessageManagerCallback* aCallback,
|
||||
nsFrameMessageManager* aParentManager,
|
||||
ChromeMessageBroadcaster* aParentManager,
|
||||
MessageManagerFlags aFlags)
|
||||
: MessageSender(aCallback, aParentManager, aFlags | MessageManagerFlags::MM_CHROME)
|
||||
{
|
||||
|
|
|
@ -12,25 +12,28 @@
|
|||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class ChromeMessageBroadcaster;
|
||||
|
||||
class ChromeMessageSender final : public MessageSender
|
||||
{
|
||||
public:
|
||||
ChromeMessageSender(ipc::MessageManagerCallback* aCallback,
|
||||
nsFrameMessageManager* aParentManager,
|
||||
ChromeMessageBroadcaster* aParentManager,
|
||||
MessageManagerFlags aFlags=MessageManagerFlags::MM_NONE);
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
// ProcessScriptLoader
|
||||
using nsFrameMessageManager::LoadProcessScript;
|
||||
void LoadProcessScript(const nsAString& aUrl, bool aAllowDelayedLoad,
|
||||
mozilla::ErrorResult& aError)
|
||||
{
|
||||
LoadScript(aUrl, aAllowDelayedLoad, false, aError);
|
||||
}
|
||||
// XPCOM RemoveDelayedProcessScript is OK
|
||||
using nsFrameMessageManager::GetDelayedProcessScripts;
|
||||
void RemoveDelayedProcessScript(const nsAString& aURL)
|
||||
{
|
||||
RemoveDelayedScript(aURL);
|
||||
}
|
||||
void GetDelayedProcessScripts(JSContext* aCx,
|
||||
nsTArray<nsTArray<JS::Value>>& aScripts,
|
||||
mozilla::ErrorResult& aError)
|
||||
|
@ -39,13 +42,15 @@ public:
|
|||
}
|
||||
|
||||
// FrameScriptLoader
|
||||
using nsFrameMessageManager::LoadFrameScript;
|
||||
void LoadFrameScript(const nsAString& aUrl, bool aAllowDelayedLoad,
|
||||
bool aRunInGlobalScope, mozilla::ErrorResult& aError)
|
||||
{
|
||||
LoadScript(aUrl, aAllowDelayedLoad, aRunInGlobalScope, aError);
|
||||
}
|
||||
using nsFrameMessageManager::GetDelayedFrameScripts;
|
||||
void RemoveDelayedFrameScript(const nsAString& aURL)
|
||||
{
|
||||
RemoveDelayedScript(aURL);
|
||||
}
|
||||
void GetDelayedFrameScripts(JSContext* aCx,
|
||||
nsTArray<nsTArray<JS::Value>>& aScripts,
|
||||
mozilla::ErrorResult& aError)
|
||||
|
|
|
@ -1703,10 +1703,9 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
|||
|
||||
UpdateEditableState(false);
|
||||
|
||||
// If we had a pre-existing XBL binding,
|
||||
// we might have anonymous children that also need to be told that they are
|
||||
// moving.
|
||||
if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) && !GetShadowRoot()) {
|
||||
// If we had a pre-existing XBL binding, we might have anonymous children that
|
||||
// also need to be told that they are moving.
|
||||
if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
|
||||
nsXBLBinding* binding =
|
||||
OwnerDoc()->BindingManager()->GetBindingWithContent(this);
|
||||
|
||||
|
@ -1768,8 +1767,7 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
|||
}
|
||||
|
||||
// Call BindToTree on shadow root children.
|
||||
ShadowRoot* shadowRoot = GetShadowRoot();
|
||||
if (shadowRoot) {
|
||||
if (ShadowRoot* shadowRoot = GetShadowRoot()) {
|
||||
shadowRoot->SetIsComposedDocParticipant(IsInComposedDoc());
|
||||
for (nsIContent* child = shadowRoot->GetFirstChild(); child;
|
||||
child = child->GetNextSibling()) {
|
||||
|
@ -1989,11 +1987,9 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
|
|||
}
|
||||
|
||||
if (document) {
|
||||
// Notify XBL- & nsIAnonymousContentCreator-generated
|
||||
// anonymous content that the document is changing.
|
||||
// Unlike XBL, bindings for web components shadow DOM
|
||||
// do not get uninstalled.
|
||||
if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) && !GetShadowRoot()) {
|
||||
if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
|
||||
// Notify XBL- & nsIAnonymousContentCreator-generated anonymous content
|
||||
// that the document is changing.
|
||||
nsContentUtils::AddScriptRunner(
|
||||
new RemoveFromBindingManagerRunnable(
|
||||
document->BindingManager(), this, document));
|
||||
|
|
|
@ -723,7 +723,8 @@ public:
|
|||
* Get the current value of the attribute. This returns a form that is
|
||||
* suitable for passing back into SetAttr.
|
||||
*
|
||||
* @param aNameSpaceID the namespace of the attr
|
||||
* @param aNameSpaceID the namespace of the attr (defaults to
|
||||
kNameSpaceID_None in the overload that omits this arg)
|
||||
* @param aName the name of the attr
|
||||
* @param aResult the value (may legitimately be the empty string) [OUT]
|
||||
* @returns true if the attribute was set (even when set to empty string)
|
||||
|
@ -733,14 +734,26 @@ public:
|
|||
*/
|
||||
bool GetAttr(int32_t aNameSpaceID, nsAtom* aName, nsAString& aResult) const;
|
||||
|
||||
bool GetAttr(nsAtom* aName, nsAString& aResult) const
|
||||
{
|
||||
return GetAttr(kNameSpaceID_None, aName, aResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an attribute has been set (empty string or otherwise).
|
||||
*
|
||||
* @param aNameSpaceId the namespace id of the attribute
|
||||
* @param aNameSpaceId the namespace id of the attribute (defaults to
|
||||
kNameSpaceID_None in the overload that omits this arg)
|
||||
* @param aAttr the attribute name
|
||||
* @return whether an attribute exists
|
||||
*/
|
||||
inline bool HasAttr(int32_t aNameSpaceID, nsAtom* aName) const;
|
||||
|
||||
bool HasAttr(nsAtom* aAttr) const
|
||||
{
|
||||
return HasAttr(kNameSpaceID_None, aAttr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether this Element's given attribute has the given value. If the
|
||||
* attribute is not set at all, this will return false.
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace mozilla {
|
|||
namespace dom {
|
||||
|
||||
MessageListenerManager::MessageListenerManager(ipc::MessageManagerCallback* aCallback,
|
||||
nsFrameMessageManager* aParentManager,
|
||||
ChromeMessageBroadcaster* aParentManager,
|
||||
ipc::MessageManagerFlags aFlags)
|
||||
: nsFrameMessageManager(aCallback, aFlags),
|
||||
mParentManager(aParentManager)
|
||||
|
|
|
@ -22,12 +22,12 @@ public:
|
|||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(MessageListenerManager,
|
||||
nsFrameMessageManager)
|
||||
|
||||
nsISupports* GetParentObject()
|
||||
ChromeMessageBroadcaster* GetParentObject()
|
||||
{
|
||||
return ToSupports(mParentManager.get());
|
||||
return mParentManager;
|
||||
}
|
||||
|
||||
virtual nsFrameMessageManager* GetParentManager() override
|
||||
virtual ChromeMessageBroadcaster* GetParentManager() override
|
||||
{
|
||||
return mParentManager;
|
||||
}
|
||||
|
@ -40,11 +40,11 @@ public:
|
|||
|
||||
protected:
|
||||
MessageListenerManager(ipc::MessageManagerCallback* aCallback,
|
||||
nsFrameMessageManager* aParentManager,
|
||||
ChromeMessageBroadcaster* aParentManager,
|
||||
MessageManagerFlags aFlags);
|
||||
virtual ~MessageListenerManager();
|
||||
|
||||
RefPtr<nsFrameMessageManager> mParentManager;
|
||||
RefPtr<ChromeMessageBroadcaster> mParentManager;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/dom/MessageManagerGlobal.h"
|
||||
#include "mozilla/IntentionalCrash.h"
|
||||
#include "mozilla/dom/DOMPrefs.h"
|
||||
#include "nsContentUtils.h"
|
||||
|
||||
#ifdef ANDROID
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
#ifdef XP_WIN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
void
|
||||
MessageManagerGlobal::Dump(const nsAString& aStr)
|
||||
{
|
||||
if (!DOMPrefs::DumpEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
__android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", NS_ConvertUTF16toUTF8(aStr).get());
|
||||
#endif
|
||||
#ifdef XP_WIN
|
||||
if (IsDebuggerPresent()) {
|
||||
OutputDebugStringW(PromiseFlatString(aStr).get());
|
||||
}
|
||||
#endif
|
||||
fputs(NS_ConvertUTF16toUTF8(aStr).get(), stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void
|
||||
MessageManagerGlobal::PrivateNoteIntentionalCrash(ErrorResult& aError)
|
||||
{
|
||||
if (XRE_IsContentProcess()) {
|
||||
NoteIntentionalCrash("tab");
|
||||
return;
|
||||
}
|
||||
aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
void
|
||||
MessageManagerGlobal::Atob(const nsAString& aAsciiString, nsAString& aBase64Data,
|
||||
ErrorResult& aError)
|
||||
{
|
||||
aError = nsContentUtils::Atob(aAsciiString, aBase64Data);
|
||||
}
|
||||
|
||||
void
|
||||
MessageManagerGlobal::Btoa(const nsAString& aBase64Data, nsAString& aAsciiString,
|
||||
ErrorResult& aError)
|
||||
{
|
||||
aError = nsContentUtils::Btoa(aBase64Data, aAsciiString);
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
|
@ -78,7 +78,7 @@ public:
|
|||
mMessageManager->SendAsyncMessage(aCx, aMessageName, aObj, aObjects,
|
||||
aPrincipal, aTransfers, aError);
|
||||
}
|
||||
already_AddRefed<nsIMessageSender> GetProcessMessageManager(mozilla::ErrorResult& aError)
|
||||
already_AddRefed<ChromeMessageSender> GetProcessMessageManager(mozilla::ErrorResult& aError)
|
||||
{
|
||||
if (!mMessageManager) {
|
||||
aError.Throw(NS_ERROR_NULL_POINTER);
|
||||
|
@ -127,44 +127,16 @@ public:
|
|||
}
|
||||
|
||||
// MessageManagerGlobal
|
||||
void Dump(const nsAString& aStr, ErrorResult& aError)
|
||||
{
|
||||
if (!mMessageManager) {
|
||||
aError.Throw(NS_ERROR_NULL_POINTER);
|
||||
return;
|
||||
}
|
||||
aError = mMessageManager->Dump(aStr);
|
||||
}
|
||||
void PrivateNoteIntentionalCrash(ErrorResult& aError)
|
||||
{
|
||||
if (!mMessageManager) {
|
||||
aError.Throw(NS_ERROR_NULL_POINTER);
|
||||
return;
|
||||
}
|
||||
aError = mMessageManager->PrivateNoteIntentionalCrash();
|
||||
}
|
||||
void Atob(const nsAString& aAsciiString, nsAString& aBase64Data,
|
||||
ErrorResult& aError)
|
||||
{
|
||||
if (!mMessageManager) {
|
||||
aError.Throw(NS_ERROR_NULL_POINTER);
|
||||
return;
|
||||
}
|
||||
aError = mMessageManager->Atob(aAsciiString, aBase64Data);
|
||||
}
|
||||
void Btoa(const nsAString& aBase64Data, nsAString& aAsciiString,
|
||||
ErrorResult& aError)
|
||||
{
|
||||
if (!mMessageManager) {
|
||||
aError.Throw(NS_ERROR_NULL_POINTER);
|
||||
return;
|
||||
}
|
||||
aError = mMessageManager->Btoa(aBase64Data, aAsciiString);
|
||||
}
|
||||
void Dump(const nsAString& aStr);
|
||||
void PrivateNoteIntentionalCrash(ErrorResult& aError);
|
||||
void Atob(const nsAString& aAsciiString, nsAString& aBase64Data, ErrorResult& aError);
|
||||
void Btoa(const nsAString& aBase64Data, nsAString& aAsciiString, ErrorResult& aError);
|
||||
|
||||
bool MarkForCC()
|
||||
void MarkForCC()
|
||||
{
|
||||
return mMessageManager && mMessageManager->MarkForCC();
|
||||
if (mMessageManager) {
|
||||
mMessageManager->MarkForCC();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
|
|
@ -25,7 +25,7 @@ MessageSender::InitWithCallback(ipc::MessageManagerCallback* aCallback)
|
|||
}
|
||||
|
||||
for (uint32_t i = 0; i < mPendingScripts.Length(); ++i) {
|
||||
LoadFrameScript(mPendingScripts[i], false, mPendingScriptsGlobalStates[i]);
|
||||
LoadScript(mPendingScripts[i], false, mPendingScriptsGlobalStates[i], IgnoreErrors());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ public:
|
|||
|
||||
protected:
|
||||
MessageSender(ipc::MessageManagerCallback* aCallback,
|
||||
nsFrameMessageManager* aParentManager,
|
||||
ChromeMessageBroadcaster* aParentManager,
|
||||
MessageManagerFlags aFlags)
|
||||
: MessageListenerManager(aCallback, aParentManager, aFlags)
|
||||
{}
|
||||
|
|
|
@ -74,7 +74,7 @@ PostMessageEvent::Run()
|
|||
RefPtr<nsGlobalWindowInner> targetWindow;
|
||||
if (mTargetWindow->IsClosedOrClosing() ||
|
||||
!(targetWindow = mTargetWindow->GetCurrentInnerWindowInternal()) ||
|
||||
targetWindow->IsClosedOrClosing())
|
||||
targetWindow->IsDying())
|
||||
return NS_OK;
|
||||
|
||||
JSAutoCompartment ac(cx, targetWindow->GetWrapper());
|
||||
|
|
|
@ -59,20 +59,18 @@ ProcessGlobal::GetOwnPropertyNames(JSContext* aCx, JS::AutoIdVector& aNames,
|
|||
ProcessGlobal*
|
||||
ProcessGlobal::Get()
|
||||
{
|
||||
nsCOMPtr<nsISyncMessageSender> service = do_GetService(NS_CHILDPROCESSMESSAGEMANAGER_CONTRACTID);
|
||||
nsCOMPtr<nsIGlobalObject> service = do_GetService(NS_CHILDPROCESSMESSAGEMANAGER_CONTRACTID);
|
||||
if (!service) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<ProcessGlobal*>(service.get());
|
||||
}
|
||||
|
||||
// This method isn't automatically forwarded safely because it's notxpcom, so
|
||||
// the IDL binding doesn't know what value to return.
|
||||
NS_IMETHODIMP_(bool)
|
||||
void
|
||||
ProcessGlobal::MarkForCC()
|
||||
{
|
||||
MarkScopesForCC();
|
||||
return MessageManagerGlobal::MarkForCC();
|
||||
MessageManagerGlobal::MarkForCC();
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(ProcessGlobal)
|
||||
|
@ -96,11 +94,8 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ProcessGlobal)
|
||||
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentProcessMessageManager)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIMessageListenerManager)
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMessageSender)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIMessageSender)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISyncMessageSender)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIContentProcessMessageManager)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace mozilla {
|
|||
namespace dom {
|
||||
|
||||
class ProcessGlobal :
|
||||
public nsIContentProcessMessageManager,
|
||||
public nsIMessageSender,
|
||||
public nsMessageManagerScriptExecutor,
|
||||
public nsIGlobalObject,
|
||||
public nsIScriptObjectPrincipal,
|
||||
|
@ -46,13 +46,16 @@ public:
|
|||
bool aEnumerableOnly, ErrorResult& aRv);
|
||||
|
||||
using ipc::MessageManagerCallback::GetProcessMessageManager;
|
||||
using MessageManagerGlobal::GetProcessMessageManager;
|
||||
|
||||
bool Init();
|
||||
|
||||
static ProcessGlobal* Get();
|
||||
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(ProcessGlobal, nsIContentProcessMessageManager)
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(ProcessGlobal, nsIMessageSender)
|
||||
|
||||
void MarkForCC();
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override
|
||||
|
@ -67,15 +70,6 @@ public:
|
|||
using MessageManagerGlobal::RemoveMessageListener;
|
||||
using MessageManagerGlobal::AddWeakMessageListener;
|
||||
using MessageManagerGlobal::RemoveWeakMessageListener;
|
||||
using MessageManagerGlobal::SendAsyncMessage;
|
||||
using MessageManagerGlobal::GetProcessMessageManager;
|
||||
using MessageManagerGlobal::GetRemoteType;
|
||||
using MessageManagerGlobal::SendSyncMessage;
|
||||
using MessageManagerGlobal::SendRpcMessage;
|
||||
using MessageManagerGlobal::Dump;
|
||||
using MessageManagerGlobal::PrivateNoteIntentionalCrash;
|
||||
using MessageManagerGlobal::Atob;
|
||||
using MessageManagerGlobal::Btoa;
|
||||
|
||||
// ContentProcessMessageManager
|
||||
void GetInitialProcessData(JSContext* aCx,
|
||||
|
@ -89,11 +83,7 @@ public:
|
|||
mMessageManager->GetInitialProcessData(aCx, aInitialProcessData, aError);
|
||||
}
|
||||
|
||||
NS_FORWARD_SAFE_NSIMESSAGELISTENERMANAGER(mMessageManager)
|
||||
NS_FORWARD_SAFE_NSIMESSAGESENDER(mMessageManager)
|
||||
NS_FORWARD_SAFE_NSISYNCMESSAGESENDER(mMessageManager)
|
||||
NS_FORWARD_SAFE_NSIMESSAGEMANAGERGLOBAL(mMessageManager)
|
||||
NS_FORWARD_SAFE_NSICONTENTPROCESSMESSAGEMANAGER(mMessageManager)
|
||||
|
||||
virtual void LoadScript(const nsAString& aURL);
|
||||
|
||||
|
|
|
@ -81,12 +81,35 @@ ShadowRoot::~ShadowRoot()
|
|||
host->RemoveMutationObserver(this);
|
||||
}
|
||||
|
||||
if (IsComposedDocParticipant()) {
|
||||
OwnerDoc()->RemoveComposedDocShadowRoot(*this);
|
||||
}
|
||||
|
||||
MOZ_DIAGNOSTIC_ASSERT(!OwnerDoc()->IsComposedDocShadowRoot(*this));
|
||||
|
||||
UnsetFlags(NODE_IS_IN_SHADOW_TREE);
|
||||
|
||||
// nsINode destructor expects mSubtreeRoot == this.
|
||||
SetSubtreeRootPointer(this);
|
||||
}
|
||||
|
||||
void
|
||||
ShadowRoot::SetIsComposedDocParticipant(bool aIsComposedDocParticipant)
|
||||
{
|
||||
bool changed = mIsComposedDocParticipant != aIsComposedDocParticipant;
|
||||
mIsComposedDocParticipant = aIsComposedDocParticipant;
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsIDocument* doc = OwnerDoc();
|
||||
if (IsComposedDocParticipant()) {
|
||||
doc->AddComposedDocShadowRoot(*this);
|
||||
} else {
|
||||
doc->RemoveComposedDocShadowRoot(*this);
|
||||
}
|
||||
}
|
||||
|
||||
JSObject*
|
||||
ShadowRoot::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
||||
{
|
||||
|
|
|
@ -186,10 +186,7 @@ public:
|
|||
return mIsComposedDocParticipant;
|
||||
}
|
||||
|
||||
void SetIsComposedDocParticipant(bool aIsComposedDocParticipant)
|
||||
{
|
||||
mIsComposedDocParticipant = aIsComposedDocParticipant;
|
||||
}
|
||||
void SetIsComposedDocParticipant(bool aIsComposedDocParticipant);
|
||||
|
||||
nsresult GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
|
||||
|
||||
|
|
|
@ -418,7 +418,7 @@ TimeoutManager::TimeoutManager(nsGlobalWindowInner& aWindow)
|
|||
|
||||
TimeoutManager::~TimeoutManager()
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(mWindow.AsInner()->InnerObjectsFreed());
|
||||
MOZ_DIAGNOSTIC_ASSERT(mWindow.IsDying());
|
||||
MOZ_DIAGNOSTIC_ASSERT(!mThrottleTimeoutsTimer);
|
||||
|
||||
mExecutor->Shutdown();
|
||||
|
@ -1297,7 +1297,7 @@ void
|
|||
TimeoutManager::MaybeStartThrottleTimeout()
|
||||
{
|
||||
if (gTimeoutThrottlingDelay <= 0 ||
|
||||
mWindow.AsInner()->InnerObjectsFreed() || mWindow.IsSuspended()) {
|
||||
mWindow.IsDying() || mWindow.IsSuspended()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -278,6 +278,7 @@ UNIFIED_SOURCES += [
|
|||
'Link.cpp',
|
||||
'Location.cpp',
|
||||
'MessageListenerManager.cpp',
|
||||
'MessageManagerGlobal.cpp',
|
||||
'MessageSender.cpp',
|
||||
'Navigator.cpp',
|
||||
'NodeInfo.cpp',
|
||||
|
|
|
@ -86,27 +86,24 @@ nsCCUncollectableMarker::Init()
|
|||
}
|
||||
|
||||
static void
|
||||
MarkChildMessageManagers(nsIMessageBroadcaster* aMM)
|
||||
MarkChildMessageManagers(ChromeMessageBroadcaster* aMM)
|
||||
{
|
||||
aMM->MarkForCC();
|
||||
|
||||
uint32_t tabChildCount = 0;
|
||||
aMM->GetChildCount(&tabChildCount);
|
||||
uint32_t tabChildCount = aMM->ChildCount();
|
||||
for (uint32_t j = 0; j < tabChildCount; ++j) {
|
||||
nsCOMPtr<nsIMessageListenerManager> childMM;
|
||||
aMM->GetChildAt(j, getter_AddRefs(childMM));
|
||||
RefPtr<MessageListenerManager> childMM = aMM->GetChildAt(j);
|
||||
if (!childMM) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIMessageBroadcaster> strongNonLeafMM = do_QueryInterface(childMM);
|
||||
nsIMessageBroadcaster* nonLeafMM = strongNonLeafMM;
|
||||
RefPtr<ChromeMessageBroadcaster> strongNonLeafMM =
|
||||
ChromeMessageBroadcaster::From(childMM);
|
||||
ChromeMessageBroadcaster* nonLeafMM = strongNonLeafMM;
|
||||
|
||||
nsCOMPtr<nsIMessageSender> strongTabMM = do_QueryInterface(childMM);
|
||||
nsIMessageSender* tabMM = strongTabMM;
|
||||
MessageListenerManager* tabMM = childMM;
|
||||
|
||||
strongNonLeafMM = nullptr;
|
||||
strongTabMM = nullptr;
|
||||
childMM = nullptr;
|
||||
|
||||
if (nonLeafMM) {
|
||||
|
@ -118,8 +115,7 @@ MarkChildMessageManagers(nsIMessageBroadcaster* aMM)
|
|||
|
||||
//XXX hack warning, but works, since we know that
|
||||
// callback is frameloader.
|
||||
mozilla::dom::ipc::MessageManagerCallback* cb =
|
||||
static_cast<nsFrameMessageManager*>(tabMM)->GetCallback();
|
||||
mozilla::dom::ipc::MessageManagerCallback* cb = tabMM->GetCallback();
|
||||
if (cb) {
|
||||
nsFrameLoader* fl = static_cast<nsFrameLoader*>(cb);
|
||||
EventTarget* et = fl->GetTabChildGlobalAsEventTarget();
|
||||
|
@ -150,27 +146,25 @@ MarkMessageManagers()
|
|||
if (!XRE_IsParentProcess()) {
|
||||
return;
|
||||
}
|
||||
nsCOMPtr<nsIMessageBroadcaster> strongGlobalMM =
|
||||
do_GetService("@mozilla.org/globalmessagemanager;1");
|
||||
RefPtr<ChromeMessageBroadcaster> strongGlobalMM =
|
||||
nsFrameMessageManager::GetGlobalMessageManager();
|
||||
if (!strongGlobalMM) {
|
||||
return;
|
||||
}
|
||||
nsIMessageBroadcaster* globalMM = strongGlobalMM;
|
||||
ChromeMessageBroadcaster* globalMM = strongGlobalMM;
|
||||
strongGlobalMM = nullptr;
|
||||
MarkChildMessageManagers(globalMM);
|
||||
|
||||
if (nsFrameMessageManager::sParentProcessManager) {
|
||||
nsFrameMessageManager::sParentProcessManager->MarkForCC();
|
||||
uint32_t childCount = 0;
|
||||
nsFrameMessageManager::sParentProcessManager->GetChildCount(&childCount);
|
||||
uint32_t childCount = nsFrameMessageManager::sParentProcessManager->ChildCount();
|
||||
for (uint32_t i = 0; i < childCount; ++i) {
|
||||
nsCOMPtr<nsIMessageListenerManager> childMM;
|
||||
nsFrameMessageManager::sParentProcessManager->
|
||||
GetChildAt(i, getter_AddRefs(childMM));
|
||||
RefPtr<MessageListenerManager> childMM =
|
||||
nsFrameMessageManager::sParentProcessManager->GetChildAt(i);
|
||||
if (!childMM) {
|
||||
continue;
|
||||
}
|
||||
nsIMessageListenerManager* child = childMM;
|
||||
MessageListenerManager* child = childMM;
|
||||
childMM = nullptr;
|
||||
child->MarkForCC();
|
||||
}
|
||||
|
@ -296,7 +290,7 @@ MarkWindowList(nsISimpleEnumerator* aWindowList, bool aCleanupJS)
|
|||
|
||||
RefPtr<TabChild> tabChild = TabChild::GetFrom(rootDocShell);
|
||||
if (tabChild) {
|
||||
nsCOMPtr<nsIContentFrameMessageManager> mm = tabChild->GetMessageManager();
|
||||
RefPtr<TabChildGlobal> mm = tabChild->GetMessageManager();
|
||||
if (mm) {
|
||||
// MarkForCC ends up calling UnmarkGray on message listeners, which
|
||||
// TraceBlackJS can't do yet.
|
||||
|
@ -477,9 +471,9 @@ mozilla::dom::TraceBlackJS(JSTracer* aTrc, bool aIsShutdownGC)
|
|||
}
|
||||
|
||||
if (nsFrameMessageManager::GetChildProcessManager()) {
|
||||
nsIContentProcessMessageManager* pg = ProcessGlobal::Get();
|
||||
ProcessGlobal* pg = ProcessGlobal::Get();
|
||||
if (pg) {
|
||||
mozilla::TraceScriptHolder(pg, aTrc);
|
||||
mozilla::TraceScriptHolder(ToSupports(pg), aTrc);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7778,20 +7778,18 @@ nsContentUtils::GetHostOrIPv6WithBrackets(nsIURI* aURI, nsAString& aHost)
|
|||
}
|
||||
|
||||
bool
|
||||
nsContentUtils::CallOnAllRemoteChildren(nsIMessageBroadcaster* aManager,
|
||||
nsContentUtils::CallOnAllRemoteChildren(ChromeMessageBroadcaster* aManager,
|
||||
CallOnRemoteChildFunction aCallback,
|
||||
void* aArg)
|
||||
{
|
||||
uint32_t tabChildCount = 0;
|
||||
aManager->GetChildCount(&tabChildCount);
|
||||
uint32_t tabChildCount = aManager->ChildCount();
|
||||
for (uint32_t j = 0; j < tabChildCount; ++j) {
|
||||
nsCOMPtr<nsIMessageListenerManager> childMM;
|
||||
aManager->GetChildAt(j, getter_AddRefs(childMM));
|
||||
RefPtr<MessageListenerManager> childMM = aManager->GetChildAt(j);
|
||||
if (!childMM) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIMessageBroadcaster> nonLeafMM = do_QueryInterface(childMM);
|
||||
RefPtr<ChromeMessageBroadcaster> nonLeafMM = ChromeMessageBroadcaster::From(childMM);
|
||||
if (nonLeafMM) {
|
||||
if (CallOnAllRemoteChildren(nonLeafMM, aCallback, aArg)) {
|
||||
return true;
|
||||
|
@ -7799,10 +7797,7 @@ nsContentUtils::CallOnAllRemoteChildren(nsIMessageBroadcaster* aManager,
|
|||
continue;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIMessageSender> tabMM = do_QueryInterface(childMM);
|
||||
|
||||
mozilla::dom::ipc::MessageManagerCallback* cb =
|
||||
static_cast<nsFrameMessageManager*>(tabMM.get())->GetCallback();
|
||||
mozilla::dom::ipc::MessageManagerCallback* cb = childMM->GetCallback();
|
||||
if (cb) {
|
||||
nsFrameLoader* fl = static_cast<nsFrameLoader*>(cb);
|
||||
TabParent* remote = TabParent::GetFrom(fl);
|
||||
|
@ -7822,10 +7817,9 @@ nsContentUtils::CallOnAllRemoteChildren(nsPIDOMWindowOuter* aWindow,
|
|||
CallOnRemoteChildFunction aCallback,
|
||||
void* aArg)
|
||||
{
|
||||
nsCOMPtr<nsIDOMChromeWindow> chromeWindow(do_QueryInterface(aWindow));
|
||||
if (chromeWindow) {
|
||||
nsCOMPtr<nsIMessageBroadcaster> windowMM;
|
||||
chromeWindow->GetMessageManager(getter_AddRefs(windowMM));
|
||||
nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(aWindow);
|
||||
if (window->IsChromeWindow()) {
|
||||
RefPtr<ChromeMessageBroadcaster> windowMM = window->GetMessageManager();
|
||||
if (windowMM) {
|
||||
CallOnAllRemoteChildren(windowMM, aCallback, aArg);
|
||||
}
|
||||
|
|
|
@ -82,7 +82,6 @@ class nsIInterfaceRequestor;
|
|||
class nsIIOService;
|
||||
class nsILoadInfo;
|
||||
class nsILoadGroup;
|
||||
class nsIMessageBroadcaster;
|
||||
class nsNameSpaceManager;
|
||||
class nsIObserver;
|
||||
class nsIParser;
|
||||
|
@ -129,6 +128,7 @@ class EventListenerManager;
|
|||
class HTMLEditor;
|
||||
|
||||
namespace dom {
|
||||
class ChromeMessageBroadcaster;
|
||||
struct CustomElementDefinition;
|
||||
class DocumentFragment;
|
||||
class Element;
|
||||
|
@ -3343,7 +3343,7 @@ private:
|
|||
mozilla::dom::AutocompleteInfo& aInfo,
|
||||
bool aGrantAllValidValue = false);
|
||||
|
||||
static bool CallOnAllRemoteChildren(nsIMessageBroadcaster* aManager,
|
||||
static bool CallOnAllRemoteChildren(mozilla::dom::ChromeMessageBroadcaster* aManager,
|
||||
CallOnRemoteChildFunction aCallback,
|
||||
void* aArg);
|
||||
|
||||
|
|
|
@ -8638,6 +8638,8 @@ nsIDocument::DestroyElementMaps()
|
|||
#endif
|
||||
mStyledLinks.Clear();
|
||||
mIdentifierMap.Clear();
|
||||
mComposedShadowRoots.Clear();
|
||||
mResponsiveContent.Clear();
|
||||
IncrementExpandoGeneration(*this);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
class nsIContent;
|
||||
class nsIDocShellTreeItem;
|
||||
class nsPIDOMWindowOuter;
|
||||
class nsIMessageBroadcaster;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
@ -178,12 +177,6 @@ protected:
|
|||
*/
|
||||
void EnsureCurrentWidgetFocused();
|
||||
|
||||
/**
|
||||
* Iterate over the children of the message broadcaster and notify them
|
||||
* of the activation change.
|
||||
*/
|
||||
void ActivateOrDeactivateChildren(nsIMessageBroadcaster* aManager, bool aActive);
|
||||
|
||||
/**
|
||||
* Activate or deactivate the window and send the activate/deactivate events.
|
||||
*/
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче