diff --git a/accessible/base/AccEvent.cpp b/accessible/base/AccEvent.cpp index d30a2bed507a..a27ceacb9f13 100644 --- a/accessible/base/AccEvent.cpp +++ b/accessible/base/AccEvent.cpp @@ -42,7 +42,23 @@ AccEvent::AccEvent(uint32_t aEventType, Accessible* aAccessible, //////////////////////////////////////////////////////////////////////////////// // AccEvent cycle collection -NS_IMPL_CYCLE_COLLECTION(AccEvent, mAccessible) +NS_IMPL_CYCLE_COLLECTION_CLASS(AccEvent) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AccEvent) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessible) + if (AccTreeMutationEvent* tmEvent = downcast_accEvent(tmp)) { + tmEvent->SetNextEvent(nullptr); + tmEvent->SetPrevEvent(nullptr); + } +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AccEvent) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessible) + if (AccTreeMutationEvent* tmEvent = downcast_accEvent(tmp)) { + CycleCollectionNoteChild(cb, tmEvent->NextEvent(), "mNext"); + CycleCollectionNoteChild(cb, tmEvent->PrevEvent(), "mPrevEvent"); + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AccEvent, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AccEvent, Release) diff --git a/accessible/base/AccEvent.h b/accessible/base/AccEvent.h index 4c7ec47eecaa..416224278905 100644 --- a/accessible/base/AccEvent.h +++ b/accessible/base/AccEvent.h @@ -94,6 +94,7 @@ public: eGenericEvent, eStateChangeEvent, eTextChangeEvent, + eTreeMutationEvent, eMutationEvent, eReorderEvent, eHideEvent, @@ -129,6 +130,7 @@ protected: friend class EventQueue; friend class EventTree; friend class ::nsEventShell; + friend class NotificationController; }; @@ -200,17 +202,51 @@ private: nsString mModifiedText; friend class EventTree; + friend class NotificationController; }; +/** + * A base class for events related to tree mutation, either an AccMutation + * event, or an AccReorderEvent. + */ +class AccTreeMutationEvent : public AccEvent +{ +public: + AccTreeMutationEvent(uint32_t aEventType, Accessible* aTarget) : + AccEvent(aEventType, aTarget, eAutoDetect, eCoalesceReorder), mGeneration(0) {} + + // Event + static const EventGroup kEventGroup = eTreeMutationEvent; + virtual unsigned int GetEventGroups() const override + { + return AccEvent::GetEventGroups() | (1U << eTreeMutationEvent); + } + + void SetNextEvent(AccTreeMutationEvent* aNext) { mNextEvent = aNext; } + void SetPrevEvent(AccTreeMutationEvent* aPrev) { mPrevEvent = aPrev; } + AccTreeMutationEvent* NextEvent() const { return mNextEvent; } + AccTreeMutationEvent* PrevEvent() const { return mPrevEvent; } + + /** + * A sequence number to know when this event was fired. + */ + uint32_t EventGeneration() const { return mGeneration; } + void SetEventGeneration(uint32_t aGeneration) { mGeneration = aGeneration; } + +private: + RefPtr mNextEvent; + RefPtr mPrevEvent; + uint32_t mGeneration; +}; /** * Base class for show and hide accessible events. */ -class AccMutationEvent: public AccEvent +class AccMutationEvent: public AccTreeMutationEvent { public: AccMutationEvent(uint32_t aEventType, Accessible* aTarget) : - AccEvent(aEventType, aTarget, eAutoDetect, eCoalesceReorder) + AccTreeMutationEvent(aEventType, aTarget) { // Don't coalesce these since they are coalesced by reorder event. Coalesce // contained text change events. @@ -222,7 +258,7 @@ public: static const EventGroup kEventGroup = eMutationEvent; virtual unsigned int GetEventGroups() const override { - return AccEvent::GetEventGroups() | (1U << eMutationEvent); + return AccTreeMutationEvent::GetEventGroups() | (1U << eMutationEvent); } // MutationEvent @@ -237,6 +273,7 @@ protected: RefPtr mTextChangeEvent; friend class EventTree; + friend class NotificationController; }; @@ -267,6 +304,7 @@ protected: RefPtr mPrevSibling; friend class EventTree; + friend class NotificationController; }; @@ -298,19 +336,18 @@ private: /** * Class for reorder accessible event. Takes care about */ -class AccReorderEvent : public AccEvent +class AccReorderEvent : public AccTreeMutationEvent { public: explicit AccReorderEvent(Accessible* aTarget) : - AccEvent(::nsIAccessibleEvent::EVENT_REORDER, aTarget, - eAutoDetect, eCoalesceReorder) { } + AccTreeMutationEvent(::nsIAccessibleEvent::EVENT_REORDER, aTarget) { } virtual ~AccReorderEvent() { } // Event static const EventGroup kEventGroup = eReorderEvent; virtual unsigned int GetEventGroups() const override { - return AccEvent::GetEventGroups() | (1U << eReorderEvent); + return AccTreeMutationEvent::GetEventGroups() | (1U << eReorderEvent); } }; diff --git a/accessible/base/EventTree.cpp b/accessible/base/EventTree.cpp index 596f1125d8ee..187eeb634c6b 100644 --- a/accessible/base/EventTree.cpp +++ b/accessible/base/EventTree.cpp @@ -25,7 +25,8 @@ EventTree* const TreeMutation::kNoEventTree = reinterpret_cast(-1); TreeMutation::TreeMutation(Accessible* aParent, bool aNoEvents) : mParent(aParent), mStartIdx(UINT32_MAX), mStateFlagsCopy(mParent->mStateFlags), - mEventTree(aNoEvents ? kNoEventTree : nullptr) + mEventTree(aNoEvents ? kNoEventTree : nullptr), + mQueueEvents(!aNoEvents) { #ifdef DEBUG mIsDone = false; @@ -62,17 +63,14 @@ TreeMutation::AfterInsertion(Accessible* aChild) mStartIdx = aChild->mIndexInParent + 1; } - if (!mEventTree) { - mEventTree = Controller()->QueueMutation(mParent); - if (!mEventTree) { - mEventTree = kNoEventTree; - } + if (!mQueueEvents) { + return; } - if (mEventTree != kNoEventTree) { - mEventTree->Shown(aChild); - Controller()->QueueNameChange(aChild); - } + RefPtr ev = new AccShowEvent(aChild); + DebugOnly added = Controller()->QueueMutationEvent(ev); + MOZ_ASSERT(added); + aChild->SetShowEventTarget(true); } void @@ -84,16 +82,13 @@ TreeMutation::BeforeRemoval(Accessible* aChild, bool aNoShutdown) mStartIdx = aChild->mIndexInParent; } - if (!mEventTree) { - mEventTree = Controller()->QueueMutation(mParent); - if (!mEventTree) { - mEventTree = kNoEventTree; - } + if (!mQueueEvents) { + return; } - if (mEventTree != kNoEventTree) { - mEventTree->Hidden(aChild, !aNoShutdown); - Controller()->QueueNameChange(aChild); + RefPtr ev = new AccHideEvent(aChild, !aNoShutdown); + if (Controller()->QueueMutationEvent(ev)) { + aChild->SetHideEventTarget(true); } } diff --git a/accessible/base/EventTree.h b/accessible/base/EventTree.h index a6ae8b1a1c55..d5be363a26e5 100644 --- a/accessible/base/EventTree.h +++ b/accessible/base/EventTree.h @@ -49,6 +49,11 @@ private: uint32_t mStateFlagsCopy; EventTree* mEventTree; + /* + * True if mutation events should be queued. + */ + bool mQueueEvents; + #ifdef DEBUG bool mIsDone; #endif @@ -67,15 +72,19 @@ public: mFireReorder(aFireReorder) { } ~EventTree() { Clear(); } - void Shown(Accessible* aChild); - - void Hidden(Accessible* aChild, bool aNeedsShutdown = true); + void Shown(Accessible* aTarget); + void Hidden(Accessible*, bool); /** * Return an event tree node for the given accessible. */ const EventTree* Find(const Accessible* aContainer) const; + /** + * Add a mutation event to this event tree. + */ + void Mutated(AccMutationEvent* aEv); + #ifdef A11Y_LOG void Log(uint32_t aLevel = UINT32_MAX) const; #endif @@ -91,7 +100,6 @@ private: */ EventTree* FindOrInsert(Accessible* aContainer); - void Mutated(AccMutationEvent* aEv); void Clear(); UniquePtr mFirst; diff --git a/accessible/base/NotificationController.cpp b/accessible/base/NotificationController.cpp index f5550234d3ae..3feb1c86fedf 100644 --- a/accessible/base/NotificationController.cpp +++ b/accessible/base/NotificationController.cpp @@ -24,7 +24,7 @@ using namespace mozilla::a11y; NotificationController::NotificationController(DocAccessible* aDocument, nsIPresShell* aPresShell) : EventQueue(aDocument), mObservingState(eNotObservingRefresh), - mPresShell(aPresShell) + mPresShell(aPresShell), mEventGeneration(0) { #ifdef DEBUG mMoveGuardOnStack = false; @@ -114,6 +114,289 @@ NotificationController::QueueMutation(Accessible* aContainer) return tree; } +bool +NotificationController::QueueMutationEvent(AccTreeMutationEvent* aEvent) +{ + // We have to allow there to be a hide and then a show event for a target + // because of targets getting moved. However we need to coalesce a show and + // then a hide for a target which means we need to check for that here. + if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE && + aEvent->GetAccessible()->ShowEventTarget()) { + AccTreeMutationEvent* showEvent = mMutationMap.GetEvent(aEvent->GetAccessible(), EventMap::ShowEvent); + DropMutationEvent(showEvent); + return false; + } + + AccMutationEvent* mutEvent = downcast_accEvent(aEvent); + mEventGeneration++; + mutEvent->SetEventGeneration(mEventGeneration); + + if (!mFirstMutationEvent) { + mFirstMutationEvent = aEvent; + ScheduleProcessing(); + } + + if (mLastMutationEvent) { + NS_ASSERTION(!mLastMutationEvent->NextEvent(), "why isn't the last event the end?"); + mLastMutationEvent->SetNextEvent(aEvent); + } + + aEvent->SetPrevEvent(mLastMutationEvent); + mLastMutationEvent = aEvent; + mMutationMap.PutEvent(aEvent); + + // Because we could be hiding the target of a show event we need to get rid + // of any such events. It may be possible to do less than coallesce all + // events, however that is easiest. + if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) { + CoalesceMutationEvents(); + + // mLastMutationEvent will point to something other than aEvent if and only + // if aEvent was just coalesced away. In that case a parent accessible + // must already have the required reorder and text change events so we are + // done here. + if (mLastMutationEvent != aEvent) { + return false; + } + } + + // We need to fire a reorder event after all of the events targeted at shown or + // hidden children of a container. So either queue a new one, or move an + // existing one to the end of the queue if the container already has a + // reorder event. + Accessible* target = aEvent->GetAccessible(); + Accessible* container = aEvent->GetAccessible()->Parent(); + RefPtr reorder; + if (!container->ReorderEventTarget()) { + reorder = new AccReorderEvent(container); + container->SetReorderEventTarget(true); + mMutationMap.PutEvent(reorder); + + // Since this is the first child of container that is changing, the name of + // container may be changing. + QueueNameChange(target); + } else { + AccReorderEvent* event = downcast_accEvent(mMutationMap.GetEvent(container, EventMap::ReorderEvent)); + reorder = event; + if (mFirstMutationEvent == event) { + mFirstMutationEvent = event->NextEvent(); + } else { + event->PrevEvent()->SetNextEvent(event->NextEvent()); + } + + event->NextEvent()->SetPrevEvent(event->PrevEvent()); + event->SetNextEvent(nullptr); + } + + reorder->SetEventGeneration(mEventGeneration); + reorder->SetPrevEvent(mLastMutationEvent); + mLastMutationEvent->SetNextEvent(reorder); + mLastMutationEvent = reorder; + + // It is not possible to have a text change event for something other than a + // hyper text accessible. + if (!container->IsHyperText()) { + return true; + } + + MOZ_ASSERT(mutEvent); + + nsString text; + aEvent->GetAccessible()->AppendTextTo(text); + if (text.IsEmpty()) { + return true; + } + + int32_t offset = container->AsHyperText()->GetChildOffset(target); + AccTreeMutationEvent* prevEvent = aEvent->PrevEvent(); + while (prevEvent && prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) { + prevEvent = prevEvent->PrevEvent(); + } + + if (prevEvent && prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE && + mutEvent->IsHide()) { + AccHideEvent* prevHide = downcast_accEvent(prevEvent); + AccTextChangeEvent* prevTextChange = prevHide->mTextChangeEvent; + if (prevTextChange) { + if (prevHide->mNextSibling == target) { + target->AppendTextTo(prevTextChange->mModifiedText); + } else if (prevHide->mPrevSibling == target) { + nsString temp; + target->AppendTextTo(temp); + + uint32_t extraLen = temp.Length(); + temp += prevTextChange->mModifiedText;; + prevTextChange->mModifiedText = temp; + prevTextChange->mStart -= extraLen; + } + + prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent); + } + } else if (prevEvent && mutEvent->IsShow() && + prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) { + AccShowEvent* prevShow = downcast_accEvent(prevEvent); + AccTextChangeEvent* prevTextChange = prevShow->mTextChangeEvent; + if (prevTextChange) { + int32_t index = target->IndexInParent(); + int32_t prevIndex = prevShow->GetAccessible()->IndexInParent(); + if (prevIndex + 1 == index) { + target->AppendTextTo(prevTextChange->mModifiedText); + } else if (index + 1 == prevIndex) { + nsString temp; + target->AppendTextTo(temp); + prevTextChange->mStart -= temp.Length(); + temp += prevTextChange->mModifiedText; + prevTextChange->mModifiedText = temp; + } + + prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent); + } + } + + if (!mutEvent->mTextChangeEvent) { + mutEvent->mTextChangeEvent = + new AccTextChangeEvent(container, offset, text, mutEvent->IsShow(), + aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput); + } + + return true; +} + +void +NotificationController::DropMutationEvent(AccTreeMutationEvent* aEvent) +{ + // unset the event bits since the event isn't being fired any more. + if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) { + aEvent->GetAccessible()->SetReorderEventTarget(false); + } else if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) { + aEvent->GetAccessible()->SetShowEventTarget(false); + } else { + AccHideEvent* hideEvent = downcast_accEvent(aEvent); + MOZ_ASSERT(hideEvent); + + if (hideEvent->NeedsShutdown()) { + mDocument->ShutdownChildrenInSubtree(aEvent->GetAccessible()); + } + } + + // Do the work to splice the event out of the list. + if (mFirstMutationEvent == aEvent) { + mFirstMutationEvent = aEvent->NextEvent(); + } else { + aEvent->PrevEvent()->SetNextEvent(aEvent->NextEvent()); + } + + if (mLastMutationEvent == aEvent) { + mLastMutationEvent = aEvent->PrevEvent(); + } else { + aEvent->NextEvent()->SetPrevEvent(aEvent->PrevEvent()); + } + + aEvent->SetPrevEvent(nullptr); + aEvent->SetNextEvent(nullptr); + mMutationMap.RemoveEvent(aEvent); +} + +void +NotificationController::CoalesceMutationEvents() +{ + AccTreeMutationEvent* event = mFirstMutationEvent; + while (event) { + AccTreeMutationEvent* nextEvent = event->NextEvent(); + uint32_t eventType = event->GetEventType(); + if (event->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) { + Accessible* acc = event->GetAccessible(); + while (acc) { + if (acc->IsDoc()) { + break; + } + + // if a parent of the reorder event's target is being hidden that + // hide event's target must have a parent that is also a reorder event + // target. That means we don't need this reorder event. + if (acc->HideEventTarget()) { + DropMutationEvent(event); + break; + } + + Accessible* parent = acc->Parent(); + if (parent->ReorderEventTarget()) { + AccReorderEvent* reorder = downcast_accEvent(mMutationMap.GetEvent(parent, EventMap::ReorderEvent)); + + // We want to make sure that a reorder event comes after any show or + // hide events targeted at the children of its target. We keep the + // invariant that event generation goes up as you are farther in the + // queue, so we want to use the spot of the event with the higher + // generation number, and keep that generation number. + if (reorder && reorder->EventGeneration() < event->EventGeneration()) { + // There really should be a show or hide event before the first + // reorder event. + if (reorder->PrevEvent()) { + reorder->PrevEvent()->SetNextEvent(reorder->NextEvent()); + } else { + mFirstMutationEvent = reorder->NextEvent(); + } + + reorder->NextEvent()->SetPrevEvent(reorder->PrevEvent()); + event->PrevEvent()->SetNextEvent(reorder); + reorder->SetPrevEvent(event->PrevEvent()); + event->SetPrevEvent(reorder); + reorder->SetNextEvent(event); + reorder->SetEventGeneration(event->EventGeneration()); + } + DropMutationEvent(event); + break; + } + + acc = parent; + } + } else if (eventType == nsIAccessibleEvent::EVENT_SHOW) { + Accessible* parent = event->GetAccessible()->Parent(); + while (parent) { + if (parent->IsDoc()) { + break; + } + + // if the parent of a show event is being either shown or hidden then + // we don't need to fire a show event for a subtree of that change. + if (parent->ShowEventTarget() || parent->HideEventTarget()) { + DropMutationEvent(event); + break; + } + + parent = parent->Parent(); + } + } else { + MOZ_ASSERT(eventType == nsIAccessibleEvent::EVENT_HIDE, "mutation event list has an invalid event"); + + AccHideEvent* hideEvent = downcast_accEvent(event); + Accessible* parent = hideEvent->Parent(); + while (parent) { + if (parent->IsDoc()) { + break; + } + + if (parent->HideEventTarget()) { + DropMutationEvent(event); + break; + } + + if (parent->ShowEventTarget()) { + AccShowEvent* showEvent = downcast_accEvent(mMutationMap.GetEvent(parent, EventMap::ShowEvent)); + if (showEvent->EventGeneration() < hideEvent->EventGeneration()) { + DropMutationEvent(hideEvent); + break; + } + } + + parent = parent->Parent(); + } + } + + event = nextEvent; + } +} + void NotificationController::ScheduleChildDocBinding(DocAccessible* aDocument) { @@ -172,6 +455,128 @@ NotificationController::IsUpdatePending() !mDocument->HasLoadState(DocAccessible::eTreeConstructed); } +void +NotificationController::ProcessMutationEvents() +{ + // there is no reason to fire a hide event for a child of a show event + // target. That can happen if something is inserted into the tree and + // removed before the next refresh driver tick, but it should not be + // observable outside gecko so it should be safe to coalesce away any such + // events. This means that it should be fine to fire all of the hide events + // first, and then deal with any shown subtrees. + for (AccTreeMutationEvent* event = mFirstMutationEvent; + event; event = event->NextEvent()) { + if (event->GetEventType() != nsIAccessibleEvent::EVENT_HIDE) { + continue; + } + + nsEventShell::FireEvent(event); + if (!mDocument) { + return; + } + + AccMutationEvent* mutEvent = downcast_accEvent(event); + if (mutEvent->mTextChangeEvent) { + nsEventShell::FireEvent(mutEvent->mTextChangeEvent); + if (!mDocument) { + return; + } + } + + // Fire menupopup end event before a hide event if a menu goes away. + + // XXX: We don't look into children of hidden subtree to find hiding + // menupopup (as we did prior bug 570275) because we don't do that when + // menu is showing (and that's impossible until bug 606924 is fixed). + // Nevertheless we should do this at least because layout coalesces + // the changes before our processing and we may miss some menupopup + // events. Now we just want to be consistent in content insertion/removal + // handling. + if (event->mAccessible->ARIARole() == roles::MENUPOPUP) { + nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, + event->mAccessible); + if (!mDocument) { + return; + } + } + + AccHideEvent* hideEvent = downcast_accEvent(event); + if (hideEvent->NeedsShutdown()) { + mDocument->ShutdownChildrenInSubtree(event->mAccessible); + } + } + + // Group the show events by the parent of their target. + nsDataHashtable, nsTArray> showEvents; + for (AccTreeMutationEvent* event = mFirstMutationEvent; + event; event = event->NextEvent()) { + if (event->GetEventType() != nsIAccessibleEvent::EVENT_SHOW) { + continue; + } + + Accessible* parent = event->GetAccessible()->Parent(); + showEvents.GetOrInsert(parent).AppendElement(event); + } + + // We need to fire show events for the children of an accessible in the order + // of their indices at this point. So sort each set of events for the same + // container by the index of their target. + for (auto iter = showEvents.Iter(); !iter.Done(); iter.Next()) { + struct AccIdxComparator { + bool LessThan(const AccTreeMutationEvent* a, const AccTreeMutationEvent* b) const + { + int32_t aIdx = a->GetAccessible()->IndexInParent(); + int32_t bIdx = b->GetAccessible()->IndexInParent(); + MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx); + return aIdx < bIdx; + } + bool Equals(const AccTreeMutationEvent* a, const AccTreeMutationEvent* b) const + { + DebugOnly aIdx = a->GetAccessible()->IndexInParent(); + DebugOnly bIdx = b->GetAccessible()->IndexInParent(); + MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx); + return false; + } + }; + + nsTArray& events = iter.Data(); + events.Sort(AccIdxComparator()); + for (AccTreeMutationEvent* event: events) { + nsEventShell::FireEvent(event); + if (!mDocument) { + return; + } + + AccMutationEvent* mutEvent = downcast_accEvent(event); + if (mutEvent->mTextChangeEvent) { + nsEventShell::FireEvent(mutEvent->mTextChangeEvent); + if (!mDocument) { + return; + } + } + } + } + + // Now we can fire the reorder events after all the show and hide events. + for (AccTreeMutationEvent* event = mFirstMutationEvent; + event; event = event->NextEvent()) { + if (event->GetEventType() != nsIAccessibleEvent::EVENT_REORDER) { + continue; + } + + nsEventShell::FireEvent(event); + if (!mDocument) { + return; + } + + Accessible* target = event->GetAccessible(); + target->Document()->MaybeNotifyOfValueChange(target); + if (!mDocument) { + return; + } + } +} + //////////////////////////////////////////////////////////////////////////////// // NotificationCollector: private @@ -397,9 +802,39 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime) // events causes script to run. mObservingState = eRefreshProcessing; - RefPtr deathGrip(mDocument); - mEventTree.Process(deathGrip); - deathGrip = nullptr; + CoalesceMutationEvents(); + ProcessMutationEvents(); + mEventGeneration = 0; + + // Now that we are done with them get rid of the events we fired. + RefPtr mutEvent = Move(mFirstMutationEvent); + mLastMutationEvent = nullptr; + mFirstMutationEvent = nullptr; + while (mutEvent) { + RefPtr nextEvent = mutEvent->NextEvent(); + Accessible* target = mutEvent->GetAccessible(); + + // We need to be careful here, while it may seem that we can simply 0 all + // the pending event bits that is not true. Because accessibles may be + // reparented they may be the target of both a hide event and a show event + // at the same time. + if (mutEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) { + target->SetShowEventTarget(false); + } + + if (mutEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) { + target->SetHideEventTarget(false); + } + + // However it is not possible for a reorder event target to also be the + // target of a show or hide, so we can just zero that. + target->SetReorderEventTarget(false); + + mutEvent->SetPrevEvent(nullptr); + mutEvent->SetNextEvent(nullptr); + mMutationMap.RemoveEvent(mutEvent); + mutEvent = nextEvent; + } ProcessEventQueue(); @@ -449,3 +884,52 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime) mObservingState = eNotObservingRefresh; } } + +void +NotificationController::EventMap::PutEvent(AccTreeMutationEvent* aEvent) +{ + EventType type = GetEventType(aEvent); + uint64_t addr = reinterpret_cast(aEvent->GetAccessible()); + MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned"); + addr |= type; + mTable.Put(addr, aEvent); +} + +AccTreeMutationEvent* +NotificationController::EventMap::GetEvent(Accessible* aTarget, EventType aType) +{ + uint64_t addr = reinterpret_cast(aTarget); + MOZ_ASSERT((addr & 0x3) == 0, "target is not 4 byte aligned"); + + addr |= aType; + return mTable.GetWeak(addr); +} + +void +NotificationController::EventMap::RemoveEvent(AccTreeMutationEvent* aEvent) +{ + EventType type = GetEventType(aEvent); + uint64_t addr = reinterpret_cast(aEvent->GetAccessible()); + MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned"); + addr |= type; + + MOZ_ASSERT(mTable.GetWeak(addr) == aEvent, "mTable has the wrong event"); + mTable.Remove(addr); +} + + NotificationController::EventMap::EventType +NotificationController::EventMap::GetEventType(AccTreeMutationEvent* aEvent) +{ + switch(aEvent->GetEventType()) + { + case nsIAccessibleEvent::EVENT_SHOW: + return ShowEvent; + case nsIAccessibleEvent::EVENT_HIDE: + return HideEvent; + case nsIAccessibleEvent::EVENT_REORDER: + return ReorderEvent; + default: + MOZ_ASSERT_UNREACHABLE("event has invalid type"); + return ShowEvent; + } +} diff --git a/accessible/base/NotificationController.h b/accessible/base/NotificationController.h index b5c8e7094e30..a909fc63d32a 100644 --- a/accessible/base/NotificationController.h +++ b/accessible/base/NotificationController.h @@ -159,6 +159,17 @@ public: const EventTree& RootEventTree() const { return mEventTree; }; #endif + /** + * Queue a mutation event to emit if not coalesced away. Returns true if the + * event was queued and has not yet been coalesced. + */ + bool QueueMutationEvent(AccTreeMutationEvent* aEvent); + + /** + * Coalesce all queued mutation events. + */ + void CoalesceMutationEvents(); + /** * Schedule binding the child document to the tree of this document. */ @@ -291,6 +302,16 @@ private: } private: + /** + * get rid of a mutation event that is no longer necessary. + */ + void DropMutationEvent(AccTreeMutationEvent* aEvent); + + /** + * Fire all necessary mutation events. + */ + void ProcessMutationEvents(); + /** * Indicates whether we're waiting on an event queue processing from our * notification controller to flush events. @@ -376,6 +397,40 @@ private: friend class MoveGuard; friend class EventTree; + + /** + * A list of all mutation events we may want to emit. Ordered from the first + * event that should be emitted to the last one to emit. + */ + RefPtr mFirstMutationEvent; + RefPtr mLastMutationEvent; + + /** + * A class to map an accessible and event type to an event. + */ + class EventMap + { + public: + enum EventType + { + ShowEvent = 0x0, + HideEvent = 0x1, + ReorderEvent = 0x2, + }; + + void PutEvent(AccTreeMutationEvent* aEvent); + AccTreeMutationEvent* GetEvent(Accessible* aTarget, EventType aType); + void RemoveEvent(AccTreeMutationEvent* aEvent); + void Clear() { mTable.Clear(); } + + private: + EventType GetEventType(AccTreeMutationEvent* aEvent); + + nsRefPtrHashtable mTable; + }; + + EventMap mMutationMap; + uint32_t mEventGeneration; }; } // namespace a11y diff --git a/accessible/generic/Accessible.cpp b/accessible/generic/Accessible.cpp index 73bb0f360156..f3f3fe5efc41 100644 --- a/accessible/generic/Accessible.cpp +++ b/accessible/generic/Accessible.cpp @@ -109,7 +109,8 @@ Accessible::Accessible(nsIContent* aContent, DocAccessible* aDoc) : mContent(aContent), mDoc(aDoc), mParent(nullptr), mIndexInParent(-1), mRoleMapEntryIndex(aria::NO_ROLE_MAP_ENTRY_INDEX), - mStateFlags(0), mContextFlags(0), mType(0), mGenericTypes(0) + mStateFlags(0), mContextFlags(0), mType(0), mGenericTypes(0), + mReorderEventTarget(false), mShowEventTarget(false), mHideEventTarget(false) { mBits.groupInfo = nullptr; mInt.mIndexOfEmbeddedChild = -1; @@ -2157,9 +2158,9 @@ Accessible::MoveChild(uint32_t aNewIndex, Accessible* aChild) "No move, same index"); MOZ_ASSERT(aNewIndex <= mChildren.Length(), "Wrong new index was given"); - EventTree* eventTree = mDoc->Controller()->QueueMutation(this); - if (eventTree) { - eventTree->Hidden(aChild, false); + RefPtr hideEvent = new AccHideEvent(aChild, false); + if (mDoc->Controller()->QueueMutationEvent(hideEvent)) { + aChild->SetHideEventTarget(true); } mEmbeddedObjCollector = nullptr; @@ -2191,10 +2192,10 @@ Accessible::MoveChild(uint32_t aNewIndex, Accessible* aChild) mChildren[idx]->mInt.mIndexOfEmbeddedChild = -1; } - if (eventTree) { - eventTree->Shown(aChild); - mDoc->Controller()->QueueNameChange(aChild); - } + RefPtr showEvent = new AccShowEvent(aChild); + DebugOnly added = mDoc->Controller()->QueueMutationEvent(showEvent); + MOZ_ASSERT(added); + aChild->SetShowEventTarget(true); } Accessible* diff --git a/accessible/generic/Accessible.h b/accessible/generic/Accessible.h index 7b49672cdaf6..493db20cf5e7 100644 --- a/accessible/generic/Accessible.h +++ b/accessible/generic/Accessible.h @@ -956,6 +956,36 @@ public: */ bool IsInsideAlert() const { return mContextFlags & eInsideAlert; } + /** + * Return true if there is a pending reorder event for this accessible. + */ + bool ReorderEventTarget() const { return mReorderEventTarget; } + + /** + * Return true if there is a pending show event for this accessible. + */ + bool ShowEventTarget() const { return mShowEventTarget; } + + /** + * Return true if there is a pending hide event for this accessible. + */ + bool HideEventTarget() const { return mHideEventTarget; } + + /** + * Set if there is a pending reorder event for this accessible. + */ + void SetReorderEventTarget(bool aTarget) { mReorderEventTarget = aTarget; } + + /** + * Set if this accessible is a show event target. + */ + void SetShowEventTarget(bool aTarget) { mShowEventTarget = aTarget; } + + /** + * Set if this accessible is a hide event target. + */ + void SetHideEventTarget(bool aTarget) { mHideEventTarget = aTarget; } + protected: virtual ~Accessible(); @@ -1132,6 +1162,9 @@ protected: uint32_t mContextFlags : kContextFlagsBits; uint32_t mType : kTypeBits; uint32_t mGenericTypes : kGenericTypesBits; + uint32_t mReorderEventTarget : 1; + uint32_t mShowEventTarget : 1; + uint32_t mHideEventTarget : 1; void StaticAsserts() const; diff --git a/accessible/generic/DocAccessible.cpp b/accessible/generic/DocAccessible.cpp index 0747ba105594..056c80f68223 100644 --- a/accessible/generic/DocAccessible.cpp +++ b/accessible/generic/DocAccessible.cpp @@ -1860,8 +1860,8 @@ DocAccessible::ProcessContentInserted(Accessible* aContainer, "container", aContainer, "child", iter.Child(), nullptr); #endif - mt.AfterInsertion(iter.Child()); CreateSubtree(iter.Child()); + mt.AfterInsertion(iter.Child()); continue; } @@ -1907,10 +1907,10 @@ DocAccessible::ProcessContentInserted(Accessible* aContainer, nsIContent* aNode) if (!aContainer->InsertAfter(child, walker.Prev())) { return; } + CreateSubtree(child); mt.AfterInsertion(child); mt.Done(); - CreateSubtree(child); FireEventsOnInsertion(aContainer); } } diff --git a/accessible/ipc/DocAccessibleChildBase.cpp b/accessible/ipc/DocAccessibleChildBase.cpp index 8750589f4591..83c91a81073e 100644 --- a/accessible/ipc/DocAccessibleChildBase.cpp +++ b/accessible/ipc/DocAccessibleChildBase.cpp @@ -85,7 +85,7 @@ DocAccessibleChildBase::ShowEvent(AccShowEvent* aShowEvent) { Accessible* parent = aShowEvent->Parent(); uint64_t parentID = parent->IsDoc() ? 0 : reinterpret_cast(parent->UniqueID()); - uint32_t idxInParent = aShowEvent->InsertionIndex(); + uint32_t idxInParent = aShowEvent->GetAccessible()->IndexInParent(); nsTArray shownTree; ShowEventData data(parentID, idxInParent, shownTree); SerializeTree(aShowEvent->GetAccessible(), data.NewTree()); diff --git a/accessible/tests/mochitest/events.js b/accessible/tests/mochitest/events.js index 08d04e18dc76..33a8d273129a 100644 --- a/accessible/tests/mochitest/events.js +++ b/accessible/tests/mochitest/events.js @@ -1705,7 +1705,9 @@ function invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg, aIsAsync) */ function orderChecker() { - this.__proto__ = new invokerChecker(null, null, null, false); + // XXX it doesn't actually work to inherit from invokerChecker, but maybe we + // should fix that? + // this.__proto__ = new invokerChecker(null, null, null, false); } /** diff --git a/accessible/tests/mochitest/events/test_aria_menu.html b/accessible/tests/mochitest/events/test_aria_menu.html index 4cd1199f551c..5ac595ebf089 100644 --- a/accessible/tests/mochitest/events/test_aria_menu.html +++ b/accessible/tests/mochitest/events/test_aria_menu.html @@ -55,8 +55,8 @@ // related show event. this.eventSeq = [ new invokerChecker(EVENT_SHOW, this.menuNode), - new asyncInvokerChecker(EVENT_MENUPOPUP_START, this.menuNode), - new invokerChecker(EVENT_REORDER, getNode(aParentMenuID)) + new invokerChecker(EVENT_REORDER, getNode(aParentMenuID)), + new invokerChecker(EVENT_MENUPOPUP_START, this.menuNode) ]; this.invoke = function showMenu_invoke() diff --git a/accessible/tests/mochitest/events/test_coalescence.html b/accessible/tests/mochitest/events/test_coalescence.html index e3ff6834db23..d95ef99b039e 100644 --- a/accessible/tests/mochitest/events/test_coalescence.html +++ b/accessible/tests/mochitest/events/test_coalescence.html @@ -419,8 +419,8 @@ this.lb = getAccessible("t5_lb"); this.eventSeq = [ - new invokerChecker(EVENT_HIDE, this.o), new invokerChecker(EVENT_HIDE, this.b), + new invokerChecker(EVENT_HIDE, this.o), new invokerChecker(EVENT_REORDER, "t5"), new unexpectedInvokerChecker(EVENT_HIDE, this.ofc), new unexpectedInvokerChecker(EVENT_REORDER, this.o), @@ -539,8 +539,8 @@ new invokerChecker(EVENT_HIDE, getNode('t8_c1_child')), new invokerChecker(EVENT_HIDE, 't8_c2_moved'), new invokerChecker(EVENT_SHOW, 't8_c2_moved'), + new invokerChecker(EVENT_REORDER, 't8_c2'), new invokerChecker(EVENT_REORDER, 't8_c1'), - new invokerChecker(EVENT_REORDER, 't8_c2') ]; this.invoke = function test8_invoke() @@ -578,13 +578,13 @@ { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getNode('t9_c1_child')), + new invokerChecker(EVENT_HIDE, getNode('t9_c2_child')), new invokerChecker(EVENT_HIDE, 't9_c3_moved'), new invokerChecker(EVENT_HIDE, 't9_c2_moved'), new invokerChecker(EVENT_SHOW, 't9_c2_moved'), - new invokerChecker(EVENT_REORDER, 't9_c1'), - new invokerChecker(EVENT_HIDE, getNode('t9_c2_child')), - new invokerChecker(EVENT_REORDER, 't9_c2'), new invokerChecker(EVENT_REORDER, 't9_c3'), + new invokerChecker(EVENT_REORDER, 't9_c2'), + new invokerChecker(EVENT_REORDER, 't9_c1'), new unexpectedInvokerChecker(EVENT_SHOW, 't9_c3_moved') ]; @@ -624,12 +624,12 @@ { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getNode('t10_c1_child')), + new invokerChecker(EVENT_HIDE, getNode('t10_c2_child')), new invokerChecker(EVENT_HIDE, getNode('t10_c2_moved')), new invokerChecker(EVENT_HIDE, getNode('t10_c3_moved')), new invokerChecker(EVENT_SHOW, getNode('t10_c2_moved')), - new invokerChecker(EVENT_REORDER, 't10_c1'), - new invokerChecker(EVENT_HIDE, getNode('t10_c2_child')), new invokerChecker(EVENT_REORDER, 't10_c2'), + new invokerChecker(EVENT_REORDER, 't10_c1'), new invokerChecker(EVENT_REORDER, 't10_c3') ]; @@ -659,9 +659,11 @@ { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getNode('t11_c1_child')), - new invokerChecker(EVENT_SHOW, 't11_c2_child'), new invokerChecker(EVENT_HIDE, getNode('t11_c2')), - new invokerChecker(EVENT_SHOW, 't11_c2'), + new orderChecker(), + new asyncInvokerChecker(EVENT_SHOW, 't11_c2_child'), + new asyncInvokerChecker(EVENT_SHOW, 't11_c2'), + new orderChecker(), new invokerChecker(EVENT_REORDER, 't11'), new unexpectedInvokerChecker(EVENT_HIDE, 't11_c2_child'), new unexpectedInvokerChecker(EVENT_REORDER, 't11_c1'), @@ -689,7 +691,7 @@ //////////////////////////////////////////////////////////////////////////// // Do tests. - //gA11yEventDumpToConsole = true; // debug stuff + gA11yEventDumpToConsole = true; // debug stuff //enableLogging("eventTree"); var gQueue = null; diff --git a/accessible/tests/mochitest/events/test_mutation.html b/accessible/tests/mochitest/events/test_mutation.html index 369f7ba2e526..232a0972777f 100644 --- a/accessible/tests/mochitest/events/test_mutation.html +++ b/accessible/tests/mochitest/events/test_mutation.html @@ -341,8 +341,8 @@ this.containerNode = getNode(aContainerID); this.eventSeq = [ - new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.lastChild; }, this.containerNode), new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.firstChild; }, this.containerNode), + new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.lastChild; }, this.containerNode), new invokerChecker(EVENT_REORDER, this.containerNode) ]; diff --git a/accessible/tests/mochitest/treeupdate/test_ariaowns.html b/accessible/tests/mochitest/treeupdate/test_ariaowns.html index 92d1e9148666..9cecb90a167c 100644 --- a/accessible/tests/mochitest/treeupdate/test_ariaowns.html +++ b/accessible/tests/mochitest/treeupdate/test_ariaowns.html @@ -27,11 +27,11 @@ { this.eventSeq = [ new invokerChecker(EVENT_HIDE, getNode("t1_button")), - new invokerChecker(EVENT_SHOW, getNode("t1_button")), - new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")), // no hide for t1_subdiv because it is contained by hidden t1_checkbox new invokerChecker(EVENT_HIDE, getNode("t1_checkbox")), new invokerChecker(EVENT_SHOW, getNode("t1_checkbox")), + new invokerChecker(EVENT_SHOW, getNode("t1_button")), + new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")), new invokerChecker(EVENT_REORDER, getNode("t1_container")) ]; @@ -73,10 +73,12 @@ function removeARIAOwns() { this.eventSeq = [ - new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")), - new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")), new invokerChecker(EVENT_HIDE, getNode("t1_button")), - new invokerChecker(EVENT_SHOW, getNode("t1_button")), + new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")), + new orderChecker(), + new asyncInvokerChecker(EVENT_SHOW, getNode("t1_button")), + new asyncInvokerChecker(EVENT_SHOW, getNode("t1_subdiv")), + new orderChecker(), new invokerChecker(EVENT_REORDER, getNode("t1_container")), new unexpectedInvokerChecker(EVENT_REORDER, getNode("t1_checkbox")) ]; @@ -108,8 +110,8 @@ function setARIAOwns() { this.eventSeq = [ - new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")), new invokerChecker(EVENT_HIDE, getNode("t1_button")), + new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")), new invokerChecker(EVENT_SHOW, getNode("t1_button")), new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")), new invokerChecker(EVENT_REORDER, getNode("t1_container")) @@ -447,6 +449,9 @@ this.eventSeq = []; for (var id of aIdList) { this.eventSeq.push(new invokerChecker(EVENT_HIDE, getNode(id))); + } + + for (var id of aIdList) { this.eventSeq.push(new invokerChecker(EVENT_SHOW, getNode(id))); } this.eventSeq.push(new invokerChecker(EVENT_REORDER, getNode(aContainer))); diff --git a/accessible/tests/mochitest/treeupdate/test_bug1189277.html b/accessible/tests/mochitest/treeupdate/test_bug1189277.html index 13ce50f2b3c2..9c995d49ed97 100644 --- a/accessible/tests/mochitest/treeupdate/test_bug1189277.html +++ b/accessible/tests/mochitest/treeupdate/test_bug1189277.html @@ -21,9 +21,9 @@ this.containerNode = getNode("outerDiv"); this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("child")), new invokerChecker(EVENT_HIDE, getNode("childDoc")), new invokerChecker(EVENT_SHOW, "newChildDoc"), - new invokerChecker(EVENT_HIDE, getNode("child")), new invokerChecker(EVENT_REORDER, this.containerNode) ]; @@ -47,7 +47,7 @@ } //enableLogging("tree"); - //gA11yEventDumpToConsole = true; + gA11yEventDumpToConsole = true; var gQueue = null; function doTest() { diff --git a/accessible/tests/mochitest/treeupdate/test_whitespace.html b/accessible/tests/mochitest/treeupdate/test_whitespace.html index b019268fa194..e7ba9b059051 100644 --- a/accessible/tests/mochitest/treeupdate/test_whitespace.html +++ b/accessible/tests/mochitest/treeupdate/test_whitespace.html @@ -101,8 +101,9 @@ this.imgNode.setAttribute("src", "../moz.png"); this.eventSeq = [ - new invokerChecker(EVENT_SHOW, getAccessible, this.imgNode), - new invokerChecker(EVENT_SHOW, getAccessible, this.textNode), + new asyncInvokerChecker(EVENT_SHOW, getAccessible, this.textNode), + new asyncInvokerChecker(EVENT_SHOW, getAccessible, this.imgNode), + new orderChecker(), new invokerChecker(EVENT_REORDER, this.topNode) ]; diff --git a/devtools/client/inspector/rules/views/text-property-editor.js b/devtools/client/inspector/rules/views/text-property-editor.js index 595b12272604..d3015f9310dd 100644 --- a/devtools/client/inspector/rules/views/text-property-editor.js +++ b/devtools/client/inspector/rules/views/text-property-editor.js @@ -735,7 +735,7 @@ TextPropertyEditor.prototype = { return; } - if (this.isDisplayGrid) { + if (this.isDisplayGrid()) { this.ruleView.highlighters._hideGridHighlighter(); } diff --git a/devtools/client/netmonitor/test/browser.ini b/devtools/client/netmonitor/test/browser.ini index fcd8241789ff..571e41d3b4ad 100644 --- a/devtools/client/netmonitor/test/browser.ini +++ b/devtools/client/netmonitor/test/browser.ini @@ -7,6 +7,7 @@ support-files = html_cause-test-page.html html_content-type-test-page.html html_content-type-without-cache-test-page.html + html_brotli-test-page.html html_image-tooltip-test-page.html html_cors-test-page.html html_custom-get-page.html @@ -68,6 +69,7 @@ skip-if = true # Bug 1309191 - replace with rewritten version in React [browser_net_clear.js] [browser_net_complex-params.js] [browser_net_content-type.js] +[browser_net_brotli.js] [browser_net_curl-utils.js] [browser_net_copy_image_as_data_uri.js] subsuite = clipboard diff --git a/devtools/client/netmonitor/test/browser_net_brotli.js b/devtools/client/netmonitor/test/browser_net_brotli.js new file mode 100644 index 000000000000..cc6908d68aed --- /dev/null +++ b/devtools/client/netmonitor/test/browser_net_brotli.js @@ -0,0 +1,91 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const BROTLI_URL = HTTPS_EXAMPLE_URL + "html_brotli-test-page.html"; +const BROTLI_REQUESTS = 1; + +/** + * Test brotli encoded response is handled correctly on HTTPS. + */ + +add_task(function* () { + let { L10N } = require("devtools/client/netmonitor/l10n"); + + let { tab, monitor } = yield initNetMonitor(BROTLI_URL); + info("Starting test... "); + + let { document, Editor, NetMonitorView } = monitor.panelWin; + let { RequestsMenu } = NetMonitorView; + + RequestsMenu.lazyUpdate = false; + + let wait = waitForNetworkEvents(monitor, BROTLI_REQUESTS); + yield ContentTask.spawn(tab.linkedBrowser, {}, function* () { + content.wrappedJSObject.performRequests(); + }); + yield wait; + + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0), + "GET", HTTPS_CONTENT_TYPE_SJS + "?fmt=br", { + status: 200, + statusText: "Connected", + type: "plain", + fullMimeType: "text/plain", + transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 10), + size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 64), + time: true + }); + + let onEvent = waitForResponseBodyDisplayed(); + EventUtils.sendMouseEvent({ type: "mousedown" }, + document.getElementById("details-pane-toggle")); + EventUtils.sendMouseEvent({ type: "mousedown" }, + document.querySelectorAll("#details-pane tab")[3]); + yield onEvent; + yield testResponseTab("br"); + + yield teardown(monitor); + + function* testResponseTab(type) { + let tabEl = document.querySelectorAll("#details-pane tab")[3]; + let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3]; + + is(tabEl.getAttribute("selected"), "true", + "The response tab in the network details pane should be selected."); + + function checkVisibility(box) { + is(tabpanel.querySelector("#response-content-info-header") + .hasAttribute("hidden"), true, + "The response info header doesn't have the intended visibility."); + is(tabpanel.querySelector("#response-content-json-box") + .hasAttribute("hidden"), box != "json", + "The response content json box doesn't have the intended visibility."); + is(tabpanel.querySelector("#response-content-textarea-box") + .hasAttribute("hidden"), box != "textarea", + "The response content textarea box doesn't have the intended visibility."); + is(tabpanel.querySelector("#response-content-image-box") + .hasAttribute("hidden"), box != "image", + "The response content image box doesn't have the intended visibility."); + } + + switch (type) { + case "br": { + checkVisibility("textarea"); + + let expected = "X".repeat(64); + let editor = yield NetMonitorView.editor("#response-content-textarea"); + is(editor.getText(), expected, + "The text shown in the source editor is incorrect for the brotli request."); + is(editor.getMode(), Editor.modes.text, + "The mode active in the source editor is incorrect for the brotli request."); + break; + } + } + } + + function waitForResponseBodyDisplayed() { + return monitor.panelWin.once(monitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED); + } +}); diff --git a/devtools/client/netmonitor/test/browser_net_content-type.js b/devtools/client/netmonitor/test/browser_net_content-type.js index b8670ff3f026..1951bc69d579 100644 --- a/devtools/client/netmonitor/test/browser_net_content-type.js +++ b/devtools/client/netmonitor/test/browser_net_content-type.js @@ -7,16 +7,10 @@ * Tests if different response content types are handled correctly. */ -function* content_type_test(isHTTPS) { +add_task(function* () { let { L10N } = require("devtools/client/netmonitor/l10n"); - let pageURL = isHTTPS ? HTTPS_CONTENT_TYPE_WITHOUT_CACHE_URL - : CONTENT_TYPE_WITHOUT_CACHE_URL; - let imageURL = isHTTPS ? HTTPS_TEST_IMAGE - : TEST_IMAGE; - let sjsURL = isHTTPS ? HTTPS_CONTENT_TYPE_SJS - : CONTENT_TYPE_SJS; - let { tab, monitor } = yield initNetMonitor(pageURL); + let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL); info("Starting test... "); let { document, Editor, NetMonitorView } = monitor.panelWin; @@ -30,89 +24,71 @@ function* content_type_test(isHTTPS) { }); yield wait; - let okStatus = isHTTPS ? "Connected" : "OK"; - verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0), - "GET", sjsURL + "?fmt=xml", { + "GET", CONTENT_TYPE_SJS + "?fmt=xml", { status: 200, - statusText: okStatus, + statusText: "OK", type: "xml", fullMimeType: "text/xml; charset=utf-8", size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 42), time: true }); verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1), - "GET", sjsURL + "?fmt=css", { + "GET", CONTENT_TYPE_SJS + "?fmt=css", { status: 200, - statusText: okStatus, + statusText: "OK", type: "css", fullMimeType: "text/css; charset=utf-8", size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 34), time: true }); verifyRequestItemTarget(RequestsMenu.getItemAtIndex(2), - "GET", sjsURL + "?fmt=js", { + "GET", CONTENT_TYPE_SJS + "?fmt=js", { status: 200, - statusText: okStatus, + statusText: "OK", type: "js", fullMimeType: "application/javascript; charset=utf-8", size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 34), time: true }); verifyRequestItemTarget(RequestsMenu.getItemAtIndex(3), - "GET", sjsURL + "?fmt=json", { + "GET", CONTENT_TYPE_SJS + "?fmt=json", { status: 200, - statusText: okStatus, + statusText: "OK", type: "json", fullMimeType: "application/json; charset=utf-8", size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29), time: true }); - if (!isHTTPS) { - // 404 doesn't work on HTTPS test harness. - verifyRequestItemTarget(RequestsMenu.getItemAtIndex(4), - "GET", sjsURL + "?fmt=bogus", { - status: 404, - statusText: "Not Found", - type: "html", - fullMimeType: "text/html; charset=utf-8", - size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 24), - time: true - }); - } + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(4), + "GET", CONTENT_TYPE_SJS + "?fmt=bogus", { + status: 404, + statusText: "Not Found", + type: "html", + fullMimeType: "text/html; charset=utf-8", + size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 24), + time: true + }); verifyRequestItemTarget(RequestsMenu.getItemAtIndex(5), - "GET", imageURL, { + "GET", TEST_IMAGE, { fuzzyUrl: true, status: 200, - statusText: okStatus, + statusText: "OK", type: "png", fullMimeType: "image/png", size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 580), time: true }); verifyRequestItemTarget(RequestsMenu.getItemAtIndex(6), - "GET", sjsURL + "?fmt=gzip", { + "GET", CONTENT_TYPE_SJS + "?fmt=gzip", { status: 200, - statusText: okStatus, + statusText: "OK", type: "plain", fullMimeType: "text/plain", transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 73), size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 10.73), time: true }); - if (isHTTPS) { - // Brotli is enabled only on HTTPS. - verifyRequestItemTarget(RequestsMenu.getItemAtIndex(6), - "GET", sjsURL + "?fmt=gzip", { - status: 200, - statusText: okStatus, - type: "plain", - fullMimeType: "text/plain", - transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 73), - size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 10.73), - time: true - }); - } let onEvent = waitForResponseBodyDisplayed(); EventUtils.sendMouseEvent({ type: "mousedown" }, @@ -140,11 +116,6 @@ function* content_type_test(isHTTPS) { yield selectIndexAndWaitForTabUpdated(6); yield testResponseTab("gzip"); - if (isHTTPS) { - yield selectIndexAndWaitForTabUpdated(7); - yield testResponseTab("br"); - } - yield teardown(monitor); function* testResponseTab(type) { @@ -269,17 +240,6 @@ function* content_type_test(isHTTPS) { "The mode active in the source editor is incorrect for the gzip request."); break; } - case "br": { - checkVisibility("textarea"); - - let expected = "X".repeat(64); - let editor = yield NetMonitorView.editor("#response-content-textarea"); - is(editor.getText(), expected, - "The text shown in the source editor is incorrect for the brotli request."); - is(editor.getMode(), Editor.modes.text, - "The mode active in the source editor is incorrect for the brotli request."); - break; - } } } @@ -292,12 +252,4 @@ function* content_type_test(isHTTPS) { function waitForResponseBodyDisplayed() { return monitor.panelWin.once(monitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED); } -} - -add_task(function* () { - yield* content_type_test(false); -}); - -add_task(function* () { - yield* content_type_test(true); }); diff --git a/devtools/client/netmonitor/test/head.js b/devtools/client/netmonitor/test/head.js index 6265b6b09f6a..d733cc1d4378 100644 --- a/devtools/client/netmonitor/test/head.js +++ b/devtools/client/netmonitor/test/head.js @@ -20,7 +20,6 @@ const SIMPLE_URL = EXAMPLE_URL + "html_simple-test-page.html"; const NAVIGATE_URL = EXAMPLE_URL + "html_navigate-test-page.html"; const CONTENT_TYPE_URL = EXAMPLE_URL + "html_content-type-test-page.html"; const CONTENT_TYPE_WITHOUT_CACHE_URL = EXAMPLE_URL + "html_content-type-without-cache-test-page.html"; -const HTTPS_CONTENT_TYPE_WITHOUT_CACHE_URL = HTTPS_EXAMPLE_URL + "html_content-type-without-cache-test-page.html"; const CONTENT_TYPE_WITHOUT_CACHE_REQUESTS = 8; const CYRILLIC_URL = EXAMPLE_URL + "html_cyrillic-test-page.html"; const STATUS_CODES_URL = EXAMPLE_URL + "html_status-codes-test-page.html"; @@ -58,7 +57,6 @@ const HSTS_BASE_URL = EXAMPLE_URL; const HSTS_PAGE_URL = CUSTOM_GET_URL; const TEST_IMAGE = EXAMPLE_URL + "test-image.png"; -const HTTPS_TEST_IMAGE = HTTPS_EXAMPLE_URL + "test-image.png"; const TEST_IMAGE_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="; const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js"; diff --git a/devtools/client/netmonitor/test/html_brotli-test-page.html b/devtools/client/netmonitor/test/html_brotli-test-page.html new file mode 100644 index 000000000000..d5afae4b3159 --- /dev/null +++ b/devtools/client/netmonitor/test/html_brotli-test-page.html @@ -0,0 +1,38 @@ + + + + + + + + + + Network Monitor test page + + + +

Brotli test

+ + + + + diff --git a/devtools/client/webconsole/moz.build b/devtools/client/webconsole/moz.build index c8324b315e14..47e0af195bdb 100644 --- a/devtools/client/webconsole/moz.build +++ b/devtools/client/webconsole/moz.build @@ -18,5 +18,6 @@ DevToolsModules( 'jsterm.js', 'panel.js', 'utils.js', + 'webconsole-connection-proxy.js', 'webconsole.js', ) diff --git a/devtools/client/webconsole/webconsole-connection-proxy.js b/devtools/client/webconsole/webconsole-connection-proxy.js new file mode 100644 index 000000000000..f0502a88b27f --- /dev/null +++ b/devtools/client/webconsole/webconsole-connection-proxy.js @@ -0,0 +1,503 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* 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/. */ + +"use strict"; + +const {Cc, Ci, Cu} = require("chrome"); + +const {Utils: WebConsoleUtils} = + require("devtools/client/webconsole/utils"); +const BrowserLoaderModule = {}; +Cu.import("resource://devtools/client/shared/browser-loader.js", BrowserLoaderModule); + +const promise = require("promise"); +const Services = require("Services"); + +const STRINGS_URI = "devtools/client/locales/webconsole.properties"; +var l10n = new WebConsoleUtils.L10n(STRINGS_URI); + +const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout"; +// Web Console connection proxy + +/** + * The WebConsoleConnectionProxy handles the connection between the Web Console + * and the application we connect to through the remote debug protocol. + * + * @constructor + * @param object webConsoleFrame + * The WebConsoleFrame object that owns this connection proxy. + * @param RemoteTarget target + * The target that the console will connect to. + */ +function WebConsoleConnectionProxy(webConsoleFrame, target) { + this.webConsoleFrame = webConsoleFrame; + this.target = target; + + this._onPageError = this._onPageError.bind(this); + this._onLogMessage = this._onLogMessage.bind(this); + this._onConsoleAPICall = this._onConsoleAPICall.bind(this); + this._onNetworkEvent = this._onNetworkEvent.bind(this); + this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this); + this._onFileActivity = this._onFileActivity.bind(this); + this._onReflowActivity = this._onReflowActivity.bind(this); + this._onServerLogCall = this._onServerLogCall.bind(this); + this._onTabNavigated = this._onTabNavigated.bind(this); + this._onAttachConsole = this._onAttachConsole.bind(this); + this._onCachedMessages = this._onCachedMessages.bind(this); + this._connectionTimeout = this._connectionTimeout.bind(this); + this._onLastPrivateContextExited = + this._onLastPrivateContextExited.bind(this); +} + +WebConsoleConnectionProxy.prototype = { + /** + * The owning Web Console Frame instance. + * + * @see WebConsoleFrame + * @type object + */ + webConsoleFrame: null, + + /** + * The target that the console connects to. + * @type RemoteTarget + */ + target: null, + + /** + * The DebuggerClient object. + * + * @see DebuggerClient + * @type object + */ + client: null, + + /** + * The WebConsoleClient object. + * + * @see WebConsoleClient + * @type object + */ + webConsoleClient: null, + + /** + * Tells if the connection is established. + * @type boolean + */ + connected: false, + + /** + * Timer used for the connection. + * @private + * @type object + */ + _connectTimer: null, + + _connectDefer: null, + _disconnecter: null, + + /** + * The WebConsoleActor ID. + * + * @private + * @type string + */ + _consoleActor: null, + + /** + * Tells if the window.console object of the remote web page is the native + * object or not. + * @private + * @type boolean + */ + _hasNativeConsoleAPI: false, + + /** + * Initialize a debugger client and connect it to the debugger server. + * + * @return object + * A promise object that is resolved/rejected based on the success of + * the connection initialization. + */ + connect: function () { + if (this._connectDefer) { + return this._connectDefer.promise; + } + + this._connectDefer = promise.defer(); + + let timeout = Services.prefs.getIntPref(PREF_CONNECTION_TIMEOUT); + this._connectTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + this._connectTimer.initWithCallback(this._connectionTimeout, + timeout, Ci.nsITimer.TYPE_ONE_SHOT); + + let connPromise = this._connectDefer.promise; + connPromise.then(() => { + this._connectTimer.cancel(); + this._connectTimer = null; + }, () => { + this._connectTimer = null; + }); + + let client = this.client = this.target.client; + + if (this.target.isWorkerTarget) { + // XXXworkers: Not Console API yet inside of workers (Bug 1209353). + } else { + client.addListener("logMessage", this._onLogMessage); + client.addListener("pageError", this._onPageError); + client.addListener("consoleAPICall", this._onConsoleAPICall); + client.addListener("fileActivity", this._onFileActivity); + client.addListener("reflowActivity", this._onReflowActivity); + client.addListener("serverLogCall", this._onServerLogCall); + client.addListener("lastPrivateContextExited", + this._onLastPrivateContextExited); + } + this.target.on("will-navigate", this._onTabNavigated); + this.target.on("navigate", this._onTabNavigated); + + this._consoleActor = this.target.form.consoleActor; + if (this.target.isTabActor) { + let tab = this.target.form; + this.webConsoleFrame.onLocationChange(tab.url, tab.title); + } + this._attachConsole(); + + return connPromise; + }, + + /** + * Connection timeout handler. + * @private + */ + _connectionTimeout: function () { + let error = { + error: "timeout", + message: l10n.getStr("connectionTimeout"), + }; + + this._connectDefer.reject(error); + }, + + /** + * Attach to the Web Console actor. + * @private + */ + _attachConsole: function () { + let listeners = ["PageError", "ConsoleAPI", "NetworkActivity", + "FileActivity"]; + this.client.attachConsole(this._consoleActor, listeners, + this._onAttachConsole); + }, + + /** + * The "attachConsole" response handler. + * + * @private + * @param object response + * The JSON response object received from the server. + * @param object webConsoleClient + * The WebConsoleClient instance for the attached console, for the + * specific tab we work with. + */ + _onAttachConsole: function (response, webConsoleClient) { + if (response.error) { + console.error("attachConsole failed: " + response.error + " " + + response.message); + this._connectDefer.reject(response); + return; + } + + this.webConsoleClient = webConsoleClient; + this._hasNativeConsoleAPI = response.nativeConsoleAPI; + + // There is no way to view response bodies from the Browser Console, so do + // not waste the memory. + let saveBodies = !this.webConsoleFrame.isBrowserConsole; + this.webConsoleFrame.setSaveRequestAndResponseBodies(saveBodies); + + this.webConsoleClient.on("networkEvent", this._onNetworkEvent); + this.webConsoleClient.on("networkEventUpdate", this._onNetworkEventUpdate); + + let msgs = ["PageError", "ConsoleAPI"]; + this.webConsoleClient.getCachedMessages(msgs, this._onCachedMessages); + + this.webConsoleFrame._onUpdateListeners(); + }, + + /** + * Dispatch a message add on the new frontend and emit an event for tests. + */ + dispatchMessageAdd: function(packet) { + this.webConsoleFrame.newConsoleOutput.dispatchMessageAdd(packet); + }, + + /** + * Batched dispatch of messages. + */ + dispatchMessagesAdd: function(packets) { + this.webConsoleFrame.newConsoleOutput.dispatchMessagesAdd(packets); + }, + + /** + * The "cachedMessages" response handler. + * + * @private + * @param object response + * The JSON response object received from the server. + */ + _onCachedMessages: function (response) { + if (response.error) { + console.error("Web Console getCachedMessages error: " + response.error + + " " + response.message); + this._connectDefer.reject(response); + return; + } + + if (!this._connectTimer) { + // This happens if the promise is rejected (eg. a timeout), but the + // connection attempt is successful, nonetheless. + console.error("Web Console getCachedMessages error: invalid state."); + } + + let messages = + response.messages.concat(...this.webConsoleClient.getNetworkEvents()); + messages.sort((a, b) => a.timeStamp - b.timeStamp); + + if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) { + this.dispatchMessagesAdd(messages); + } else { + this.webConsoleFrame.displayCachedMessages(messages); + if (!this._hasNativeConsoleAPI) { + this.webConsoleFrame.logWarningAboutReplacedAPI(); + } + } + + this.connected = true; + this._connectDefer.resolve(this); + }, + + /** + * The "pageError" message type handler. We redirect any page errors to the UI + * for displaying. + * + * @private + * @param string type + * Message type. + * @param object packet + * The message received from the server. + */ + _onPageError: function (type, packet) { + if (this.webConsoleFrame && packet.from == this._consoleActor) { + if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) { + this.dispatchMessageAdd(packet); + return; + } + this.webConsoleFrame.handlePageError(packet.pageError); + } + }, + + /** + * The "logMessage" message type handler. We redirect any message to the UI + * for displaying. + * + * @private + * @param string type + * Message type. + * @param object packet + * The message received from the server. + */ + _onLogMessage: function (type, packet) { + if (this.webConsoleFrame && packet.from == this._consoleActor) { + this.webConsoleFrame.handleLogMessage(packet); + } + }, + + /** + * The "consoleAPICall" message type handler. We redirect any message to + * the UI for displaying. + * + * @private + * @param string type + * Message type. + * @param object packet + * The message received from the server. + */ + _onConsoleAPICall: function (type, packet) { + if (this.webConsoleFrame && packet.from == this._consoleActor) { + if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) { + this.dispatchMessageAdd(packet); + } else { + this.webConsoleFrame.handleConsoleAPICall(packet.message); + } + } + }, + + /** + * The "networkEvent" message type handler. We redirect any message to + * the UI for displaying. + * + * @private + * @param string type + * Message type. + * @param object networkInfo + * The network request information. + */ + _onNetworkEvent: function (type, networkInfo) { + if (this.webConsoleFrame) { + if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) { + this.dispatchMessageAdd(networkInfo); + } else { + this.webConsoleFrame.handleNetworkEvent(networkInfo); + } + } + }, + + /** + * The "networkEventUpdate" message type handler. We redirect any message to + * the UI for displaying. + * + * @private + * @param string type + * Message type. + * @param object packet + * The message received from the server. + * @param object networkInfo + * The network request information. + */ + _onNetworkEventUpdate: function (type, { packet, networkInfo }) { + if (this.webConsoleFrame) { + this.webConsoleFrame.handleNetworkEventUpdate(networkInfo, packet); + } + }, + + /** + * The "fileActivity" message type handler. We redirect any message to + * the UI for displaying. + * + * @private + * @param string type + * Message type. + * @param object packet + * The message received from the server. + */ + _onFileActivity: function (type, packet) { + if (this.webConsoleFrame && packet.from == this._consoleActor) { + this.webConsoleFrame.handleFileActivity(packet.uri); + } + }, + + _onReflowActivity: function (type, packet) { + if (this.webConsoleFrame && packet.from == this._consoleActor) { + this.webConsoleFrame.handleReflowActivity(packet); + } + }, + + /** + * The "serverLogCall" message type handler. We redirect any message to + * the UI for displaying. + * + * @private + * @param string type + * Message type. + * @param object packet + * The message received from the server. + */ + _onServerLogCall: function (type, packet) { + if (this.webConsoleFrame && packet.from == this._consoleActor) { + this.webConsoleFrame.handleConsoleAPICall(packet.message); + } + }, + + /** + * The "lastPrivateContextExited" message type handler. When this message is + * received the Web Console UI is cleared. + * + * @private + * @param string type + * Message type. + * @param object packet + * The message received from the server. + */ + _onLastPrivateContextExited: function (type, packet) { + if (this.webConsoleFrame && packet.from == this._consoleActor) { + this.webConsoleFrame.jsterm.clearPrivateMessages(); + } + }, + + /** + * The "will-navigate" and "navigate" event handlers. We redirect any message + * to the UI for displaying. + * + * @private + * @param string event + * Event type. + * @param object packet + * The message received from the server. + */ + _onTabNavigated: function (event, packet) { + if (!this.webConsoleFrame) { + return; + } + + this.webConsoleFrame.handleTabNavigated(event, packet); + }, + + /** + * Release an object actor. + * + * @param string actor + * The actor ID to send the request to. + */ + releaseActor: function (actor) { + if (this.client) { + this.client.release(actor); + } + }, + + /** + * Disconnect the Web Console from the remote server. + * + * @return object + * A promise object that is resolved when disconnect completes. + */ + disconnect: function () { + if (this._disconnecter) { + return this._disconnecter.promise; + } + + this._disconnecter = promise.defer(); + + if (!this.client) { + this._disconnecter.resolve(null); + return this._disconnecter.promise; + } + + this.client.removeListener("logMessage", this._onLogMessage); + this.client.removeListener("pageError", this._onPageError); + this.client.removeListener("consoleAPICall", this._onConsoleAPICall); + this.client.removeListener("fileActivity", this._onFileActivity); + this.client.removeListener("reflowActivity", this._onReflowActivity); + this.client.removeListener("serverLogCall", this._onServerLogCall); + this.client.removeListener("lastPrivateContextExited", + this._onLastPrivateContextExited); + this.webConsoleClient.off("networkEvent", this._onNetworkEvent); + this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate); + this.target.off("will-navigate", this._onTabNavigated); + this.target.off("navigate", this._onTabNavigated); + + this.client = null; + this.webConsoleClient = null; + this.target = null; + this.connected = false; + this.webConsoleFrame = null; + this._disconnecter.resolve(null); + + return this._disconnecter.promise; + }, +}; + +exports.WebConsoleConnectionProxy = WebConsoleConnectionProxy; \ No newline at end of file diff --git a/devtools/client/webconsole/webconsole.js b/devtools/client/webconsole/webconsole.js index 2d5098ae9b04..236b5f25e9db 100644 --- a/devtools/client/webconsole/webconsole.js +++ b/devtools/client/webconsole/webconsole.js @@ -16,15 +16,12 @@ Cu.import("resource://devtools/client/shared/browser-loader.js", BrowserLoaderMo const promise = require("promise"); const Services = require("Services"); -const ErrorDocs = require("devtools/server/actors/errordocs"); const Telemetry = require("devtools/client/shared/telemetry"); loader.lazyServiceGetter(this, "clipboardHelper", "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper"); loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter"); -loader.lazyRequireGetter(this, "AutocompletePopup", "devtools/client/shared/autocomplete-popup", true); -loader.lazyRequireGetter(this, "ToolSidebar", "devtools/client/framework/sidebar", true); loader.lazyRequireGetter(this, "ConsoleOutput", "devtools/client/webconsole/console-output", true); loader.lazyRequireGetter(this, "Messages", "devtools/client/webconsole/console-output", true); loader.lazyRequireGetter(this, "EnvironmentClient", "devtools/shared/client/main", true); @@ -37,6 +34,7 @@ loader.lazyImporter(this, "VariablesViewController", "resource://devtools/client loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true); loader.lazyRequireGetter(this, "KeyShortcuts", "devtools/client/shared/key-shortcuts", true); loader.lazyRequireGetter(this, "ZoomKeys", "devtools/client/shared/zoom-keys"); +loader.lazyRequireGetter(this, "WebConsoleConnectionProxy", "devtools/client/webconsole/webconsole-connection-proxy", true); const {PluralForm} = require("devtools/shared/plural-form"); const STRINGS_URI = "devtools/client/locales/webconsole.properties"; @@ -183,10 +181,6 @@ const THROTTLE_UPDATES = 1000; // The preference prefix for all of the Web Console filters. const FILTER_PREFS_PREFIX = "devtools.webconsole.filter."; -// The minimum font size. -const MIN_FONT_SIZE = 10; - -const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout"; const PREF_PERSISTLOG = "devtools.webconsole.persistlog"; const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages"; const PREF_NEW_FRONTEND_ENABLED = "devtools.webconsole.new-frontend-enabled"; @@ -3053,486 +3047,6 @@ CommandController.prototype = { } }; -// Web Console connection proxy - -/** - * The WebConsoleConnectionProxy handles the connection between the Web Console - * and the application we connect to through the remote debug protocol. - * - * @constructor - * @param object webConsoleFrame - * The WebConsoleFrame object that owns this connection proxy. - * @param RemoteTarget target - * The target that the console will connect to. - */ -function WebConsoleConnectionProxy(webConsoleFrame, target) { - this.webConsoleFrame = webConsoleFrame; - this.target = target; - - this._onPageError = this._onPageError.bind(this); - this._onLogMessage = this._onLogMessage.bind(this); - this._onConsoleAPICall = this._onConsoleAPICall.bind(this); - this._onNetworkEvent = this._onNetworkEvent.bind(this); - this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this); - this._onFileActivity = this._onFileActivity.bind(this); - this._onReflowActivity = this._onReflowActivity.bind(this); - this._onServerLogCall = this._onServerLogCall.bind(this); - this._onTabNavigated = this._onTabNavigated.bind(this); - this._onAttachConsole = this._onAttachConsole.bind(this); - this._onCachedMessages = this._onCachedMessages.bind(this); - this._connectionTimeout = this._connectionTimeout.bind(this); - this._onLastPrivateContextExited = - this._onLastPrivateContextExited.bind(this); -} - -WebConsoleConnectionProxy.prototype = { - /** - * The owning Web Console Frame instance. - * - * @see WebConsoleFrame - * @type object - */ - webConsoleFrame: null, - - /** - * The target that the console connects to. - * @type RemoteTarget - */ - target: null, - - /** - * The DebuggerClient object. - * - * @see DebuggerClient - * @type object - */ - client: null, - - /** - * The WebConsoleClient object. - * - * @see WebConsoleClient - * @type object - */ - webConsoleClient: null, - - /** - * Tells if the connection is established. - * @type boolean - */ - connected: false, - - /** - * Timer used for the connection. - * @private - * @type object - */ - _connectTimer: null, - - _connectDefer: null, - _disconnecter: null, - - /** - * The WebConsoleActor ID. - * - * @private - * @type string - */ - _consoleActor: null, - - /** - * Tells if the window.console object of the remote web page is the native - * object or not. - * @private - * @type boolean - */ - _hasNativeConsoleAPI: false, - - /** - * Initialize a debugger client and connect it to the debugger server. - * - * @return object - * A promise object that is resolved/rejected based on the success of - * the connection initialization. - */ - connect: function () { - if (this._connectDefer) { - return this._connectDefer.promise; - } - - this._connectDefer = promise.defer(); - - let timeout = Services.prefs.getIntPref(PREF_CONNECTION_TIMEOUT); - this._connectTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - this._connectTimer.initWithCallback(this._connectionTimeout, - timeout, Ci.nsITimer.TYPE_ONE_SHOT); - - let connPromise = this._connectDefer.promise; - connPromise.then(() => { - this._connectTimer.cancel(); - this._connectTimer = null; - }, () => { - this._connectTimer = null; - }); - - let client = this.client = this.target.client; - - if (this.target.isWorkerTarget) { - // XXXworkers: Not Console API yet inside of workers (Bug 1209353). - } else { - client.addListener("logMessage", this._onLogMessage); - client.addListener("pageError", this._onPageError); - client.addListener("consoleAPICall", this._onConsoleAPICall); - client.addListener("fileActivity", this._onFileActivity); - client.addListener("reflowActivity", this._onReflowActivity); - client.addListener("serverLogCall", this._onServerLogCall); - client.addListener("lastPrivateContextExited", - this._onLastPrivateContextExited); - } - this.target.on("will-navigate", this._onTabNavigated); - this.target.on("navigate", this._onTabNavigated); - - this._consoleActor = this.target.form.consoleActor; - if (this.target.isTabActor) { - let tab = this.target.form; - this.webConsoleFrame.onLocationChange(tab.url, tab.title); - } - this._attachConsole(); - - return connPromise; - }, - - /** - * Connection timeout handler. - * @private - */ - _connectionTimeout: function () { - let error = { - error: "timeout", - message: l10n.getStr("connectionTimeout"), - }; - - this._connectDefer.reject(error); - }, - - /** - * Attach to the Web Console actor. - * @private - */ - _attachConsole: function () { - let listeners = ["PageError", "ConsoleAPI", "NetworkActivity", - "FileActivity"]; - this.client.attachConsole(this._consoleActor, listeners, - this._onAttachConsole); - }, - - /** - * The "attachConsole" response handler. - * - * @private - * @param object response - * The JSON response object received from the server. - * @param object webConsoleClient - * The WebConsoleClient instance for the attached console, for the - * specific tab we work with. - */ - _onAttachConsole: function (response, webConsoleClient) { - if (response.error) { - console.error("attachConsole failed: " + response.error + " " + - response.message); - this._connectDefer.reject(response); - return; - } - - this.webConsoleClient = webConsoleClient; - this._hasNativeConsoleAPI = response.nativeConsoleAPI; - - // There is no way to view response bodies from the Browser Console, so do - // not waste the memory. - let saveBodies = !this.webConsoleFrame.isBrowserConsole; - this.webConsoleFrame.setSaveRequestAndResponseBodies(saveBodies); - - this.webConsoleClient.on("networkEvent", this._onNetworkEvent); - this.webConsoleClient.on("networkEventUpdate", this._onNetworkEventUpdate); - - let msgs = ["PageError", "ConsoleAPI"]; - this.webConsoleClient.getCachedMessages(msgs, this._onCachedMessages); - - this.webConsoleFrame._onUpdateListeners(); - }, - - /** - * Dispatch a message add on the new frontend and emit an event for tests. - */ - dispatchMessageAdd: function(packet) { - this.webConsoleFrame.newConsoleOutput.dispatchMessageAdd(packet); - }, - - /** - * Batched dispatch of messages. - */ - dispatchMessagesAdd: function(packets) { - this.webConsoleFrame.newConsoleOutput.dispatchMessagesAdd(packets); - }, - - /** - * The "cachedMessages" response handler. - * - * @private - * @param object response - * The JSON response object received from the server. - */ - _onCachedMessages: function (response) { - if (response.error) { - console.error("Web Console getCachedMessages error: " + response.error + - " " + response.message); - this._connectDefer.reject(response); - return; - } - - if (!this._connectTimer) { - // This happens if the promise is rejected (eg. a timeout), but the - // connection attempt is successful, nonetheless. - console.error("Web Console getCachedMessages error: invalid state."); - } - - let messages = - response.messages.concat(...this.webConsoleClient.getNetworkEvents()); - messages.sort((a, b) => a.timeStamp - b.timeStamp); - - if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) { - this.dispatchMessagesAdd(messages); - } else { - this.webConsoleFrame.displayCachedMessages(messages); - if (!this._hasNativeConsoleAPI) { - this.webConsoleFrame.logWarningAboutReplacedAPI(); - } - } - - this.connected = true; - this._connectDefer.resolve(this); - }, - - /** - * The "pageError" message type handler. We redirect any page errors to the UI - * for displaying. - * - * @private - * @param string type - * Message type. - * @param object packet - * The message received from the server. - */ - _onPageError: function (type, packet) { - if (this.webConsoleFrame && packet.from == this._consoleActor) { - if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) { - this.dispatchMessageAdd(packet); - return; - } - this.webConsoleFrame.handlePageError(packet.pageError); - } - }, - - /** - * The "logMessage" message type handler. We redirect any message to the UI - * for displaying. - * - * @private - * @param string type - * Message type. - * @param object packet - * The message received from the server. - */ - _onLogMessage: function (type, packet) { - if (this.webConsoleFrame && packet.from == this._consoleActor) { - this.webConsoleFrame.handleLogMessage(packet); - } - }, - - /** - * The "consoleAPICall" message type handler. We redirect any message to - * the UI for displaying. - * - * @private - * @param string type - * Message type. - * @param object packet - * The message received from the server. - */ - _onConsoleAPICall: function (type, packet) { - if (this.webConsoleFrame && packet.from == this._consoleActor) { - if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) { - this.dispatchMessageAdd(packet); - } else { - this.webConsoleFrame.handleConsoleAPICall(packet.message); - } - } - }, - - /** - * The "networkEvent" message type handler. We redirect any message to - * the UI for displaying. - * - * @private - * @param string type - * Message type. - * @param object networkInfo - * The network request information. - */ - _onNetworkEvent: function (type, networkInfo) { - if (this.webConsoleFrame) { - if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) { - this.dispatchMessageAdd(networkInfo); - } else { - this.webConsoleFrame.handleNetworkEvent(networkInfo); - } - } - }, - - /** - * The "networkEventUpdate" message type handler. We redirect any message to - * the UI for displaying. - * - * @private - * @param string type - * Message type. - * @param object packet - * The message received from the server. - * @param object networkInfo - * The network request information. - */ - _onNetworkEventUpdate: function (type, { packet, networkInfo }) { - if (this.webConsoleFrame) { - this.webConsoleFrame.handleNetworkEventUpdate(networkInfo, packet); - } - }, - - /** - * The "fileActivity" message type handler. We redirect any message to - * the UI for displaying. - * - * @private - * @param string type - * Message type. - * @param object packet - * The message received from the server. - */ - _onFileActivity: function (type, packet) { - if (this.webConsoleFrame && packet.from == this._consoleActor) { - this.webConsoleFrame.handleFileActivity(packet.uri); - } - }, - - _onReflowActivity: function (type, packet) { - if (this.webConsoleFrame && packet.from == this._consoleActor) { - this.webConsoleFrame.handleReflowActivity(packet); - } - }, - - /** - * The "serverLogCall" message type handler. We redirect any message to - * the UI for displaying. - * - * @private - * @param string type - * Message type. - * @param object packet - * The message received from the server. - */ - _onServerLogCall: function (type, packet) { - if (this.webConsoleFrame && packet.from == this._consoleActor) { - this.webConsoleFrame.handleConsoleAPICall(packet.message); - } - }, - - /** - * The "lastPrivateContextExited" message type handler. When this message is - * received the Web Console UI is cleared. - * - * @private - * @param string type - * Message type. - * @param object packet - * The message received from the server. - */ - _onLastPrivateContextExited: function (type, packet) { - if (this.webConsoleFrame && packet.from == this._consoleActor) { - this.webConsoleFrame.jsterm.clearPrivateMessages(); - } - }, - - /** - * The "will-navigate" and "navigate" event handlers. We redirect any message - * to the UI for displaying. - * - * @private - * @param string event - * Event type. - * @param object packet - * The message received from the server. - */ - _onTabNavigated: function (event, packet) { - if (!this.webConsoleFrame) { - return; - } - - this.webConsoleFrame.handleTabNavigated(event, packet); - }, - - /** - * Release an object actor. - * - * @param string actor - * The actor ID to send the request to. - */ - releaseActor: function (actor) { - if (this.client) { - this.client.release(actor); - } - }, - - /** - * Disconnect the Web Console from the remote server. - * - * @return object - * A promise object that is resolved when disconnect completes. - */ - disconnect: function () { - if (this._disconnecter) { - return this._disconnecter.promise; - } - - this._disconnecter = promise.defer(); - - if (!this.client) { - this._disconnecter.resolve(null); - return this._disconnecter.promise; - } - - this.client.removeListener("logMessage", this._onLogMessage); - this.client.removeListener("pageError", this._onPageError); - this.client.removeListener("consoleAPICall", this._onConsoleAPICall); - this.client.removeListener("fileActivity", this._onFileActivity); - this.client.removeListener("reflowActivity", this._onReflowActivity); - this.client.removeListener("serverLogCall", this._onServerLogCall); - this.client.removeListener("lastPrivateContextExited", - this._onLastPrivateContextExited); - this.webConsoleClient.off("networkEvent", this._onNetworkEvent); - this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate); - this.target.off("will-navigate", this._onTabNavigated); - this.target.off("navigate", this._onTabNavigated); - - this.client = null; - this.webConsoleClient = null; - this.target = null; - this.connected = false; - this.webConsoleFrame = null; - this._disconnecter.resolve(null); - - return this._disconnecter.promise; - }, -}; - // Context Menu /* diff --git a/dom/base/nsObjectLoadingContent.cpp b/dom/base/nsObjectLoadingContent.cpp index 613dae74dd57..a18d625621cd 100644 --- a/dom/base/nsObjectLoadingContent.cpp +++ b/dom/base/nsObjectLoadingContent.cpp @@ -372,78 +372,6 @@ nsPluginCrashedEvent::Run() return NS_OK; } -class nsStopPluginRunnable : public Runnable, public nsITimerCallback -{ -public: - NS_DECL_ISUPPORTS_INHERITED - - nsStopPluginRunnable(nsPluginInstanceOwner* aInstanceOwner, - nsObjectLoadingContent* aContent) - : mInstanceOwner(aInstanceOwner) - , mContent(aContent) - { - NS_ASSERTION(aInstanceOwner, "need an owner"); - NS_ASSERTION(aContent, "need a nsObjectLoadingContent"); - } - - // Runnable - NS_IMETHOD Run() override; - - // nsITimerCallback - NS_IMETHOD Notify(nsITimer* timer) override; - -protected: - ~nsStopPluginRunnable() override = default; - -private: - nsCOMPtr mTimer; - RefPtr mInstanceOwner; - nsCOMPtr mContent; -}; - -NS_IMPL_ISUPPORTS_INHERITED(nsStopPluginRunnable, Runnable, nsITimerCallback) - -NS_IMETHODIMP -nsStopPluginRunnable::Notify(nsITimer *aTimer) -{ - return Run(); -} - -NS_IMETHODIMP -nsStopPluginRunnable::Run() -{ - // InitWithCallback calls Release before AddRef so we need to hold a - // strong ref on 'this' since we fall through to this scope if it fails. - nsCOMPtr kungFuDeathGrip = this; - nsCOMPtr appShell = do_GetService(kAppShellCID); - if (appShell) { - uint32_t currentLevel = 0; - appShell->GetEventloopNestingLevel(¤tLevel); - if (currentLevel > mInstanceOwner->GetLastEventloopNestingLevel()) { - if (!mTimer) - mTimer = do_CreateInstance("@mozilla.org/timer;1"); - if (mTimer) { - // Fire 100ms timer to try to tear down this plugin as quickly as - // possible once the nesting level comes back down. - nsresult rv = mTimer->InitWithCallback(this, 100, - nsITimer::TYPE_ONE_SHOT); - if (NS_SUCCEEDED(rv)) { - return rv; - } - } - NS_ERROR("Failed to setup a timer to stop the plugin later (at a safe " - "time). Stopping the plugin now, this might crash."); - } - } - - mTimer = nullptr; - - static_cast(mContent.get())-> - DoStopPlugin(mInstanceOwner, false, true); - - return NS_OK; -} - // You can't take the address of bitfield members, so we have two separate // classes for these :-/ @@ -3070,29 +2998,6 @@ nsObjectLoadingContent::GetSrcURI(nsIURI** aURI) return NS_OK; } -static bool -DoDelayedStop(nsPluginInstanceOwner* aInstanceOwner, - nsObjectLoadingContent* aContent, - bool aDelayedStop) -{ - // Don't delay stopping QuickTime (bug 425157), Flip4Mac (bug 426524), - // XStandard (bug 430219), CMISS Zinc (bug 429604). - if (aDelayedStop -#if !(defined XP_WIN || defined MOZ_X11) - && !aInstanceOwner->MatchPluginName("QuickTime") - && !aInstanceOwner->MatchPluginName("Flip4Mac") - && !aInstanceOwner->MatchPluginName("XStandard plugin") - && !aInstanceOwner->MatchPluginName("CMISS Zinc Plugin") -#endif - ) { - nsCOMPtr evt = - new nsStopPluginRunnable(aInstanceOwner, aContent); - NS_DispatchToCurrentThread(evt); - return true; - } - return false; -} - void nsObjectLoadingContent::LoadFallback(FallbackType aType, bool aNotify) { EventStates oldState = ObjectState(); @@ -3156,16 +3061,13 @@ nsObjectLoadingContent::LoadFallback(FallbackType aType, bool aNotify) { } void -nsObjectLoadingContent::DoStopPlugin(nsPluginInstanceOwner* aInstanceOwner, - bool aDelayedStop, - bool aForcedReentry) +nsObjectLoadingContent::DoStopPlugin(nsPluginInstanceOwner* aInstanceOwner) { // DoStopPlugin can process events -- There may be pending // CheckPluginStopEvent events which can drop in underneath us and destroy the - // instance we are about to destroy. We prevent that with the mPluginStopping - // flag. (aForcedReentry is only true from the callback of an earlier delayed - // stop) - if (mIsStopping && !aForcedReentry) { + // instance we are about to destroy. We prevent that with the mIsStopping + // flag. + if (mIsStopping) { return; } mIsStopping = true; @@ -3174,10 +3076,6 @@ nsObjectLoadingContent::DoStopPlugin(nsPluginInstanceOwner* aInstanceOwner, RefPtr inst; aInstanceOwner->GetInstance(getter_AddRefs(inst)); if (inst) { - if (DoDelayedStop(aInstanceOwner, this, aDelayedStop)) { - return; - } - #if defined(XP_MACOSX) aInstanceOwner->HidePluginWindow(); #endif @@ -3230,27 +3128,11 @@ nsObjectLoadingContent::StopPluginInstance() // the instance owner until the plugin is stopped. mInstanceOwner->SetFrame(nullptr); - bool delayedStop = false; -#ifdef XP_WIN - // Force delayed stop for Real plugin only; see bug 420886, 426852. - RefPtr inst; - mInstanceOwner->GetInstance(getter_AddRefs(inst)); - if (inst) { - const char* mime = nullptr; - if (NS_SUCCEEDED(inst->GetMIMEType(&mime)) && mime) { - if (nsPluginHost::GetSpecialType(nsDependentCString(mime)) == - nsPluginHost::eSpecialType_RealPlayer) { - delayedStop = true; - } - } - } -#endif - RefPtr ownerGrip(mInstanceOwner); mInstanceOwner = nullptr; // This can/will re-enter - DoStopPlugin(ownerGrip, delayedStop); + DoStopPlugin(ownerGrip); return NS_OK; } diff --git a/dom/base/nsObjectLoadingContent.h b/dom/base/nsObjectLoadingContent.h index cad1a1b7252c..27b7f8ef0361 100644 --- a/dom/base/nsObjectLoadingContent.h +++ b/dom/base/nsObjectLoadingContent.h @@ -331,8 +331,7 @@ class nsObjectLoadingContent : public nsImageLoadingContent void CreateStaticClone(nsObjectLoadingContent* aDest) const; - void DoStopPlugin(nsPluginInstanceOwner* aInstanceOwner, bool aDelayedStop, - bool aForcedReentry = false); + void DoStopPlugin(nsPluginInstanceOwner* aInstanceOwner); nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent, nsIContent* aBindingParent, diff --git a/dom/canvas/TexUnpackBlob.cpp b/dom/canvas/TexUnpackBlob.cpp index 08f3a29b2e2a..8923e01431b4 100644 --- a/dom/canvas/TexUnpackBlob.cpp +++ b/dom/canvas/TexUnpackBlob.cpp @@ -490,7 +490,7 @@ TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec, const char* fun *out_error = DoTexOrSubImage(false, gl, target, level, dui, xOffset, yOffset, zOffset, mWidth, mHeight, mDepth, nullptr); if (*out_error) - return false; + return true; } const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER, @@ -587,7 +587,7 @@ TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec, const char* fun yOffset, zOffset, mWidth, mHeight, mDepth, nullptr); if (*out_error) - return false; + return true; } do { @@ -650,7 +650,7 @@ TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec, const char* fun webgl->ErrorOutOfMemory("%s: GetAsSourceSurface or GetDataSurface failed after" " blit failed for TexUnpackImage.", funcName); - return false; + return true; } const TexUnpackSurface surfBlob(webgl, target, mWidth, mHeight, mDepth, dataSurf, @@ -751,8 +751,10 @@ TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec, const char* f } gfx::DataSourceSurface::ScopedMap map(mSurf, gfx::DataSourceSurface::MapType::READ); - if (!map.IsMapped()) + if (!map.IsMapped()) { + webgl->ErrorOutOfMemory("%s: Failed to map source surface for upload.", funcName); return false; + } const auto srcBytes = map.GetData(); const auto srcStride = map.GetStride(); diff --git a/dom/canvas/TexUnpackBlob.h b/dom/canvas/TexUnpackBlob.h index 73f66894fc0e..aa1ec45128eb 100644 --- a/dom/canvas/TexUnpackBlob.h +++ b/dom/canvas/TexUnpackBlob.h @@ -74,6 +74,10 @@ public: virtual bool Validate(WebGLContext* webgl, const char* funcName, const webgl::PackingInfo& pi) = 0; + + // Returns false when we've generated a WebGL error. + // Returns true but with a non-zero *out_error if we still need to generate a WebGL + // error. virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName, WebGLTexture* tex, TexImageTarget target, GLint level, const webgl::DriverUnpackInfo* dui, GLint xOffset, diff --git a/dom/canvas/WebGLFramebuffer.cpp b/dom/canvas/WebGLFramebuffer.cpp index 398e2cf25dc2..f64e491647a1 100644 --- a/dom/canvas/WebGLFramebuffer.cpp +++ b/dom/canvas/WebGLFramebuffer.cpp @@ -1609,10 +1609,22 @@ WebGLFramebuffer::BlitFramebuffer(WebGLContext* webgl, bool colorFormatsMatch = true; bool colorTypesMatch = true; + const auto fnNarrowComponentType = [&](const webgl::FormatInfo* format) { + switch (format->componentType) { + case webgl::ComponentType::NormInt: + case webgl::ComponentType::NormUInt: + return webgl::ComponentType::Float; + + default: + return format->componentType; + } + }; + const auto fnCheckColorFormat = [&](const webgl::FormatInfo* dstFormat) { dstHasColor = true; colorFormatsMatch &= (dstFormat == srcColorFormat); - colorTypesMatch &= (dstFormat->componentType == srcColorFormat->componentType); + colorTypesMatch &= ( fnNarrowComponentType(dstFormat) == + fnNarrowComponentType(srcColorFormat) ); }; if (dstFB) { diff --git a/dom/canvas/WebGLTextureUpload.cpp b/dom/canvas/WebGLTextureUpload.cpp index 6d4c07200b86..f333c802b10a 100644 --- a/dom/canvas/WebGLTextureUpload.cpp +++ b/dom/canvas/WebGLTextureUpload.cpp @@ -1239,8 +1239,11 @@ WebGLTexture::TexImage(const char* funcName, TexImageTarget target, GLint level, const GLint zOffset = 0; GLenum glError; - blob->TexOrSubImage(isSubImage, needsRespec, funcName, this, target, level, - driverUnpackInfo, xOffset, yOffset, zOffset, &glError); + if (!blob->TexOrSubImage(isSubImage, needsRespec, funcName, this, target, level, + driverUnpackInfo, xOffset, yOffset, zOffset, &glError)) + { + return; + } if (glError == LOCAL_GL_OUT_OF_MEMORY) { mContext->ErrorOutOfMemory("%s: Driver ran out of memory during upload.", @@ -1324,8 +1327,11 @@ WebGLTexture::TexSubImage(const char* funcName, TexImageTarget target, GLint lev const bool needsRespec = false; GLenum glError; - blob->TexOrSubImage(isSubImage, needsRespec, funcName, this, target, level, - driverUnpackInfo, xOffset, yOffset, zOffset, &glError); + if (!blob->TexOrSubImage(isSubImage, needsRespec, funcName, this, target, level, + driverUnpackInfo, xOffset, yOffset, zOffset, &glError)) + { + return; + } if (glError == LOCAL_GL_OUT_OF_MEMORY) { mContext->ErrorOutOfMemory("%s: Driver ran out of memory during upload.", diff --git a/dom/plugins/base/nsPluginHost.cpp b/dom/plugins/base/nsPluginHost.cpp index e092ad6974dc..983bbe9ca1f1 100644 --- a/dom/plugins/base/nsPluginHost.cpp +++ b/dom/plugins/base/nsPluginHost.cpp @@ -1849,11 +1849,6 @@ nsPluginHost::GetSpecialType(const nsACString & aMIMEType) return eSpecialType_Silverlight; } - if (aMIMEType.LowerCaseEqualsASCII("audio/x-pn-realaudio-plugin")) { - NS_WARNING("You are loading RealPlayer"); - return eSpecialType_RealPlayer; - } - if (aMIMEType.LowerCaseEqualsASCII("application/vnd.unity")) { return eSpecialType_Unity; } diff --git a/dom/plugins/base/nsPluginHost.h b/dom/plugins/base/nsPluginHost.h index 8eb17218059f..cfa46139ada3 100644 --- a/dom/plugins/base/nsPluginHost.h +++ b/dom/plugins/base/nsPluginHost.h @@ -205,8 +205,6 @@ public: // Some IPC quirks eSpecialType_Silverlight, // Native widget quirks - eSpecialType_RealPlayer, - // Native widget quirks eSpecialType_Unity }; static SpecialType GetSpecialType(const nsACString & aMIMEType); diff --git a/dom/plugins/base/nsPluginNativeWindowWin.cpp b/dom/plugins/base/nsPluginNativeWindowWin.cpp index 91f96578aa05..16e4ef6c53e0 100644 --- a/dom/plugins/base/nsPluginNativeWindowWin.cpp +++ b/dom/plugins/base/nsPluginNativeWindowWin.cpp @@ -129,9 +129,7 @@ public: nsPluginHost::SpecialType mPluginType; }; -static bool sInMessageDispatch = false; static bool sInPreviousMessageDispatch = false; -static UINT sLastMsg = 0; static bool ProcessFlashMessageDelayed(nsPluginNativeWindowWin * aWin, nsNPAPIPluginInstance * aInst, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) @@ -201,16 +199,6 @@ static LRESULT CALLBACK PluginWndProcInternal(HWND hWnd, UINT msg, WPARAM wParam RefPtr inst; win->GetPluginInstance(inst); - // Real may go into a state where it recursivly dispatches the same event - // when subclassed. If this is Real, lets examine the event and drop it - // on the floor if we get into this recursive situation. See bug 192914. - if (win->mPluginType == nsPluginHost::eSpecialType_RealPlayer) { - if (sInMessageDispatch && msg == sLastMsg) - return true; - // Cache the last message sent - sLastMsg = msg; - } - bool enablePopups = false; // Activate/deactivate mouse capture on the plugin widget @@ -280,10 +268,6 @@ static LRESULT CALLBACK PluginWndProcInternal(HWND hWnd, UINT msg, WPARAM wParam case WM_SETFOCUS: case WM_KILLFOCUS: { - // RealPlayer can crash, don't process the message for those, - // see bug 328675. - if (win->mPluginType == nsPluginHost::eSpecialType_RealPlayer && msg == sLastMsg) - return TRUE; // Make sure setfocus and killfocus get through to the widget procedure // even if they are eaten by the plugin. Also make sure we aren't calling // recursively. @@ -313,7 +297,6 @@ static LRESULT CALLBACK PluginWndProcInternal(HWND hWnd, UINT msg, WPARAM wParam } } - sInMessageDispatch = true; LRESULT res; WNDPROC proc = (WNDPROC)win->GetWindowProc(); if (PluginWndProc == proc) { @@ -323,7 +306,6 @@ static LRESULT CALLBACK PluginWndProcInternal(HWND hWnd, UINT msg, WPARAM wParam } else { res = CallWindowProc(proc, hWnd, msg, wParam, lParam); } - sInMessageDispatch = false; if (inst) { // Popups are enabled (were enabled before the call to diff --git a/image/Image.cpp b/image/Image.cpp index b757a60f71fc..5436cbb93464 100644 --- a/image/Image.cpp +++ b/image/Image.cpp @@ -146,5 +146,25 @@ ImageResource::EvaluateAnimation() } } +void +ImageResource::SendOnUnlockedDraw(uint32_t aFlags) +{ + if (!mProgressTracker) { + return; + } + + if (!(aFlags & FLAG_ASYNC_NOTIFY)) { + mProgressTracker->OnUnlockedDraw(); + } else { + NotNull> image = WrapNotNull(this); + NS_DispatchToMainThread(NS_NewRunnableFunction([=]() -> void { + RefPtr tracker = image->GetProgressTracker(); + if (tracker) { + tracker->OnUnlockedDraw(); + } + })); + } +} + } // namespace image } // namespace mozilla diff --git a/image/Image.h b/image/Image.h index bcabd1cc7b4d..8656b569f78c 100644 --- a/image/Image.h +++ b/image/Image.h @@ -305,6 +305,8 @@ protected: virtual nsresult StartAnimation() = 0; virtual nsresult StopAnimation() = 0; + void SendOnUnlockedDraw(uint32_t aFlags); + // Member data shared by all implementations of this abstract class RefPtr mProgressTracker; RefPtr mURI; diff --git a/image/RasterImage.cpp b/image/RasterImage.cpp index 58b6da95c664..735c844c63da 100644 --- a/image/RasterImage.cpp +++ b/image/RasterImage.cpp @@ -587,8 +587,8 @@ RasterImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags) return nullptr; } - if (IsUnlocked() && mProgressTracker) { - mProgressTracker->OnUnlockedDraw(); + if (IsUnlocked()) { + SendOnUnlockedDraw(aFlags); } RefPtr container = mImageContainer.get(); @@ -1343,10 +1343,11 @@ RasterImage::Draw(gfxContext* aContext, return DrawResult::BAD_ARGS; } - if (IsUnlocked() && mProgressTracker) { - mProgressTracker->OnUnlockedDraw(); + if (IsUnlocked()) { + SendOnUnlockedDraw(aFlags); } + // If we're not using SamplingFilter::GOOD, we shouldn't high-quality scale or // downscale during decode. uint32_t flags = aSamplingFilter == SamplingFilter::GOOD diff --git a/image/VectorImage.cpp b/image/VectorImage.cpp index 6e3928362255..41c83efea60b 100644 --- a/image/VectorImage.cpp +++ b/image/VectorImage.cpp @@ -835,8 +835,8 @@ VectorImage::Draw(gfxContext* aContext, return DrawResult::TEMPORARY_ERROR; } - if (mAnimationConsumers == 0 && mProgressTracker) { - mProgressTracker->OnUnlockedDraw(); + if (mAnimationConsumers == 0) { + SendOnUnlockedDraw(aFlags); } AutoRestore autoRestoreIsDrawing(mIsDrawing); diff --git a/image/decoders/nsICODecoder.cpp b/image/decoders/nsICODecoder.cpp index 633bb125523a..8791d67396ec 100644 --- a/image/decoders/nsICODecoder.cpp +++ b/image/decoders/nsICODecoder.cpp @@ -374,7 +374,7 @@ nsICODecoder::ReadBIH(const char* aData) // tells it to skip this, and pass in the required data (dataOffset) that // would have been present in the header. uint32_t dataOffset = bmp::FILE_HEADER_LENGTH + BITMAPINFOSIZE; - if (mDirEntry.mBitCount <= 8) { + if (mBPP <= 8) { // The color table is present only if BPP is <= 8. uint16_t numColors = GetNumColors(); if (numColors == (uint16_t)-1) { diff --git a/image/decoders/nsPNGDecoder.cpp b/image/decoders/nsPNGDecoder.cpp index 1faffd5cb148..0f385b3396fb 100644 --- a/image/decoders/nsPNGDecoder.cpp +++ b/image/decoders/nsPNGDecoder.cpp @@ -340,12 +340,20 @@ nsPNGDecoder::InitInternal() png_set_check_for_invalid_index(mPNG, 0); #endif -#if defined(PNG_SET_OPTION_SUPPORTED) && defined(PNG_sRGB_PROFILE_CHECKS) && \ - PNG_sRGB_PROFILE_CHECKS >= 0 +#ifdef PNG_SET_OPTION_SUPPORTED +#if defined(PNG_sRGB_PROFILE_CHECKS) && PNG_sRGB_PROFILE_CHECKS >= 0 // Skip checking of sRGB ICC profiles png_set_option(mPNG, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif +#ifdef PNG_MAXIMUM_INFLATE_WINDOW + // Force a larger zlib inflate window as some images in the wild have + // incorrectly set metadata (specifically CMF bits) which prevent us from + // decoding them otherwise. + png_set_option(mPNG, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON); +#endif +#endif + // use this as libpng "progressive pointer" (retrieve in callbacks) png_set_progressive_read_fn(mPNG, static_cast(this), nsPNGDecoder::info_callback, diff --git a/image/test/gtest/Common.cpp b/image/test/gtest/Common.cpp index 5a24bbb145a1..fbce02be325e 100644 --- a/image/test/gtest/Common.cpp +++ b/image/test/gtest/Common.cpp @@ -558,6 +558,15 @@ ImageTestCase CorruptICOWithBadBMPHeightTestCase() IntSize(100, 100), TEST_CASE_HAS_ERROR); } +ImageTestCase CorruptICOWithBadBppTestCase() +{ + // This test case is an ICO with a BPP (15) in the ICO header which differs + // from that in the BMP header itself (1). It should ignore the ICO BPP when + // the BMP BPP is available and thus correctly decode the image. + return ImageTestCase("corrupt-with-bad-ico-bpp.ico", "image/x-icon", + IntSize(100, 100), TEST_CASE_IS_TRANSPARENT); +} + ImageTestCase TransparentPNGTestCase() { return ImageTestCase("transparent.png", "image/png", IntSize(32, 32), diff --git a/image/test/gtest/Common.h b/image/test/gtest/Common.h index 79bed9fc1b23..3a4b7e0e1b7d 100644 --- a/image/test/gtest/Common.h +++ b/image/test/gtest/Common.h @@ -392,6 +392,7 @@ ImageTestCase CorruptTestCase(); ImageTestCase CorruptBMPWithTruncatedHeader(); ImageTestCase CorruptICOWithBadBMPWidthTestCase(); ImageTestCase CorruptICOWithBadBMPHeightTestCase(); +ImageTestCase CorruptICOWithBadBppTestCase(); ImageTestCase TransparentPNGTestCase(); ImageTestCase TransparentGIFTestCase(); diff --git a/image/test/gtest/TestDecoders.cpp b/image/test/gtest/TestDecoders.cpp index 58caa77a2e59..0eba5b34a53c 100644 --- a/image/test/gtest/TestDecoders.cpp +++ b/image/test/gtest/TestDecoders.cpp @@ -364,6 +364,11 @@ TEST_F(ImageDecoders, CorruptICOWithBadBMPHeightMultiChunk) CheckDecoderMultiChunk(CorruptICOWithBadBMPHeightTestCase()); } +TEST_F(ImageDecoders, CorruptICOWithBadBppSingleChunk) +{ + CheckDecoderSingleChunk(CorruptICOWithBadBppTestCase()); +} + TEST_F(ImageDecoders, AnimatedGIFWithFRAME_FIRST) { ImageTestCase testCase = GreenFirstFrameAnimatedGIFTestCase(); diff --git a/image/test/gtest/corrupt-with-bad-ico-bpp.ico b/image/test/gtest/corrupt-with-bad-ico-bpp.ico new file mode 100644 index 000000000000..5db4922e3486 Binary files /dev/null and b/image/test/gtest/corrupt-with-bad-ico-bpp.ico differ diff --git a/image/test/gtest/moz.build b/image/test/gtest/moz.build index 5cf6d5116f10..ad69e098518c 100644 --- a/image/test/gtest/moz.build +++ b/image/test/gtest/moz.build @@ -35,6 +35,7 @@ TEST_HARNESS_FILES.gtest += [ 'animated-with-extra-image-sub-blocks.gif', 'corrupt-with-bad-bmp-height.ico', 'corrupt-with-bad-bmp-width.ico', + 'corrupt-with-bad-ico-bpp.ico', 'corrupt.jpg', 'downscaled.bmp', 'downscaled.gif', diff --git a/js/src/doc/Debugger/Conventions.md b/js/src/doc/Debugger/Conventions.md index 211b222b9b0b..e8bd3ee430b9 100644 --- a/js/src/doc/Debugger/Conventions.md +++ b/js/src/doc/Debugger/Conventions.md @@ -110,6 +110,10 @@ resumption value has one of the following forms: the `new` expression returns the frame's `this` value. Similarly, if the function is the constructor for a subclass, then a non-object value may result in a TypeError. + If the frame is a generator or async function, then value must + conform to the iterator protocol: it must be a non-proxy object of the form + { done: boolean, value: v }, where + both `done` and `value` are ordinary properties. { throw: value } : Throw value as an exception from the current bytecode diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js new file mode 100644 index 000000000000..d4e7e857666c --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-async.js @@ -0,0 +1,130 @@ +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = Debugger(g); + +g.eval(` +async function f() { + return e; +} +`); + +// To continue testing after uncaught exception, remember the exception and +// return normal completeion. +var currentFrame; +var uncaughtException; +dbg.uncaughtExceptionHook = function(e) { + uncaughtException = e; + return { + return: currentFrame.eval("({ done: true, value: 'uncaught' })").return + }; +}; +function testUncaughtException() { + uncaughtException = undefined; + var val = g.eval(` +var val; +f().then(v => { val = v }); +drainJobQueue(); +val; +`); + assertEq(val, "uncaught"); + assertEq(uncaughtException instanceof TypeError, true); +} + +// Just continue +dbg.onExceptionUnwind = function(frame) { + return undefined; +}; +g.eval(` +var E; +f().catch(e => { exc = e }); +drainJobQueue(); +assertEq(exc instanceof ReferenceError, true); +`); + +// Should return object. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: "foo" + }; +}; +testUncaughtException(); + +// The object should have `done` property and `value` property. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({})").return + }; +}; +testUncaughtException(); + +// The object should have `done` property. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ value: 10 })").return + }; +}; +testUncaughtException(); + +// The object should have `value` property. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ done: true })").return + }; +}; +testUncaughtException(); + +// `done` property should be a boolean value. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ done: 10, value: 10 })").return + }; +}; +testUncaughtException(); + +// `done` property shouldn't be an accessor. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ get done() { return true; }, value: 10 })").return + }; +}; +testUncaughtException(); + +// `value` property shouldn't be an accessor. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ done: true, get value() { return 10; } })").return + }; +}; +testUncaughtException(); + +// The object shouldn't be a Proxy. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("new Proxy({ done: true, value: 10 }, {})").return + }; +}; +testUncaughtException(); + +// Correct resumption value. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ done: true, value: 10 })").return + }; +}; +var val = g.eval(` +var val; +f().then(v => { val = v }); +drainJobQueue(); +val; +`); +assertEq(val, 10); diff --git a/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-generator.js b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-generator.js new file mode 100644 index 000000000000..6239d9e7e7c6 --- /dev/null +++ b/js/src/jit-test/tests/debug/onExceptionUnwind-resumption-generator.js @@ -0,0 +1,117 @@ +load(libdir + "asserts.js"); + +var g = newGlobal(); +var dbg = Debugger(g); + +g.eval(` +function* f() { + e; +} +`); + +// To continue testing after uncaught exception, remember the exception and +// return normal completeion. +var currentFrame; +var uncaughtException; +dbg.uncaughtExceptionHook = function(e) { + uncaughtException = e; + return { + return: currentFrame.eval("({ done: true, value: 'uncaught' })").return + }; +}; +function testUncaughtException() { + uncaughtException = undefined; + var obj = g.eval(`f().next()`); + assertEq(obj.done, true); + assertEq(obj.value, 'uncaught'); + assertEq(uncaughtException instanceof TypeError, true); +} + +// Just continue +dbg.onExceptionUnwind = function(frame) { + return undefined; +}; +assertThrowsInstanceOf(() => g.eval(`f().next();`), g.ReferenceError); + +// Should return object. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: "foo" + }; +}; +testUncaughtException(); + +// The object should have `done` property and `value` property. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({})").return + }; +}; +testUncaughtException(); + +// The object should have `done` property. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ value: 10 })").return + }; +}; +testUncaughtException(); + +// The object should have `value` property. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ done: true })").return + }; +}; +testUncaughtException(); + +// `done` property should be a boolean value. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ done: 10, value: 10 })").return + }; +}; +testUncaughtException(); + +// `done` property shouldn't be an accessor. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ get done() { return true; }, value: 10 })").return + }; +}; +testUncaughtException(); + +// `value` property shouldn't be an accessor. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ done: true, get value() { return 10; } })").return + }; +}; +testUncaughtException(); + +// The object shouldn't be a Proxy. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("new Proxy({ done: true, value: 10 }, {})").return + }; +}; +testUncaughtException(); + +// Correct resumption value. +dbg.onExceptionUnwind = function(frame) { + currentFrame = frame; + return { + return: frame.eval("({ done: true, value: 10 })").return + }; +}; +var obj = g.eval(`f().next()`); +assertEq(obj.done, true); +assertEq(obj.value, 10); diff --git a/js/src/jit-test/tests/debug/resumption-06.js b/js/src/jit-test/tests/debug/resumption-06.js index 4b58e3e464b9..61e63f8acee9 100644 --- a/js/src/jit-test/tests/debug/resumption-06.js +++ b/js/src/jit-test/tests/debug/resumption-06.js @@ -7,7 +7,7 @@ load(libdir + 'iteration.js') var g = newGlobal(); g.debuggeeGlobal = this; g.eval("var dbg = new Debugger(debuggeeGlobal);" + - "dbg.onDebuggerStatement = function () { return {return: '!'}; };"); + "dbg.onDebuggerStatement = function (frame) { return { return: frame.eval(\"({ done: true, value: '!' })\").return }; };"); function* gen() { yield '1'; @@ -16,6 +16,6 @@ function* gen() { } var iter = gen(); assertIteratorNext(iter, '1'); -assertEq(iter.next(), '!'); +assertIteratorDone(iter, '!'); iter.next(); assertEq(0, 1); diff --git a/js/src/js.msg b/js/src/js.msg index f2b794082812..4f635d72ebe0 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -432,10 +432,12 @@ MSG_DEF(JSMSG_SC_SAB_DISABLED, 0, JSEXN_TYPEERR, "SharedArrayBuffer not // Debugger MSG_DEF(JSMSG_ASSIGN_FUNCTION_OR_NULL, 1, JSEXN_TYPEERR, "value assigned to {0} must be a function or null") +MSG_DEF(JSMSG_DEBUG_BAD_AWAIT, 0, JSEXN_TYPEERR, "await expression received invalid value") MSG_DEF(JSMSG_DEBUG_BAD_LINE, 0, JSEXN_TYPEERR, "invalid line number") MSG_DEF(JSMSG_DEBUG_BAD_OFFSET, 0, JSEXN_TYPEERR, "invalid script offset") MSG_DEF(JSMSG_DEBUG_BAD_REFERENT, 2, JSEXN_TYPEERR, "{0} does not refer to {1}") MSG_DEF(JSMSG_DEBUG_BAD_RESUMPTION, 0, JSEXN_TYPEERR, "debugger resumption value must be undefined, {throw: val}, {return: val}, or null") +MSG_DEF(JSMSG_DEBUG_BAD_YIELD, 0, JSEXN_TYPEERR, "generator yielded invalid value") MSG_DEF(JSMSG_DEBUG_CANT_DEBUG_GLOBAL, 0, JSEXN_TYPEERR, "passing non-debuggable global to addDebuggee") MSG_DEF(JSMSG_DEBUG_CCW_REQUIRED, 1, JSEXN_TYPEERR, "{0}: argument must be an object from a different compartment") MSG_DEF(JSMSG_DEBUG_COMPARTMENT_MISMATCH, 2, JSEXN_TYPEERR, "{0}: descriptor .{1} property is an object in a different compartment than the target object") diff --git a/js/src/tests/ecma_7/AsyncFunctions/BoundNames.js b/js/src/tests/ecma_2017/AsyncFunctions/BoundNames.js similarity index 100% rename from js/src/tests/ecma_7/AsyncFunctions/BoundNames.js rename to js/src/tests/ecma_2017/AsyncFunctions/BoundNames.js diff --git a/js/src/tests/ecma_7/AsyncFunctions/EarlyErrors.js b/js/src/tests/ecma_2017/AsyncFunctions/EarlyErrors.js similarity index 100% rename from js/src/tests/ecma_7/AsyncFunctions/EarlyErrors.js rename to js/src/tests/ecma_2017/AsyncFunctions/EarlyErrors.js diff --git a/js/src/tests/ecma_7/AsyncFunctions/arguments_callee.js b/js/src/tests/ecma_2017/AsyncFunctions/arguments_callee.js similarity index 100% rename from js/src/tests/ecma_7/AsyncFunctions/arguments_callee.js rename to js/src/tests/ecma_2017/AsyncFunctions/arguments_callee.js diff --git a/js/src/tests/ecma_7/AsyncFunctions/async-contains-unicode-escape.js b/js/src/tests/ecma_2017/AsyncFunctions/async-contains-unicode-escape.js similarity index 100% rename from js/src/tests/ecma_7/AsyncFunctions/async-contains-unicode-escape.js rename to js/src/tests/ecma_2017/AsyncFunctions/async-contains-unicode-escape.js diff --git a/js/src/tests/ecma_7/AsyncFunctions/clone.js b/js/src/tests/ecma_2017/AsyncFunctions/clone.js similarity index 100% rename from js/src/tests/ecma_7/AsyncFunctions/clone.js rename to js/src/tests/ecma_2017/AsyncFunctions/clone.js diff --git a/js/src/tests/ecma_7/AsyncFunctions/constructor.js b/js/src/tests/ecma_2017/AsyncFunctions/constructor.js similarity index 100% rename from js/src/tests/ecma_7/AsyncFunctions/constructor.js rename to js/src/tests/ecma_2017/AsyncFunctions/constructor.js diff --git a/js/src/tests/ecma_7/AsyncFunctions/identity.js b/js/src/tests/ecma_2017/AsyncFunctions/identity.js similarity index 100% rename from js/src/tests/ecma_7/AsyncFunctions/identity.js rename to js/src/tests/ecma_2017/AsyncFunctions/identity.js diff --git a/js/src/tests/ecma_7/AsyncFunctions/length.js b/js/src/tests/ecma_2017/AsyncFunctions/length.js similarity index 100% rename from js/src/tests/ecma_7/AsyncFunctions/length.js rename to js/src/tests/ecma_2017/AsyncFunctions/length.js diff --git a/js/src/tests/ecma_7/AsyncFunctions/methods.js b/js/src/tests/ecma_2017/AsyncFunctions/methods.js similarity index 100% rename from js/src/tests/ecma_7/AsyncFunctions/methods.js rename to js/src/tests/ecma_2017/AsyncFunctions/methods.js diff --git a/js/src/tests/ecma_7/AsyncFunctions/property.js b/js/src/tests/ecma_2017/AsyncFunctions/property.js similarity index 100% rename from js/src/tests/ecma_7/AsyncFunctions/property.js rename to js/src/tests/ecma_2017/AsyncFunctions/property.js diff --git a/js/src/tests/ecma_7/AsyncFunctions/semantics.js b/js/src/tests/ecma_2017/AsyncFunctions/semantics.js similarity index 100% rename from js/src/tests/ecma_7/AsyncFunctions/semantics.js rename to js/src/tests/ecma_2017/AsyncFunctions/semantics.js diff --git a/js/src/tests/ecma_7/AsyncFunctions/syntax-arrow.js b/js/src/tests/ecma_2017/AsyncFunctions/syntax-arrow.js similarity index 100% rename from js/src/tests/ecma_7/AsyncFunctions/syntax-arrow.js rename to js/src/tests/ecma_2017/AsyncFunctions/syntax-arrow.js diff --git a/js/src/tests/ecma_7/AsyncFunctions/syntax-modules.js b/js/src/tests/ecma_2017/AsyncFunctions/syntax-modules.js similarity index 100% rename from js/src/tests/ecma_7/AsyncFunctions/syntax-modules.js rename to js/src/tests/ecma_2017/AsyncFunctions/syntax-modules.js diff --git a/js/src/tests/ecma_7/AsyncFunctions/syntax.js b/js/src/tests/ecma_2017/AsyncFunctions/syntax.js similarity index 100% rename from js/src/tests/ecma_7/AsyncFunctions/syntax.js rename to js/src/tests/ecma_2017/AsyncFunctions/syntax.js diff --git a/js/src/tests/ecma_7/AsyncFunctions/toString.js b/js/src/tests/ecma_2017/AsyncFunctions/toString.js similarity index 100% rename from js/src/tests/ecma_7/AsyncFunctions/toString.js rename to js/src/tests/ecma_2017/AsyncFunctions/toString.js diff --git a/js/src/tests/ecma_7/AsyncFunctions/yield.js b/js/src/tests/ecma_2017/AsyncFunctions/yield.js similarity index 100% rename from js/src/tests/ecma_7/AsyncFunctions/yield.js rename to js/src/tests/ecma_2017/AsyncFunctions/yield.js diff --git a/js/src/tests/ecma_7/AsyncFunctions/shell.js b/js/src/tests/ecma_7/AsyncFunctions/shell.js deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/js/src/vm/AsyncFunction.cpp b/js/src/vm/AsyncFunction.cpp index eca1db94660f..bd0b4f32ae88 100644 --- a/js/src/vm/AsyncFunction.cpp +++ b/js/src/vm/AsyncFunction.cpp @@ -9,6 +9,7 @@ #include "jscompartment.h" #include "builtin/Promise.h" +#include "vm/GeneratorObject.h" #include "vm/GlobalObject.h" #include "vm/Interpreter.h" #include "vm/SelfHosting.h" @@ -232,3 +233,8 @@ js::IsWrappedAsyncFunction(JSFunction* fun) return fun->maybeNative() == WrappedAsyncFunction; } +MOZ_MUST_USE bool +js::CheckAsyncResumptionValue(JSContext* cx, HandleValue v) +{ + return CheckStarGeneratorResumptionValue(cx, v); +} diff --git a/js/src/vm/AsyncFunction.h b/js/src/vm/AsyncFunction.h index c7a2dd562447..ddf81a177486 100644 --- a/js/src/vm/AsyncFunction.h +++ b/js/src/vm/AsyncFunction.h @@ -32,6 +32,9 @@ MOZ_MUST_USE bool AsyncFunctionAwaitedRejected(JSContext* cx, Handle resultPromise, HandleValue generatorVal, HandleValue reason); +MOZ_MUST_USE bool +CheckAsyncResumptionValue(JSContext* cx, HandleValue v); + } // namespace js #endif /* vm_AsyncFunction_h */ diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 67d42d837b29..e7d93c3d6603 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -32,7 +32,9 @@ #include "js/Vector.h" #include "proxy/ScriptedProxyHandler.h" #include "vm/ArgumentsObject.h" +#include "vm/AsyncFunction.h" #include "vm/DebuggerMemory.h" +#include "vm/GeneratorObject.h" #include "vm/SPSProfiler.h" #include "vm/TraceLogging.h" #include "vm/WrapperObject.h" @@ -1557,6 +1559,24 @@ static bool CheckResumptionValue(JSContext* cx, AbstractFramePtr frame, const Maybe& maybeThisv, JSTrapStatus status, MutableHandleValue vp) { + if (status == JSTRAP_RETURN && frame && frame.isFunctionFrame()) { + // Don't let a { return: ... } resumption value make a generator or + // async function violate the iterator protocol. The return value from + // such a frame must have the form { done: , value: }. + RootedFunction callee(cx, frame.callee()); + if (callee->isAsync()) { + if (!CheckAsyncResumptionValue(cx, vp)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_AWAIT); + return false; + } + } else if (callee->isStarGenerator()) { + if (!CheckStarGeneratorResumptionValue(cx, vp)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_YIELD); + return false; + } + } + } + if (maybeThisv.isSome()) { const HandleValue& thisv = maybeThisv.ref(); if (status == JSTRAP_RETURN && vp.isPrimitive()) { diff --git a/js/src/vm/GeneratorObject.cpp b/js/src/vm/GeneratorObject.cpp index 25cacd29478c..690c0bf485c6 100644 --- a/js/src/vm/GeneratorObject.cpp +++ b/js/src/vm/GeneratorObject.cpp @@ -6,6 +6,8 @@ #include "vm/GeneratorObject.h" +#include "jsobj.h" + #include "jsatominlines.h" #include "jsscriptinlines.h" @@ -334,3 +336,32 @@ GlobalObject::initStarGenerators(JSContext* cx, Handle global) global->setReservedSlot(STAR_GENERATOR_FUNCTION_PROTO, ObjectValue(*genFunctionProto)); return true; } + +MOZ_MUST_USE bool +js::CheckStarGeneratorResumptionValue(JSContext* cx, HandleValue v) +{ + // yield/return value should be an Object. + if (!v.isObject()) + return false; + + JSObject* obj = &v.toObject(); + + // It should have `done` data property with boolean value. + Value doneVal; + if (!GetPropertyPure(cx, obj, NameToId(cx->names().done), &doneVal)) + return false; + if (!doneVal.isBoolean()) + return false; + + // It should have `value` data property, but the type doesn't matter + JSObject* ignored; + Shape* shape; + if (!LookupPropertyPure(cx, obj, NameToId(cx->names().value), &ignored, &shape)) + return false; + if (!shape) + return false; + if (!shape->hasDefaultGetter()) + return false; + + return true; +} diff --git a/js/src/vm/GeneratorObject.h b/js/src/vm/GeneratorObject.h index af19ee533f45..ca1452b34701 100644 --- a/js/src/vm/GeneratorObject.h +++ b/js/src/vm/GeneratorObject.h @@ -216,6 +216,9 @@ bool GeneratorThrowOrClose(JSContext* cx, AbstractFramePtr frame, Handle diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index d8cef483012a..8fe653cd9d60 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -147,7 +147,6 @@ using namespace mozilla::gfx; #define GRID_ENABLED_PREF_NAME "layout.css.grid.enabled" #define GRID_TEMPLATE_SUBGRID_ENABLED_PREF_NAME "layout.css.grid-template-subgrid-value.enabled" #define WEBKIT_PREFIXES_ENABLED_PREF_NAME "layout.css.prefixes.webkit" -#define DISPLAY_CONTENTS_ENABLED_PREF_NAME "layout.css.display-contents.enabled" #define TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME "layout.css.text-align-unsafe-value.enabled" #define FLOAT_LOGICAL_VALUES_ENABLED_PREF_NAME "layout.css.float-logical-values.enabled" #define BG_CLIP_TEXT_ENABLED_PREF_NAME "layout.css.background-clip-text.enabled" @@ -312,36 +311,6 @@ WebkitPrefixEnabledPrefChangeCallback(const char* aPrefName, void* aClosure) } } -// When the pref "layout.css.display-contents.enabled" changes, this function is -// invoked to let us update kDisplayKTable, to selectively disable or restore -// the entries for "contents" in that table. -static void -DisplayContentsEnabledPrefChangeCallback(const char* aPrefName, void* aClosure) -{ - NS_ASSERTION(strcmp(aPrefName, DISPLAY_CONTENTS_ENABLED_PREF_NAME) == 0, - "Did you misspell " DISPLAY_CONTENTS_ENABLED_PREF_NAME " ?"); - - static bool sIsDisplayContentsKeywordIndexInitialized; - static int32_t sIndexOfContentsInDisplayTable; - bool isDisplayContentsEnabled = - Preferences::GetBool(DISPLAY_CONTENTS_ENABLED_PREF_NAME, false); - - if (!sIsDisplayContentsKeywordIndexInitialized) { - // First run: find the position of "contents" in kDisplayKTable. - sIndexOfContentsInDisplayTable = - nsCSSProps::FindIndexOfKeyword(eCSSKeyword_contents, - nsCSSProps::kDisplayKTable); - sIsDisplayContentsKeywordIndexInitialized = true; - } - - // OK -- now, stomp on or restore the "contents" entry in kDisplayKTable, - // depending on whether the pref is enabled vs. disabled. - if (sIndexOfContentsInDisplayTable >= 0) { - nsCSSProps::kDisplayKTable[sIndexOfContentsInDisplayTable].mKeyword = - isDisplayContentsEnabled ? eCSSKeyword_contents : eCSSKeyword_UNKNOWN; - } -} - // When the pref "layout.css.text-align-unsafe-value.enabled" changes, this // function is called to let us update kTextAlignKTable & kTextAlignLastKTable, // to selectively disable or restore the entries for "unsafe" in those tables. @@ -7536,8 +7505,6 @@ static const PrefCallbacks kPrefCallbacks[] = { WebkitPrefixEnabledPrefChangeCallback }, { TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME, TextAlignUnsafeEnabledPrefChangeCallback }, - { DISPLAY_CONTENTS_ENABLED_PREF_NAME, - DisplayContentsEnabledPrefChangeCallback }, { FLOAT_LOGICAL_VALUES_ENABLED_PREF_NAME, FloatLogicalValuesEnabledPrefChangeCallback }, { BG_CLIP_TEXT_ENABLED_PREF_NAME, diff --git a/layout/reftests/css-display/reftest-stylo.list b/layout/reftests/css-display/reftest-stylo.list index 8f7e66498b2e..887114fef188 100644 --- a/layout/reftests/css-display/reftest-stylo.list +++ b/layout/reftests/css-display/reftest-stylo.list @@ -2,36 +2,36 @@ # Tests for CSS Display spec features. # http://dev.w3.org/csswg/css-display -fuzzy-if(Android,8,604) pref(layout.css.display-contents.enabled,true) == display-contents-acid.html display-contents-acid.html -random pref(layout.css.display-contents.enabled,true) == display-contents-acid-dyn-1.html display-contents-acid-dyn-1.html -random pref(layout.css.display-contents.enabled,true) == display-contents-acid-dyn-2.html display-contents-acid-dyn-2.html -random pref(layout.css.display-contents.enabled,true) == display-contents-acid-dyn-3.html display-contents-acid-dyn-3.html -pref(layout.css.display-contents.enabled,true) == display-contents-generated-content.html display-contents-generated-content.html -pref(layout.css.display-contents.enabled,true) == display-contents-generated-content-2.html display-contents-generated-content-2.html -pref(layout.css.display-contents.enabled,true) == display-contents-style-inheritance-1.html display-contents-style-inheritance-1.html -skip pref(layout.css.display-contents.enabled,true) == display-contents-style-inheritance-1-stylechange.html display-contents-style-inheritance-1-stylechange.html -skip pref(layout.css.display-contents.enabled,true) fuzzy-if(winWidget,12,100) == display-contents-style-inheritance-1-dom-mutations.html display-contents-style-inheritance-1-dom-mutations.html -pref(layout.css.display-contents.enabled,true) == display-contents-tables.xhtml display-contents-tables.xhtml -pref(layout.css.display-contents.enabled,true) == display-contents-tables-2.xhtml display-contents-tables-2.xhtml -pref(layout.css.display-contents.enabled,true) == display-contents-tables-3.xhtml display-contents-tables-3.xhtml -pref(layout.css.display-contents.enabled,true) == display-contents-visibility-hidden.html display-contents-visibility-hidden.html -pref(layout.css.display-contents.enabled,true) == display-contents-visibility-hidden-2.html display-contents-visibility-hidden-2.html -random pref(layout.css.display-contents.enabled,true) == display-contents-495385-2d.html display-contents-495385-2d.html -skip-if(B2G||Mulet) fuzzy-if(Android,7,3935) pref(layout.css.display-contents.enabled,true) == display-contents-xbl.xhtml display-contents-xbl.xhtml +fuzzy-if(Android,8,604) == display-contents-acid.html display-contents-acid.html +random == display-contents-acid-dyn-1.html display-contents-acid-dyn-1.html +random == display-contents-acid-dyn-2.html display-contents-acid-dyn-2.html +random == display-contents-acid-dyn-3.html display-contents-acid-dyn-3.html +== display-contents-generated-content.html display-contents-generated-content.html +== display-contents-generated-content-2.html display-contents-generated-content-2.html +== display-contents-style-inheritance-1.html display-contents-style-inheritance-1.html +skip == display-contents-style-inheritance-1-stylechange.html display-contents-style-inheritance-1-stylechange.html +skip fuzzy-if(winWidget,12,100) == display-contents-style-inheritance-1-dom-mutations.html display-contents-style-inheritance-1-dom-mutations.html +== display-contents-tables.xhtml display-contents-tables.xhtml +== display-contents-tables-2.xhtml display-contents-tables-2.xhtml +== display-contents-tables-3.xhtml display-contents-tables-3.xhtml +== display-contents-visibility-hidden.html display-contents-visibility-hidden.html +== display-contents-visibility-hidden-2.html display-contents-visibility-hidden-2.html +random == display-contents-495385-2d.html display-contents-495385-2d.html +skip-if(B2G||Mulet) fuzzy-if(Android,7,3935) == display-contents-xbl.xhtml display-contents-xbl.xhtml # Initial mulet triage: parity with B2G/B2G Desktop -# fuzzy-if(Android,7,1186) pref(layout.css.display-contents.enabled,true) pref(dom.webcomponents.enabled,true) == display-contents-shadow-dom-1.html display-contents-shadow-dom-1.html -skip-if(B2G||Mulet) pref(layout.css.display-contents.enabled,true) == display-contents-xbl-2.xul display-contents-xbl-2.xul +# fuzzy-if(Android,7,1186) pref(dom.webcomponents.enabled,true) == display-contents-shadow-dom-1.html display-contents-shadow-dom-1.html +skip-if(B2G||Mulet) == display-contents-xbl-2.xul display-contents-xbl-2.xul # Initial mulet triage: parity with B2G/B2G Desktop -skip-if(B2G||Mulet) asserts(1) pref(layout.css.display-contents.enabled,true) == display-contents-xbl-3.xul display-contents-xbl-3.xul +skip-if(B2G||Mulet) asserts(1) == display-contents-xbl-3.xul display-contents-xbl-3.xul # bug 1089223 # Initial mulet triage: parity with B2G/B2G Desktop -skip pref(layout.css.display-contents.enabled,true) == display-contents-xbl-4.xul display-contents-xbl-4.xul +skip == display-contents-xbl-4.xul display-contents-xbl-4.xul # fails (not just asserts) due to bug 1089223 -asserts(0-1) fuzzy-if(Android,8,3216) pref(layout.css.display-contents.enabled,true) == display-contents-fieldset.html display-contents-fieldset.html +asserts(0-1) fuzzy-if(Android,8,3216) == display-contents-fieldset.html display-contents-fieldset.html # bug 1089223 -skip-if(B2G||Mulet) asserts(1) pref(layout.css.display-contents.enabled,true) == display-contents-xbl-5.xul display-contents-xbl-5.xul +skip-if(B2G||Mulet) asserts(1) == display-contents-xbl-5.xul display-contents-xbl-5.xul # bug 1089223 # Initial mulet triage: parity with B2G/B2G Desktop -pref(layout.css.display-contents.enabled,true) == display-contents-list-item-child.html display-contents-list-item-child.html -pref(layout.css.display-contents.enabled,true) == display-contents-writing-mode-1.html display-contents-writing-mode-1.html -pref(layout.css.display-contents.enabled,true) == display-contents-writing-mode-2.html display-contents-writing-mode-2.html +== display-contents-list-item-child.html display-contents-list-item-child.html +== display-contents-writing-mode-1.html display-contents-writing-mode-1.html +== display-contents-writing-mode-2.html display-contents-writing-mode-2.html diff --git a/layout/reftests/css-display/reftest.list b/layout/reftests/css-display/reftest.list index d310422bb357..97f243b57199 100644 --- a/layout/reftests/css-display/reftest.list +++ b/layout/reftests/css-display/reftest.list @@ -1,28 +1,28 @@ # Tests for CSS Display spec features. # http://dev.w3.org/csswg/css-display -fuzzy-if(Android,8,604) pref(layout.css.display-contents.enabled,true) == display-contents-acid.html display-contents-acid-ref.html -fuzzy-if(Android,8,604) pref(layout.css.display-contents.enabled,true) == display-contents-acid-dyn-1.html display-contents-acid-ref.html -fuzzy-if(Android,8,604) pref(layout.css.display-contents.enabled,true) == display-contents-acid-dyn-2.html display-contents-acid-ref.html -fuzzy-if(Android,8,604) pref(layout.css.display-contents.enabled,true) == display-contents-acid-dyn-3.html display-contents-acid-ref.html -pref(layout.css.display-contents.enabled,true) == display-contents-generated-content.html display-contents-generated-content-ref.html -pref(layout.css.display-contents.enabled,true) == display-contents-generated-content-2.html display-contents-generated-content-ref.html -pref(layout.css.display-contents.enabled,true) == display-contents-style-inheritance-1.html display-contents-style-inheritance-1-ref.html -pref(layout.css.display-contents.enabled,true) == display-contents-style-inheritance-1-stylechange.html display-contents-style-inheritance-1-ref.html -pref(layout.css.display-contents.enabled,true) fuzzy-if(winWidget,12,100) == display-contents-style-inheritance-1-dom-mutations.html display-contents-style-inheritance-1-ref.html -pref(layout.css.display-contents.enabled,true) == display-contents-tables.xhtml display-contents-tables-ref.xhtml -pref(layout.css.display-contents.enabled,true) == display-contents-tables-2.xhtml display-contents-tables-ref.xhtml -pref(layout.css.display-contents.enabled,true) == display-contents-tables-3.xhtml display-contents-tables-3-ref.xhtml -pref(layout.css.display-contents.enabled,true) == display-contents-visibility-hidden.html display-contents-visibility-hidden-ref.html -pref(layout.css.display-contents.enabled,true) == display-contents-visibility-hidden-2.html display-contents-visibility-hidden-ref.html -pref(layout.css.display-contents.enabled,true) == display-contents-495385-2d.html display-contents-495385-2d-ref.html -fuzzy-if(Android,7,3935) pref(layout.css.display-contents.enabled,true) == display-contents-xbl.xhtml display-contents-xbl-ref.html -fuzzy-if(Android,7,1186) pref(layout.css.display-contents.enabled,true) pref(dom.webcomponents.enabled,true) == display-contents-shadow-dom-1.html display-contents-shadow-dom-1-ref.html -pref(layout.css.display-contents.enabled,true) == display-contents-xbl-2.xul display-contents-xbl-2-ref.xul -asserts(1) pref(layout.css.display-contents.enabled,true) == display-contents-xbl-3.xul display-contents-xbl-3-ref.xul # bug 1089223 -skip pref(layout.css.display-contents.enabled,true) == display-contents-xbl-4.xul display-contents-xbl-4-ref.xul # fails (not just asserts) due to bug 1089223 -asserts(0-1) fuzzy-if(Android,8,3216) pref(layout.css.display-contents.enabled,true) == display-contents-fieldset.html display-contents-fieldset-ref.html # bug 1089223 -asserts(1) pref(layout.css.display-contents.enabled,true) == display-contents-xbl-5.xul display-contents-xbl-3-ref.xul # bug 1089223 -pref(layout.css.display-contents.enabled,true) == display-contents-list-item-child.html display-contents-list-item-child-ref.html -pref(layout.css.display-contents.enabled,true) == display-contents-writing-mode-1.html display-contents-writing-mode-1-ref.html -pref(layout.css.display-contents.enabled,true) == display-contents-writing-mode-2.html display-contents-writing-mode-2-ref.html +fuzzy-if(Android,8,604) == display-contents-acid.html display-contents-acid-ref.html +fuzzy-if(Android,8,604) == display-contents-acid-dyn-1.html display-contents-acid-ref.html +fuzzy-if(Android,8,604) == display-contents-acid-dyn-2.html display-contents-acid-ref.html +fuzzy-if(Android,8,604) == display-contents-acid-dyn-3.html display-contents-acid-ref.html +== display-contents-generated-content.html display-contents-generated-content-ref.html +== display-contents-generated-content-2.html display-contents-generated-content-ref.html +== display-contents-style-inheritance-1.html display-contents-style-inheritance-1-ref.html +== display-contents-style-inheritance-1-stylechange.html display-contents-style-inheritance-1-ref.html +fuzzy-if(winWidget,12,100) == display-contents-style-inheritance-1-dom-mutations.html display-contents-style-inheritance-1-ref.html +== display-contents-tables.xhtml display-contents-tables-ref.xhtml +== display-contents-tables-2.xhtml display-contents-tables-ref.xhtml +== display-contents-tables-3.xhtml display-contents-tables-3-ref.xhtml +== display-contents-visibility-hidden.html display-contents-visibility-hidden-ref.html +== display-contents-visibility-hidden-2.html display-contents-visibility-hidden-ref.html +== display-contents-495385-2d.html display-contents-495385-2d-ref.html +fuzzy-if(Android,7,3935) == display-contents-xbl.xhtml display-contents-xbl-ref.html +fuzzy-if(Android,7,1186) pref(dom.webcomponents.enabled,true) == display-contents-shadow-dom-1.html display-contents-shadow-dom-1-ref.html +== display-contents-xbl-2.xul display-contents-xbl-2-ref.xul +asserts(1) == display-contents-xbl-3.xul display-contents-xbl-3-ref.xul # bug 1089223 +skip == display-contents-xbl-4.xul display-contents-xbl-4-ref.xul # fails (not just asserts) due to bug 1089223 +asserts(0-1) fuzzy-if(Android,8,3216) == display-contents-fieldset.html display-contents-fieldset-ref.html # bug 1089223 +asserts(1) == display-contents-xbl-5.xul display-contents-xbl-3-ref.xul # bug 1089223 +== display-contents-list-item-child.html display-contents-list-item-child-ref.html +== display-contents-writing-mode-1.html display-contents-writing-mode-1-ref.html +== display-contents-writing-mode-2.html display-contents-writing-mode-2-ref.html diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index 4b618d426626..ced9aa1e112a 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -1330,8 +1330,6 @@ KTableEntry nsCSSProps::kDisplayKTable[] = { { eCSSKeyword__webkit_inline_box, StyleDisplay::WebkitInlineBox }, { eCSSKeyword__webkit_flex, StyleDisplay::Flex }, { eCSSKeyword__webkit_inline_flex, StyleDisplay::InlineFlex }, - // The next entry is controlled by the layout.css.display-contents.enabled - // pref. { eCSSKeyword_contents, StyleDisplay::Contents }, { eCSSKeyword_UNKNOWN, -1 } }; diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index fc1efefc24e7..92fa4052541c 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -6671,9 +6671,7 @@ if (IsCSSPropertyPrefEnabled("layout.css.grid.enabled")) { }; } -if (IsCSSPropertyPrefEnabled("layout.css.display-contents.enabled")) { - gCSSProperties["display"].other_values.push("contents"); -} +gCSSProperties["display"].other_values.push("contents"); if (IsCSSPropertyPrefEnabled("layout.css.contain.enabled")) { gCSSProperties["contain"] = { diff --git a/media/libpng/pnglibconf.h b/media/libpng/pnglibconf.h index 6e6e58a93d84..3a39f86fac83 100644 --- a/media/libpng/pnglibconf.h +++ b/media/libpng/pnglibconf.h @@ -25,6 +25,7 @@ #define PNG_LINKAGE_DATA extern #define PNG_LINKAGE_FUNCTION extern #define PNG_MAX_GAMMA_8 11 +#define PNG_SET_OPTION_SUPPORTED #define PNG_sRGB_PROFILE_CHECKS -1 #define PNG_USER_CHUNK_CACHE_MAX 128 #define PNG_USER_CHUNK_MALLOC_MAX 4000000L diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index e3bc44e880cc..c25dcc2e600a 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2619,9 +2619,6 @@ pref("layout.css.grid-template-subgrid-value.enabled", false); // Is support for CSS contain enabled? pref("layout.css.contain.enabled", false); -// Is support for CSS display:contents enabled? -pref("layout.css.display-contents.enabled", true); - // Is support for CSS box-decoration-break enabled? pref("layout.css.box-decoration-break.enabled", true); diff --git a/taskcluster/ci/android-test/tests.yml b/taskcluster/ci/android-test/tests.yml index 0a3021c16f85..53caaa1e6714 100644 --- a/taskcluster/ci/android-test/tests.yml +++ b/taskcluster/ci/android-test/tests.yml @@ -95,7 +95,7 @@ mochitest: instance-size: xlarge chunks: by-test-platform: - android-4.3-arm7-api-15/debug: 26 + android-4.3-arm7-api-15/debug: 32 default: 20 loopback-video: true e10s: false diff --git a/testing/mozharness/scripts/release/generate-checksums.py b/testing/mozharness/scripts/release/generate-checksums.py index 15ef1b1c7148..a5a435101182 100644 --- a/testing/mozharness/scripts/release/generate-checksums.py +++ b/testing/mozharness/scripts/release/generate-checksums.py @@ -130,6 +130,7 @@ class ChecksumsGenerator(BaseScript, VirtualenvMixin, SigningMixin, VCSMixin, Bu r"^.*\.mar$", r"^.*Setup.*\.exe$", r"^.*\.xpi$", + r"^.*fennec.*\.apk$", ] def _get_bucket_name(self): diff --git a/testing/mozharness/scripts/release/push-candidate-to-releases.py b/testing/mozharness/scripts/release/push-candidate-to-releases.py index c66366e13b1c..5339fa38a83f 100644 --- a/testing/mozharness/scripts/release/push-candidate-to-releases.py +++ b/testing/mozharness/scripts/release/push-candidate-to-releases.py @@ -49,6 +49,7 @@ class ReleasePusher(BaseScript, VirtualenvMixin): r"^.*/host.*$", r"^.*/mar-tools/.*$", r"^.*robocop.apk$", + r"^.*bouncer.apk$", r"^.*contrib.*", r"^.*/beetmover-checksums/.*$", ], diff --git a/xpcom/glue/nsBaseHashtable.h b/xpcom/glue/nsBaseHashtable.h index 985aa3b7e713..f52df3dd186a 100644 --- a/xpcom/glue/nsBaseHashtable.h +++ b/xpcom/glue/nsBaseHashtable.h @@ -115,6 +115,22 @@ public: return ent->mData; } + /** + * Add key to the table if not already present, and return a reference to its + * value. If key is not already in the table then the value is default + * constructed. + */ + DataType& GetOrInsert(const KeyType& aKey) + { + EntryType* ent = this->GetEntry(aKey); + if (ent) { + return ent->mData; + } + + ent = this->PutEntry(aKey); + return ent->mData; + } + /** * put a new value for the associated key * @param aKey the key to put