Issue #2135 - Bug 1413102: Ensure Shadow DOM boundaries are dealt properly in event handling

* RE: BasicEvents.h - our WidgetEvent is not movable (yet), so the change that requires that wasn't included.
* Parts of this use code that was introduced in bug 1427511. For now, they were replaced with their equivalents.
This commit is contained in:
FranklinDM 2023-03-03 21:22:02 +08:00 • committed by roytam1
commit 24572438a0
9 changed files with 299 additions and 70 deletions

View file

@ -148,6 +148,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Event)
tmp->mEvent->mCurrentTarget = nullptr;
tmp->mEvent->mOriginalTarget = nullptr;
tmp->mEvent->mRelatedTarget = nullptr;
tmp->mEvent->mOriginalRelatedTarget = nullptr;
switch (tmp->mEvent->mClass) {
case eDragEventClass: {
WidgetDragEvent* dragEvent = tmp->mEvent->AsDragEvent();
@ -176,6 +177,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Event)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mCurrentTarget)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mOriginalTarget)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mRelatedTarget)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mOriginalRelatedTarget)
switch (tmp->mEvent->mClass) {
case eDragEventClass: {
WidgetDragEvent* dragEvent = tmp->mEvent->AsDragEvent();
@ -1227,44 +1229,6 @@ Event::SetOwner(EventTarget* aOwner)
#endif
}
// static
nsIContent*
Event::GetShadowRelatedTarget(nsIContent* aCurrentTarget,
nsIContent* aRelatedTarget)
{
if (!aCurrentTarget || !aRelatedTarget) {
return nullptr;
}
// Walk up the ancestor node trees of the related target until
// we encounter the node tree of the current target in order
// to find the adjusted related target. Walking up the tree may
// not find a common ancestor node tree if the related target is in
// an ancestor tree, but in that case it does not need to be adjusted.
ShadowRoot* currentTargetShadow = aCurrentTarget->GetContainingShadow();
if (!currentTargetShadow) {
return nullptr;
}
nsIContent* relatedTarget = aCurrentTarget;
while (relatedTarget) {
ShadowRoot* ancestorShadow = relatedTarget->GetContainingShadow();
if (currentTargetShadow == ancestorShadow) {
return relatedTarget;
}
// Didn't find the ancestor tree, thus related target does not have to
// adjusted.
if (!ancestorShadow) {
return nullptr;
}
relatedTarget = ancestorShadow->GetHost();
}
return nullptr;
}
NS_IMETHODIMP
Event::GetCancelBubble(bool* aCancelBubble)
{

View file

@ -251,14 +251,6 @@ public:
return mIsMainThreadEvent;
}
/**
* For a given current target, returns the related target adjusted with
* shadow DOM retargeting rules. Returns nullptr if related target
* is not adjusted.
*/
static nsIContent* GetShadowRelatedTarget(nsIContent* aCurrentTarget,
nsIContent* aRelatedTarget);
void MarkUninitialized()
{
mEvent->mMessage = eVoidEvent;

View file

@ -204,6 +204,16 @@ public:
mNewTarget = aNewTarget;
}
EventTarget* GetRetargetedRelatedTarget()
{
return mRetargetedRelatedTarget;
}
void SetRetargetedRelatedTarget(EventTarget* aTarget)
{
mRetargetedRelatedTarget = aTarget;
}
void SetForceContentDispatch(bool aForce)
{
mFlags.mForceContentDispatch = aForce;
@ -354,6 +364,7 @@ public:
private:
nsCOMPtr<EventTarget> mTarget;
nsCOMPtr<EventTarget> mRetargetedRelatedTarget;
class EventTargetChainFlags
{
@ -422,6 +433,7 @@ EventTargetChainItem::GetEventTargetParent(EventChainPreVisitor& aVisitor)
SetWantsPreHandleEvent(aVisitor.mWantsPreHandleEvent);
SetPreHandleEventOnly(aVisitor.mWantsPreHandleEvent && !aVisitor.mCanHandle);
SetRootOfClosedTree(aVisitor.mRootOfClosedTree);
SetRetargetedRelatedTarget(aVisitor.mRetargetedRelatedTarget);
mItemFlags = aVisitor.mItemFlags;
mItemData = aVisitor.mItemData;
}
@ -454,6 +466,7 @@ EventTargetChainItem::HandleEventTargetChain(
{
// Save the target so that it can be restored later.
nsCOMPtr<EventTarget> firstTarget = aVisitor.mEvent->mTarget;
nsCOMPtr<EventTarget> firstRelatedTarget = aVisitor.mEvent->mRelatedTarget;
uint32_t chainLength = aChain.Length();
uint32_t firstCanHandleEventTargetIdx =
EventTargetChainItem::GetFirstCanHandleEventTargetIdx(aChain);
@ -483,6 +496,30 @@ EventTargetChainItem::HandleEventTargetChain(
}
}
}
// https://dom.spec.whatwg.org/#dispatching-events
// Step 14.2
// "Set event's relatedTarget to tuple's relatedTarget."
// Note, the initial retargeting was done already when creating
// event target chain, so we need to do this only after calling
// HandleEvent, not before, like in the specification.
if (item.GetRetargetedRelatedTarget()) {
bool found = false;
for (uint32_t j = i; j > 0; --j) {
uint32_t childIndex = j - 1;
EventTarget* relatedTarget =
aChain[childIndex].GetRetargetedRelatedTarget();
if (relatedTarget) {
found = true;
aVisitor.mEvent->mRelatedTarget = relatedTarget;
break;
}
}
if (!found) {
aVisitor.mEvent->mRelatedTarget =
aVisitor.mEvent->mOriginalRelatedTarget;
}
}
}
// Target
@ -511,6 +548,14 @@ EventTargetChainItem::HandleEventTargetChain(
aVisitor.mEvent->mTarget = newTarget;
}
// https://dom.spec.whatwg.org/#dispatching-events
// Step 15.2
// "Set event's relatedTarget to tuple's relatedTarget."
EventTarget* relatedTarget = item.GetRetargetedRelatedTarget();
if (relatedTarget) {
aVisitor.mEvent->mRelatedTarget = relatedTarget;
}
if (aVisitor.mEvent->mFlags.mBubbles || newTarget) {
if ((!aVisitor.mEvent->mFlags.mNoContentDispatch ||
item.ForceContentDispatch()) &&
@ -542,6 +587,7 @@ EventTargetChainItem::HandleEventTargetChain(
// Retarget for system event group (which does the default handling too).
// Setting back the target which was used also for default event group.
aVisitor.mEvent->mTarget = firstTarget;
aVisitor.mEvent->mRelatedTarget = aVisitor.mEvent->mOriginalRelatedTarget;
aVisitor.mEvent->mFlags.mInSystemGroup = true;
HandleEventTargetChain(aChain,
aVisitor,
@ -599,6 +645,7 @@ MayRetargetToChromeIfCanNotHandleEvent(
EventTargetChainItem::DestroyLast(aChain, aTargetEtci);
}
if (aPreVisitor.mAutomaticChromeDispatch && aContent) {
aPreVisitor.mRelatedTargetRetargetedInCurrentScope = false;
// Event target couldn't handle the event. Try to propagate to chrome.
EventTargetChainItem* chromeTargetEtci =
EventTargetChainItemForChromeTarget(aChain, aContent, aChildEtci);
@ -762,9 +809,10 @@ EventDispatcher::Dispatch(nsISupports* aTarget,
aEvent->mOriginalTarget = aEvent->mTarget;
}
aEvent->mOriginalRelatedTarget = aEvent->mRelatedTarget;
nsCOMPtr<nsIContent> content = do_QueryInterface(aEvent->mOriginalTarget);
bool isInAnon = (content && (content->IsInAnonymousSubtree() ||
content->IsInShadowTree()));
bool isInAnon = (content && content->IsInAnonymousSubtree());
aEvent->mFlags.mIsBeingDispatched = true;
@ -772,7 +820,7 @@ EventDispatcher::Dispatch(nsISupports* aTarget,
// GetEventTargetParent for the original target.
nsEventStatus status = aEventStatus ? *aEventStatus : nsEventStatus_eIgnore;
EventChainPreVisitor preVisitor(aPresContext, aEvent, aDOMEvent, status,
isInAnon);
isInAnon, aEvent->mTarget);
targetEtci->GetEventTargetParent(preVisitor);
if (!preVisitor.mCanHandle) {
@ -810,12 +858,18 @@ EventDispatcher::Dispatch(nsISupports* aTarget,
if (preVisitor.mEventTargetAtParent) {
// Need to set the target of the event
// so that also the next retargeting works.
preVisitor.mTargetInKnownToBeHandledScope = preVisitor.mEvent->mTarget;
preVisitor.mEvent->mTarget = preVisitor.mEventTargetAtParent;
parentEtci->SetNewTarget(preVisitor.mEventTargetAtParent);
}
if (preVisitor.mRetargetedRelatedTarget) {
preVisitor.mEvent->mRelatedTarget = preVisitor.mRetargetedRelatedTarget;
}
parentEtci->GetEventTargetParent(preVisitor);
if (preVisitor.mCanHandle) {
preVisitor.mTargetInKnownToBeHandledScope = preVisitor.mEvent->mTarget;
topEtci = parentEtci;
} else {
nsCOMPtr<nsINode> disabledTarget = do_QueryInterface(parentTarget);
@ -825,6 +879,7 @@ EventDispatcher::Dispatch(nsISupports* aTarget,
topEtci,
disabledTarget);
if (parentEtci && preVisitor.mCanHandle) {
preVisitor.mTargetInKnownToBeHandledScope = preVisitor.mEvent->mTarget;
EventTargetChainItem* item =
EventTargetChainItem::GetFirstCanHandleEventTarget(chain);
item->SetNewTarget(parentTarget);
@ -871,6 +926,19 @@ EventDispatcher::Dispatch(nsISupports* aTarget,
aEvent->mFlags.mIsBeingDispatched = false;
aEvent->mFlags.mDispatchedAtLeastOnce = true;
// https://dom.spec.whatwg.org/#concept-event-dispatch
// Step 18
// "If target's root is a shadow root, then set event's target attribute and
// event's relatedTarget to null."
nsCOMPtr<nsIContent> finalTarget = do_QueryInterface(aEvent->mTarget);
// TODO: replace with IsShadowRoot() check once bug 1427511 lands.
if (finalTarget && ShadowRoot::FromNode(finalTarget->SubtreeRoot())) {
aEvent->mTarget = nullptr;
aEvent->mOriginalTarget = nullptr;
aEvent->mRelatedTarget = nullptr;
aEvent->mOriginalRelatedTarget = nullptr;
}
if (!externalDOMEvent && preVisitor.mDOMEvent) {
// An dom::Event was created while dispatching the event.
// Duplicate private data if someone holds a pointer to it.

View file

@ -113,7 +113,8 @@ public:
WidgetEvent* aEvent,
nsIDOMEvent* aDOMEvent,
nsEventStatus aEventStatus,
bool aIsInAnon)
bool aIsInAnon,
dom::EventTarget* aTargetInKnownToBeHandledScope)
: EventChainVisitor(aPresContext, aEvent, aDOMEvent, aEventStatus)
, mCanHandle(true)
, mAutomaticChromeDispatch(true)
@ -126,8 +127,11 @@ public:
, mRootOfClosedTree(false)
, mParentIsSlotInClosedTree(false)
, mParentIsChromeHandler(false)
, mRelatedTargetRetargetedInCurrentScope(false)
, mParentTarget(nullptr)
, mEventTargetAtParent(nullptr)
, mRetargetedRelatedTarget(nullptr)
, mTargetInKnownToBeHandledScope(aTargetInKnownToBeHandledScope)
{
}
@ -144,8 +148,12 @@ public:
mRootOfClosedTree = false;
mParentIsSlotInClosedTree = false;
mParentIsChromeHandler = false;
// Note, we don't clear mRelatedTargetRetargetedInCurrentScope explicitly,
// since it is used during event path creation to indicate whether
// relatedTarget may need to be retargeted.
mParentTarget = nullptr;
mEventTargetAtParent = nullptr;
mRetargetedRelatedTarget = nullptr;
}
dom::EventTarget* GetParentTarget()
@ -161,6 +169,13 @@ public:
}
}
void IgnoreCurrentTarget()
{
mCanHandle = false;
SetParentTarget(nullptr, false);
mEventTargetAtParent = nullptr;
}
/**
* Member that must be set in GetEventTargetParent by event targets. If set to
* false, indicates that this event target will not be handling the event and
@ -229,6 +244,13 @@ public:
*/
bool mParentIsChromeHandler;
/**
* True if event's related target has been already retargeted in the
* current 'scope'. This should be set to false initially and whenever
* event path creation crosses shadow boundary.
*/
bool mRelatedTargetRetargetedInCurrentScope;
private:
/**
* Parent item in the event target chain.
@ -241,6 +263,19 @@ public:
* which should be used when the event is handled at mParentTarget.
*/
dom::EventTarget* mEventTargetAtParent;
/**
* If the related target of the event needs to be retargeted, set this
* to a new EventTarget.
*/
dom::EventTarget* mRetargetedRelatedTarget;
/**
* Set to the value of mEvent->mTarget of the previous scope in case of
* Shadow DOM or such, and if there is no anonymous content this just points
* to the initial target.
*/
dom::EventTarget* mTargetInKnownToBeHandledScope;
};
class EventChainPostVisitor : public mozilla::EventChainVisitor