mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 22:32:46 +00:00
Bug 1771113 - Add chromeOnly StyleSheetRemoved
event, emitted when a stylesheet is removed. r=emilio.
This new event will be consumed by DevTools to update style information. As for `StyleSheetApplicableStateChanged`, this event is emitted from the document. It's only enabled when `document.styleSheetChangeEventsEnabled` is set to true. The existing test around the `StyleSheetApplicableStateChanged` is renamed and modified to assert both events when stylesheets are added, modified and removed. Differential Revision: https://phabricator.services.mozilla.com/D147271
This commit is contained in:
parent
1dbe69a36a
commit
f086602a6f
@ -66,7 +66,7 @@ dom/xml/test/old/toc/book.css
|
||||
dom/xml/test/old/toc/toc.css
|
||||
|
||||
# Tests we don't want to modify at this point:
|
||||
layout/base/tests/bug839103.css
|
||||
layout/base/tests/stylesheet_change_events.css
|
||||
layout/inspector/tests/bug856317.css
|
||||
layout/inspector/tests/chrome/test_bug467669.css
|
||||
layout/inspector/tests/chrome/test_bug708874.css
|
||||
|
@ -225,6 +225,8 @@
|
||||
#include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h"
|
||||
#include "mozilla/dom/StyleSheetApplicableStateChangeEventBinding.h"
|
||||
#include "mozilla/dom/StyleSheetList.h"
|
||||
#include "mozilla/dom/StyleSheetRemovedEvent.h"
|
||||
#include "mozilla/dom/StyleSheetRemovedEventBinding.h"
|
||||
#include "mozilla/dom/TimeoutManager.h"
|
||||
#include "mozilla/dom/ToggleEvent.h"
|
||||
#include "mozilla/dom/Touch.h"
|
||||
@ -7450,6 +7452,26 @@ void Document::PostStyleSheetApplicableStateChangeEvent(StyleSheet& aSheet) {
|
||||
asyncDispatcher->PostDOMEvent();
|
||||
}
|
||||
|
||||
void Document::PostStyleSheetRemovedEvent(StyleSheet& aSheet) {
|
||||
if (!StyleSheetChangeEventsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
StyleSheetRemovedEventInit init;
|
||||
init.mBubbles = true;
|
||||
init.mCancelable = false;
|
||||
init.mStylesheet = &aSheet;
|
||||
|
||||
RefPtr<StyleSheetRemovedEvent> event =
|
||||
StyleSheetRemovedEvent::Constructor(this, u"StyleSheetRemoved"_ns, init);
|
||||
event->SetTrusted(true);
|
||||
event->SetTarget(this);
|
||||
RefPtr<AsyncEventDispatcher> asyncDispatcher =
|
||||
new AsyncEventDispatcher(this, event);
|
||||
asyncDispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
|
||||
asyncDispatcher->PostDOMEvent();
|
||||
}
|
||||
|
||||
static int32_t FindSheet(const nsTArray<RefPtr<StyleSheet>>& aSheets,
|
||||
nsIURI* aSheetURI) {
|
||||
for (int32_t i = aSheets.Length() - 1; i >= 0; i--) {
|
||||
|
@ -1687,8 +1687,8 @@ class Document : public nsINode,
|
||||
* and that observers should be notified and style sets updated
|
||||
*/
|
||||
void StyleSheetApplicableStateChanged(StyleSheet&);
|
||||
|
||||
void PostStyleSheetApplicableStateChangeEvent(StyleSheet&);
|
||||
void PostStyleSheetRemovedEvent(StyleSheet&);
|
||||
|
||||
enum additionalSheetType {
|
||||
eAgentSheet,
|
||||
|
@ -93,6 +93,7 @@ void DocumentOrShadowRoot::RemoveStyleSheet(StyleSheet& aSheet) {
|
||||
mStyleSheets.RemoveElementAt(index);
|
||||
RemoveSheetFromStylesIfApplicable(*sheet);
|
||||
sheet->ClearAssociatedDocumentOrShadowRoot();
|
||||
AsNode().OwnerDoc()->PostStyleSheetRemovedEvent(aSheet);
|
||||
}
|
||||
|
||||
void DocumentOrShadowRoot::RemoveSheetFromStylesIfApplicable(
|
||||
|
17
dom/chrome-webidl/StyleSheetRemovedEvent.webidl
Normal file
17
dom/chrome-webidl/StyleSheetRemovedEvent.webidl
Normal file
@ -0,0 +1,17 @@
|
||||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/.
|
||||
*/
|
||||
|
||||
[ChromeOnly, Exposed=Window]
|
||||
interface StyleSheetRemovedEvent : Event {
|
||||
constructor(DOMString type,
|
||||
optional StyleSheetRemovedEventInit eventInitDict = {});
|
||||
|
||||
readonly attribute CSSStyleSheet? stylesheet;
|
||||
};
|
||||
|
||||
dictionary StyleSheetRemovedEventInit : EventInit {
|
||||
CSSStyleSheet? stylesheet = null;
|
||||
};
|
@ -81,6 +81,8 @@ WEBIDL_FILES = [
|
||||
"SessionStoreUtils.webidl",
|
||||
"StripOnShareRule.webidl",
|
||||
"StructuredCloneHolder.webidl",
|
||||
"StyleSheetApplicableStateChangeEvent.webidl",
|
||||
"StyleSheetRemovedEvent.webidl",
|
||||
"TelemetryStopwatch.webidl",
|
||||
"UserInteraction.webidl",
|
||||
"WebExtensionContentScript.webidl",
|
||||
@ -92,6 +94,11 @@ WEBIDL_FILES = [
|
||||
"XULTreeElement.webidl",
|
||||
]
|
||||
|
||||
GENERATED_EVENTS_WEBIDL_FILES = [
|
||||
"StyleSheetApplicableStateChangeEvent.webidl",
|
||||
"StyleSheetRemovedEvent.webidl",
|
||||
]
|
||||
|
||||
if CONFIG["MOZ_BUILD_APP"] != "mobile/android":
|
||||
WEBIDL_FILES += [
|
||||
"UniFFI.webidl",
|
||||
|
@ -350,6 +350,11 @@ const kEventConstructors = {
|
||||
},
|
||||
chromeOnly: true,
|
||||
},
|
||||
StyleSheetRemovedEvent: { create (aName, aProps) {
|
||||
return new StyleSheetRemovedEvent(aName, aProps);
|
||||
},
|
||||
chromeOnly: true,
|
||||
},
|
||||
SubmitEvent: { create (aName, aProps) {
|
||||
return new SubmitEvent(aName, aProps);
|
||||
},
|
||||
|
@ -1108,7 +1108,6 @@ WEBIDL_FILES += [
|
||||
"PopStateEvent.webidl",
|
||||
"PopupBlockedEvent.webidl",
|
||||
"ProgressEvent.webidl",
|
||||
"StyleSheetApplicableStateChangeEvent.webidl",
|
||||
]
|
||||
|
||||
# WebExtensions API.
|
||||
@ -1188,7 +1187,6 @@ GENERATED_EVENTS_WEBIDL_FILES = [
|
||||
"PromiseRejectionEvent.webidl",
|
||||
"ScrollViewChangeEvent.webidl",
|
||||
"SecurityPolicyViolationEvent.webidl",
|
||||
"StyleSheetApplicableStateChangeEvent.webidl",
|
||||
"SubmitEvent.webidl",
|
||||
"TaskPriorityChangeEvent.webidl",
|
||||
"TCPServerSocketEvent.webidl",
|
||||
|
@ -1,8 +1,3 @@
|
||||
[browser_bug617076.js]
|
||||
[browser_bug839103.js]
|
||||
support-files =
|
||||
file_bug839103.html
|
||||
bug839103.css
|
||||
[browser_bug1701027-1.js]
|
||||
support-files =
|
||||
helper_bug1701027-1.html
|
||||
@ -13,6 +8,9 @@ support-files =
|
||||
run-if = (((os == 'mac') || (os == 'win' && os_version != '6.1' && processor == 'x86_64')) && debug)
|
||||
[browser_bug1787079.js]
|
||||
run-if = ((os == 'win' && os_version != '6.1' && processor == 'x86_64') && debug)
|
||||
[browser_bug1791083.js]
|
||||
skip-if = !sessionHistoryInParent
|
||||
[browser_bug617076.js]
|
||||
[browser_disableDialogs_onbeforeunload.js]
|
||||
[browser_onbeforeunload_only_after_interaction.js]
|
||||
[browser_onbeforeunload_only_after_interaction_in_frame.js]
|
||||
@ -20,10 +18,6 @@ run-if = ((os == 'win' && os_version != '6.1' && processor == 'x86_64') && debug
|
||||
support-files =
|
||||
test_scroll_into_view_in_oopif.html
|
||||
scroll_into_view_in_child.html
|
||||
[browser_visual_viewport_iframe.js]
|
||||
support-files =
|
||||
test_visual_viewport_in_oopif.html
|
||||
visual_viewport_in_child.html
|
||||
[browser_select_popup_position_in_out_of_process_iframe.js]
|
||||
skip-if =
|
||||
(verify && (os == 'mac')) # bug 1627874
|
||||
@ -32,5 +26,11 @@ skip-if =
|
||||
support-files =
|
||||
!/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
|
||||
!/browser/base/content/test/forms/head.js
|
||||
[browser_bug1791083.js]
|
||||
skip-if = !sessionHistoryInParent
|
||||
[browser_stylesheet_change_events.js]
|
||||
support-files =
|
||||
file_stylesheet_change_events.html
|
||||
stylesheet_change_events.css
|
||||
[browser_visual_viewport_iframe.js]
|
||||
support-files =
|
||||
test_visual_viewport_in_oopif.html
|
||||
visual_viewport_in_child.html
|
||||
|
@ -1,82 +0,0 @@
|
||||
const gTestRoot = getRootDirectory(gTestPath).replace(
|
||||
"chrome://mochitests/content/",
|
||||
"http://127.0.0.1:8888/"
|
||||
);
|
||||
|
||||
add_task(async function test() {
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{ gBrowser, url: gTestRoot + "file_bug839103.html" },
|
||||
async function (browser) {
|
||||
await SpecialPowers.spawn(browser, [gTestRoot], testBody);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// This function runs entirely in the content process. It doesn't have access
|
||||
// any free variables in this file.
|
||||
async function testBody(testRoot) {
|
||||
const gStyleSheet = "bug839103.css";
|
||||
|
||||
function unexpectedContentEvent(event) {
|
||||
ok(false, "Received a " + event.type + " event on content");
|
||||
}
|
||||
|
||||
// We've seen the original stylesheet in the document.
|
||||
// Now add a stylesheet on the fly and make sure we see it.
|
||||
let doc = content.document;
|
||||
doc.styleSheetChangeEventsEnabled = true;
|
||||
doc.addEventListener(
|
||||
"StyleSheetApplicableStateChanged",
|
||||
unexpectedContentEvent
|
||||
);
|
||||
doc.defaultView.addEventListener(
|
||||
"StyleSheetApplicableStateChanged",
|
||||
unexpectedContentEvent
|
||||
);
|
||||
|
||||
let link = doc.createElement("link");
|
||||
link.setAttribute("rel", "stylesheet");
|
||||
link.setAttribute("type", "text/css");
|
||||
link.setAttribute("href", testRoot + gStyleSheet);
|
||||
|
||||
let stateChanged = ContentTaskUtils.waitForEvent(
|
||||
docShell.chromeEventHandler,
|
||||
"StyleSheetApplicableStateChanged",
|
||||
true
|
||||
);
|
||||
doc.body.appendChild(link);
|
||||
|
||||
info("waiting for applicable state change event");
|
||||
let evt = await stateChanged;
|
||||
info("received dynamic style sheet applicable state change event");
|
||||
is(
|
||||
evt.type,
|
||||
"StyleSheetApplicableStateChanged",
|
||||
"evt.type has expected value"
|
||||
);
|
||||
is(evt.target, doc, "event targets correct document");
|
||||
is(evt.stylesheet, link.sheet, "evt.stylesheet has the right value");
|
||||
is(evt.applicable, true, "evt.applicable has the right value");
|
||||
|
||||
stateChanged = ContentTaskUtils.waitForEvent(
|
||||
docShell.chromeEventHandler,
|
||||
"StyleSheetApplicableStateChanged",
|
||||
true
|
||||
);
|
||||
link.sheet.disabled = true;
|
||||
|
||||
evt = await stateChanged;
|
||||
is(
|
||||
evt.type,
|
||||
"StyleSheetApplicableStateChanged",
|
||||
"evt.type has expected value"
|
||||
);
|
||||
info(
|
||||
'received dynamic style sheet applicable state change event after media="" changed'
|
||||
);
|
||||
is(evt.target, doc, "event targets correct document");
|
||||
is(evt.stylesheet, link.sheet, "evt.stylesheet has the right value");
|
||||
is(evt.applicable, false, "evt.applicable has the right value");
|
||||
|
||||
doc.body.removeChild(link);
|
||||
}
|
227
layout/base/tests/browser_stylesheet_change_events.js
Normal file
227
layout/base/tests/browser_stylesheet_change_events.js
Normal file
@ -0,0 +1,227 @@
|
||||
const gTestRoot = getRootDirectory(gTestPath).replace(
|
||||
"chrome://mochitests/content/",
|
||||
"http://127.0.0.1:8888/"
|
||||
);
|
||||
|
||||
add_task(async function test() {
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{ gBrowser, url: gTestRoot + "file_stylesheet_change_events.html" },
|
||||
async function (browser) {
|
||||
await SpecialPowers.spawn(
|
||||
browser,
|
||||
[gTestRoot],
|
||||
testApplicableStateChangeEvent
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// This function runs entirely in the content process. It doesn't have access
|
||||
// any free variables in this file.
|
||||
async function testApplicableStateChangeEvent(testRoot) {
|
||||
// We've seen the original stylesheet in the document.
|
||||
// Now add a stylesheet on the fly and make sure we see it.
|
||||
let doc = content.document;
|
||||
doc.styleSheetChangeEventsEnabled = true;
|
||||
|
||||
const unexpectedContentEvent = event =>
|
||||
ok(false, "Received a " + event.type + " event on content");
|
||||
doc.addEventListener(
|
||||
"StyleSheetApplicableStateChanged",
|
||||
unexpectedContentEvent
|
||||
);
|
||||
doc.defaultView.addEventListener(
|
||||
"StyleSheetApplicableStateChanged",
|
||||
unexpectedContentEvent
|
||||
);
|
||||
doc.addEventListener("StyleSheetRemoved", unexpectedContentEvent);
|
||||
doc.defaultView.addEventListener("StyleSheetRemoved", unexpectedContentEvent);
|
||||
|
||||
function shouldIgnoreEvent(e) {
|
||||
// accessiblecaret.css might be reported, interfering with the test
|
||||
// assertions, so let's ignore it
|
||||
return (
|
||||
e.stylesheet?.href === "resource://content-accessible/accessiblecaret.css"
|
||||
);
|
||||
}
|
||||
|
||||
function waitForStyleApplicableStateChanged() {
|
||||
return ContentTaskUtils.waitForEvent(
|
||||
docShell.chromeEventHandler,
|
||||
"StyleSheetApplicableStateChanged",
|
||||
true,
|
||||
e => !shouldIgnoreEvent(e)
|
||||
);
|
||||
}
|
||||
|
||||
function waitForStyleSheetRemovedEvent() {
|
||||
return ContentTaskUtils.waitForEvent(
|
||||
docShell.chromeEventHandler,
|
||||
"StyleSheetRemoved",
|
||||
true,
|
||||
e => !shouldIgnoreEvent(e)
|
||||
);
|
||||
}
|
||||
|
||||
function checkApplicableStateChangeEvent(event, { applicable, stylesheet }) {
|
||||
is(
|
||||
event.type,
|
||||
"StyleSheetApplicableStateChanged",
|
||||
"event.type has expected value"
|
||||
);
|
||||
is(event.target, doc, "event targets correct document");
|
||||
is(event.stylesheet, stylesheet, "event.stylesheet has the expected value");
|
||||
is(event.applicable, applicable, "event.applicable has the expected value");
|
||||
}
|
||||
|
||||
function checkStyleSheetRemovedEvent(event, { stylesheet }) {
|
||||
is(event.type, "StyleSheetRemoved", "event.type has expected value");
|
||||
is(event.target, doc, "event targets correct document");
|
||||
is(event.stylesheet, stylesheet, "event.stylesheet has the expected value");
|
||||
}
|
||||
|
||||
// Updating the text content will actually create a new StyleSheet instance,
|
||||
// and so we should get one event for the new instance, and another one for
|
||||
// the removal of the "previous"one.
|
||||
function waitForTextContentChange() {
|
||||
return Promise.all([
|
||||
waitForStyleSheetRemovedEvent(),
|
||||
waitForStyleApplicableStateChanged(),
|
||||
]);
|
||||
}
|
||||
|
||||
let stateChanged, evt;
|
||||
|
||||
{
|
||||
const gStyleSheet = "stylesheet_change_events.css";
|
||||
|
||||
info("Add <link> and wait for applicable state change event");
|
||||
let linkEl = doc.createElement("link");
|
||||
linkEl.setAttribute("rel", "stylesheet");
|
||||
linkEl.setAttribute("type", "text/css");
|
||||
linkEl.setAttribute("href", testRoot + gStyleSheet);
|
||||
|
||||
stateChanged = waitForStyleApplicableStateChanged();
|
||||
doc.body.appendChild(linkEl);
|
||||
evt = await stateChanged;
|
||||
|
||||
ok(true, "received dynamic style sheet applicable state change event");
|
||||
checkApplicableStateChangeEvent(evt, {
|
||||
stylesheet: linkEl.sheet,
|
||||
applicable: true,
|
||||
});
|
||||
|
||||
stateChanged = waitForStyleApplicableStateChanged();
|
||||
linkEl.sheet.disabled = true;
|
||||
evt = await stateChanged;
|
||||
|
||||
ok(true, "received dynamic style sheet applicable state change event");
|
||||
checkApplicableStateChangeEvent(evt, {
|
||||
stylesheet: linkEl.sheet,
|
||||
applicable: false,
|
||||
});
|
||||
|
||||
info("Remove stylesheet and wait for removed event");
|
||||
const removedStylesheet = linkEl.sheet;
|
||||
const onStyleSheetRemoved = waitForStyleSheetRemovedEvent();
|
||||
doc.body.removeChild(linkEl);
|
||||
const removedStyleSheetEvt = await onStyleSheetRemoved;
|
||||
|
||||
ok(true, "received removed sheet event");
|
||||
checkStyleSheetRemovedEvent(removedStyleSheetEvt, {
|
||||
stylesheet: removedStylesheet,
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
info("Add <style> node and wait for applicable state changed event");
|
||||
let styleEl = doc.createElement("style");
|
||||
styleEl.textContent = `body { background: tomato; }`;
|
||||
|
||||
stateChanged = waitForStyleApplicableStateChanged();
|
||||
doc.head.appendChild(styleEl);
|
||||
evt = await stateChanged;
|
||||
|
||||
ok(true, "received dynamic style sheet applicable state change event");
|
||||
checkApplicableStateChangeEvent(evt, {
|
||||
stylesheet: styleEl.sheet,
|
||||
applicable: true,
|
||||
});
|
||||
|
||||
info("Updating <style> text content");
|
||||
stateChanged = waitForTextContentChange();
|
||||
const inlineStyleSheetBeforeChange = styleEl.sheet;
|
||||
|
||||
styleEl.textContent = `body { background: gold; }`;
|
||||
const [inlineRemovedEvt, inlineAddedEvt] = await stateChanged;
|
||||
|
||||
ok(true, "received expected style sheet events");
|
||||
checkStyleSheetRemovedEvent(inlineRemovedEvt, {
|
||||
stylesheet: inlineStyleSheetBeforeChange,
|
||||
});
|
||||
checkApplicableStateChangeEvent(inlineAddedEvt, {
|
||||
stylesheet: styleEl.sheet,
|
||||
applicable: true,
|
||||
});
|
||||
|
||||
info("Remove stylesheet and wait for removed event");
|
||||
const onStyleSheetRemoved = waitForStyleSheetRemovedEvent();
|
||||
|
||||
const removedInlineStylesheet = styleEl.sheet;
|
||||
styleEl.remove();
|
||||
const removedStyleSheetEvt = await onStyleSheetRemoved;
|
||||
|
||||
ok(true, "received removed style sheet event");
|
||||
checkStyleSheetRemovedEvent(removedStyleSheetEvt, {
|
||||
stylesheet: removedInlineStylesheet,
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
info(
|
||||
"Create a custom element and check we get an event for its stylesheet"
|
||||
);
|
||||
stateChanged = waitForStyleApplicableStateChanged();
|
||||
const el = doc.createElement("div");
|
||||
const shadowRoot = el.attachShadow({ mode: "open" });
|
||||
doc.body.appendChild(el);
|
||||
shadowRoot.innerHTML = `
|
||||
<span>custom</span>
|
||||
<style>
|
||||
span { color: salmon; }
|
||||
</style>`;
|
||||
evt = await stateChanged;
|
||||
|
||||
ok(true, "received dynamic style sheet applicable state change event");
|
||||
const shadowStyleEl = shadowRoot.querySelector("style");
|
||||
checkApplicableStateChangeEvent(evt, {
|
||||
stylesheet: shadowStyleEl.sheet,
|
||||
applicable: true,
|
||||
});
|
||||
|
||||
info("Updating <style> text content");
|
||||
stateChanged = waitForTextContentChange();
|
||||
const styleSheetBeforeChange = shadowStyleEl.sheet;
|
||||
shadowStyleEl.textContent = `span { color: cyan; }`;
|
||||
const [removedEvt, addedEvt] = await stateChanged;
|
||||
|
||||
ok(true, "received expected style sheet events");
|
||||
checkStyleSheetRemovedEvent(removedEvt, {
|
||||
stylesheet: styleSheetBeforeChange,
|
||||
});
|
||||
checkApplicableStateChangeEvent(addedEvt, {
|
||||
stylesheet: shadowStyleEl.sheet,
|
||||
applicable: true,
|
||||
});
|
||||
|
||||
info("Remove stylesheet and wait for removed event");
|
||||
const onStyleSheetRemoved = waitForStyleSheetRemovedEvent();
|
||||
const removedShadowStylesheet = shadowStyleEl.sheet;
|
||||
shadowStyleEl.remove();
|
||||
const removedStyleSheetEvt = await onStyleSheetRemoved;
|
||||
ok(true, "received removed style sheet event");
|
||||
checkStyleSheetRemovedEvent(removedStyleSheetEvt, {
|
||||
stylesheet: removedShadowStylesheet,
|
||||
});
|
||||
}
|
||||
}
|
@ -648,6 +648,7 @@ module.exports = {
|
||||
StyleSheet: false,
|
||||
StyleSheetApplicableStateChangeEvent: false,
|
||||
StyleSheetList: false,
|
||||
StyleSheetRemovedEvent: false,
|
||||
SubtleCrypto: false,
|
||||
SyncMessageSender: false,
|
||||
TCPServerSocket: false,
|
||||
|
Loading…
Reference in New Issue
Block a user