Merge inbound to mozilla-central. a=merge

This commit is contained in:
Margareta Eliza Balazs 2018-04-04 12:43:55 +03:00
Родитель f14ee0f38e 98be3d3ef9
Коммит 210797c4c1
258 изменённых файлов: 6423 добавлений и 2467 удалений

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

@ -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.
*/

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше