Bug 1412775 - Implement Event.composedPath, r=stone

--HG--
extra : rebase_source : a970c4a0daac4a7a02cec34733131734eb93b151
This commit is contained in:
Olli Pettay 2017-12-18 18:07:36 +02:00
Родитель b1b879f86e
Коммит d78d3295c5
25 изменённых файлов: 191 добавлений и 114 удалений

Просмотреть файл

@ -980,6 +980,7 @@ nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor)
// Don't propagate mouseover and mouseout events when mouse is moving
// inside chrome access only content.
bool isAnonForEvents = IsRootOfChromeAccessOnlySubtree();
aVisitor.mRootOfClosedTree = isAnonForEvents;
if ((aVisitor.mEvent->mMessage == eMouseOver ||
aVisitor.mEvent->mMessage == eMouseOut ||
aVisitor.mEvent->mMessage == ePointerOver ||
@ -1001,7 +1002,7 @@ nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor)
nsIContent* adjustedTarget =
Event::GetShadowRelatedTarget(this, relatedTarget);
if (this == adjustedTarget) {
aVisitor.mParentTarget = nullptr;
aVisitor.SetParentTarget(nullptr, false);
aVisitor.mCanHandle = false;
return NS_OK;
}
@ -1058,7 +1059,7 @@ nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor)
originalTarget->FindFirstNonChromeOnlyAccessContent())
? "" : "Wrong event propagation!?!\n");
#endif
aVisitor.mParentTarget = nullptr;
aVisitor.SetParentTarget(nullptr, false);
// Event should not propagate to non-anon content.
aVisitor.mCanHandle = isAnonForEvents;
return NS_OK;
@ -1110,11 +1111,17 @@ nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor)
if (!aVisitor.mEvent->mFlags.mComposedInNativeAnonymousContent &&
IsRootOfNativeAnonymousSubtree() && OwnerDoc() &&
OwnerDoc()->GetWindow()) {
aVisitor.mParentTarget = OwnerDoc()->GetWindow()->GetParentTarget();
aVisitor.SetParentTarget(OwnerDoc()->GetWindow()->GetParentTarget(), true);
} else if (parent) {
aVisitor.mParentTarget = parent;
aVisitor.SetParentTarget(parent, false);
if (slot) {
ShadowRoot* root = slot->GetContainingShadow();
if (root && root->IsClosed()) {
aVisitor.mParentIsSlotInClosedTree = true;
}
}
} else {
aVisitor.mParentTarget = GetComposedDoc();
aVisitor.SetParentTarget(GetComposedDoc(), false);
}
return NS_OK;
}

Просмотреть файл

@ -307,6 +307,7 @@ nsresult
ShadowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor)
{
aVisitor.mCanHandle = true;
aVisitor.mRootOfClosedTree = IsClosed();
// https://dom.spec.whatwg.org/#ref-for-get-the-parent%E2%91%A6
if (!aVisitor.mEvent->mFlags.mComposed) {
@ -321,13 +322,13 @@ ShadowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor)
EventTarget* parentTarget = win && aVisitor.mEvent->mMessage != eLoad
? win->GetParentTarget() : nullptr;
aVisitor.mParentTarget = parentTarget;
aVisitor.SetParentTarget(parentTarget, true);
return NS_OK;
}
}
nsIContent* shadowHost = GetHost();
aVisitor.mParentTarget = shadowHost;
aVisitor.SetParentTarget(shadowHost, false);
if (aVisitor.mOriginalTargetIsInAnon) {
nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mEvent->mTarget));

Просмотреть файл

@ -8209,8 +8209,8 @@ nsDocument::GetEventTargetParent(EventChainPreVisitor& aVisitor)
// Load events must not propagate to |window| object, see bug 335251.
if (aVisitor.mEvent->mMessage != eLoad) {
nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetWindow());
aVisitor.mParentTarget =
window ? window->GetTargetForEventTargetChain() : nullptr;
aVisitor.SetParentTarget(
window ? window->GetTargetForEventTargetChain() : nullptr, false);
}
return NS_OK;
}

Просмотреть файл

