зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1694548 - Move devtools breakpoints out of mutation events. r=smaug,jdescottes
Use internal events which get fired for CSSOM changes, and in Shadow DOM. This will also allow us to remove mutation events in the future. Depends on D106273 Differential Revision: https://phabricator.services.mozilla.com/D106274
This commit is contained in:
Родитель
156a1feb88
Коммит
8508ab62bb
|
@ -15,12 +15,16 @@ Services.scriptloader.loadSubScript(
|
|||
const DMB_TEST_URL =
|
||||
"http://example.com/browser/devtools/client/debugger/test/mochitest/examples/doc-dom-mutation.html";
|
||||
|
||||
add_task(async function() {
|
||||
// Enable features
|
||||
async function enableMutationBreakpoints() {
|
||||
await pushPref("devtools.debugger.features.dom-mutation-breakpoints", true);
|
||||
await pushPref("devtools.markup.mutationBreakpoints.enabled", true);
|
||||
await pushPref("devtools.debugger.dom-mutation-breakpoints-visible", true);
|
||||
}
|
||||
|
||||
|
||||
add_task(async function() {
|
||||
// Enable features
|
||||
await enableMutationBreakpoints();
|
||||
info("Switches over to the inspector pane");
|
||||
|
||||
const { inspector, toolbox } = await openInspectorForURL(DMB_TEST_URL);
|
||||
|
@ -66,6 +70,13 @@ add_task(async function() {
|
|||
await waitForPaused(dbg);
|
||||
await resume(dbg);
|
||||
|
||||
info("Changing style to trigger debugger pause");
|
||||
SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() {
|
||||
content.document.querySelector("#style-attribute").click();
|
||||
});
|
||||
await waitForPaused(dbg);
|
||||
await resume(dbg);
|
||||
|
||||
info("Changing subtree to trigger debugger pause");
|
||||
SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() {
|
||||
content.document.querySelector("#subtree").click();
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
<body>
|
||||
<button title="Hello" id="attribute" onclick="changeAttribute()">Click me!</button>
|
||||
<button id="style-attribute" onclick="changeStyleAttribute()">Click me!</button>
|
||||
<button id="subtree" onclick="changeSubtree()">Click me!</button>
|
||||
<button id="blackbox" onclick="changeAttribute();
debugger;
//# sourceURL=click.js">Click me!</button>
|
||||
<script src="dom-mutation.js"></script>
|
||||
|
|
|
@ -3,6 +3,10 @@ function changeAttribute() {
|
|||
document.body.setAttribute("title", title);
|
||||
}
|
||||
|
||||
function changeStyleAttribute() {
|
||||
document.body.style.color = "blue";
|
||||
}
|
||||
|
||||
function changeSubtree() {
|
||||
document.body.appendChild(document.createElement("div"));
|
||||
}
|
||||
|
|
|
@ -231,8 +231,8 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
|
|||
// list contains orphaned nodes that were so retained.
|
||||
this._retainedOrphans = new Set();
|
||||
|
||||
this.onNodeInserted = this.onNodeInserted.bind(this);
|
||||
this.onNodeInserted[EXCLUDED_LISTENER] = true;
|
||||
this.onSubtreeModified = this.onSubtreeModified.bind(this);
|
||||
this.onSubtreeModified[EXCLUDED_LISTENER] = true;
|
||||
this.onNodeRemoved = this.onNodeRemoved.bind(this);
|
||||
this.onNodeRemoved[EXCLUDED_LISTENER] = true;
|
||||
this.onAttributeModified = this.onAttributeModified.bind(this);
|
||||
|
@ -2076,65 +2076,58 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
|
|||
_updateDocumentMutationListeners(rawDoc) {
|
||||
const docMutationBreakpoints = this._mutationBreakpointsForDoc(rawDoc);
|
||||
if (!docMutationBreakpoints) {
|
||||
rawDoc.devToolsWatchingDOMMutations = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const origFlag = rawDoc.devToolsWatchingDOMMutations;
|
||||
rawDoc.devToolsWatchingDOMMutations = true;
|
||||
const anyBreakpoint =
|
||||
docMutationBreakpoints.counts.subtree > 0 ||
|
||||
docMutationBreakpoints.counts.removal > 0 ||
|
||||
docMutationBreakpoints.counts.attribute > 0;
|
||||
|
||||
rawDoc.devToolsWatchingDOMMutations = anyBreakpoint;
|
||||
|
||||
if (docMutationBreakpoints.counts.subtree > 0) {
|
||||
eventListenerService.addSystemEventListener(
|
||||
rawDoc,
|
||||
"DOMNodeInserted",
|
||||
this.onNodeInserted,
|
||||
this.chromeEventHandler.addEventListener(
|
||||
"devtoolschildinserted",
|
||||
this.onSubtreeModified,
|
||||
true /* capture */
|
||||
);
|
||||
} else {
|
||||
eventListenerService.removeSystemEventListener(
|
||||
rawDoc,
|
||||
"DOMNodeInserted",
|
||||
this.onNodeInserted,
|
||||
this.chromeEventHandler.removeEventListener(
|
||||
"devtoolschildinserted",
|
||||
this.onSubtreeModified,
|
||||
true /* capture */
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
docMutationBreakpoints.counts.subtree > 0 ||
|
||||
docMutationBreakpoints.counts.removal > 0 ||
|
||||
docMutationBreakpoints.counts.attribute > 0
|
||||
) {
|
||||
eventListenerService.addSystemEventListener(
|
||||
rawDoc,
|
||||
"DOMNodeRemoved",
|
||||
if (anyBreakpoint) {
|
||||
this.chromeEventHandler.addEventListener(
|
||||
"devtoolschildremoved",
|
||||
this.onNodeRemoved,
|
||||
true /* capture */
|
||||
);
|
||||
} else {
|
||||
eventListenerService.removeSystemEventListener(
|
||||
rawDoc,
|
||||
"DOMNodeRemoved",
|
||||
this.chromeEventHandler.removeEventListener(
|
||||
"devtoolschildremoved",
|
||||
this.onNodeRemoved,
|
||||
true /* capture */
|
||||
);
|
||||
}
|
||||
|
||||
if (docMutationBreakpoints.counts.attribute > 0) {
|
||||
eventListenerService.addSystemEventListener(
|
||||
rawDoc,
|
||||
"DOMAttrModified",
|
||||
this.chromeEventHandler.addEventListener(
|
||||
"devtoolsattrmodified",
|
||||
this.onAttributeModified,
|
||||
true /* capture */
|
||||
);
|
||||
} else {
|
||||
eventListenerService.removeSystemEventListener(
|
||||
rawDoc,
|
||||
"DOMAttrModified",
|
||||
this.chromeEventHandler.removeEventListener(
|
||||
"devtoolsattrmodified",
|
||||
this.onAttributeModified,
|
||||
true /* capture */
|
||||
);
|
||||
}
|
||||
|
||||
rawDoc.devToolsWatchingDOMMutations = origFlag;
|
||||
},
|
||||
|
||||
_breakOnMutation: function(mutationType, targetNode, ancestorNode, action) {
|
||||
|
@ -2172,20 +2165,15 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
|
|||
);
|
||||
},
|
||||
|
||||
onNodeInserted: function(evt) {
|
||||
this.onSubtreeModified(evt, "add");
|
||||
},
|
||||
|
||||
onNodeRemoved: function(evt) {
|
||||
const mutationBpInfo = this._breakpointInfoForNode(evt.target);
|
||||
const hasNodeRemovalEvent = mutationBpInfo?.removal;
|
||||
|
||||
this._clearMutationBreakpointsFromSubtree(evt.target);
|
||||
|
||||
if (hasNodeRemovalEvent) {
|
||||
this._breakOnMutation("nodeRemoved", evt.target);
|
||||
} else {
|
||||
this.onSubtreeModified(evt, "remove");
|
||||
this.onSubtreeModified(evt);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -2196,7 +2184,8 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
|
|||
}
|
||||
},
|
||||
|
||||
onSubtreeModified: function(evt, action) {
|
||||
onSubtreeModified: function(evt) {
|
||||
const action = evt.type === "devtoolschildinserted" ? "add" : "remove";
|
||||
let node = evt.target;
|
||||
while ((node = node.parentNode) !== null) {
|
||||
const mutationBpInfo = this._breakpointInfoForNode(node);
|
||||
|
|
|
@ -9567,8 +9567,7 @@ nsINode* Document::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv) {
|
|||
// Scope firing mutation events so that we don't carry any state that
|
||||
// might be stale
|
||||
{
|
||||
nsINode* parent = adoptedNode->GetParentNode();
|
||||
if (parent) {
|
||||
if (nsINode* parent = adoptedNode->GetParentNode()) {
|
||||
nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent);
|
||||
}
|
||||
}
|
||||
|
@ -10830,9 +10829,12 @@ bool Document::CanSavePresentation(nsIRequest* aNewRequest,
|
|||
void Document::Destroy() {
|
||||
// The ContentViewer wants to release the document now. So, tell our content
|
||||
// to drop any references to the document so that it can be destroyed.
|
||||
if (mIsGoingAway) return;
|
||||
if (mIsGoingAway) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReportDocumentUseCounters();
|
||||
SetDevToolsWatchingDOMMutations(false);
|
||||
|
||||
mIsGoingAway = true;
|
||||
|
||||
|
@ -13310,6 +13312,80 @@ already_AddRefed<nsINode> Document::GetTooltipNode() {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class DevToolsMutationObserver final : public nsStubMutationObserver {
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
|
||||
|
||||
// We handle this in nsContentUtils::MaybeFireNodeRemoved, since devtools
|
||||
// relies on the event firing _before_ the removal happens.
|
||||
// NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
|
||||
|
||||
// NOTE(emilio, bug 1694627): DevTools doesn't seem to deal with character
|
||||
// data changes right now (maybe intentionally?).
|
||||
// NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
|
||||
|
||||
DevToolsMutationObserver() = default;
|
||||
|
||||
private:
|
||||
void FireEvent(nsINode* aTarget, const nsAString& aType);
|
||||
|
||||
~DevToolsMutationObserver() = default;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(DevToolsMutationObserver, nsIMutationObserver)
|
||||
|
||||
void DevToolsMutationObserver::FireEvent(nsINode* aTarget,
|
||||
const nsAString& aType) {
|
||||
if (aTarget->ChromeOnlyAccess()) {
|
||||
return;
|
||||
}
|
||||
(new AsyncEventDispatcher(aTarget, aType, CanBubble::eNo,
|
||||
ChromeOnlyDispatch::eYes, Composed::eYes))
|
||||
->RunDOMEventWhenSafe();
|
||||
}
|
||||
|
||||
void DevToolsMutationObserver::AttributeChanged(Element* aElement,
|
||||
int32_t aNamespaceID,
|
||||
nsAtom* aAttribute,
|
||||
int32_t aModType,
|
||||
const nsAttrValue* aOldValue) {
|
||||
FireEvent(aElement, u"devtoolsattrmodified"_ns);
|
||||
}
|
||||
|
||||
void DevToolsMutationObserver::ContentAppended(nsIContent* aFirstNewContent) {
|
||||
for (nsIContent* c = aFirstNewContent; c; c = c->GetNextSibling()) {
|
||||
ContentInserted(c);
|
||||
}
|
||||
}
|
||||
|
||||
void DevToolsMutationObserver::ContentInserted(nsIContent* aChild) {
|
||||
FireEvent(aChild, u"devtoolschildinserted"_ns);
|
||||
}
|
||||
|
||||
static StaticRefPtr<DevToolsMutationObserver> sDevToolsMutationObserver;
|
||||
|
||||
} // namespace
|
||||
|
||||
void Document::SetDevToolsWatchingDOMMutations(bool aValue) {
|
||||
if (mDevToolsWatchingDOMMutations == aValue || mIsGoingAway) {
|
||||
return;
|
||||
}
|
||||
mDevToolsWatchingDOMMutations = aValue;
|
||||
if (aValue) {
|
||||
if (MOZ_UNLIKELY(!sDevToolsMutationObserver)) {
|
||||
sDevToolsMutationObserver = new DevToolsMutationObserver();
|
||||
ClearOnShutdown(&sDevToolsMutationObserver);
|
||||
}
|
||||
AddMutationObserver(sDevToolsMutationObserver);
|
||||
} else if (sDevToolsMutationObserver) {
|
||||
RemoveMutationObserver(sDevToolsMutationObserver);
|
||||
}
|
||||
}
|
||||
|
||||
void Document::MaybeWarnAboutZoom() {
|
||||
if (mHasWarnedAboutZoom) {
|
||||
return;
|
||||
|
|
|
@ -3549,9 +3549,7 @@ class Document : public nsINode,
|
|||
bool DevToolsWatchingDOMMutations() const {
|
||||
return mDevToolsWatchingDOMMutations;
|
||||
}
|
||||
void SetDevToolsWatchingDOMMutations(bool aValue) {
|
||||
mDevToolsWatchingDOMMutations = aValue;
|
||||
}
|
||||
void SetDevToolsWatchingDOMMutations(bool aValue);
|
||||
|
||||
void MaybeWarnAboutZoom();
|
||||
|
||||
|
|
|
@ -4617,8 +4617,7 @@ void nsContentUtils::MaybeFireNodeRemoved(nsINode* aChild, nsINode* aParent) {
|
|||
// that is a know case when we'd normally fire a mutation event, but can't
|
||||
// make that safe and so we suppress it at this time. Ideally this should
|
||||
// go away eventually.
|
||||
if (!(aChild->IsContent() &&
|
||||
aChild->AsContent()->IsInNativeAnonymousSubtree()) &&
|
||||
if (!aChild->IsInNativeAnonymousSubtree() &&
|
||||
!sDOMNodeRemovedSuppressCount) {
|
||||
NS_ERROR("Want to fire DOMNodeRemoved event, but it's not safe");
|
||||
WarnScriptWasIgnored(aChild->OwnerDoc());
|
||||
|
@ -4626,6 +4625,15 @@ void nsContentUtils::MaybeFireNodeRemoved(nsINode* aChild, nsINode* aParent) {
|
|||
return;
|
||||
}
|
||||
|
||||
{
|
||||
Document* doc = aParent->OwnerDoc();
|
||||
if (MOZ_UNLIKELY(doc->DevToolsWatchingDOMMutations()) &&
|
||||
aChild->IsInComposedDoc() && !aChild->ChromeOnlyAccess()) {
|
||||
DispatchChromeEvent(doc, aChild, u"devtoolschildremoved"_ns,
|
||||
CanBubble::eNo, Cancelable::eNo);
|
||||
}
|
||||
}
|
||||
|
||||
if (HasMutationListeners(aChild, NS_EVENT_BITS_MUTATION_NODEREMOVED,
|
||||
aParent)) {
|
||||
InternalMutationEvent mutation(true, eLegacyNodeRemoved);
|
||||
|
|
|
@ -287,9 +287,7 @@ void EventListenerManager::AddEventListenerInternal(
|
|||
mMayHaveMutationListeners = true;
|
||||
// Go from our target to the nearest enclosing DOM window.
|
||||
if (nsPIDOMWindowInner* window = GetInnerWindowForTarget()) {
|
||||
nsCOMPtr<Document> doc = window->GetExtantDoc();
|
||||
if (doc &&
|
||||
!(aFlags.mInSystemGroup && doc->DevToolsWatchingDOMMutations())) {
|
||||
if (Document* doc = window->GetExtantDoc()) {
|
||||
doc->WarnOnceAbout(DeprecatedOperations::eMutationEvent);
|
||||
}
|
||||
// If aEventMessage is eLegacySubtreeModified, we need to listen all
|
||||
|
|
Загрузка…
Ссылка в новой задаче