Bug 1752535 - [devtools] Extend the coverage of breakable lines in the source editor. r=bomsy

This patch introduce a new test helper to more easily test a page that reloads with an updated content.
This especially take care of source map support.

Differential Revision: https://phabricator.services.mozilla.com/D137162
This commit is contained in:
Alexandre Poirot 2022-01-30 09:04:27 +00:00
Родитель 433caa9ce3
Коммит e75e22366c
17 изменённых файлов: 466 добавлений и 0 удалений

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

@ -25,6 +25,7 @@ prefs =
[browser_dbg-asyncstacks.js]
[browser_dbg-audiocontext.js]
[browser_dbg-async-stepping.js]
[browser_dbg-breakable-lines.js]
[browser_dbg-sourcemapped-breakpoint-console.js]
skip-if = (os == "win" && ccov) # Bug 1453549
[browser_dbg-xhr-breakpoints.js]

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

@ -0,0 +1,84 @@
/* 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/>. */
const testServer = createVersionizedHttpTestServer("sourcemaps-reload2");
const TEST_URL = testServer.urlFor("index.html");
// Assert the behavior of the gutter that grays out non-breakable lines
add_task(async function testBreakableLinesOverReloads() {
const dbg = await initDebuggerWithAbsoluteURL(TEST_URL, "script.js");
info("Assert breakable lines of the first html page load");
await assertBreakableLines(dbg, "index.html", 20, [[16], [17]]);
info("Assert breakable lines of the first original source file, original.js");
await waitForSource(dbg, "original.js");
// The length of original.js is longer than the test file
// because the sourcemap replaces the content or the original file
// and appends a few lines with a "WEBPACK FOOTER" comment
// All the appended line are empty lines or comments, so none of them are breakable.
await assertBreakableLines(dbg, "original.js", 13, [[1,3], [5,8]]);
info("Assert breakable lines of the simple first load of script.js");
await assertBreakableLines(dbg, "script.js", 3, [[1], [3]]);
info("Reload the page and assert that breakable lines get updated");
testServer.switchToNextVersion();
await reload(dbg);
info("Wait for script.js to be ready");
await waitForSelectedSource(dbg, "script.js");
info("Assert breakable lines of the more complex second load of script.js");
await assertBreakableLines(dbg, "script.js", 23, [[2], [13,23]]);
info("Assert breakable lines of the second html page load");
await assertBreakableLines(dbg, "index.html", 21, [[15], [17]]);
info("Assert breakable lines of the second orignal file");
// See first assertion about original.js,
// the size of original.js doesn't match the size of the test file
await assertBreakableLines(dbg, "original.js", 18, [[1,3], [8,11], [13]]);
});
function assertLineIsBreakable(dbg, file, line, shouldBeBreakable) {
const lineInfo = getCM(dbg).lineInfo(line - 1);
// When a line is not breakable, the "empty-line" class is added
// and the line is greyed out
if (shouldBeBreakable) {
ok(
!lineInfo.wrapClass?.includes("empty-line"),
`${file}:${line} should be breakable`);
} else {
ok(
lineInfo?.wrapClass?.includes("empty-line"),
`${file}:${line} should NOT be breakable`);
}
}
function shouldLineBeBreakable(breakableLines, line) {
for(const range of breakableLines) {
if (range.length == 2) {
if (line >= range[0] && line <= range[1]) {
return true;
}
} else if (range.length == 1) {
if (line == range[0]) {
return true;
}
} else {
ok(false, "Ranges of breakable lines should only be made of arrays, with one item for single lines, or two items for subsequent breakable lines");
}
}
return false;
}
async function assertBreakableLines(dbg, file, numberOfLines, breakableLines) {
await selectSource(dbg, file);
const editorLines = dbg.win.document.querySelectorAll(".CodeMirror-lines .CodeMirror-code > div");
is(editorLines.length, numberOfLines, `We show the expected number of lines in CodeMirror for ${file}`);
for(let line = 1; line <= numberOfLines; line++) {
assertLineIsBreakable(dbg, file, line, shouldLineBeBreakable(breakableLines, line));
}
}

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

@ -0,0 +1,5 @@
### Steps to Rebuild
1. make changes to files within v1, v2, v3 folders
2. run `yarn` to install webpack & babel
3. run `yarn run pack`

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