@ -1949,7 +1949,7 @@ nsGlobalWindowInner::GetEventTargetParent(EventChainPreVisitor& aVisitor)
}
}
aVisitor.mParentTarget = GetParentTarget();
aVisitor.SetParentTarget(GetParentTarget(), true);
// Handle 'active' event.
if (!mIdleObservers.IsEmpty() &&

Просмотреть файл

@ -277,7 +277,7 @@ nsInProcessTabChildGlobal::GetEventTargetParent(EventChainPreVisitor& aVisitor)
#endif
if (mPreventEventsEscaping) {
aVisitor.mParentTarget = nullptr;
aVisitor.SetParentTarget(nullptr, false);
return NS_OK;
}
@ -285,11 +285,13 @@ nsInProcessTabChildGlobal::GetEventTargetParent(EventChainPreVisitor& aVisitor)
(!mOwner || !nsContentUtils::IsInChromeDocshell(mOwner->OwnerDoc()))) {
if (mOwner) {
if (nsPIDOMWindowInner* innerWindow = mOwner->OwnerDoc()->GetInnerWindow()) {
aVisitor.mParentTarget = innerWindow->GetParentTarget();
// 'this' is already a "chrome handler", so we consider window's
// parent target to be part of that same part of the event path.
aVisitor.SetParentTarget(innerWindow->GetParentTarget(), false);
}
}
} else {
aVisitor.mParentTarget = mOwner;
aVisitor.SetParentTarget(mOwner, false);
}
return NS_OK;

Просмотреть файл

@ -174,7 +174,7 @@ nsWindowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor)
aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119
// To keep mWindow alive
aVisitor.mItemData = static_cast<nsISupports *>(mWindow);
aVisitor.mParentTarget = mParent;
aVisitor.SetParentTarget(mParent, false);
return NS_OK;
}

Просмотреть файл

@ -307,7 +307,7 @@ nsresult
DOMEventTargetHelper::GetEventTargetParent(EventChainPreVisitor& aVisitor)
{
aVisitor.mCanHandle = true;
aVisitor.mParentTarget = nullptr;
aVisitor.SetParentTarget(nullptr, false);
return NS_OK;
}

Просмотреть файл

