Bug 1483882 - Teach IDTracker about Shadow DOM. r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D3533
This commit is contained in:
Emilio Cobos Álvarez 2018-08-17 11:35:15 +00:00
parent bb47a9554c
commit 66b986e252
19 changed files with 282 additions and 90 deletions

View file

@ -349,6 +349,18 @@ DocumentOrShadowRoot::RemoveIDTargetObserver(nsAtom* aID,
entry->RemoveContentChangeCallback(aObserver, aData, aForImage);
}
Element*
DocumentOrShadowRoot::LookupImageElement(const nsAString& aId)
{
if (aId.IsEmpty()) {
return nullptr;
}
nsIdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
return entry ? entry->GetImageIdElement() : nullptr;
}
void
DocumentOrShadowRoot::ReportEmptyGetElementByIdArg()
{

View file

@ -160,6 +160,15 @@ public:
void RemoveIDTargetObserver(nsAtom* aID, IDTargetObserver aObserver,
void* aData, bool aForImage);
/**
* Lookup an image element using its associated ID, which is usually provided
* by |-moz-element()|. Similar to GetElementById, with the difference that
* elements set using mozSetImageElement have higher priority.
* @param aId the ID associated the element we want to lookup
* @return the element associated with |aId|
*/
Element* LookupImageElement(const nsAString& aElementId);
/**
* Check that aId is not empty and log a message to the console
* service if it is.

View file

@ -1464,10 +1464,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement)
unbind the child nodes.
} */
// Clear flag here because unlinking slots will clear the
// containing shadow root pointer.
tmp->UnsetFlags(NODE_IS_IN_SHADOW_TREE);
if (ShadowRoot* shadowRoot = tmp->GetShadowRoot()) {
for (nsIContent* child = shadowRoot->GetFirstChild();
child;

View file

@ -17,9 +17,28 @@
namespace mozilla {
namespace dom {
static DocumentOrShadowRoot*
DocOrShadowFromContent(nsIContent& aContent)
{
ShadowRoot* shadow = aContent.GetContainingShadow();
// We never look in <svg:use> shadow trees, for backwards compat.
while (shadow && shadow->Host()->IsSVGElement(nsGkAtoms::use)) {
shadow = shadow->Host()->GetContainingShadow();
}
if (shadow) {
return shadow;
}
return aContent.OwnerDoc();
}
void
IDTracker::Reset(nsIContent* aFromContent, nsIURI* aURI,
bool aWatch, bool aReferenceImage)
IDTracker::Reset(nsIContent* aFromContent,
nsIURI* aURI,
bool aWatch,
bool aReferenceImage)
{
MOZ_ASSERT(aFromContent, "Reset() expects non-null content pointer");
@ -34,13 +53,11 @@ IDTracker::Reset(nsIContent* aFromContent, nsIURI* aURI,
// document charset, hopefully...
NS_UnescapeURL(refPart);
// Get the current document
nsIDocument *doc = aFromContent->OwnerDoc();
if (!doc) {
return;
}
// Get the thing to observe changes to.
nsIDocument* doc = aFromContent->OwnerDoc();
DocumentOrShadowRoot* docOrShadow = DocOrShadowFromContent(*aFromContent);
auto encoding = doc->GetDocumentCharacterSet();
nsAutoString ref;
nsresult rv = encoding->DecodeWithoutBOMHandling(refPart, ref);
if (NS_FAILED(rv) || ref.IsEmpty()) {
@ -49,7 +66,7 @@ IDTracker::Reset(nsIContent* aFromContent, nsIURI* aURI,
rv = NS_OK;
nsIContent* bindingParent = aFromContent->GetBindingParent();
if (bindingParent) {
if (bindingParent && !aFromContent->IsInShadowTree()) {
nsXBLBinding* binding = bindingParent->GetXBLBinding();
if (!binding) {
// This happens, for example, if aFromContent is part of the content
@ -100,6 +117,7 @@ IDTracker::Reset(nsIContent* aFromContent, nsIURI* aURI,
RefPtr<nsIDocument::ExternalResourceLoad> load;
doc = doc->RequestExternalResource(aURI, aFromContent,
getter_AddRefs(load));
docOrShadow = doc;
if (!doc) {
if (!load || !aWatch) {
// Nothing will ever happen here
@ -109,9 +127,7 @@ IDTracker::Reset(nsIContent* aFromContent, nsIURI* aURI,
DocumentLoadNotification* observer =
new DocumentLoadNotification(this, ref);
mPendingNotification = observer;
if (observer) {
load->AddObserver(observer);
}
load->AddObserver(observer);
// Keep going so we set up our watching stuff a bit
}
}
@ -124,51 +140,49 @@ IDTracker::Reset(nsIContent* aFromContent, nsIURI* aURI,
}
mReferencingImage = aReferenceImage;
HaveNewDocument(doc, aWatch, ref);
HaveNewDocumentOrShadowRoot(docOrShadow, aWatch, ref);
}
void
IDTracker::ResetWithID(nsIContent* aFromContent, const nsString& aID,
IDTracker::ResetWithID(nsIContent* aFromContent,
nsAtom* aID,
bool aWatch)
{
nsIDocument *doc = aFromContent->OwnerDoc();
if (!doc)
return;
// XXX Need to take care of XBL/XBL2
MOZ_ASSERT(aFromContent);
MOZ_ASSERT(aID);
if (aWatch) {
RefPtr<nsAtom> atom = NS_Atomize(aID);
if (!atom)
return;
RefPtr<nsAtom> atom = aID;
atom.swap(mWatchID);
}
mReferencingImage = false;
HaveNewDocument(doc, aWatch, aID);
DocumentOrShadowRoot* docOrShadow = DocOrShadowFromContent(*aFromContent);
HaveNewDocumentOrShadowRoot(docOrShadow, aWatch, nsDependentAtomString(aID));
}
void
IDTracker::HaveNewDocument(nsIDocument* aDocument, bool aWatch,
const nsString& aRef)
IDTracker::HaveNewDocumentOrShadowRoot(
DocumentOrShadowRoot* aDocOrShadow,
bool aWatch,
const nsString& aRef)
{
if (aWatch) {
mWatchDocument = aDocument;
if (mWatchDocument) {
mElement = mWatchDocument->AddIDTargetObserver(mWatchID, Observe, this,
mReferencingImage);
mWatchDocumentOrShadowRoot = nullptr;
if (aDocOrShadow) {
mWatchDocumentOrShadowRoot = &aDocOrShadow->AsNode();
mElement = aDocOrShadow->AddIDTargetObserver(mWatchID, Observe, this, mReferencingImage);
}
return;
}
if (!aDocument) {
if (!aDocOrShadow) {
return;
}
Element *e = mReferencingImage ? aDocument->LookupImageElement(aRef) :
aDocument->GetElementById(aRef);
Element* e = mReferencingImage ? aDocOrShadow->LookupImageElement(aRef)
: aDocOrShadow->GetElementById(aRef);
if (e) {
mElement = e;
}
@ -177,32 +191,33 @@ IDTracker::HaveNewDocument(nsIDocument* aDocument, bool aWatch,
void
IDTracker::Traverse(nsCycleCollectionTraversalCallback* aCB)
{
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mWatchDocument");
aCB->NoteXPCOMChild(mWatchDocument);
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mContent");
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mWatchDocumentOrShadowRoot");
aCB->NoteXPCOMChild(mWatchDocumentOrShadowRoot);
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mElement");
aCB->NoteXPCOMChild(mElement);
}
void
IDTracker::Unlink()
{
if (mWatchDocument && mWatchID) {
mWatchDocument->RemoveIDTargetObserver(mWatchID, Observe, this,
mReferencingImage);
if (mWatchID) {
if (DocumentOrShadowRoot* docOrShadow = GetWatchDocOrShadowRoot()) {
docOrShadow->RemoveIDTargetObserver(
mWatchID, Observe, this, mReferencingImage);
}
}
if (mPendingNotification) {
mPendingNotification->Clear();
mPendingNotification = nullptr;
}
mWatchDocument = nullptr;
mWatchDocumentOrShadowRoot = nullptr;
mWatchID = nullptr;
mElement = nullptr;
mReferencingImage = false;
}
bool
IDTracker::Observe(Element* aOldElement,
Element* aNewElement, void* aData)
IDTracker::Observe(Element* aOldElement, Element* aNewElement, void* aData)
{
IDTracker* p = static_cast<IDTracker*>(aData);
if (p->mPendingNotification) {
@ -216,17 +231,14 @@ IDTracker::Observe(Element* aOldElement,
}
bool keepTracking = p->IsPersistent();
if (!keepTracking) {
p->mWatchDocument = nullptr;
p->mWatchDocumentOrShadowRoot = nullptr;
p->mWatchID = nullptr;
}
return keepTracking;
}
NS_IMPL_ISUPPORTS_INHERITED0(IDTracker::ChangeNotification,
mozilla::Runnable)
NS_IMPL_ISUPPORTS(IDTracker::DocumentLoadNotification,
nsIObserver)
NS_IMPL_ISUPPORTS_INHERITED0(IDTracker::ChangeNotification, mozilla::Runnable)
NS_IMPL_ISUPPORTS(IDTracker::DocumentLoadNotification, nsIObserver)
NS_IMETHODIMP
IDTracker::DocumentLoadNotification::Observe(nsISupports* aSubject,
@ -241,7 +253,7 @@ IDTracker::DocumentLoadNotification::Observe(nsISupports* aSubject,
NS_ASSERTION(!mTarget->mElement, "Why do we have content here?");
// If we got here, that means we had Reset() called with aWatch ==
// true. So keep watching if IsPersistent().
mTarget->HaveNewDocument(doc, mTarget->IsPersistent(), mRef);
mTarget->HaveNewDocumentOrShadowRoot(doc, mTarget->IsPersistent(), mRef);
mTarget->ElementChanged(nullptr, mTarget->mElement);
}
return NS_OK;

View file

@ -9,6 +9,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ShadowRoot.h"
#include "nsAtom.h"
#include "nsIDocument.h"
#include "nsThreadUtils.h"
@ -39,10 +40,10 @@ class IDTracker {
public:
typedef mozilla::dom::Element Element;
IDTracker()
: mReferencingImage(false)
{}
~IDTracker() {
IDTracker() = default;
~IDTracker()
{
Unlink();
}
@ -63,7 +64,9 @@ public:
* @param aReferenceImage whether the ID references image elements which are
* subject to the document's mozSetImageElement overriding mechanism.
*/
void Reset(nsIContent* aFrom, nsIURI* aURI, bool aWatch = true,
void Reset(nsIContent* aFrom,
nsIURI* aURI,
bool aWatch = true,
bool aReferenceImage = false);
/**
@ -75,8 +78,7 @@ public:
* changes, so ElementChanged won't fire and get() will always return the same
* value, the current element for the ID.
*/
void ResetWithID(nsIContent* aFrom, const nsString& aID,
bool aWatch = true);
void ResetWithID(nsIContent* aFrom, nsAtom* aID, bool aWatch = true);
/**
* Clears the reference. ElementChanged is not triggered. get() will return
@ -92,7 +94,8 @@ protected:
* to call this superclass method to change mElement. This is called
* at script-runnable time.
*/
virtual void ElementChanged(Element* aFrom, Element* aTo) {
virtual void ElementChanged(Element* aFrom, Element* aTo)
{
mElement = aTo;
}
@ -106,8 +109,9 @@ protected:
* Set ourselves up with our new document. Note that aDocument might be
* null. Either aWatch must be false or aRef must be empty.
*/
void HaveNewDocument(nsIDocument* aDocument, bool aWatch,
const nsString& aRef);
void HaveNewDocumentOrShadowRoot(DocumentOrShadowRoot*,
bool aWatch,
const nsString& aRef);
private:
static bool Observe(Element* aOldElement,
@ -167,9 +171,8 @@ private:
public nsIObserver
{
public:
DocumentLoadNotification(IDTracker* aTarget,
const nsString& aRef) :
Notification(aTarget)
DocumentLoadNotification(IDTracker* aTarget, const nsString& aRef)
: Notification(aTarget)
{
if (!mTarget->IsPersistent()) {
mRef = aRef;
@ -187,11 +190,24 @@ private:
};
friend class DocumentLoadNotification;
RefPtr<nsAtom> mWatchID;
nsCOMPtr<nsIDocument> mWatchDocument;
DocumentOrShadowRoot* GetWatchDocOrShadowRoot() const
{
if (!mWatchDocumentOrShadowRoot) {
return nullptr;
}
MOZ_ASSERT(mWatchDocumentOrShadowRoot->IsDocument() ||
mWatchDocumentOrShadowRoot->IsShadowRoot());
if (ShadowRoot* shadow = ShadowRoot::FromNode(*mWatchDocumentOrShadowRoot)) {
return shadow;
}
return mWatchDocumentOrShadowRoot->AsDocument();
}
RefPtr<nsAtom> mWatchID;
nsCOMPtr<nsINode> mWatchDocumentOrShadowRoot; // Always a `DocumentOrShadowRoot`.
RefPtr<Element> mElement;
RefPtr<Notification> mPendingNotification;
bool mReferencingImage;
bool mReferencingImage = false;
};
inline void

View file

@ -4953,16 +4953,6 @@ nsIDocument::MozSetImageElement(const nsAString& aImageElementId,
}
}
Element*
nsIDocument::LookupImageElement(const nsAString& aId)
{
if (aId.IsEmpty())
return nullptr;
nsIdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
return entry ? entry->GetImageIdElement() : nullptr;
}
void
nsIDocument::DispatchContentLoadedEvents()
{

View file

@ -2844,15 +2844,6 @@ public:
using mozilla::dom::DocumentOrShadowRoot::GetElementsByTagNameNS;
using mozilla::dom::DocumentOrShadowRoot::GetElementsByClassName;
/**
* Lookup an image element using its associated ID, which is usually provided
* by |-moz-element()|. Similar to GetElementById, with the difference that
* elements set using mozSetImageElement have higher priority.
* @param aId the ID associated the element we want to lookup
* @return the element associated with |aId|
*/
Element* LookupImageElement(const nsAString& aElementId);
mozilla::dom::DocumentTimeline* Timeline();
mozilla::LinkedList<mozilla::dom::DocumentTimeline>& Timelines()
{

View file

@ -105,8 +105,7 @@ nsSMILTimeValueSpec::ResolveReferences(nsIContent* aContextNode)
RefPtr<Element> oldReferencedElement = mReferencedElement.get();
if (mParams.mDependentElemID) {
mReferencedElement.ResetWithID(aContextNode,
nsDependentAtomString(mParams.mDependentElemID));
mReferencedElement.ResetWithID(aContextNode, mParams.mDependentElemID);
} else if (mParams.mType == nsSMILTimeValueSpecParams::EVENT) {
Element* target = mOwner->GetTargetElement();
mReferencedElement.ResetWithElement(target);

View file

@ -0,0 +1,22 @@
<!doctype html>
<svg style="position: absolute; width: 0; height: 0">
<defs>
<pattern id="rect" width="100" height="100">
<rect fill="red" width="100" height="100" />
</pattern>
</defs>
</svg>
<div id="host"></div>
<script>
// Should peek the pattern from the shadow root (green), not from the document (red).
host.attachShadow({ mode: "open" }).innerHTML = `
<svg width="100" height="100">
<defs>
<pattern id="rect" width="100" height="100">
<rect fill="lime" width="100" height="100" />
</pattern>
</defs>
<rect fill="url(#rect)" width="100" height="100" />
</svg>
`;
</script>

View file

@ -0,0 +1,17 @@
<!doctype html>
<div id="host"></div>
<script>
// Test dynamic id changes inside the shadow root.
host.attachShadow({ mode: "open" }).innerHTML = `
<svg width="100" height="100">
<defs>
<pattern id="rect1" width="100" height="100">
<rect fill="lime" width="100" height="100" />
</pattern>
</defs>
<rect fill="url(#rect)" width="100" height="100" />
</svg>
`;
document.body.offsetTop;
host.shadowRoot.getElementById("rect1").id = "rect";
</script>

View file

@ -0,0 +1,14 @@
<!doctype html>
<div id="host"></div>
<svg height="0">
<!-- use an empty g to force fragid-shadow-resource.svg to load before onload -->
<use href="fragid-shadow-resource.svg#empty">
</svg>
<script>
// Test that external resource URIs resolve properly inside shadow trees.
host.attachShadow({ mode: "open" }).innerHTML = `
<svg width="100" height="100">
<rect fill="url(fragid-shadow-resource.svg#rect)" width="100" height="100" />
</svg>
`;
</script>

View file

@ -0,0 +1,13 @@
<!doctype html>
<div id="host"></div>
<script>
// Test references in <svg:use> work properly.
host.attachShadow({ mode: "open" }).innerHTML = `
<svg width="100" height="100">
<defs>
<rect fill="lime" id="rect-4" width="100" height="100">
</defs>
<use href="#rect-4" />
</svg>
`;
</script>

View file

@ -0,0 +1,13 @@
<!doctype html>
<div id="host"></div>
<script>
// Test absolute URIs inside shadow trees, which behave the same way as just the fragment id.
host.attachShadow({ mode: "open" }).innerHTML = `
<svg width="100" height="100">
<defs>
<rect fill="lime" id="rect-5" width="100" height="100">
</defs>
<use href="${location.href}#rect-5" />
</svg>
`;
</script>

View file

@ -0,0 +1,23 @@
<!doctype html>
<svg style="position: absolute; width: 0; height: 0">
<defs>
<pattern id="rect" width="100" height="100">
<rect fill="red" width="100" height="100" />
</pattern>
</defs>
</svg>
<div id="host"></div>
<script>
// Should peek the pattern from the shadow root (green), not from the document (red),
// even though the uri is absolute.
host.attachShadow({ mode: "open" }).innerHTML = `
<svg width="100" height="100">
<defs>
<pattern id="rect" width="100" height="100">
<rect fill="lime" width="100" height="100" />
</pattern>
</defs>
<rect fill="url(${location.href}#rect)" width="100" height="100" />
</svg>
`;
</script>

View file

@ -0,0 +1,28 @@
<!doctype html>
<svg style="position: absolute; width: 0; height: 0">
<defs>
<pattern id="rect" width="100" height="100">
<rect fill="red" width="100" height="100" />
</pattern>
</defs>
</svg>
<div id="host"></div>
<script>
// Test references from a <svg:use> subtree.
host.attachShadow({ mode: "open" }).innerHTML = `
<svg width="100" height="100">
<defs>
<pattern id="rect" width="100" height="100">
<rect fill="lime" width="100" height="100" />
</pattern>
<symbol id="useme">
<pattern id="rect" width="100" height="100">
<rect fill="red" width="100" height="100" />
</pattern>
<rect fill="url(#rect)" width="100" height="100" />
</symbol>
</defs>
<use href="#useme" />
</svg>
`;
</script>

View file

@ -0,0 +1,15 @@
<!doctype html>
<svg width="100" height="100">
<defs>
<pattern id="rect" width="100" height="100">
<rect fill="lime" width="100" height="100" />
</pattern>
<symbol id="useme">
<pattern id="rect" width="100" height="100">
<rect fill="red" width="100" height="100" />
</pattern>
<rect fill="url(#rect)" width="100" height="100" />
</symbol>
</defs>
<use href="#useme" />
</svg>

View file

@ -0,0 +1,4 @@
<!doctype html>
<svg width="100" height="100">
<rect fill="lime" width="100" height="100" />
</svg>

View file

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<g id="empty" />
<pattern id="rect" width="100" height="100">
<rect fill="lime" width="100" height="100" />
</pattern>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 204 B

View file

@ -552,3 +552,13 @@ fuzzy-if(skiaContent,0-1,0-100) == tspan-xy-anchor-end-01.svg tspan-xy-anchor-en
== currentColor-override-flood.svg pass.svg
== currentColor-override-lighting.svg currentColor-override-lighting-ref.svg
== currentColor-override-stop.svg pass.svg
# Shadow DOM id tracking.
pref(dom.webcomponents.shadowdom.enabled,true) == fragid-shadow-1.html fragid-shadow-ref.html
pref(dom.webcomponents.shadowdom.enabled,true) == fragid-shadow-2.html fragid-shadow-ref.html
pref(dom.webcomponents.shadowdom.enabled,true) == fragid-shadow-3.html fragid-shadow-ref.html
pref(dom.webcomponents.shadowdom.enabled,true) == fragid-shadow-4.html fragid-shadow-ref.html
pref(dom.webcomponents.shadowdom.enabled,true) == fragid-shadow-5.html fragid-shadow-ref.html
pref(dom.webcomponents.shadowdom.enabled,true) == fragid-shadow-6.html fragid-shadow-ref.html
pref(dom.webcomponents.shadowdom.enabled,true) == fragid-shadow-7.html fragid-shadow-ref.html
pref(dom.webcomponents.shadowdom.enabled,true) == fragid-shadow-8.html fragid-shadow-ref.html