зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1538056 Part 2 - Use HTML file contents from parser in devtools server when possible, r=loganfsmyth.
Depends on D33178 Differential Revision: https://phabricator.services.mozilla.com/D33181 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
eb95b88a4d
Коммит
23cddcde7f
|
@ -241,6 +241,44 @@ dbg.onNewScript = function(script) {
|
|||
installPendingHandlers();
|
||||
};
|
||||
|
||||
// Listen to the content of all loaded HTML files, with similar logic to that
|
||||
// in TabSources.
|
||||
const gHtmlContent = new Map();
|
||||
|
||||
getWindow().docShell.watchedByDevtools = true;
|
||||
|
||||
Services.obs.addObserver(
|
||||
{
|
||||
observe(subject, topic, data) {
|
||||
assert(topic == "webnavigation-create");
|
||||
subject.watchedByDevtools = true;
|
||||
},
|
||||
},
|
||||
"webnavigation-create"
|
||||
);
|
||||
|
||||
Services.obs.addObserver(
|
||||
{
|
||||
observe(subject, topic, data) {
|
||||
assert(topic == "devtools-html-content");
|
||||
const { uri, offset, contents } = JSON.parse(data);
|
||||
if (gHtmlContent.has(uri)) {
|
||||
const existing = gHtmlContent.get(uri);
|
||||
if (existing.content.length == offset) {
|
||||
assert(!existing.complete);
|
||||
existing.content = existing.content + contents;
|
||||
}
|
||||
} else {
|
||||
gHtmlContent.set(uri, {
|
||||
content: contents,
|
||||
contentType: "text/html",
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
"devtools-html-content"
|
||||
);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Object Snapshots
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1517,6 +1555,9 @@ const gRequestHandlers = {
|
|||
},
|
||||
|
||||
getContent(request) {
|
||||
if (gHtmlContent.has(request.url)) {
|
||||
return gHtmlContent.get(request.url);
|
||||
}
|
||||
return RecordReplayControl.getContent(request.url);
|
||||
},
|
||||
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { Ci, Cu } = require("chrome");
|
||||
const { Cu } = require("chrome");
|
||||
const {
|
||||
setBreakpointAtEntryPoints,
|
||||
} = require("devtools/server/actors/breakpoint");
|
||||
const { ActorClassWithSpec } = require("devtools/shared/protocol");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const { assert, fetch } = DevToolsUtils;
|
||||
const { assert } = DevToolsUtils;
|
||||
const { joinURI } = require("devtools/shared/path");
|
||||
const { sourceSpec } = require("devtools/shared/specs/source");
|
||||
|
||||
|
@ -165,13 +165,6 @@ const SourceActor = ActorClassWithSpec(sourceSpec, {
|
|||
return this._extensionName;
|
||||
},
|
||||
|
||||
get isCacheEnabled() {
|
||||
if (this.threadActor._parent._getCacheDisabled) {
|
||||
return !this.threadActor._parent._getCacheDisabled();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
form: function() {
|
||||
const source = this._source;
|
||||
|
||||
|
@ -210,18 +203,6 @@ const SourceActor = ActorClassWithSpec(sourceSpec, {
|
|||
return this.dbg.findScripts(query);
|
||||
},
|
||||
|
||||
_reportLoadSourceError: function(error) {
|
||||
try {
|
||||
DevToolsUtils.reportException("SourceActor", error);
|
||||
|
||||
JSON.stringify(this.form(), null, 4)
|
||||
.split(/\n/g)
|
||||
.forEach(line => console.error("\t", line));
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
},
|
||||
|
||||
_getSourceText: async function() {
|
||||
const toResolvedContent = t => ({
|
||||
content: t,
|
||||
|
@ -263,53 +244,16 @@ const SourceActor = ActorClassWithSpec(sourceSpec, {
|
|||
return toResolvedContent(this._source.text);
|
||||
}
|
||||
|
||||
// Only load the HTML page source from cache (which exists when
|
||||
// there are inline sources). Otherwise, we can't trust the
|
||||
// cache because we are most likely here because we are
|
||||
// fetching the original text for sourcemapped code, and the
|
||||
// page hasn't requested it before (if it has, it was a
|
||||
// previous debugging session).
|
||||
// Additionally, we should only try the cache if it is currently enabled
|
||||
// for the document. Without this check, the cache may return stale data
|
||||
// that doesn't match the document shown in the browser.
|
||||
const loadFromCache = this.isInlineSource && this.isCacheEnabled;
|
||||
|
||||
// Fetch the sources with the same principal as the original document
|
||||
const win = this.threadActor._parent.window;
|
||||
let principal, cacheKey;
|
||||
// On xpcshell, we don't have a window but a Sandbox
|
||||
if (!isWorker && win instanceof Ci.nsIDOMWindow) {
|
||||
const docShell = win.docShell;
|
||||
const channel = docShell.currentDocumentChannel;
|
||||
principal = channel.loadInfo.loadingPrincipal;
|
||||
|
||||
// Retrieve the cacheKey in order to load POST requests from cache
|
||||
// Note that chrome:// URLs don't support this interface.
|
||||
if (
|
||||
loadFromCache &&
|
||||
docShell.currentDocumentChannel instanceof Ci.nsICacheInfoChannel
|
||||
) {
|
||||
cacheKey = docShell.currentDocumentChannel.cacheKey;
|
||||
}
|
||||
}
|
||||
|
||||
const sourceFetched = fetch(this.url, {
|
||||
principal,
|
||||
cacheKey,
|
||||
loadFromCache,
|
||||
});
|
||||
const result = await this.sources.htmlFileContents(
|
||||
this.url,
|
||||
/* partial */ false,
|
||||
/* canUseCache */ this.isInlineSource
|
||||
);
|
||||
|
||||
// Record the contentType we just learned during fetching
|
||||
return sourceFetched.then(
|
||||
result => {
|
||||
this._contentType = result.contentType;
|
||||
return result;
|
||||
},
|
||||
error => {
|
||||
this._reportLoadSourceError(error);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
this._contentType = result.contentType;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
getBreakableLines() {
|
||||
|
|
|
@ -905,7 +905,11 @@ const browsingContextTargetPrototype = {
|
|||
_destroyThreadActor() {
|
||||
this.threadActor.exit();
|
||||
this.threadActor = null;
|
||||
this._sources = null;
|
||||
|
||||
if (this._sources) {
|
||||
this._sources.destroy();
|
||||
this._sources = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -182,6 +182,11 @@ const ContentProcessTargetActor = ActorClassWithSpec(contentProcessTargetSpec, {
|
|||
if (this._workerList) {
|
||||
this._workerList.onListChanged = null;
|
||||
}
|
||||
|
||||
if (this._sources) {
|
||||
this._sources.destroy();
|
||||
this._sources = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { Ci } = require("chrome");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const { assert } = DevToolsUtils;
|
||||
const { assert, fetch } = DevToolsUtils;
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const { SourceLocation } = require("devtools/server/actors/common");
|
||||
const Services = require("Services");
|
||||
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
|
@ -23,8 +25,8 @@ loader.lazyRequireGetter(
|
|||
);
|
||||
|
||||
/**
|
||||
* Manages the sources for a thread. Handles source maps, locations in the
|
||||
* sources, etc for ThreadActors.
|
||||
* Manages the sources for a thread. Handles HTML file contents, locations in
|
||||
* the sources, etc for ThreadActors.
|
||||
*/
|
||||
function TabSources(threadActor, allowSourceFn = () => true) {
|
||||
EventEmitter.decorate(this);
|
||||
|
@ -41,6 +43,16 @@ function TabSources(threadActor, allowSourceFn = () => true) {
|
|||
// Debugger.Source -> SourceActor
|
||||
this._sourceActors = new Map();
|
||||
|
||||
// URL -> content
|
||||
//
|
||||
// Any possibly incomplete content that has been loaded for each URL.
|
||||
this._htmlContents = new Map();
|
||||
|
||||
// URL -> Promise[]
|
||||
//
|
||||
// Any promises waiting on a URL to be completely loaded.
|
||||
this._htmlWaiters = new Map();
|
||||
|
||||
// Debugger.Source.id -> Debugger.Source
|
||||
//
|
||||
// The IDs associated with ScriptSources and available via DebuggerSource.id
|
||||
|
@ -49,6 +61,10 @@ function TabSources(threadActor, allowSourceFn = () => true) {
|
|||
// has not been GC'ed and the actor has been created. This is lazily populated
|
||||
// the first time it is needed.
|
||||
this._sourcesByInternalSourceId = null;
|
||||
|
||||
if (!isWorker) {
|
||||
Services.obs.addObserver(this, "devtools-html-content");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,6 +75,12 @@ function TabSources(threadActor, allowSourceFn = () => true) {
|
|||
const MINIFIED_SOURCE_REGEXP = /\bmin\.js$/;
|
||||
|
||||
TabSources.prototype = {
|
||||
destroy() {
|
||||
if (!isWorker) {
|
||||
Services.obs.removeObserver(this, "devtools-html-content");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update preferences and clear out existing sources
|
||||
*/
|
||||
|
@ -80,6 +102,8 @@ TabSources.prototype = {
|
|||
*/
|
||||
reset: function() {
|
||||
this._sourceActors = new Map();
|
||||
this._htmlContents = new Map();
|
||||
this._htmlWaiters = new Map();
|
||||
this._sourcesByInternalSourceId = null;
|
||||
},
|
||||
|
||||
|
@ -449,6 +473,130 @@ TabSources.prototype = {
|
|||
iter: function() {
|
||||
return [...this._sourceActors.values()];
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener for new HTML content.
|
||||
*/
|
||||
observe(subject, topic, data) {
|
||||
if (topic == "devtools-html-content") {
|
||||
const { parserID, uri, contents, complete } = JSON.parse(data);
|
||||
if (this._htmlContents.has(uri)) {
|
||||
const existing = this._htmlContents.get(uri);
|
||||
if (existing.parserID == parserID) {
|
||||
assert(!existing.complete);
|
||||
existing.content = existing.content + contents;
|
||||
existing.complete = complete;
|
||||
|
||||
// After the HTML has finished loading, resolve any promises
|
||||
// waiting for the complete file contents. Waits will only
|
||||
// occur when the URL was ever partially loaded.
|
||||
if (complete) {
|
||||
const waiters = this._htmlWaiters.get(uri);
|
||||
if (waiters) {
|
||||
for (const waiter of waiters) {
|
||||
waiter();
|
||||
}
|
||||
this._htmlWaiters.delete(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._htmlContents.set(uri, {
|
||||
content: contents,
|
||||
complete,
|
||||
contentType: "text/html",
|
||||
parserID,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the contents of the HTML file at a URL, fetching it if necessary.
|
||||
* If partial is set and any content for the URL has been received,
|
||||
* that partial content is returned synchronously.
|
||||
*/
|
||||
htmlFileContents(url, partial, canUseCache) {
|
||||
if (this._htmlContents.has(url)) {
|
||||
const data = this._htmlContents.get(url);
|
||||
if (!partial && !data.complete) {
|
||||
return new Promise(resolve => {
|
||||
if (!this._htmlWaiters.has(url)) {
|
||||
this._htmlWaiters.set(url, []);
|
||||
}
|
||||
this._htmlWaiters.get(url).push(resolve);
|
||||
}).then(() => {
|
||||
assert(data.complete);
|
||||
return {
|
||||
content: data.content,
|
||||
contentType: data.contentType,
|
||||
};
|
||||
});
|
||||
}
|
||||
return {
|
||||
content: data.content,
|
||||
contentType: data.contentType,
|
||||
};
|
||||
}
|
||||
|
||||
return this._fetchHtmlFileContents(url, partial, canUseCache);
|
||||
},
|
||||
|
||||
_fetchHtmlFileContents: async function(url, partial, canUseCache) {
|
||||
// Only try the cache if it is currently enabled for the document.
|
||||
// Without this check, the cache may return stale data that doesn't match
|
||||
// the document shown in the browser.
|
||||
let loadFromCache = canUseCache;
|
||||
if (canUseCache && this._thread._parent._getCacheDisabled) {
|
||||
loadFromCache = !this._thread._parent._getCacheDisabled();
|
||||
}
|
||||
|
||||
// Fetch the sources with the same principal as the original document
|
||||
const win = this._thread._parent.window;
|
||||
let principal, cacheKey;
|
||||
// On xpcshell, we don't have a window but a Sandbox
|
||||
if (!isWorker && win instanceof Ci.nsIDOMWindow) {
|
||||
const docShell = win.docShell;
|
||||
const channel = docShell.currentDocumentChannel;
|
||||
principal = channel.loadInfo.loadingPrincipal;
|
||||
|
||||
// Retrieve the cacheKey in order to load POST requests from cache
|
||||
// Note that chrome:// URLs don't support this interface.
|
||||
if (
|
||||
loadFromCache &&
|
||||
docShell.currentDocumentChannel instanceof Ci.nsICacheInfoChannel
|
||||
) {
|
||||
cacheKey = docShell.currentDocumentChannel.cacheKey;
|
||||
}
|
||||
}
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await fetch(url, {
|
||||
principal,
|
||||
cacheKey,
|
||||
loadFromCache,
|
||||
});
|
||||
} catch (error) {
|
||||
this._reportLoadSourceError(error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
this._htmlContents.set(url, result);
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
_reportLoadSourceError: function(error) {
|
||||
try {
|
||||
DevToolsUtils.reportException("SourceActor", error);
|
||||
|
||||
const lines = JSON.stringify(this.form(), null, 4).split(/\n/g);
|
||||
lines.forEach(line => console.error("\t", line));
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
Загрузка…
Ссылка в новой задаче