@ -273,6 +273,12 @@ Event::GetCurrentTarget() const
return mEvent->GetCurrentDOMEventTarget();
}
void
Event::ComposedPath(nsTArray<RefPtr<EventTarget>>& aPath)
{
EventDispatcher::GetComposedPathFor(mEvent, aPath);
}
NS_IMETHODIMP
Event::GetCurrentTarget(nsIDOMEventTarget** aCurrentTarget)
{

Просмотреть файл

@ -158,6 +158,8 @@ public:
EventTarget* GetTarget() const;
EventTarget* GetCurrentTarget() const;
void ComposedPath(nsTArray<RefPtr<EventTarget>>& aPath);
uint16_t EventPhase() const;
// xpidl implementation

Просмотреть файл

@ -246,6 +246,36 @@ public:
return mFlags.mPreHandleEventOnly;
}
void SetRootOfClosedTree(bool aSet)
{
mFlags.mRootOfClosedTree = aSet;
}
bool IsRootOfClosedTree()
{
return mFlags.mRootOfClosedTree;
}
void SetIsSlotInClosedTree(bool aSet)
{
mFlags.mIsSlotInClosedTree = aSet;
}
bool IsSlotInClosedTree()
{
return mFlags.mIsSlotInClosedTree;
}
void SetIsChromeHandler(bool aSet)
{
mFlags.mIsChromeHandler = aSet;
}
bool IsChromeHandler()
{
return mFlags.mIsChromeHandler;
}
void SetMayHaveListenerManager(bool aMayHave)
{
mFlags.mMayHaveManager = aMayHave;
@ -344,6 +374,9 @@ private:
bool mIsChromeContent : 1;
bool mWantsPreHandleEvent : 1;
bool mPreHandleEventOnly : 1;
bool mRootOfClosedTree : 1;
bool mIsSlotInClosedTree : 1;
bool mIsChromeHandler : 1;
private:
typedef uint32_t RawFlags;
void SetRawFlags(RawFlags aRawFlags)
@ -390,6 +423,7 @@ EventTargetChainItem::GetEventTargetParent(EventChainPreVisitor& aVisitor)
SetMayHaveListenerManager(aVisitor.mMayHaveListenerManager);
SetWantsPreHandleEvent(aVisitor.mWantsPreHandleEvent);
SetPreHandleEventOnly(aVisitor.mWantsPreHandleEvent && !aVisitor.mCanHandle);
SetRootOfClosedTree(aVisitor.mRootOfClosedTree);
mItemFlags = aVisitor.mItemFlags;
mItemData = aVisitor.mItemData;
}
@ -770,16 +804,19 @@ EventDispatcher::Dispatch(nsISupports* aTarget,
targetEtci->SetNewTarget(t);
EventTargetChainItem* topEtci = targetEtci;
targetEtci = nullptr;
while (preVisitor.mParentTarget) {
EventTarget* parentTarget = preVisitor.mParentTarget;
while (preVisitor.GetParentTarget()) {
EventTarget* parentTarget = preVisitor.GetParentTarget();
EventTargetChainItem* parentEtci =
EventTargetChainItem::Create(chain, preVisitor.mParentTarget, topEtci);
EventTargetChainItem::Create(chain, parentTarget, topEtci);
if (!parentEtci->IsValid()) {
EventTargetChainItem::DestroyLast(chain, parentEtci);
rv = NS_ERROR_FAILURE;
break;
}
parentEtci->SetIsSlotInClosedTree(preVisitor.mParentIsSlotInClosedTree);
parentEtci->SetIsChromeHandler(preVisitor.mParentIsChromeHandler);
// Item needs event retargetting.
if (preVisitor.mEventTargetAtParent) {
// Need to set the target of the event
@ -823,8 +860,11 @@ EventDispatcher::Dispatch(nsISupports* aTarget,
}
// Handle the chain.
EventChainPostVisitor postVisitor(preVisitor);
MOZ_RELEASE_ASSERT(!aEvent->mPath);
aEvent->mPath = &chain;
EventTargetChainItem::HandleEventTargetChain(chain, postVisitor,
aCallback, cd);
aEvent->mPath = nullptr;
preVisitor.mEventStatus = postVisitor.mEventStatus;
// If the DOM event was created during event flow.
@ -1117,4 +1157,64 @@ EventDispatcher::CreateEvent(EventTarget* aOwner,
return nullptr;
}
// static
void
EventDispatcher::GetComposedPathFor(WidgetEvent* aEvent,
nsTArray<RefPtr<EventTarget>>& aPath)
{
nsTArray<EventTargetChainItem>* path = aEvent->mPath;
if (!path || path->IsEmpty() || !aEvent->mCurrentTarget) {
return;
}
EventTarget* currentTarget =
aEvent->mCurrentTarget->GetTargetForEventTargetChain();
if (!currentTarget) {
return;
}
AutoTArray<EventTarget*, 128> reversedComposedPath;
bool hasSeenCurrentTarget = false;
uint32_t hiddenSubtreeLevel = 0;
for (uint32_t i = path->Length(); i; ) {
--i;
EventTargetChainItem& item = path->ElementAt(i);
if (item.PreHandleEventOnly()) {
continue;
}
if (!hasSeenCurrentTarget && currentTarget == item.CurrentTarget()) {
hasSeenCurrentTarget = true;
} else if (hasSeenCurrentTarget && item.IsRootOfClosedTree()) {
++hiddenSubtreeLevel;
}
if (hiddenSubtreeLevel == 0) {
reversedComposedPath.AppendElement(item.CurrentTarget());
}
if (item.IsSlotInClosedTree() && hiddenSubtreeLevel > 0) {
--hiddenSubtreeLevel;
}
if (item.IsChromeHandler()) {
if (hasSeenCurrentTarget) {
// The current behavior is to include only EventTargets from
// either chrome side of event path or content side, not from both.
break;
}
// Need to start all over to collect the composed path on content side.
reversedComposedPath.Clear();
}
}
aPath.SetCapacity(reversedComposedPath.Length());
for (uint32_t i = reversedComposedPath.Length(); i; ) {
--i;
aPath.AppendElement(reversedComposedPath[i]->GetTargetForDOMEvent());
}
}
} // namespace mozilla

Просмотреть файл

@ -125,6 +125,9 @@ public:
, mWantsWillHandleEvent(false)
, mMayHaveListenerManager(true)
, mWantsPreHandleEvent(false)
, mRootOfClosedTree(false)
, mParentIsSlotInClosedTree(false)
, mParentIsChromeHandler(false)
, mParentTarget(nullptr)
, mEventTargetAtParent(nullptr)
{
@ -140,10 +143,26 @@ public:
mWantsWillHandleEvent = false;
mMayHaveListenerManager = true;
mWantsPreHandleEvent = false;
mRootOfClosedTree = false;
mParentIsSlotInClosedTree = false;
mParentIsChromeHandler = false;
mParentTarget = nullptr;
mEventTargetAtParent = nullptr;
}
dom::EventTarget* GetParentTarget()
{
return mParentTarget;
}
void SetParentTarget(dom::EventTarget* aParentTarget, bool aIsChromeHandler)
{
mParentTarget = aParentTarget;
if (mParentTarget) {
mParentIsChromeHandler = aIsChromeHandler;
}
}
/**
* 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
@ -195,11 +214,30 @@ public:
*/
bool mWantsPreHandleEvent;
/**
* True if the current target is either closed ShadowRoot or root of
* chrome only access tree (for example native anonymous content).
*/
bool mRootOfClosedTree;
/**
* True if mParentTarget is HTMLSlotElement in a closed shadow tree and the
* current target is assigned to that slot.
*/
bool mParentIsSlotInClosedTree;
/**
* True if mParentTarget is a chrome handler in the event path.
*/
bool mParentIsChromeHandler;
private:
/**
* Parent item in the event target chain.
*/
dom::EventTarget* mParentTarget;
public:
/**
* If the event needs to be retargeted, this is the event target,
* which should be used when the event is handled at mParentTarget.
@ -284,6 +322,9 @@ public:
dom::CallerType aCallerType =
dom::CallerType::System);
static void GetComposedPathFor(WidgetEvent* aEvent,
nsTArray<RefPtr<dom::EventTarget>>& aPath);
/**
* Called at shutting down.
*/

Просмотреть файл

@ -786,7 +786,7 @@ IDBFileHandle::GetEventTargetParent(EventChainPreVisitor& aVisitor)
AssertIsOnOwningThread();
aVisitor.mCanHandle = true;
aVisitor.mParentTarget = mMutableFile;
aVisitor.SetParentTarget(mMutableFile, false);
return NS_OK;
}

Просмотреть файл

@ -113,7 +113,7 @@ IDBFileRequest::GetEventTargetParent(EventChainPreVisitor& aVisitor)
AssertIsOnOwningThread();
aVisitor.mCanHandle = true;
aVisitor.mParentTarget = mFileHandle;
aVisitor.SetParentTarget(mFileHandle, false);
return NS_OK;
}

Просмотреть файл

@ -411,7 +411,7 @@ IDBRequest::GetEventTargetParent(EventChainPreVisitor& aVisitor)
AssertIsOnOwningThread();
aVisitor.mCanHandle = true;
aVisitor.mParentTarget = mTransaction;
aVisitor.SetParentTarget(mTransaction, false);
return NS_OK;
}

