Bug 1771113 - [devtools] Notify about removed stylesheet resources. r=devtools-reviewers,ochameau.

Differential Revision: https://phabricator.services.mozilla.com/D185790
This commit is contained in:
Nicolas Chevobbe 2023-08-10 14:36:04 +00:00
parent f086602a6f
commit a825b29421
3 changed files with 149 additions and 7 deletions

View File

@ -19,6 +19,7 @@ class StyleSheetWatcher {
this._onApplicableStylesheetAdded =
this._onApplicableStylesheetAdded.bind(this);
this._onStylesheetUpdated = this._onStylesheetUpdated.bind(this);
this._onStylesheetRemoved = this._onStylesheetRemoved.bind(this);
}
/**
@ -31,10 +32,11 @@ class StyleSheetWatcher {
* - onAvailable: mandatory function
* This will be called for each resource.
*/
async watch(targetActor, { onAvailable, onUpdated }) {
async watch(targetActor, { onAvailable, onUpdated, onDestroyed }) {
this._targetActor = targetActor;
this._onAvailable = onAvailable;
this._onUpdated = onUpdated;
this._onDestroyed = onDestroyed;
this._styleSheetsManager = targetActor.getStyleSheetsManager();
@ -47,6 +49,10 @@ class StyleSheetWatcher {
"stylesheet-updated",
this._onStylesheetUpdated
);
this._styleSheetsManager.on(
"applicable-stylesheet-removed",
this._onStylesheetRemoved
);
// startWatching will emit applicable-stylesheet-added for already existing stylesheet
await this._styleSheetsManager.startWatching();
@ -60,6 +66,10 @@ class StyleSheetWatcher {
this._notifyResourceUpdated(resourceId, updateKind, updates);
}
_onStylesheetRemoved({ resourceId }) {
return this._notifyResourcesDestroyed(resourceId);
}
async _toResource(
styleSheet,
{ isCreatedByDevTools = false, fileName = null, resourceId } = {}
@ -124,6 +134,15 @@ class StyleSheetWatcher {
]);
}
_notifyResourcesDestroyed(resourceId) {
this._onDestroyed([
{
resourceType: STYLESHEET,
resourceId,
},
]);
}
destroy() {
this._styleSheetsManager.off(
"applicable-stylesheet-added",
@ -133,6 +152,10 @@ class StyleSheetWatcher {
"stylesheet-updated",
this._onStylesheetUpdated
);
this._styleSheetsManager.off(
"applicable-stylesheet-removed",
this._onStylesheetRemoved
);
}
}

View File

@ -90,6 +90,7 @@ class StyleSheetsManager extends EventEmitter {
this._targetActor = targetActor;
this._onApplicableStateChanged = this._onApplicableStateChanged.bind(this);
this._onStylesheetRemoved = this._onStylesheetRemoved.bind(this);
this._onTargetActorWindowReady = this._onTargetActorWindowReady.bind(this);
}
@ -112,6 +113,11 @@ class StyleSheetsManager extends EventEmitter {
this._onApplicableStateChanged,
true
);
this._targetActor.chromeEventHandler.addEventListener(
"StyleSheetRemoved",
this._onStylesheetRemoved,
true
);
this._watchStyleSheetChangeEvents();
this._targetActor.on("window-ready", this._onTargetActorWindowReady);
@ -145,7 +151,7 @@ class StyleSheetsManager extends EventEmitter {
_watchStyleSheetChangeEventsForWindow(window) {
// We have to set this flag in order to get the
// StyleSheetApplicableStateChanged events. See Document.webidl.
// StyleSheetApplicableStateChanged and StyleSheetRemoved events. See Document.webidl.
window.document.styleSheetChangeEventsEnabled = true;
}
@ -758,10 +764,11 @@ class StyleSheetsManager extends EventEmitter {
* When appending <link>, <style> or changing `disabled` attribute to false,
* `applicable` is passed as true. The other hand, when changing `disabled`
* to true, this will be false.
* NOTE: For now, StyleSheetApplicableStateChanged will not be called when removing the
* link and style element.
*
* @param {StyleSheetApplicableStateChanged}
* NOTE: StyleSheetApplicableStateChanged is _not_ called when removing the <link>/<style>,
* but a StyleSheetRemovedEvent is emitted in such case (see _onStyleSheetRemoved)
*
* @param {StyleSheetApplicableStateChangedEvent}
* The triggering event.
*/
_onApplicableStateChanged({ applicable, stylesheet: styleSheet }) {
@ -779,6 +786,16 @@ class StyleSheetsManager extends EventEmitter {
}
}
/**
* Event handler that is called when a style sheet is removed.
*
* @param {StyleSheetRemovedEvent}
* The triggering event.
*/
_onStylesheetRemoved(event) {
this._unregisterStyleSheet(event.stylesheet);
}
/**
* If the stylesheet isn't registered yet, this function will generate an associated
* resourceId and will emit an "applicable-stylesheet-added" event.
@ -820,6 +837,25 @@ class StyleSheetsManager extends EventEmitter {
return resourceId;
}
/**
* If the stylesheet is registered, this function will emit an "applicable-stylesheet-removed" event
* with the stylesheet resourceId.
*
* @param {StyleSheet} styleSheet
*/
_unregisterStyleSheet(styleSheet) {
const existingResourceId = this._findStyleSheetResourceId(styleSheet);
if (!existingResourceId) {
return;
}
this._styleSheetMap.delete(existingResourceId);
this._styleSheetCreationData?.delete(styleSheet);
this.emit("applicable-stylesheet-removed", {
resourceId: existingResourceId,
});
}
/**
* Returns true if the passed styleSheet should be handled.
*
@ -860,6 +896,11 @@ class StyleSheetsManager extends EventEmitter {
this._onApplicableStateChanged,
true
);
this._targetActor.chromeEventHandler.removeEventListener(
"StyleSheetRemoved",
this._onStylesheetRemoved,
true
);
this._unwatchStyleSheetChangeEvents();
} catch (e) {
console.error(

View File

@ -120,7 +120,7 @@ const ADDITIONAL_FROM_ACTOR_RESOURCE = {
};
add_task(async function () {
await testResourceAvailableFeature();
await testResourceAvailableDestroyedFeature();
await testResourceUpdateFeature();
await testNestedResourceUpdateFeature();
});
@ -137,7 +137,7 @@ function pushAvailableResource(availableResources) {
};
}
async function testResourceAvailableFeature() {
async function testResourceAvailableDestroyedFeature() {
info("Check resource available feature of the ResourceCommand");
const tab = await addTab(STYLE_TEST_URL);
@ -154,8 +154,10 @@ async function testResourceAvailableFeature() {
info("Check whether ResourceCommand gets existing stylesheet");
const availableResources = [];
const destroyedResources = [];
await resourceCommand.watchResources([resourceCommand.TYPES.STYLESHEET], {
onAvailable: pushAvailableResource(availableResources),
onDestroyed: resources => destroyedResources.push(...resources),
});
is(
@ -186,6 +188,7 @@ async function testResourceAvailableFeature() {
text => {
const document = content.document;
const stylesheet = document.createElement("style");
stylesheet.id = "inline-from-test";
stylesheet.textContent = text;
document.body.appendChild(stylesheet);
}
@ -233,6 +236,72 @@ async function testResourceAvailableFeature() {
ADDITIONAL_FROM_ACTOR_RESOURCE
);
info("Check resource destroyed feature of the ResourceCommand");
is(destroyedResources.length, 0, "There was no removed stylesheets yet");
info("Remove inline stylesheet added in the test");
await ContentTask.spawn(tab.linkedBrowser, null, () => {
content.document.querySelector("#inline-from-test").remove();
});
await waitUntil(() => destroyedResources.length === 1);
assertDestroyed(destroyedResources[0], {
resourceId: availableResources.at(-3).resourceId,
});
info("Remove existing top-level inline stylesheet");
await ContentTask.spawn(tab.linkedBrowser, null, () => {
content.document.querySelector("style").remove();
});
await waitUntil(() => destroyedResources.length === 2);
assertDestroyed(destroyedResources[1], {
resourceId: availableResources.find(
resource =>
findMatchingExpectedResource(resource) === EXISTING_RESOURCES[0]
).resourceId,
});
info("Remove existing top-level <link> stylesheet");
await ContentTask.spawn(tab.linkedBrowser, null, () => {
content.document.querySelector("link").remove();
});
await waitUntil(() => destroyedResources.length === 3);
assertDestroyed(destroyedResources[2], {
resourceId: availableResources.find(
resource =>
findMatchingExpectedResource(resource) === EXISTING_RESOURCES[1]
).resourceId,
});
info("Remove existing iframe inline stylesheet");
const iframeBrowsingContext = await SpecialPowers.spawn(
tab.linkedBrowser,
[],
() => content.document.querySelector("iframe").browsingContext
);
await SpecialPowers.spawn(iframeBrowsingContext, [], () => {
content.document.querySelector("style").remove();
});
await waitUntil(() => destroyedResources.length === 4);
assertDestroyed(destroyedResources[3], {
resourceId: availableResources.find(
resource =>
findMatchingExpectedResource(resource) === EXISTING_RESOURCES[3]
).resourceId,
});
info("Remove existing iframe <link> stylesheet");
await SpecialPowers.spawn(iframeBrowsingContext, [], () => {
content.document.querySelector("link").remove();
});
await waitUntil(() => destroyedResources.length === 5);
assertDestroyed(destroyedResources[4], {
resourceId: availableResources.find(
resource =>
findMatchingExpectedResource(resource) === EXISTING_RESOURCES[4]
).resourceId,
});
targetCommand.destroy();
await client.close();
}
@ -634,6 +703,15 @@ function assertUpdate(update, expected) {
}
}
function assertDestroyed(resource, expected) {
is(
resource.resourceType,
ResourceCommand.TYPES.STYLESHEET,
"Resource type is correct"
);
is(resource.resourceId, expected.resourceId, "resourceId is correct");
}
function getResourceTimingCount(tab) {
return ContentTask.spawn(tab.linkedBrowser, [], () => {
return content.performance.getEntriesByType("resource").length;