@ -0,0 +1,19 @@
{
"name": "sourcemaps-reload",
"version": "1.0.0",
"description": "Rebuild assets for sourcemaps-reload test, see README.md in the same folder",
"scripts": {
"pack": "cd v1 && webpack && cd ../v2 && webpack && cd ../v3 && webpack",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "MPL-2.0",
"dependencies": {},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-es2015": "^6.24.1",
"webpack": "^3.7.1"
}
}

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

@ -0,0 +1,88 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(1);
/***/ }),
/* 1 */
/***/ (function(module, exports) {
window.bar = function bar() {
return new Promise(resolve => setTimeout(resolve, 100));
};
window.foo = async function foo() {
await bar();
console.log("YO");
};
/***/ })
/******/ ]);
//# sourceMappingURL=bundle.js.map

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

@ -0,0 +1 @@
{"version":3,"sources":["webpack:///webpack/bootstrap 6fda1f7ea9ecbc1a2d5b","webpack:///./original.js"],"names":["window","bar","Promise","resolve","setTimeout","foo","console","log"],"mappings":";QAAA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;;;QAGA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA,KAAK;QACL;QACA;;QAEA;QACA;QACA;QACA,2BAA2B,0BAA0B,EAAE;QACvD,iCAAiC,eAAe;QAChD;QACA;QACA;;QAEA;QACA,sDAAsD,+DAA+D;;QAErH;QACA;;QAEA;QACA;;;;;;;;;;;;;;AC7DAA,OAAOC,GAAP,GAAa,SAASA,GAAT,GAAe;AAC1B,SAAO,IAAIC,OAAJ,CAAYC,WAAWC,WAAWD,OAAX,EAAoB,GAApB,CAAvB,CAAP;AACD,CAFD;;AAIAH,OAAOK,GAAP,GAAa,eAAeA,GAAf,GAAqB;AAChC,QAAMJ,KAAN;AACAK,UAAQC,GAAR,CAAY,IAAZ;AACD,CAHD,C","file":"bundle.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 0);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 6fda1f7ea9ecbc1a2d5b","window.bar = function bar() {\n return new Promise(resolve => setTimeout(resolve, 100))\n}\n\nwindow.foo = async function foo() {\n await bar();\n console.log(\"YO\")\n}\n\n\n\n// WEBPACK FOOTER //\n// ./original.js"],"sourceRoot":""}

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

@ -0,0 +1,20 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<script src="script.js"></script>
<script src="bundle.js"></script>
<head>
<meta charset="utf-8"/>
<title>Empty test page 1</title>
</head>
<body>
<script>
// Comment
console.log('breakable-line');
</script>
</body>
</html>

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

@ -0,0 +1,8 @@
window.bar = function bar() {
return new Promise(resolve => setTimeout(resolve, 100))
}
window.foo = async function foo() {
await bar();
console.log("YO")
}

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

@ -0,0 +1,2 @@
console.log("only one breakable line");
// And one non-breakable line

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

@ -0,0 +1,22 @@
const path = require("path");
const webpack = require("webpack");
module.exports = {
entry: [path.join(__dirname, "original.js")],
output: {
path: __dirname,
filename: "bundle.js"
},
devtool: "sourcemap",
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
]
},
plugins: [
]
};

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

@ -0,0 +1,90 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(1);
/***/ }),
/* 1 */
/***/ (function(module, exports) {
window.bar = function bar() {
return new Promise(resolve => setTimeout(resolve, 100));
};
window.foo = async function foo() {
await bar();
console.log("YO");
};
console.log("HEY");
/***/ })
/******/ ]);
//# sourceMappingURL=bundle.js.map

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