Просмотреть файл

@ -1054,7 +1054,7 @@ IDBTransaction::GetEventTargetParent(EventChainPreVisitor& aVisitor)
AssertIsOnOwningThread();
aVisitor.mCanHandle = true;
aVisitor.mParentTarget = mDatabase;
aVisitor.SetParentTarget(mDatabase, false);
return NS_OK;
}

Просмотреть файл

@ -20,6 +20,8 @@ interface Event {
[Pure]
readonly attribute EventTarget? currentTarget;
sequence<EventTarget> composedPath();
const unsigned short NONE = 0;
const unsigned short CAPTURING_PHASE = 1;
const unsigned short AT_TARGET = 2;

Просмотреть файл

@ -201,7 +201,7 @@ SharedWorker::GetEventTargetParent(EventChainPreVisitor& aVisitor)
QueueEvent(event);
aVisitor.mCanHandle = false;
aVisitor.mParentTarget = nullptr;
aVisitor.SetParentTarget(nullptr, false);
return NS_OK;
}

Просмотреть файл

@ -1291,7 +1291,7 @@ nsXULElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
if (IsEventStoppedFromAnonymousScrollbar(aVisitor.mEvent->mMessage)) {
// Don't propagate these events from native anonymous scrollbar.
aVisitor.mCanHandle = true;
aVisitor.mParentTarget = nullptr;
aVisitor.SetParentTarget(nullptr, false);
return NS_OK;
}
if (aVisitor.mEvent->mMessage == eXULCommand &&

Просмотреть файл

@ -1,13 +1,5 @@
[Extensions-to-Event-Interface.html]
type: testharness
[composedPath() must exist on Event]
expected: FAIL
[composedPath() must return an empty array when the event has not been dispatched]
expected: FAIL
[composedPath() must return an empty array when the event is no longer dispatched]
expected: FAIL
[The event must propagate out of open mode shadow boundaries when the composed flag is set]
expected: FAIL

Просмотреть файл

@ -1,7 +0,0 @@
[event-composed-path-after-dom-mutation.html]
[Event.composedPath() should return the same result even if DOM is mutated (1/2)]
expected: FAIL
[Event.composedPath() should return the same result even if DOM is mutated (2/2)]
expected: FAIL

Просмотреть файл

@ -1,9 +1,5 @@
[event-composed-path-with-related-target.html]
type: testharness
expected: ERROR
[Event path for an event with a relatedTarget. relatedTarget != target.]
expected: FAIL
[Event path for an event with a relatedTarget. Event shoul be dispatched if 1) target and relatedTarget are same, and 2) they are not in a shadow tree.]
expected: FAIL
@ -40,9 +36,6 @@
[Event path for an event with a relatedTarget. Event should be dispatched at every slots. relatedTarget should be correctly retargeted.]
expected: FAIL
[Event path for an event with a relatedTarget. Event should be dispatched even when target and relatedTarget are same.]
expected: FAIL
[Event path for an event with a relatedTarget which is identical to target. Event should be dispatched and should stop at the shadow root.]
expected: FAIL

