Merge mozilla-central to inbound. a=merge CLOSED TREE

This commit is contained in:
shindli 2019-02-13 23:36:24 +02:00
commit 54aa04bfd1
332 changed files with 6739 additions and 3580 deletions

View File

@ -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

View File

@ -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

View File

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

View File

@ -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();

View File

@ -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;

View File

@ -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" />

View File

@ -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")],

View File

@ -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) {

View File

@ -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();

View File

@ -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);
}

View File

@ -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

View File

@ -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() {

View File

@ -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,

View File

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

View File

@ -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">

View File

@ -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];

View File

@ -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%;

View File

@ -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;
}

View File

@ -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",

View File

@ -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>

View File

@ -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

View File

@ -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");
});

View File

@ -36,6 +36,9 @@ add_task(async function() {
await pressResume(dbg);
assertPausedLocation(dbg);
await pressStepOver(dbg);
assertPausedLocation(dbg);
await pressStepIn(dbg);
assertPausedLocation(dbg);

View File

@ -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]
]
});

View File

@ -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 }]
]

View File

@ -81,7 +81,6 @@ add_task(async function() {
await waitForPaused(dbg);
assertPausedLocation(dbg);
await stepIn(dbg);
await stepIn(dbg);
assertPausedLocation(dbg);

View File

@ -24,6 +24,6 @@ add_task(async function test() {
await stepIn(dbg);
await stepIn(dbg);
assertDebugLine(dbg, 42267);
assertDebugLine(dbg, 42271);
assertPausedLocation(dbg);
});

View File

@ -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']

View File

@ -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

View File

@ -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",
});
});

View File

@ -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",
});
});

View File

@ -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

View File

@ -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",
});
});

View File

@ -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",
});
});

View File

@ -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);
}

View File

@ -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>

View File

@ -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");

View File

@ -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)

View File

@ -27,7 +27,6 @@ DIRS += [
'scratchpad',
'shadereditor',
'shared',
'sourceeditor',
'storage',
'styleeditor',
'themes',

View File

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

View File

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

View File

@ -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;

View File

@ -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));

View File

@ -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"),

View File

@ -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);

View File

@ -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");

View File

@ -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} =

View File

@ -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);
}

View File

@ -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 });
}

View File

@ -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,

View File

@ -35,3 +35,4 @@ support-files =
[test_tree_11.html]
[test_tree_12.html]
[test_tree_13.html]
[test_tree_14.html]

View File

@ -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 {

View File

@ -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>

View 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();
}
}
};

View File

@ -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',

View File

@ -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')

View File

@ -0,0 +1,6 @@
"use strict";
module.exports = {
// Extend from the common devtools xpcshell eslintrc config.
"extends": "../../../../../.eslintrc.xpcshell.js"
};

View File

@ -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");
});

View File

@ -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");

View File

@ -0,0 +1,7 @@
[DEFAULT]
tags = devtools
head = xpcshell-head.js
firefox-appdir = browser
skip-if = toolkit == 'android'
[test_version_checker.js]

View 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;

View File

@ -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

View File

@ -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

View File

@ -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();

View 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