diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index f6ecc7b991444..28fbed0af7404 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -1822,8 +1822,8 @@ nsEventStatus AsyncPanZoomController::OnScaleEnd( } nsEventStatus AsyncPanZoomController::HandleEndOfPan() { - MOZ_ASSERT(GetCurrentTouchBlock()); - GetCurrentTouchBlock()->GetOverscrollHandoffChain()->FlushRepaints(); + MOZ_ASSERT(GetCurrentTouchBlock() || GetCurrentPanGestureBlock()); + GetCurrentInputBlock()->GetOverscrollHandoffChain()->FlushRepaints(); ParentLayerPoint flingVelocity = GetVelocityVector(); // Clear our velocities; if DispatchFling() gives the fling to us, @@ -1845,7 +1845,7 @@ nsEventStatus AsyncPanZoomController::HandleEndOfPan() { if (flingVelocity.Length() < StaticPrefs::APZFlingMinVelocityThreshold()) { // Relieve overscroll now if needed, since we will not transition to a fling // animation and then an overscroll animation, and relieve it then. - GetCurrentTouchBlock() + GetCurrentInputBlock() ->GetOverscrollHandoffChain() ->SnapBackOverscrolledApzc(this); return nsEventStatus_eConsumeNoDefault; @@ -1856,8 +1856,8 @@ nsEventStatus AsyncPanZoomController::HandleEndOfPan() { // which nulls out mTreeManager, could be called concurrently. if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) { const FlingHandoffState handoffState{ - flingVelocity, GetCurrentTouchBlock()->GetOverscrollHandoffChain(), - false /* not handoff */, GetCurrentTouchBlock()->GetScrolledApzc()}; + flingVelocity, GetCurrentInputBlock()->GetOverscrollHandoffChain(), + false /* not handoff */, GetCurrentInputBlock()->GetScrolledApzc()}; treeManagerLocal->DispatchFling(this, handoffState); } return nsEventStatus_eConsumeNoDefault; @@ -2556,6 +2556,35 @@ nsEventStatus AsyncPanZoomController::OnPan(const PanGestureInput& aEvent, ScreenPoint physicalPanDisplacement = aEvent.mPanDisplacement; ParentLayerPoint logicalPanDisplacement = aEvent.UserMultipliedLocalPanDisplacement(); + if (aEvent.mDeltaType == PanGestureInput::PANDELTA_PAGE) { + // Pan events with page units are used by Gtk, so this replicates Gtk: + // https://gitlab.gnome.org/GNOME/gtk/blob/c734c7e9188b56f56c3a504abee05fa40c5475ac/gtk/gtkrange.c#L3065-3073 + CSSSize pageScrollSize; + CSSToParentLayerScale2D zoom; + { + // Grab the lock to access the frame metrics. + RecursiveMutexAutoLock lock(mRecursiveMutex); + pageScrollSize = mScrollMetadata.GetPageScrollAmount() / + Metrics().GetDevPixelsPerCSSPixel(); + zoom = Metrics().GetZoom(); + } + // scrollUnit* is in units of "ParentLayer pixels per page proportion"... + auto scrollUnitWidth = std::min(std::pow(pageScrollSize.width, 2.0 / 3.0), + pageScrollSize.width / 2.0) * + zoom.xScale; + auto scrollUnitHeight = std::min(std::pow(pageScrollSize.height, 2.0 / 3.0), + pageScrollSize.height / 2.0) * + zoom.yScale; + // ... and pan displacements are in units of "page proportion count" + // here, so the products of them and scrollUnit* are in ParentLayer pixels + ParentLayerPoint physicalPanDisplacementPL( + physicalPanDisplacement.x * scrollUnitWidth, + physicalPanDisplacement.y * scrollUnitHeight); + physicalPanDisplacement = ToScreenCoordinates(physicalPanDisplacementPL, + aEvent.mLocalPanStartPoint); + logicalPanDisplacement.x *= scrollUnitWidth; + logicalPanDisplacement.y *= scrollUnitHeight; + } MOZ_ASSERT(GetCurrentPanGestureBlock()); AdjustDeltaForAllowedScrollDirections( @@ -2609,6 +2638,12 @@ nsEventStatus AsyncPanZoomController::OnPanEnd(const PanGestureInput& aEvent) { mX.EndTouch(aEvent.mTime); mY.EndTouch(aEvent.mTime); + // Use HandleEndOfPan for fling on platforms that don't + // emit momentum events (Gtk). + if (aEvent.mSimulateMomentum) { + return HandleEndOfPan(); + } + // Drop any velocity on axes where we don't have room to scroll anyways // (in this APZC, or an APZC further in the handoff chain). // This ensures that we don't enlarge the display port unnecessarily. diff --git a/gfx/layers/ipc/LayersMessageUtils.h b/gfx/layers/ipc/LayersMessageUtils.h index 0ba8ac2165e68..67d39e6cbb545 100644 --- a/gfx/layers/ipc/LayersMessageUtils.h +++ b/gfx/layers/ipc/LayersMessageUtils.h @@ -167,25 +167,6 @@ struct ParamTraits { } }; -// Helper class for reading bitfields. -// If T has bitfields members, derive ParamTraits from BitfieldHelper. -template -struct BitfieldHelper { - // We need this helper because we can't get the address of a bitfield to - // pass directly to ReadParam. So instead we read it into a temporary bool - // and set the bitfield using a setter function - static bool ReadBoolForBitfield(const Message* aMsg, PickleIterator* aIter, - ParamType* aResult, - void (ParamType::*aSetter)(bool)) { - bool value; - if (ReadParam(aMsg, aIter, &value)) { - (aResult->*aSetter)(value); - return true; - } - return false; - } -}; - template <> struct ParamTraits : BitfieldHelper { diff --git a/ipc/glue/IPCMessageUtils.h b/ipc/glue/IPCMessageUtils.h index 86604566e4692..047d154601589 100644 --- a/ipc/glue/IPCMessageUtils.h +++ b/ipc/glue/IPCMessageUtils.h @@ -1088,6 +1088,25 @@ struct ParamTraits : EnumSerializer {}; +// Helper class for reading bitfields. +// If T has bitfields members, derive ParamTraits from BitfieldHelper. +template +struct BitfieldHelper { + // We need this helper because we can't get the address of a bitfield to + // pass directly to ReadParam. So instead we read it into a temporary bool + // and set the bitfield using a setter function + static bool ReadBoolForBitfield(const Message* aMsg, PickleIterator* aIter, + ParamType* aResult, + void (ParamType::*aSetter)(bool)) { + bool value; + if (ReadParam(aMsg, aIter, &value)) { + (aResult->*aSetter)(value); + return true; + } + return false; + } +}; + } /* namespace IPC */ #endif /* __IPC_GLUE_IPCMESSAGEUTILS_H__ */ diff --git a/js/src/gc/GCEnum.h b/js/src/gc/GCEnum.h index 4703717d3fc26..bc7455e5604b0 100644 --- a/js/src/gc/GCEnum.h +++ b/js/src/gc/GCEnum.h @@ -112,7 +112,14 @@ enum class ZealMode { _(RegExpStatics) \ _(RegExpSharedBytecode) \ _(TypedArrayElements) \ - _(TypeDescrTraceList) + _(TypeDescrTraceList) \ + _(NativeIterator) \ + _(JitScript) \ + _(ObjectGroupAddendum) \ + _(ScriptDebugScript) \ + _(BreakpointSite) \ + _(ForOfPIC) \ + _(ForOfPICStub) #define JS_FOR_EACH_MEMORY_USE(_) \ JS_FOR_EACH_PUBLIC_MEMORY_USE(_) \ diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp index f8f191b552387..a5c071f44280d 100644 --- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -701,7 +701,8 @@ void MemoryTracker::checkEmptyOnDestroy() { inline bool MemoryTracker::allowMultipleAssociations(MemoryUse use) const { // For most uses only one association is possible for each GC thing. Allow a // one-to-many relationship only where necessary. - return use == MemoryUse::RegExpSharedBytecode; + return use == MemoryUse::RegExpSharedBytecode || + use == MemoryUse::BreakpointSite || use == MemoryUse::ForOfPICStub; } void MemoryTracker::trackMemory(Cell* cell, size_t nbytes, MemoryUse use) { diff --git a/js/src/jit/BaselineCacheIRCompiler.cpp b/js/src/jit/BaselineCacheIRCompiler.cpp index 09b67a9b9308e..85c5394538b33 100644 --- a/js/src/jit/BaselineCacheIRCompiler.cpp +++ b/js/src/jit/BaselineCacheIRCompiler.cpp @@ -1831,7 +1831,7 @@ bool BaselineCacheIRCompiler::emitGuardAndGetIterator() { EmitPreBarrier(masm, iterObjAddr, MIRType::Object); // Mark iterator as active. - Address iterFlagsAddr(niScratch, NativeIterator::offsetOfFlags()); + Address iterFlagsAddr(niScratch, NativeIterator::offsetOfFlagsAndCount()); masm.storePtr(obj, iterObjAddr); masm.or32(Imm32(NativeIterator::Flags::Active), iterFlagsAddr); diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 392fa619ed5c5..0da32e593f8e4 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -3145,7 +3145,7 @@ void jit::DestroyJitScripts(FreeOp* fop, JSScript* script) { } if (script->hasJitScript()) { - JitScript::Destroy(script->zone(), script->jitScript()); + script->releaseJitScript(); } } diff --git a/js/src/jit/IonCacheIRCompiler.cpp b/js/src/jit/IonCacheIRCompiler.cpp index 07fa7d604d0e9..0afa8a0226be6 100644 --- a/js/src/jit/IonCacheIRCompiler.cpp +++ b/js/src/jit/IonCacheIRCompiler.cpp @@ -2210,7 +2210,7 @@ bool IonCacheIRCompiler::emitGuardAndGetIterator() { EmitPreBarrier(masm, iterObjAddr, MIRType::Object); // Mark iterator as active. - Address iterFlagsAddr(niScratch, NativeIterator::offsetOfFlags()); + Address iterFlagsAddr(niScratch, NativeIterator::offsetOfFlagsAndCount()); masm.storePtr(obj, iterObjAddr); masm.or32(Imm32(NativeIterator::Flags::Active), iterFlagsAddr); diff --git a/js/src/jit/JitScript.cpp b/js/src/jit/JitScript.cpp index 2f9096db44907..dcf56291dc34a 100644 --- a/js/src/jit/JitScript.cpp +++ b/js/src/jit/JitScript.cpp @@ -40,9 +40,10 @@ static size_t NumTypeSets(JSScript* script) { } JitScript::JitScript(JSScript* script, uint32_t typeSetOffset, - uint32_t bytecodeTypeMapOffset) + uint32_t bytecodeTypeMapOffset, uint32_t allocBytes) : typeSetOffset_(typeSetOffset), - bytecodeTypeMapOffset_(bytecodeTypeMapOffset) { + bytecodeTypeMapOffset_(bytecodeTypeMapOffset), + allocBytes_(allocBytes) { setTypesGeneration(script->zone()->types.generation); uint8_t* base = reinterpret_cast(this); @@ -98,8 +99,8 @@ bool JSScript::createJitScript(JSContext* cx) { uint32_t typeSetOffset = sizeof(JitScript) + numICEntries() * sizeof(ICEntry); uint32_t bytecodeTypeMapOffset = typeSetOffset + numTypeSets * sizeof(StackTypeSet); - UniquePtr jitScript( - new (raw) JitScript(this, typeSetOffset, bytecodeTypeMapOffset)); + UniquePtr jitScript(new (raw) JitScript( + this, typeSetOffset, bytecodeTypeMapOffset, allocSize.value())); // Sanity check the length computations. MOZ_ASSERT(jitScript->numICEntries() == numICEntries()); @@ -116,6 +117,7 @@ bool JSScript::createJitScript(JSContext* cx) { MOZ_ASSERT(!jitScript_); prepareForDestruction.release(); jitScript_ = jitScript.release(); + AddCellMemory(this, allocSize.value(), MemoryUse::JitScript); // We have a JitScript so we can set the script's jitCodeRaw_ pointer to the // Baseline Interpreter code. @@ -150,8 +152,14 @@ void JSScript::maybeReleaseJitScript() { return; } + releaseJitScript(); +} + +void JSScript::releaseJitScript() { MOZ_ASSERT(!hasIonScript()); + RemoveCellMemory(this, jitScript_->allocBytes(), MemoryUse::JitScript); + JitScript::Destroy(zone(), jitScript_); jitScript_ = nullptr; updateJitCodeRaw(runtimeFromMainThread()); diff --git a/js/src/jit/JitScript.h b/js/src/jit/JitScript.h index cefebd0f28c20..63772182f225f 100644 --- a/js/src/jit/JitScript.h +++ b/js/src/jit/JitScript.h @@ -115,6 +115,9 @@ class alignas(uintptr_t) JitScript final { // bytecode map queries are in linear order. uint32_t bytecodeTypeMapHint_ = 0; + // The size of this allocation. + uint32_t allocBytes_ = 0; + struct Flags { // Flag set when discarding JIT code to indicate this script is on the stack // and type information and JIT code should not be discarded. @@ -147,7 +150,7 @@ class alignas(uintptr_t) JitScript final { public: JitScript(JSScript* script, uint32_t typeSetOffset, - uint32_t bytecodeTypeMapOffset); + uint32_t bytecodeTypeMapOffset, uint32_t allocBytes); #ifdef DEBUG ~JitScript() { @@ -327,6 +330,8 @@ class alignas(uintptr_t) JitScript final { uint32_t idx); void removeDependentWasmImport(wasm::Instance& instance, uint32_t idx); void unlinkDependentWasmImports(); + + size_t allocBytes() const { return allocBytes_; } }; // Ensures no JitScripts are purged in the current zone. diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index 13b92db3d9723..521509ac938f5 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -3462,7 +3462,7 @@ void MacroAssembler::debugAssertObjHasFixedSlots(Register obj, void MacroAssembler::branchIfNativeIteratorNotReusable(Register ni, Label* notReusable) { // See NativeIterator::isReusable. - Address flagsAddr(ni, NativeIterator::offsetOfFlags()); + Address flagsAddr(ni, NativeIterator::offsetOfFlagsAndCount()); #ifdef DEBUG Label niIsInitialized; @@ -3530,7 +3530,7 @@ void MacroAssembler::iteratorClose(Register obj, Register temp1, Register temp2, // Clear active bit. and32(Imm32(~NativeIterator::Flags::Active), - Address(temp1, NativeIterator::offsetOfFlags())); + Address(temp1, NativeIterator::offsetOfFlagsAndCount())); // Reset property cursor. loadPtr(Address(temp1, NativeIterator::offsetOfGuardsEnd()), temp2); diff --git a/js/src/vm/Iteration.cpp b/js/src/vm/Iteration.cpp index ce423aedbe75f..91a39a4b06295 100644 --- a/js/src/vm/Iteration.cpp +++ b/js/src/vm/Iteration.cpp @@ -592,14 +592,7 @@ static PropertyIteratorObject* NewPropertyIteratorObject(JSContext* cx) { return res; } -static PropertyIteratorObject* CreatePropertyIterator( - JSContext* cx, Handle objBeingIterated, HandleIdVector props, - uint32_t numGuards, uint32_t guardKey) { - Rooted propIter(cx, NewPropertyIteratorObject(cx)); - if (!propIter) { - return nullptr; - } - +static inline size_t ExtraStringCount(size_t propertyCount, size_t guardCount) { static_assert(sizeof(ReceiverGuard) == 2 * sizeof(GCPtrFlatString), "NativeIterators are allocated in space for 1) themselves, " "2) the properties a NativeIterator iterates (as " @@ -608,9 +601,24 @@ static PropertyIteratorObject* CreatePropertyIterator( "this size-relationship when determining the extra space to " "allocate"); - size_t extraCount = props.length() + numGuards * 2; - void* mem = - cx->pod_malloc_with_extra(extraCount); + return propertyCount + guardCount * 2; +} + +static inline size_t AllocationSize(size_t propertyCount, size_t guardCount) { + return sizeof(NativeIterator) + + ExtraStringCount(propertyCount, guardCount) * sizeof(GCPtrFlatString); +} + +static PropertyIteratorObject* CreatePropertyIterator( + JSContext* cx, Handle objBeingIterated, HandleIdVector props, + uint32_t numGuards, uint32_t guardKey) { + Rooted propIter(cx, NewPropertyIteratorObject(cx)); + if (!propIter) { + return nullptr; + } + + void* mem = cx->pod_malloc_with_extra( + ExtraStringCount(props.length(), numGuards)); if (!mem) { return nullptr; } @@ -680,7 +688,7 @@ NativeIterator::NativeIterator(JSContext* cx, reinterpret_cast(guardsBegin() + numGuards)), propertiesEnd_(propertyCursor_), guardKey_(guardKey), - flags_(0) // note: no Flags::Initialized + flagsAndCount_(0) // note: no Flags::Initialized { MOZ_ASSERT(!*hadError); @@ -688,6 +696,15 @@ NativeIterator::NativeIterator(JSContext* cx, // can only free |this| (and not leak it) if this has happened. propIter->setNativeIterator(this); + if (!setInitialPropertyCount(props.length())) { + ReportAllocationOverflow(cx); + *hadError = true; + return; + } + + size_t nbytes = AllocationSize(props.length(), numGuards); + AddCellMemory(propIter, nbytes, MemoryUse::NativeIterator); + for (size_t i = 0, len = props.length(); i < len; i++) { JSFlatString* str = IdToString(cx, props[i]); if (!str) { @@ -754,6 +771,11 @@ NativeIterator::NativeIterator(JSContext* cx, MOZ_ASSERT(!*hadError); } +inline size_t NativeIterator::allocationSize() const { + size_t numGuards = guardsEnd() - guardsBegin(); + return AllocationSize(initialPropertyCount(), numGuards); +} + /* static */ bool IteratorHashPolicy::match(PropertyIteratorObject* obj, const Lookup& lookup) { @@ -1059,7 +1081,7 @@ void PropertyIteratorObject::trace(JSTracer* trc, JSObject* obj) { void PropertyIteratorObject::finalize(FreeOp* fop, JSObject* obj) { if (NativeIterator* ni = obj->as().getNativeIterator()) { - fop->free_(ni); + fop->free_(obj, ni, ni->allocationSize(), MemoryUse::NativeIterator); } } diff --git a/js/src/vm/Iteration.h b/js/src/vm/Iteration.h index f7584517b8ea9..c24c6f076580e 100644 --- a/js/src/vm/Iteration.h +++ b/js/src/vm/Iteration.h @@ -87,7 +87,13 @@ struct NativeIterator { }; private: - uint32_t flags_ = 0; // consists of Flags bits + static constexpr uint32_t FlagsBits = 3; + static constexpr uint32_t FlagsMask = (1 << FlagsBits) - 1; + static constexpr uint32_t PropCountLimit = 1 << (32 - FlagsBits); + + // Stores Flags bits in the lower bits and the initial property count above + // them. + uint32_t flagsAndCount_ = 0; /* While in compartment->enumerators, these form a doubly linked list. */ NativeIterator* next_ = nullptr; @@ -223,31 +229,50 @@ struct NativeIterator { uint32_t guardKey() const { return guardKey_; } - bool isInitialized() const { return flags_ & Flags::Initialized; } + bool isInitialized() const { return flags() & Flags::Initialized; } + + size_t allocationSize() const; private: + uint32_t flags() const { return flagsAndCount_ & FlagsMask; } + + uint32_t initialPropertyCount() const { return flagsAndCount_ >> FlagsBits; } + + void setFlags(uint32_t flags) { + MOZ_ASSERT((flags & ~FlagsMask) == 0); + flagsAndCount_ = (initialPropertyCount() << FlagsBits) | flags; + } + + MOZ_MUST_USE bool setInitialPropertyCount(uint32_t count) { + if (count >= PropCountLimit) { + return false; + } + flagsAndCount_ = (count << FlagsBits) | flags(); + return true; + } + void markInitialized() { - MOZ_ASSERT(flags_ == 0); - flags_ = Flags::Initialized; + MOZ_ASSERT(flags() == 0); + setFlags(Flags::Initialized); } public: bool isActive() const { MOZ_ASSERT(isInitialized()); - return flags_ & Flags::Active; + return flags() & Flags::Active; } void markActive() { MOZ_ASSERT(isInitialized()); - flags_ |= Flags::Active; + flagsAndCount_ |= Flags::Active; } void markInactive() { MOZ_ASSERT(isInitialized()); - flags_ &= ~Flags::Active; + flagsAndCount_ &= ~Flags::Active; } bool isReusable() const { @@ -258,13 +283,13 @@ struct NativeIterator { // |Flags::Initialized| is set. Using |Flags::NotReusable| to test // would also work, but this formulation is safer against memory // corruption. - return flags_ == Flags::Initialized; + return flags() == Flags::Initialized; } void markHasUnvisitedPropertyDeletion() { MOZ_ASSERT(isInitialized()); - flags_ |= Flags::HasUnvisitedPropertyDeletion; + flagsAndCount_ |= Flags::HasUnvisitedPropertyDeletion; } void link(NativeIterator* other) { @@ -310,8 +335,8 @@ struct NativeIterator { return offsetof(NativeIterator, propertiesEnd_); } - static constexpr size_t offsetOfFlags() { - return offsetof(NativeIterator, flags_); + static constexpr size_t offsetOfFlagsAndCount() { + return offsetof(NativeIterator, flagsAndCount_); } static constexpr size_t offsetOfNext() { diff --git a/js/src/vm/JSScript.cpp b/js/src/vm/JSScript.cpp index a08eb280d1729..2943f86ab89ce 100644 --- a/js/src/vm/JSScript.cpp +++ b/js/src/vm/JSScript.cpp @@ -4657,24 +4657,25 @@ void JSScript::destroyDebugScript(FreeOp* fop) { if (hasDebugScript()) { #ifdef DEBUG for (jsbytecode* pc = code(); pc < codeEnd(); pc++) { - if (BreakpointSite* site = getBreakpointSite(pc)) { - /* Breakpoints are swept before finalization. */ - MOZ_ASSERT(site->firstBreakpoint() == nullptr); - MOZ_ASSERT(getBreakpointSite(pc) == nullptr); - } + MOZ_ASSERT(!getBreakpointSite(pc)); } #endif - fop->free_(releaseDebugScript()); + freeDebugScript(fop); } } +void JSScript::freeDebugScript(FreeOp* fop) { + MOZ_ASSERT(hasDebugScript()); + fop->free_(this, releaseDebugScript(), DebugScript::allocSize(length()), + MemoryUse::ScriptDebugScript); +} + DebugScript* JSScript::getOrCreateDebugScript(JSContext* cx) { if (hasDebugScript()) { return debugScript(); } - size_t nbytes = - offsetof(DebugScript, breakpoints) + length() * sizeof(BreakpointSite*); + size_t nbytes = DebugScript::allocSize(length()); UniqueDebugScript debug( reinterpret_cast(cx->pod_calloc(nbytes))); if (!debug) { @@ -4699,6 +4700,7 @@ DebugScript* JSScript::getOrCreateDebugScript(JSContext* cx) { setFlag(MutableFlags::HasDebugScript); // safe to set this; we can't fail // after this point + AddCellMemory(this, nbytes, MemoryUse::ScriptDebugScript); /* * Ensure that any Interpret() instances running on this script have @@ -4744,7 +4746,7 @@ void JSScript::decrementGeneratorObserverCount(js::FreeOp* fop) { debug->generatorObserverCount--; if (!debug->needed()) { - fop->free_(releaseDebugScript()); + destroyDebugScript(fop); } } @@ -4783,7 +4785,7 @@ void JSScript::decrementStepperCount(FreeOp* fop) { } if (!debug->needed()) { - fop->free_(releaseDebugScript()); + freeDebugScript(fop); } } } @@ -4805,6 +4807,7 @@ BreakpointSite* JSScript::getOrCreateBreakpointSite(JSContext* cx, return nullptr; } debug->numSites++; + AddCellMemory(this, sizeof(JSBreakpointSite), MemoryUse::BreakpointSite); } return site; @@ -4815,12 +4818,14 @@ void JSScript::destroyBreakpointSite(FreeOp* fop, jsbytecode* pc) { BreakpointSite*& site = debug->breakpoints[pcToOffset(pc)]; MOZ_ASSERT(site); + RemoveCellMemory(this, sizeof(JSBreakpointSite), MemoryUse::BreakpointSite); + fop->delete_(site); site = nullptr; debug->numSites--; if (!debug->needed()) { - fop->free_(releaseDebugScript()); + freeDebugScript(fop); } } diff --git a/js/src/vm/JSScript.h b/js/src/vm/JSScript.h index 94bbae140a910..9e5fbbefb5145 100644 --- a/js/src/vm/JSScript.h +++ b/js/src/vm/JSScript.h @@ -298,6 +298,11 @@ class DebugScript { bool needed() const { return generatorObserverCount > 0 || stepperCount > 0 || numSites > 0; } + + static size_t allocSize(size_t codeLength) { + return offsetof(DebugScript, breakpoints) + + codeLength * sizeof(BreakpointSite*); + } }; using UniqueDebugScript = js::UniquePtr; @@ -2661,6 +2666,7 @@ class JSScript : public js::gc::TenuredCell { js::jit::JitScript* jitScript() { return jitScript_; } void maybeReleaseJitScript(); + void releaseJitScript(); inline js::GlobalObject& global() const; inline bool hasGlobal(const js::GlobalObject* global) const; @@ -2958,6 +2964,7 @@ class JSScript : public js::gc::TenuredCell { js::DebugScript* debugScript(); js::DebugScript* releaseDebugScript(); void destroyDebugScript(js::FreeOp* fop); + void freeDebugScript(js::FreeOp* fop); bool hasDebugScript() const { return hasFlag(MutableFlags::HasDebugScript); } diff --git a/js/src/vm/ObjectGroup.cpp b/js/src/vm/ObjectGroup.cpp index b66693cb12da2..bebdf828dee7b 100644 --- a/js/src/vm/ObjectGroup.cpp +++ b/js/src/vm/ObjectGroup.cpp @@ -48,14 +48,16 @@ ObjectGroup::ObjectGroup(const Class* clasp, TaggedProto proto, } void ObjectGroup::finalize(FreeOp* fop) { - if (newScriptDontCheckGeneration()) { - newScriptDontCheckGeneration()->clear(); + if (auto newScript = newScriptDontCheckGeneration()) { + newScript->clear(); + fop->delete_(this, newScript, newScript->gcMallocBytes(), + MemoryUse::ObjectGroupAddendum); } - fop->delete_(newScriptDontCheckGeneration()); if (maybePreliminaryObjectsDontCheckGeneration()) { maybePreliminaryObjectsDontCheckGeneration()->clear(); } - fop->delete_(maybePreliminaryObjectsDontCheckGeneration()); + fop->delete_(this, maybePreliminaryObjectsDontCheckGeneration(), + MemoryUse::ObjectGroupAddendum); } void ObjectGroup::setProtoUnchecked(TaggedProto proto) { @@ -78,11 +80,27 @@ size_t ObjectGroup::sizeOfExcludingThis( return n; } +static inline size_t AddendumAllocSize(ObjectGroup::AddendumKind kind, + void* addendum) { + if (kind == ObjectGroup::Addendum_NewScript) { + auto newScript = static_cast(addendum); + return newScript->gcMallocBytes(); + } + if (kind == ObjectGroup::Addendum_PreliminaryObjects) { + return sizeof(PreliminaryObjectArrayWithTemplate); + } + // Other addendum kinds point to GC memory tracked elsewhere. + return 0; +} + void ObjectGroup::setAddendum(AddendumKind kind, void* addendum, bool writeBarrier /* = true */) { MOZ_ASSERT(!needsSweep()); MOZ_ASSERT(kind <= (OBJECT_FLAG_ADDENDUM_MASK >> OBJECT_FLAG_ADDENDUM_SHIFT)); + RemoveCellMemory(this, AddendumAllocSize(addendumKind(), addendum_), + MemoryUse::ObjectGroupAddendum); + if (writeBarrier) { // Manually trigger barriers if we are clearing new script or // preliminary object information. Other addendums are immutable. @@ -105,6 +123,9 @@ void ObjectGroup::setAddendum(AddendumKind kind, void* addendum, flags_ &= ~OBJECT_FLAG_ADDENDUM_MASK; flags_ |= kind << OBJECT_FLAG_ADDENDUM_SHIFT; addendum_ = addendum; + + AddCellMemory(this, AddendumAllocSize(kind, addendum), + MemoryUse::ObjectGroupAddendum); } /* static */ diff --git a/js/src/vm/PIC.cpp b/js/src/vm/PIC.cpp index 8186e81ab7694..ebc0f18e3ee54 100644 --- a/js/src/vm/PIC.cpp +++ b/js/src/vm/PIC.cpp @@ -19,6 +19,25 @@ using namespace js; +template +void PICChain::addStub(JSObject* obj, CatStub* stub) { + MOZ_ASSERT(stub); + MOZ_ASSERT(!stub->next()); + + AddCellMemory(obj, sizeof(CatStub), MemoryUse::ForOfPICStub); + + if (!stubs_) { + stubs_ = stub; + return; + } + + CatStub* cur = stubs_; + while (cur->next()) { + cur = cur->next(); + } + cur->append(stub); +} + bool js::ForOfPIC::Chain::initialize(JSContext* cx) { MOZ_ASSERT(!initialized_); @@ -106,7 +125,7 @@ bool js::ForOfPIC::Chain::tryOptimizeArray(JSContext* cx, } else if (!disabled_ && !isArrayStateStillSane()) { // Otherwise, if array state is no longer sane, reinitialize. - reset(); + reset(cx); if (!initialize(cx)) { return false; @@ -142,7 +161,7 @@ bool js::ForOfPIC::Chain::tryOptimizeArray(JSContext* cx, // existing cache before adding new stubs. We shouldn't really have heavy // churn on these. if (numStubs() >= MAX_STUBS) { - eraseChain(); + eraseChain(cx); } // Good to optimize now, create stub to add. @@ -153,7 +172,7 @@ bool js::ForOfPIC::Chain::tryOptimizeArray(JSContext* cx, } // Add the stub. - addStub(stub); + addStub(picObject_, stub); *optimized = true; return true; @@ -172,7 +191,7 @@ bool js::ForOfPIC::Chain::tryOptimizeArrayIteratorNext(JSContext* cx, } } else if (!disabled_ && !isArrayNextStillSane()) { // Otherwise, if array iterator state is no longer sane, reinitialize. - reset(); + reset(cx); if (!initialize(cx)) { return false; @@ -222,12 +241,12 @@ bool js::ForOfPIC::Chain::isArrayStateStillSane() { return isArrayNextStillSane(); } -void js::ForOfPIC::Chain::reset() { +void js::ForOfPIC::Chain::reset(JSContext* cx) { // Should never reset a disabled_ stub. MOZ_ASSERT(!disabled_); // Erase the chain. - eraseChain(); + eraseChain(cx); arrayProto_ = nullptr; arrayIteratorProto_ = nullptr; @@ -243,22 +262,16 @@ void js::ForOfPIC::Chain::reset() { initialized_ = false; } -void js::ForOfPIC::Chain::eraseChain() { +void js::ForOfPIC::Chain::eraseChain(JSContext* cx) { // Should never need to clear the chain of a disabled stub. MOZ_ASSERT(!disabled_); - - // Free all stubs. - Stub* stub = stubs_; - while (stub) { - Stub* next = stub->next(); - js_delete(stub); - stub = next; - } - stubs_ = nullptr; + freeAllStubs(cx->defaultFreeOp()); } // Trace the pointers stored directly on the stub. void js::ForOfPIC::Chain::trace(JSTracer* trc) { + TraceEdge(trc, &picObject_, "ForOfPIC object"); + if (!initialized_ || disabled_) { return; } @@ -274,28 +287,33 @@ void js::ForOfPIC::Chain::trace(JSTracer* trc) { TraceEdge(trc, &canonicalNextFunc_, "ForOfPIC ArrayIterator.prototype.next builtin."); - // Free all the stubs in the chain. - while (stubs_) { - removeStub(stubs_, nullptr); - } -} - -void js::ForOfPIC::Chain::sweep(FreeOp* fop) { - // Free all the stubs in the chain. - while (stubs_) { - Stub* next = stubs_->next(); - fop->delete_(stubs_); - stubs_ = next; + if (trc->isMarkingTracer()) { + // Free all the stubs in the chain. + freeAllStubs(trc->runtime()->defaultFreeOp()); } - fop->delete_(this); } static void ForOfPIC_finalize(FreeOp* fop, JSObject* obj) { MOZ_ASSERT(fop->maybeOnHelperThread()); if (ForOfPIC::Chain* chain = ForOfPIC::fromJSObject(&obj->as())) { - chain->sweep(fop); + chain->finalize(fop, obj); + } +} + +void js::ForOfPIC::Chain::finalize(FreeOp* fop, JSObject* obj) { + freeAllStubs(fop); + fop->delete_(obj, this, MemoryUse::ForOfPIC); +} + +void js::ForOfPIC::Chain::freeAllStubs(FreeOp* fop) { + Stub* stub = stubs_; + while (stub) { + Stub* next = stub->next(); + fop->delete_(picObject_, stub, MemoryUse::ForOfPICStub); + stub = next; } + stubs_ = nullptr; } static void ForOfPIC_traceObject(JSTracer* trc, JSObject* obj) { @@ -330,10 +348,11 @@ NativeObject* js::ForOfPIC::createForOfPICObject(JSContext* cx, if (!obj) { return nullptr; } - ForOfPIC::Chain* chain = cx->new_(); + ForOfPIC::Chain* chain = cx->new_(obj); if (!chain) { return nullptr; } + InitObjectPrivate(obj, chain, MemoryUse::ForOfPIC); obj->setPrivate(chain); return obj; } diff --git a/js/src/vm/PIC.h b/js/src/vm/PIC.h index 667336d621c1c..e28e146d215a6 100644 --- a/js/src/vm/PIC.h +++ b/js/src/vm/PIC.h @@ -64,20 +64,7 @@ class PICChain { public: CatStub* stubs() const { return stubs_; } - void addStub(CatStub* stub) { - MOZ_ASSERT(stub); - MOZ_ASSERT(!stub->next()); - if (!stubs_) { - stubs_ = stub; - return; - } - - CatStub* cur = stubs_; - while (cur->next()) { - cur = cur->next(); - } - cur->append(stub); - } + void addStub(JSObject* obj, CatStub* stub); unsigned numStubs() const { unsigned count = 0; @@ -86,17 +73,6 @@ class PICChain { } return count; } - - void removeStub(CatStub* stub, CatStub* previous) { - if (previous) { - MOZ_ASSERT(previous->next() == stub); - previous->next_ = stub->next(); - } else { - MOZ_ASSERT(stub == stubs_); - stubs_ = stub->next(); - } - js_delete(stub); - } }; /* @@ -158,6 +134,9 @@ struct ForOfPIC { */ class Chain : public BaseChain { private: + // Pointer to owning JSObject for memory accounting purposes. + GCPtrObject picObject_; + // Pointer to canonical Array.prototype and ArrayIterator.prototype GCPtrNativeObject arrayProto_; GCPtrNativeObject arrayIteratorProto_; @@ -184,8 +163,9 @@ struct ForOfPIC { static const unsigned MAX_STUBS = 10; public: - Chain() + explicit Chain(JSObject* picObject) : BaseChain(), + picObject_(picObject), arrayProto_(nullptr), arrayIteratorProto_(nullptr), arrayProtoShape_(nullptr), @@ -207,7 +187,7 @@ struct ForOfPIC { bool tryOptimizeArrayIteratorNext(JSContext* cx, bool* optimized); void trace(JSTracer* trc); - void sweep(FreeOp* fop); + void finalize(FreeOp* fop, JSObject* obj); private: // Check if the global array-related objects have not been messed with @@ -226,10 +206,12 @@ struct ForOfPIC { bool hasMatchingStub(ArrayObject* obj); // Reset the PIC and all info associated with it. - void reset(); + void reset(JSContext* cx); // Erase the stub chain. - void eraseChain(); + void eraseChain(JSContext* cx); + + void freeAllStubs(FreeOp* fop); }; // Class for object that holds ForOfPIC chain. diff --git a/js/src/vm/TypeInference-inl.h b/js/src/vm/TypeInference-inl.h index 9bcb1f1d7922c..8e4b02d9b6e30 100644 --- a/js/src/vm/TypeInference-inl.h +++ b/js/src/vm/TypeInference-inl.h @@ -282,6 +282,12 @@ static inline const char* TypeIdString(jsid id) { // complex ways which can't be understood statically. class TypeNewScript { private: + // Variable-length list of TypeNewScriptInitializer pointers. + struct InitializerList { + size_t length; + TypeNewScriptInitializer entries[1]; + }; + // Scripted function which this information was computed for. HeapPtr function_ = {}; @@ -303,7 +309,7 @@ class TypeNewScript { // shape. Property assignments in inner frames are preceded by a series of // SETPROP_FRAME entries specifying the stack down to the frame containing // the write. - TypeNewScriptInitializer* initializerList = nullptr; + InitializerList* initializerList = nullptr; // If there are additional properties found by the acquired properties // analysis which were not found by the definite properties analysis, this @@ -356,9 +362,13 @@ class TypeNewScript { size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + size_t gcMallocBytes() const; + static size_t offsetOfPreliminaryObjects() { return offsetof(TypeNewScript, preliminaryObjects); } + + static size_t sizeOfInitializerList(size_t length); }; inline bool ObjectGroup::hasUnanalyzedPreliminaryObjects() { diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index ac098288546e9..526e7758040a9 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -3679,6 +3679,26 @@ size_t TypeNewScript::sizeOfIncludingThis( return n; } +/* static */ size_t TypeNewScript::sizeOfInitializerList(size_t length) { + // TypeNewScriptInitializer struct contains [1] element in its size. + size_t size = sizeof(TypeNewScript::InitializerList); + if (length > 0) { + size += (length - 1) * sizeof(TypeNewScriptInitializer); + } + return size; +} + +size_t TypeNewScript::gcMallocBytes() const { + size_t size = sizeof(TypeNewScript); + if (preliminaryObjects) { + size += sizeof(PreliminaryObjectArray); + } + if (initializerList) { + size += sizeOfInitializerList(initializerList->length); + } + return size; +} + void TypeNewScript::registerNewObject(PlainObject* res) { MOZ_ASSERT(!analyzed()); @@ -3835,6 +3855,8 @@ bool TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, MOZ_ASSERT(OnlyHasDataProperties(templateObject()->lastProperty())); + MOZ_ASSERT(!initializerList); + InitializerList* newInitializerList = nullptr; if (templateObject()->slotSpan() != 0) { // Make sure that all definite properties found are reflected in the // prefix shape. Otherwise, the constructor behaved differently before @@ -3866,25 +3888,27 @@ bool TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, } } - TypeNewScriptInitializer done(TypeNewScriptInitializer::DONE, 0); - - if (!initializerVector.append(done)) { - return false; - } - - initializerList = group->zone()->pod_calloc( - initializerVector.length()); - if (!initializerList) { + size_t nbytes = sizeOfInitializerList(initializerVector.length()); + newInitializerList = reinterpret_cast( + group->zone()->pod_calloc(nbytes)); + if (!newInitializerList) { ReportOutOfMemory(cx); return false; } - PodCopy(initializerList, initializerVector.begin(), + newInitializerList->length = initializerVector.length(); + PodCopy(newInitializerList->entries, initializerVector.begin(), initializerVector.length()); } + RemoveCellMemory(group, gcMallocBytes(), MemoryUse::ObjectGroupAddendum); + + initializerList = newInitializerList; + js_delete(preliminaryObjects); preliminaryObjects = nullptr; + AddCellMemory(group, gcMallocBytes(), MemoryUse::ObjectGroupAddendum); + if (prefixShape->slotSpan() == templateObject()->slotSpan()) { // The definite properties analysis found exactly the properties that // are held in common by the preliminary objects. No further analysis @@ -4006,9 +4030,10 @@ bool TypeNewScript::rollbackPartiallyInitializedObjects(JSContext* cx, // Index in pcOffsets of the frame currently being checked for a SETPROP. int setpropDepth = callDepth; - for (TypeNewScriptInitializer* init = initializerList;; init++) { - if (init->kind == TypeNewScriptInitializer::SETPROP) { - if (!pastProperty && pcOffsets[setpropDepth] < init->offset) { + for (size_t i = 0; i < initializerList->length; i++) { + const TypeNewScriptInitializer init = initializerList->entries[i]; + if (init.kind == TypeNewScriptInitializer::SETPROP) { + if (!pastProperty && pcOffsets[setpropDepth] < init.offset) { // Have not yet reached this setprop. break; } @@ -4016,12 +4041,12 @@ bool TypeNewScript::rollbackPartiallyInitializedObjects(JSContext* cx, numProperties++; pastProperty = false; setpropDepth = callDepth; - } else if (init->kind == TypeNewScriptInitializer::SETPROP_FRAME) { + } else if (init.kind == TypeNewScriptInitializer::SETPROP_FRAME) { if (!pastProperty) { - if (pcOffsets[setpropDepth] < init->offset) { + if (pcOffsets[setpropDepth] < init.offset) { // Have not yet reached this inner call. break; - } else if (pcOffsets[setpropDepth] > init->offset) { + } else if (pcOffsets[setpropDepth] > init.offset) { // Have advanced past this inner call. pastProperty = true; } else if (setpropDepth == 0) { @@ -4033,7 +4058,7 @@ bool TypeNewScript::rollbackPartiallyInitializedObjects(JSContext* cx, } } } else { - MOZ_ASSERT(init->kind == TypeNewScriptInitializer::DONE); + MOZ_ASSERT(init.kind == TypeNewScriptInitializer::DONE); finished = true; break; } diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index 402f4d65166c4..a9cab8dc58773 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -808,7 +808,7 @@ struct nsGridContainerFrame::UsedTrackSizes { /** Helper function for the above method */ void ResolveSubgridTrackSizesForAxis(nsGridContainerFrame* aFrame, LogicalAxis aAxis, Subgrid* aSubgrid, - gfxContext& aRC, nscoord aCBSize); + gfxContext& aRC, nscoord aContentBoxSize); // This only has valid sizes when mCanResolveLineRangeSize is true in // the same axis. It may have zero tracks (a grid with only abs.pos. @@ -2021,7 +2021,7 @@ struct nsGridContainerFrame::Tracks { void CalculateSizes(GridReflowInput& aState, nsTArray& aGridItems, const TrackSizingFunctions& aFunctions, - nscoord aContentSize, LineRange GridArea::*aRange, + nscoord aContentBoxSize, LineRange GridArea::*aRange, SizingConstraint aConstraint); /** @@ -2029,7 +2029,7 @@ struct nsGridContainerFrame::Tracks { * https://drafts.csswg.org/css-align-3/#propdef-align-content */ void AlignJustifyContent(const nsStylePosition* aStyle, WritingMode aWM, - nscoord aContentSize, bool aIsSubgridded); + nscoord aContentBoxSize, bool aIsSubgridded); nscoord GridLineEdge(uint32_t aLine, GridLineSide aSide) const { if (MOZ_UNLIKELY(mSizes.IsEmpty())) { @@ -3010,20 +3010,23 @@ void nsGridContainerFrame::UsedTrackSizes::ResolveTrackSizesForAxis( mCanResolveLineRangeSize[aAxis] = true; } else { const auto& range = subgrid->mArea.LineRangeForAxis(parentAxis); - nscoord cbSize = range.ToLength(parentSizes->mSizes[parentAxis]); - ResolveSubgridTrackSizesForAxis(aFrame, aAxis, subgrid, aRC, cbSize); + nscoord contentBoxSize = range.ToLength(parentSizes->mSizes[parentAxis]); + auto parentWM = aFrame->GetParent()->GetWritingMode(); + contentBoxSize -= subgrid->mMarginBorderPadding.StartEnd(parentAxis, parentWM); + contentBoxSize = std::max(nscoord(0), contentBoxSize); + ResolveSubgridTrackSizesForAxis(aFrame, aAxis, subgrid, aRC, contentBoxSize); } } void nsGridContainerFrame::UsedTrackSizes::ResolveSubgridTrackSizesForAxis( nsGridContainerFrame* aFrame, LogicalAxis aAxis, Subgrid* aSubgrid, - gfxContext& aRC, nscoord aCBSize) { + gfxContext& aRC, nscoord aContentBoxSize) { GridReflowInput state(aFrame, aRC); state.mGridItems = aSubgrid->mGridItems; Grid grid; grid.mGridColEnd = aSubgrid->mGridColEnd; grid.mGridRowEnd = aSubgrid->mGridRowEnd; - state.CalculateTrackSizesForAxis(aAxis, grid, aCBSize, + state.CalculateTrackSizesForAxis(aAxis, grid, aContentBoxSize, SizingConstraint::NoConstraint); const auto& tracks = aAxis == eLogicalAxisInline ? state.mCols : state.mRows; mSizes[aAxis] = tracks.mSizes; @@ -3032,7 +3035,7 @@ void nsGridContainerFrame::UsedTrackSizes::ResolveSubgridTrackSizesForAxis( } void nsGridContainerFrame::GridReflowInput::CalculateTrackSizesForAxis( - LogicalAxis aAxis, const Grid& aGrid, nscoord aContentSize, + LogicalAxis aAxis, const Grid& aGrid, nscoord aContentBoxSize, SizingConstraint aConstraint) { auto& tracks = aAxis == eLogicalAxisInline ? mCols : mRows; const auto& sizingFunctions = @@ -3046,10 +3049,10 @@ void nsGridContainerFrame::GridReflowInput::CalculateTrackSizesForAxis( bool useParentGaps = false; const bool isSubgriddedAxis = mFrame->IsSubgrid(aAxis); if (MOZ_LIKELY(!isSubgriddedAxis)) { - tracks.Initialize(sizingFunctions, gapStyle, gridEnd, aContentSize); + tracks.Initialize(sizingFunctions, gapStyle, gridEnd, aContentBoxSize); } else { - tracks.mGridGap = nsLayoutUtils::ResolveGapToLength(gapStyle, aContentSize); - tracks.mContentBoxSize = aContentSize; + tracks.mGridGap = nsLayoutUtils::ResolveGapToLength(gapStyle, aContentBoxSize); + tracks.mContentBoxSize = aContentBoxSize; const auto* subgrid = mFrame->GetProperty(Subgrid::Prop()); tracks.mSizes.SetLength(gridEnd); auto* parent = mFrame->ParentGridContainerForSubgrid(); @@ -3068,7 +3071,7 @@ void nsGridContainerFrame::GridReflowInput::CalculateTrackSizesForAxis( *fallbackTrackSizing->mAutoMaxSizing, fallbackTrackSizing->mHasRepeatAuto, fallbackTrackSizing->mRepeatAutoIndex), - gapStyle, gridEnd, aContentSize); + gapStyle, gridEnd, aContentBoxSize); } } @@ -3089,7 +3092,7 @@ void nsGridContainerFrame::GridReflowInput::CalculateTrackSizesForAxis( fallbackTrackSizing->mHasRepeatAuto, fallbackTrackSizing->mRepeatAutoIndex) : sizingFunctions, - aContentSize, + aContentBoxSize, aAxis == eLogicalAxisInline ? &GridArea::mCols : &GridArea::mRows, aConstraint); // XXXmats we're losing the baseline state of subgrid descendants that @@ -3098,8 +3101,8 @@ void nsGridContainerFrame::GridReflowInput::CalculateTrackSizesForAxis( mGridItems.TruncateLength(origGridItemCount); } - if (aContentSize != NS_UNCONSTRAINEDSIZE) { - tracks.AlignJustifyContent(mGridStyle, mWM, aContentSize, isSubgriddedAxis); + if (aContentBoxSize != NS_UNCONSTRAINEDSIZE) { + tracks.AlignJustifyContent(mGridStyle, mWM, aContentBoxSize, isSubgriddedAxis); } else if (!useParentGaps) { const nscoord gridGap = tracks.mGridGap; nscoord pos = 0; @@ -5612,7 +5615,7 @@ void nsGridContainerFrame::Tracks::StretchFlexibleTracks( } void nsGridContainerFrame::Tracks::AlignJustifyContent( - const nsStylePosition* aStyle, WritingMode aWM, nscoord aContentSize, + const nsStylePosition* aStyle, WritingMode aWM, nscoord aContentBoxSize, bool aIsSubgriddedAxis) { const bool isAlign = mAxis == eLogicalAxisBlock; // Align-/justify-content doesn't apply in a subgridded axis. @@ -5702,7 +5705,7 @@ void nsGridContainerFrame::Tracks::AlignJustifyContent( } } } - space = aContentSize - trackSizeSum - SumOfGridGaps(); + space = aContentBoxSize - trackSizeSum - SumOfGridGaps(); // Use the fallback value instead when applicable. if (space < 0 || (alignment == NS_STYLE_ALIGN_SPACE_BETWEEN && mSizes.Length() == 1)) { diff --git a/modules/libpref/init/StaticPrefList.h b/modules/libpref/init/StaticPrefList.h index 2c6491db29d42..d3ba68d5c88c8 100644 --- a/modules/libpref/init/StaticPrefList.h +++ b/modules/libpref/init/StaticPrefList.h @@ -406,6 +406,15 @@ VARCACHE_PREF( RelaxedAtomicBool, false ) +#ifdef MOZ_WIDGET_GTK +VARCACHE_PREF( + Live, + "apz.gtk.kinetic_scroll.enabled", + APZGTKKineticScrollEnabled, + RelaxedAtomicBool, false +) +#endif + #if !defined(MOZ_WIDGET_ANDROID) # define PREF_VALUE true #else diff --git a/netwerk/build/components.conf b/netwerk/build/components.conf index aa930f1c862e0..bfd27e3336bbb 100644 --- a/netwerk/build/components.conf +++ b/netwerk/build/components.conf @@ -340,7 +340,7 @@ Classes = [ { 'cid': '{a181af0d-68b8-4308-94db-d4f859058215}', 'contract_ids': ['@mozilla.org/network/safe-file-output-stream;1'], - 'type': 'nsAtomicFileOutputStream', + 'type': 'nsSafeFileOutputStream', 'headers': ['nsFileStreams.h'], }, { diff --git a/testing/web-platform/meta/css/css-ui/appearance-auto-001.html.ini b/testing/web-platform/meta/css/css-ui/appearance-auto-001.html.ini index 3e5bd9cd9313f..1139378eeb2b7 100644 --- a/testing/web-platform/meta/css/css-ui/appearance-auto-001.html.ini +++ b/testing/web-platform/meta/css/css-ui/appearance-auto-001.html.ini @@ -3,3 +3,4 @@ if os == "win": FAIL disabled: if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 + if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 diff --git a/testing/web-platform/meta/css/css-ui/appearance-button-bevel-001.html.ini b/testing/web-platform/meta/css/css-ui/appearance-button-bevel-001.html.ini index 04bdcd1891fc2..f928bcf8786d5 100644 --- a/testing/web-platform/meta/css/css-ui/appearance-button-bevel-001.html.ini +++ b/testing/web-platform/meta/css/css-ui/appearance-button-bevel-001.html.ini @@ -1,6 +1,7 @@ [appearance-button-bevel-001.html] disabled: if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 + if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 expected: if (os == "win") and debug and (processor == "x86_64"): FAIL if (os == "win") and not debug: FAIL diff --git a/testing/web-platform/meta/css/css-ui/appearance-checkbox-001.html.ini b/testing/web-platform/meta/css/css-ui/appearance-checkbox-001.html.ini index b3dad4a10f775..9a2701c62b788 100644 --- a/testing/web-platform/meta/css/css-ui/appearance-checkbox-001.html.ini +++ b/testing/web-platform/meta/css/css-ui/appearance-checkbox-001.html.ini @@ -3,3 +3,4 @@ if os == "win": FAIL disabled: if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 + if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 diff --git a/testing/web-platform/meta/css/css-ui/appearance-listbox-001.html.ini b/testing/web-platform/meta/css/css-ui/appearance-listbox-001.html.ini index f15fc865ffaba..3c8392cda6885 100644 --- a/testing/web-platform/meta/css/css-ui/appearance-listbox-001.html.ini +++ b/testing/web-platform/meta/css/css-ui/appearance-listbox-001.html.ini @@ -3,3 +3,4 @@ if os == "win": FAIL disabled: if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 + if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 diff --git a/testing/web-platform/meta/css/css-ui/appearance-menulist-001.html.ini b/testing/web-platform/meta/css/css-ui/appearance-menulist-001.html.ini index 1d6091b45ccfe..1270fef6badb4 100644 --- a/testing/web-platform/meta/css/css-ui/appearance-menulist-001.html.ini +++ b/testing/web-platform/meta/css/css-ui/appearance-menulist-001.html.ini @@ -3,3 +3,4 @@ if os == "win": FAIL disabled: if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 + if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 diff --git a/testing/web-platform/meta/css/css-ui/appearance-menulist-button-001.html.ini b/testing/web-platform/meta/css/css-ui/appearance-menulist-button-001.html.ini index 860b95c6c30af..006e354ff2f5f 100644 --- a/testing/web-platform/meta/css/css-ui/appearance-menulist-button-001.html.ini +++ b/testing/web-platform/meta/css/css-ui/appearance-menulist-button-001.html.ini @@ -1,6 +1,7 @@ [appearance-menulist-button-001.html] disabled: if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 + if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 expected: if (os == "win") and debug and (processor == "x86_64"): FAIL if (os == "win") and not debug: FAIL diff --git a/testing/web-platform/meta/css/css-ui/appearance-menulist-button-002.html.ini b/testing/web-platform/meta/css/css-ui/appearance-menulist-button-002.html.ini index 69bc63c2f64af..1bc643469fa14 100644 --- a/testing/web-platform/meta/css/css-ui/appearance-menulist-button-002.html.ini +++ b/testing/web-platform/meta/css/css-ui/appearance-menulist-button-002.html.ini @@ -1,6 +1,7 @@ [appearance-menulist-button-002.html] disabled: if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 + if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 expected: if os == "win": PASS FAIL diff --git a/testing/web-platform/meta/css/css-ui/appearance-meter-001.html.ini b/testing/web-platform/meta/css/css-ui/appearance-meter-001.html.ini index 5b1a797b04aa5..d09dbfcf63e53 100644 --- a/testing/web-platform/meta/css/css-ui/appearance-meter-001.html.ini +++ b/testing/web-platform/meta/css/css-ui/appearance-meter-001.html.ini @@ -1,5 +1,6 @@ [appearance-meter-001.html] disabled: if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 + if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 expected: if os == "win": FAIL diff --git a/testing/web-platform/meta/css/css-ui/appearance-progress-bar-001.html.ini b/testing/web-platform/meta/css/css-ui/appearance-progress-bar-001.html.ini index 905cd84884029..1328ccf058024 100644 --- a/testing/web-platform/meta/css/css-ui/appearance-progress-bar-001.html.ini +++ b/testing/web-platform/meta/css/css-ui/appearance-progress-bar-001.html.ini @@ -1,5 +1,6 @@ [appearance-progress-bar-001.html] disabled: if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 + if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 expected: if os == "win": FAIL diff --git a/testing/web-platform/meta/css/css-ui/appearance-push-button-001.html.ini b/testing/web-platform/meta/css/css-ui/appearance-push-button-001.html.ini index 4409dd0dfe6e4..338b2cdc45873 100644 --- a/testing/web-platform/meta/css/css-ui/appearance-push-button-001.html.ini +++ b/testing/web-platform/meta/css/css-ui/appearance-push-button-001.html.ini @@ -1,3 +1,6 @@ [appearance-push-button-001.html] + disabled: + if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 + if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 expected: if os == "win": FAIL diff --git a/testing/web-platform/meta/css/css-ui/appearance-radio-001.html.ini b/testing/web-platform/meta/css/css-ui/appearance-radio-001.html.ini index 75a53a6b4de13..0987c3f5b6e2d 100644 --- a/testing/web-platform/meta/css/css-ui/appearance-radio-001.html.ini +++ b/testing/web-platform/meta/css/css-ui/appearance-radio-001.html.ini @@ -1,4 +1,7 @@ [appearance-radio-001.html] + disabled: + if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 + if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 expected: if (os == "win") and debug and (processor == "x86_64"): FAIL if (os == "win") and not debug: FAIL diff --git a/testing/web-platform/meta/css/css-ui/appearance-searchfield-001.html.ini b/testing/web-platform/meta/css/css-ui/appearance-searchfield-001.html.ini index 919b7a7b0e528..0d4bf87a20ee4 100644 --- a/testing/web-platform/meta/css/css-ui/appearance-searchfield-001.html.ini +++ b/testing/web-platform/meta/css/css-ui/appearance-searchfield-001.html.ini @@ -1,4 +1,7 @@ [appearance-searchfield-001.html] + disabled: + if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 + if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 expected: if (os == "win") and debug and (processor == "x86_64"): FAIL if (os == "win") and not debug: FAIL diff --git a/testing/web-platform/meta/css/css-ui/appearance-slider-horizontal-001.html.ini b/testing/web-platform/meta/css/css-ui/appearance-slider-horizontal-001.html.ini index a8e46f6a67bc1..4b11854c2501d 100644 --- a/testing/web-platform/meta/css/css-ui/appearance-slider-horizontal-001.html.ini +++ b/testing/web-platform/meta/css/css-ui/appearance-slider-horizontal-001.html.ini @@ -1,4 +1,7 @@ [appearance-slider-horizontal-001.html] + disabled: + if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 + if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 expected: if (os == "win") and debug and (processor == "x86_64"): FAIL if (os == "win") and not debug: FAIL diff --git a/testing/web-platform/meta/css/css-ui/appearance-square-button-001.html.ini b/testing/web-platform/meta/css/css-ui/appearance-square-button-001.html.ini index 65f0c69945352..c4c95cc24e892 100644 --- a/testing/web-platform/meta/css/css-ui/appearance-square-button-001.html.ini +++ b/testing/web-platform/meta/css/css-ui/appearance-square-button-001.html.ini @@ -1,4 +1,7 @@ [appearance-square-button-001.html] + disabled: + if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 + if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 expected: if (os == "win") and debug and (processor == "x86_64"): FAIL if (os == "win") and not debug: FAIL diff --git a/testing/web-platform/meta/css/css-ui/appearance-textarea-001.html.ini b/testing/web-platform/meta/css/css-ui/appearance-textarea-001.html.ini index 2b40d2c3f90dc..53ea79b556aaf 100644 --- a/testing/web-platform/meta/css/css-ui/appearance-textarea-001.html.ini +++ b/testing/web-platform/meta/css/css-ui/appearance-textarea-001.html.ini @@ -1,4 +1,7 @@ [appearance-textarea-001.html] + disabled: + if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 + if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360 expected: if (os == "win") and debug and (processor == "x86_64"): FAIL if (os == "win") and not debug: FAIL diff --git a/testing/web-platform/tests/css/css-grid/subgrid/auto-track-sizing-001-ref.html b/testing/web-platform/tests/css/css-grid/subgrid/auto-track-sizing-001-ref.html new file mode 100644 index 0000000000000..800f87e5d00a7 --- /dev/null +++ b/testing/web-platform/tests/css/css-grid/subgrid/auto-track-sizing-001-ref.html @@ -0,0 +1,65 @@ + + + + + Reference: subgrid auto track sizing + + + + + +
+
+ The cat can not be separated from milk +
+
+ +
+
+ The cat can not be separated from milk +
+
+ +
+
+ The cat can not be separated from milk +
+
+ +
+
+ The cat can not be separated from milk +
+
+ +
+
+ The cat can not be separated from milk +
+
+ + + diff --git a/testing/web-platform/tests/css/css-grid/subgrid/auto-track-sizing-001.html b/testing/web-platform/tests/css/css-grid/subgrid/auto-track-sizing-001.html new file mode 100644 index 0000000000000..0fdb9b0b7e2ed --- /dev/null +++ b/testing/web-platform/tests/css/css-grid/subgrid/auto-track-sizing-001.html @@ -0,0 +1,68 @@ + + + + + Test: subgrid auto track sizing + + + + + + + +
+
+ The cat can not be separated from milk +
+
+ +
+
+ The cat can not be separated from milk +
+
+ +
+
+ The cat can not be separated from milk +
+
+ +
+
+ The cat can not be separated from milk +
+
+ +
+
+ The cat can not be separated from milk +
+
+ + + diff --git a/toolkit/modules/ContentDOMReference.jsm b/toolkit/modules/ContentDOMReference.jsm index b46c91b0f6be5..5db1cf08f4fb9 100644 --- a/toolkit/modules/ContentDOMReference.jsm +++ b/toolkit/modules/ContentDOMReference.jsm @@ -18,29 +18,56 @@ var EXPORTED_SYMBOLS = ["ContentDOMReference"]; const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); -XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator", - "@mozilla.org/uuid-generator;1", - "nsIUUIDGenerator"); +XPCOMUtils.defineLazyServiceGetter(this, "finalizationService", + "@mozilla.org/toolkit/finalizationwitness;1", + "nsIFinalizationWitnessService"); + +/** + * @typedef {number} ElementID + * @typedef {Object} ElementIdentifier + */ + +const FINALIZATION_TOPIC = "content-dom-reference-finalized"; + +// A WeakMap which ties finalization witness objects to the lifetime of the DOM +// nodes they're meant to witness. When the DOM node in the map key is +// finalized, the WeakMap stops holding the finalization witness in its value +// alive, which alerts our observer that the element has been destroyed. +const finalizerRoots = new WeakMap(); /** * An identifier generated by ContentDOMReference is a unique pair of BrowsingContext - * ID and a UUID. gRegistry maps BrowsingContext's to an object with the following + * ID and a numeric ID. gRegistry maps BrowsingContext's to an object with the following * properties: * - * UUIDToElement: - * A Map of UUID strings to WeakReference's to the elements they refer to. + * IDToElement: + * A Map of IDs to WeakReference's to the elements they refer to. * - * elementToUUID: - * A WeakMap from a DOM element to a UUID that refers to it. + * elementToID: + * A WeakMap from a DOM element to an ID that refers to it. */ var gRegistry = new WeakMap(); var ContentDOMReference = { + _init() { + const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); + Services.obs.addObserver(this, FINALIZATION_TOPIC); + }, + + observe(subject, topic, data) { + if (topic !== FINALIZATION_TOPIC) { + throw new Error("Unexpected observer topic"); + } + + let identifier = JSON.parse(data); + this._revoke(identifier); + }, + /** * Generate and return an identifier for a given DOM element. * * @param {Element} element The DOM element to generate the identifier for. - * @return {Object} The identifier for the DOM element that can be passed between + * @return {ElementIdentifier} The identifier for the DOM element that can be passed between * processes as a message. */ get(element) { @@ -53,85 +80,85 @@ var ContentDOMReference = { let mappings = gRegistry.get(browsingContext); if (!mappings) { mappings = { - UUIDToElement: new Map(), - elementToUUID: new WeakMap(), + IDToElement: new Map(), + elementToID: new WeakMap(), }; gRegistry.set(browsingContext, mappings); } - let uuid = mappings.elementToUUID.get(element); - if (uuid) { - // We already had this element registered, so return the pre-existing UUID. - return { browsingContextId: browsingContext.id, uuid }; + let id = mappings.elementToID.get(element); + if (id) { + // We already had this element registered, so return the pre-existing ID. + return { browsingContextId: browsingContext.id, id }; } // We must be registering a new element at this point. - uuid = gUUIDGenerator.generateUUID().toString(); - mappings.elementToUUID.set(element, uuid); - mappings.UUIDToElement.set(uuid, Cu.getWeakReference(element)); + id = Math.random(); + mappings.elementToID.set(element, id); + mappings.IDToElement.set(id, Cu.getWeakReference(element)); + + let identifier = { browsingContextId: browsingContext.id, id }; - return { browsingContextId: browsingContext.id, uuid }; + finalizerRoots.set( + element, + finalizationService.make(FINALIZATION_TOPIC, + JSON.stringify(identifier))); + + return identifier; }, /** * Resolves an identifier back into the DOM Element that it was generated from. * - * @param {Object} The identifier generated via ContentDOMReference.get for a + * @param {ElementIdentifier} The identifier generated via ContentDOMReference.get for a * DOM element. * @return {Element} The DOM element that the identifier was generated for, or * null if the element does not still exist. */ resolve(identifier) { let browsingContext = BrowsingContext.get(identifier.browsingContextId); - let uuid = identifier.uuid; - return this._resolveUUIDToElement(browsingContext, uuid); + let {id} = identifier; + return this._resolveIDToElement(browsingContext, id); }, /** * Removes an identifier from the registry so that subsequent attempts - * to resolve it will result in null. This is generally a good idea to avoid - * identifiers lying around taking up space (identifiers don't keep the - * DOM element alive, but the weak pointers themselves consume memory, and - * that's what we reclaim when revoking). + * to resolve it will result in null. This is done automatically when the + * target node is GCed. * - * @param {Object} The identifier to revoke, issued by ContentDOMReference.get for + * @param {ElementIdentifier} The identifier to revoke, issued by ContentDOMReference.get for * a DOM element. */ - revoke(identifier) { + _revoke(identifier) { let browsingContext = BrowsingContext.get(identifier.browsingContextId); - let uuid = identifier.uuid; + let {id} = identifier; let mappings = gRegistry.get(browsingContext); if (!mappings) { return; } - let element = this._resolveUUIDToElement(browsingContext, uuid); - if (element) { - mappings.elementToUUID.delete(element); - } - - mappings.UUIDToElement.delete(uuid); + mappings.IDToElement.delete(id); }, /** - * Private helper function that resolves a BrowsingContext and UUID (the + * Private helper function that resolves a BrowsingContext and ID (the * pair that makes up an identifier) to a DOM element. * * @param {BrowsingContext} browsingContext The BrowsingContext that was hosting * the DOM element at the time that the identifier was generated. - * @param {String} uuid The UUID generated for the DOM element. + * @param {ElementID} id The ID generated for the DOM element. * * @return {Element} The DOM element that the identifier was generated for, or * null if the element does not still exist. */ - _resolveUUIDToElement(browsingContext, uuid) { + _resolveIDToElement(browsingContext, id) { let mappings = gRegistry.get(browsingContext); if (!mappings) { return null; } - let weakReference = mappings.UUIDToElement.get(uuid); + let weakReference = mappings.IDToElement.get(id); if (!weakReference) { return null; } @@ -139,3 +166,5 @@ var ContentDOMReference = { return weakReference.get(); }, }; + +ContentDOMReference._init(); diff --git a/widget/InputData.cpp b/widget/InputData.cpp index 2205ff5216a65..3f5ac85bb8e07 100644 --- a/widget/InputData.cpp +++ b/widget/InputData.cpp @@ -437,7 +437,8 @@ PanGestureInput::PanGestureInput() mHandledByAPZ(false), mFollowedByMomentum(false), mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection(false), - mOverscrollBehaviorAllowsSwipe(false) {} + mOverscrollBehaviorAllowsSwipe(false), + mSimulateMomentum(false) {} PanGestureInput::PanGestureInput(PanGestureType aType, uint32_t aTime, TimeStamp aTimeStamp, @@ -455,7 +456,8 @@ PanGestureInput::PanGestureInput(PanGestureType aType, uint32_t aTime, mHandledByAPZ(false), mFollowedByMomentum(false), mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection(false), - mOverscrollBehaviorAllowsSwipe(false) {} + mOverscrollBehaviorAllowsSwipe(false), + mSimulateMomentum(false) {} bool PanGestureInput::IsMomentum() const { switch (mType) { @@ -477,7 +479,6 @@ WidgetWheelEvent PanGestureInput::ToWidgetWheelEvent(nsIWidget* aWidget) const { mPanStartPoint, PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent)); wheelEvent.mButtons = 0; - wheelEvent.mDeltaMode = WheelEvent_Binding::DOM_DELTA_PIXEL; wheelEvent.mMayHaveMomentum = true; // pan inputs may have momentum wheelEvent.mIsMomentum = IsMomentum(); wheelEvent.mLineOrPageDeltaX = mLineOrPageDeltaX; @@ -486,6 +487,16 @@ WidgetWheelEvent PanGestureInput::ToWidgetWheelEvent(nsIWidget* aWidget) const { wheelEvent.mDeltaY = mPanDisplacement.y; wheelEvent.mFlags.mHandledByAPZ = mHandledByAPZ; wheelEvent.mFocusSequenceNumber = mFocusSequenceNumber; + if (mDeltaType == PanGestureInput::PANDELTA_PAGE) { + // Emulate legacy widget/gtk behavior + wheelEvent.mDeltaMode = WheelEvent_Binding::DOM_DELTA_LINE; + wheelEvent.mIsNoLineOrPageDelta = true; + wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY; + wheelEvent.mDeltaX *= 3; + wheelEvent.mDeltaY *= 3; + } else { + wheelEvent.mDeltaMode = WheelEvent_Binding::DOM_DELTA_PIXEL; + } return wheelEvent; } @@ -498,6 +509,14 @@ bool PanGestureInput::TransformToLocal( } mLocalPanStartPoint = *panStartPoint; + if (mDeltaType == PanGestureInput::PANDELTA_PAGE) { + // Skip transforming the pan displacement because we want + // raw page proportion counts. + mLocalPanDisplacement.x = mPanDisplacement.x; + mLocalPanDisplacement.y = mPanDisplacement.y; + return true; + } + Maybe panDisplacement = UntransformVector(aTransform, mPanDisplacement, mPanStartPoint); if (!panDisplacement) { diff --git a/widget/InputData.h b/widget/InputData.h index eb579bc8147fe..fc87949cc80aa 100644 --- a/widget/InputData.h +++ b/widget/InputData.h @@ -332,6 +332,17 @@ class PanGestureInput : public InputData { // user has stopped the animation by putting their fingers on a touchpad. PANGESTURE_MOMENTUMEND )); + + MOZ_DEFINE_ENUM_AT_CLASS_SCOPE( + PanDeltaType, ( + // There are three kinds of scroll delta modes in Gecko: "page", "line" + // and "pixel". Touchpad pan gestures only support "page" and "pixel". + // + // NOTE: PANDELTA_PAGE currently replicates Gtk behavior + // (see AsyncPanZoomController::OnPan). + PANDELTA_PAGE, + PANDELTA_PIXEL + )); // clang-format on PanGestureInput(PanGestureType aType, uint32_t aTime, TimeStamp aTimeStamp, @@ -368,11 +379,13 @@ class PanGestureInput : public InputData { double mUserDeltaMultiplierX; double mUserDeltaMultiplierY; - bool mHandledByAPZ; + PanDeltaType mDeltaType = PANDELTA_PIXEL; + + bool mHandledByAPZ: 1; // true if this is a PANGESTURE_END event that will be followed by a // PANGESTURE_MOMENTUMSTART event. - bool mFollowedByMomentum; + bool mFollowedByMomentum: 1; // If this is true, and this event started a new input block that couldn't // find a scrollable target which is scrollable in the horizontal component @@ -380,15 +393,25 @@ class PanGestureInput : public InputData { // hold until a content response has arrived, even if the block has a // confirmed target. // This is used by events that can result in a swipe instead of a scroll. - bool mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection; + bool mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection: 1; // This is used by APZ to communicate to the macOS widget code whether // the overscroll-behavior of the scroll frame handling this swipe allows // non-local overscroll behaviors in the horizontal direction (such as // swipe navigation). - bool mOverscrollBehaviorAllowsSwipe; + bool mOverscrollBehaviorAllowsSwipe: 1; - // XXX: If adding any more bools, switch to using bitfields instead. + // true if APZ should do a fling animation after this pan ends, like + // it would with touchscreens. (For platforms that don't emit momentum events.) + bool mSimulateMomentum: 1; + + void SetHandledByAPZ(bool aHandled) { mHandledByAPZ = aHandled; } + void SetFollowedByMomentum(bool aFollowed) { mFollowedByMomentum = aFollowed; } + void SetRequiresContentResponseIfCannotScrollHorizontallyInStartDirection(bool aRequires) { + mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection = aRequires; + } + void SetOverscrollBehaviorAllowsSwipe(bool aAllows) { mOverscrollBehaviorAllowsSwipe = aAllows; } + void SetSimulateMomentum(bool aSimulate) { mSimulateMomentum = aSimulate; } }; /** diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp index e0c7536e330b3..1537c452cbac1 100644 --- a/widget/gtk/nsWindow.cpp +++ b/widget/gtk/nsWindow.cpp @@ -119,6 +119,9 @@ using namespace mozilla::widget; #include "mozilla/layers/CompositorThread.h" #include "mozilla/layers/KnowsCompositor.h" +#include "mozilla/layers/APZInputBridge.h" +#include "mozilla/layers/IAPZCTreeManager.h" + #ifdef MOZ_X11 # include "GLContextGLX.h" // for GLContextGLX::FindVisual() # include "GtkCompositorWidget.h" @@ -3034,8 +3037,11 @@ void nsWindow::OnScrollEvent(GdkEventScroll* aEvent) { #if GTK_CHECK_VERSION(3, 4, 0) // check for duplicate legacy scroll event, see GNOME bug 726878 if (aEvent->direction != GDK_SCROLL_SMOOTH && - mLastScrollEventTime == aEvent->time) + mLastScrollEventTime == aEvent->time) { + LOG(("[%d] duplicate legacy scroll event %d\n", aEvent->time, + aEvent->direction)); return; + } #endif WidgetWheelEvent wheelEvent(true, eWheel, this); wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_LINE; @@ -3043,21 +3049,56 @@ void nsWindow::OnScrollEvent(GdkEventScroll* aEvent) { #if GTK_CHECK_VERSION(3, 4, 0) case GDK_SCROLL_SMOOTH: { // As of GTK 3.4, all directional scroll events are provided by - // the GDK_SCROLL_SMOOTH direction on XInput2 devices. + // the GDK_SCROLL_SMOOTH direction on XInput2 and Wayland devices. mLastScrollEventTime = aEvent->time; - // TODO - use a more appropriate scrolling unit than lines. - // Multiply event deltas by 3 to emulate legacy behaviour. - wheelEvent.mDeltaX = aEvent->delta_x * 3; - wheelEvent.mDeltaY = aEvent->delta_y * 3; - wheelEvent.mIsNoLineOrPageDelta = true; - // This next step manually unsets smooth scrolling for touch devices - // that trigger GDK_SCROLL_SMOOTH. We use the slave device, which - // represents the actual input. + + // Special handling for touchpads to support flings + // (also known as kinetic/inertial/momentum scrolling) GdkDevice* device = gdk_event_get_source_device((GdkEvent*)aEvent); GdkInputSource source = gdk_device_get_source(device); if (source == GDK_SOURCE_TOUCHSCREEN || source == GDK_SOURCE_TOUCHPAD) { + if (StaticPrefs::APZGTKKineticScrollEnabled() && + gtk_check_version(3, 20, 0) == nullptr) { + static auto sGdkEventIsScrollStopEvent = + (gboolean(*)(const GdkEvent*))dlsym( + RTLD_DEFAULT, "gdk_event_is_scroll_stop_event"); + + LOG(("[%d] pan smooth event dx=%f dy=%f inprogress=%d\n", + aEvent->time, aEvent->delta_x, aEvent->delta_y, mPanInProgress)); + PanGestureInput::PanGestureType eventType = + PanGestureInput::PANGESTURE_PAN; + if (sGdkEventIsScrollStopEvent((GdkEvent*)aEvent)) { + eventType = PanGestureInput::PANGESTURE_END; + mPanInProgress = false; + } else if (!mPanInProgress) { + eventType = PanGestureInput::PANGESTURE_START; + mPanInProgress = true; + } + + LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent); + PanGestureInput panEvent( + eventType, aEvent->time, GetEventTimeStamp(aEvent->time), + ScreenPoint(touchPoint.x, touchPoint.y), + ScreenPoint(aEvent->delta_x, aEvent->delta_y), + KeymapWrapper::ComputeKeyModifiers(aEvent->state)); + panEvent.mDeltaType = PanGestureInput::PANDELTA_PAGE; + panEvent.mSimulateMomentum = true; + + DispatchPanGestureInput(panEvent); + + return; + } + + // Older GTK doesn't support stop events, so we can't support fling wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY; } + + // TODO - use a more appropriate scrolling unit than lines. + // Multiply event deltas by 3 to emulate legacy behaviour. + wheelEvent.mDeltaX = aEvent->delta_x * 3; + wheelEvent.mDeltaY = aEvent->delta_y * 3; + wheelEvent.mIsNoLineOrPageDelta = true; + break; } #endif diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h index 64e7effa61d94..98b740eb23851 100644 --- a/widget/gtk/nsWindow.h +++ b/widget/gtk/nsWindow.h @@ -492,6 +492,8 @@ class nsWindow final : public nsBaseWidget { // This field omits duplicate scroll events caused by GNOME bug 726878. guint32 mLastScrollEventTime; + bool mPanInProgress = false; + // for touch event handling nsRefPtrHashtable, mozilla::dom::Touch> mTouches; diff --git a/widget/nsBaseWidget.cpp b/widget/nsBaseWidget.cpp index a6fa8d8613871..2627300b46f52 100644 --- a/widget/nsBaseWidget.cpp +++ b/widget/nsBaseWidget.cpp @@ -1132,6 +1132,29 @@ void nsBaseWidget::DispatchTouchInput(MultiTouchInput& aInput) { } } +void nsBaseWidget::DispatchPanGestureInput(PanGestureInput& aInput) { + MOZ_ASSERT(NS_IsMainThread()); + if (mAPZC) { + MOZ_ASSERT(APZThreadUtils::IsControllerThread()); + uint64_t inputBlockId = 0; + ScrollableLayerGuid guid; + + nsEventStatus result = + mAPZC->InputBridge()->ReceiveInputEvent(aInput, &guid, &inputBlockId); + if (result == nsEventStatus_eConsumeNoDefault) { + return; + } + + WidgetWheelEvent event = aInput.ToWidgetWheelEvent(this); + ProcessUntransformedAPZEvent(&event, guid, inputBlockId, result); + } else { + WidgetWheelEvent event = aInput.ToWidgetWheelEvent(this); + + nsEventStatus status; + DispatchEvent(&event, status); + } +} + nsEventStatus nsBaseWidget::DispatchInputEvent(WidgetInputEvent* aEvent) { MOZ_ASSERT(NS_IsMainThread()); if (mAPZC) { diff --git a/widget/nsBaseWidget.h b/widget/nsBaseWidget.h index 2972d341c50cc..fbc5ccf44138c 100644 --- a/widget/nsBaseWidget.h +++ b/widget/nsBaseWidget.h @@ -619,6 +619,14 @@ class nsBaseWidget : public nsIWidget, public nsSupportsWeakReference { */ void DispatchTouchInput(mozilla::MultiTouchInput& aInput); + /** + * Dispatch the given PanGestureInput through APZ to Gecko (if APZ is enabled) + * or directly to gecko (if APZ is not enabled). This function must only + * be called from the main thread, and if APZ is enabled, that must also be + * the APZ controller thread. + */ + void DispatchPanGestureInput(mozilla::PanGestureInput& aInput); + #if defined(XP_WIN) void UpdateScrollCapture() override; diff --git a/widget/nsGUIEventIPC.h b/widget/nsGUIEventIPC.h index b558fcb0b3179..a99a99d440b9e 100644 --- a/widget/nsGUIEventIPC.h +++ b/widget/nsGUIEventIPC.h @@ -1211,7 +1211,15 @@ struct ParamTraits mozilla::PanGestureInput::sHighestPanGestureType> {}; template <> -struct ParamTraits { +struct ParamTraits + : public ContiguousEnumSerializerInclusive< + mozilla::PanGestureInput::PanDeltaType, + mozilla::PanGestureInput::PanDeltaType::PANDELTA_PAGE, + mozilla::PanGestureInput::sHighestPanDeltaType> {}; + +template <> +struct ParamTraits + : BitfieldHelper { typedef mozilla::PanGestureInput paramType; static void Write(Message* aMsg, const paramType& aParam) { @@ -1225,6 +1233,7 @@ struct ParamTraits { WriteParam(aMsg, aParam.mLineOrPageDeltaY); WriteParam(aMsg, aParam.mUserDeltaMultiplierX); WriteParam(aMsg, aParam.mUserDeltaMultiplierY); + WriteParam(aMsg, aParam.mDeltaType); WriteParam(aMsg, aParam.mHandledByAPZ); WriteParam(aMsg, aParam.mFollowedByMomentum); WriteParam( @@ -1232,6 +1241,7 @@ struct ParamTraits { aParam .mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection); WriteParam(aMsg, aParam.mOverscrollBehaviorAllowsSwipe); + WriteParam(aMsg, aParam.mSimulateMomentum); } static bool Read(const Message* aMsg, PickleIterator* aIter, @@ -1246,13 +1256,19 @@ struct ParamTraits { ReadParam(aMsg, aIter, &aResult->mLineOrPageDeltaY) && ReadParam(aMsg, aIter, &aResult->mUserDeltaMultiplierX) && ReadParam(aMsg, aIter, &aResult->mUserDeltaMultiplierY) && - ReadParam(aMsg, aIter, &aResult->mHandledByAPZ) && - ReadParam(aMsg, aIter, &aResult->mFollowedByMomentum) && - ReadParam( - aMsg, aIter, - &aResult - ->mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection) && - ReadParam(aMsg, aIter, &aResult->mOverscrollBehaviorAllowsSwipe); + ReadParam(aMsg, aIter, &aResult->mDeltaType) && + ReadBoolForBitfield(aMsg, aIter, aResult, + ¶mType::SetHandledByAPZ) && + ReadBoolForBitfield(aMsg, aIter, aResult, + ¶mType::SetFollowedByMomentum) && + ReadBoolForBitfield( + aMsg, aIter, aResult, + ¶mType:: + SetRequiresContentResponseIfCannotScrollHorizontallyInStartDirection) && + ReadBoolForBitfield(aMsg, aIter, aResult, + ¶mType::SetOverscrollBehaviorAllowsSwipe) && + ReadBoolForBitfield(aMsg, aIter, aResult, + ¶mType::SetSimulateMomentum); } };