Backed out 4 changesets (bug 1834725) for causing failures on browser_styleeditor_loading_with_containers.js. CLOSED TREE

Backed out changeset 0965e956200a (bug 1834725)
Backed out changeset 4a8151163607 (bug 1834725)
Backed out changeset a56f42223377 (bug 1834725)
Backed out changeset a920356b63eb (bug 1834725)
This commit is contained in:
Natalia Csoregi 2024-01-11 19:57:47 +02:00
Родитель 15caa404ac
Коммит 9d85b6aa2f
26 изменённых файлов: 259 добавлений и 428 удалений

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

@ -51,8 +51,6 @@ const DBG_STRINGS_URI = [
// These are used in the AppErrorBoundary component
"devtools/client/locales/startup.properties",
"devtools/client/locales/components.properties",
// Used by SourceMapLoader
"devtools/client/locales/toolbox.properties",
];
const L10N = new MultiLocalizationHelper(...DBG_STRINGS_URI);

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

@ -6,6 +6,7 @@
* Redux actions for the sources state
* @module actions/sources
*/
import { PROMISE } from "../utils/middleware/promise";
import { insertSourceActors } from "../../actions/source-actors";
import {
makeSourceId,
@ -68,67 +69,36 @@ function loadSourceMaps(sources) {
* @static
*/
function loadSourceMap(sourceActor) {
return async function ({ dispatch, getState, sourceMapLoader, panel }) {
return async function ({ dispatch, getState, sourceMapLoader }) {
if (!prefs.clientSourceMapsEnabled || !sourceActor.sourceMapURL) {
return [];
}
let sources, ignoreListUrls, resolvedSourceMapURL, exception;
let data = null;
try {
// Ignore sourceMapURL on scripts that are part of HTML files, since
// we currently treat sourcemaps as Source-wide, not SourceActor-specific.
const source = getSourceByActorId(getState(), sourceActor.id);
if (source) {
({ sources, ignoreListUrls, resolvedSourceMapURL, exception } =
await sourceMapLoader.loadSourceMap({
// Using source ID here is historical and eventually we'll want to
// switch to all of this being per-source-actor.
id: source.id,
url: sourceActor.url || "",
sourceMapBaseURL: sourceActor.sourceMapBaseURL || "",
sourceMapURL: sourceActor.sourceMapURL || "",
isWasm: sourceActor.introductionType === "wasm",
}));
data = await sourceMapLoader.getOriginalURLs({
// Using source ID here is historical and eventually we'll want to
// switch to all of this being per-source-actor.
id: source.id,
url: sourceActor.url || "",
sourceMapBaseURL: sourceActor.sourceMapBaseURL || "",
sourceMapURL: sourceActor.sourceMapURL || "",
isWasm: sourceActor.introductionType === "wasm",
});
dispatch({
type: "ADD_SOURCEMAP_IGNORE_LIST_SOURCES",
[PROMISE]: sourceMapLoader.getSourceMapIgnoreList(source.id),
});
}
} catch (e) {
exception = `Internal error: ${e.message}`;
console.error(e);
}
if (resolvedSourceMapURL) {
dispatch({
type: "RESOLVED_SOURCEMAP_URL",
sourceActor,
resolvedSourceMapURL,
});
}
if (ignoreListUrls?.length) {
dispatch({
type: "ADD_SOURCEMAP_IGNORE_LIST_SOURCES",
ignoreListUrls,
});
}
if (exception) {
// Catch all errors and log them to the Web Console for users to see.
const message = L10N.getFormatStr(
"toolbox.sourceMapFailure",
exception,
sourceActor.url,
sourceActor.sourceMapURL
);
panel.toolbox.commands.targetCommand.targetFront.logWarningInPage(
message,
"source map",
resolvedSourceMapURL
);
dispatch({
type: "SOURCE_MAP_ERROR",
sourceActor,
errorMessage: exception,
});
if (!data || !data.length) {
// If this source doesn't have a sourcemap or there are no original files
// existing, enable it for pretty printing
dispatch({
@ -140,7 +110,7 @@ function loadSourceMap(sourceActor) {
// Before dispatching this action, ensure that the related sourceActor is still registered
validateSourceActor(getState(), sourceActor);
return sources;
return data;
};
}

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

@ -9,8 +9,12 @@ import {
makeSource,
makeSourceURL,
makeOriginalSource,
waitForState,
} from "../../../utils/test-head";
const { getSource, getSourceCount, getSelectedSource } = selectors;
const { getSource, getSourceCount, getSelectedSource, getSourceByURL } =
selectors;
import sourceQueue from "../../../utils/source-queue";
import { generatedToOriginalId } from "devtools/client/shared/source-map-loader/index";
import { mockCommandClient } from "../../tests/helpers/mockCommandClient";
@ -63,21 +67,46 @@ describe("sources - new sources", () => {
expect(selected && selected.url).toBe(baseSource.url);
});
it("should add original sources", async () => {
const { dispatch, getState } = createStore(
mockCommandClient,
{},
{
getOriginalURLs: async source => [
{
id: generatedToOriginalId(source.id, "magic.js"),
url: "magic.js",
},
],
getOriginalLocations: async items => items,
getOriginalLocation: location => location,
}
);
await dispatch(
actions.newGeneratedSource(
makeSource("base.js", { sourceMapURL: "base.js.map" })
)
);
const magic = getSourceByURL(getState(), "magic.js");
expect(magic && magic.url).toEqual("magic.js");
});
// eslint-disable-next-line
it("should not attempt to fetch original sources if it's missing a source map url", async () => {
const loadSourceMap = jest.fn();
const getOriginalURLs = jest.fn();
const { dispatch } = createStore(
mockCommandClient,
{},
{
loadSourceMap,
getOriginalURLs,
getOriginalLocations: async items => items,
getOriginalLocation: location => location,
}
);
await dispatch(actions.newGeneratedSource(makeSource("base.js")));
expect(loadSourceMap).not.toHaveBeenCalled();
expect(getOriginalURLs).not.toHaveBeenCalled();
});
// eslint-disable-next-line
@ -86,7 +115,7 @@ describe("sources - new sources", () => {
mockCommandClient,
{},
{
loadSourceMap: async () => new Promise(_ => {}),
getOriginalURLs: async () => new Promise(_ => {}),
getOriginalLocations: async items => items,
getOriginalLocation: location => location,
}
@ -100,4 +129,44 @@ describe("sources - new sources", () => {
const base = getSource(getState(), "base.js");
expect(base && base.id).toEqual("base.js");
});
// eslint-disable-next-line
it("shouldn't let one slow loading source map delay all the other source maps", async () => {
const dbg = createStore(
mockCommandClient,
{},
{
getOriginalURLs: async source => {
if (source.id == "foo.js") {
// simulate a hang loading foo.js.map
return new Promise(_ => {});
}
const url = source.id.replace(".js", ".cljs");
return [
{
id: generatedToOriginalId(source.id, url),
url,
},
];
},
getOriginalLocations: async items => items,
getGeneratedLocation: location => location,
}
);
const { dispatch, getState } = dbg;
await dispatch(
actions.newGeneratedSources([
makeSource("foo.js", { sourceMapURL: "foo.js.map" }),
makeSource("bar.js", { sourceMapURL: "bar.js.map" }),
makeSource("bazz.js", { sourceMapURL: "bazz.js.map" }),
])
);
await sourceQueue.flush();
await waitForState(dbg, state => getSourceCount(state) == 5);
expect(getSourceCount(getState())).toEqual(5);
const barCljs = getSourceByURL(getState(), "bar.cljs");
expect(barCljs && barCljs.url).toEqual("bar.cljs");
const bazzCljs = getSourceByURL(getState(), "bazz.cljs");
expect(bazzCljs && bazzCljs.url).toEqual("bazz.cljs");
});
});

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

@ -12,14 +12,13 @@ import actions from "../actions";
import AccessibleImage from "./shared/AccessibleImage";
import {
getSelectedLocation,
getSelectedSource,
getPaneCollapse,
getActiveSearch,
getQuickOpenEnabled,
getOrientation,
getIsCurrentThreadPaused,
isMapScopesEnabled,
getSourceMapErrorForSourceActor,
} from "../selectors";
const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
@ -66,7 +65,7 @@ class App extends Component {
openQuickOpen: PropTypes.func.isRequired,
orientation: PropTypes.oneOf(["horizontal", "vertical"]).isRequired,
quickOpenEnabled: PropTypes.bool.isRequired,
selectedLocation: PropTypes.object,
selectedSource: PropTypes.object,
setActiveSearch: PropTypes.func.isRequired,
setOrientation: PropTypes.func.isRequired,
setPrimaryPaneTab: PropTypes.func.isRequired,
@ -209,16 +208,6 @@ class App extends Component {
}
renderEditorNotificationBar() {
if (this.props.sourceMapError) {
return div(
{ className: "editor-notification-footer", "aria-role": "status" },
span(
{ className: "info icon" },
React.createElement(AccessibleImage, { className: "sourcemap" })
),
`Source Map Error: ${this.props.sourceMapError}`
);
}
if (this.props.showOriginalVariableMappingWarning) {
return div(
{ className: "editor-notification-footer", "aria-role": "status" },
@ -256,7 +245,7 @@ class App extends Component {
startPanelSize: startPanelSize,
endPanelSize: endPanelSize,
}),
!this.props.selectedLocation
!this.props.selectedSource
? React.createElement(WelcomeBox, {
horizontal,
toggleShortcutsModal: () => this.toggleShortcutsModal(),
@ -362,27 +351,24 @@ App.childContextTypes = {
};
const mapStateToProps = state => {
const selectedLocation = getSelectedLocation(state);
const selectedSource = getSelectedSource(state);
const mapScopeEnabled = isMapScopesEnabled(state);
const isPaused = getIsCurrentThreadPaused(state);
const showOriginalVariableMappingWarning =
isPaused &&
selectedLocation?.source.isOriginal &&
!selectedLocation?.source.isPrettyPrinted &&
selectedSource?.isOriginal &&
!selectedSource.isPrettyPrinted &&
!mapScopeEnabled;
return {
showOriginalVariableMappingWarning,
selectedLocation,
selectedSource,
startPanelCollapsed: getPaneCollapse(state, "start"),
endPanelCollapsed: getPaneCollapse(state, "end"),
activeSearch: getActiveSearch(state),
quickOpenEnabled: getQuickOpenEnabled(state),
orientation: getOrientation(state),
sourceMapError: selectedLocation?.sourceActor
? getSourceMapErrorForSourceActor(state, selectedLocation.sourceActor.id)
: null,
};
};

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

@ -25,14 +25,6 @@ function initialSourceActorsState() {
// but this may be invalid. The source map URL or source map file content may be invalid.
// In these scenarios we will remove the source actor from this set.
mutableSourceActorsWithSourceMap: new Set(),
// Map(Source Actor ID: string => string)
// Store the exception message when processing the sourceMapURL field of the source actor.
mutableSourceMapErrors: new Map(),
// Map(Source Actor ID: string => string)
// When a bundle has a functional sourcemap, reports the resolved source map URL.
mutableResolvedSourceMapURL: new Map(),
};
}
@ -87,22 +79,6 @@ export default function update(state = initialSourceActorsState(), action) {
};
}
return state;
case "SOURCE_MAP_ERROR": {
state.mutableSourceMapErrors.set(
action.sourceActor.id,
action.errorMessage
);
return { ...state };
}
case "RESOLVED_SOURCEMAP_URL": {
state.mutableResolvedSourceMapURL.set(
action.sourceActor.id,
action.resolvedSourceMapURL
);
return { ...state };
}
}
return state;

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

@ -125,15 +125,17 @@ function update(state = initialSourceBlackBoxState(), action) {
};
}
case "ADD_SOURCEMAP_IGNORE_LIST_SOURCES": {
return {
...state,
sourceMapIgnoreListUrls: [
...state.sourceMapIgnoreListUrls,
...action.ignoreListUrls,
],
};
}
case "ADD_SOURCEMAP_IGNORE_LIST_SOURCES":
if (action.status == "done") {
return {
...state,
sourceMapIgnoreListUrls: [
...state.sourceMapIgnoreListUrls,
...action.value,
],
};
}
return state;
case "NAVIGATE":
return initialSourceBlackBoxState(state);

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

@ -40,14 +40,6 @@ export function isSourceActorWithSourceMap(state, sourceActorId) {
return state.sourceActors.mutableSourceActorsWithSourceMap.has(sourceActorId);
}
export function getSourceMapErrorForSourceActor(state, sourceActorId) {
return state.sourceActors.mutableSourceMapErrors.get(sourceActorId);
}
export function getSourceMapResolvedURL(state, sourceActorId) {
return state.sourceActors.mutableResolvedSourceMapURL.get(sourceActorId);
}
// Used by threads selectors
/**
* Get all Source Actor objects for a given thread. See create.js:createSourceActor()

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

@ -71,3 +71,8 @@ function getOriginalScope(dbg) {
dbg.selectors.getCurrentThread()
);
}
function findFooterNotificationMessage(dbg) {
return dbg.win.document.querySelector(".editor-notification-footer")
?.innerText;
}

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

@ -20,18 +20,12 @@ add_task(async function () {
"doc-sourcemap-bogus.html",
"non-existant-map.js",
"map-with-failed-original-request.js",
"map-with-failed-original-request.original.js",
"invalid-json-map.js"
"map-with-failed-original-request.original.js"
);
// Make sure there is only the expected sources and we miss some original sources.
is(dbg.selectors.getSourceCount(), 4, "Only 4 source exists");
is(dbg.selectors.getSourceCount(), 3, "Only 3 source exists");
await selectSource(dbg, "non-existant-map.js");
is(
findFooterNotificationMessage(dbg),
"Source Map Error: request failed with status 404",
"There is a warning about the missing source map file"
);
// We should still be able to set breakpoints and pause in the
// generated source.
@ -45,20 +39,7 @@ add_task(async function () {
);
await resume(dbg);
// Test a Source Map with invalid JSON
await selectSource(dbg, "invalid-json-map.js");
is(
findFooterNotificationMessage(dbg),
"Source Map Error: JSON.parse: expected property name or '}' at line 2 column 3 of the JSON data",
"There is a warning about the missing source map file"
);
// Test a Source Map with missing original text content
await selectSource(dbg, "map-with-failed-original-request.js");
ok(
!findElement(dbg, "editorNotificationFooter"),
"The source-map is valid enough to not spawn a warning message"
);
await addBreakpoint(dbg, "map-with-failed-original-request.js", 7);
invokeInTab("changeStyleAttribute");
await waitForPaused(dbg);
@ -73,15 +54,6 @@ add_task(async function () {
// The original file is visible in the source tree and can be selected,
// but its content can't be displayed
await selectSource(dbg, "map-with-failed-original-request.original.js");
const notificationMessage = DEBUGGER_L10N.getFormatStr(
"editorNotificationFooter.noOriginalScopes",
DEBUGGER_L10N.getStr("scopes.showOriginalScopes")
);
is(
findFooterNotificationMessage(dbg),
notificationMessage,
"There is no warning about source-map but rather one about original scopes"
);
is(
getCM(dbg).getValue(),
`Error while fetching an original source: request failed with status 404\nSource URL: ${EXAMPLE_URL}map-with-failed-original-request.original.js`

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

@ -48,7 +48,7 @@ add_task(async function () {
is(
value,
`Source map error: NetworkError when attempting to fetch resource.\nResource URL: ${BASE_URL}/index.html\nSource Map URL: ${BASE_URL}/redirect[Learn More]`,
`Source map error: Error: NetworkError when attempting to fetch resource.\nResource URL: ${BASE_URL}/index.html\nSource Map URL: ${BASE_URL}/redirect[Learn More]`,
"A source map error message is logged indicating the redirect failed"
);
});

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

@ -11,6 +11,5 @@
<body>
<script src="non-existant-map.js"></script>
<script src="map-with-failed-original-request.js"></script>
<script src="invalid-json-map.js"></script>
</body>
</html>

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

@ -1,3 +0,0 @@
function aBundleWithCorruptedSourceMapFile() {}
//# sourceMappingURL=map-with-json-error.map

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

@ -1,3 +0,0 @@
{
thisIsInvalidJSON()
}

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

@ -1832,7 +1832,6 @@ const selectors = {
fileSearchInput: ".search-bar input",
watchExpressionsHeader: ".watch-expressions-pane ._header .header-label",
watchExpressionsAddButton: ".watch-expressions-pane ._header .plus",
editorNotificationFooter: ".editor-notification-footer",
};
function getSelector(elementName, ...args) {
@ -3110,10 +3109,3 @@ async function checkAdditionalThreadCount(dbg, count) {
);
ok(true, `Have ${count} threads`);
}
/**
* Retrieve the text displayed as warning under the editor.
*/
function findFooterNotificationMessage(dbg) {
return findElement(dbg, "editorNotificationFooter")?.innerText;
}

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

@ -319,13 +319,7 @@ class SourceMapURLService {
let result = null;
try {
await map.loaded;
} catch (e) {
// SourceMapLoader.getOriginalURLs may throw, but it will handle
// the exception and notify the user via a console message.
// So ignore the exception here, which is meant to be used by the Debugger.
}
try {
const position = await this._sourceMapLoader.getOriginalLocation({
sourceId: map.id,
line: query.line,

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

@ -1409,7 +1409,10 @@ Toolbox.prototype = {
if (this._sourceMapLoader) {
return this._sourceMapLoader;
}
this._sourceMapLoader = new SourceMapLoader(this.commands.targetCommand);
this._sourceMapLoader = new SourceMapLoader();
this._sourceMapLoader.on("source-map-error", message =>
this.target.logWarningInPage(message, "source map")
);
return this._sourceMapLoader;
},
@ -4079,7 +4082,9 @@ Toolbox.prototype = {
this._pausedTargets = null;
if (this._sourceMapLoader) {
this._sourceMapLoader.destroy();
this._sourceMapLoader.stopSourceMapWorker();
// Unregister all listeners
this._sourceMapLoader.clearEvents();
this._sourceMapLoader = null;
}

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

@ -210,7 +210,7 @@ async function testRuleViewLinkLabel(view) {
is(
value,
STYLESHEET_DATA_URL_CONTENTS + ":1",
encodeURIComponent(STYLESHEET_DATA_URL_CONTENTS) + ":1",
"Rule view data URL stylesheet display value matches contents"
);
is(

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

@ -25,36 +25,31 @@ const {
} = require("resource://devtools/client/shared/source-map-loader/utils/index.js");
class SourceMapLoader extends WorkerDispatcher {
constructor(targetCommand) {
super(SOURCE_MAP_WORKER_URL);
this.#targetCommand = targetCommand;
}
#setSourceMapForGeneratedSources = this.task(
"setSourceMapForGeneratedSources"
);
#getOriginalURLs = this.task("getOriginalURLs");
#getOriginalSourceText = this.task("getOriginalSourceText");
#targetCommand = null;
constructor() {
super(SOURCE_MAP_WORKER_URL);
}
async getOriginalURLs(urlInfo) {
try {
return await this.#getOriginalURLs(urlInfo);
} catch (error) {
// Note that tests may not pass the targetCommand
if (this.#targetCommand) {
// Catch all errors and log them to the Web Console for users to see.
const message = L10N.getFormatStr(
"toolbox.sourceMapFailure",
error,
urlInfo.url,
urlInfo.sourceMapURL
);
this.#targetCommand.targetFront.logWarningInPage(message, "source map");
}
const message = L10N.getFormatStr(
"toolbox.sourceMapFailure",
error,
urlInfo.url,
urlInfo.sourceMapURL
);
this.emit("source-map-error", message);
// And re-throw the error so that the debugger can also interpret the error
throw error;
// It's ok to swallow errors here, because a null
// result just means that no source map was found.
return null;
}
}
@ -74,7 +69,7 @@ class SourceMapLoader extends WorkerDispatcher {
getOriginalLocations = this.task("getOriginalLocations");
getGeneratedRangesForOriginal = this.task("getGeneratedRangesForOriginal");
getFileGeneratedRange = this.task("getFileGeneratedRange");
loadSourceMap = this.task("loadSourceMap");
getSourceMapIgnoreList = this.task("getSourceMapIgnoreList");
async getOriginalSourceText(originalSourceId) {
try {
@ -85,12 +80,7 @@ class SourceMapLoader extends WorkerDispatcher {
error.message,
error.metadata ? error.metadata.url : "<unknown>"
);
// Note that tests may not pass the targetCommand
if (this.#targetCommand) {
// Catch all errors and log them to the Web Console for users to see.
this.#targetCommand.targetFront.logWarningInPage(message, "source map");
}
this.emit("source-map-error", message);
// Also replace the result with the error text.
// Note that this result has to have the same form
@ -119,14 +109,7 @@ class SourceMapLoader extends WorkerDispatcher {
return rv;
}
destroy() {
// Request to stop the underlyin DOM Worker
this.stop();
// Unregister all listeners
this.clearEvents();
// SourceMapLoader may be leaked and so it is important to clear most outer references
this.#targetCommand = null;
}
stopSourceMapWorker = this.stop.bind(this);
}
EventEmitter.decorate(SourceMapLoader.prototype);

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

@ -25,7 +25,6 @@ const {
const assert = require("resource://devtools/client/shared/source-map-loader/utils/assert.js");
const {
fetchSourceMap,
resolveSourceMapURL,
hasOriginalURL,
clearOriginalURLs,
} = require("resource://devtools/client/shared/source-map-loader/utils/fetchSourceMap.js");
@ -46,87 +45,15 @@ const {
clearWasmXScopes,
} = require("resource://devtools/client/shared/source-map-loader/wasm-dwarf/wasmXScopes.js");
/**
* Create "original source info" objects being handed over to the main thread
* to describe original sources referenced in a source map
*/
function mapToOriginalSourceInfos(generatedId, urls) {
return urls.map(url => {
return {
id: generatedToOriginalId(generatedId, url),
url,
};
});
}
/**
* Load the source map and retrieved infos about all the original sources
* referenced in that source map.
*
* @param {Object} generatedSource
* Source object for a bundle referencing a source map
* @return {Array<Object>|null}
* List of object with id and url attributes describing the original sources.
*/
async function getOriginalURLs(generatedSource) {
const { resolvedSourceMapURL, baseURL } =
resolveSourceMapURL(generatedSource);
const map = await fetchSourceMap(
generatedSource,
resolvedSourceMapURL,
baseURL
);
return map ? mapToOriginalSourceInfos(generatedSource.id, map.sources) : null;
await fetchSourceMap(generatedSource);
const data = await getSourceMapWithMetadata(generatedSource.id);
return data ? data.sources : null;
}
/**
* Load the source map for a given bundle and return information
* about the related original sources and the source map itself.
*
* @param {Object} generatedSource
* Source object for the bundle.
* @return {Object}
* - {Array<Object>} sources
* Object with id and url attributes, refering to the related original sources
* referenced in the source map.
* - [String} resolvedSourceMapURL
* Absolute URL for the source map file.
* - {Array<String>} ignoreListUrls
* List of URLs of sources, designated by the source map, to be ignored in the debugger.
* - {String} exception
* In case of error, a string describing the situation.
*/
async function loadSourceMap(generatedSource) {
const { resolvedSourceMapURL, baseURL } =
resolveSourceMapURL(generatedSource);
try {
const map = await fetchSourceMap(
generatedSource,
resolvedSourceMapURL,
baseURL
);
if (!map.sources.length) {
throw new Error("No sources are declared in this source map.");
}
let ignoreListUrls = [];
if (map.x_google_ignoreList?.length) {
ignoreListUrls = map.x_google_ignoreList.map(
sourceIndex => map.sources[sourceIndex]
);
}
return {
sources: mapToOriginalSourceInfos(generatedSource.id, map.sources),
resolvedSourceMapURL,
ignoreListUrls,
};
} catch (e) {
return {
sources: [],
resolvedSourceMapURL,
ignoreListUrls: [],
exception: e.message,
};
}
async function getSourceMapIgnoreList(generatedSourceId) {
const data = await getSourceMapWithMetadata(generatedSourceId);
return data ? data.ignoreListUrls : [];
}
const COMPUTED_SPANS = new WeakSet();
@ -631,7 +558,6 @@ function clearSourceMaps() {
module.exports = {
getOriginalURLs,
loadSourceMap,
hasOriginalURL,
getOriginalRanges,
getGeneratedRanges,
@ -641,6 +567,7 @@ module.exports = {
getOriginalSourceText,
getGeneratedRangesForOriginal,
getFileGeneratedRange,
getSourceMapIgnoreList,
setSourceMapForGeneratedSources,
clearSourceMaps,
};

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

@ -154,10 +154,16 @@ add_task(async function testBaseURLErrorHandling() {
sourceMapBaseURL: "http:://example.com/",
};
try {
await gSourceMapLoader.getOriginalURLs(source);
ok(false, "Should throw");
} catch (e) {
is(e.message, "URL constructor: http:://example.com/ is not a valid URL.");
}
const onError = gSourceMapLoader.once("source-map-error");
is(
await gSourceMapLoader.getOriginalURLs(source),
null,
"The error is silented..."
);
info("Wait for source-map-error event");
const error = await onError;
is(
error,
`Source map error: Error: URL constructor: http:://example.com/ is not a valid URL.\nResource URL: undefined\nSource Map URL: missingmap.js.map`
);
});

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

@ -33,14 +33,14 @@ function hasOriginalURL(url) {
return originalURLs.has(url);
}
function resolveSourceMapURL(source) {
function _resolveSourceMapURL(source) {
let { sourceMapBaseURL, sourceMapURL } = source;
sourceMapBaseURL = sourceMapBaseURL || "";
sourceMapURL = sourceMapURL || "";
if (!sourceMapBaseURL) {
// If the source doesn't have a URL, don't resolve anything.
return { resolvedSourceMapURL: sourceMapURL, baseURL: sourceMapURL };
return { sourceMapURL, baseURL: sourceMapURL };
}
let resolvedString;
@ -63,11 +63,14 @@ function resolveSourceMapURL(source) {
baseURL = resolvedString;
}
return { resolvedSourceMapURL: resolvedString, baseURL };
return { sourceMapURL: resolvedString, baseURL };
}
async function _fetch(generatedSource, resolvedSourceMapURL, baseURL) {
let fetched = await networkRequest(resolvedSourceMapURL, {
async function _resolveAndFetch(generatedSource) {
// Fetch the sourcemap over the network and create it.
const { sourceMapURL, baseURL } = _resolveSourceMapURL(generatedSource);
let fetched = await networkRequest(sourceMapURL, {
loadFromCache: false,
// Blocking redirects on the sourceMappingUrl as its not easy to verify if the
// redirect protocol matches the supported ones.
@ -102,7 +105,7 @@ async function _fetch(generatedSource, resolvedSourceMapURL, baseURL) {
return map;
}
function fetchSourceMap(generatedSource, resolvedSourceMapURL, baseURL) {
function fetchSourceMap(generatedSource) {
const existingRequest = getSourceMap(generatedSource.id);
// If it has already been requested, return the request. Make sure
@ -121,7 +124,7 @@ function fetchSourceMap(generatedSource, resolvedSourceMapURL, baseURL) {
}
// Fire off the request, set it in the cache, and return it.
const req = _fetch(generatedSource, resolvedSourceMapURL, baseURL);
const req = _resolveAndFetch(generatedSource);
// Make sure the cached promise does not reject, because we only
// want to report the error once.
setSourceMap(
@ -131,9 +134,4 @@ function fetchSourceMap(generatedSource, resolvedSourceMapURL, baseURL) {
return req;
}
module.exports = {
fetchSourceMap,
hasOriginalURL,
clearOriginalURLs,
resolveSourceMapURL,
};
module.exports = { fetchSourceMap, hasOriginalURL, clearOriginalURLs };

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

@ -78,12 +78,22 @@ function setSourceMap(generatedId, request) {
}
const urlsById = new Map();
const sources = [];
let ignoreListUrls = [];
if (map.x_google_ignoreList?.length) {
ignoreListUrls = map.x_google_ignoreList.map(
sourceIndex => map.sources[sourceIndex]
);
}
for (const url of map.sources) {
const id = generatedToOriginalId(generatedId, url);
urlsById.set(id, url);
sources.push({ id, url });
}
return { map, urlsById };
return { map, urlsById, sources, ignoreListUrls };
})
);
}

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

@ -17,7 +17,7 @@ const {
getOriginalSourceText,
getGeneratedRangesForOriginal,
getFileGeneratedRange,
loadSourceMap,
getSourceMapIgnoreList,
clearSourceMaps,
setSourceMapForGeneratedSources,
} = require("resource://devtools/client/shared/source-map-loader/source-map.js");
@ -44,7 +44,7 @@ self.onmessage = workerHandler({
getOriginalStackFrames,
getGeneratedRangesForOriginal,
getFileGeneratedRange,
loadSourceMap,
getSourceMapIgnoreList,
setSourceMapForGeneratedSources,
clearSourceMaps,
});

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

@ -20,9 +20,6 @@ import { StyleSheetEditor } from "resource://devtools/client/styleeditor/StyleSh
const { PrefObserver } = require("resource://devtools/client/shared/prefs.js");
const KeyShortcuts = require("resource://devtools/client/shared/key-shortcuts.js");
const {
shortSource,
} = require("resource://devtools/shared/inspector/css-logic.js");
const lazy = {};
@ -565,87 +562,62 @@ export class StyleEditorUI extends EventEmitter {
#addStyleSheet(resource) {
if (!this.#seenSheets.has(resource)) {
const promise = (async () => {
// When the StyleSheet is mapped to one or many original sources,
// do not create an editor for the minified StyleSheet.
const hasValidOriginalSource = await this.#tryAddingOriginalStyleSheets(
resource
);
if (hasValidOriginalSource) {
return null;
let editor = await this.#addStyleSheetEditor(resource);
const sourceMapLoader = this.#toolbox.sourceMapLoader;
if (
!sourceMapLoader ||
!Services.prefs.getBoolPref(PREF_ORIG_SOURCES)
) {
return editor;
}
// Otherwise, if source-map failed or this is a non-source-map CSS
// create an editor for it.
return this.#addStyleSheetEditor(resource);
const {
href,
nodeHref,
resourceId: id,
sourceMapURL,
sourceMapBaseURL,
} = resource;
const sources = await sourceMapLoader.getOriginalURLs({
id,
url: href || nodeHref,
sourceMapBaseURL,
sourceMapURL,
});
// A single generated sheet might map to multiple original
// sheets, so make editors for each of them.
if (sources && sources.length) {
const parentEditorName = editor.friendlyName;
this.#removeStyleSheetEditor(editor);
editor = null;
for (const { id: originalId, url: originalURL } of sources) {
const original = new lazy.OriginalSource(
originalURL,
originalId,
sourceMapLoader
);
// set so the first sheet will be selected, even if it's a source
original.styleSheetIndex = resource.styleSheetIndex;
original.relatedStyleSheet = resource;
original.relatedEditorName = parentEditorName;
original.resourceId = resource.resourceId;
original.targetFront = resource.targetFront;
original.atRules = resource.atRules;
await this.#addStyleSheetEditor(original);
}
}
return editor;
})();
this.#seenSheets.set(resource, promise);
}
return this.#seenSheets.get(resource);
}
/**
* Check if the given StyleSheet relates to an original StyleSheet (via source maps).
* If one is found, create an editor for the original one.
*
* @param {Resource} resource
* The STYLESHEET resource which is received from resource command.
* @return Boolean
* Return true, when we found a viable related original StyleSheet.
*/
async #tryAddingOriginalStyleSheets(resource) {
// Avoid querying the SourceMap if this feature is disabled.
if (!Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
return false;
}
const sourceMapLoader = this.#toolbox.sourceMapLoader;
const {
href,
nodeHref,
resourceId: id,
sourceMapURL,
sourceMapBaseURL,
} = resource;
let sources;
try {
sources = await sourceMapLoader.getOriginalURLs({
id,
url: href || nodeHref,
sourceMapBaseURL,
sourceMapURL,
});
} catch (e) {
// Ignore any source map error, they will be logged
// via the SourceMapLoader and Toolbox into the Web Console.
return false;
}
// Return the generated CSS if the source-map failed to be parsed
// or did not generate any original source.
if (!sources || !sources.length) {
return false;
}
// A single generated sheet might map to multiple original
// sheets, so make editors for each of them.
for (const { id: originalId, url: originalURL } of sources) {
const original = new lazy.OriginalSource(
originalURL,
originalId,
sourceMapLoader
);
// set so the first sheet will be selected, even if it's a source
original.styleSheetIndex = resource.styleSheetIndex;
original.relatedStyleSheet = resource;
original.resourceId = resource.resourceId;
original.targetFront = resource.targetFront;
original.atRules = resource.atRules;
await this.#addStyleSheetEditor(original);
}
return true;
}
#removeStyleSheet(resource, editor) {
this.#seenSheets.delete(resource);
this.#removeStyleSheetEditor(editor);
@ -1301,13 +1273,8 @@ export class StyleEditorUI extends EventEmitter {
let linkedCSSSource = "";
if (editor.linkedCSSFile) {
linkedCSSSource = PathUtils.filename(editor.linkedCSSFile);
} else if (editor.styleSheet.relatedStyleSheet) {
// Compute a friendly name for the related generated source
// (relatedStyleSheet is set on original CSS to refer to the generated one)
linkedCSSSource = shortSource(editor.styleSheet.relatedStyleSheet);
try {
linkedCSSSource = decodeURI(linkedCSSSource);
} catch (e) {}
} else if (editor.styleSheet.relatedEditorName) {
linkedCSSSource = editor.styleSheet.relatedEditorName;
}
text(summary, ".stylesheet-linked-file", linkedCSSSource);
text(summary, ".stylesheet-title", editor.styleSheet.title || "");

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

@ -105,12 +105,6 @@ add_task(async function testSystemStylesheet() {
const formsToggle = formsEditor.summary.querySelector(".stylesheet-toggle");
ok(formsToggle, "enabled toggle button exists");
ok(formsToggle.disabled, "enabled toggle button is disabled");
// For some unexplained reason, this is updated asynchronously
await waitFor(
() =>
formsToggle.getAttribute("tooltiptext") ==
"System style sheets cant be disabled"
);
is(
formsToggle.getAttribute("tooltiptext"),
"System style sheets cant be disabled"

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

@ -153,43 +153,35 @@ exports.shortSource = function (sheet) {
);
}
let name = sheet.href;
// If the sheet is a data URL, return a trimmed version of it.
const dataUrl = sheet.href.trim().match(/^data:.*?,((?:.|\r|\n)*)$/);
if (dataUrl) {
name =
dataUrl[1].length > MAX_DATA_URL_LENGTH
? `${dataUrl[1].substr(0, MAX_DATA_URL_LENGTH - 1)}`
: dataUrl[1];
} else {
// We try, in turn, the filename, filePath, query string, whole thing
let url = {};
try {
url = new URL(sheet.href);
} catch (ex) {
// Some UA-provided stylesheets are not valid URLs.
}
if (url.pathname) {
const index = url.pathname.lastIndexOf("/");
if (index !== -1 && index < url.pathname.length) {
name = url.pathname.slice(index + 1);
} else {
name = url.pathname;
}
} else if (url.query) {
name = url.query;
}
return dataUrl[1].length > MAX_DATA_URL_LENGTH
? `${dataUrl[1].substr(0, MAX_DATA_URL_LENGTH - 1)}`
: dataUrl[1];
}
// We try, in turn, the filename, filePath, query string, whole thing
let url = {};
try {
name = decodeURIComponent(name);
} catch (e) {
// This may still fail if the URL contains invalid % numbers (for ex)
url = new URL(sheet.href);
} catch (ex) {
// Some UA-provided stylesheets are not valid URLs.
}
return name;
if (url.pathname) {
const index = url.pathname.lastIndexOf("/");
if (index !== -1 && index < url.pathname.length) {
return url.pathname.slice(index + 1);
}
return url.pathname;
}
if (url.query) {
return url.query;
}
return sheet.href;
};
/**