Просмотреть файл

@ -1,36 +1,9 @@
[event-composed-path.html]
type: testharness
expected: ERROR
[Event Path without ShadowRoots.]
expected: FAIL
[Event Path with an open ShadowRoot.]
expected: FAIL
[Event Path with a closed ShadowRoot.]
expected: FAIL
[Event Path with nested ShadowRoots: open > open.]
expected: FAIL
[Event Path with nested ShadowRoots: open > closed.]
expected: FAIL
[Event Path with nested ShadowRoots: closed > open.]
expected: FAIL
[Event Path with nested ShadowRoots: closed > closed.]
expected: FAIL
[Event Path with a slot in an open Shadow Root.]
expected: FAIL
[Event Path with a slot in a closed Shadow Root.]
expected: FAIL
[Event Path with slots in nested ShadowRoots: open > open.]
expected: FAIL
[Event Path with slots in nested ShadowRoots: closed > closed.]
expected: FAIL

Просмотреть файл

@ -1,24 +0,0 @@
[event-composed.html]
type: testharness
expected: ERROR
[An event should be scoped by default]
expected: FAIL
[An event should not be scoped if composed is specified]
expected: FAIL
[A synthetic MouseEvent should be scoped by default]
expected: FAIL
[A synthetic MouseEvent with composed=true should not be scoped]
expected: FAIL
[A synthetic FocusEvent should be scoped by default]
expected: FAIL
[A synthetic FocusEvent with composed=true should not be scoped]
expected: FAIL
[A UA click event should not be scoped]
expected: FAIL

Просмотреть файл

@ -1,25 +1,11 @@
[event-post-dispatch.html]
[Event properties post dispatch without ShadowRoots (composed: true).]
expected: FAIL
[Event properties post dispatch without ShadowRoots (composed: false).]
expected: FAIL
[Event properties post dispatch with an open ShadowRoot (composed: true).]
expected: FAIL
[Event properties post dispatch with an open ShadowRoot (composed: false).]
expected: FAIL
[Event properties post dispatch with a closed ShadowRoot (composed: true).]
expected: FAIL
[Event properties post dispatch with a closed ShadowRoot (composed: false).]
expected: FAIL
[Event properties post dispatch with nested ShadowRoots (composed: true).]
expected: FAIL
[Event properties post dispatch with nested ShadowRoots (composed: false).]
expected: FAIL
@ -29,9 +15,6 @@
[Event properties post dispatch with relatedTarget in the same shadow tree. (composed: false)]
expected: FAIL
[Event properties post dispatch with relatedTarget in the document tree and the shadow tree. (composed: true)]
expected: FAIL
[Event properties post dispatch with relatedTarget in the document tree and the shadow tree. (composed: false)]
expected: FAIL

Просмотреть файл

@ -29,6 +29,8 @@ struct ParamTraits;
namespace mozilla {
class EventTargetChainItem;
/******************************************************************************
* mozilla::BaseEventFlags
*
@ -514,6 +516,7 @@ protected:
, mLastRefPoint(0, 0)
, mFocusSequenceNumber(0)
, mSpecifiedEventType(nullptr)
, mPath(nullptr)
{
MOZ_COUNT_CTOR(WidgetEvent);
mFlags.Clear();
@ -525,6 +528,7 @@ protected:
WidgetEvent()
: WidgetEventTime()
, mPath(nullptr)
{
MOZ_COUNT_CTOR(WidgetEvent);
}
@ -588,6 +592,8 @@ public:
nsCOMPtr<dom::EventTarget> mCurrentTarget;
nsCOMPtr<dom::EventTarget> mOriginalTarget;
nsTArray<EventTargetChainItem>* mPath;
dom::EventTarget* GetDOMEventTarget() const;
dom::EventTarget* GetCurrentDOMEventTarget() const;
dom::EventTarget* GetOriginalDOMEventTarget() const;