@ -0,0 +1 @@
{"version":3,"sources":["webpack:///webpack/bootstrap 7143b86b0b4c7bdf7cb8","webpack:///./original.js"],"names":["window","bar","Promise","resolve","setTimeout","foo","console","log"],"mappings":";QAAA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;;;QAGA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA,KAAK;QACL;QACA;;QAEA;QACA;QACA;QACA,2BAA2B,0BAA0B,EAAE;QACvD,iCAAiC,eAAe;QAChD;QACA;QACA;;QAEA;QACA,sDAAsD,+DAA+D;;QAErH;QACA;;QAEA;QACA;;;;;;;;;;;;;;AC7DAA,OAAOC,GAAP,GAAa,SAASA,GAAT,GAAe;AAC1B,SAAO,IAAIC,OAAJ,CAAYC,WAAWC,WAAWD,OAAX,EAAoB,GAApB,CAAvB,CAAP;AACD,CAFD;;AAOAH,OAAOK,GAAP,GAAa,eAAeA,GAAf,GAAqB;AAChC,QAAMJ,KAAN;AACAK,UAAQC,GAAR,CAAY,IAAZ;AACD,CAHD;;AAKAD,QAAQC,GAAR,CAAY,KAAZ,E","file":"bundle.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 0);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 7143b86b0b4c7bdf7cb8","window.bar = function bar() {\n return new Promise(resolve => setTimeout(resolve, 100))\n}\n\n\n\n\nwindow.foo = async function foo() {\n await bar();\n console.log(\"YO\")\n}\n\nconsole.log(\"HEY\")\n\n\n\n// WEBPACK FOOTER //\n// ./original.js"],"sourceRoot":""}

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

@ -0,0 +1,20 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<script src="script.js"></script>
<script src="bundle.js"></script>
<head>
<meta charset="utf-8"/>
<title>Empty test page 2</title>
</head>
<body>
<script>
console.log('breakable-line');
// Comment
</script>
</body>
</html>

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

@ -0,0 +1,13 @@
window.bar = function bar() {
return new Promise(resolve => setTimeout(resolve, 100))
}
window.foo = async function foo() {
await bar();
console.log("YO")
}
console.log("HEY")

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

@ -0,0 +1,22 @@
// Single line comment <<=== this is line 1
console.log("body instruction");
/* Multi
* line
* comment
*/
function bodyFunction() {
// Inline comment
/* Inline
* multiline comment
*/
function inlineFunction() {
console.log("nested instruction");
}
const inlineCount = 1;
let inlineLet = 2;
var inlineVar = 3;
console.log("inline instruction");
}
const bodyCount = 1;
let bodyLet = 2;
var bodyVar = 3;

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

@ -0,0 +1,22 @@
const path = require("path");
const webpack = require("webpack");
module.exports = {
entry: [path.join(__dirname, "original.js")],
output: {
path: __dirname,
filename: "bundle.js"
},
devtool: "sourcemap",
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
]
},
plugins: [
]
};

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

@ -2190,6 +2190,54 @@ async function setLogPoint(dbg, index, value) {
await onBreakpointSet;
}
/**
* Instantiate a HTTP Server that serves files from a given test folder.
* The test folder should be made of multiple sub folder named: v1, v2, v3,...
* We will serve the content from one of these sub folder
* and switch to the next one, each time `httpServer.switchToNextVersion()`
* is called.
*
* @return Object Test server with two functions:
* - urlFor(path)
* Returns the absolute url for a given file.
* - switchToNextVersion()
* Start serving files from the next available sub folder.
*/
function createVersionizedHttpTestServer(testFolderName) {
const httpServer = createTestHTTPServer();
let currentVersion = 1;
httpServer.registerPrefixHandler("/", async (request, response) => {
response.processAsync();
response.setStatusLine(request.httpVersion, 200, "OK");
if (request.path.endsWith(".js")) {
response.setHeader("Content-Type", "application/javascript");
} else if (request.path.endsWith(".js.map")) {
response.setHeader("Content-Type", "application/json");
}
if (request.path == "/" || request.path == "/index.html") {
response.setHeader("Content-Type", "text/html");
}
const url = URL_ROOT + `examples/${testFolderName}/v${currentVersion}${request.path}`;
info("[test-http-server] serving: " + url);
const content = await fetch(url);
const text = await content.text();
response.write(text);
response.finish();
});
return {
switchToNextVersion() {
currentVersion++;
},
urlFor(path) {
const port = httpServer.identity.primaryPort;
return `http://localhost:${port}/${path}`;
},
};
}
// This module is also loaded for Browser Toolbox tests, within the browser toolbox process
// which doesn't contain mochitests resource://testing-common URL.
// This isn't important to allow rejections in the context of the browser toolbox tests.