Bug 1500632 - source-map-url-service should trigger subscribers when files matching them are detected. r=tromey

Differential Revision: https://phabricator.services.mozilla.com/D9433

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Logan Smyth 2018-10-24 12:55:01 +00:00
parent 79d0d6f396
commit 5053ff0b5a
6 changed files with 246 additions and 14 deletions

View File

@ -61,7 +61,7 @@ SourceMapURLService.prototype._getLoadingPromise = function() {
this._stylesheetsFront.on("stylesheet-added", this._onNewStyleSheet);
const styleSheetsLoadingPromise =
this._stylesheetsFront.getStyleSheets().then(sheets => {
sheets.forEach(this._onNewStyleSheet);
sheets.forEach(this._registerNewStyleSheet, this);
}, () => {
// Ignore any protocol-based errors.
});
@ -71,7 +71,7 @@ SourceMapURLService.prototype._getLoadingPromise = function() {
// Ignore errors. Register the sources we got; we can't rely on
// an event to arrive if the source actor already existed.
for (const source of sources) {
this._onSourceUpdated({source});
this._registerNewSource(source);
}
}, e => {
// Also ignore any protocol-based errors.
@ -92,6 +92,7 @@ SourceMapURLService.prototype.reset = function() {
this._urls.clear();
this._subscriptions.clear();
this._idMap.clear();
this._loadingPromise = null;
};
/**
@ -114,12 +115,28 @@ SourceMapURLService.prototype.destroy = function() {
* A helper function that is called when a new source is available.
*/
SourceMapURLService.prototype._onSourceUpdated = function(sourceEvent) {
const url = this._registerNewSource(sourceEvent.source);
if (url) {
// Subscribers might have been added for this file before the
// "source-updated" event was fired.
this._dispatchSubscribersForURL(url);
}
};
/**
* A helper function that registers a new source file with the service.
*
* @param {SourceActor} source The new source's actor.
* @returns {string | undefined} A URL for the registered file,
* if registered successfully.
*/
SourceMapURLService.prototype._registerNewSource = function(source) {
// Maybe we were shut down while waiting.
if (!this._urls) {
return;
}
const { source } = sourceEvent;
const { generatedUrl, url, actor: id, sourceMapURL } = source;
// |generatedUrl| comes from the actor and is extracted from the
@ -127,6 +144,8 @@ SourceMapURLService.prototype._onSourceUpdated = function(sourceEvent) {
const seenUrl = generatedUrl || url;
this._urls.set(seenUrl, { id, url: seenUrl, sourceMapURL });
this._idMap.set(id, seenUrl);
return seenUrl;
};
/**
@ -136,6 +155,23 @@ SourceMapURLService.prototype._onSourceUpdated = function(sourceEvent) {
* The new style sheet's actor.
*/
SourceMapURLService.prototype._onNewStyleSheet = function(sheet) {
const url = this._registerNewStyleSheet(sheet);
if (url) {
// Subscribers might have been added for this file before the
// "stylesheet-added" event was fired.
this._dispatchSubscribersForURL(url);
}
};
/**
* A helper function that registers a new stylesheet with the service.
* @param {StyleSheetActor} sheet
* The new style sheet's actor.
* @returns {string | undefined} A URL for the registered file,
* if registered successfully.
*/
SourceMapURLService.prototype._registerNewStyleSheet = function(sheet) {
// Maybe we were shut down while waiting.
if (!this._urls) {
return;
@ -145,6 +181,8 @@ SourceMapURLService.prototype._onNewStyleSheet = function(sheet) {
const url = href || nodeHref;
this._urls.set(url, { id, url, sourceMapURL});
this._idMap.set(id, url);
return url;
};
/**
@ -167,17 +205,26 @@ SourceMapURLService.prototype.sourceMapChanged = function(id, newUrl) {
// The source map URL here doesn't actually matter.
this._urls.set(urlKey, { id, url: newUrl, sourceMapURL: "" });
// Walk over all the location subscribers, looking for any that
// are subscribed to a location coming from |urlKey|. Then,
// re-notify any such subscriber by clearing the stored promise
// and forcing a re-evaluation.
for (const [, subscriptionEntry] of this._subscriptions) {
if (subscriptionEntry.url === urlKey) {
// Force an update.
subscriptionEntry.promise = null;
for (const callback of subscriptionEntry.callbacks) {
this._callOneCallback(subscriptionEntry, callback);
}
this._dispatchSubscribersForURL(urlKey);
}
};
/**
* A helper function that dispatches subscribers for a specific URL.
* @param {string} urlKey
* The url to trigger subscribers for.
*/
SourceMapURLService.prototype._dispatchSubscribersForURL = function(urlKey) {
// Walk over all the location subscribers, looking for any that
// are subscribed to a location coming from |urlKey|. Then,
// re-notify any such subscriber by clearing the stored promise
// and forcing a re-evaluation.
for (const [, subscriptionEntry] of this._subscriptions) {
if (subscriptionEntry.url === urlKey) {
// Force an update.
subscriptionEntry.promise = null;
for (const callback of subscriptionEntry.callbacks) {
this._callOneCallback(subscriptionEntry, callback);
}
}
}

View File

@ -11,6 +11,8 @@ support-files =
browser_toolbox_sidebar_toolURL.xul
browser_toolbox_window_title_changes_page.html
browser_toolbox_window_title_frame_select_page.html
code_bundle_late_script.js
code_bundle_late_script.js.map
code_binary_search.coffee
code_binary_search.js
code_binary_search.map
@ -71,6 +73,7 @@ skip-if = os == 'win' || debug # Bug 1282269, 1448084
[browser_source_map-inline.js]
[browser_source_map-no-race.js]
[browser_source_map-reload.js]
[browser_source_map-late-script.js]
[browser_target_from_url.js]
[browser_target_events.js]
[browser_target_remote.js]

View File

@ -0,0 +1,51 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that you can subscribe to notifications on a source before it has loaded.
"use strict";
const PAGE_URL = `${URL_ROOT}doc_empty-tab-01.html`;
const JS_URL = URL_ROOT + "code_bundle_late_script.js";
const ORIGINAL_URL = "webpack:///code_late_script.js";
const GENERATED_LINE = 107;
const ORIGINAL_LINE = 11;
add_task(async function() {
// Start with the empty page, then navigate, so that we can properly
// listen for new sources arriving.
const toolbox = await openNewTabAndToolbox(PAGE_URL, "webconsole");
const service = toolbox.sourceMapURLService;
const scriptMapped = new Promise(resolve => {
let count = 0;
service.subscribe(
JS_URL,
GENERATED_LINE,
undefined,
(...args) => {
if (count === 0) {
resolve(args);
}
count += 1;
}
);
});
// Inject JS script
const sourceSeen = waitForSourceLoad(toolbox, JS_URL);
await createScript(JS_URL);
await sourceSeen;
// Ensure that the URL service fired an event about the location loading.
const [isSourceMapped, url, line] = await scriptMapped;
is(isSourceMapped, true, "check is mapped");
is(url, ORIGINAL_URL, "check mapped URL");
is(line, ORIGINAL_LINE, "check mapped line number");
await toolbox.destroy();
gBrowser.removeCurrentTab();
finish();
});

View File

@ -0,0 +1,116 @@
/******/ (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, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // 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 = "./code_late_script.js");
/******/ })
/************************************************************************/
/******/ ({
/***/ "./code_late_script.js":
/*!*****************************!*\
!*** ./code_late_script.js ***!
\*****************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Original source code for the inline source map test.
// The generated file was made with
// webpack --devtool source-map code_late_script.js code_bundle_late_script.js
function f() {
console.log("The first version of the script");
}
f();
/***/ })
/******/ });
//# sourceMappingURL=code_bundle_late_script.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["webpack:///webpack/bootstrap","webpack:///./code_late_script.js"],"names":[],"mappings":";AAAA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,kDAA0C,gCAAgC;AAC1E;AACA;;AAEA;AACA;AACA;AACA,gEAAwD,kBAAkB;AAC1E;AACA,yDAAiD,cAAc;AAC/D;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iDAAyC,iCAAiC;AAC1E,wHAAgH,mBAAmB,EAAE;AACrI;AACA;;AAEA;AACA;AACA;AACA,mCAA2B,0BAA0B,EAAE;AACvD,yCAAiC,eAAe;AAChD;AACA;AACA;;AAEA;AACA,8DAAsD,+DAA+D;;AAErH;AACA;;;AAGA;AACA;;;;;;;;;;;;;AClFA;AACA;;AAEA;AACA;AACA;;AAEa;;AAEb;AACA;AACA;;AAEA","file":"code_bundle_late_script.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, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\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\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = \"./code_late_script.js\");\n","/* Any copyright is dedicated to the Public Domain.\n http://creativecommons.org/publicdomain/zero/1.0/ */\n\n// Original source code for the inline source map test.\n// The generated file was made with\n// webpack --devtool source-map code_late_script.js code_bundle_late_script.js\n\n\"use strict\";\n\nfunction f() {\n console.log(\"The first version of the script\");\n}\n\nf();\n"],"sourceRoot":""}

View File

@ -0,0 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Original source code for the inline source map test.
// The generated file was made with
// webpack --devtool source-map code_late_script.js --output code_bundle_late_script.js --mode development
"use strict";
function f() {
console.log("The first version of the script");
}
f();