mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 23:31:56 +00:00
Merge mozilla-central to inbound. a=merge CLOSED TREE
This commit is contained in:
commit
54aa04bfd1
@ -59,8 +59,8 @@ xpcom/reflect/xptcall/md/unix/.*
|
||||
browser/components/translation/cld2/.*
|
||||
browser/extensions/mortar/ppapi/.*
|
||||
db/sqlite3/src/.*
|
||||
devtools/client/sourceeditor/codemirror/.*
|
||||
devtools/client/sourceeditor/tern/.*
|
||||
devtools/client/shared/sourceeditor/codemirror/.*
|
||||
devtools/client/shared/sourceeditor/tern/.*
|
||||
dom/canvas/test/webgl-conf/checkout/closure-library/.*
|
||||
dom/media/gmp/rlz/.*
|
||||
dom/media/gmp/widevine-adapter/content_decryption_module.h
|
||||
|
@ -143,11 +143,11 @@ devtools/client/jsonview/lib/require.js
|
||||
devtools/client/shared/demangle.js
|
||||
devtools/client/shared/source-map/*
|
||||
devtools/client/shared/vendor/*
|
||||
devtools/client/sourceeditor/codemirror/*.js
|
||||
devtools/client/sourceeditor/codemirror/**/*.js
|
||||
devtools/client/sourceeditor/tern/*
|
||||
devtools/client/sourceeditor/test/cm_mode_ruby.js
|
||||
devtools/client/sourceeditor/test/codemirror/*
|
||||
devtools/client/shared/sourceeditor/codemirror/*.js
|
||||
devtools/client/shared/sourceeditor/codemirror/**/*.js
|
||||
devtools/client/shared/sourceeditor/tern/*
|
||||
devtools/client/shared/sourceeditor/test/cm_mode_ruby.js
|
||||
devtools/client/shared/sourceeditor/test/codemirror/*
|
||||
devtools/server/actors/utils/automation-timeline.js
|
||||
|
||||
# Ignore devtools files testing sourcemaps / code style
|
||||
|
@ -237,8 +237,6 @@ pref("browser.startup.homepage", "about:home");
|
||||
// Whether we should skip the homepage when opening the first-run page
|
||||
pref("browser.startup.firstrunSkipsHomepage", true);
|
||||
|
||||
pref("browser.dedicatedprofile.welcome.accounts.endpoint", "https://accounts.firefox.com/");
|
||||
|
||||
// Show an about:blank window as early as possible for quick startup feedback.
|
||||
// Held to nightly on Linux due to bug 1450626.
|
||||
// Disabled on Mac because the bouncing dock icon already provides feedback.
|
||||
|
@ -2,12 +2,13 @@
|
||||
* 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/. */
|
||||
|
||||
/* global RPMGetUpdateChannel, RPMGetFxAccountsEndpoint */
|
||||
|
||||
const PARAMS = new URL(location).searchParams;
|
||||
const ENTRYPOINT = "new-install-page";
|
||||
const SOURCE = `new-install-page-${PARAMS.get("channel")}`;
|
||||
const SOURCE = `new-install-page-${RPMGetUpdateChannel()}`;
|
||||
const CAMPAIGN = "dedicated-profiles";
|
||||
const ENDPOINT = PARAMS.get("endpoint");
|
||||
const CONTEXT = "fx_desktop_v3";
|
||||
|
||||
function appendAccountsParams(url) {
|
||||
url.searchParams.set("entrypoint", ENTRYPOINT);
|
||||
@ -24,7 +25,8 @@ function appendParams(url, params) {
|
||||
}
|
||||
|
||||
async function requestFlowMetrics() {
|
||||
let requestURL = new URL(`${ENDPOINT}metrics-flow`);
|
||||
let requestURL = new URL(await endpoint);
|
||||
requestURL.pathname = "metrics-flow";
|
||||
appendParams(requestURL, {
|
||||
"form_type": "email",
|
||||
});
|
||||
@ -47,11 +49,9 @@ async function submitForm(event) {
|
||||
|
||||
let { flowId, flowBeginTime } = await metrics;
|
||||
|
||||
let requestURL = new URL(ENDPOINT);
|
||||
let requestURL = new URL(await endpoint);
|
||||
appendParams(requestURL, {
|
||||
"service": "sync",
|
||||
"action": "email",
|
||||
"context": CONTEXT,
|
||||
"utm_campaign": CAMPAIGN,
|
||||
"email": input.value,
|
||||
"flow_id": flowId,
|
||||
@ -61,6 +61,8 @@ async function submitForm(event) {
|
||||
window.open(requestURL, "_blank", "noopener");
|
||||
}
|
||||
|
||||
const endpoint = RPMGetFxAccountsEndpoint(ENTRYPOINT);
|
||||
|
||||
// This must come before the CSP is set or it will be blocked.
|
||||
const metrics = requestFlowMetrics();
|
||||
|
||||
|
@ -15,6 +15,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
SessionStartup: "resource:///modules/sessionstore/SessionStartup.jsm",
|
||||
ShellService: "resource:///modules/ShellService.jsm",
|
||||
UpdatePing: "resource://gre/modules/UpdatePing.jsm",
|
||||
RemotePages: "resource://gre/modules/remotepagemanager/RemotePageManagerParent.jsm",
|
||||
});
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
|
||||
"@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
|
||||
@ -23,6 +24,8 @@ XPCOMUtils.defineLazyGetter(this, "gSystemPrincipal",
|
||||
() => Services.scriptSecurityManager.getSystemPrincipal());
|
||||
XPCOMUtils.defineLazyGlobalGetters(this, [URL]);
|
||||
|
||||
const NEWINSTALL_PAGE = "about:newinstall";
|
||||
|
||||
function shouldLoadURI(aURI) {
|
||||
if (aURI && !aURI.schemeIs("chrome"))
|
||||
return true;
|
||||
@ -60,12 +63,14 @@ function resolveURIInternal(aCmdLine, aArgument) {
|
||||
return uri;
|
||||
}
|
||||
|
||||
let gRemoteInstallPage = null;
|
||||
|
||||
function getNewInstallPage() {
|
||||
let url = new URL("about:newinstall");
|
||||
let endpoint = Services.prefs.getCharPref("browser.dedicatedprofile.welcome.accounts.endpoint");
|
||||
url.searchParams.set("endpoint", endpoint);
|
||||
url.searchParams.set("channel", AppConstants.MOZ_UPDATE_CHANNEL);
|
||||
return url.toString();
|
||||
if (!gRemoteInstallPage) {
|
||||
gRemoteInstallPage = new RemotePages(NEWINSTALL_PAGE);
|
||||
}
|
||||
|
||||
return NEWINSTALL_PAGE;
|
||||
}
|
||||
|
||||
var gFirstWindow = false;
|
||||
|
@ -125,9 +125,6 @@
|
||||
</radiogroup>
|
||||
</box>
|
||||
</row>
|
||||
<label data-l10n-id="connection-proxy-noproxy" control="networkProxyNone"/>
|
||||
<textbox id="networkProxyNone" preference="network.proxy.no_proxies_on" multiline="true" rows="2"/>
|
||||
<label control="networkProxyNone" data-l10n-id="connection-proxy-noproxy-desc" />
|
||||
</rows>
|
||||
</grid>
|
||||
<radio value="2" data-l10n-id="connection-proxy-autotype" />
|
||||
@ -142,6 +139,9 @@
|
||||
</radiogroup>
|
||||
</groupbox>
|
||||
<separator class="thin"/>
|
||||
<label data-l10n-id="connection-proxy-noproxy" control="networkProxyNone"/>
|
||||
<textbox id="networkProxyNone" preference="network.proxy.no_proxies_on" multiline="true" rows="2"/>
|
||||
<label control="networkProxyNone" data-l10n-id="connection-proxy-noproxy-desc" />
|
||||
<checkbox id="autologinProxy"
|
||||
data-l10n-id="connection-proxy-autologin"
|
||||
preference="signon.autologin.proxy" />
|
||||
|
@ -762,7 +762,7 @@ add_task(async function testExtensionControlledProxyConfig() {
|
||||
...doc.querySelectorAll("#networkProxySOCKSVersion > radio")],
|
||||
pacControls: [doc.getElementById("networkProxyAutoconfigURL")],
|
||||
otherControls: [
|
||||
manualControlContainer.querySelector("label[control=networkProxyNone]"),
|
||||
doc.querySelector("label[control=networkProxyNone]"),
|
||||
doc.getElementById("networkProxyNone"),
|
||||
...controlGroup.querySelectorAll(":scope > radio"),
|
||||
...doc.querySelectorAll("#ConnectionsDialogPane > checkbox")],
|
||||
|
@ -298,7 +298,7 @@ class SearchOneOffs {
|
||||
// If the button doesn't have an engine, then clear the popup's
|
||||
// selection to indicate that pressing Return while the button is
|
||||
// selected will do the button's command, not search.
|
||||
this.popup.selectedIndex = -1;
|
||||
this.selectedAutocompleteIndex = -1;
|
||||
}
|
||||
let event = new CustomEvent("SelectedOneOffButtonChanged", {
|
||||
previousSelectedButton: previousButton,
|
||||
@ -334,6 +334,14 @@ class SearchOneOffs {
|
||||
return -1;
|
||||
}
|
||||
|
||||
get selectedAutocompleteIndex() {
|
||||
return (this._view || this.popup).selectedIndex;
|
||||
}
|
||||
|
||||
set selectedAutocompleteIndex(val) {
|
||||
return (this._view || this.popup).selectedIndex = val;
|
||||
}
|
||||
|
||||
get compact() {
|
||||
return this.getAttribute("compact") == "true";
|
||||
}
|
||||
@ -887,7 +895,7 @@ class SearchOneOffs {
|
||||
this.selectedButton = null;
|
||||
return false;
|
||||
}
|
||||
this.popup.selectedIndex = -1;
|
||||
this.selectedAutocompleteIndex = -1;
|
||||
this.advanceSelection(!event.shiftKey, true, false);
|
||||
return !!this.selectedButton;
|
||||
}
|
||||
@ -904,13 +912,13 @@ class SearchOneOffs {
|
||||
this.advanceSelection(false, true, false);
|
||||
return true;
|
||||
}
|
||||
if (this.popup.selectedIndex > 0) {
|
||||
if (this.selectedAutocompleteIndex > 0) {
|
||||
// Moving up within the list. The autocomplete controller should
|
||||
// handle this case. A button may be selected, so null it.
|
||||
this.selectedButton = null;
|
||||
return false;
|
||||
}
|
||||
if (this.popup.selectedIndex == 0) {
|
||||
if (this.selectedAutocompleteIndex == 0) {
|
||||
// Moving up from the top of the list.
|
||||
if (allowEmptySelection) {
|
||||
// Let the autocomplete controller remove selection in the list
|
||||
@ -952,14 +960,14 @@ class SearchOneOffs {
|
||||
this.advanceSelection(true, true, false);
|
||||
return true;
|
||||
}
|
||||
if (this.popup.selectedIndex >= 0 &&
|
||||
this.popup.selectedIndex < numListItems - 1) {
|
||||
if (this.selectedAutocompleteIndex >= 0 &&
|
||||
this.selectedAutocompleteIndex < numListItems - 1) {
|
||||
// Moving down within the list. The autocomplete controller
|
||||
// should handle this case. A button may be selected, so null it.
|
||||
this.selectedButton = null;
|
||||
return false;
|
||||
}
|
||||
if (this.popup.selectedIndex == numListItems - 1) {
|
||||
if (this.selectedAutocompleteIndex == numListItems - 1) {
|
||||
// Moving down from the last item in the list to the buttons.
|
||||
this.selectedButtonIndex = 0;
|
||||
if (allowEmptySelection) {
|
||||
@ -970,7 +978,7 @@ class SearchOneOffs {
|
||||
if (this.textbox && typeof textboxUserValue == "string") {
|
||||
this.textbox.value = textboxUserValue;
|
||||
}
|
||||
this.popup.selectedIndex = -1;
|
||||
this.selectedAutocompleteIndex = -1;
|
||||
return true;
|
||||
}
|
||||
if (this.selectedButton) {
|
||||
|
@ -198,6 +198,20 @@ class UrlbarController {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.view.isOpen) {
|
||||
let queryContext = this._lastQueryContext;
|
||||
if (queryContext) {
|
||||
this.view.oneOffSearchButtons.handleKeyPress(
|
||||
event,
|
||||
queryContext.results.length,
|
||||
this.view.allowEmptySelection,
|
||||
queryContext.searchString);
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (event.keyCode) {
|
||||
case KeyEvent.DOM_VK_ESCAPE:
|
||||
this.input.handleRevert();
|
||||
|
@ -64,6 +64,12 @@ class UrlbarView {
|
||||
return this.panel.state == "open" || this.panel.state == "showing";
|
||||
}
|
||||
|
||||
get allowEmptySelection() {
|
||||
return !(this._queryContext &&
|
||||
this._queryContext.results[0] &&
|
||||
this._queryContext.results[0].heuristic);
|
||||
}
|
||||
|
||||
get selectedIndex() {
|
||||
if (!this.isOpen || !this._selected) {
|
||||
return -1;
|
||||
@ -115,15 +121,13 @@ class UrlbarView {
|
||||
throw new Error("UrlbarView: Cannot select an item if the view isn't open.");
|
||||
}
|
||||
|
||||
// TODO bug 1527260: handle one-off search buttons
|
||||
|
||||
let row;
|
||||
if (reverse) {
|
||||
row = (this._selected && this._selected.previousElementSibling) ||
|
||||
this._rows.lastElementChild;
|
||||
((this._selected && this.allowEmptySelection) ? null : this._rows.lastElementChild);
|
||||
} else {
|
||||
row = (this._selected && this._selected.nextElementSibling) ||
|
||||
this._rows.firstElementChild;
|
||||
((this._selected && this.allowEmptySelection) ? null : this._rows.firstElementChild);
|
||||
}
|
||||
this._selectItem(row);
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ connection-proxy-socks4 =
|
||||
connection-proxy-socks5 =
|
||||
.label = SOCKS v5
|
||||
.accesskey = v
|
||||
connection-proxy-noproxy = No Proxy for
|
||||
connection-proxy-noproxy = No proxy for
|
||||
.accesskey = N
|
||||
|
||||
connection-proxy-noproxy-desc = Example: .mozilla.org, .net.nz, 192.168.1.0/24
|
||||
|
@ -18,6 +18,7 @@ const InspectAction = createFactory(require("./InspectAction"));
|
||||
|
||||
const Actions = require("../../actions/index");
|
||||
const Types = require("../../types/index");
|
||||
const { SERVICE_WORKER_STATUSES } = require("../../constants");
|
||||
|
||||
/**
|
||||
* This component displays buttons for service worker.
|
||||
@ -105,25 +106,29 @@ class ServiceWorkerAction extends PureComponent {
|
||||
}
|
||||
|
||||
_renderAction() {
|
||||
const { isActive, isRunning } = this.props.target.details;
|
||||
const { status } = this.props.target.details;
|
||||
|
||||
if (!isRunning) {
|
||||
return [
|
||||
this._renderUnregisterButton(),
|
||||
this._renderStartButton(),
|
||||
];
|
||||
switch (status) {
|
||||
case SERVICE_WORKER_STATUSES.RUNNING:
|
||||
return [
|
||||
this._renderUnregisterButton(),
|
||||
this._renderPushButton(),
|
||||
this._renderInspectAction(),
|
||||
];
|
||||
case SERVICE_WORKER_STATUSES.REGISTERING:
|
||||
// Only inspect is available if the service worker is not active.
|
||||
return [
|
||||
this._renderInspectAction(),
|
||||
];
|
||||
case SERVICE_WORKER_STATUSES.STOPPED:
|
||||
return [
|
||||
this._renderUnregisterButton(),
|
||||
this._renderStartButton(),
|
||||
];
|
||||
default:
|
||||
console.error("Unexpected service worker status: " + status);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!isActive) {
|
||||
// Only inspect is available if the service worker is not active.
|
||||
return [this._renderInspectAction()];
|
||||
}
|
||||
|
||||
return [
|
||||
this._renderUnregisterButton(),
|
||||
this._renderPushButton(),
|
||||
this._renderInspectAction(),
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -28,7 +28,10 @@ const workerComponentDataMiddleware = store => next => action => {
|
||||
return next(action);
|
||||
};
|
||||
|
||||
function getServiceWorkerStatus(isActive, isRunning) {
|
||||
function getServiceWorkerStatus(worker) {
|
||||
const isActive = worker.active;
|
||||
const isRunning = !!worker.workerTargetFront;
|
||||
|
||||
if (isActive && isRunning) {
|
||||
return SERVICE_WORKER_STATUSES.RUNNING;
|
||||
} else if (isActive) {
|
||||
@ -59,25 +62,19 @@ function toComponentData(workers, isServiceWorker) {
|
||||
// service worker registration.
|
||||
const id = workerTargetFront ? workerTargetFront.actorID : registrationFront.actorID;
|
||||
|
||||
let isActive = false;
|
||||
let isRunning = false;
|
||||
let pushServiceEndpoint = null;
|
||||
let status = null;
|
||||
|
||||
if (isServiceWorker) {
|
||||
fetch = fetch ? SERVICE_WORKER_FETCH_STATES.LISTENING
|
||||
: SERVICE_WORKER_FETCH_STATES.NOT_LISTENING;
|
||||
isActive = worker.active;
|
||||
isRunning = !!worker.workerTargetFront;
|
||||
status = getServiceWorkerStatus(isActive, isRunning);
|
||||
status = getServiceWorkerStatus(worker);
|
||||
pushServiceEndpoint = subscription ? subscription.endpoint : null;
|
||||
}
|
||||
|
||||
return {
|
||||
details: {
|
||||
fetch,
|
||||
isActive,
|
||||
isRunning,
|
||||
pushServiceEndpoint,
|
||||
registrationFront,
|
||||
scope,
|
||||
|
@ -27,10 +27,6 @@ const tabTargetDetails = {
|
||||
const workerTargetDetails = {
|
||||
// (service worker specific) one of "LISTENING", "NOT_LISTENING". undefined otherwise.
|
||||
fetch: PropTypes.string,
|
||||
// (service worker specific) true if they reached the activated state.
|
||||
isActive: PropTypes.bool,
|
||||
// (service worker specific) true if they are currently running.
|
||||
isRunning: PropTypes.bool,
|
||||
// front for the ServiceWorkerRegistration related to this service worker.
|
||||
registrationFront: PropTypes.object,
|
||||
// (service worker specific) scope of the service worker registration.
|
||||
|
@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Service worker push test</title>
|
||||
<title>Service worker controlled</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
|
@ -46,6 +46,11 @@ add_task(async function() {
|
||||
|
||||
await waitForServiceWorkerActivation(SERVICE_WORKER, document);
|
||||
|
||||
info("Wait until the service worker is running");
|
||||
const container = await waitUntilServiceWorkerContainer(SERVICE_WORKER, document);
|
||||
await waitUntil(
|
||||
() => container.querySelector(".target-status").textContent === "Running", 100);
|
||||
|
||||
// Retrieve the Push button for the worker.
|
||||
const names = [...document.querySelectorAll("#service-workers .target-name")];
|
||||
const name = names.filter(element => element.textContent === SERVICE_WORKER)[0];
|
||||
|
@ -383,22 +383,31 @@ body {
|
||||
background-color: var(--theme-selection-background-hover);
|
||||
}
|
||||
|
||||
.accessible .tree:focus .node.focused {
|
||||
.accessible .tree:focus .node.focused,
|
||||
.accessible .tree .tree-node-active .node.focused {
|
||||
background-color: var(--theme-selection-background);
|
||||
}
|
||||
|
||||
.accessible .tree:focus .node.focused * {
|
||||
.accessible .tree:focus .node.focused *,
|
||||
.accessible .tree .tree-node-active .node.focused * {
|
||||
color: var(--theme-selection-color);
|
||||
}
|
||||
|
||||
.accessible .tree:focus .node.focused .open-inspector {
|
||||
.accessible .tree:focus .node.focused .open-inspector,
|
||||
.accessible .tree .tree-node-active .node.focused .open-inspector {
|
||||
background-color: var(--grey-30);
|
||||
}
|
||||
|
||||
.accessible .tree:focus .node.focused:hover .open-inspector {
|
||||
.accessible .tree:focus .node.focused:hover .open-inspector,
|
||||
.accessible .tree .tree-node-active .node.focused:hover .open-inspector {
|
||||
background-color: var(--theme-selection-color);
|
||||
}
|
||||
|
||||
.accessible .tree .tree-node-active .node.focused .open-inspector:focus,
|
||||
.accessible .tree .tree-node-active .node.focused:hover .open-inspector:focus {
|
||||
background-color: var(--grey-40);
|
||||
}
|
||||
|
||||
.accessible .tree .arrow {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@ -425,20 +434,28 @@ body {
|
||||
margin-inline-start: 5px;
|
||||
}
|
||||
|
||||
.accessible .tree:focus .node.focused .objectBox-accessible .accessible-role {
|
||||
.accessible .tree:focus .node.focused .objectBox-accessible .accessible-role,
|
||||
.accessible .tree .tree-node-active .node.focused .objectBox-accessible .accessible-role {
|
||||
background-color: var(--accessible-role-active-background-color);
|
||||
border-color: var(--accessible-role-active-border-color);
|
||||
color: var(--theme-selection-color);
|
||||
}
|
||||
|
||||
.accessible .tree:focus .node.focused .open-accessibility-inspector {
|
||||
.accessible .tree:focus .node.focused .open-accessibility-inspector,
|
||||
.accessible .tree .tree-node-active .node.focused .open-accessibility-inspector {
|
||||
background-color: var(--grey-30);
|
||||
}
|
||||
|
||||
.accessible .tree:focus .node.focused:hover .open-accessibility-inspector {
|
||||
.accessible .tree:focus .node.focused:hover .open-accessibility-inspector,
|
||||
.accessible .tree .tree-node-active .node.focused:hover .open-accessibility-inspector {
|
||||
background-color: var(--theme-selection-color);
|
||||
}
|
||||
|
||||
.accessible .tree .tree-node-active .node.focused .open-accessibility-inspector:focus,
|
||||
.accessible .tree .tree-node-active .node.focused:hover .open-accessibility-inspector:focus {
|
||||
background-color: var(--grey-40);
|
||||
}
|
||||
|
||||
.accessible .tree .objectBox-accessible,
|
||||
.accessible .tree .objectBox-node {
|
||||
width: 100%;
|
||||
|
@ -93,6 +93,7 @@ class Accessible extends Component {
|
||||
|
||||
this.state = {
|
||||
expanded: new Set(),
|
||||
active: null,
|
||||
focused: null,
|
||||
};
|
||||
|
||||
@ -230,7 +231,7 @@ class Accessible extends Component {
|
||||
if (props) {
|
||||
props.refs.tree.blur();
|
||||
}
|
||||
await this.setState({ focused: null });
|
||||
await this.setState({ active: null, focused: null });
|
||||
|
||||
window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED);
|
||||
}
|
||||
@ -300,7 +301,7 @@ class Accessible extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { expanded, focused } = this.state;
|
||||
const { expanded, active, focused } = this.state;
|
||||
const { items, parents, accessible, labelledby } = this.props;
|
||||
|
||||
if (accessible) {
|
||||
@ -320,17 +321,15 @@ class Accessible extends Component {
|
||||
this.setState({ focused: item.path });
|
||||
}
|
||||
},
|
||||
onActivate: ({ contents }) => {
|
||||
if (isNode(contents)) {
|
||||
this.selectNode(this.props.DOMNode, "accessibility-keyboard");
|
||||
} else if (isAccessible(contents)) {
|
||||
const target = findAccessibleTarget(this.props.relations, contents.actor);
|
||||
if (target) {
|
||||
this.selectAccessible(target);
|
||||
}
|
||||
onActivate: item => {
|
||||
if (item == null) {
|
||||
this.setState({ active: null });
|
||||
} else if (this.state.active !== item.path) {
|
||||
this.setState({ active: item.path });
|
||||
}
|
||||
},
|
||||
focused: findFocused(focused, items),
|
||||
focused: findByPath(focused, items),
|
||||
active: findByPath(active, items),
|
||||
renderItem: this.renderItem,
|
||||
labelledby,
|
||||
});
|
||||
@ -362,17 +361,21 @@ const findAccessibleTarget = (relations, actorID) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Find an item based on a given path.
|
||||
* @param {String} path
|
||||
* Key of the item to be looked up.
|
||||
* @param {Array} items
|
||||
* Accessibility properties array.
|
||||
* @return {Object?}
|
||||
* Possibly found item.
|
||||
*/
|
||||
const findFocused = (focused, items) => {
|
||||
const findByPath = (path, items) => {
|
||||
for (const item of items) {
|
||||
if (item.path === focused) {
|
||||
if (item.path === path) {
|
||||
return item;
|
||||
}
|
||||
const found = findFocused(focused, item.children);
|
||||
|
||||
const found = findByPath(path, item.children);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
|
@ -16,8 +16,8 @@ const EXCLUDED_FILES = {
|
||||
|
||||
const mappings = Object.assign(
|
||||
{
|
||||
"./source-editor": "devtools/client/sourceeditor/editor",
|
||||
"../editor/source-editor": "devtools/client/sourceeditor/editor",
|
||||
"./source-editor": "devtools/client/shared/sourceeditor/editor",
|
||||
"../editor/source-editor": "devtools/client/shared/sourceeditor/editor",
|
||||
"./test-flag": "devtools/shared/flags",
|
||||
"./fronts-device": "devtools/shared/fronts/device",
|
||||
immutable: "devtools/client/shared/vendor/immutable",
|
||||
|
@ -5,9 +5,9 @@
|
||||
<html dir="">
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css" />
|
||||
<link rel="stylesheet" type="text/css" href="chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css" />
|
||||
<link rel="stylesheet" type="text/css" href="chrome://devtools/content/sourceeditor/codemirror/mozilla.css" />
|
||||
<link rel="stylesheet" type="text/css" href="chrome://devtools/content/shared/sourceeditor/codemirror/lib/codemirror.css" />
|
||||
<link rel="stylesheet" type="text/css" href="chrome://devtools/content/shared/sourceeditor/codemirror/addon/dialog/dialog.css" />
|
||||
<link rel="stylesheet" type="text/css" href="chrome://devtools/content/shared/sourceeditor/codemirror/mozilla.css" />
|
||||
<link rel="stylesheet" type="text/css" href="resource://devtools/client/debugger/new/dist/debugger.css" />
|
||||
</head>
|
||||
|
||||
|
@ -14,19 +14,6 @@ import { getGeneratedLocation } from "../../utils/source-maps";
|
||||
import type { SourceId } from "../../types";
|
||||
import type { ThunkArgs, Action } from "../types";
|
||||
|
||||
function compressPausePoints(pausePoints) {
|
||||
const compressed = {};
|
||||
for (const line in pausePoints) {
|
||||
compressed[line] = {};
|
||||
for (const col in pausePoints[line]) {
|
||||
const { types } = pausePoints[line][col];
|
||||
compressed[line][col] = (types.break ? 1 : 0) | (types.step ? 2 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
return compressed;
|
||||
}
|
||||
|
||||
async function mapLocations(pausePoints, state, source, sourceMaps) {
|
||||
const pausePointList = convertToList(pausePoints);
|
||||
const sourceId = source.id;
|
||||
@ -58,17 +45,8 @@ export function setPausePoints(sourceId: SourceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
let pausePoints = await parser.getPausePoints(sourceId);
|
||||
|
||||
if (isGenerated(source)) {
|
||||
const compressed = compressPausePoints(pausePoints);
|
||||
for (const sourceActor of getSourceActors(getState(), sourceId)) {
|
||||
await client.setPausePoints(sourceActor, compressed);
|
||||
}
|
||||
}
|
||||
|
||||
pausePoints = await mapLocations(
|
||||
pausePoints,
|
||||
const pausePoints = await mapLocations(
|
||||
await parser.getPausePoints(sourceId),
|
||||
getState(),
|
||||
source,
|
||||
sourceMaps
|
||||
|
@ -28,7 +28,6 @@ add_task(async function() {
|
||||
await selectSource(dbg, "simple1");
|
||||
await waitForSelectedSource(dbg, "simple1");
|
||||
|
||||
await addBreakpoint(dbg, "simple1", 1);
|
||||
await addBreakpoint(dbg, "simple1", 4);
|
||||
await addBreakpoint(dbg, "simple1", 5);
|
||||
await addBreakpoint(dbg, "simple1", 6);
|
||||
@ -40,14 +39,14 @@ add_task(async function() {
|
||||
// which promises get resolved. The problem seems to indicate a coverage gap
|
||||
// in waitUntilService(). Workaround this by only waiting for one dispatch,
|
||||
// though this is fragile and could break again in the future.
|
||||
let dispatched = waitForDispatch(dbg, "DISABLE_BREAKPOINT", /*3*/ 1);
|
||||
let dispatched = waitForDispatch(dbg, "DISABLE_BREAKPOINT", /*2*/ 1);
|
||||
selectContextMenuItem(dbg, selectors.breakpointContextMenu.disableOthers);
|
||||
await waitForState(dbg, state =>
|
||||
dbg.selectors.getBreakpointsList(state)
|
||||
.every(bp => (bp.location.line !== 1) === bp.disabled)
|
||||
.every(bp => (bp.location.line !== 4) === bp.disabled)
|
||||
);
|
||||
await dispatched;
|
||||
ok("breakpoint at 1 is the only enabled breakpoint");
|
||||
ok("breakpoint at 4 is the only enabled breakpoint");
|
||||
|
||||
openFirstBreakpointContextMenu(dbg);
|
||||
// select "Disable All"
|
||||
@ -61,23 +60,23 @@ add_task(async function() {
|
||||
|
||||
openFirstBreakpointContextMenu(dbg);
|
||||
// select "Enable Others"
|
||||
dispatched = waitForDispatch(dbg, "ENABLE_BREAKPOINT", 3);
|
||||
dispatched = waitForDispatch(dbg, "ENABLE_BREAKPOINT", 2);
|
||||
selectContextMenuItem(dbg, selectors.breakpointContextMenu.enableOthers);
|
||||
await waitForState(dbg, state =>
|
||||
dbg.selectors.getBreakpointsList(state)
|
||||
.every(bp => (bp.location.line === 1) === bp.disabled)
|
||||
.every(bp => (bp.location.line === 4) === bp.disabled)
|
||||
);
|
||||
await dispatched;
|
||||
ok("all breakpoints except line 1 are enabled");
|
||||
|
||||
openFirstBreakpointContextMenu(dbg);
|
||||
// select "Remove Others"
|
||||
dispatched = waitForDispatch(dbg, "REMOVE_BREAKPOINT", 3);
|
||||
dispatched = waitForDispatch(dbg, "REMOVE_BREAKPOINT", 2);
|
||||
selectContextMenuItem(dbg, selectors.breakpointContextMenu.removeOthers);
|
||||
await waitForState(dbg, state =>
|
||||
dbg.selectors.getBreakpointsList(state).length === 1 &&
|
||||
dbg.selectors.getBreakpointsList(state)[0].location.line === 1
|
||||
dbg.selectors.getBreakpointsList(state)[0].location.line === 4
|
||||
);
|
||||
await dispatched;
|
||||
ok("remaining breakpoint should be on line 1");
|
||||
ok("remaining breakpoint should be on line 4");
|
||||
});
|
||||
|
@ -36,6 +36,9 @@ add_task(async function() {
|
||||
await pressResume(dbg);
|
||||
assertPausedLocation(dbg);
|
||||
|
||||
await pressStepOver(dbg);
|
||||
assertPausedLocation(dbg);
|
||||
|
||||
await pressStepIn(dbg);
|
||||
assertPausedLocation(dbg);
|
||||
|
||||
|
@ -33,28 +33,17 @@ add_task(async function test() {
|
||||
await selectSource(dbg, "pause-points.js")
|
||||
await testCase(dbg, {
|
||||
name: "statements",
|
||||
steps: [
|
||||
[9, 2],
|
||||
[10, 4],
|
||||
[10, 13],
|
||||
[11, 2],
|
||||
[11, 10],
|
||||
[11, 21],
|
||||
[11, 29],
|
||||
[12, 2],
|
||||
[12, 12],
|
||||
[13, 0]
|
||||
]
|
||||
steps: [[9,2], [10,4], [10,13], [11,2], [11,21], [12,2], [12,12], [13,0]]
|
||||
});
|
||||
|
||||
await testCase(dbg, {
|
||||
name: "expressions",
|
||||
steps: [[40,2], [41,2], [41,8], [42,8], [43,0]]
|
||||
steps: [[40,2], [41,2], [42,12], [43,0]]
|
||||
});
|
||||
|
||||
await testCase(dbg, {
|
||||
name: "sequences",
|
||||
steps: [[23,2], [25,8], [29,8], [31,4], [34,2], [37,0]]
|
||||
steps: [[23,2], [25,12], [29,12], [34,2], [37,0]]
|
||||
});
|
||||
|
||||
await testCase(dbg, {
|
||||
@ -62,15 +51,11 @@ add_task(async function test() {
|
||||
steps: [
|
||||
[16, 2],
|
||||
[17, 12],
|
||||
[17, 20],
|
||||
[18, 6],
|
||||
[19, 2],
|
||||
[18, 10],
|
||||
[19, 8],
|
||||
[19, 17],
|
||||
[19, 25],
|
||||
[19, 8],
|
||||
[19, 17],
|
||||
[19, 25],
|
||||
[19, 8]
|
||||
]
|
||||
});
|
||||
|
@ -56,6 +56,7 @@ function testStepOverForOf(dbg) {
|
||||
"step-over-for-of",
|
||||
{ line: 4, column: 2 },
|
||||
[
|
||||
["stepOver", { line: 6, column: 20 }],
|
||||
["stepOver", { line: 6, column: 2 }],
|
||||
["stepOver", { line: 7, column: 4 }],
|
||||
["stepOver", { line: 6, column: 2 }],
|
||||
@ -76,10 +77,10 @@ function testStepOverForOfArray(dbg) {
|
||||
{ line: 3, column: 2 },
|
||||
[
|
||||
["stepOver", { line: 5, column: 2 }],
|
||||
["stepOver", { line: 5, column: 7 }],
|
||||
["stepOver", { line: 5, column: 13 }],
|
||||
["stepOver", { line: 6, column: 4 }],
|
||||
["stepOver", { line: 5, column: 2 }],
|
||||
["stepOver", { line: 5, column: 7 }],
|
||||
["stepOver", { line: 5, column: 13 }],
|
||||
["stepOver", { line: 6, column: 4 }],
|
||||
["stepOver", { line: 5, column: 2 }],
|
||||
["stepOver", { line: 9, column: 2 }]
|
||||
@ -96,6 +97,7 @@ function testStepOveForOfClosure(dbg) {
|
||||
"step-over-for-of-closure",
|
||||
{ line: 6, column: 2 },
|
||||
[
|
||||
["stepOver", { line: 8, column: 20 }],
|
||||
["stepOver", { line: 8, column: 2 }],
|
||||
["stepOver", { line: 12, column: 2 }]
|
||||
]
|
||||
@ -113,9 +115,9 @@ function testStepOverForOfArrayClosure(dbg) {
|
||||
{ line: 3, column: 2 },
|
||||
[
|
||||
["stepOver", { line: 5, column: 2 }],
|
||||
["stepOver", { line: 5, column: 7 }],
|
||||
["stepOver", { line: 5, column: 13 }],
|
||||
["stepOver", { line: 5, column: 2 }],
|
||||
["stepOver", { line: 5, column: 7 }],
|
||||
["stepOver", { line: 5, column: 13 }],
|
||||
["stepOver", { line: 5, column: 2 }],
|
||||
["stepOver", { line: 9, column: 2 }]
|
||||
]
|
||||
|
@ -81,7 +81,6 @@ add_task(async function() {
|
||||
await waitForPaused(dbg);
|
||||
assertPausedLocation(dbg);
|
||||
|
||||
await stepIn(dbg);
|
||||
await stepIn(dbg);
|
||||
assertPausedLocation(dbg);
|
||||
|
||||
|
@ -24,6 +24,6 @@ add_task(async function test() {
|
||||
await stepIn(dbg);
|
||||
await stepIn(dbg);
|
||||
|
||||
assertDebugLine(dbg, 42267);
|
||||
assertDebugLine(dbg, 42271);
|
||||
assertPausedLocation(dbg);
|
||||
});
|
||||
|
@ -6,7 +6,9 @@
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += [
|
||||
'test/browser.ini',
|
||||
'test/metrics/browser_metrics_debugger.ini',
|
||||
'test/metrics/browser_metrics_inspector.ini',
|
||||
'test/metrics/browser_metrics_netmonitor.ini',
|
||||
'test/metrics/browser_metrics_webconsole.ini',
|
||||
]
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
|
||||
|
@ -0,0 +1,12 @@
|
||||
[DEFAULT]
|
||||
tags = devtools
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
head.js
|
||||
!/devtools/client/shared/test/shared-head.js
|
||||
!/devtools/client/shared/test/telemetry-test-helpers.js
|
||||
|
||||
# Each metrics tests is loaded in a separate .ini file. This way the test is executed
|
||||
# individually, without any other test being executed before or after.
|
||||
[browser_metrics_debugger.js]
|
||||
skip-if = os != 'linux' || debug || asan # Results should be platform agnostic - only run on linux64-opt
|
@ -0,0 +1,31 @@
|
||||
/* -*- 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";
|
||||
|
||||
/* import-globals-from ../../../shared/test/shared-head.js */
|
||||
|
||||
/**
|
||||
* This test records the number of modules loaded by DevTools, as well as the total count
|
||||
* of characters in those modules, when opening the debugger. These metrics are
|
||||
* retrieved by perfherder via logs.
|
||||
*/
|
||||
|
||||
const TEST_URL = "data:text/html;charset=UTF-8,<div>Debugger modules load test</div>";
|
||||
|
||||
add_task(async function() {
|
||||
const toolbox = await openNewTabAndToolbox(TEST_URL, "jsdebugger");
|
||||
const panel = toolbox.getCurrentPanel();
|
||||
|
||||
// Retrieve the browser loader dedicated to the Debugger.
|
||||
const debuggerLoader = panel.panelWin.getBrowserLoaderForWindow();
|
||||
const loaders = [loader.provider.loader, debuggerLoader.loader];
|
||||
|
||||
runMetricsTest({
|
||||
filterString: "devtools/client/debugger",
|
||||
loaders,
|
||||
panelName: "debugger",
|
||||
});
|
||||
});
|
@ -21,47 +21,9 @@ add_task(async function() {
|
||||
// The inspector does not use a dedicated browser loader.
|
||||
const loaders = [loader.provider.loader];
|
||||
|
||||
const allModules = getFilteredModules("", loaders);
|
||||
const inspectorModules = getFilteredModules("devtools/client/inspector", loaders);
|
||||
|
||||
const allModulesCount = allModules.length;
|
||||
const inspectorModulesCount = inspectorModules.length;
|
||||
|
||||
const allModulesChars = countCharsInModules(allModules);
|
||||
const inspectorModulesChars = countCharsInModules(inspectorModules);
|
||||
|
||||
const PERFHERDER_DATA = {
|
||||
framework: {
|
||||
name: "devtools",
|
||||
},
|
||||
suites: [{
|
||||
name: "inspector-metrics",
|
||||
value: allModulesChars,
|
||||
subtests: [
|
||||
{
|
||||
name: "inspector-modules",
|
||||
value: inspectorModulesCount,
|
||||
},
|
||||
{
|
||||
name: "inspector-chars",
|
||||
value: inspectorModulesChars,
|
||||
},
|
||||
{
|
||||
name: "all-modules",
|
||||
value: allModulesCount,
|
||||
},
|
||||
{
|
||||
name: "all-chars",
|
||||
value: allModulesChars,
|
||||
},
|
||||
],
|
||||
}],
|
||||
};
|
||||
info("PERFHERDER_DATA: " + JSON.stringify(PERFHERDER_DATA));
|
||||
|
||||
// Simply check that we found valid values.
|
||||
ok(allModulesCount > inspectorModulesCount &&
|
||||
inspectorModulesCount > 0, "Successfully recorded module count for Inspector");
|
||||
ok(allModulesChars > inspectorModulesChars &&
|
||||
inspectorModulesChars > 0, "Successfully recorded char count for Inspector");
|
||||
runMetricsTest({
|
||||
filterString: "devtools/client/inspector",
|
||||
loaders,
|
||||
panelName: "inspector",
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,12 @@
|
||||
[DEFAULT]
|
||||
tags = devtools
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
head.js
|
||||
!/devtools/client/shared/test/shared-head.js
|
||||
!/devtools/client/shared/test/telemetry-test-helpers.js
|
||||
|
||||
# Each metrics tests is loaded in a separate .ini file. This way the test is executed
|
||||
# individually, without any other test being executed before or after.
|
||||
[browser_metrics_netmonitor.js]
|
||||
skip-if = os != 'linux' || debug || asan # Results should be platform agnostic - only run on linux64-opt
|
@ -0,0 +1,31 @@
|
||||
/* -*- 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";
|
||||
|
||||
/* import-globals-from ../../../shared/test/shared-head.js */
|
||||
|
||||
/**
|
||||
* This test records the number of modules loaded by DevTools, as well as the total count
|
||||
* of characters in those modules, when opening the netmonitor. These metrics are
|
||||
* retrieved by perfherder via logs.
|
||||
*/
|
||||
|
||||
const TEST_URL = "data:text/html;charset=UTF-8,<div>Netmonitor modules load test</div>";
|
||||
|
||||
add_task(async function() {
|
||||
const toolbox = await openNewTabAndToolbox(TEST_URL, "netmonitor");
|
||||
const panel = toolbox.getCurrentPanel();
|
||||
|
||||
// Retrieve the browser loader dedicated to the Netmonitor.
|
||||
const netmonitorLoader = panel.panelWin.getBrowserLoaderForWindow();
|
||||
const loaders = [loader.provider.loader, netmonitorLoader.loader];
|
||||
|
||||
runMetricsTest({
|
||||
filterString: "devtools/client/netmonitor",
|
||||
loaders,
|
||||
panelName: "netmonitor",
|
||||
});
|
||||
});
|
@ -17,53 +17,15 @@ const TEST_URL = "data:text/html;charset=UTF-8,<div>Webconsole modules load test
|
||||
|
||||
add_task(async function() {
|
||||
const toolbox = await openNewTabAndToolbox(TEST_URL, "webconsole");
|
||||
const hud = toolbox.getCurrentPanel().hud;
|
||||
const panel = toolbox.getCurrentPanel();
|
||||
|
||||
// Retrieve the browser loader dedicated to the WebConsole.
|
||||
const webconsoleLoader = hud.ui.browserLoader;
|
||||
const webconsoleLoader = panel._frameWindow.getBrowserLoaderForWindow();
|
||||
const loaders = [loader.provider.loader, webconsoleLoader.loader];
|
||||
|
||||
const allModules = getFilteredModules("", loaders);
|
||||
const webconsoleModules = getFilteredModules("devtools/client/webconsole", loaders);
|
||||
|
||||
const allModulesCount = allModules.length;
|
||||
const webconsoleModulesCount = webconsoleModules.length;
|
||||
|
||||
const allModulesChars = countCharsInModules(allModules);
|
||||
const webconsoleModulesChars = countCharsInModules(webconsoleModules);
|
||||
|
||||
const PERFHERDER_DATA = {
|
||||
framework: {
|
||||
name: "devtools",
|
||||
},
|
||||
suites: [{
|
||||
name: "webconsole-metrics",
|
||||
value: allModulesChars,
|
||||
subtests: [
|
||||
{
|
||||
name: "webconsole-modules",
|
||||
value: webconsoleModulesCount,
|
||||
},
|
||||
{
|
||||
name: "webconsole-chars",
|
||||
value: webconsoleModulesChars,
|
||||
},
|
||||
{
|
||||
name: "all-modules",
|
||||
value: allModulesCount,
|
||||
},
|
||||
{
|
||||
name: "all-chars",
|
||||
value: allModulesChars,
|
||||
},
|
||||
],
|
||||
}],
|
||||
};
|
||||
info("PERFHERDER_DATA: " + JSON.stringify(PERFHERDER_DATA));
|
||||
|
||||
// Simply check that we found valid values.
|
||||
ok(allModulesCount > webconsoleModulesCount &&
|
||||
webconsoleModulesCount > 0, "Successfully recorded module count for WebConsole");
|
||||
ok(allModulesChars > webconsoleModulesChars &&
|
||||
webconsoleModulesChars > 0, "Successfully recorded char count for WebConsole");
|
||||
runMetricsTest({
|
||||
filterString: "devtools/client/webconsole",
|
||||
loaders,
|
||||
panelName: "webconsole",
|
||||
});
|
||||
});
|
||||
|
@ -30,3 +30,57 @@ function countCharsInModules(modules) {
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record module loading data.
|
||||
*
|
||||
* @param {Object}
|
||||
* - filterString {String} path to use to filter modules specific to the current panel
|
||||
* - loaders {Array} Array of Loaders to check for modules
|
||||
* - panelName {String} reused in identifiers for perfherder data
|
||||
*/
|
||||
function runMetricsTest({ filterString, loaders, panelName }) {
|
||||
const allModules = getFilteredModules("", loaders);
|
||||
const panelModules = getFilteredModules(filterString, loaders);
|
||||
|
||||
const allModulesCount = allModules.length;
|
||||
const panelModulesCount = panelModules.length;
|
||||
|
||||
const allModulesChars = countCharsInModules(allModules);
|
||||
const panelModulesChars = countCharsInModules(panelModules);
|
||||
|
||||
const PERFHERDER_DATA = {
|
||||
framework: {
|
||||
name: "devtools",
|
||||
},
|
||||
suites: [{
|
||||
name: panelName + "-metrics",
|
||||
value: allModulesChars,
|
||||
subtests: [
|
||||
{
|
||||
name: panelName + "-modules",
|
||||
value: panelModulesCount,
|
||||
},
|
||||
{
|
||||
name: panelName + "-chars",
|
||||
value: panelModulesChars,
|
||||
},
|
||||
{
|
||||
name: "all-modules",
|
||||
value: allModulesCount,
|
||||
},
|
||||
{
|
||||
name: "all-chars",
|
||||
value: allModulesChars,
|
||||
},
|
||||
],
|
||||
}],
|
||||
};
|
||||
info("PERFHERDER_DATA: " + JSON.stringify(PERFHERDER_DATA));
|
||||
|
||||
// Simply check that we found valid values.
|
||||
ok(allModulesCount > panelModulesCount &&
|
||||
panelModulesCount > 0, "Successfully recorded module count for " + panelName);
|
||||
ok(allModulesChars > panelModulesChars &&
|
||||
panelModulesChars > 0, "Successfully recorded char count for " + panelName);
|
||||
}
|
||||
|
@ -9,9 +9,9 @@
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/badge.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/skin/markup.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/mozilla.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/content/shared/sourceeditor/codemirror/lib/codemirror.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/content/shared/sourceeditor/codemirror/addon/dialog/dialog.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://devtools/content/shared/sourceeditor/codemirror/mozilla.css" type="text/css"/>
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://devtools/content/shared/theme-switching.js"></script>
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const Editor = require("devtools/client/sourceeditor/editor");
|
||||
const Editor = require("devtools/client/shared/sourceeditor/editor");
|
||||
const Services = require("Services");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
|
@ -15,13 +15,13 @@ devtools.jar:
|
||||
* content/styleeditor/index.xul (styleeditor/index.xul)
|
||||
* content/storage/index.xul (storage/index.xul)
|
||||
content/inspector/markup/markup.xhtml (inspector/markup/markup.xhtml)
|
||||
content/sourceeditor/codemirror/addon/dialog/dialog.css (sourceeditor/codemirror/addon/dialog/dialog.css)
|
||||
content/sourceeditor/codemirror/addon/hint/show-hint.js (sourceeditor/codemirror/addon/hint/show-hint.js)
|
||||
content/sourceeditor/codemirror/addon/tern/tern.js (sourceeditor/codemirror/addon/tern/tern.js)
|
||||
content/sourceeditor/codemirror/codemirror.bundle.js (sourceeditor/codemirror/codemirror.bundle.js)
|
||||
content/sourceeditor/codemirror/lib/codemirror.css (sourceeditor/codemirror/lib/codemirror.css)
|
||||
content/sourceeditor/codemirror/mozilla.css (sourceeditor/codemirror/mozilla.css)
|
||||
content/sourceeditor/codemirror/cmiframe.html (sourceeditor/codemirror/cmiframe.html)
|
||||
content/shared/sourceeditor/codemirror/addon/dialog/dialog.css (shared/sourceeditor/codemirror/addon/dialog/dialog.css)
|
||||
content/shared/sourceeditor/codemirror/addon/hint/show-hint.js (shared/sourceeditor/codemirror/addon/hint/show-hint.js)
|
||||
content/shared/sourceeditor/codemirror/addon/tern/tern.js (shared/sourceeditor/codemirror/addon/tern/tern.js)
|
||||
content/shared/sourceeditor/codemirror/codemirror.bundle.js (shared/sourceeditor/codemirror/codemirror.bundle.js)
|
||||
content/shared/sourceeditor/codemirror/lib/codemirror.css (shared/sourceeditor/codemirror/lib/codemirror.css)
|
||||
content/shared/sourceeditor/codemirror/mozilla.css (shared/sourceeditor/codemirror/mozilla.css)
|
||||
content/shared/sourceeditor/codemirror/cmiframe.html (shared/sourceeditor/codemirror/cmiframe.html)
|
||||
content/debugger/new/index.html (debugger/new/index.html)
|
||||
content/shadereditor/index.xul (shadereditor/index.xul)
|
||||
content/canvasdebugger/index.xul (canvasdebugger/index.xul)
|
||||
|
@ -27,7 +27,6 @@ DIRS += [
|
||||
'scratchpad',
|
||||
'shadereditor',
|
||||
'shared',
|
||||
'sourceeditor',
|
||||
'storage',
|
||||
'styleeditor',
|
||||
'themes',
|
||||
|
@ -8,9 +8,9 @@
|
||||
@import "resource://devtools/client/shared/components/tabs/Tabs.css";
|
||||
@import "resource://devtools/client/shared/components/tabs/TabBar.css";
|
||||
@import "chrome://devtools/skin/components-frame.css";
|
||||
@import "chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css";
|
||||
@import "chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css";
|
||||
@import "chrome://devtools/content/sourceeditor/codemirror/mozilla.css";
|
||||
@import "chrome://devtools/content/shared/sourceeditor/codemirror/lib/codemirror.css";
|
||||
@import "chrome://devtools/content/shared/sourceeditor/codemirror/addon/dialog/dialog.css";
|
||||
@import "chrome://devtools/content/shared/sourceeditor/codemirror/mozilla.css";
|
||||
@import "resource://devtools/client/shared/components/MdnLink.css";
|
||||
|
||||
/* Network panel components & styles */
|
||||
|
@ -8,9 +8,9 @@
|
||||
@import "resource://devtools/client/shared/components/tabs/Tabs.css";
|
||||
@import "resource://devtools/client/shared/components/tabs/TabBar.css";
|
||||
@import "chrome://devtools/skin/components-frame.css";
|
||||
@import "chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css";
|
||||
@import "chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css";
|
||||
@import "chrome://devtools/content/sourceeditor/codemirror/mozilla.css";
|
||||
@import "chrome://devtools/content/shared/sourceeditor/codemirror/lib/codemirror.css";
|
||||
@import "chrome://devtools/content/shared/sourceeditor/codemirror/addon/dialog/dialog.css";
|
||||
@import "chrome://devtools/content/shared/sourceeditor/codemirror/mozilla.css";
|
||||
@import "resource://devtools/client/shared/components/MdnLink.css";
|
||||
|
||||
/* Network panel components & styles */
|
||||
|
@ -7,7 +7,7 @@
|
||||
const { Component } = require("devtools/client/shared/vendor/react");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const Editor = require("devtools/client/sourceeditor/editor");
|
||||
const Editor = require("devtools/client/shared/sourceeditor/editor");
|
||||
|
||||
const { div } = dom;
|
||||
|
||||
|
@ -20,7 +20,7 @@ add_task(async function() {
|
||||
getSelectedRequest,
|
||||
getSortedRequests,
|
||||
} = windowRequire("devtools/client/netmonitor/src/selectors/index");
|
||||
const Editor = require("devtools/client/sourceeditor/editor");
|
||||
const Editor = require("devtools/client/shared/sourceeditor/editor");
|
||||
|
||||
store.dispatch(Actions.batchEnable(false));
|
||||
|
||||
|
@ -83,7 +83,7 @@ const webpackConfig = {
|
||||
"devtools/client/shared/vendor/reselect": "reselect",
|
||||
"devtools/client/shared/vendor/jszip": "jszip",
|
||||
|
||||
"devtools/client/sourceeditor/editor": "devtools-source-editor/src/source-editor",
|
||||
"devtools/client/shared/sourceeditor/editor": "devtools-source-editor/src/source-editor",
|
||||
|
||||
"devtools/shared/event-emitter": "devtools-modules/src/utils/event-emitter",
|
||||
"devtools/shared/platform/clipboard": path.join(__dirname, "../../client/shared/webpack/shims/platform-clipboard-stub"),
|
||||
|
@ -72,4 +72,4 @@ pref("devtools.debugger.features.xhr-breakpoints", true);
|
||||
pref("devtools.debugger.features.original-blackbox", true);
|
||||
pref("devtools.debugger.features.windowless-workers", false);
|
||||
pref("devtools.debugger.features.event-listeners-breakpoints", false);
|
||||
pref("devtools.debugger.features.log-points", false);
|
||||
pref("devtools.debugger.features.log-points", true);
|
||||
|
@ -45,7 +45,7 @@ const VARIABLES_VIEW_URL = "chrome://devtools/content/shared/widgets/VariablesVi
|
||||
|
||||
const {require, loader} = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
|
||||
|
||||
const Editor = require("devtools/client/sourceeditor/editor");
|
||||
const Editor = require("devtools/client/shared/sourceeditor/editor");
|
||||
const TargetFactory = require("devtools/client/framework/target").TargetFactory;
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
|
@ -9,7 +9,7 @@ const promise = require("promise");
|
||||
const {Task} = require("devtools/shared/task");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const { HTMLTooltip } = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
|
||||
const Editor = require("devtools/client/sourceeditor/editor");
|
||||
const Editor = require("devtools/client/shared/sourceeditor/editor");
|
||||
const {LocalizationHelper} = require("devtools/shared/l10n");
|
||||
const {extend} = require("devtools/shared/extend");
|
||||
const {WidgetMethods, setNamedTimeout} =
|
||||
|
@ -188,6 +188,10 @@ function BrowserLoaderBuilder({ baseURI, window, useOnlyShared, commonLibRequire
|
||||
|
||||
const mainModule = loaders.Module(baseURI, joinURI(baseURI, "main.js"));
|
||||
this.loader = loaders.Loader(opts);
|
||||
// When running tests, expose the BrowserLoader instance for metrics tests.
|
||||
if (flags.testing) {
|
||||
window.getBrowserLoaderForWindow = () => this;
|
||||
}
|
||||
this.require = loaders.Require(this.loader, mainModule);
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const { ul, li, div } = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
|
||||
const { scrollIntoView } = require("devtools/client/shared/scroll");
|
||||
const { preventDefaultAndStopPropagation } = require("devtools/client/shared/events");
|
||||
|
||||
loader.lazyRequireGetter(this, "focusableSelector", "devtools/client/shared/focus", true);
|
||||
|
||||
@ -182,8 +183,6 @@ class List extends Component {
|
||||
|
||||
this._setCurrentItem = this._setCurrentItem.bind(this);
|
||||
this._preventArrowKeyScrolling = this._preventArrowKeyScrolling.bind(this);
|
||||
this._preventDefaultAndStopPropagation =
|
||||
this._preventDefaultAndStopPropagation.bind(this);
|
||||
this._onKeyDown = this._onKeyDown.bind(this);
|
||||
}
|
||||
|
||||
@ -201,24 +200,11 @@ class List extends Component {
|
||||
case "ArrowDown":
|
||||
case "ArrowLeft":
|
||||
case "ArrowRight":
|
||||
this._preventDefaultAndStopPropagation(e);
|
||||
preventDefaultAndStopPropagation(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_preventDefaultAndStopPropagation(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (e.nativeEvent) {
|
||||
if (e.nativeEvent.preventDefault) {
|
||||
e.nativeEvent.preventDefault();
|
||||
}
|
||||
if (e.nativeEvent.stopPropagation) {
|
||||
e.nativeEvent.stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the passed in item to be the current item.
|
||||
*
|
||||
@ -293,7 +279,7 @@ class List extends Component {
|
||||
// On space or enter make current list item active. This means keyboard focus
|
||||
// handling is passed on to the component within the list item.
|
||||
if (document.activeElement === this.listRef.current) {
|
||||
this._preventDefaultAndStopPropagation(e);
|
||||
preventDefaultAndStopPropagation(e);
|
||||
if (active !== current) {
|
||||
this.setState({ active: current });
|
||||
}
|
||||
@ -303,7 +289,7 @@ class List extends Component {
|
||||
case "Escape":
|
||||
// If current list item is active, make it inactive and let keyboard focusing be
|
||||
// handled normally.
|
||||
this._preventDefaultAndStopPropagation(e);
|
||||
preventDefaultAndStopPropagation(e);
|
||||
if (active != null) {
|
||||
this.setState({ active: null });
|
||||
}
|
||||
|
@ -8,6 +8,9 @@ const { Component, createFactory } = require("devtools/client/shared/vendor/reac
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const { scrollIntoView } = require("devtools/client/shared/scroll");
|
||||
const { preventDefaultAndStopPropagation } = require("devtools/client/shared/events");
|
||||
|
||||
loader.lazyRequireGetter(this, "focusableSelector", "devtools/client/shared/focus", true);
|
||||
|
||||
const AUTO_EXPAND_DEPTH = 0;
|
||||
const NUMBER_OF_OFFSCREEN_ITEMS = 1;
|
||||
@ -196,6 +199,9 @@ class Tree extends Component {
|
||||
// Handle when a new item is focused.
|
||||
onFocus: PropTypes.func,
|
||||
|
||||
// The currently active (keyboard) item, if any such item exists.
|
||||
active: PropTypes.any,
|
||||
|
||||
// Handle when item is activated with a keyboard (using Space or Enter)
|
||||
onActivate: PropTypes.func,
|
||||
|
||||
@ -255,7 +261,6 @@ class Tree extends Component {
|
||||
this._focusParentNode = oncePerAnimationFrame(this._focusParentNode).bind(this);
|
||||
this._focusFirstNode = oncePerAnimationFrame(this._focusFirstNode).bind(this);
|
||||
this._focusLastNode = oncePerAnimationFrame(this._focusLastNode).bind(this);
|
||||
this._activateNode = oncePerAnimationFrame(this._activateNode).bind(this);
|
||||
|
||||
this._autoExpand = this._autoExpand.bind(this);
|
||||
this._preventArrowKeyScrolling = this._preventArrowKeyScrolling.bind(this);
|
||||
@ -264,7 +269,7 @@ class Tree extends Component {
|
||||
this._dfs = this._dfs.bind(this);
|
||||
this._dfsFromRoots = this._dfsFromRoots.bind(this);
|
||||
this._focus = this._focus.bind(this);
|
||||
this._onBlur = this._onBlur.bind(this);
|
||||
this._activate = this._activate.bind(this);
|
||||
this._onKeyDown = this._onKeyDown.bind(this);
|
||||
}
|
||||
|
||||
@ -329,16 +334,8 @@ class Tree extends Component {
|
||||
case "ArrowDown":
|
||||
case "ArrowLeft":
|
||||
case "ArrowRight":
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (e.nativeEvent) {
|
||||
if (e.nativeEvent.preventDefault) {
|
||||
e.nativeEvent.preventDefault();
|
||||
}
|
||||
if (e.nativeEvent.stopPropagation) {
|
||||
e.nativeEvent.stopPropagation();
|
||||
}
|
||||
}
|
||||
preventDefaultAndStopPropagation(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -440,11 +437,24 @@ class Tree extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
if (this.props.active != null) {
|
||||
this._activate(null);
|
||||
if (this.refs.tree !== this.activeElement) {
|
||||
this.refs.tree.focus();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.onFocus) {
|
||||
this.props.onFocus(item);
|
||||
}
|
||||
}
|
||||
|
||||
_activate(item) {
|
||||
if (this.props.onActivate) {
|
||||
this.props.onActivate(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state height and tree's scrollTop if necessary.
|
||||
*/
|
||||
@ -459,13 +469,6 @@ class Tree extends Component {
|
||||
this._updateHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state to have no focused item.
|
||||
*/
|
||||
_onBlur() {
|
||||
this._focus(0, undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired on a scroll within the tree's container, updates
|
||||
* the stored position of the view port to handle virtual view rendering.
|
||||
@ -533,15 +536,31 @@ class Tree extends Component {
|
||||
|
||||
case "Enter":
|
||||
case " ":
|
||||
this._activateNode();
|
||||
// On space or enter make focused tree node active. This means keyboard focus
|
||||
// handling is passed on to the tree node itself.
|
||||
if (this.refs.tree === this.activeElement) {
|
||||
preventDefaultAndStopPropagation(e);
|
||||
if (this.props.active !== this.props.focused) {
|
||||
this._activate(this.props.focused);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "Escape":
|
||||
preventDefaultAndStopPropagation(e);
|
||||
if (this.props.active != null) {
|
||||
this._activate(null);
|
||||
}
|
||||
|
||||
if (this.refs.tree !== this.activeElement) {
|
||||
this.refs.tree.focus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_activateNode() {
|
||||
if (this.props.onActivate) {
|
||||
this.props.onActivate(this.props.focused);
|
||||
}
|
||||
get activeElement() {
|
||||
return this.refs.tree.ownerDocument.activeElement;
|
||||
}
|
||||
|
||||
_focusFirstNode() {
|
||||
@ -640,7 +659,7 @@ class Tree extends Component {
|
||||
// the top and bottom of the page are filled with the `NUMBER_OF_OFFSCREEN_ITEMS`
|
||||
// previous and next items respectively, which helps the user to see fewer empty
|
||||
// gaps when scrolling quickly.
|
||||
const { itemHeight, focused } = this.props;
|
||||
const { itemHeight, active, focused } = this.props;
|
||||
const { scroll, height } = this.state;
|
||||
const begin = Math.max(((scroll / itemHeight) | 0) - NUMBER_OF_OFFSCREEN_ITEMS, 0);
|
||||
const end = Math.ceil((scroll + height) / itemHeight) + NUMBER_OF_OFFSCREEN_ITEMS;
|
||||
@ -667,7 +686,10 @@ class Tree extends Component {
|
||||
const { item, depth } = toRender[i];
|
||||
const key = this.props.getKey(item);
|
||||
nodes.push(TreeNode({
|
||||
key,
|
||||
// We make a key unique depending on whether the tree node is in active or
|
||||
// inactive state to make sure that it is actually replaced and the tabbable
|
||||
// state is reset.
|
||||
key: `${key}-${active === item ? "active" : "inactive"}`,
|
||||
index,
|
||||
first,
|
||||
last,
|
||||
@ -676,6 +698,7 @@ class Tree extends Component {
|
||||
id: key,
|
||||
renderItem: this.props.renderItem,
|
||||
focused: focused === item,
|
||||
active: active === item,
|
||||
expanded: this.props.isExpanded(item),
|
||||
hasChildren: !!this.props.getChildren(item).length,
|
||||
onExpand: this._onExpand,
|
||||
@ -718,6 +741,14 @@ class Tree extends Component {
|
||||
// interarction.
|
||||
this._focus(begin, toRender[0].item);
|
||||
},
|
||||
onBlur: e => {
|
||||
if (active != null) {
|
||||
const { relatedTarget } = e;
|
||||
if (!this.refs.tree.contains(relatedTarget)) {
|
||||
this._activate(null);
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick: () => {
|
||||
// Focus should always remain on the tree container itself.
|
||||
this.refs.tree.focus();
|
||||
@ -759,6 +790,8 @@ class ArrowExpanderClass extends Component {
|
||||
render() {
|
||||
const attrs = {
|
||||
className: "arrow theme-twisty",
|
||||
// To collapse/expand the tree rows use left/right arrow keys.
|
||||
tabIndex: "-1",
|
||||
onClick: this.props.expanded
|
||||
? () => this.props.onCollapse(this.props.item)
|
||||
: e => this.props.onExpand(this.props.item, e.altKey),
|
||||
@ -783,6 +816,7 @@ class TreeNodeClass extends Component {
|
||||
return {
|
||||
id: PropTypes.any.isRequired,
|
||||
focused: PropTypes.bool.isRequired,
|
||||
active: PropTypes.boool.isRequired,
|
||||
item: PropTypes.any.isRequired,
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
hasChildren: PropTypes.bool.isRequired,
|
||||
@ -797,6 +831,85 @@ class TreeNodeClass extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._onKeyDown = this._onKeyDown.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Make sure that none of the focusable elements inside the tree node container are
|
||||
// tabbable if the tree node is not active. If the tree node is active and focus is
|
||||
// outside its container, focus on the first focusable element inside.
|
||||
const elms = this.getFocusableElements();
|
||||
if (elms.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.props.active) {
|
||||
elms.forEach(elm => elm.setAttribute("tabindex", "-1"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!elms.includes(this.refs.treenode.ownerDocument.activeElement)) {
|
||||
elms[0].focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all elements that are focusable with a keyboard inside the tree node.
|
||||
*/
|
||||
getFocusableElements() {
|
||||
return Array.from(this.refs.treenode.querySelectorAll(focusableSelector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap and move keyboard focus to first/last focusable element inside the tree node to
|
||||
* prevent the focus from escaping the tree node boundaries.
|
||||
* element).
|
||||
*
|
||||
* @param {DOMNode} current currently focused element
|
||||
* @param {Boolean} back direction
|
||||
* @return {Boolean} true there is a newly focused element.
|
||||
*/
|
||||
_wrapMoveFocus(current, back) {
|
||||
const elms = this.getFocusableElements();
|
||||
let next;
|
||||
|
||||
if (elms.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (back) {
|
||||
if (elms.indexOf(current) === 0) {
|
||||
next = elms[elms.length - 1];
|
||||
next.focus();
|
||||
}
|
||||
} else if (elms.indexOf(current) === elms.length - 1) {
|
||||
next = elms[0];
|
||||
next.focus();
|
||||
}
|
||||
|
||||
return !!next;
|
||||
}
|
||||
|
||||
_onKeyDown(e) {
|
||||
const { target, key, shiftKey } = e;
|
||||
|
||||
if (key !== "Tab") {
|
||||
return;
|
||||
}
|
||||
|
||||
const focusMoved = this._wrapMoveFocus(target, shiftKey);
|
||||
if (focusMoved) {
|
||||
// Focus was moved to the begining/end of the list, so we need to prevent the
|
||||
// default focus change that would happen here.
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
render() {
|
||||
const arrow = ArrowExpander({
|
||||
item: this.props.item,
|
||||
@ -816,6 +929,9 @@ class TreeNodeClass extends Component {
|
||||
if (this.props.last) {
|
||||
classList.push("tree-node-last");
|
||||
}
|
||||
if (this.props.active) {
|
||||
classList.push("tree-node-active");
|
||||
}
|
||||
|
||||
let ariaExpanded;
|
||||
if (this.props.hasChildren) {
|
||||
@ -830,8 +946,10 @@ class TreeNodeClass extends Component {
|
||||
id: this.props.id,
|
||||
className: classList.join(" "),
|
||||
role: "treeitem",
|
||||
ref: "treenode",
|
||||
"aria-level": this.props.depth + 1,
|
||||
onClick: this.props.onClick,
|
||||
onKeyDownCapture: this.props.active && this._onKeyDown,
|
||||
"aria-expanded": ariaExpanded,
|
||||
"data-expanded": this.props.expanded ? "" : undefined,
|
||||
"data-depth": this.props.depth,
|
||||
|
@ -35,3 +35,4 @@ support-files =
|
||||
[test_tree_11.html]
|
||||
[test_tree_12.html]
|
||||
[test_tree_13.html]
|
||||
[test_tree_14.html]
|
||||
|
@ -38,12 +38,6 @@ window.onload = async function () {
|
||||
return ReactDOM.render(Tree(treeProps), window.document.body);
|
||||
}
|
||||
|
||||
const checker = Symbol();
|
||||
let isActivated;
|
||||
const mockFn = activated => {
|
||||
isActivated = activated;
|
||||
};
|
||||
|
||||
const tree = renderTree();
|
||||
|
||||
TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split(""));
|
||||
@ -140,112 +134,6 @@ window.onload = async function () {
|
||||
"-N:false",
|
||||
"--O:true",
|
||||
], "After the End key again, O should still be focused.");
|
||||
|
||||
// Test Enter key ----------------------------------------------------------
|
||||
|
||||
info("Press Enter to activate node, when onActivate is not passed.");
|
||||
isActivated = checker;
|
||||
renderTree({ focused: "L" });
|
||||
Simulate.keyDown(document.querySelector(".tree"), { key: "Enter" });
|
||||
await forceRender(tree);
|
||||
|
||||
isRenderedTree(document.body.textContent, [
|
||||
"A:false",
|
||||
"-B:false",
|
||||
"--E:false",
|
||||
"---K:false",
|
||||
"---L:true",
|
||||
"--F:false",
|
||||
"--G:false",
|
||||
"-C:false",
|
||||
"--H:false",
|
||||
"--I:false",
|
||||
"-D:false",
|
||||
"--J:false",
|
||||
"M:false",
|
||||
"-N:false",
|
||||
"--O:false",
|
||||
], "After the Enter, L should be focused and the tree remained unchanged.");
|
||||
ok(isActivated === checker,
|
||||
"Since onActivate was not specified, 'isActivated' should not be set.");
|
||||
|
||||
info("Press Enter to activate node, when onActivate is passed.");
|
||||
isActivated = checker;
|
||||
renderTree({ focused: "L", onActivate: mockFn });
|
||||
Simulate.keyDown(document.querySelector(".tree"), { key: "Enter" });
|
||||
await forceRender(tree);
|
||||
|
||||
isRenderedTree(document.body.textContent, [
|
||||
"A:false",
|
||||
"-B:false",
|
||||
"--E:false",
|
||||
"---K:false",
|
||||
"---L:true",
|
||||
"--F:false",
|
||||
"--G:false",
|
||||
"-C:false",
|
||||
"--H:false",
|
||||
"--I:false",
|
||||
"-D:false",
|
||||
"--J:false",
|
||||
"M:false",
|
||||
"-N:false",
|
||||
"--O:false",
|
||||
], "After the Enter, L should be focused and the tree remained unchanged.");
|
||||
is(isActivated, "L", "onActivate function was called with the right node.");
|
||||
|
||||
// Test Space key ----------------------------------------------------------
|
||||
|
||||
info("Press Space to activate node, when onActivate is not passed.");
|
||||
isActivated = checker;
|
||||
renderTree({ focused: "K" });
|
||||
Simulate.keyDown(document.querySelector(".tree"), { key: " " });
|
||||
await forceRender(tree);
|
||||
|
||||
isRenderedTree(document.body.textContent, [
|
||||
"A:false",
|
||||
"-B:false",
|
||||
"--E:false",
|
||||
"---K:true",
|
||||
"---L:false",
|
||||
"--F:false",
|
||||
"--G:false",
|
||||
"-C:false",
|
||||
"--H:false",
|
||||
"--I:false",
|
||||
"-D:false",
|
||||
"--J:false",
|
||||
"M:false",
|
||||
"-N:false",
|
||||
"--O:false",
|
||||
], "After the Space, K should be focused and the tree remained unchanged.");
|
||||
ok(isActivated === checker,
|
||||
"Since onActivate was not specified, 'isActivated' should not be set.");
|
||||
|
||||
info("Press Space to activate node, when onActivate is passed.");
|
||||
isActivated = checker;
|
||||
renderTree({ focused: "K", onActivate: mockFn });
|
||||
Simulate.keyDown(document.querySelector(".tree"), { key: " " });
|
||||
await forceRender(tree);
|
||||
|
||||
isRenderedTree(document.body.textContent, [
|
||||
"A:false",
|
||||
"-B:false",
|
||||
"--E:false",
|
||||
"---K:true",
|
||||
"---L:false",
|
||||
"--F:false",
|
||||
"--G:false",
|
||||
"-C:false",
|
||||
"--H:false",
|
||||
"--I:false",
|
||||
"-D:false",
|
||||
"--J:false",
|
||||
"M:false",
|
||||
"-N:false",
|
||||
"--O:false",
|
||||
], "After the Space, K should be focused and the tree remained unchanged.");
|
||||
is(isActivated, "K", "onActivate function was called with the right node.");
|
||||
} catch (e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
|
@ -0,0 +1,245 @@
|
||||
<!-- 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>
|
||||
<!--
|
||||
Test that Tree component has working keyboard interactions.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Tree component keyboard test</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script src="head.js" type="application/javascript"></script>
|
||||
<script type="application/javascript">
|
||||
|
||||
"use strict";
|
||||
|
||||
window.onload = async function() {
|
||||
try {
|
||||
const { a, button, div } =
|
||||
require("devtools/client/shared/vendor/react-dom-factories");
|
||||
const { createFactory } = browserRequire("devtools/client/shared/vendor/react");
|
||||
const {
|
||||
Simulate,
|
||||
findRenderedDOMComponentWithClass,
|
||||
findRenderedDOMComponentWithTag,
|
||||
scryRenderedDOMComponentsWithTag,
|
||||
} = browserRequire("devtools/client/shared/vendor/react-dom-test-utils");
|
||||
const Tree = createFactory(
|
||||
browserRequire("devtools/client/shared/components/VirtualizedTree"));
|
||||
|
||||
let gTree, gFocused, gActive;
|
||||
function renderTree(props = {}) {
|
||||
let toggle = true;
|
||||
const treeProps = {
|
||||
...TEST_TREE_INTERFACE,
|
||||
onFocus: x => {
|
||||
gFocused = x;
|
||||
renderTree({ focused: gFocused, active: gActive });
|
||||
},
|
||||
onActivate: x => {
|
||||
gActive = x;
|
||||
renderTree({ focused: gFocused, active: gActive });
|
||||
},
|
||||
renderItem: (x, depth, focused) => {
|
||||
toggle = !toggle;
|
||||
return toggle ?
|
||||
(div(
|
||||
{},
|
||||
`${"-".repeat(depth)}${x}:${focused}`,
|
||||
a({ href: "#" }, "Focusable 1"),
|
||||
button({ }, "Focusable 2"),
|
||||
"\n",
|
||||
)
|
||||
) : `${"-".repeat(depth)}${x}:${focused}`;
|
||||
},
|
||||
...props
|
||||
};
|
||||
|
||||
gTree = ReactDOM.render(Tree(treeProps), document.body);
|
||||
}
|
||||
|
||||
renderTree();
|
||||
const els = {
|
||||
get tree() {
|
||||
// React will replace the tree via renderTree.
|
||||
return findRenderedDOMComponentWithClass(gTree, "tree");
|
||||
},
|
||||
get anchor() {
|
||||
// When tree node becomes active/inactive, it is replaced with a newly rendered
|
||||
// one.
|
||||
return findRenderedDOMComponentWithTag(gTree, "a");
|
||||
},
|
||||
get button() {
|
||||
// When tree node becomes active/inactive, it is replaced with a newly rendered
|
||||
// one.
|
||||
return findRenderedDOMComponentWithTag(gTree, "button");
|
||||
},
|
||||
};
|
||||
|
||||
const tests = [{
|
||||
name: "Test default Tree props. Keyboard focus is set to document body by default.",
|
||||
props: { focused: undefined, active: undefined },
|
||||
activeElement: document.body,
|
||||
}, {
|
||||
name: "Focused props must be set to the first node on initial focus. " +
|
||||
"Keyboard focus should be set on the tree.",
|
||||
action: () => els.tree.focus(),
|
||||
activeElement: "tree",
|
||||
props: { focused: "A" },
|
||||
}, {
|
||||
name: "Focused node should remain set even when the tree is blured. " +
|
||||
"Keyboard focus should be set back to document body.",
|
||||
action: () => els.tree.blur(),
|
||||
props: { focused: "A" },
|
||||
activeElement: document.body,
|
||||
}, {
|
||||
name: "Unset tree's focused prop.",
|
||||
action: () => renderTree({ focused: null }),
|
||||
props: { focused: null },
|
||||
}, {
|
||||
name: "Focused node must be re-set again to the first tree node on initial " +
|
||||
"focus. Keyboard focus should be set on tree's conatiner.",
|
||||
action: () => els.tree.focus(),
|
||||
activeElement: "tree",
|
||||
props: { focused: "A" },
|
||||
}, {
|
||||
name: "Focused node should be set as active on Enter.",
|
||||
event: { type: "keyDown", el: "tree", options: { key: "Enter" }},
|
||||
props: { focused: "A", active: "A" },
|
||||
activeElement: "tree",
|
||||
}, {
|
||||
name: "Active node should be unset on Escape.",
|
||||
event: { type: "keyDown", el: "tree", options: { key: "Escape" }},
|
||||
props: { focused: "A", active: null },
|
||||
}, {
|
||||
name: "Focused node should be set as active on Space.",
|
||||
event: { type: "keyDown", el: "tree", options: { key: " " }},
|
||||
props: { focused: "A", active: "A" },
|
||||
activeElement: "tree",
|
||||
}, {
|
||||
name: "Active node should unset when focus leaves the tree.",
|
||||
action: () => els.tree.blur(),
|
||||
props: { focused: "A", active: null },
|
||||
activeElement: document.body,
|
||||
}, {
|
||||
name: "Keyboard focus should be set on tree's conatiner on focus.",
|
||||
action: () => els.tree.focus(),
|
||||
activeElement: "tree",
|
||||
}, {
|
||||
name: "Focused node should be updated to next on ArrowDown.",
|
||||
event: { type: "keyDown", el: "tree", options: { key: "ArrowDown" }},
|
||||
props: { focused: "M", active: null },
|
||||
}, {
|
||||
name: "Focused item should be set as active on Enter. Keyboard focus should be " +
|
||||
"set on the first focusable element inside the tree node, if available.",
|
||||
event: { type: "keyDown", el: "tree", options: { key: "Enter" }},
|
||||
props: { focused: "M", active: "M" },
|
||||
activeElement: "anchor",
|
||||
}, {
|
||||
name: "Keyboard focus should be set to next tabbable element inside the active " +
|
||||
"node on Tab.",
|
||||
action() {
|
||||
synthesizeKey("KEY_Tab");
|
||||
},
|
||||
props: { focused: "M", active: "M" },
|
||||
activeElement: "button",
|
||||
}, {
|
||||
name: "Keyboard focus should wrap inside the tree node when focused on last " +
|
||||
"tabbable element.",
|
||||
action() {
|
||||
synthesizeKey("KEY_Tab");
|
||||
},
|
||||
props: { focused: "M", active: "M" },
|
||||
activeElement: "anchor",
|
||||
}, {
|
||||
name: "Keyboard focus should wrap inside the tree node when focused on first " +
|
||||
"tabbable element.",
|
||||
action() {
|
||||
synthesizeKey("KEY_Tab", { shiftKey: true });
|
||||
},
|
||||
props: { focused: "M", active: "M" },
|
||||
activeElement: "button",
|
||||
}, {
|
||||
name: "Active tree node should be unset on Escape. Focus should move back to the " +
|
||||
"tree container.",
|
||||
event: { type: "keyDown", el: "tree", options: { key: "Escape" }},
|
||||
props: { focused: "M", active: null },
|
||||
activeElement: "tree",
|
||||
}, {
|
||||
name: "Focused node should be set as active on Space. Keyboard focus should be " +
|
||||
"set on the first focusable element inside the tree node, if available.",
|
||||
event: { type: "keyDown", el: "tree", options: { key: " " }},
|
||||
props: { focused: "M", active: "M" },
|
||||
activeElement: "anchor",
|
||||
}, {
|
||||
name: "Focused tree node should remain set even when the tree is blured. " +
|
||||
"Keyboard focus should be set back to document body.",
|
||||
action: () => document.activeElement.blur(),
|
||||
props: { focused: "M", active: null, },
|
||||
activeElement: document.body,
|
||||
}, {
|
||||
name: "Keyboard focus should be set on tree's conatiner on focus.",
|
||||
action: () => els.tree.focus(),
|
||||
props: { focused: "M", active: null },
|
||||
activeElement: "tree",
|
||||
}, {
|
||||
name: "Focused tree node should be updated to previous on ArrowUp.",
|
||||
event: { type: "keyDown", el: "tree", options: { key: "ArrowUp" }},
|
||||
props: { focused: "A", active: null },
|
||||
}, {
|
||||
name: "Focused item should be set as active on Enter.",
|
||||
event: { type: "keyDown", el: "tree", options: { key: "Enter" }},
|
||||
props: { focused: "A", active: "A" },
|
||||
activeElement: "tree",
|
||||
}, {
|
||||
name: "Keyboard focus should move to another focusable element outside of the " +
|
||||
"tree when there's nothing to focus on inside the tree node.",
|
||||
action() {
|
||||
synthesizeKey("KEY_Tab", { shiftKey: true });
|
||||
},
|
||||
props: { focused: "A", active: null },
|
||||
activeElement: document.documentElement,
|
||||
}];
|
||||
|
||||
for (const test of tests) {
|
||||
const { action, condition, event, props, name } = test;
|
||||
|
||||
info(name);
|
||||
if (event) {
|
||||
const { type, options, el } = event;
|
||||
const target = typeof el === "string" ? els[el] : el;
|
||||
Simulate[type](target, options);
|
||||
} else if (action) {
|
||||
action();
|
||||
}
|
||||
|
||||
await forceRender(gTree);
|
||||
|
||||
if (test.activeElement) {
|
||||
const expected = typeof test.activeElement === "string" ?
|
||||
els[test.activeElement] : test.activeElement;
|
||||
if (document.activeElement!==expected) {debugger;}
|
||||
is(document.activeElement, expected, "Focus is set correctly.");
|
||||
}
|
||||
|
||||
for (let key in props) {
|
||||
is(gTree.props[key], props[key], `${key} prop is correct.`);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
|
||||
} finally {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
22
devtools/client/shared/events.js
Normal file
22
devtools/client/shared/events.js
Normal file
@ -0,0 +1,22 @@
|
||||
/* 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";
|
||||
|
||||
/**
|
||||
* Prevent event default behaviour and stop its propagation.
|
||||
* @param {Object} event
|
||||
* Event or react synthetic event.
|
||||
*/
|
||||
exports.preventDefaultAndStopPropagation = function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (event.nativeEvent) {
|
||||
if (event.nativeEvent.preventDefault) {
|
||||
event.nativeEvent.preventDefault();
|
||||
}
|
||||
if (event.nativeEvent.stopPropagation) {
|
||||
event.nativeEvent.stopPropagation();
|
||||
}
|
||||
}
|
||||
};
|
@ -15,6 +15,7 @@ DIRS += [
|
||||
'redux',
|
||||
'remote-debugging',
|
||||
'source-map',
|
||||
'sourceeditor',
|
||||
'vendor',
|
||||
'webpack',
|
||||
'widgets',
|
||||
@ -30,6 +31,7 @@ DevToolsModules(
|
||||
'devices.js',
|
||||
'DOMHelpers.jsm',
|
||||
'enum.js',
|
||||
'events.js',
|
||||
'file-saver.js',
|
||||
'focus.js',
|
||||
'getjson.js',
|
||||
|
@ -6,7 +6,12 @@
|
||||
|
||||
DevToolsModules(
|
||||
'remote-client-manager.js',
|
||||
'version-checker.js',
|
||||
)
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += [
|
||||
'test/unit/xpcshell.ini'
|
||||
]
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('DevTools', 'about:debugging')
|
@ -0,0 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Extend from the common devtools xpcshell eslintrc config.
|
||||
"extends": "../../../../../.eslintrc.xpcshell.js"
|
||||
};
|
@ -0,0 +1,110 @@
|
||||
/* global equal */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
_compareVersionCompatibility,
|
||||
checkVersionCompatibility,
|
||||
COMPATIBILITY_STATUS,
|
||||
} = require("devtools/client/shared/remote-debugging/version-checker");
|
||||
|
||||
const TEST_DATA = [
|
||||
{
|
||||
description: "same build date and same version number",
|
||||
localBuildId: "20190131000000",
|
||||
localVersion: "60.0",
|
||||
runtimeBuildId: "20190131000000",
|
||||
runtimeVersion: "60.0",
|
||||
expected: COMPATIBILITY_STATUS.COMPATIBLE,
|
||||
},
|
||||
{
|
||||
description: "same build date and older version in range (-1)",
|
||||
localBuildId: "20190131000000",
|
||||
localVersion: "60.0",
|
||||
runtimeBuildId: "20190131000000",
|
||||
runtimeVersion: "59.0",
|
||||
expected: COMPATIBILITY_STATUS.COMPATIBLE,
|
||||
},
|
||||
{
|
||||
description: "same build date and older version in range (-2)",
|
||||
localBuildId: "20190131000000",
|
||||
localVersion: "60.0",
|
||||
runtimeBuildId: "20190131000000",
|
||||
runtimeVersion: "58.0",
|
||||
expected: COMPATIBILITY_STATUS.COMPATIBLE,
|
||||
},
|
||||
{
|
||||
description: "same build date and older version in range (-2 Nightly)",
|
||||
localBuildId: "20190131000000",
|
||||
localVersion: "60.0",
|
||||
runtimeBuildId: "20190131000000",
|
||||
runtimeVersion: "58.0a1",
|
||||
expected: COMPATIBILITY_STATUS.COMPATIBLE,
|
||||
},
|
||||
{
|
||||
description: "same build date and older version out of range (-3)",
|
||||
localBuildId: "20190131000000",
|
||||
localVersion: "60.0",
|
||||
runtimeBuildId: "20190131000000",
|
||||
runtimeVersion: "57.0",
|
||||
expected: COMPATIBILITY_STATUS.TOO_OLD,
|
||||
},
|
||||
{
|
||||
description: "same build date and newer version out of range (+1)",
|
||||
localBuildId: "20190131000000",
|
||||
localVersion: "60.0",
|
||||
runtimeBuildId: "20190131000000",
|
||||
runtimeVersion: "61.0",
|
||||
expected: COMPATIBILITY_STATUS.TOO_RECENT,
|
||||
},
|
||||
{
|
||||
description: "same major version and build date in range (-10 days)",
|
||||
localBuildId: "20190131000000",
|
||||
localVersion: "60.0",
|
||||
runtimeBuildId: "20190121000000",
|
||||
runtimeVersion: "60.0",
|
||||
expected: COMPATIBILITY_STATUS.COMPATIBLE,
|
||||
},
|
||||
{
|
||||
description: "same major version and build date in range (+2 days)",
|
||||
localBuildId: "20190131000000",
|
||||
localVersion: "60.0",
|
||||
runtimeBuildId: "20190202000000",
|
||||
runtimeVersion: "60.0",
|
||||
expected: COMPATIBILITY_STATUS.COMPATIBLE,
|
||||
},
|
||||
{
|
||||
description: "same major version and build date out of range (+8 days)",
|
||||
localBuildId: "20190131000000",
|
||||
localVersion: "60.0",
|
||||
runtimeBuildId: "20190208000000",
|
||||
runtimeVersion: "60.0",
|
||||
expected: COMPATIBILITY_STATUS.TOO_RECENT,
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function testVersionChecker() {
|
||||
for (const testData of TEST_DATA) {
|
||||
const localDescription = {
|
||||
appbuildid: testData.localBuildId,
|
||||
platformversion: testData.localVersion,
|
||||
};
|
||||
|
||||
const runtimeDescription = {
|
||||
appbuildid: testData.runtimeBuildId,
|
||||
platformversion: testData.runtimeVersion,
|
||||
};
|
||||
|
||||
const report = _compareVersionCompatibility(localDescription, runtimeDescription);
|
||||
equal(report.status, testData.expected,
|
||||
"Expected status for test: " + testData.description);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function testVersionCheckWithVeryOldClient() {
|
||||
// Use an empty object as debugger client, calling any method on it will fail.
|
||||
const emptyClient = {};
|
||||
const report = await checkVersionCompatibility(emptyClient);
|
||||
equal(report.status, COMPATIBILITY_STATUS.TOO_OLD,
|
||||
"Report status too old if debugger client is not implementing expected interface");
|
||||
});
|
@ -0,0 +1,8 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* eslint no-unused-vars: [2, {"vars": "local"}] */
|
||||
|
||||
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
|
@ -0,0 +1,7 @@
|
||||
[DEFAULT]
|
||||
tags = devtools
|
||||
head = xpcshell-head.js
|
||||
firefox-appdir = browser
|
||||
skip-if = toolkit == 'android'
|
||||
|
||||
[test_version_checker.js]
|
147
devtools/client/shared/remote-debugging/version-checker.js
Normal file
147
devtools/client/shared/remote-debugging/version-checker.js
Normal file
@ -0,0 +1,147 @@
|
||||
/* 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 Services = require("Services");
|
||||
const {AppConstants} = require("resource://gre/modules/AppConstants.jsm");
|
||||
|
||||
const MS_PER_DAY = 1000 * 60 * 60 * 24;
|
||||
|
||||
const COMPATIBILITY_STATUS = {
|
||||
COMPATIBLE: "compatible",
|
||||
TOO_OLD: "too-old",
|
||||
TOO_RECENT: "too-recent",
|
||||
};
|
||||
exports.COMPATIBILITY_STATUS = COMPATIBILITY_STATUS;
|
||||
|
||||
function getDateFromBuildID(buildID) {
|
||||
// Build IDs are a timestamp in the yyyyMMddHHmmss format.
|
||||
// Extract the year, month and day information.
|
||||
const fields = buildID.match(/(\d{4})(\d{2})(\d{2})/);
|
||||
// Date expects 0 - 11 for months
|
||||
const month = Number.parseInt(fields[2], 10) - 1;
|
||||
return new Date(fields[1], month, fields[3]);
|
||||
}
|
||||
|
||||
function getMajorVersion(platformVersion) {
|
||||
// Retrieve the major platform version, i.e. if we are on Firefox 64.0a1, it will be 64.
|
||||
return Number.parseInt(platformVersion.match(/\d+/)[0], 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the minimum and maximum supported version for remote debugging for the provided
|
||||
* version of Firefox. Backward compatibility policy for devtools supports at most 2
|
||||
* versions older than the current version.
|
||||
*
|
||||
* @param {String} localVersion
|
||||
* The version of the local Firefox instance, eg "67.0"
|
||||
* @return {Object}
|
||||
* - minVersion {String} the minimum supported version, eg "65.0a1"
|
||||
* - maxVersion {String} the first unsupported version, eg "68.0a1"
|
||||
*/
|
||||
function computeMinMaxVersion(localVersion) {
|
||||
// Retrieve the major platform version, i.e. if we are on Firefox 64.0a1, it will be 64.
|
||||
const localMajorVersion = getMajorVersion(localVersion);
|
||||
|
||||
return {
|
||||
// Define the minimum officially supported version of Firefox when connecting to a
|
||||
// remote runtime. (Use ".0a1" to support the very first nightly version)
|
||||
// This matches the release channel's version when we are on nightly,
|
||||
// or 2 versions before when we are on other channels.
|
||||
minVersion: (localMajorVersion - 2) + ".0a1",
|
||||
// The maximum version is the first excluded from the support range. That's why we
|
||||
// increase the current version by 1 and use ".0a1" to point to the first Nightly.
|
||||
// We do not support forward compatibility at all.
|
||||
maxVersion: (localMajorVersion + 1) + ".0a1",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the remote device is using a supported version of Firefox.
|
||||
*
|
||||
* @param {DebuggerClient} debuggerClient
|
||||
* DebuggerClient instance connected to the target remote Firefox.
|
||||
* @return Object with the following attributes:
|
||||
* * String status, one of COMPATIBILITY_STATUS
|
||||
* COMPATIBLE if the runtime is compatible,
|
||||
* TOO_RECENT if the runtime uses a too recent version,
|
||||
* TOO_OLD if the runtime uses a too old version.
|
||||
* * String minVersion
|
||||
* The minimum supported version.
|
||||
* * String runtimeVersion
|
||||
* The remote runtime version.
|
||||
* * String localID
|
||||
* Build ID of local runtime. A date with like this: YYYYMMDD.
|
||||
* * String deviceID
|
||||
* Build ID of remote runtime. A date with like this: YYYYMMDD.
|
||||
*/
|
||||
async function checkVersionCompatibility(debuggerClient) {
|
||||
const localDescription = {
|
||||
appbuildid: Services.appinfo.appBuildID,
|
||||
platformversion: AppConstants.MOZ_APP_VERSION,
|
||||
};
|
||||
|
||||
try {
|
||||
const deviceFront = await debuggerClient.mainRoot.getFront("device");
|
||||
const description = await deviceFront.getDescription();
|
||||
return _compareVersionCompatibility(localDescription, description);
|
||||
} catch (e) {
|
||||
// If we failed to retrieve the device description, assume we are trying to connect to
|
||||
// a really old version of Firefox.
|
||||
const localVersion = localDescription.platformversion;
|
||||
const { minVersion } = computeMinMaxVersion(localVersion);
|
||||
return {
|
||||
minVersion,
|
||||
runtimeVersion: "<55",
|
||||
status: COMPATIBILITY_STATUS.TOO_OLD,
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.checkVersionCompatibility = checkVersionCompatibility;
|
||||
|
||||
function _compareVersionCompatibility(localDescription, deviceDescription) {
|
||||
const runtimeID = deviceDescription.appbuildid.substr(0, 8);
|
||||
const localID = localDescription.appbuildid.substr(0, 8);
|
||||
|
||||
const runtimeDate = getDateFromBuildID(runtimeID);
|
||||
const localDate = getDateFromBuildID(localID);
|
||||
|
||||
const runtimeVersion = deviceDescription.platformversion;
|
||||
const localVersion = localDescription.platformversion;
|
||||
|
||||
const { minVersion, maxVersion } = computeMinMaxVersion(localVersion);
|
||||
const isTooOld = Services.vc.compare(runtimeVersion, minVersion) < 0;
|
||||
const isTooRecent = Services.vc.compare(runtimeVersion, maxVersion) >= 0;
|
||||
|
||||
const runtimeMajorVersion = getMajorVersion(runtimeVersion);
|
||||
const localMajorVersion = getMajorVersion(localVersion);
|
||||
const isSameMajorVersion = runtimeMajorVersion === localMajorVersion;
|
||||
|
||||
let status;
|
||||
if (isTooOld) {
|
||||
status = COMPATIBILITY_STATUS.TOO_OLD;
|
||||
} else if (isTooRecent) {
|
||||
status = COMPATIBILITY_STATUS.TOO_RECENT;
|
||||
} else if (isSameMajorVersion && runtimeDate - localDate > 7 * MS_PER_DAY) {
|
||||
// If both local and remote runtimes have the same major version, compare build dates.
|
||||
// This check is useful for Gecko developers as we might introduce breaking changes
|
||||
// within a Nightly cycle.
|
||||
// Still allow devices to be newer by up to a week. This accommodates those with local
|
||||
// device builds, since their devices will almost always be newer than the client.
|
||||
status = COMPATIBILITY_STATUS.TOO_RECENT;
|
||||
} else {
|
||||
status = COMPATIBILITY_STATUS.COMPATIBLE;
|
||||
}
|
||||
|
||||
return {
|
||||
localID,
|
||||
minVersion,
|
||||
runtimeID,
|
||||
runtimeVersion,
|
||||
status,
|
||||
};
|
||||
}
|
||||
// Exported for tests.
|
||||
exports._compareVersionCompatibility = _compareVersionCompatibility;
|
@ -2,7 +2,7 @@
|
||||
|
||||
module.exports = {
|
||||
// Extend from the devtools eslintrc.
|
||||
"extends": "../../.eslintrc.js",
|
||||
"extends": "../../../.eslintrc.js",
|
||||
|
||||
"rules": {
|
||||
// The inspector is being migrated to HTML and cleaned of
|
@ -10,7 +10,7 @@ CodeMirror from the project's page [1] and replace all JavaScript and
|
||||
CSS files inside the codemirror directory [2].
|
||||
|
||||
Then to recreate codemirror.bundle.js:
|
||||
> cd devtools/client/sourceeditor
|
||||
> cd devtools/client/shared/sourceeditor
|
||||
> npm install
|
||||
> webpack
|
||||
|
||||
@ -46,14 +46,14 @@ below.
|
||||
|
||||
# License
|
||||
|
||||
The following files in this directory and devtools/client/sourceeditor/test/codemirror/
|
||||
The following files in this directory and devtools/client/shared/sourceeditor/test/codemirror/
|
||||
are licensed according to the contents in the LICENSE file.
|
||||
|
||||
# Localization patches
|
||||
|
||||
diff --git a/devtools/client/sourceeditor/codemirror/addon/search/search.js b/devtools/client/sourceeditor/codemirror/addon/search/search.js
|
||||
--- a/devtools/client/sourceeditor/codemirror/addon/search/search.js
|
||||
+++ b/devtools/client/sourceeditor/codemirror/addon/search/search.js
|
||||
diff --git a/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js b/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js
|
||||
--- a/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js
|
||||
+++ b/devtools/client/shared/sourceeditor/codemirror/addon/search/search.js
|
||||
@@ -93,32 +93,47 @@
|
||||
} else {
|
||||
query = parseString(query)
|
||||
@ -231,9 +231,9 @@ diff --git a/devtools/client/sourceeditor/codemirror/addon/search/search.js b/de
|
||||
See Bug 1482875. Not needed anymore when https://github.com/codemirror/CodeMirror/pull/5751 lands.
|
||||
|
||||
```diff
|
||||
diff --git a/devtools/client/sourceeditor/codemirror/lib/codemirror.js b/devtools/client/sourceeditor/codemirror/lib/codemirror.js
|
||||
--- a/devtools/client/sourceeditor/codemirror/lib/codemirror.js
|
||||
+++ b/devtools/client/sourceeditor/codemirror/lib/codemirror.js
|
||||
diff --git a/devtools/client/shared/sourceeditor/codemirror/lib/codemirror.js b/devtools/client/shared/sourceeditor/codemirror/lib/codemirror.js
|
||||
--- a/devtools/client/shared/sourceeditor/codemirror/lib/codemirror.js
|
||||
+++ b/devtools/client/shared/sourceeditor/codemirror/lib/codemirror.js
|
||||
@@ -9256,8 +9256,9 @@ TextareaInput.prototype.init = function
|
||||
|
||||
on(display.scroller, "paste", function (e) {
|
||||
@ -251,7 +251,7 @@ diff --git a/devtools/client/sourceeditor/codemirror/lib/codemirror.js b/devtool
|
||||
# Footnotes
|
||||
|
||||
[1] http://codemirror.net
|
||||
[2] devtools/client/sourceeditor/codemirror
|
||||
[3] devtools/client/sourceeditor/test/browser_codemirror.js
|
||||
[2] devtools/client/shared/sourceeditor/codemirror
|
||||
[3] devtools/client/shared/sourceeditor/test/browser_codemirror.js
|
||||
[4] devtools/client/jar.mn
|
||||
[5] devtools/client/sourceeditor/editor.js
|
||||
[5] devtools/client/shared/sourceeditor/editor.js
|
@ -8,11 +8,11 @@
|
||||
const AutocompletePopup = require("devtools/client/shared/autocomplete-popup");
|
||||
|
||||
loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true);
|
||||
loader.lazyRequireGetter(this, "CSSCompleter", "devtools/client/sourceeditor/css-autocompleter");
|
||||
loader.lazyRequireGetter(this, "CSSCompleter", "devtools/client/shared/sourceeditor/css-autocompleter");
|
||||
|
||||
const CM_TERN_SCRIPTS = [
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/tern/tern.js",
|
||||
"chrome://devtools/content/sourceeditor/codemirror/addon/hint/show-hint.js",
|
||||
"chrome://devtools/content/shared/sourceeditor/codemirror/addon/tern/tern.js",
|
||||
"chrome://devtools/content/shared/sourceeditor/codemirror/addon/hint/show-hint.js",
|
||||
];
|
||||
|
||||
const autocompleteMap = new WeakMap();
|
14
devtools/client/shared/sourceeditor/codemirror/cmiframe.html
Normal file
14
devtools/client/shared/sourceeditor/codemirror/cmiframe.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html dir='ltr'>
|
||||
<head>
|
||||
<style id="cmBaseStyle">
|
||||
html, body { height: 100%; }
|
||||
body { margin: 0; overflow: hidden; }
|
||||
.CodeMirror { width: 100% !important; line-height: 1.25 !important; }
|
||||
</style>
|
||||
<link rel='stylesheet' href="chrome://devtools/content/shared/sourceeditor/codemirror/lib/codemirror.css">
|
||||
<link rel='stylesheet' href="chrome://devtools/content/shared/sourceeditor/codemirror/addon/dialog/dialog.css">
|
||||
<link rel='stylesheet' href="chrome://devtools/content/shared/sourceeditor/codemirror/mozilla.css">
|
||||
</head>
|
||||
<body class='theme-body devtools-monospace'></body>
|
||||
</html>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user