diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index cd75c0a340..ed1eed0c38 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -431,6 +431,8 @@ @RESPATH@/components/nsLoginManagerPrompter.js @RESPATH@/components/NetworkGeolocationProvider.manifest @RESPATH@/components/NetworkGeolocationProvider.js +@RESPATH@/components/TVSimulatorService.js +@RESPATH@/components/TVSimulatorService.manifest #ifdef MOZ_WEBRTC @RESPATH@/components/PeerConnection.js @RESPATH@/components/PeerConnection.manifest diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index c88562ac76..b5f4028e30 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -569,6 +569,9 @@ @RESPATH@/components/SlowScriptDebug.manifest @RESPATH@/components/SlowScriptDebug.js +@RESPATH@/components/TVSimulatorService.js +@RESPATH@/components/TVSimulatorService.manifest + #ifndef RELEASE_BUILD @RESPATH@/components/InterAppComm.manifest @RESPATH@/components/InterAppCommService.js diff --git a/dom/animation/KeyframeEffect.cpp b/dom/animation/KeyframeEffect.cpp index 53012700a3..019470c71b 100644 --- a/dom/animation/KeyframeEffect.cpp +++ b/dom/animation/KeyframeEffect.cpp @@ -2026,8 +2026,11 @@ KeyframeEffectReadOnly::CanAnimateTransformOnCompositor( const nsIFrame* aFrame, const nsIContent* aContent) { + // Disallow OMTA for preserve-3d transform. Note that we check the style property + // rather than Extend3DContext() since that can recurse back into this function + // via HasOpacity(). if (aFrame->Combines3DTransformWithAncestors() || - aFrame->Extend3DContext()) { + aFrame->StyleDisplay()->mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D) { if (aContent) { nsCString message; message.AppendLiteral("Gecko bug: Async animation of 'preserve-3d' " diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index e6fc9363bf..4d925c1c3d 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -1576,9 +1576,7 @@ CanvasRenderingContext2D::ReturnTarget() { if (mTarget && mBufferProvider) { CurrentState().transform = mTarget->GetTransform(); - DrawTarget* oldDT = mTarget; - mTarget = nullptr; - mBufferProvider->ReturnAndUseDT(oldDT); + mBufferProvider->ReturnAndUseDT(mTarget.forget()); } } diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index 8687acdd26..6ba5a638bc 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -44,6 +44,7 @@ #include "mozilla/ipc/GeckoChildProcessHost.h" #include "mozilla/ipc/TestShellChild.h" #include "mozilla/jsipc/CrossProcessObjectWrappers.h" +#include "mozilla/layers/APZChild.h" #include "mozilla/layers/CompositorChild.h" #include "mozilla/layers/ImageBridgeChild.h" #include "mozilla/layers/PCompositorChild.h" @@ -1255,6 +1256,19 @@ ContentChild::AllocPGMPServiceChild(mozilla::ipc::Transport* aTransport, return GMPServiceChild::Create(aTransport, aOtherProcess); } +PAPZChild* +ContentChild::AllocPAPZChild(const TabId& aTabId) +{ + return APZChild::Create(aTabId); +} + +bool +ContentChild::DeallocPAPZChild(PAPZChild* aActor) +{ + delete aActor; + return true; +} + PCompositorChild* ContentChild::AllocPCompositorChild(mozilla::ipc::Transport* aTransport, base::ProcessId aOtherProcess) diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h index a0d4bf4dc1..c7e320bb7a 100644 --- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -145,6 +145,11 @@ public: AllocPGMPServiceChild(mozilla::ipc::Transport* transport, base::ProcessId otherProcess) override; + PAPZChild* + AllocPAPZChild(const TabId& aTabId) override; + bool + DeallocPAPZChild(PAPZChild* aActor) override; + PCompositorChild* AllocPCompositorChild(mozilla::ipc::Transport* aTransport, base::ProcessId aOtherProcess) override; diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index 184d26d429..c76c8afb6d 100755 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -80,6 +80,7 @@ #include "mozilla/ipc/TestShellParent.h" #include "mozilla/ipc/InputStreamUtils.h" #include "mozilla/jsipc/CrossProcessObjectWrappers.h" +#include "mozilla/layers/PAPZParent.h" #include "mozilla/layers/CompositorParent.h" #include "mozilla/layers/ImageBridgeParent.h" #include "mozilla/layers/SharedBufferManagerParent.h" @@ -2026,11 +2027,61 @@ NestedBrowserLayerIds() } } // namespace +/* static */ bool -ContentParent::RecvAllocateLayerTreeId(uint64_t* aId) +ContentParent::AllocateLayerTreeId(TabParent* aTabParent, uint64_t* aId) +{ + return AllocateLayerTreeId(aTabParent->Manager()->AsContentParent(), + aTabParent, aTabParent->GetTabId(), aId); +} + +/* static */ +bool +ContentParent::AllocateLayerTreeId(ContentParent* aContent, + TabParent* aTopLevel, const TabId& aTabId, + uint64_t* aId) { *aId = CompositorParent::AllocateLayerTreeId(); + if (!gfxPlatform::AsyncPanZoomEnabled()) { + return true; + } + + if (!aContent || !aTopLevel) { + return false; + } + + return CompositorParent::UpdateRemoteContentController(*aId, aContent, + aTabId, aTopLevel); +} + +bool +ContentParent::RecvAllocateLayerTreeId(const ContentParentId& aCpId, + const TabId& aTabId, uint64_t* aId) +{ + // Protect against spoofing by a compromised child. aCpId must either + // correspond to the process that this ContentParent represents or be a + // child of it. + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + if (ChildID() != aCpId) { + ContentParentId parent; + if (!cpm->GetParentProcessId(aCpId, &parent) || + ChildID() != parent) { + return false; + } + } + + // GetTopLevelTabParentByProcessAndTabId will make sure that aTabId + // lives in the process for aCpId. + RefPtr contentParent = cpm->GetContentProcessById(aCpId); + RefPtr browserParent = + cpm->GetTopLevelTabParentByProcessAndTabId(aCpId, aTabId); + MOZ_ASSERT(contentParent && browserParent); + + if (!AllocateLayerTreeId(contentParent, browserParent, aTabId, aId)) { + return false; + } + auto iter = NestedBrowserLayerIds().find(this); if (iter == NestedBrowserLayerIds().end()) { std::set ids; @@ -3401,6 +3452,21 @@ ContentParent::AllocPGMPServiceParent(mozilla::ipc::Transport* aTransport, return GMPServiceParent::Create(aTransport, aOtherProcess); } +PAPZParent* +ContentParent::AllocPAPZParent(const TabId& aTabId) +{ + // The PAPZParent should just be created in the main process and then an IPDL + // constructor message sent to hook it up. + MOZ_CRASH("This shouldn't be called"); + return nullptr; +} + +bool +ContentParent::DeallocPAPZParent(PAPZParent* aActor) +{ + return true; +} + PCompositorParent* ContentParent::AllocPCompositorParent(mozilla::ipc::Transport* aTransport, base::ProcessId aOtherProcess) @@ -5767,6 +5833,18 @@ ContentParent::RecvGetGraphicsDeviceInitData(DeviceInitData* aOut) return true; } +bool +ContentParent::RecvGraphicsError(const nsCString& aError) +{ + gfx::LogForwarder* lf = gfx::Factory::GetLogForwarder(); + if (lf) { + std::stringstream message; + message << "CP+" << aError.get(); + lf->UpdateStringsVector(message.str()); + } + return true; +} + bool ContentParent::RecvBeginDriverCrashGuard(const uint32_t& aGuardType, bool* aOutCrashed) { diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index 558b558993..354c81ffb1 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -502,6 +502,8 @@ public: InfallibleTArray* aFrameScripts, nsCString* aURLToLoad) override; + static bool AllocateLayerTreeId(TabParent* aTabParent, uint64_t* aId); + protected: void OnChannelConnected(int32_t pid) override; @@ -645,10 +647,19 @@ private: static void ForceKillTimerCallback(nsITimer* aTimer, void* aClosure); + static bool AllocateLayerTreeId(ContentParent* aContent, + TabParent* aTopLevel, const TabId& aTabId, + uint64_t* aId); + PGMPServiceParent* AllocPGMPServiceParent(mozilla::ipc::Transport* aTransport, base::ProcessId aOtherProcess) override; + PAPZParent* + AllocPAPZParent(const TabId& aTabId) override; + bool + DeallocPAPZParent(PAPZParent* aActor) override; + PCompositorParent* AllocPCompositorParent(mozilla::ipc::Transport* aTransport, base::ProcessId aOtherProcess) override; @@ -993,7 +1004,9 @@ private: virtual void ProcessingError(Result aCode, const char* aMsgName) override; - virtual bool RecvAllocateLayerTreeId(uint64_t* aId) override; + virtual bool RecvAllocateLayerTreeId(const ContentParentId& aCpId, + const TabId& aTabId, + uint64_t* aId) override; virtual bool RecvDeallocateLayerTreeId(const uint64_t& aId) override; @@ -1001,6 +1014,8 @@ private: int32_t* aStatus, bool* aSuccess) override; + virtual bool RecvGraphicsError(const nsCString& aError) override; + virtual bool RecvBeginDriverCrashGuard(const uint32_t& aGuardType, bool* aOutCrashed) override; diff --git a/dom/ipc/ContentProcess.cpp b/dom/ipc/ContentProcess.cpp index 0cedce12bd..e3f3ba8bf8 100644 --- a/dom/ipc/ContentProcess.cpp +++ b/dom/ipc/ContentProcess.cpp @@ -28,9 +28,9 @@ SetUpSandboxEnvironment() "SetUpSandboxEnvironment relies on nsDirectoryService being initialized"); // A low integrity temp only currently makes sense for Vista or Later and - // sandbox pref level 1. + // sandbox pref level >= 1. if (!IsVistaOrLater() || - Preferences::GetInt("security.sandbox.content.level") != 1) { + Preferences::GetInt("security.sandbox.content.level") < 1) { return; } diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index 4adc3a562b..5f18018fbd 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -56,13 +56,9 @@ using class mozilla::WidgetPluginEvent from "ipc/nsGUIEventIPC.h"; using struct mozilla::dom::RemoteDOMEvent from "mozilla/dom/TabMessageUtils.h"; using mozilla::dom::ScreenOrientationInternal from "mozilla/dom/ScreenOrientation.h"; using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h"; -using mozilla::CSSPoint from "Units.h"; using mozilla::CSSToScreenScale from "Units.h"; using mozilla::CommandInt from "mozilla/EventForwards.h"; -using mozilla::Modifiers from "mozilla/EventForwards.h"; -using mozilla::layers::GeckoContentController::APZStateChange from "mozilla/layers/GeckoContentController.h"; using mozilla::WritingMode from "mozilla/WritingModes.h"; -using mozilla::layers::AsyncDragMetrics from "mozilla/layers/AsyncDragMetrics.h"; using mozilla::layers::TouchBehaviorFlags from "mozilla/layers/APZUtils.h"; using nsIWidget::TouchPointerState from "nsIWidget.h"; using struct LookAndFeelInt from "mozilla/widget/WidgetMessageUtils.h"; @@ -417,43 +413,6 @@ parent: nsString aName, nsString aFeatures) returns (bool windowOpened); - /** - * Instructs the TabParent to forward a request to zoom to a rect given in - * CSS pixels. This rect is relative to the document. - */ - async ZoomToRect(uint32_t aPresShellId, ViewID aViewId, CSSRect aRect, uint32_t aFlags); - - /** - * We know for sure that content has either preventDefaulted or not - * preventDefaulted. This applies to an entire batch of touch events. It is - * expected that, if there are any DOM touch listeners, touch events will be - * batched and only processed for panning and zooming if content does not - * preventDefault. - */ - async ContentReceivedInputBlock(ScrollableLayerGuid aGuid, uint64_t aInputBlockId, bool aPreventDefault); - - /** - * Notifies the APZ code of the results of the gecko hit-test for a - * particular input block. Each target corresponds to one touch point in the - * touch event. - */ - async SetTargetAPZC(uint64_t aInputBlockId, ScrollableLayerGuid[] aTargets); - - /** - * Notifies the APZ code of the allowed touch-behaviours for a particular - * input block. Each item in the aFlags array corresponds to one touch point - * in the touch event. - */ - async SetAllowedTouchBehavior(uint64_t aInputBlockId, TouchBehaviorFlags[] aFlags); - - /** - * Updates the zoom constraints for a scrollable frame in this tab. - * The zoom controller code lives on the parent side and so this allows it to - * have up-to-date zoom constraints. - */ - async UpdateZoomConstraints(uint32_t aPresShellId, ViewID aViewId, - MaybeZoomConstraints aConstraints); - /** * Tells the containing widget whether the given input block results in a * swipe. Should be called in response to a WidgetWheelEvent that has @@ -537,9 +496,6 @@ parent: prio(high) sync DispatchMouseEvent(WidgetMouseEvent event); prio(high) sync DispatchKeyboardEvent(WidgetKeyboardEvent event); - // Start an APZ drag on a scrollbar - async StartScrollbarDrag(AsyncDragMetrics aDragMetrics); - async InvokeDragSession(IPCDataTransfer[] transfers, uint32_t action, nsCString visualData, uint32_t width, uint32_t height, uint32_t stride, uint8_t format, @@ -575,20 +531,6 @@ child: ScreenOrientationInternal orientation, LayoutDeviceIntPoint chromeDisp) compressall; - async UpdateFrame(FrameMetrics frame); - - // The following methods correspond to functions on the GeckoContentController - // interface in gfx/layers/apz/public/GeckoContentController.h. Refer to documentation - // in that file for these functions. - async RequestFlingSnap(ViewID aScrollID, CSSPoint aDestination); - async AcknowledgeScrollUpdate(ViewID aScrollId, uint32_t aScrollGeneration); - async HandleDoubleTap(CSSPoint aPoint, Modifiers aModifiers, ScrollableLayerGuid aGuid); - async HandleSingleTap(CSSPoint aPoint, Modifiers aModifiers, ScrollableLayerGuid aGuid); - async HandleLongTap(CSSPoint point, Modifiers aModifiers, ScrollableLayerGuid aGuid, uint64_t aInputBlockId); - async NotifyAPZStateChange(ViewID aViewId, APZStateChange aChange, int aArg); - async NotifyFlushComplete(); - - /** * Sending an activate message moves focus to the child. */ @@ -660,7 +602,7 @@ child: /** * APZ notification for mouse scroll testing events. */ - async MouseScrollTestEvent(ViewID aScrollId, nsString aEvent); + async MouseScrollTestEvent(uint64_t aLayersId, ViewID aScrollId, nsString aEvent); async CompositionEvent(WidgetCompositionEvent event); diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 1842ca318a..9f314e02a1 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -4,6 +4,7 @@ * 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/. */ +include protocol PAPZ; include protocol PBackground; include protocol PBlob; include protocol PBluetooth; @@ -467,6 +468,7 @@ prio(normal upto urgent) sync protocol PContent parent opens PVRManager; child opens PBackground; + manages PAPZ; manages PBlob; manages PBluetooth; manages PBrowser; @@ -569,6 +571,8 @@ child: async PTestShell(); + async PAPZ(TabId tabId); + async RegisterChrome(ChromePackage[] packages, SubstitutionMapping[] substitutions, OverrideMapping[] overrides, nsCString locale, bool reset); async RegisterChromeItem(ChromeRegistryItem item); @@ -987,7 +991,7 @@ parent: async CopyFavicon(URIParams oldURI, URIParams newURI, bool isPrivate); // Tell the compositor to allocate a layer tree id for nested remote mozbrowsers. - sync AllocateLayerTreeId() + sync AllocateLayerTreeId(ContentParentId cpId, TabId tabId) returns (uint64_t id); async DeallocateLayerTreeId(uint64_t id); @@ -1010,6 +1014,9 @@ parent: sync GetGraphicsFeatureStatus(int32_t aFeature) returns (int32_t aStatus, bool aSuccess); + // Graphics errors + async GraphicsError(nsCString aError); + // Driver crash guards. aGuardType must be a member of CrashGuardType. sync BeginDriverCrashGuard(uint32_t aGuardType) returns (bool crashDetected); sync EndDriverCrashGuard(uint32_t aGuardType); diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index 7072b02ef6..ae55e93041 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -16,6 +16,7 @@ #include "ContentChild.h" #include "TabParent.h" #include "mozilla/Preferences.h" +#include "mozilla/BrowserElementParent.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/EventListenerManager.h" #include "mozilla/dom/workers/ServiceWorkerManager.h" @@ -28,6 +29,7 @@ #include "ipc/Nuwa.h" #endif #include "mozilla/ipc/FileDescriptorUtils.h" +#include "mozilla/layers/APZChild.h" #include "mozilla/layers/APZCCallbackHelper.h" #include "mozilla/layers/APZCTreeManager.h" #include "mozilla/layers/APZEventState.h" @@ -37,6 +39,7 @@ #include "mozilla/layers/InputAPZContext.h" #include "mozilla/layers/ShadowLayers.h" #include "mozilla/layout/RenderFrameChild.h" +#include "mozilla/layout/RenderFrameParent.h" #include "mozilla/LookAndFeel.h" #include "mozilla/MouseEvents.h" #include "mozilla/Move.h" @@ -590,6 +593,7 @@ TabChild::TabChild(nsIContentChild* aManager, , mIPCOpen(true) , mParentIsActive(false) , mDidSetRealShowInfo(false) + , mAPZChild(nullptr) { // In the general case having the TabParent tell us if APZ is enabled or not // doesn't really work because the TabParent itself may not have a reference @@ -603,7 +607,7 @@ TabChild::TabChild(nsIContentChild* aManager, const nsTArray& aFlags) { if (nsCOMPtr tabChild = do_QueryReferent(weakPtrThis)) { - static_cast(tabChild.get())->SendSetAllowedTouchBehavior(aInputBlockId, aFlags); + static_cast(tabChild.get())->SetAllowedTouchBehavior(aInputBlockId, aFlags); } }; @@ -711,6 +715,35 @@ TabChild::Observe(nsISupports *aSubject, return NS_OK; } +void +TabChild::ContentReceivedInputBlock(const ScrollableLayerGuid& aGuid, + uint64_t aInputBlockId, + bool aPreventDefault) const +{ + if (mAPZChild) { + mAPZChild->SendContentReceivedInputBlock(aGuid, aInputBlockId, + aPreventDefault); + } +} + +void +TabChild::SetTargetAPZC(uint64_t aInputBlockId, + const nsTArray& aTargets) const +{ + if (mAPZChild) { + mAPZChild->SendSetTargetAPZC(aInputBlockId, aTargets); + } +} + +void +TabChild::SetAllowedTouchBehavior(uint64_t aInputBlockId, + const nsTArray& aTargets) const +{ + if (mAPZChild) { + mAPZChild->SendSetAllowedTouchBehavior(aInputBlockId, aTargets); + } +} + bool TabChild::DoUpdateZoomConstraints(const uint32_t& aPresShellId, const ViewID& aViewId, @@ -723,9 +756,12 @@ TabChild::DoUpdateZoomConstraints(const uint32_t& aPresShellId, return true; } - return SendUpdateZoomConstraints(aPresShellId, - aViewId, - aConstraints); + if (!mAPZChild) { + return false; + } + + return mAPZChild->SendUpdateZoomConstraints(aPresShellId, aViewId, + aConstraints); } nsresult @@ -812,7 +848,7 @@ TabChild::Init() bool aPreventDefault) { if (nsCOMPtr tabChild = do_QueryReferent(weakPtrThis)) { - static_cast(tabChild.get())->SendContentReceivedInputBlock(aGuid, aInputBlockId, aPreventDefault); + static_cast(tabChild.get())->ContentReceivedInputBlock(aGuid, aInputBlockId, aPreventDefault); } }); mAPZEventState = new APZEventState(mPuppetWidget, Move(callback)); @@ -1629,82 +1665,73 @@ TabChild::RecvUpdateDimensions(const CSSRect& rect, const CSSSize& size, } bool -TabChild::RecvUpdateFrame(const FrameMetrics& aFrameMetrics) +TabChild::UpdateFrame(const FrameMetrics& aFrameMetrics) { return TabChildBase::UpdateFrameHandler(aFrameMetrics); } -bool -TabChild::RecvRequestFlingSnap(const FrameMetrics::ViewID& aScrollId, - const mozilla::CSSPoint& aDestination) +void +TabChild::HandleDoubleTap(const CSSPoint& aPoint, const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid) { - APZCCallbackHelper::RequestFlingSnap(aScrollId, aDestination); - return true; + TABC_LOG("Handling double tap at %s with %p %p\n", + Stringify(aPoint).c_str(), mGlobal.get(), mTabChildGlobal.get()); + + if (!mGlobal || !mTabChildGlobal) { + return; + } + + // Note: there is nothing to do with the modifiers here, as we are not + // synthesizing any sort of mouse event. + CSSPoint point = APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid); + nsCOMPtr document = GetDocument(); + CSSRect zoomToRect = CalculateRectToZoomTo(document, point); + // The double-tap can be dispatched by any scroll frame (so |aGuid| could be + // the guid of any scroll frame), but the zoom-to-rect operation must be + // performed by the root content scroll frame, so query its identifiers + // for the SendZoomToRect() call rather than using the ones from |aGuid|. + uint32_t presShellId; + ViewID viewId; + if (APZCCallbackHelper::GetOrCreateScrollIdentifiers( + document->GetDocumentElement(), &presShellId, &viewId) && + mAPZChild) { + mAPZChild->SendZoomToRect(presShellId, viewId, zoomToRect, + DEFAULT_BEHAVIOR); + } } -bool -TabChild::RecvAcknowledgeScrollUpdate(const ViewID& aScrollId, - const uint32_t& aScrollGeneration) -{ - APZCCallbackHelper::AcknowledgeScrollUpdate(aScrollId, aScrollGeneration); - return true; -} - -bool -TabChild::RecvHandleDoubleTap(const CSSPoint& aPoint, const Modifiers& aModifiers, const ScrollableLayerGuid& aGuid) -{ - TABC_LOG("Handling double tap at %s with %p %p\n", - Stringify(aPoint).c_str(), mGlobal.get(), mTabChildGlobal.get()); - - if (!mGlobal || !mTabChildGlobal) { - return true; - } - - // Note: there is nothing to do with the modifiers here, as we are not - // synthesizing any sort of mouse event. - CSSPoint point = APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid); - nsCOMPtr document = GetDocument(); - CSSRect zoomToRect = CalculateRectToZoomTo(document, point); - // The double-tap can be dispatched by any scroll frame (so |aGuid| could be - // the guid of any scroll frame), but the zoom-to-rect operation must be - // performed by the root content scroll frame, so query its identifiers - // for the SendZoomToRect() call rather than using the ones from |aGuid|. - uint32_t presShellId; - ViewID viewId; - if (APZCCallbackHelper::GetOrCreateScrollIdentifiers( - document->GetDocumentElement(), &presShellId, &viewId)) { - SendZoomToRect(presShellId, viewId, zoomToRect, DEFAULT_BEHAVIOR); - } - - return true; -} - -bool -TabChild::RecvHandleSingleTap(const CSSPoint& aPoint, const Modifiers& aModifiers, const ScrollableLayerGuid& aGuid) +void +TabChild::HandleSingleTap(const CSSPoint& aPoint, + const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid, + bool aCallTakeFocusForClickFromTap) { + if (aCallTakeFocusForClickFromTap && mRemoteFrame) { + mRemoteFrame->SendTakeFocusForClickFromTap(); + } if (mGlobal && mTabChildGlobal) { mAPZEventState->ProcessSingleTap(aPoint, aModifiers, aGuid); } - return true; } -bool -TabChild::RecvHandleLongTap(const CSSPoint& aPoint, const Modifiers& aModifiers, const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId) +void +TabChild::HandleLongTap(const CSSPoint& aPoint, const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { if (mGlobal && mTabChildGlobal) { mAPZEventState->ProcessLongTap(GetPresShell(), aPoint, aModifiers, aGuid, aInputBlockId); } - return true; } bool -TabChild::RecvNotifyAPZStateChange(const ViewID& aViewId, - const APZStateChange& aChange, - const int& aArg) +TabChild::NotifyAPZStateChange(const ViewID& aViewId, + const layers::GeckoContentController::APZStateChange& aChange, + const int& aArg) { mAPZEventState->ProcessAPZStateChange(GetDocument(), aViewId, aChange, aArg); - if (aChange == APZStateChange::TransformEnd) { + if (aChange == layers::GeckoContentController::APZStateChange::TransformEnd) { // This is used by tests to determine when the APZ is done doing whatever // it's doing. XXX generify this as needed when writing additional tests. DispatchMessageManagerMessage( @@ -1714,11 +1741,23 @@ TabChild::RecvNotifyAPZStateChange(const ViewID& aViewId, return true; } -bool -TabChild::RecvNotifyFlushComplete() +void +TabChild::StartScrollbarDrag(const layers::AsyncDragMetrics& aDragMetrics) { - APZCCallbackHelper::NotifyFlushComplete(); - return true; + if (mAPZChild) { + mAPZChild->SendStartScrollbarDrag(aDragMetrics); + } +} + +void +TabChild::ZoomToRect(const uint32_t& aPresShellId, + const FrameMetrics::ViewID& aViewId, + const CSSRect& aRect, + const uint32_t& aFlags) +{ + if (mAPZChild) { + mAPZChild->SendZoomToRect(aPresShellId, aViewId, aRect, aFlags); + } } bool @@ -1836,8 +1875,21 @@ TabChild::RecvMouseWheelEvent(const WidgetWheelEvent& aEvent, } bool -TabChild::RecvMouseScrollTestEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent) +TabChild::RecvMouseScrollTestEvent(const uint64_t& aLayersId, + const FrameMetrics::ViewID& aScrollId, const nsString& aEvent) { + if (aLayersId != mLayersId) { + RefPtr browser = TabParent::GetTabParentFromLayersId(aLayersId); + if (!browser) { + return false; + } + NS_DispatchToMainThread(NS_NewRunnableFunction( + [aLayersId, browser, aScrollId, aEvent] () -> void { + Unused << browser->SendMouseScrollTestEvent(aLayersId, aScrollId, aEvent); + })); + return true; + } + APZCCallbackHelper::NotifyMozMouseScrollEvent(aScrollId, aEvent); return true; } @@ -2599,7 +2651,10 @@ TabChild::MakeHidden() void TabChild::UpdateHitRegion(const nsRegion& aRegion) { - mRemoteFrame->SendUpdateHitRegion(aRegion); + mRemoteFrame->SendUpdateHitRegion(aRegion); + if (mAPZChild) { + mAPZChild->SendUpdateHitRegion(aRegion); + } } NS_IMETHODIMP diff --git a/dom/ipc/TabChild.h b/dom/ipc/TabChild.h index eedde6d020..ab46b9b30b 100644 --- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -37,6 +37,7 @@ #include "mozilla/dom/ipc/IdType.h" #include "AudioChannelService.h" #include "PuppetWidget.h" +#include "mozilla/layers/GeckoContentController.h" class nsICachedFileDescriptorListener; class nsIDOMWindowUtils; @@ -47,7 +48,9 @@ class RenderFrameChild; } // namespace layout namespace layers { +class APZChild; class APZEventState; +class AsyncDragMetrics; class ImageCompositeNotification; } // namespace layers @@ -324,33 +327,6 @@ public: const ScreenOrientationInternal& orientation, const LayoutDeviceIntPoint& chromeDisp) override; - virtual bool RecvUpdateFrame(const layers::FrameMetrics& aFrameMetrics) override; - - virtual bool RecvRequestFlingSnap(const ViewID& aScrollId, - const CSSPoint& aDestination) override; - - virtual bool RecvAcknowledgeScrollUpdate(const ViewID& aScrollId, - const uint32_t& aScrollGeneration) override; - - virtual bool RecvHandleDoubleTap(const CSSPoint& aPoint, - const Modifiers& aModifiers, - const mozilla::layers::ScrollableLayerGuid& aGuid) override; - - virtual bool RecvHandleSingleTap(const CSSPoint& aPoint, - const Modifiers& aModifiers, - const mozilla::layers::ScrollableLayerGuid& aGuid) override; - - virtual bool RecvHandleLongTap(const CSSPoint& aPoint, - const Modifiers& aModifiers, - const mozilla::layers::ScrollableLayerGuid& aGuid, - const uint64_t& aInputBlockId) override; - - virtual bool RecvNotifyAPZStateChange(const ViewID& aViewId, - const APZStateChange& aChange, - const int& aArg) override; - - virtual bool RecvNotifyFlushComplete() override; - virtual bool RecvActivate() override; virtual bool RecvDeactivate() override; @@ -402,7 +378,8 @@ public: const int32_t& aModifiers, const bool& aPreventDefault) override; - virtual bool RecvMouseScrollTestEvent(const FrameMetrics::ViewID& aScrollId, + virtual bool RecvMouseScrollTestEvent(const uint64_t& aLayersId, + const FrameMetrics::ViewID& aScrollId, const nsString& aEvent) override; virtual bool RecvNativeSynthesisResponse(const uint64_t& aObserverId, @@ -585,6 +562,40 @@ public: PRenderFrameChild* aRenderFrame, const ShowInfo& aShowInfo); + void ContentReceivedInputBlock(const ScrollableLayerGuid& aGuid, + uint64_t aInputBlockId, + bool aPreventDefault) const; + void SetTargetAPZC(uint64_t aInputBlockId, + const nsTArray& aTargets) const; + void HandleDoubleTap(const CSSPoint& aPoint, + const Modifiers& aModifiers, + const mozilla::layers::ScrollableLayerGuid& aGuid); + void HandleSingleTap(const CSSPoint& aPoint, + const Modifiers& aModifiers, + const mozilla::layers::ScrollableLayerGuid& aGuid, + bool aCallTakeFocusForClickFromTap); + void HandleLongTap(const CSSPoint& aPoint, + const Modifiers& aModifiers, + const mozilla::layers::ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + void SetAllowedTouchBehavior(uint64_t aInputBlockId, + const nsTArray& aFlags) const; + + bool UpdateFrame(const FrameMetrics& aFrameMetrics); + bool NotifyAPZStateChange(const ViewID& aViewId, + const layers::GeckoContentController::APZStateChange& aChange, + const int& aArg); + void StartScrollbarDrag(const layers::AsyncDragMetrics& aDragMetrics); + void ZoomToRect(const uint32_t& aPresShellId, + const FrameMetrics::ViewID& aViewId, + const CSSRect& aRect, + const uint32_t& aFlags); + + void SetAPZChild(layers::APZChild* aAPZChild) + { + mAPZChild = aAPZChild; + } + protected: virtual ~TabChild(); @@ -695,6 +706,10 @@ private: AutoTArray mAudioChannelsActive; + // APZChild clears this pointer from its destructor, so it shouldn't be a + // dangling pointer. + layers::APZChild* mAPZChild; + DISALLOW_EVIL_CONSTRUCTORS(TabChild); }; diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index 1b5641a7ab..ba89e72fac 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -977,14 +977,6 @@ TabParent::UpdateDimensions(const nsIntRect& rect, const ScreenIntSize& size) } } -void -TabParent::UpdateFrame(const FrameMetrics& aFrameMetrics) -{ - if (!mIsDestroyed) { - Unused << SendUpdateFrame(aFrameMetrics); - } -} - void TabParent::UIResolutionChanged() { @@ -1024,76 +1016,6 @@ TabParent::HandleAccessKey(nsTArray& aCharCodes, } } -void -TabParent::RequestFlingSnap(const FrameMetrics::ViewID& aScrollId, - const mozilla::CSSPoint& aDestination) -{ - if (!mIsDestroyed) { - Unused << SendRequestFlingSnap(aScrollId, aDestination); - } -} - -void -TabParent::AcknowledgeScrollUpdate(const ViewID& aScrollId, const uint32_t& aScrollGeneration) -{ - if (!mIsDestroyed) { - Unused << SendAcknowledgeScrollUpdate(aScrollId, aScrollGeneration); - } -} - -void TabParent::HandleDoubleTap(const CSSPoint& aPoint, - Modifiers aModifiers, - const ScrollableLayerGuid &aGuid) -{ - if (!mIsDestroyed) { - Unused << SendHandleDoubleTap(aPoint, aModifiers, aGuid); - } -} - -void TabParent::HandleSingleTap(const CSSPoint& aPoint, - Modifiers aModifiers, - const ScrollableLayerGuid &aGuid) -{ - if (!mIsDestroyed) { - Unused << SendHandleSingleTap(aPoint, aModifiers, aGuid); - } -} - -void TabParent::HandleLongTap(const CSSPoint& aPoint, - Modifiers aModifiers, - const ScrollableLayerGuid &aGuid, - uint64_t aInputBlockId) -{ - if (!mIsDestroyed) { - Unused << SendHandleLongTap(aPoint, aModifiers, aGuid, aInputBlockId); - } -} - -void TabParent::NotifyAPZStateChange(ViewID aViewId, - APZStateChange aChange, - int aArg) -{ - if (!mIsDestroyed) { - Unused << SendNotifyAPZStateChange(aViewId, aChange, aArg); - } -} - -void -TabParent::NotifyMouseScrollTestEvent(const ViewID& aScrollId, const nsString& aEvent) -{ - if (!mIsDestroyed) { - Unused << SendMouseScrollTestEvent(aScrollId, aEvent); - } -} - -void -TabParent::NotifyFlushComplete() -{ - if (!mIsDestroyed) { - Unused << SendNotifyFlushComplete(); - } -} - void TabParent::Activate() { @@ -1382,33 +1304,6 @@ CSSPoint TabParent::AdjustTapToChildWidget(const CSSPoint& aPoint) return aPoint + (LayoutDevicePoint(GetChildProcessOffset()) * GetLayoutDeviceToCSSScale()); } -bool TabParent::SendHandleSingleTap(const CSSPoint& aPoint, const Modifiers& aModifiers, const ScrollableLayerGuid& aGuid) -{ - if (mIsDestroyed) { - return false; - } - - return PBrowserParent::SendHandleSingleTap(AdjustTapToChildWidget(aPoint), aModifiers, aGuid); -} - -bool TabParent::SendHandleLongTap(const CSSPoint& aPoint, const Modifiers& aModifiers, const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId) -{ - if (mIsDestroyed) { - return false; - } - - return PBrowserParent::SendHandleLongTap(AdjustTapToChildWidget(aPoint), aModifiers, aGuid, aInputBlockId); -} - -bool TabParent::SendHandleDoubleTap(const CSSPoint& aPoint, const Modifiers& aModifiers, const ScrollableLayerGuid& aGuid) -{ - if (mIsDestroyed) { - return false; - } - - return PBrowserParent::SendHandleDoubleTap(AdjustTapToChildWidget(aPoint), aModifiers, aGuid); -} - bool TabParent::SendMouseWheelEvent(WidgetWheelEvent& event) { if (mIsDestroyed) { @@ -2789,29 +2684,6 @@ TabParent::RecvBrowserFrameOpenWindow(PBrowserParent* aOpener, return true; } -bool -TabParent::RecvZoomToRect(const uint32_t& aPresShellId, - const ViewID& aViewId, - const CSSRect& aRect, - const uint32_t& aFlags) -{ - if (RenderFrameParent* rfp = GetRenderFrame()) { - rfp->ZoomToRect(aPresShellId, aViewId, aRect, aFlags); - } - return true; -} - -bool -TabParent::RecvUpdateZoomConstraints(const uint32_t& aPresShellId, - const ViewID& aViewId, - const MaybeZoomConstraints& aConstraints) -{ - if (RenderFrameParent* rfp = GetRenderFrame()) { - rfp->UpdateZoomConstraints(aPresShellId, aViewId, aConstraints); - } - return true; -} - bool TabParent::RecvRespondStartSwipeEvent(const uint64_t& aInputBlockId, const bool& aStartSwipe) @@ -2822,46 +2694,6 @@ TabParent::RecvRespondStartSwipeEvent(const uint64_t& aInputBlockId, return true; } -bool -TabParent::RecvContentReceivedInputBlock(const ScrollableLayerGuid& aGuid, - const uint64_t& aInputBlockId, - const bool& aPreventDefault) -{ - if (RenderFrameParent* rfp = GetRenderFrame()) { - rfp->ContentReceivedInputBlock(aGuid, aInputBlockId, aPreventDefault); - } - return true; -} - -bool -TabParent::RecvSetTargetAPZC(const uint64_t& aInputBlockId, - nsTArray&& aTargets) -{ - if (RenderFrameParent* rfp = GetRenderFrame()) { - rfp->SetTargetAPZC(aInputBlockId, aTargets); - } - return true; -} - -bool -TabParent::RecvStartScrollbarDrag(const AsyncDragMetrics& aDragMetrics) -{ - if (RenderFrameParent* rfp = GetRenderFrame()) { - rfp->StartScrollbarDrag(aDragMetrics); - } - return true; -} - -bool -TabParent::RecvSetAllowedTouchBehavior(const uint64_t& aInputBlockId, - nsTArray&& aFlags) -{ - if (RenderFrameParent* rfp = GetRenderFrame()) { - rfp->SetAllowedTouchBehavior(aInputBlockId, aFlags); - } - return true; -} - already_AddRefed TabParent::GetLoadContext() { diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h index 5722df869f..aed50ed6f3 100644 --- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -49,8 +49,6 @@ class CpowHolder; } // namespace jsipc namespace layers { -class AsyncDragMetrics; -struct FrameMetrics; struct TextureFactoryIdentifier; } // namespace layers @@ -88,7 +86,6 @@ class TabParent final : public PBrowserParent , public nsAPostRefreshObserver { typedef mozilla::dom::ClonedMessageData ClonedMessageData; - typedef mozilla::layers::AsyncDragMetrics AsyncDragMetrics; virtual ~TabParent(); @@ -297,31 +294,9 @@ public: virtual bool RecvDispatchFocusToTopLevelWindow() override; - virtual bool RecvZoomToRect(const uint32_t& aPresShellId, - const ViewID& aViewId, - const CSSRect& aRect, - const uint32_t& aFlags) override; - - virtual bool - RecvUpdateZoomConstraints(const uint32_t& aPresShellId, - const ViewID& aViewId, - const MaybeZoomConstraints& aConstraints) override; - virtual bool RecvRespondStartSwipeEvent(const uint64_t& aInputBlockId, const bool& aStartSwipe) override; - virtual bool - RecvContentReceivedInputBlock(const ScrollableLayerGuid& aGuid, - const uint64_t& aInputBlockId, - const bool& aPreventDefault) override; - - virtual bool RecvSetTargetAPZC(const uint64_t& aInputBlockId, - nsTArray&& aTargets) override; - - virtual bool - RecvSetAllowedTouchBehavior(const uint64_t& aInputBlockId, - nsTArray&& aTargets) override; - virtual bool RecvDispatchWheelEvent(const mozilla::WidgetWheelEvent& aEvent) override; @@ -331,9 +306,6 @@ public: virtual bool RecvDispatchKeyboardEvent(const mozilla::WidgetKeyboardEvent& aEvent) override; - virtual bool - RecvStartScrollbarDrag(const AsyncDragMetrics& aDragMetrics) override; - virtual PColorPickerParent* AllocPColorPickerParent(const nsString& aTitle, const nsString& aInitialColor) override; @@ -365,8 +337,6 @@ public: void UpdateDimensions(const nsIntRect& rect, const ScreenIntSize& size); - void UpdateFrame(const layers::FrameMetrics& aFrameMetrics); - void UIResolutionChanged(); void ThemeChanged(); @@ -375,34 +345,6 @@ public: const bool& aIsTrusted, const int32_t& aModifierMask); - void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId, - const mozilla::CSSPoint& aDestination); - - void AcknowledgeScrollUpdate(const ViewID& aScrollId, - const uint32_t& aScrollGeneration); - - void HandleDoubleTap(const CSSPoint& aPoint, - Modifiers aModifiers, - const ScrollableLayerGuid& aGuid); - - void HandleSingleTap(const CSSPoint& aPoint, - Modifiers aModifiers, - const ScrollableLayerGuid& aGuid); - - void HandleLongTap(const CSSPoint& aPoint, - Modifiers aModifiers, - const ScrollableLayerGuid& aGuid, - uint64_t aInputBlockId); - - void NotifyAPZStateChange(ViewID aViewId, - APZStateChange aChange, - int aArg); - - void NotifyMouseScrollTestEvent(const ViewID& aScrollId, - const nsString& aEvent); - - void NotifyFlushComplete(); - void Activate(); void Deactivate(); @@ -482,19 +424,6 @@ public: bool SendRealTouchEvent(WidgetTouchEvent& event); - bool SendHandleSingleTap(const CSSPoint& aPoint, - const Modifiers& aModifiers, - const ScrollableLayerGuid& aGuid); - - bool SendHandleLongTap(const CSSPoint& aPoint, - const Modifiers& aModifiers, - const ScrollableLayerGuid& aGuid, - const uint64_t& aInputBlockId); - - bool SendHandleDoubleTap(const CSSPoint& aPoint, - const Modifiers& aModifiers, - const ScrollableLayerGuid& aGuid); - virtual PDocumentRendererParent* AllocPDocumentRendererParent(const nsRect& documentRect, const gfx::Matrix& transform, diff --git a/dom/plugins/base/nsPluginNativeWindowGtk.cpp b/dom/plugins/base/nsPluginNativeWindowGtk.cpp index f3180c9d7b..1aa66c6b8f 100644 --- a/dom/plugins/base/nsPluginNativeWindowGtk.cpp +++ b/dom/plugins/base/nsPluginNativeWindowGtk.cpp @@ -24,6 +24,7 @@ #endif #include "mozilla/X11Util.h" +static void plug_added_cb(GtkWidget *widget, gpointer data); static gboolean plug_removed_cb (GtkWidget *widget, gpointer data); static void socket_unrealize_cb (GtkWidget *widget, gpointer data); @@ -163,6 +164,9 @@ nsresult nsPluginNativeWindowGtk::CreateXEmbedWindow(bool aEnableXtFocus) { // see plugin_window_filter_func() for details g_object_set_data(G_OBJECT(mSocketWidget), "enable-xt-focus", (void *)aEnableXtFocus); + g_signal_connect(mSocketWidget, "plug_added", + G_CALLBACK(plug_added_cb), nullptr); + // Make sure to handle the plug_removed signal. If we don't the // socket will automatically be destroyed when the plug is // removed, which means we're destroying it more than once. @@ -278,6 +282,32 @@ nsresult nsPluginNativeWindowGtk::CreateXtWindow() { } #endif +static void +plug_window_finalize_cb(gpointer socket, GObject* plug_window) +{ + g_object_unref(socket); +} + +static void +plug_added_cb(GtkWidget *socket, gpointer data) +{ + // The plug window has been embedded, and gtk_socket_add_window() has added + // a filter to the socket's plug_window, passing the socket as data for the + // filter, so the socket must live as long as events may be received on the + // plug window. + // + // https://git.gnome.org/browse/gtk+/tree/gtk/gtksocket.c?h=3.18.7#n1124 + g_object_ref(socket); + // When the socket is unrealized, perhaps during gtk_widget_destroy() from + // ~nsPluginNativeWindowGtk, the plug is removed. The plug in the child + // process then destroys its widget and window. When the browser process + // receives the DestroyNotify event for the plug window, GDK releases its + // reference to plugWindow. This is typically the last reference and so the + // weak ref callback triggers release of the socket. + GdkWindow* plugWindow = gtk_socket_get_plug_window(GTK_SOCKET(socket)); + g_object_weak_ref(G_OBJECT(plugWindow), plug_window_finalize_cb, socket); +} + /* static */ gboolean plug_removed_cb (GtkWidget *widget, gpointer data) diff --git a/dom/tv/FakeTVService.cpp b/dom/tv/FakeTVService.cpp deleted file mode 100644 index 4940df8e42..0000000000 --- a/dom/tv/FakeTVService.cpp +++ /dev/null @@ -1,532 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=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/. */ - -#include "mozilla/dom/TVServiceRunnables.h" -#include "mozilla/dom/TVTypes.h" -#include "nsCOMPtr.h" -#include "nsIMutableArray.h" -#include "nsITimer.h" -#include "nsServiceManagerUtils.h" -#include "prtime.h" -#include "FakeTVService.h" - -namespace mozilla { -namespace dom { - -NS_IMPL_CYCLE_COLLECTION_CLASS(FakeTVService) - -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FakeTVService) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceListener) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTuners) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannels) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrograms) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEITBroadcastedTimer) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScanCompleteTimer) -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END - -NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FakeTVService) - tmp->Shutdown(); - NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceListener) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mTuners) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannels) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrograms) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mEITBroadcastedTimer) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mScanCompleteTimer) -NS_IMPL_CYCLE_COLLECTION_UNLINK_END - -NS_IMPL_CYCLE_COLLECTING_ADDREF(FakeTVService) -NS_IMPL_CYCLE_COLLECTING_RELEASE(FakeTVService) - -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FakeTVService) - NS_INTERFACE_MAP_ENTRY(nsITVService) - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END - -FakeTVService::FakeTVService() -{ - Init(); -} - -FakeTVService::~FakeTVService() -{ - Shutdown(); -} - -void -FakeTVService::Init() -{ - const char* sourceTypes1[2] = {"dvb-t", "dvb-c"}; - nsCOMPtr tunerData1 = MockTuner(NS_LITERAL_STRING("1"), 2, sourceTypes1); - mTuners.AppendElement(tunerData1); - const char* sourceTypes2[1] = {"dvb-s"}; - nsCOMPtr tunerData2 = MockTuner(NS_LITERAL_STRING("2"), 1, sourceTypes2); - mTuners.AppendElement(tunerData2); - - nsCOMPtr channelData1 = - MockChannel(NS_LITERAL_STRING("networkId1"), NS_LITERAL_STRING("transportStreamId1"), - NS_LITERAL_STRING("serviceId1"), NS_LITERAL_STRING("tv"), - NS_LITERAL_STRING("1"), NS_LITERAL_STRING("name1"), true, true); - mChannels.AppendElement(channelData1); - nsCOMPtr channelData2 = - MockChannel(NS_LITERAL_STRING("networkId2"), NS_LITERAL_STRING("transportStreamId2"), - NS_LITERAL_STRING("serviceId2"), NS_LITERAL_STRING("radio"), - NS_LITERAL_STRING("2"), NS_LITERAL_STRING("name2"), true, true); - mChannels.AppendElement(channelData2); - - uint64_t now = PR_Now(); - const char* audioLanguages1[2] = {"eng", "jpn"}; - const char* subtitleLanguages1[2] = {"fre", "spa"}; - nsCOMPtr programData1 = - MockProgram(NS_LITERAL_STRING("eventId1"), NS_LITERAL_STRING("title1"), - now - 1, 3600000, - NS_LITERAL_STRING("description1"), NS_LITERAL_STRING("rating1"), - 2, audioLanguages1, 2, subtitleLanguages1); - mPrograms.AppendElement(programData1); - nsCOMPtr programData2 = - MockProgram(NS_LITERAL_STRING("eventId2"), NS_LITERAL_STRING("title2"), - now + 3600000 , 3600000, - NS_LITERAL_STRING(""), NS_LITERAL_STRING(""), - 0, nullptr, 0, nullptr); - mPrograms.AppendElement(programData2); -} - -void -FakeTVService::Shutdown() -{ - if (mEITBroadcastedTimer) { - mEITBroadcastedTimer->Cancel(); - } - if (mScanCompleteTimer) { - mScanCompleteTimer->Cancel(); - } -} - -/* virtual */ NS_IMETHODIMP -FakeTVService::GetSourceListener(nsITVSourceListener** aSourceListener) -{ - if (!mSourceListener) { - *aSourceListener = nullptr; - return NS_OK; - } - - *aSourceListener = mSourceListener; - NS_ADDREF(*aSourceListener); - return NS_OK; -} - -/* virtual */ NS_IMETHODIMP -FakeTVService::SetSourceListener(nsITVSourceListener* aSourceListener) -{ - mSourceListener = aSourceListener; - return NS_OK; -} - -/* virtual */ NS_IMETHODIMP -FakeTVService::GetTuners(nsITVServiceCallback* aCallback) -{ - if (!aCallback) { - return NS_ERROR_INVALID_ARG; - } - - nsCOMPtr tunerDataList = do_CreateInstance(NS_ARRAY_CONTRACTID); - if (!tunerDataList) { - return NS_ERROR_OUT_OF_MEMORY; - } - - for (uint32_t i = 0; i < mTuners.Length(); i++) { - tunerDataList->AppendElement(mTuners[i], false); - } - - nsCOMPtr runnable = - new TVServiceNotifyRunnable(aCallback, tunerDataList); - return NS_DispatchToCurrentThread(runnable); -} - -/* virtual */ NS_IMETHODIMP -FakeTVService::SetSource(const nsAString& aTunerId, - const nsAString& aSourceType, - nsITVServiceCallback* aCallback) -{ - if (!aCallback) { - return NS_ERROR_INVALID_ARG; - } - - for (uint32_t i = 0; i < mTuners.Length(); i++) { - nsString tunerId; - mTuners[i]->GetId(tunerId); - if (aTunerId.Equals(tunerId)) { - uint32_t sourceTypeCount; - char** sourceTypes; - mTuners[i]->GetSupportedSourceTypes(&sourceTypeCount, &sourceTypes); - for (uint32_t j = 0; j < sourceTypeCount; j++) { - nsString sourceType; - sourceType.AssignASCII(sourceTypes[j]); - if (aSourceType.Equals(sourceType)) { - NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(sourceTypeCount, sourceTypes); - nsCOMPtr runnable = - new TVServiceNotifyRunnable(aCallback, nullptr); - return NS_DispatchToCurrentThread(runnable); - } - } - NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(sourceTypeCount, sourceTypes); - } - } - - nsCOMPtr runnable = - new TVServiceNotifyRunnable(aCallback, nullptr, nsITVServiceCallback::TV_ERROR_FAILURE); - return NS_DispatchToCurrentThread(runnable); -} - -class EITBroadcastedCallback final : public nsITimerCallback -{ -public: - NS_DECL_ISUPPORTS - - EITBroadcastedCallback(const nsAString& aTunerId, - const nsAString& aSourceType, - nsITVSourceListener* aSourceListener, - nsITVChannelData* aChannelData) - : mTunerId(aTunerId) - , mSourceType(aSourceType) - , mSourceListener(aSourceListener) - , mChannelData(aChannelData) - {} - - NS_IMETHODIMP - Notify(nsITimer* aTimer) override - { - // Notify mock EIT broadcasting. - nsITVProgramData** programDataList = - static_cast(NS_Alloc(1 * sizeof(nsITVProgramData*))); - programDataList[0] = new TVProgramData(); - programDataList[0]->SetEventId(NS_LITERAL_STRING("eventId")); - programDataList[0]->SetTitle(NS_LITERAL_STRING("title")); - programDataList[0]->SetStartTime(PR_Now() + 3600000); - programDataList[0]->SetDuration(3600000); - programDataList[0]->SetDescription(NS_LITERAL_STRING("description")); - programDataList[0]->SetRating(NS_LITERAL_STRING("rating")); - programDataList[0]->SetAudioLanguages(0, nullptr); - programDataList[0]->SetSubtitleLanguages(0, nullptr); - nsresult rv = mSourceListener->NotifyEITBroadcasted(mTunerId, mSourceType, - mChannelData, - programDataList, 1); - NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(1, programDataList); - return rv; - } - -private: - ~EITBroadcastedCallback() {} - - nsString mTunerId; - nsString mSourceType; - nsCOMPtr mSourceListener; - nsCOMPtr mChannelData; -}; - -NS_IMPL_ISUPPORTS(EITBroadcastedCallback, nsITimerCallback) - -class ScanCompleteCallback final : public nsITimerCallback -{ -public: - NS_DECL_ISUPPORTS - - ScanCompleteCallback(const nsAString& aTunerId, - const nsAString& aSourceType, - nsITVSourceListener* aSourceListener) - : mTunerId(aTunerId) - , mSourceType(aSourceType) - , mSourceListener(aSourceListener) - {} - - NS_IMETHODIMP - Notify(nsITimer* aTimer) override - { - return mSourceListener->NotifyChannelScanComplete(mTunerId, mSourceType); - } - -private: - ~ScanCompleteCallback() {} - - nsString mTunerId; - nsString mSourceType; - nsCOMPtr mSourceListener; -}; - -NS_IMPL_ISUPPORTS(ScanCompleteCallback, nsITimerCallback) - -/* virtual */ NS_IMETHODIMP -FakeTVService::StartScanningChannels(const nsAString& aTunerId, - const nsAString& aSourceType, - nsITVServiceCallback* aCallback) -{ - if (!aCallback) { - return NS_ERROR_INVALID_ARG; - } - - nsCOMPtr runnable = - new TVServiceNotifyRunnable(aCallback, nullptr); - nsresult rv = NS_DispatchToCurrentThread(runnable); - NS_ENSURE_SUCCESS(rv, rv); - - if (IsAllowed(aTunerId, aSourceType)) { - rv = mSourceListener->NotifyChannelScanned(aTunerId, aSourceType, mChannels[0]); - NS_ENSURE_SUCCESS(rv, rv); - - // Set a timer. |notifyEITBroadcasted| will be called after the timer - // fires (10ms). (The timer could be canceled if |StopScanningChannels| gets - // called before firing.) - mEITBroadcastedTimer = do_CreateInstance(NS_TIMER_CONTRACTID); - NS_ENSURE_TRUE(mEITBroadcastedTimer, NS_ERROR_OUT_OF_MEMORY); - RefPtr eitBroadcastedCb = - new EITBroadcastedCallback(aTunerId, aSourceType, mSourceListener, mChannels[0]); - rv = mEITBroadcastedTimer->InitWithCallback(eitBroadcastedCb, 10, - nsITimer::TYPE_ONE_SHOT); - NS_ENSURE_SUCCESS(rv, rv); - - // Set a timer. |notifyChannelScanComplete| will be called after the timer - // fires (20ms). (The timer could be canceled if |StopScanningChannels| gets - // called before firing.) - mScanCompleteTimer = do_CreateInstance(NS_TIMER_CONTRACTID); - NS_ENSURE_TRUE(mScanCompleteTimer, NS_ERROR_OUT_OF_MEMORY); - RefPtr scanCompleteCb = - new ScanCompleteCallback(aTunerId, aSourceType, mSourceListener); - rv = mScanCompleteTimer->InitWithCallback(scanCompleteCb, 20, - nsITimer::TYPE_ONE_SHOT); - NS_ENSURE_SUCCESS(rv, rv); - } - - return NS_OK; -} - -/* virtual */ NS_IMETHODIMP -FakeTVService::StopScanningChannels(const nsAString& aTunerId, - const nsAString& aSourceType, - nsITVServiceCallback* aCallback) -{ - if (!aCallback) { - return NS_ERROR_INVALID_ARG; - } - - if (mEITBroadcastedTimer) { - mEITBroadcastedTimer->Cancel(); - mEITBroadcastedTimer = nullptr; - } - if (mScanCompleteTimer) { - mScanCompleteTimer->Cancel(); - mScanCompleteTimer = nullptr; - } - nsresult rv = mSourceListener->NotifyChannelScanStopped(aTunerId, aSourceType); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr runnable = - new TVServiceNotifyRunnable(aCallback, nullptr); - return NS_DispatchToCurrentThread(runnable); -} - -/* virtual */ NS_IMETHODIMP -FakeTVService::ClearScannedChannelsCache() -{ - // Fake service doesn't support channel cache, so there's nothing to do here. - return NS_OK; -} - -/* virtual */ NS_IMETHODIMP -FakeTVService::SetChannel(const nsAString& aTunerId, - const nsAString& aSourceType, - const nsAString& aChannelNumber, - nsITVServiceCallback* aCallback) -{ - if (!aCallback) { - return NS_ERROR_INVALID_ARG; - } - - nsCOMPtr channelDataList = do_CreateInstance(NS_ARRAY_CONTRACTID); - if (!channelDataList) { - return NS_ERROR_OUT_OF_MEMORY; - } - - if (IsAllowed(aTunerId, aSourceType)) { - for (uint32_t i = 0; i < mChannels.Length(); i++) { - nsString channelNumber; - mChannels[i]->GetNumber(channelNumber); - if (aChannelNumber.Equals(channelNumber)) { - channelDataList->AppendElement(mChannels[i], false); - break; - } - } - } - - uint32_t length; - nsresult rv = channelDataList->GetLength(&length); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr runnable = new TVServiceNotifyRunnable( - aCallback, - (length == 1) ? channelDataList : nullptr, - (length == 1) ? nsITVServiceCallback::TV_ERROR_OK : nsITVServiceCallback::TV_ERROR_FAILURE - ); - return NS_DispatchToCurrentThread(runnable); -} - -/* virtual */ NS_IMETHODIMP -FakeTVService::GetChannels(const nsAString& aTunerId, - const nsAString& aSourceType, - nsITVServiceCallback* aCallback) -{ - if (!aCallback) { - return NS_ERROR_INVALID_ARG; - } - - nsCOMPtr channelDataList = do_CreateInstance(NS_ARRAY_CONTRACTID); - if (!channelDataList) { - return NS_ERROR_OUT_OF_MEMORY; - } - - if (IsAllowed(aTunerId, aSourceType)) { - for (uint32_t i = 0; i < mChannels.Length(); i++) { - channelDataList->AppendElement(mChannels[i], false); - } - } - - nsCOMPtr runnable = - new TVServiceNotifyRunnable(aCallback, channelDataList); - return NS_DispatchToCurrentThread(runnable); -} - -/* virtual */ NS_IMETHODIMP -FakeTVService::GetPrograms(const nsAString& aTunerId, - const nsAString& aSourceType, - const nsAString& aChannelNumber, - uint64_t startTime, - uint64_t endTime, - nsITVServiceCallback* aCallback) -{ - if (!aCallback) { - return NS_ERROR_INVALID_ARG; - } - - nsCOMPtr programDataList = do_CreateInstance(NS_ARRAY_CONTRACTID); - if (!programDataList) { - return NS_ERROR_OUT_OF_MEMORY; - } - - // Only return mock programs for the first channel. - nsString channelNumber; - mChannels[0]->GetNumber(channelNumber); - if (IsAllowed(aTunerId, aSourceType) && aChannelNumber.Equals(channelNumber)) { - for (uint32_t i = 0; i < mPrograms.Length(); i++) { - programDataList->AppendElement(mPrograms[i], false); - } - } - - nsCOMPtr runnable = - new TVServiceNotifyRunnable(aCallback, programDataList); - return NS_DispatchToCurrentThread(runnable); -} - -/* virtual */ NS_IMETHODIMP -FakeTVService::GetOverlayId(const nsAString& aTunerId, - nsITVServiceCallback* aCallback) -{ - if (!aCallback) { - return NS_ERROR_INVALID_ARG; - } - - nsCOMPtr overlayIds = do_CreateInstance(NS_ARRAY_CONTRACTID); - if (!overlayIds) { - return NS_ERROR_OUT_OF_MEMORY; - } - - // TODO Implement in follow-up patches. - - nsCOMPtr runnable = - new TVServiceNotifyRunnable(aCallback, overlayIds); - return NS_DispatchToCurrentThread(runnable); -} - -bool -FakeTVService::IsAllowed(const nsAString& aTunerId, - const nsAString& aSourceType) -{ - // Only allow for the first source of the first tuner. - nsString tunerId; - mTuners[0]->GetId(tunerId); - if (!aTunerId.Equals(tunerId)) { - return false; - } - - uint32_t sourceTypeCount; - char** sourceTypes; - mTuners[0]->GetSupportedSourceTypes(&sourceTypeCount, &sourceTypes); - nsString sourceType; - sourceType.AssignASCII(sourceTypes[0]); - NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(sourceTypeCount, sourceTypes); - if (!aSourceType.Equals(sourceType)) { - return false; - } - - return true; -} - -already_AddRefed -FakeTVService::MockTuner(const nsAString& aId, - uint32_t aSupportedSourceTypeCount, - const char** aSupportedSourceTypes) -{ - nsCOMPtr tunerData = new TVTunerData(); - tunerData->SetId(aId); - tunerData->SetSupportedSourceTypes(aSupportedSourceTypeCount, aSupportedSourceTypes); - return tunerData.forget(); -} - -already_AddRefed -FakeTVService::MockChannel(const nsAString& aNetworkId, - const nsAString& aTransportStreamId, - const nsAString& aServiceId, - const nsAString& aType, - const nsAString& aNumber, - const nsAString& aName, - bool aIsEmergency, - bool aIsFree) -{ - nsCOMPtr channelData = new TVChannelData(); - channelData->SetNetworkId(aNetworkId); - channelData->SetTransportStreamId(aTransportStreamId); - channelData->SetServiceId(aServiceId); - channelData->SetType(aType); - channelData->SetNumber(aNumber); - channelData->SetName(aName); - channelData->SetIsEmergency(aIsEmergency); - channelData->SetIsFree(aIsFree); - return channelData.forget(); -} - -already_AddRefed -FakeTVService::MockProgram(const nsAString& aEventId, - const nsAString& aTitle, - uint64_t aStartTime, - uint64_t aDuration, - const nsAString& aDescription, - const nsAString& aRating, - uint32_t aAudioLanguageCount, - const char** aAudioLanguages, - uint32_t aSubtitleLanguageCount, - const char** aSubtitleLanguages) -{ - nsCOMPtr programData = new TVProgramData(); - programData->SetEventId(aEventId); - programData->SetTitle(aTitle); - programData->SetStartTime(aStartTime); - programData->SetDuration(aDuration); - programData->SetDescription(aDescription); - programData->SetRating(aRating); - programData->SetAudioLanguages(aAudioLanguageCount, aAudioLanguages); - programData->SetSubtitleLanguages(aSubtitleLanguageCount, aSubtitleLanguages); - return programData.forget(); -} - -} // namespace dom -} // namespace mozilla diff --git a/dom/tv/FakeTVService.h b/dom/tv/FakeTVService.h deleted file mode 100644 index 5ab07108a3..0000000000 --- a/dom/tv/FakeTVService.h +++ /dev/null @@ -1,84 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=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/. */ - -#ifndef mozilla_dom_FakeTVService_h -#define mozilla_dom_FakeTVService_h - -#include "nsCOMPtr.h" -#include "nsCycleCollectionParticipant.h" -#include "nsITVService.h" -#include "nsTArray.h" - -#define FAKE_TV_SERVICE_CONTRACTID \ - "@mozilla.org/tv/faketvservice;1" -#define FAKE_TV_SERVICE_CID \ - { 0x60fb3c53, 0x017f, 0x4340, { 0x91, 0x1b, 0xd5, 0x5c, 0x31, 0x28, 0x88, 0xb6 } } - -class nsITimer; -class nsITVTunerData; -class nsITVChannelData; -class nsITVProgramData; - -namespace mozilla { -namespace dom { - -class FakeTVService final : public nsITVService -{ -public: - NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_CLASS(FakeTVService) - NS_DECL_NSITVSERVICE - - FakeTVService(); - -private: - ~FakeTVService(); - - void Init(); - - void Shutdown(); - - bool IsAllowed(const nsAString& aTunerId, - const nsAString& aSourceType); - - already_AddRefed MockTuner(const nsAString& aId, - uint32_t aSupportedSourceTypeCount, - const char** aSupportedSourceTypes); - - already_AddRefed MockChannel(const nsAString& aNetworkId, - const nsAString& aTransportStreamId, - const nsAString& aServiceId, - const nsAString& aType, - const nsAString& aNumber, - const nsAString& aName, - bool aIsEmergency, - bool aIsFree); - - already_AddRefed MockProgram(const nsAString& aEventId, - const nsAString& aTitle, - uint64_t aStartTime, - uint64_t aDuration, - const nsAString& aDescription, - const nsAString& aRating, - uint32_t aAudioLanguageCount, - const char** aAudioLanguages, - uint32_t aSubtitleLanguageCount, - const char** aSubtitleLanguages); - - nsCOMPtr mSourceListener; - - // The real implementation may want to use more efficient data structures. - nsTArray> mTuners; - nsTArray> mChannels; - nsTArray> mPrograms; - nsCOMPtr mEITBroadcastedTimer; - nsCOMPtr mScanCompleteTimer; -}; - -} // namespace dom -} // namespace mozilla - -#endif // mozilla_dom_FakeTVService_h diff --git a/dom/tv/TVServiceFactory.cpp b/dom/tv/TVServiceFactory.cpp index 70979505b3..2a2fda4463 100644 --- a/dom/tv/TVServiceFactory.cpp +++ b/dom/tv/TVServiceFactory.cpp @@ -4,7 +4,6 @@ * 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/. */ -#include "mozilla/dom/FakeTVService.h" #include "mozilla/dom/TVListeners.h" #include "mozilla/Preferences.h" #include "nsITVService.h" @@ -15,26 +14,14 @@ namespace mozilla { namespace dom { -/* static */ already_AddRefed -TVServiceFactory::CreateFakeTVService() -{ - RefPtr service = new FakeTVService(); - return service.forget(); -} - /* static */ already_AddRefed TVServiceFactory::AutoCreateTVService() { - nsresult rv; + nsresult rv = NS_OK; nsCOMPtr service = do_CreateInstance(TV_SERVICE_CONTRACTID); if (!service) { - if (Preferences::GetBool("dom.ignore_webidl_scope_checks", false)) { - // Fallback to the fake service. - service = do_CreateInstance(FAKE_TV_SERVICE_CONTRACTID, &rv); - } else { - // Fallback to the TV Simulator Service - service = do_CreateInstance(TV_SIMULATOR_SERVICE_CONTRACTID, &rv); - } + // Fallback to the TV Simulator Service + service = do_CreateInstance(TV_SIMULATOR_SERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; diff --git a/dom/tv/TVServiceFactory.h b/dom/tv/TVServiceFactory.h index 004bc7ae6f..93e81a8339 100644 --- a/dom/tv/TVServiceFactory.h +++ b/dom/tv/TVServiceFactory.h @@ -14,13 +14,9 @@ class nsITVService; namespace mozilla { namespace dom { -class FakeTVService; - class TVServiceFactory { public: - static already_AddRefed CreateFakeTVService(); - static already_AddRefed AutoCreateTVService(); }; diff --git a/dom/tv/TVSimulatorService.js b/dom/tv/TVSimulatorService.js index 785e9d8d22..8b641e055f 100644 --- a/dom/tv/TVSimulatorService.js +++ b/dom/tv/TVSimulatorService.js @@ -13,9 +13,11 @@ const Ci = Components.interfaces; const Cr = Components.returnCode; Cu.importGlobalProperties(["File"]); +Cu.import("resource://gre/modules/Services.jsm"); -const TV_SIMULATOR_DUMMY_DIRECTORY = "dummy"; -const TV_SIMULATOR_DUMMY_FILE = "settings.json"; +const TV_SIMULATOR_DUMMY_DIRECTORY = "dummy"; +const TV_SIMULATOR_DUMMY_FILE = "settings.json"; +const TV_SIMULATOR_MOCK_DATA = Services.prefs.getCharPref("dom.testing.tv_mock_data"); // See http://seanyhlin.github.io/TV-Manager-API/#idl-def-TVSourceType const TV_SOURCE_TYPES = ["dvb-t","dvb-t2","dvb-c","dvb-c2","dvb-s", @@ -49,36 +51,16 @@ TVSimulatorService.prototype = { return; } - // Load the setting file from local JSON file. - // Synchrhronous File Reading. - let file = Cc["@mozilla.org/file/local;1"] - .createInstance(Ci.nsILocalFile); - - file.initWithPath(this._getFilePath(TV_SIMULATOR_DUMMY_FILE)); - - let fstream = Cc["@mozilla.org/network/file-input-stream;1"] - .createInstance(Ci.nsIFileInputStream); - let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"] - .createInstance(Ci.nsIConverterInputStream); - let settingStr = ""; - try { - fstream.init(file, -1, 0, 0); - cstream.init(fstream, - "UTF-8", - 1024, - Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); - - let str = {}; - while (cstream.readString(0xffffffff, str) != 0) { - settingStr += str.value; + if (TV_SIMULATOR_MOCK_DATA) { + settingStr = TV_SIMULATOR_MOCK_DATA; + } else { + settingStr = this._getDummyData(); } } catch(e) { debug("Error occurred : " + e ); return; - } finally { - cstream.close(); } let settingsObj; @@ -281,10 +263,11 @@ TVSimulatorService.prototype = { } this._scanCompleteTimer = null; + let notifyResult = this._sourceListener.notifyChannelScanComplete( + this._scanningWrapTunerData.tuner.id, + this._scanningWrapTunerData.sourceType); this._scanningWrapTunerData = null; - return this._sourceListener.notifyChannelScanComplete( - this._scanningWrapTunerData.tuner.id, - this._scanningWrapTunerData.sourceType); + return notifyResult; }, stopScanningChannels: function TVSimStopScanningChannels(aTunerId, aSourceType, aCallback) { @@ -434,6 +417,41 @@ TVSimulatorService.prototype = { return videoBlobURL; }, + _getDummyData : function TVSimGetDummyData() { + // Load the setting file from local JSON file. + // Synchrhronous File Reading. + let file = Cc["@mozilla.org/file/local;1"] + .createInstance(Ci.nsILocalFile); + + let fstream = Cc["@mozilla.org/network/file-input-stream;1"] + .createInstance(Ci.nsIFileInputStream); + let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"] + .createInstance(Ci.nsIConverterInputStream); + + let settingsStr = ""; + + try { + file.initWithPath(this._getFilePath(TV_SIMULATOR_DUMMY_FILE)); + fstream.init(file, -1, 0, 0); + cstream.init(fstream, + "UTF-8", + 1024, + Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); + + let str = {}; + while (cstream.readString(0xffffffff, str) != 0) { + settingsStr += str.value; + } + } catch(e) { + debug("Catch the Exception when reading the dummy file:" + e ); + throw e; + } finally { + cstream.close(); + } + + return settingsStr; + }, + _getTunerMapKey: function TVSimGetTunerMapKey(aTunerId, aSourceType) { return JSON.stringify({'tunerId': aTunerId, 'sourceType': aSourceType}); }, diff --git a/dom/tv/moz.build b/dom/tv/moz.build index 58a9ff9808..44bd63a03b 100644 --- a/dom/tv/moz.build +++ b/dom/tv/moz.build @@ -5,7 +5,6 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. EXPORTS.mozilla.dom += [ - 'FakeTVService.h', 'TVChannel.h', 'TVListeners.h', 'TVManager.h', @@ -20,7 +19,6 @@ EXPORTS.mozilla.dom += [ ] UNIFIED_SOURCES += [ - 'FakeTVService.cpp', 'TVChannel.cpp', 'TVListeners.cpp', 'TVManager.cpp', diff --git a/dom/tv/test/mochitest/head.js b/dom/tv/test/mochitest/head.js index 3afe89296c..27471e929d 100644 --- a/dom/tv/test/mochitest/head.js +++ b/dom/tv/test/mochitest/head.js @@ -9,8 +9,20 @@ function setupPrefsAndPermissions(callback) { } function setupPrefs(callback) { - SpecialPowers.pushPrefEnv({"set": [["dom.tv.enabled", true], - ["dom.ignore_webidl_scope_checks", true]]}, function() { + let xhr = new XMLHttpRequest; + let data; + + xhr.open("GET", "./mock_data.json", false); + xhr.send(null); + if (xhr.status == 200) { + data = xhr.responseText; + } + + SpecialPowers.pushPrefEnv({"set": [ + ["dom.tv.enabled", true], + ["dom.ignore_webidl_scope_checks", true], + ["dom.testing.tv_mock_data", data] + ]}, function() { callback(); }); } diff --git a/dom/tv/test/mochitest/mochitest.ini b/dom/tv/test/mochitest/mochitest.ini index 8b58db1f90..60df11a302 100644 --- a/dom/tv/test/mochitest/mochitest.ini +++ b/dom/tv/test/mochitest/mochitest.ini @@ -1,6 +1,8 @@ [DEFAULT] -support-files = head.js -skip-if = (os == 'mac' && debug) || asan # Bug 1125477 +support-files = + head.js + mock_data.json + [test_tv_non_permitted_app.html] [test_tv_permitted_app.html] [test_tv_get_tuners.html] diff --git a/dom/tv/test/mochitest/mock_data.json b/dom/tv/test/mochitest/mock_data.json new file mode 100644 index 0000000000..b2a4f8e73b --- /dev/null +++ b/dom/tv/test/mochitest/mock_data.json @@ -0,0 +1,99 @@ +{ + "tuners": [ + { + "id":"1", + "supportedType": ["dvb-t"], + "sources": [ + { + "type": "dvb-t", + "channels": [ + { + "networkId": "32112", + "transportStreamId": "32112", + "serviceId": "40960", + "type": "tv", + "name": "TV #1", + "number" : 1, + "isEmergency": false, + "isFree" : true, + "videoFilePath": "tv1.ogv", + "programs": [ + {"eventId":"734475972", "title":"News of Morning", "startTime":"1430686800", "duration":"10800", "description":"Morning News", "rating":"0", "audioLanguages":["eng"], "subtitleLanguages":["eng"]}, + {"eventId":"422158917", "title":"News of Midnight", "startTime":"1431266400", "duration":"3600", "description":"Summary of today news", "rating":"0", "audioLanguages":["eng"], "subtitleLanguages":["eng"]}, + {"eventId":"533612446", "title":"Entertainment Program", "startTime":"1431270000", "duration":"7200", "description":"Midnight entertainment program", "rating":"0", "audioLanguages":["eng"], "subtitleLanguages":["eng"]} + ] + }, + { + "_comment": "Channel Data", + "networkId": "32122", + "transportStreamId": "32122", + "serviceId": "40990", + "type": "tv", + "name": "TV #2", + "number" : 2, + "isEmergency": false, + "isFree" : true, + "videoFilePath": "tv2.ogv", + "programs": [ + {"eventId":"931109607","title":"News of Morning", "startTime":"1430686800", "duration":"10800", "description":"Provide news in morning", "rating":"0", "audioLanguages":["eng"], "subtitleLanguages":["eng"]}, + {"eventId":"297834220","title":"Weekly News", "startTime":"1431259200", "duration":"10800", "description":"Poopular Music program", "rating":"0", "audioLanguages":["eng"], "subtitleLanguages":["eng"]}, + {"eventId":"866886159","title":"Weekly news", "startTime":"1431270000", "duration":"7200", "description":"Information program on Monday", "rating":"0", "audioLanguages":["eng"], "subtitleLanguages":["eng"]} + ] + }, + { + "_comment": "Channel Data", + "networkId": "32132", + "transportStreamId": "32132", + "serviceId": "41020", + "type": "tv", + "name": "TV #3", + "number" : 3, + "isEmergency": false, + "isFree" : true, + "videoFilePath": "tv1.ogv", + "programs": [ + {"eventId":"734475972", "title":"News of Morning", "startTime":"1430686800", "duration":"10800", "description":"Morning News", "rating":"0", "audioLanguages":["eng"], "subtitleLanguages":["eng"]}, + {"eventId":"586216742", "title":"Information Program", "startTime":"1430697600", "duration":"7200", "description":"Provide program Information.", "rating":"0", "audioLanguages":["eng"], "subtitleLanguages":["eng"]}, + {"eventId":"533612446", "title":"Entertainment Program", "startTime":"1431270000", "duration":"7200", "description":"Midnight entertainment program", "rating":"0", "audioLanguages":["eng"], "subtitleLanguages":["eng"]} + ] + }, + { + "_comment": "Channel Data", + "networkId": "32142", + "transportStreamId": "32142", + "serviceId": "41040", + "type": "tv", + "name": "TV #5", + "number" : 4, + "isEmergency": false, + "isFree" : true, + "videoFilePath": "tv2.ogv", + "programs": [ + {"eventId":"931109607","title":"News of Morning", "startTime":"1430686800", "duration":"10800", "description":"Provide news in morning", "rating":"0", "audioLanguages":["eng"], "subtitleLanguages":["eng"]}, + {"eventId":"297834220","title":"Weekly News", "startTime":"1431259200", "duration":"10800", "description":"Poopular Music program", "rating":"0", "audioLanguages":["eng"], "subtitleLanguages":["eng"]}, + {"eventId":"866886159","title":"Weekly news", "startTime":"1431270000", "duration":"7200", "description":"Information program on Monday", "rating":"0", "audioLanguages":["eng"], "subtitleLanguages":["eng"]} + ] + }, + { + "_comment": "Channel Data", + "networkId": "32152", + "transportStreamId": "32152", + "serviceId": "41060", + "type": "tv", + "name": "TV #5", + "number" : 5, + "isEmergency": false, + "isFree" : true, + "videoFilePath": "tv1.ogv", + "programs": [ + {"eventId":"734475972", "title":"News of Morning", "startTime":"1430686800", "duration":"10800", "description":"Morning News", "rating":"0", "audioLanguages":["eng"], "subtitleLanguages":["eng"]}, + {"eventId":"586216742", "title":"Information Program", "startTime":"1430697600", "duration":"7200", "description":"Provide program Information.", "rating":"0", "audioLanguages":["eng"], "subtitleLanguages":["eng"]}, + {"eventId":"533612446", "title":"Entertainment Program", "startTime":"1431270000", "duration":"7200", "description":"Midnight entertainment program", "rating":"0", "audioLanguages":["eng"], "subtitleLanguages":["eng"]} + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/dom/webidl/CheckerboardReportService.webidl b/dom/webidl/CheckerboardReportService.webidl new file mode 100644 index 0000000000..1e78f874d1 --- /dev/null +++ b/dom/webidl/CheckerboardReportService.webidl @@ -0,0 +1,48 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + */ + +/* + * This file declares data structures used to communicate checkerboard reports + * from C++ code to about:checkerboard (see bug 1238042). These dictionaries + * are NOT exposed to standard web content. + */ + +enum CheckerboardReason { + "severe", + "recent" +}; + +// Individual checkerboard report. Contains fields for the severity of the +// checkerboard event, the timestamp at which it was reported, the detailed +// log of the event, and the reason this report was saved (currently either +// "severe" or "recent"). +dictionary CheckerboardReport { + unsigned long severity; + DOMTimeStamp timestamp; // milliseconds since epoch + DOMString log; + CheckerboardReason reason; +}; + +// The guard function only allows creation of this interface on the +// about:checkerboard page, and only if it's in the parent process. +[Func="mozilla::dom::CheckerboardReportService::IsEnabled", + Constructor] +interface CheckerboardReportService { + /** + * Gets the available checkerboard reports. + */ + sequence getReports(); + + /** + * Gets the state of the apz.record_checkerboarding pref. + */ + boolean isRecordingEnabled(); + + /** + * Sets the state of the apz.record_checkerboarding pref. + */ + void setRecordingEnabled(boolean aEnabled); +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 3242409c16..9a04c7009e 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -77,6 +77,7 @@ WEBIDL_FILES = [ 'ChannelMergerNode.webidl', 'ChannelSplitterNode.webidl', 'CharacterData.webidl', + 'CheckerboardReportService.webidl', 'ChildNode.webidl', 'ChromeNodeList.webidl', 'ChromeNotifications.webidl', diff --git a/gfx/2d/Logging.h b/gfx/2d/Logging.h index 3f1a11c351..61a15e6726 100644 --- a/gfx/2d/Logging.h +++ b/gfx/2d/Logging.h @@ -14,6 +14,7 @@ #ifdef MOZ_LOGGING #include "mozilla/Logging.h" #endif +#include "mozilla/Tuple.h" #if defined(MOZ_WIDGET_GONK) || defined(MOZ_WIDGET_ANDROID) #include "nsDebug.h" @@ -95,8 +96,7 @@ public: /// In the event of a crash, the crash report is annotated with first and /// the last few of these errors, under the key GraphicsCriticalError. /// The total number of errors stored in the crash report is controlled -/// by preference gfx.logging.crash.length (default is six, so by default, -/// the first as well as the last five would show up in the crash log.) +/// by preference gfx.logging.crash.length. /// /// On platforms that support MOZ_LOGGING, the story is slightly more involved. /// In that case, unless gfx.logging.level is set to 4 or higher, the output @@ -130,6 +130,7 @@ enum class LogReason : int { GlyphAllocFailedCG, InvalidRect, CannotDraw3D, // 20 + IncompatibleBasicTexturedEffect, // End MustBeLessThanThis = 101, }; @@ -196,19 +197,24 @@ struct CriticalLogger { static void CrashAction(LogReason aReason); }; +// The int is the index of the Log call; if the number of logs exceeds some preset +// capacity we may not get all of them, so the indices help figure out which +// ones we did save. The double is expected to be the "TimeDuration", +// time in seconds since the process creation. +typedef mozilla::Tuple LoggingRecordEntry; + // Implement this interface and init the Factory with an instance to // forward critical logs. +typedef std::vector LoggingRecord; class LogForwarder { public: virtual ~LogForwarder() {} virtual void Log(const std::string &aString) = 0; virtual void CrashAction(LogReason aReason) = 0; + virtual bool UpdateStringsVector(const std::string& aString) = 0; - // Provide a copy of the logs to the caller. The int is the index - // of the Log call, if the number of logs exceeds some preset capacity - // we may not get all of them, so the indices help figure out which - // ones we did save. - virtual std::vector > StringsVectorCopy() = 0; + // Provide a copy of the logs to the caller. + virtual LoggingRecord LoggingRecordCopy() = 0; }; class NoLog @@ -273,7 +279,7 @@ public: if (!str.empty()) { WriteLog(str); } - mMessage.clear(); + mMessage.str(""); } Log &operator <<(char aChar) { @@ -378,7 +384,9 @@ public: template Log &operator<<(Hexa aHex) { if (MOZ_UNLIKELY(LogIt())) { - mMessage << "0x" << std::hex << aHex.mVal << std::dec; + mMessage << std::showbase << std::hex + << aHex.mVal + << std::noshowbase << std::dec; } return *this; } diff --git a/gfx/layers/IMFYCbCrImage.cpp b/gfx/layers/IMFYCbCrImage.cpp index 791df163c5..520b0bd24d 100644 --- a/gfx/layers/IMFYCbCrImage.cpp +++ b/gfx/layers/IMFYCbCrImage.cpp @@ -220,6 +220,10 @@ IMFYCbCrImage::GetD3D9TextureClient(CompositableClient* aClient) TextureClient* IMFYCbCrImage::GetTextureClient(CompositableClient* aClient) { + if (mTextureClient) { + return mTextureClient; + } + LayersBackend backend = aClient->GetForwarder()->GetCompositorBackendType(); ID3D11Device* device = gfxWindowsPlatform::GetPlatform()->GetD3D11ImageBridgeDevice(); @@ -231,10 +235,6 @@ IMFYCbCrImage::GetTextureClient(CompositableClient* aClient) return nullptr; } - if (mTextureClient) { - return mTextureClient; - } - RefPtr ctx; device->GetImmediateContext(getter_AddRefs(ctx)); diff --git a/gfx/layers/ImageContainer.h b/gfx/layers/ImageContainer.h index 6fce2f5a9b..e94e8875ff 100644 --- a/gfx/layers/ImageContainer.h +++ b/gfx/layers/ImageContainer.h @@ -869,7 +869,7 @@ public: already_AddRefed GetAsSourceSurface() { return nullptr; } ; int32_t GetOverlayId() { return mOverlayId; } - const GonkNativeHandle& GetSidebandStream() { return mSidebandStream; } + GonkNativeHandle& GetSidebandStream() { return mSidebandStream; } gfx::IntSize GetSize() { return mSize; } diff --git a/gfx/layers/LayerSorter.cpp b/gfx/layers/LayerSorter.cpp index 3408d3c492..2cac39e41c 100644 --- a/gfx/layers/LayerSorter.cpp +++ b/gfx/layers/LayerSorter.cpp @@ -76,8 +76,8 @@ static gfxFloat RecoverZDepth(const Matrix4x4& aTransform, const gfxPoint& aPoin * unsolved without changing our rendering code. */ static LayerSortOrder CompareDepth(Layer* aOne, Layer* aTwo) { - gfxRect ourRect = ThebesRect(aOne->GetEffectiveVisibleRegion().ToUnknownRegion().GetBounds()); - gfxRect otherRect = ThebesRect(aTwo->GetEffectiveVisibleRegion().ToUnknownRegion().GetBounds()); + gfxRect ourRect = ThebesRect(aOne->GetLocalVisibleRegion().ToUnknownRegion().GetBounds()); + gfxRect otherRect = ThebesRect(aTwo->GetLocalVisibleRegion().ToUnknownRegion().GetBounds()); MOZ_ASSERT(aOne->GetParent() && aOne->GetParent()->Extend3DContext() && aTwo->GetParent() && aTwo->GetParent()->Extend3DContext()); diff --git a/gfx/layers/LayerTreeInvalidation.cpp b/gfx/layers/LayerTreeInvalidation.cpp index 4017a2ff9f..f554caa7af 100644 --- a/gfx/layers/LayerTreeInvalidation.cpp +++ b/gfx/layers/LayerTreeInvalidation.cpp @@ -123,7 +123,7 @@ NotifySubdocumentInvalidationRecursive(Layer* aLayer, NotifySubDocInvalidationFu NotifySubdocumentInvalidationRecursive(child, aCallback); } - aCallback(container, container->GetVisibleRegion().ToUnknownRegion()); + aCallback(container, container->GetLocalVisibleRegion().ToUnknownRegion()); } struct LayerPropertiesBase : public LayerProperties @@ -131,7 +131,7 @@ struct LayerPropertiesBase : public LayerProperties explicit LayerPropertiesBase(Layer* aLayer) : mLayer(aLayer) , mMaskLayer(nullptr) - , mVisibleRegion(mLayer->GetEffectiveVisibleRegion().ToUnknownRegion()) + , mVisibleRegion(mLayer->GetLocalVisibleRegion().ToUnknownRegion()) , mInvalidRegion(aLayer->GetInvalidRegion()) , mPostXScale(aLayer->GetPostXScale()) , mPostYScale(aLayer->GetPostYScale()) @@ -233,7 +233,7 @@ struct LayerPropertiesBase : public LayerProperties virtual IntRect NewTransformedBounds() { - return TransformRect(mLayer->GetVisibleRegion().ToUnknownRegion().GetBounds(), + return TransformRect(mLayer->GetLocalVisibleRegion().ToUnknownRegion().GetBounds(), GetTransformForInvalidation(mLayer)); } @@ -345,7 +345,7 @@ struct ContainerLayerProperties : public LayerPropertiesBase } if (invalidateChildsCurrentArea) { aGeometryChanged = true; - AddTransformedRegion(result, child->GetVisibleRegion().ToUnknownRegion(), + AddTransformedRegion(result, child->GetLocalVisibleRegion().ToUnknownRegion(), GetTransformForInvalidation(child)); if (aCallback) { NotifySubdocumentInvalidationRecursive(child, aCallback); @@ -477,7 +477,7 @@ struct ImageLayerProperties : public LayerPropertiesBase { ImageLayer* imageLayer = static_cast(mLayer.get()); - if (!imageLayer->GetVisibleRegion().ToUnknownRegion().IsEqual(mVisibleRegion)) { + if (!imageLayer->GetLocalVisibleRegion().ToUnknownRegion().IsEqual(mVisibleRegion)) { aGeometryChanged = true; IntRect result = NewTransformedBounds(); result = result.Union(OldTransformedBounds()); @@ -619,7 +619,7 @@ LayerPropertiesBase::ComputeDifferences(Layer* aRoot, NotifySubDocInvalidationFu } else { ClearInvalidations(aRoot); } - IntRect result = TransformRect(aRoot->GetVisibleRegion().ToUnknownRegion().GetBounds(), + IntRect result = TransformRect(aRoot->GetLocalVisibleRegion().ToUnknownRegion().GetBounds(), aRoot->GetLocalTransform()); result = result.Union(OldTransformedBounds()); if (aGeometryChanged != nullptr) { diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index 8e2a8fac29..e73cf33e6d 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -602,7 +602,7 @@ Layer::GetEffectiveClipRect() } const LayerIntRegion& -Layer::GetEffectiveVisibleRegion() +Layer::GetLocalVisibleRegion() { if (LayerComposite* shadow = AsLayerComposite()) { return shadow->GetShadowVisibleRegion(); @@ -799,6 +799,11 @@ Layer::CalculateScissorRect(const RenderTargetIntRect& aCurrentScissorRect) // When our visible region is empty, our parent may not have created the // intermediate surface that we would require for correct clipping; however, // this does not matter since we are invisible. + // Note that we do not use GetLocalVisibleRegion(), because that can be + // empty for a layer whose rendered contents have been async-scrolled + // completely offscreen, but for which we still need to draw a + // checkerboarding backround color, and calculating an empty scissor rect + // for such a layer would prevent that (see bug 1247452 comment 10). return RenderTargetIntRect(currentClip.TopLeft(), RenderTargetIntSize(0, 0)); } @@ -888,7 +893,7 @@ Layer::GetLocalTransform() { Matrix4x4 transform; if (LayerComposite* shadow = AsLayerComposite()) - transform = shadow->GetShadowTransform(); + transform = shadow->GetShadowBaseTransform(); else transform = mTransform; @@ -1017,7 +1022,7 @@ Layer::GetVisibleRegionRelativeToRootLayer(nsIntRegion& aResult, } IntPoint offset; - aResult = GetEffectiveVisibleRegion().ToUnknownRegion(); + aResult = GetLocalVisibleRegion().ToUnknownRegion(); for (Layer* layer = this; layer; layer = layer->GetParent()) { gfx::Matrix matrix; if (!layer->GetLocalTransform().Is2D(&matrix) || @@ -1054,7 +1059,7 @@ Layer::GetVisibleRegionRelativeToRootLayer(nsIntRegion& aResult, // Retreive the translation from sibling to |layer|. The accumulated // visible region is currently oriented with |layer|. IntPoint siblingOffset = RoundedToInt(siblingMatrix.GetTranslation()); - nsIntRegion siblingVisibleRegion(sibling->GetEffectiveVisibleRegion().ToUnknownRegion()); + nsIntRegion siblingVisibleRegion(sibling->GetLocalVisibleRegion().ToUnknownRegion()); // Translate the siblings region to |layer|'s origin. siblingVisibleRegion.MoveBy(-siblingOffset.x, -siblingOffset.y); // Apply the sibling's clip. @@ -1303,7 +1308,7 @@ ContainerLayer::HasMultipleChildren() const Maybe& clipRect = child->GetEffectiveClipRect(); if (clipRect && clipRect->IsEmpty()) continue; - if (child->GetVisibleRegion().IsEmpty()) + if (child->GetLocalVisibleRegion().IsEmpty()) continue; ++count; if (count > 1) @@ -1382,7 +1387,7 @@ ContainerLayer::DefaultComputeEffectiveTransforms(const Matrix4x4& aTransformToS } else { float opacity = GetEffectiveOpacity(); CompositionOp blendMode = GetEffectiveMixBlendMode(); - if (((opacity != 1.0f || blendMode != CompositionOp::OP_OVER) && HasMultipleChildren()) || + if (((opacity != 1.0f || blendMode != CompositionOp::OP_OVER) && (HasMultipleChildren() || Creates3DContextWithExtendingChildren())) || (!idealTransform.Is2D() && Creates3DContextWithExtendingChildren())) { useIntermediateSurface = true; } else { @@ -1419,7 +1424,7 @@ ContainerLayer::DefaultComputeEffectiveTransforms(const Matrix4x4& aTransformToS * the calculations performed by CalculateScissorRect above. * Nor for a child with a mask layer. */ - if (checkClipRect && (clipRect && !clipRect->IsEmpty() && !child->GetVisibleRegion().IsEmpty())) { + if (checkClipRect && (clipRect && !clipRect->IsEmpty() && !child->GetLocalVisibleRegion().IsEmpty())) { useIntermediateSurface = true; break; } @@ -1445,7 +1450,7 @@ ContainerLayer::DefaultComputeEffectiveTransforms(const Matrix4x4& aTransformToS // transform while 2D is expected. idealTransform.ProjectTo2D(); } - mUseIntermediateSurface = useIntermediateSurface && !GetEffectiveVisibleRegion().IsEmpty(); + mUseIntermediateSurface = useIntermediateSurface && !GetLocalVisibleRegion().IsEmpty(); if (useIntermediateSurface) { ComputeEffectiveTransformsForChildren(Matrix4x4::From2D(residual)); } else { @@ -1475,7 +1480,7 @@ ContainerLayer::DefaultComputeSupportsComponentAlphaChildren(bool* aNeedsSurface bool needsSurfaceCopy = false; CompositionOp blendMode = GetEffectiveMixBlendMode(); if (UseIntermediateSurface()) { - if (GetEffectiveVisibleRegion().GetNumRects() == 1 && + if (GetLocalVisibleRegion().GetNumRects() == 1 && (GetContentFlags() & Layer::CONTENT_OPAQUE)) { mSupportsComponentAlphaChildren = true; @@ -1884,6 +1889,9 @@ Layer::PrintInfo(std::stringstream& aStream, const char* aPrefix) if (!mTransform.IsIdentity()) { AppendToString(aStream, mTransform, " [transform=", "]"); } + if (!GetEffectiveTransform().IsIdentity()) { + AppendToString(aStream, GetEffectiveTransform(), " [effective-transform=", "]"); + } if (mTransformIsPerspective) { aStream << " [perspective]"; } @@ -2005,8 +2013,8 @@ Layer::DumpPacket(layerscope::LayersPacket* aPacket, const void* aParent) if (const Maybe& clipRect = lc->GetShadowClipRect()) { DumpRect(s->mutable_clip(), *clipRect); } - if (!lc->GetShadowTransform().IsIdentity()) { - DumpTransform(s->mutable_transform(), lc->GetShadowTransform()); + if (!lc->GetShadowBaseTransform().IsIdentity()) { + DumpTransform(s->mutable_transform(), lc->GetShadowBaseTransform()); } if (!lc->GetShadowVisibleRegion().IsEmpty()) { DumpRegion(s->mutable_vregion(), lc->GetShadowVisibleRegion().ToUnknownRegion()); @@ -2414,8 +2422,8 @@ PrintInfo(std::stringstream& aStream, LayerComposite* aLayerComposite) if (const Maybe& clipRect = aLayerComposite->GetShadowClipRect()) { AppendToString(aStream, *clipRect, " [shadow-clip=", "]"); } - if (!aLayerComposite->GetShadowTransform().IsIdentity()) { - AppendToString(aStream, aLayerComposite->GetShadowTransform(), " [shadow-transform=", "]"); + if (!aLayerComposite->GetShadowBaseTransform().IsIdentity()) { + AppendToString(aStream, aLayerComposite->GetShadowBaseTransform(), " [shadow-transform=", "]"); } if (!aLayerComposite->GetShadowVisibleRegion().IsEmpty()) { AppendToString(aStream, aLayerComposite->GetShadowVisibleRegion().ToUnknownRegion(), " [shadow-visible=", "]"); diff --git a/gfx/layers/Layers.h b/gfx/layers/Layers.h index c4b1ccee3f..ed55e4a5e0 100644 --- a/gfx/layers/Layers.h +++ b/gfx/layers/Layers.h @@ -1351,7 +1351,7 @@ public: /** * Returns the local transform for this layer: either mTransform or, - * for shadow layers, GetShadowTransform(), in either case with the + * for shadow layers, GetShadowBaseTransform(), in either case with the * pre- and post-scales applied. */ const gfx::Matrix4x4 GetLocalTransform(); @@ -1485,7 +1485,7 @@ public: // values that should be used when drawing this layer to screen, // accounting for this layer possibly being a shadow. const Maybe& GetEffectiveClipRect(); - const LayerIntRegion& GetEffectiveVisibleRegion(); + const LayerIntRegion& GetLocalVisibleRegion(); bool Extend3DContext() { return GetContentFlags() & CONTENT_EXTEND_3D_CONTEXT; @@ -1507,7 +1507,7 @@ public: // For containers extending 3D context, visible region // is meaningless, since they are just intermediate result of // content. - return !GetEffectiveVisibleRegion().IsEmpty() || Extend3DContext(); + return !GetLocalVisibleRegion().IsEmpty() || Extend3DContext(); } /** @@ -2100,7 +2100,7 @@ public: RenderTargetIntRect GetIntermediateSurfaceRect() { NS_ASSERTION(mUseIntermediateSurface, "Must have intermediate surface"); - return RenderTargetIntRect::FromUnknownRect(GetEffectiveVisibleRegion().ToUnknownRegion().GetBounds()); + return RenderTargetIntRect::FromUnknownRect(GetLocalVisibleRegion().ToUnknownRegion().GetBounds()); } /** diff --git a/gfx/layers/MacIOSurfaceHelpers.cpp b/gfx/layers/MacIOSurfaceHelpers.cpp new file mode 100644 index 0000000000..8d7863f315 --- /dev/null +++ b/gfx/layers/MacIOSurfaceHelpers.cpp @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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/. */ + +#include "MacIOSurfaceHelpers.h" +#include "mozilla/gfx/MacIOSurface.h" +#include "YCbCrUtils.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +already_AddRefed +CreateSourceSurfaceFromMacIOSurface(MacIOSurface* aSurface) +{ + RefPtr dataSurface; + aSurface->Lock(); + size_t bytesPerRow = aSurface->GetBytesPerRow(); + size_t ioWidth = aSurface->GetDevicePixelWidth(); + size_t ioHeight = aSurface->GetDevicePixelHeight(); + + SurfaceFormat format = aSurface->GetFormat() == SurfaceFormat::NV12 ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8; + + dataSurface = Factory::CreateDataSourceSurface(IntSize(ioWidth, ioHeight), format); + if (NS_WARN_IF(!dataSurface)) { + return nullptr; + } + + DataSourceSurface::MappedSurface mappedSurface; + if (!dataSurface->Map(DataSourceSurface::WRITE, &mappedSurface)) + return nullptr; + + if (aSurface->GetFormat() == SurfaceFormat::NV12) { + if (aSurface->GetDevicePixelWidth() > PlanarYCbCrImage::MAX_DIMENSION || + aSurface->GetDevicePixelHeight() > PlanarYCbCrImage::MAX_DIMENSION) { + return nullptr; + } + + /* Extract and separate the CbCr planes */ + size_t cbCrStride = aSurface->GetBytesPerRow(1); + size_t cbCrWidth = aSurface->GetDevicePixelWidth(1); + size_t cbCrHeight = aSurface->GetDevicePixelHeight(1); + + auto cbPlane = MakeUnique(cbCrWidth * cbCrHeight); + auto crPlane = MakeUnique(cbCrWidth * cbCrHeight); + + uint8_t* src = (uint8_t*)aSurface->GetBaseAddressOfPlane(1); + uint8_t* cbDest = cbPlane.get(); + uint8_t* crDest = crPlane.get(); + + for (size_t i = 0; i < cbCrHeight; i++) { + uint8_t* rowSrc = src + cbCrStride * i; + for (size_t j = 0; j < cbCrWidth; j++) { + *cbDest = *rowSrc; + cbDest++; + rowSrc++; + *crDest = *rowSrc; + crDest++; + rowSrc++; + } + } + + /* Convert to RGB */ + PlanarYCbCrData data; + data.mYChannel = (uint8_t*)aSurface->GetBaseAddressOfPlane(0); + data.mYStride = aSurface->GetBytesPerRow(0); + data.mYSize = IntSize(aSurface->GetDevicePixelWidth(0), aSurface->GetDevicePixelHeight(0)); + data.mCbChannel = cbPlane.get(); + data.mCrChannel = crPlane.get(); + data.mCbCrStride = cbCrWidth; + data.mCbCrSize = IntSize(cbCrWidth, cbCrHeight); + data.mPicSize = data.mYSize; + + ConvertYCbCrToRGB(data, SurfaceFormat::B8G8R8X8, IntSize(ioWidth, ioHeight), mappedSurface.mData, mappedSurface.mStride); + } else { + unsigned char* ioData = (unsigned char*)aSurface->GetBaseAddress(); + + for (size_t i = 0; i < ioHeight; ++i) { + memcpy(mappedSurface.mData + i * mappedSurface.mStride, + ioData + i * bytesPerRow, + ioWidth * 4); + } + } + + dataSurface->Unmap(); + aSurface->Unlock(); + + return dataSurface.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/layers/MacIOSurfaceHelpers.h b/gfx/layers/MacIOSurfaceHelpers.h new file mode 100644 index 0000000000..80f9cf872b --- /dev/null +++ b/gfx/layers/MacIOSurfaceHelpers.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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/. */ + +#ifndef GFX_MACIOSURFACEHELPERS_H +#define GFX_MACIOSURFACEHELPERS_H + +class MacIOSurface; +template struct already_AddRefed; + +namespace mozilla { + +namespace gfx { +class SourceSurface; +} + +namespace layers { + +// Unlike MacIOSurface::GetAsSurface, this also handles IOSurface formats +// with multiple planes and does YCbCr to RGB conversion, if necessary. +already_AddRefed +CreateSourceSurfaceFromMacIOSurface(MacIOSurface* aSurface); + +} // namespace layers +} // namespace mozilla + +#endif // GFX_MACIOSURFACEHELPERS_H diff --git a/gfx/layers/MacIOSurfaceImage.cpp b/gfx/layers/MacIOSurfaceImage.cpp index e8817f1634..a61380ded1 100644 --- a/gfx/layers/MacIOSurfaceImage.cpp +++ b/gfx/layers/MacIOSurfaceImage.cpp @@ -3,12 +3,12 @@ * 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/. */ +#include "MacIOSurfaceHelpers.h" #include "MacIOSurfaceImage.h" #include "mozilla/layers/CompositableClient.h" #include "mozilla/layers/CompositableForwarder.h" #include "mozilla/layers/MacIOSurfaceTextureClientOGL.h" #include "mozilla/UniquePtr.h" -#include "YCbCrUtils.h" using namespace mozilla; using namespace mozilla::layers; @@ -30,77 +30,5 @@ MacIOSurfaceImage::GetTextureClient(CompositableClient* aClient) already_AddRefed MacIOSurfaceImage::GetAsSourceSurface() { - RefPtr dataSurface; - mSurface->Lock(); - size_t bytesPerRow = mSurface->GetBytesPerRow(); - size_t ioWidth = mSurface->GetDevicePixelWidth(); - size_t ioHeight = mSurface->GetDevicePixelHeight(); - - SurfaceFormat format = mSurface->GetFormat() == SurfaceFormat::NV12 ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8; - - dataSurface = Factory::CreateDataSourceSurface(IntSize(ioWidth, ioHeight), format); - if (NS_WARN_IF(!dataSurface)) { - return nullptr; - } - - DataSourceSurface::MappedSurface mappedSurface; - if (!dataSurface->Map(DataSourceSurface::WRITE, &mappedSurface)) - return nullptr; - - if (mSurface->GetFormat() == SurfaceFormat::NV12) { - if (mSurface->GetDevicePixelWidth() > PlanarYCbCrImage::MAX_DIMENSION || - mSurface->GetDevicePixelHeight() > PlanarYCbCrImage::MAX_DIMENSION) { - return nullptr; - } - - /* Extract and separate the CbCr planes */ - size_t cbCrStride = mSurface->GetBytesPerRow(1); - size_t cbCrWidth = mSurface->GetDevicePixelWidth(1); - size_t cbCrHeight = mSurface->GetDevicePixelHeight(1); - - auto cbPlane = MakeUnique(cbCrWidth * cbCrHeight); - auto crPlane = MakeUnique(cbCrWidth * cbCrHeight); - - uint8_t* src = (uint8_t*)mSurface->GetBaseAddressOfPlane(1); - uint8_t* cbDest = cbPlane.get(); - uint8_t* crDest = crPlane.get(); - - for (size_t i = 0; i < cbCrHeight; i++) { - uint8_t* rowSrc = src + cbCrStride * i; - for (size_t j = 0; j < cbCrWidth; j++) { - *cbDest = *rowSrc; - cbDest++; - rowSrc++; - *crDest = *rowSrc; - crDest++; - rowSrc++; - } - } - - /* Convert to RGB */ - PlanarYCbCrData data; - data.mYChannel = (uint8_t*)mSurface->GetBaseAddressOfPlane(0); - data.mYStride = mSurface->GetBytesPerRow(0); - data.mYSize = IntSize(mSurface->GetDevicePixelWidth(0), mSurface->GetDevicePixelHeight(0)); - data.mCbChannel = cbPlane.get(); - data.mCrChannel = crPlane.get(); - data.mCbCrStride = cbCrWidth; - data.mCbCrSize = IntSize(cbCrWidth, cbCrHeight); - data.mPicSize = data.mYSize; - - ConvertYCbCrToRGB(data, SurfaceFormat::B8G8R8X8, IntSize(ioWidth, ioHeight), mappedSurface.mData, mappedSurface.mStride); - } else { - unsigned char* ioData = (unsigned char*)mSurface->GetBaseAddress(); - - for (size_t i = 0; i < ioHeight; ++i) { - memcpy(mappedSurface.mData + i * mappedSurface.mStride, - ioData + i * bytesPerRow, - ioWidth * 4); - } - } - - dataSurface->Unmap(); - mSurface->Unlock(); - - return dataSurface.forget(); + return CreateSourceSurfaceFromMacIOSurface(mSurface); } diff --git a/gfx/layers/PersistentBufferProvider.h b/gfx/layers/PersistentBufferProvider.h index 35835b7e94..80cd95a0d2 100644 --- a/gfx/layers/PersistentBufferProvider.h +++ b/gfx/layers/PersistentBufferProvider.h @@ -40,13 +40,13 @@ public: * to have remained the same since the call to * ReturnAndUseDT. */ - virtual gfx::DrawTarget* GetDT(const gfx::IntRect& aPersistedRect) = 0; + virtual already_AddRefed GetDT(const gfx::IntRect& aPersistedRect) = 0; /** * Return a DrawTarget to the PersistentBufferProvider and indicate the * contents of this DrawTarget is to be considered current by the - * BufferProvider + * BufferProvider. The caller should forget any references to the DrawTarget. */ - virtual bool ReturnAndUseDT(gfx::DrawTarget* aDT) = 0; + virtual bool ReturnAndUseDT(already_AddRefed aDT) = 0; virtual already_AddRefed GetSnapshot() = 0; protected: @@ -63,8 +63,15 @@ public: bool IsValid() { return !!mDrawTarget; } virtual LayersBackend GetType() { return LayersBackend::LAYERS_BASIC; } - gfx::DrawTarget* GetDT(const gfx::IntRect& aPersistedRect) { return mDrawTarget; } - bool ReturnAndUseDT(gfx::DrawTarget* aDT) { MOZ_ASSERT(mDrawTarget == aDT); return true; } + already_AddRefed GetDT(const gfx::IntRect& aPersistedRect) { + RefPtr dt(mDrawTarget); + return dt.forget(); + } + bool ReturnAndUseDT(already_AddRefed aDT) { + RefPtr dt(aDT); + MOZ_ASSERT(mDrawTarget == dt); + return true; + } virtual already_AddRefed GetSnapshot() { return mDrawTarget->Snapshot(); } private: RefPtr mDrawTarget; diff --git a/gfx/layers/ReadbackProcessor.cpp b/gfx/layers/ReadbackProcessor.cpp index 0b3cb70e57..de8042efb5 100644 --- a/gfx/layers/ReadbackProcessor.cpp +++ b/gfx/layers/ReadbackProcessor.cpp @@ -63,7 +63,7 @@ FindBackgroundLayer(ReadbackLayer* aLayer, nsIntPoint* aOffset) nsIntPoint backgroundOffset(int32_t(backgroundTransform._31), int32_t(backgroundTransform._32)); IntRect rectInBackground(transformOffset - backgroundOffset, aLayer->GetSize()); - const nsIntRegion visibleRegion = l->GetEffectiveVisibleRegion().ToUnknownRegion(); + const nsIntRegion visibleRegion = l->GetLocalVisibleRegion().ToUnknownRegion(); if (!visibleRegion.Intersects(rectInBackground)) continue; // Since l is present in the background, from here on we either choose l diff --git a/gfx/layers/RenderTrace.cpp b/gfx/layers/RenderTrace.cpp index 15b08789b5..d383d0a1d5 100644 --- a/gfx/layers/RenderTrace.cpp +++ b/gfx/layers/RenderTrace.cpp @@ -30,7 +30,7 @@ void RenderTraceLayers(Layer *aLayer, const char *aColor, const gfx::Matrix4x4 a gfx::Matrix4x4 trans = aRootTransform * aLayer->GetTransform(); trans.ProjectTo2D(); - gfx::IntRect clipRect = aLayer->GetEffectiveVisibleRegion().GetBounds(); + gfx::IntRect clipRect = aLayer->GetLocalVisibleRegion().GetBounds(); Rect rect(clipRect.x, clipRect.y, clipRect.width, clipRect.height); trans.TransformBounds(rect); diff --git a/gfx/layers/RotatedBuffer.cpp b/gfx/layers/RotatedBuffer.cpp index 9f781235a3..0dfff9ca15 100644 --- a/gfx/layers/RotatedBuffer.cpp +++ b/gfx/layers/RotatedBuffer.cpp @@ -222,13 +222,13 @@ RotatedContentBuffer::DrawTo(PaintedLayer* aLayer, if (!aLayer->GetValidRegion().Contains(BufferRect()) || (ToData(aLayer)->GetClipToVisibleRegion() && !aLayer->GetVisibleRegion().ToUnknownRegion().Contains(BufferRect())) || - IsClippingCheap(aTarget, aLayer->GetEffectiveVisibleRegion().ToUnknownRegion())) { + IsClippingCheap(aTarget, aLayer->GetLocalVisibleRegion().ToUnknownRegion())) { // We don't want to draw invalid stuff, so we need to clip. Might as // well clip to the smallest area possible --- the visible region. // Bug 599189 if there is a non-integer-translation transform in aTarget, - // we might sample pixels outside GetEffectiveVisibleRegion(), which is wrong + // we might sample pixels outside GetLocalVisibleRegion(), which is wrong // and may cause gray lines. - gfxUtils::ClipToRegion(aTarget, aLayer->GetEffectiveVisibleRegion().ToUnknownRegion()); + gfxUtils::ClipToRegion(aTarget, aLayer->GetLocalVisibleRegion().ToUnknownRegion()); clipped = true; } diff --git a/gfx/layers/TextureWrapperImage.cpp b/gfx/layers/TextureWrapperImage.cpp index 1329728ea5..14c3645883 100644 --- a/gfx/layers/TextureWrapperImage.cpp +++ b/gfx/layers/TextureWrapperImage.cpp @@ -40,7 +40,12 @@ TextureWrapperImage::GetAsSourceSurface() return nullptr; } - return mTextureClient->BorrowDrawTarget()->Snapshot(); + RefPtr dt = mTextureClient->BorrowDrawTarget(); + if (!dt) { + return nullptr; + } + + return dt->Snapshot(); } TextureClient* diff --git a/gfx/layers/TreeTraversal.h b/gfx/layers/TreeTraversal.h index 7c5f958794..0bd8debcff 100644 --- a/gfx/layers/TreeTraversal.h +++ b/gfx/layers/TreeTraversal.h @@ -8,19 +8,129 @@ #define mozilla_layers_TreeTraversal_h #include -#include namespace mozilla { namespace layers { /* - * Returned by |aAction| in ForEachNode. If the action returns - * TraversalFlag::Skip, the node's children are not added to the traverrsal - * stack. Otherwise, a return value of TraversalFlag::Continue indicates that - * ForEachNode should traverse each of the node's children. + * Returned by |aPostAction| and |aPreAction| in ForEachNode, indicates + * the behavior to follow either action: + * + * TraversalFlag::Skip - the node's children are not traversed. If this + * flag is returned by |aPreAction|, |aPostAction| is skipped for the + * current node, as well. + * TraversalFlag::Continue - traversal continues normally. + * TraversalFlag::Abort - traversal stops immediately. */ -enum class TraversalFlag { Skip, Continue }; +enum class TraversalFlag { Skip, Continue, Abort }; + +/* + * Do a depth-first traversal of the tree rooted at |aRoot|, performing + * |aPreAction| before traversal of children and |aPostAction| after. + * + * Returns true if traversal aborted, false if continued normally. If + * TraversalFlag::Skip is returned in |aPreAction|, then |aPostAction| + * is not performed. + */ +template +static auto ForEachNode(Node* aRoot, const PreAction& aPreAction, const PostAction& aPostAction) -> +typename EnableIf::value && + IsSame::value, bool>::Type +{ + if (!aRoot) { + return false; + } + + TraversalFlag result = aPreAction(aRoot); + + if (result == TraversalFlag::Abort) { + return true; + } + + if (result == TraversalFlag::Continue) { + for (Node* child = aRoot->GetLastChild(); + child; + child = child->GetPrevSibling()) { + bool abort = ForEachNode(child, aPreAction, aPostAction); + if (abort) { + return true; + } + } + + result = aPostAction(aRoot); + + if (result == TraversalFlag::Abort) { + return true; + } + } + + return false; +} + +/* + * Do a depth-first traversal of the tree rooted at |aRoot|, performing + * |aPreAction| before traversal of children and |aPostAction| after. + */ +template +static auto ForEachNode(Node* aRoot, const PreAction& aPreAction, const PostAction& aPostAction) -> +typename EnableIf::value && + IsSame::value, void>::Type +{ + if (!aRoot) { + return; + } + + aPreAction(aRoot); + + for (Node* child = aRoot->GetLastChild(); + child; + child = child->GetPrevSibling()) { + ForEachNode(child, aPreAction, aPostAction); + } + + aPostAction(aRoot); +} + +/* + * ForEachNode pre-order traversal, using TraversalFlag. + */ +template +auto ForEachNode(Node* aRoot, const PreAction& aPreAction) -> +typename EnableIf::value, bool>::Type +{ + return ForEachNode(aRoot, aPreAction, [](Node* aNode){ return TraversalFlag::Continue; }); +} + +/* + * ForEachNode pre-order, not using TraversalFlag. + */ +template +auto ForEachNode(Node* aRoot, const PreAction& aPreAction) -> +typename EnableIf::value, void>::Type +{ + ForEachNode(aRoot, aPreAction, [](Node* aNode){}); +} + +/* + * ForEachNode post-order traversal, using TraversalFlag. + */ +template +auto ForEachNodePostOrder(Node* aRoot, const PostAction& aPostAction) -> +typename EnableIf::value, bool>::Type +{ + return ForEachNode(aRoot, [](Node* aNode){ return TraversalFlag::Continue; }, aPostAction); +} + +/* + * ForEachNode post-order, not using TraversalFlag. + */ +template +auto ForEachNodePostOrder(Node* aRoot, const PostAction& aPostAction) -> +typename EnableIf::value, void>::Type +{ + ForEachNode(aRoot, [](Node* aNode){}, aPostAction); +} /* * Do a breadth-first search of the tree rooted at |aRoot|, and return the @@ -57,97 +167,51 @@ Node* BreadthFirstSearch(Node* aRoot, const Condition& aCondition) } /* - * Do a depth-first search of the tree rooted at |aRoot|, and return the - * first visited node that satisfies |aCondition|, or nullptr if no such node - * was found. + * Do a pre-order, depth-first search of the tree rooted at |aRoot|, and + * return the first visited node that satisfies |aCondition|, or nullptr + * if no such node was found. * * |Node| should have methods GetLastChild() and GetPrevSibling(). */ template Node* DepthFirstSearch(Node* aRoot, const Condition& aCondition) { - if (!aRoot) { - return nullptr; - } + Node* result = nullptr; - std::stack stack; - stack.push(aRoot); - while (!stack.empty()) { - Node* node = stack.top(); - stack.pop(); + ForEachNode(aRoot, + [&aCondition, &result](Node* aNode) + { + if (aCondition(aNode)) { + result = aNode; + return TraversalFlag::Abort; + } - if (aCondition(node)) { - return node; - } + return TraversalFlag::Continue; + }); - for (Node* child = node->GetLastChild(); - child; - child = child->GetPrevSibling()) { - stack.push(child); - } - } - - return nullptr; + return result; } /* - * Do a depth-first traversal of the tree rooted at |aRoot|, performing - * |aAction| for each node. |aAction| can return a TraversalFlag to determine - * whether or not to omit the children of a particular node. - * - * If |aAction| does not return a TraversalFlag, it must return nothing. There - * is no ForEachNode instance handling types other than void or TraversalFlag. + * Perform a post-order, depth-first search starting at aRoot. */ -template -auto ForEachNode(Node* aRoot, const Action& aAction) -> -typename EnableIf::value, void>::Type +template +Node* DepthFirstSearchPostOrder(Node* aRoot, const Condition& aCondition) { - if (!aRoot) { - return; - } + Node* result = nullptr; - std::stack stack; - stack.push(aRoot); + ForEachNodePostOrder(aRoot, + [&aCondition, &result](Node* aNode) + { + if (aCondition(aNode)) { + result = aNode; + return TraversalFlag::Abort; + } - while (!stack.empty()) { - Node* node = stack.top(); - stack.pop(); + return TraversalFlag::Continue; + }); - TraversalFlag result = aAction(node); - - if (result == TraversalFlag::Continue) { - for (Node* child = node->GetLastChild(); - child; - child = child->GetPrevSibling()) { - stack.push(child); - } - } - } -} - -template -auto ForEachNode(Node* aRoot, const Action& aAction) -> -typename EnableIf::value, void>::Type -{ - if (!aRoot) { - return; - } - - std::stack stack; - stack.push(aRoot); - - while (!stack.empty()) { - Node* node = stack.top(); - stack.pop(); - - aAction(node); - - for (Node* child = node->GetLastChild(); - child; - child = child->GetPrevSibling()) { - stack.push(child); - } - } + return result; } } diff --git a/gfx/layers/apz/public/GeckoContentController.h b/gfx/layers/apz/public/GeckoContentController.h index 169c3731c4..ecaafe20c5 100644 --- a/gfx/layers/apz/public/GeckoContentController.h +++ b/gfx/layers/apz/public/GeckoContentController.h @@ -12,6 +12,7 @@ #include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2 #include "mozilla/EventForwards.h" // for Modifiers #include "nsISupportsImpl.h" +#include "ThreadSafeRefcountingWithMainThreadDestruction.h" class Task; @@ -21,7 +22,12 @@ namespace layers { class GeckoContentController { public: - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GeckoContentController) + /** + * At least one class deriving from GeckoContentController needs to do + * synchronous cleanup on the main thread, so we use + * NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION. + */ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION(GeckoContentController) /** * Requests a paint of the given FrameMetrics |aFrameMetrics| from Gecko. @@ -88,6 +94,7 @@ public: * controller. This method allows APZ to query the controller for such a * region. A return value of true indicates that the controller has such a * region, and it is returned in |aOutRegion|. + * This method needs to be called on the main thread. * TODO: once bug 928833 is implemented, this should be removed, as * APZ can then get the correct touch-sensitive region for each frame * directly from the layer. @@ -144,7 +151,14 @@ public: */ virtual void NotifyFlushComplete() = 0; + virtual void UpdateOverscrollVelocity(const float aX, const float aY) {} + virtual void UpdateOverscrollOffset(const float aX,const float aY) {} + GeckoContentController() {} + virtual void ChildAdopted() {} + /** + * Needs to be called on the main thread. + */ virtual void Destroy() {} protected: diff --git a/gfx/layers/apz/src/APZCTreeManager.cpp b/gfx/layers/apz/src/APZCTreeManager.cpp index 81ec7c33b5..499402b7fa 100644 --- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -3,6 +3,7 @@ * 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/. */ +#include #include "APZCTreeManager.h" #include "AsyncPanZoomController.h" #include "Compositor.h" // for Compositor @@ -1502,7 +1503,20 @@ APZCTreeManager::GetTargetNode(const ScrollableLayerGuid& aGuid, GuidComparator aComparator) { mTreeLock.AssertCurrentThreadOwns(); - RefPtr target = FindTargetNode(mRootNode, aGuid, aComparator); + RefPtr target = DepthFirstSearchPostOrder(mRootNode.get(), + [&aGuid, &aComparator](HitTestingTreeNode* node) + { + bool matches = false; + if (node->GetApzc()) { + if (aComparator) { + matches = aComparator(aGuid, node->GetApzc()->GetGuid()); + } else { + matches = node->GetApzc()->Matches(aGuid); + } + } + return matches; + } + ); return target.forget(); } @@ -1612,36 +1626,6 @@ APZCTreeManager::SetLongTapEnabled(bool aLongTapEnabled) NewRunnableFunction(GestureEventListener::SetLongTapEnabled, aLongTapEnabled)); } -HitTestingTreeNode* -APZCTreeManager::FindTargetNode(HitTestingTreeNode* aNode, - const ScrollableLayerGuid& aGuid, - GuidComparator aComparator) -{ - mTreeLock.AssertCurrentThreadOwns(); - - // This walks the tree in depth-first, reverse order, so that it encounters - // APZCs front-to-back on the screen. - for (HitTestingTreeNode* node = aNode; node; node = node->GetPrevSibling()) { - HitTestingTreeNode* match = FindTargetNode(node->GetLastChild(), aGuid, aComparator); - if (match) { - return match; - } - - bool matches = false; - if (node->GetApzc()) { - if (aComparator) { - matches = aComparator(aGuid, node->GetApzc()->GetGuid()); - } else { - matches = node->GetApzc()->Matches(aGuid); - } - } - if (matches) { - return node; - } - } - return nullptr; -} - RefPtr APZCTreeManager::FindScrollNode(const AsyncDragMetrics& aDragMetrics) { @@ -1662,51 +1646,60 @@ APZCTreeManager::GetAPZCAtPoint(HitTestingTreeNode* aNode, // This walks the tree in depth-first, reverse order, so that it encounters // APZCs front-to-back on the screen. - for (HitTestingTreeNode* node = aNode; node; node = node->GetPrevSibling()) { - if (node->IsOutsideClip(aHitTestPoint)) { - // If the point being tested is outside the clip region for this node - // then we don't need to test against this node or any of its children. - // Just skip it and move on. - APZCTM_LOG("Point %f %f outside clip for node %p\n", - aHitTestPoint.x, aHitTestPoint.y, node); - continue; - } + HitTestingTreeNode* resultNode; + HitTestingTreeNode* root = aNode; + std::stack hitTestPoints; + hitTestPoints.push(aHitTestPoint); - AsyncPanZoomController* result = nullptr; - - // First check the subtree rooted at this node, because deeper nodes - // are more "in front". - Maybe hitTestPointForChildLayers = node->Untransform(aHitTestPoint); - if (hitTestPointForChildLayers) { - ParentLayerPoint childPoint = ViewAs(hitTestPointForChildLayers.ref(), - PixelCastJustification::MovingDownToChildren); - result = GetAPZCAtPoint(node->GetLastChild(), childPoint, aOutHitResult); - } - - // If we didn't match anything in the subtree, check |node|. - if (*aOutHitResult == HitNothing) { - APZCTM_LOG("Testing ParentLayer point %s (Layer %s) against node %p\n", - Stringify(aHitTestPoint).c_str(), - hitTestPointForChildLayers ? Stringify(hitTestPointForChildLayers.ref()).c_str() : "nil", - node); - HitTestResult hitResult = node->HitTest(aHitTestPoint); - if (hitResult != HitTestResult::HitNothing) { - result = node->GetNearestContainingApzcWithSameLayersId(); - if (!result) { - result = FindRootApzcForLayersId(node->GetLayersId()); - MOZ_ASSERT(result); + ForEachNode(root, + [&hitTestPoints](HitTestingTreeNode* aNode) { + if (aNode->IsOutsideClip(hitTestPoints.top())) { + // If the point being tested is outside the clip region for this node + // then we don't need to test against this node or any of its children. + // Just skip it and move on. + APZCTM_LOG("Point %f %f outside clip for node %p\n", + hitTestPoints.top().x, hitTestPoints.top().y, aNode); + return TraversalFlag::Skip; } - APZCTM_LOG("Successfully matched APZC %p via node %p (hit result %d)\n", - result, node, hitResult); - MOZ_ASSERT(hitResult == HitLayer || hitResult == HitDispatchToContentRegion); - // If event regions are disabled, *aOutHitResult will be HitLayer - *aOutHitResult = hitResult; + // First check the subtree rooted at this node, because deeper nodes + // are more "in front". + Maybe hitTestPointForChildLayers = aNode->Untransform(hitTestPoints.top()); + APZCTM_LOG("Transformed ParentLayer point %s to layer %s\n", + Stringify(hitTestPoints.top()).c_str(), + hitTestPointForChildLayers ? Stringify(hitTestPointForChildLayers.ref()).c_str() : "nil"); + if (!hitTestPointForChildLayers) { + return TraversalFlag::Skip; + } + hitTestPoints.push(ViewAs(hitTestPointForChildLayers.ref(), + PixelCastJustification::MovingDownToChildren)); + return TraversalFlag::Continue; + }, + [&resultNode, &hitTestPoints, &aOutHitResult](HitTestingTreeNode* aNode) { + hitTestPoints.pop(); + HitTestResult hitResult = aNode->HitTest(hitTestPoints.top()); + APZCTM_LOG("Testing ParentLayer point %s against node %p\n", + Stringify(hitTestPoints.top()).c_str(), aNode); + if (hitResult != HitTestResult::HitNothing) { + resultNode = aNode; + MOZ_ASSERT(hitResult == HitLayer || hitResult == HitDispatchToContentRegion); + // If event regions are disabled, *aOutHitResult will be HitLayer + *aOutHitResult = hitResult; + return TraversalFlag::Abort; + } + return TraversalFlag::Continue; } - } + ); - if (*aOutHitResult != HitNothing) { + if (*aOutHitResult != HitNothing) { + MOZ_ASSERT(resultNode); + AsyncPanZoomController* result = resultNode->GetNearestContainingApzcWithSameLayersId(); + if (!result) { + result = FindRootApzcForLayersId(resultNode->GetLayersId()); + MOZ_ASSERT(result); + } + APZCTM_LOG("Successfully matched APZC %p via node %p (hit result %d)\n", + result, aNode, *aOutHitResult); return result; - } } return nullptr; diff --git a/gfx/layers/apz/src/AsyncPanZoomAnimation.h b/gfx/layers/apz/src/AsyncPanZoomAnimation.h index 33e7d1b10d..a2eebb68ac 100644 --- a/gfx/layers/apz/src/AsyncPanZoomAnimation.h +++ b/gfx/layers/apz/src/AsyncPanZoomAnimation.h @@ -23,8 +23,7 @@ class AsyncPanZoomAnimation { NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPanZoomAnimation) public: - explicit AsyncPanZoomAnimation(const TimeDuration& aRepaintInterval) - : mRepaintInterval(aRepaintInterval) + explicit AsyncPanZoomAnimation() { } virtual bool DoSample(FrameMetrics& aFrameMetrics, @@ -50,18 +49,14 @@ public: return Move(mDeferredTasks); } - /** - * Specifies how frequently (at most) we want to do repaints during the - * animation sequence. TimeDuration::Forever() will cause it to only repaint - * at the end of the animation. - */ - TimeDuration mRepaintInterval; - -public: virtual WheelScrollAnimation* AsWheelScrollAnimation() { return nullptr; } + virtual bool WantsRepaints() { + return true; + } + protected: // Protected destructor, to discourage deletion outside of Release(): virtual ~AsyncPanZoomAnimation() diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index e75a44e88b..e41e6b1477 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -37,6 +37,8 @@ #include "mozilla/StaticPtr.h" // for StaticAutoPtr #include "mozilla/Telemetry.h" // for Telemetry #include "mozilla/TimeStamp.h" // for TimeDuration, TimeStamp +#include "mozilla/dom/CheckerboardReportService.h" // for CheckerboardEventStorage + // note: CheckerboardReportService.h actually lives in gfx/layers/apz/util/ #include "mozilla/dom/KeyframeEffect.h" // for ComputedTimingFunction #include "mozilla/dom/Touch.h" // for Touch #include "mozilla/gfx/BasePoint.h" // for BasePoint @@ -52,6 +54,7 @@ #include "mozilla/layers/CompositorParent.h" // for CompositorParent #include "mozilla/layers/LayerTransactionParent.h" // for LayerTransactionParent #include "mozilla/layers/PCompositorParent.h" // for PCompositorParent +#include "mozilla/layers/ScrollInputMethods.h" // for ScrollInputMethod #include "mozilla/mozalloc.h" // for operator new, etc #include "mozilla/unused.h" // for unused #include "mozilla/FloatingPoint.h" // for FuzzyEquals* @@ -153,6 +156,14 @@ using mozilla::gfx::PointTyped; * pixels would make us drop to low-res at y=490...990.\n * This value is in layer pixels. * + * \li\b apz.displayport_expiry_ms + * While a scrollable frame is scrolling async, we set a displayport on it + * to make sure it is layerized. However this takes up memory, so once the + * scrolling stops we want to remove the displayport. This pref controls how + * long after scrolling stops the displayport is removed. A value of 0 will + * disable the expiry behavior entirely. + * Units: milliseconds + * * \li\b apz.enlarge_displayport_when_clipped * Pref that enables enlarging of the displayport along one axis when the * generated displayport's size is beyond that of the scrollable rect on the @@ -204,11 +215,6 @@ using mozilla::gfx::PointTyped; * value of this pref, and (t1 - t0) is the amount of time, in milliseconds, * that has elapsed between the two samples. * - * \li\b apz.fling_repaint_interval - * Maximum amount of time flinging before sending a viewport change. This will - * asynchronously repaint the page.\n - * Units: milliseconds - * * \li\b apz.fling_stop_on_tap_threshold * When flinging, if the velocity is above this number, then a tap on the * screen will stop the fling without dispatching a tap to content. If the @@ -272,18 +278,9 @@ using mozilla::gfx::PointTyped; * Units: screen pixels (for distance) * screen pixels per millisecond (for velocity) * - * \li\b apz.pan_repaint_interval - * Maximum amount of time while panning before sending a viewport change. This - * will asynchronously repaint the page. It is also forced when panning stops. - * * \li\b apz.record_checkerboarding * Whether or not to record detailed info on checkerboarding events. * - * \li\b apz.smooth_scroll_repaint_interval - * Maximum amount of time doing a smooth scroll before sending a viewport - * change. This will asynchronously repaint the page.\n - * Units: milliseconds - * * \li\b apz.test.logging_enabled * Enable logging of APZ test data (see bug 961289). * @@ -433,8 +430,7 @@ public: const RefPtr& aOverscrollHandoffChain, bool aApplyAcceleration, const RefPtr& aScrolledApzc) - : AsyncPanZoomAnimation(TimeDuration::FromMilliseconds(gfxPrefs::APZFlingRepaintInterval())) - , mApzc(aApzc) + : mApzc(aApzc) , mOverscrollHandoffChain(aOverscrollHandoffChain) , mScrolledApzc(aScrolledApzc) { @@ -601,8 +597,7 @@ class ZoomAnimation: public AsyncPanZoomAnimation { public: ZoomAnimation(CSSPoint aStartOffset, CSSToParentLayerScale2D aStartZoom, CSSPoint aEndOffset, CSSToParentLayerScale2D aEndZoom) - : AsyncPanZoomAnimation(TimeDuration::Forever()) - , mTotalDuration(TimeDuration::FromMilliseconds(gfxPrefs::APZZoomAnimationDuration())) + : mTotalDuration(TimeDuration::FromMilliseconds(gfxPrefs::APZZoomAnimationDuration())) , mStartOffset(aStartOffset) , mStartZoom(aStartZoom) , mEndOffset(aEndOffset) @@ -639,6 +634,11 @@ public: return true; } + virtual bool WantsRepaints() override + { + return false; + } + private: TimeDuration mDuration; const TimeDuration mTotalDuration; @@ -660,8 +660,7 @@ private: class OverscrollAnimation: public AsyncPanZoomAnimation { public: explicit OverscrollAnimation(AsyncPanZoomController& aApzc, const ParentLayerPoint& aVelocity) - : AsyncPanZoomAnimation(TimeDuration::Forever()) - , mApzc(aApzc) + : mApzc(aApzc) { mApzc.mX.StartOverscrollAnimation(aVelocity.x); mApzc.mY.StartOverscrollAnimation(aVelocity.y); @@ -691,6 +690,12 @@ public: } return true; } + + virtual bool WantsRepaints() override + { + return false; + } + private: AsyncPanZoomController& mApzc; }; @@ -703,9 +708,7 @@ public: const nsPoint &aInitialVelocity, const nsPoint& aDestination, double aSpringConstant, double aDampingRatio) - : AsyncPanZoomAnimation(TimeDuration::FromMilliseconds( - gfxPrefs::APZSmoothScrollRepaintInterval())) - , mApzc(aApzc) + : mApzc(aApzc) , mXAxisModel(aInitialPosition.x, aDestination.x, aInitialVelocity.x, aSpringConstant, aDampingRatio) , mYAxisModel(aInitialPosition.y, aDestination.y, aInitialVelocity.y, @@ -863,7 +866,8 @@ AsyncPanZoomController::AsyncPanZoomController(uint64_t aLayersId, mInputQueue(aInputQueue), mAPZCId(sAsyncPanZoomControllerCount++), mSharedLock(nullptr), - mAsyncTransformAppliedToContent(false) + mAsyncTransformAppliedToContent(false), + mCheckerboardEventLock("APZCBELock") { if (aGestures == USE_GESTURE_DETECTOR) { mGestureEventListener = new GestureEventListener(this); @@ -1032,6 +1036,9 @@ nsEventStatus AsyncPanZoomController::HandleDragEvent(const MouseInput& aEvent, return nsEventStatus_eConsumeNoDefault; } + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t) ScrollInputMethod::ApzScrollbarDrag); + ReentrantMonitorAutoEnter lock(mMonitor); CSSPoint scrollFramePoint = aEvent.mLocalOrigin / GetFrameMetrics().GetZoom(); // The scrollbar can be transformed with the frame but the pres shell @@ -1337,7 +1344,13 @@ nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent) if (CurrentTouchBlock()->GetActiveTouchCount() == 0) { // It's possible we may be overscrolled if the user tapped during a // previous overscroll pan. Make sure to snap back in this situation. - if (!SnapBackIfOverscrolled()) { + // An ancestor APZC could be overscrolled instead of this APZC, so + // walk the handoff chain as well. + CurrentTouchBlock()->GetOverscrollHandoffChain()->SnapBackOverscrolledApzc(this); + // SnapBackOverscrolledApzc() will put any APZC it causes to snap back + // into the OVERSCROLL_ANIMATION state. If that's not us, since we're + // done TOUCHING enter the NOTHING state. + if (mState != OVERSCROLL_ANIMATION) { SetState(NOTHING); } } @@ -1723,6 +1736,25 @@ AsyncPanZoomController::AllowScrollHandoffInCurrentBlock() const return result; } +static ScrollInputMethod +ScrollInputMethodForWheelDeltaType(ScrollWheelInput::ScrollDeltaType aDeltaType) +{ + switch (aDeltaType) { + case ScrollWheelInput::SCROLLDELTA_LINE: { + return ScrollInputMethod::ApzWheelLine; + } + case ScrollWheelInput::SCROLLDELTA_PAGE: { + return ScrollInputMethod::ApzWheelPage; + } + case ScrollWheelInput::SCROLLDELTA_PIXEL: { + return ScrollInputMethod::ApzWheelPixel; + } + default: + MOZ_ASSERT_UNREACHABLE("unexpected scroll delta type"); + return ScrollInputMethod::ApzWheelLine; + } +} + nsEventStatus AsyncPanZoomController::OnScrollWheel(const ScrollWheelInput& aEvent) { ParentLayerPoint delta = GetScrollWheelDelta(aEvent); @@ -1746,6 +1778,9 @@ nsEventStatus AsyncPanZoomController::OnScrollWheel(const ScrollWheelInput& aEve return nsEventStatus_eIgnore; } + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t) ScrollInputMethodForWheelDeltaType(aEvent.mDeltaType)); + switch (aEvent.mScrollMode) { case ScrollWheelInput::SCROLLMODE_INSTANT: { ScreenPoint distance = ToScreenCoordinates( @@ -1904,6 +1939,9 @@ nsEventStatus AsyncPanZoomController::OnPan(const PanGestureInput& aEvent, bool HandlePanningUpdate(aEvent.mPanDisplacement); + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t) ScrollInputMethod::ApzPanGesture); + ScreenPoint panDistance(fabs(aEvent.mPanDisplacement.x), fabs(aEvent.mPanDisplacement.y)); OverscrollHandoffState handoffState( *CurrentPanGestureBlock()->GetOverscrollHandoffChain(), @@ -2540,6 +2578,8 @@ void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) { UpdateWithTouchAtDevicePoint(aEvent); if (prevTouchPoint != touchPoint) { + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t) ScrollInputMethod::ApzTouch); OverscrollHandoffState handoffState( *CurrentTouchBlock()->GetOverscrollHandoffChain(), panDistance, @@ -2749,7 +2789,7 @@ void AsyncPanZoomController::FlushRepaintForNewInputBlock() { APZC_LOG("%p flushing repaint for new input block\n", this); ReentrantMonitorAutoEnter lock(mMonitor); - RequestContentRepaint(mFrameMetrics); + RequestContentRepaint(); UpdateSharedCompositorFrameMetrics(); } @@ -2792,14 +2832,44 @@ int32_t AsyncPanZoomController::GetLastTouchIdentifier() const { } void AsyncPanZoomController::RequestContentRepaint() { - RequestContentRepaint(mFrameMetrics); + // Reinvoke this method on the main thread if it's not there already. It's + // important to do this before the call to CalculatePendingDisplayPort, so + // that CalculatePendingDisplayPort uses the most recent available version of + // mFrameMetrics, just before the paint request is dispatched to content. + if (!NS_IsMainThread()) { + // use the local variable to resolve the function overload. + auto func = static_cast + (&AsyncPanZoomController::RequestContentRepaint); + NS_DispatchToMainThread(NS_NewRunnableMethod(this, func)); + return; + } + + MOZ_ASSERT(NS_IsMainThread()); + + ReentrantMonitorAutoEnter lock(mMonitor); + ParentLayerPoint velocity = GetVelocityVector(); + mFrameMetrics.SetDisplayPortMargins(CalculatePendingDisplayPort(mFrameMetrics, velocity)); + mFrameMetrics.SetUseDisplayPortMargins(true); + mFrameMetrics.SetPaintRequestTime(TimeStamp::Now()); + RequestContentRepaint(mFrameMetrics, velocity); } -void AsyncPanZoomController::RequestContentRepaint(FrameMetrics& aFrameMetrics) +/*static*/ CSSRect +GetDisplayPortRect(const FrameMetrics& aFrameMetrics) { - ParentLayerPoint velocity = GetVelocityVector(); - aFrameMetrics.SetDisplayPortMargins(CalculatePendingDisplayPort(aFrameMetrics, velocity)); - aFrameMetrics.SetUseDisplayPortMargins(true); + // This computation is based on what happens in CalculatePendingDisplayPort. If that + // changes then this might need to change too + CSSRect baseRect(aFrameMetrics.GetScrollOffset(), + aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels()); + baseRect.Inflate(aFrameMetrics.GetDisplayPortMargins() / aFrameMetrics.DisplayportPixelsPerCSSPixel()); + return baseRect; +} + +void +AsyncPanZoomController::RequestContentRepaint(const FrameMetrics& aFrameMetrics, + const ParentLayerPoint& aVelocity) +{ + MOZ_ASSERT(NS_IsMainThread()); // If we're trying to paint what we already think is painted, discard this // request since it's a pointless paint. @@ -2822,47 +2892,25 @@ void AsyncPanZoomController::RequestContentRepaint(FrameMetrics& aFrameMetrics) return; } - aFrameMetrics.SetPaintRequestTime(TimeStamp::Now()); - DispatchRepaintRequest(aFrameMetrics, velocity); - aFrameMetrics.SetPresShellId(mLastContentPaintMetrics.GetPresShellId()); -} - -/*static*/ CSSRect -GetDisplayPortRect(const FrameMetrics& aFrameMetrics) -{ - // This computation is based on what happens in CalculatePendingDisplayPort. If that - // changes then this might need to change too - CSSRect baseRect(aFrameMetrics.GetScrollOffset(), - aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels()); - baseRect.Inflate(aFrameMetrics.GetDisplayPortMargins() / aFrameMetrics.DisplayportPixelsPerCSSPixel()); - return baseRect; -} - -void -AsyncPanZoomController::DispatchRepaintRequest(const FrameMetrics& aFrameMetrics, - const ParentLayerPoint& aVelocity) -{ RefPtr controller = GetGeckoContentController(); if (!controller) { return; } APZC_LOG_FM(aFrameMetrics, "%p requesting content repaint", this); - if (mCheckerboardEvent) { - std::stringstream info; - info << " velocity " << aVelocity; - std::string str = info.str(); - mCheckerboardEvent->UpdateRendertraceProperty( - CheckerboardEvent::RequestedDisplayPort, GetDisplayPortRect(aFrameMetrics), - str); + { // scope lock + MutexAutoLock lock(mCheckerboardEventLock); + if (mCheckerboardEvent && mCheckerboardEvent->IsRecordingTrace()) { + std::stringstream info; + info << " velocity " << aVelocity; + std::string str = info.str(); + mCheckerboardEvent->UpdateRendertraceProperty( + CheckerboardEvent::RequestedDisplayPort, GetDisplayPortRect(aFrameMetrics), + str); + } } - if (NS_IsMainThread()) { - controller->RequestContentRepaint(aFrameMetrics); - } else { - NS_DispatchToMainThread(NS_NewRunnableMethodWithArg( - controller, &GeckoContentController::RequestContentRepaint, aFrameMetrics)); - } + controller->RequestContentRepaint(aFrameMetrics); mExpectedGeckoMetrics = aFrameMetrics; mLastPaintRequestMetrics = aFrameMetrics; } @@ -2884,12 +2932,15 @@ bool AsyncPanZoomController::UpdateAnimation(const TimeStamp& aSampleTime, if (mAnimation) { bool continueAnimation = mAnimation->Sample(mFrameMetrics, sampleTimeDelta); + bool wantsRepaints = mAnimation->WantsRepaints(); *aOutDeferredTasks = mAnimation->TakeDeferredTasks(); if (!continueAnimation) { mAnimation = nullptr; SetState(NOTHING); } - RequestContentRepaint(); + if (wantsRepaints) { + RequestContentRepaint(); + } UpdateSharedCompositorFrameMetrics(); return true; } @@ -2963,11 +3014,14 @@ bool AsyncPanZoomController::AdvanceAnimations(const TimeStamp& aSampleTime) requestAnimationFrame = UpdateAnimation(aSampleTime, &deferredTasks); - if (mCheckerboardEvent) { - mCheckerboardEvent->UpdateRendertraceProperty( - CheckerboardEvent::UserVisible, - CSSRect(mFrameMetrics.GetScrollOffset(), - mFrameMetrics.CalculateCompositedSizeInCssPixels())); + { // scope lock + MutexAutoLock lock(mCheckerboardEventLock); + if (mCheckerboardEvent) { + mCheckerboardEvent->UpdateRendertraceProperty( + CheckerboardEvent::UserVisible, + CSSRect(mFrameMetrics.GetScrollOffset(), + mFrameMetrics.CalculateCompositedSizeInCssPixels())); + } } } @@ -3084,24 +3138,42 @@ AsyncPanZoomController::ReportCheckerboard(const TimeStamp& aSampleTime) // checkerboard once per composite though. return; } - uint32_t time = (aSampleTime - mLastCheckerboardReport).ToMilliseconds(); - uint32_t magnitude = GetCheckerboardMagnitude(); - // TODO: make this a function of velocity - mozilla::Telemetry::Accumulate( - mozilla::Telemetry::CHECKERBOARDED_CSSPIXELS_MS, magnitude * time); mLastCheckerboardReport = aSampleTime; - if (!mCheckerboardEvent && gfxPrefs::APZRecordCheckerboarding()) { - mCheckerboardEvent = MakeUnique(); + bool recordTrace = gfxPrefs::APZRecordCheckerboarding(); + bool forTelemetry = Telemetry::CanRecordExtended(); + uint32_t magnitude = GetCheckerboardMagnitude(); + + MutexAutoLock lock(mCheckerboardEventLock); + if (!mCheckerboardEvent && (recordTrace || forTelemetry)) { + mCheckerboardEvent = MakeUnique(recordTrace); } - if (mCheckerboardEvent) { - if (mCheckerboardEvent->RecordFrameInfo(magnitude)) { - // This checkerboard event is done. TODO: save the info somewhere or - // dispatch it to telemetry or something. For now we just print it. - std::stringstream log(mCheckerboardEvent->GetLog()); - print_stderr(log); - mCheckerboardEvent = nullptr; + if (magnitude) { + mPotentialCheckerboardTracker.CheckerboardSeen(); + } + if (mCheckerboardEvent && mCheckerboardEvent->RecordFrameInfo(magnitude)) { + // This checkerboard event is done. Report some metrics to telemetry. + mozilla::Telemetry::Accumulate(mozilla::Telemetry::CHECKERBOARD_SEVERITY, + mCheckerboardEvent->GetSeverity()); + mozilla::Telemetry::Accumulate(mozilla::Telemetry::CHECKERBOARD_PEAK, + mCheckerboardEvent->GetPeak()); + mozilla::Telemetry::Accumulate(mozilla::Telemetry::CHECKERBOARD_DURATION, + (uint32_t)mCheckerboardEvent->GetDuration().ToMilliseconds()); + + mPotentialCheckerboardTracker.CheckerboardDone(); + + if (recordTrace) { + // if the pref is enabled, also send it to the storage class. it may be + // chosen for public display on about:checkerboard, the hall of fame for + // checkerboard events. + uint32_t severity = mCheckerboardEvent->GetSeverity(); + std::string log = mCheckerboardEvent->GetLog(); + NS_DispatchToMainThread(NS_NewRunnableFunction([severity, log]() { + RefPtr storage = CheckerboardEventStorage::GetInstance(); + storage->ReportCheckerboard(severity, log); + })); } + mCheckerboardEvent = nullptr; } } @@ -3139,36 +3211,39 @@ void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetri APZC_LOG_FM(aLayerMetrics, "%p got a NotifyLayersUpdated with aIsFirstPaint=%d, aThisLayerTreeUpdated=%d", this, aIsFirstPaint, aThisLayerTreeUpdated); - if (mCheckerboardEvent) { - std::string str; - if (aThisLayerTreeUpdated) { - if (!aLayerMetrics.GetPaintRequestTime().IsNull()) { - // Note that we might get the paint request time as non-null, but with - // aThisLayerTreeUpdated false. That can happen if we get a layer transaction - // from a different process right after we get the layer transaction with - // aThisLayerTreeUpdated == true. In this case we want to ignore the - // paint request time because it was already dumped in the previous layer - // transaction. - TimeDuration paintTime = TimeStamp::Now() - aLayerMetrics.GetPaintRequestTime(); - std::stringstream info; - info << " painttime " << paintTime.ToMilliseconds(); - str = info.str(); - } else { - // This might be indicative of a wasted paint particularly if it happens - // during a checkerboard event. - str = " (this layertree updated)"; + { // scope lock + MutexAutoLock lock(mCheckerboardEventLock); + if (mCheckerboardEvent && mCheckerboardEvent->IsRecordingTrace()) { + std::string str; + if (aThisLayerTreeUpdated) { + if (!aLayerMetrics.GetPaintRequestTime().IsNull()) { + // Note that we might get the paint request time as non-null, but with + // aThisLayerTreeUpdated false. That can happen if we get a layer transaction + // from a different process right after we get the layer transaction with + // aThisLayerTreeUpdated == true. In this case we want to ignore the + // paint request time because it was already dumped in the previous layer + // transaction. + TimeDuration paintTime = TimeStamp::Now() - aLayerMetrics.GetPaintRequestTime(); + std::stringstream info; + info << " painttime " << paintTime.ToMilliseconds(); + str = info.str(); + } else { + // This might be indicative of a wasted paint particularly if it happens + // during a checkerboard event. + str = " (this layertree updated)"; + } } - } - mCheckerboardEvent->UpdateRendertraceProperty( - CheckerboardEvent::Page, aLayerMetrics.GetScrollableRect()); - mCheckerboardEvent->UpdateRendertraceProperty( - CheckerboardEvent::PaintedDisplayPort, - aLayerMetrics.GetDisplayPort() + aLayerMetrics.GetScrollOffset(), - str); - if (!aLayerMetrics.GetCriticalDisplayPort().IsEmpty()) { mCheckerboardEvent->UpdateRendertraceProperty( - CheckerboardEvent::PaintedCriticalDisplayPort, - aLayerMetrics.GetCriticalDisplayPort() + aLayerMetrics.GetScrollOffset()); + CheckerboardEvent::Page, aLayerMetrics.GetScrollableRect()); + mCheckerboardEvent->UpdateRendertraceProperty( + CheckerboardEvent::PaintedDisplayPort, + aLayerMetrics.GetDisplayPort() + aLayerMetrics.GetScrollOffset(), + str); + if (!aLayerMetrics.GetCriticalDisplayPort().IsEmpty()) { + mCheckerboardEvent->UpdateRendertraceProperty( + CheckerboardEvent::PaintedCriticalDisplayPort, + aLayerMetrics.GetCriticalDisplayPort() + aLayerMetrics.GetScrollOffset()); + } } } @@ -3456,9 +3531,6 @@ void AsyncPanZoomController::ZoomToRect(CSSRect aRect, const uint32_t aFlags) { } endZoomToMetrics.SetScrollOffset(aRect.TopLeft()); - endZoomToMetrics.SetDisplayPortMargins( - CalculatePendingDisplayPort(endZoomToMetrics, ParentLayerPoint(0,0))); - endZoomToMetrics.SetUseDisplayPortMargins(true); StartAnimation(new ZoomAnimation( mFrameMetrics.GetScrollOffset(), @@ -3468,7 +3540,21 @@ void AsyncPanZoomController::ZoomToRect(CSSRect aRect, const uint32_t aFlags) { // Schedule a repaint now, so the new displayport will be painted before the // animation finishes. - RequestContentRepaint(endZoomToMetrics); + ParentLayerPoint velocity(0, 0); + endZoomToMetrics.SetDisplayPortMargins( + CalculatePendingDisplayPort(endZoomToMetrics, velocity)); + endZoomToMetrics.SetUseDisplayPortMargins(true); + endZoomToMetrics.SetPaintRequestTime(TimeStamp::Now()); + if (NS_IsMainThread()) { + RequestContentRepaint(endZoomToMetrics, velocity); + } else { + // use a local var to resolve the function overload + auto func = static_cast + (&AsyncPanZoomController::RequestContentRepaint); + NS_DispatchToMainThread( + NS_NewRunnableMethodWithArgs( + this, func, endZoomToMetrics, velocity)); + } } } @@ -3542,6 +3628,7 @@ void AsyncPanZoomController::DispatchStateChangeNotification(PanZoomState aOldSt if (RefPtr controller = GetGeckoContentController()) { if (!IsTransformingState(aOldState) && IsTransformingState(aNewState)) { + mPotentialCheckerboardTracker.TransformStarted(); controller->NotifyAPZStateChange( GetGuid(), APZStateChange::TransformBegin); #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) @@ -3552,6 +3639,7 @@ void AsyncPanZoomController::DispatchStateChangeNotification(PanZoomState aOldSt } #endif } else if (IsTransformingState(aOldState) && !IsTransformingState(aNewState)) { + mPotentialCheckerboardTracker.TransformStopped(); controller->NotifyAPZStateChange( GetGuid(), APZStateChange::TransformEnd); #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) diff --git a/gfx/layers/apz/src/AsyncPanZoomController.h b/gfx/layers/apz/src/AsyncPanZoomController.h index 3cf998773f..2f87004411 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.h +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -26,6 +26,7 @@ #include "LayersTypes.h" #include "mozilla/gfx/Matrix.h" #include "nsRegion.h" +#include "PotentialCheckerboardDurationTracker.h" #include "base/message_loop.h" @@ -577,27 +578,19 @@ protected: /** * Utility function to send updated FrameMetrics to Gecko so that it can paint * the displayport area. Calls into GeckoContentController to do the actual - * work. Note that only one paint request can be active at a time. If a paint - * request is made while a paint is currently happening, it gets queued up. If - * a new paint request arrives before a paint is completed, the old request - * gets discarded. + * work. This call will use the current metrics. If this function is called + * from a non-main thread, it will redispatch itself to the main thread, and + * use the latest metrics during the redispatch. */ void RequestContentRepaint(); /** - * Tell the paint throttler to request a content repaint with the given - * metrics. (Helper function used by RequestContentRepaint.) If aThrottled - * is set to false, the repaint request is sent directly without going through - * the paint throttler. In particular, the GeckoContentController::RequestContentRepaint - * function will be invoked before this function returns. + * Send the provided metrics to Gecko to trigger a repaint. This function + * may filter duplicate calls with the same metrics. This function must be + * called on the main thread. */ - void RequestContentRepaint(FrameMetrics& aFrameMetrics); - - /** - * Actually send the next pending paint request to gecko. - */ - void DispatchRepaintRequest(const FrameMetrics& aFrameMetrics, - const ParentLayerPoint& aVelocity); + void RequestContentRepaint(const FrameMetrics& aFrameMetrics, + const ParentLayerPoint& aVelocity); /** * Gets the current frame metrics. This is *not* the Gecko copy stored in the @@ -1106,10 +1099,16 @@ private: * recording. */ private: + // Mutex protecting mCheckerboardEvent + Mutex mCheckerboardEventLock; // This is created when this APZC instance is first included as part of a // composite. If a checkerboard event takes place, this is destroyed at the // end of the event, and a new one is created on the next composite. UniquePtr mCheckerboardEvent; + // This is used to track the total amount of time that we could reasonably + // be checkerboarding. Combined with other info, this allows us to meaningfully + // say how frequently users actually encounter checkerboarding. + PotentialCheckerboardDurationTracker mPotentialCheckerboardTracker; }; } // namespace layers diff --git a/gfx/layers/apz/src/CheckerboardEvent.cpp b/gfx/layers/apz/src/CheckerboardEvent.cpp index afa913aceb..ea40a5fa7d 100644 --- a/gfx/layers/apz/src/CheckerboardEvent.cpp +++ b/gfx/layers/apz/src/CheckerboardEvent.cpp @@ -32,8 +32,9 @@ const char* CheckerboardEvent::sColors[] = { "red", }; -CheckerboardEvent::CheckerboardEvent() - : mOriginTime(TimeStamp::Now()) +CheckerboardEvent::CheckerboardEvent(bool aRecordTrace) + : mRecordTrace(aRecordTrace) + , mOriginTime(TimeStamp::Now()) , mCheckerboardingActive(false) , mLastSampleTime(mOriginTime) , mFrameCount(0) @@ -43,10 +44,23 @@ CheckerboardEvent::CheckerboardEvent() { } -uint64_t +uint32_t CheckerboardEvent::GetSeverity() { - return mTotalPixelMs; + // Scale the total into a 32-bit value + return (uint32_t)sqrt((double)mTotalPixelMs); +} + +uint32_t +CheckerboardEvent::GetPeak() +{ + return mPeakPixels; +} + +TimeDuration +CheckerboardEvent::GetDuration() +{ + return mEndTime - mStartTime; } std::string @@ -56,11 +70,20 @@ CheckerboardEvent::GetLog() return mRendertraceInfo.str(); } +bool +CheckerboardEvent::IsRecordingTrace() +{ + return mRecordTrace; +} + void CheckerboardEvent::UpdateRendertraceProperty(RendertraceProperty aProperty, const CSSRect& aRect, const std::string& aExtraInfo) { + if (!mRecordTrace) { + return; + } MonitorAutoLock lock(mRendertraceLock); if (!mCheckerboardingActive) { mBufferedProperties[aProperty].Update(aProperty, aRect, aExtraInfo, lock); @@ -76,6 +99,7 @@ CheckerboardEvent::LogInfo(RendertraceProperty aProperty, const std::string& aExtraInfo, const MonitorAutoLock& aProofOfLock) { + MOZ_ASSERT(mRecordTrace); if (mRendertraceInfo.tellp() >= LOG_LENGTH_LIMIT) { // The log is already long enough, don't put more things into it. We'll // append a truncation message when this event ends. @@ -130,6 +154,9 @@ CheckerboardEvent::StartEvent() mCheckerboardingActive = true; mStartTime = TimeStamp::Now(); + if (!mRecordTrace) { + return; + } MonitorAutoLock lock(mRendertraceLock); std::vector history; for (int i = 0; i < MAX_RendertraceProperty; i++) { @@ -148,6 +175,9 @@ CheckerboardEvent::StopEvent() mCheckerboardingActive = false; mEndTime = TimeStamp::Now(); + if (!mRecordTrace) { + return; + } MonitorAutoLock lock(mRendertraceLock); if (mRendertraceInfo.tellp() >= LOG_LENGTH_LIMIT) { mRendertraceInfo << "[logging aborted due to length limitations]\n"; diff --git a/gfx/layers/apz/src/CheckerboardEvent.h b/gfx/layers/apz/src/CheckerboardEvent.h index 5d3448c44f..f82c5cf811 100644 --- a/gfx/layers/apz/src/CheckerboardEvent.h +++ b/gfx/layers/apz/src/CheckerboardEvent.h @@ -39,14 +39,25 @@ public: static const char* sColors[MAX_RendertraceProperty]; public: - CheckerboardEvent(); + explicit CheckerboardEvent(bool aRecordTrace); /** * Gets the "severity" of the checkerboard event. This doesn't have units, * it's just useful for comparing two checkerboard events to see which one * is worse, for some implementation-specific definition of "worse". */ - uint64_t GetSeverity(); + uint32_t GetSeverity(); + + /** + * Gets the number of CSS pixels that were checkerboarded at the peak of the + * checkerboard event. + */ + uint32_t GetPeak(); + + /** + * Gets the length of the checkerboard event. + */ + TimeDuration GetDuration(); /** * Gets the raw log of the checkerboard event. This can be called any time, @@ -55,6 +66,12 @@ public: */ std::string GetLog(); + /** + * Returns true iff this event is recording a detailed trace of the event. + * This is the argument passed in to the constructor. + */ + bool IsRecordingTrace(); + /** * Provide a new value for one of the rects that is tracked for * checkerboard events. @@ -137,6 +154,12 @@ private: }; private: + /** + * If true, we should log the various properties during the checkerboard + * event. If false, we only need to record things we need for telemetry + * measures. + */ + const bool mRecordTrace; /** * A base time so that the other timestamps can be turned into durations. */ diff --git a/gfx/layers/apz/src/PotentialCheckerboardDurationTracker.cpp b/gfx/layers/apz/src/PotentialCheckerboardDurationTracker.cpp new file mode 100644 index 0000000000..0f9a1d32d8 --- /dev/null +++ b/gfx/layers/apz/src/PotentialCheckerboardDurationTracker.cpp @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et 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/. */ + +#include "PotentialCheckerboardDurationTracker.h" + +#include "mozilla/Telemetry.h" // for Telemetry + +namespace mozilla { +namespace layers { + +PotentialCheckerboardDurationTracker::PotentialCheckerboardDurationTracker() + : mInCheckerboard(false) + , mInTransform(false) +{ +} + +void +PotentialCheckerboardDurationTracker::CheckerboardSeen() +{ + // This might get called while mInCheckerboard is already true + if (!Tracking()) { + mCurrentPeriodStart = TimeStamp::Now(); + } + mInCheckerboard = true; +} + +void +PotentialCheckerboardDurationTracker::CheckerboardDone() +{ + MOZ_ASSERT(Tracking()); + mInCheckerboard = false; + if (!Tracking()) { + mozilla::Telemetry::AccumulateTimeDelta( + mozilla::Telemetry::CHECKERBOARD_POTENTIAL_DURATION, + mCurrentPeriodStart); + } +} + +void +PotentialCheckerboardDurationTracker::TransformStarted() +{ + MOZ_ASSERT(!mInTransform); + if (!Tracking()) { + mCurrentPeriodStart = TimeStamp::Now(); + } + mInTransform = true; +} + +void +PotentialCheckerboardDurationTracker::TransformStopped() +{ + MOZ_ASSERT(mInTransform); + mInTransform = false; + if (!Tracking()) { + mozilla::Telemetry::AccumulateTimeDelta( + mozilla::Telemetry::CHECKERBOARD_POTENTIAL_DURATION, + mCurrentPeriodStart); + } +} + +bool +PotentialCheckerboardDurationTracker::Tracking() const +{ + return mInTransform || mInCheckerboard; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/PotentialCheckerboardDurationTracker.h b/gfx/layers/apz/src/PotentialCheckerboardDurationTracker.h new file mode 100644 index 0000000000..b6bd3ea882 --- /dev/null +++ b/gfx/layers/apz/src/PotentialCheckerboardDurationTracker.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_layers_PotentialCheckerboardDurationTracker_h +#define mozilla_layers_PotentialCheckerboardDurationTracker_h + +#include "mozilla/TimeStamp.h" + +namespace mozilla { +namespace layers { + +/** + * This class allows the owner to track the duration of time considered + * "potentially checkerboarding". This is the union of two possibly-intersecting + * sets of time periods. The first set is that in which checkerboarding was + * actually happening, since by definition it could potentially be happening. + * The second set is that in which the APZC is actively transforming content + * in the compositor, since it could potentially transform it so as to display + * checkerboarding to the user. + * The caller of this class calls the appropriate methods to indicate the start + * and stop of these two sets, and this class manages accumulating the union + * of the various durations. + */ +class PotentialCheckerboardDurationTracker { +public: + PotentialCheckerboardDurationTracker(); + + /** + * This should be called if checkerboarding is encountered. It can be called + * multiple times during a checkerboard event. + */ + void CheckerboardSeen(); + /** + * This should be called when checkerboarding is done. It must have been + * preceded by one or more calls to CheckerboardSeen(). + */ + void CheckerboardDone(); + + /** + * This should be called when a transform is started. Calls to this must be + * interleaved with calls to TransformStopped(). + */ + void TransformStarted(); + /** + * This should be called when a transform is stopped. Calls to this must be + * interleaved with calls to TransformStarted(). + */ + void TransformStopped(); + +private: + bool Tracking() const; + +private: + bool mInCheckerboard; + bool mInTransform; + + TimeStamp mCurrentPeriodStart; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_PotentialCheckerboardDurationTracker_h diff --git a/gfx/layers/apz/src/WheelScrollAnimation.cpp b/gfx/layers/apz/src/WheelScrollAnimation.cpp index 2e3054c904..7f08161407 100644 --- a/gfx/layers/apz/src/WheelScrollAnimation.cpp +++ b/gfx/layers/apz/src/WheelScrollAnimation.cpp @@ -14,8 +14,7 @@ namespace mozilla { namespace layers { WheelScrollAnimation::WheelScrollAnimation(AsyncPanZoomController& aApzc, const nsPoint& aInitialPosition) - : AsyncPanZoomAnimation(TimeDuration::FromMilliseconds(gfxPrefs::APZSmoothScrollRepaintInterval())) - , AsyncScrollBase(aInitialPosition) + : AsyncScrollBase(aInitialPosition) , mApzc(aApzc) , mFinalDestination(aInitialPosition) { diff --git a/gfx/layers/apz/test/gtest/InputUtils.h b/gfx/layers/apz/test/gtest/InputUtils.h index 4770a4acc3..1c32d134c6 100644 --- a/gfx/layers/apz/test/gtest/InputUtils.h +++ b/gfx/layers/apz/test/gtest/InputUtils.h @@ -32,21 +32,28 @@ // (which expects an untransformed point). We handle both cases by setting both // the transformed and untransformed fields to the same value. SingleTouchData -CreateSingleTouchData(int32_t aIdentifier, int aX, int aY) +CreateSingleTouchData(int32_t aIdentifier, const ScreenIntPoint& aPoint) { - SingleTouchData touch(aIdentifier, ScreenIntPoint(aX, aY), ScreenSize(0, 0), 0, 0); - touch.mLocalScreenPoint = ParentLayerPoint(aX, aY); + SingleTouchData touch(aIdentifier, aPoint, ScreenSize(0, 0), 0, 0); + touch.mLocalScreenPoint = ParentLayerPoint(aPoint.x, aPoint.y); return touch; } +// Convenience wrapper for CreateSingleTouchData() that takes loose coordinates. +SingleTouchData +CreateSingleTouchData(int32_t aIdentifier, ScreenIntCoord aX, ScreenIntCoord aY) +{ + return CreateSingleTouchData(aIdentifier, ScreenIntPoint(aX, aY)); +} + PinchGestureInput CreatePinchGestureInput(PinchGestureInput::PinchGestureType aType, - int aFocusX, int aFocusY, + const ScreenIntPoint& aFocus, float aCurrentSpan, float aPreviousSpan) { - PinchGestureInput result(aType, 0, TimeStamp(), ScreenPoint(aFocusX, aFocusY), + PinchGestureInput result(aType, 0, TimeStamp(), aFocus, aCurrentSpan, aPreviousSpan, 0); - result.mLocalFocusPoint = ParentLayerPoint(aFocusX, aFocusY); + result.mLocalFocusPoint = ParentLayerPoint(aFocus.x, aFocus.y); return result; } @@ -76,34 +83,38 @@ CreateMultiTouchInput(MultiTouchInput::MultiTouchType aType, TimeStamp aTime) template nsEventStatus -TouchDown(const RefPtr& aTarget, int aX, int aY, TimeStamp aTime, uint64_t* aOutInputBlockId = nullptr) +TouchDown(const RefPtr& aTarget, const ScreenIntPoint& aPoint, + TimeStamp aTime, uint64_t* aOutInputBlockId = nullptr) { MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime); - mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY)); + mti.mTouches.AppendElement(CreateSingleTouchData(0, aPoint)); return aTarget->ReceiveInputEvent(mti, nullptr, aOutInputBlockId); } template nsEventStatus -TouchMove(const RefPtr& aTarget, int aX, int aY, TimeStamp aTime) +TouchMove(const RefPtr& aTarget, const ScreenIntPoint& aPoint, + TimeStamp aTime) { MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime); - mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY)); + mti.mTouches.AppendElement(CreateSingleTouchData(0, aPoint)); return aTarget->ReceiveInputEvent(mti, nullptr, nullptr); } template nsEventStatus -TouchUp(const RefPtr& aTarget, int aX, int aY, TimeStamp aTime) +TouchUp(const RefPtr& aTarget, const ScreenIntPoint& aPoint, + TimeStamp aTime) { MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime); - mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY)); + mti.mTouches.AppendElement(CreateSingleTouchData(0, aPoint)); return aTarget->ReceiveInputEvent(mti, nullptr, nullptr); } template void -Tap(const RefPtr& aTarget, int aX, int aY, MockContentControllerDelayed* aMcc, +Tap(const RefPtr& aTarget, const ScreenIntPoint& aPoint, + MockContentControllerDelayed* aMcc, TimeDuration aTapLength, nsEventStatus (*aOutEventStatuses)[2] = nullptr, uint64_t* aOutInputBlockId = nullptr) @@ -115,7 +126,7 @@ Tap(const RefPtr& aTarget, int aX, int aY, MockContentControllerD aOutInputBlockId = &blockId; } - nsEventStatus status = TouchDown(aTarget, aX, aY, aMcc->Time(), aOutInputBlockId); + nsEventStatus status = TouchDown(aTarget, aPoint, aMcc->Time(), aOutInputBlockId); if (aOutEventStatuses) { (*aOutEventStatuses)[0] = status; } @@ -127,7 +138,7 @@ Tap(const RefPtr& aTarget, int aX, int aY, MockContentControllerD SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId); } - status = TouchUp(aTarget, aX, aY, aMcc->Time()); + status = TouchUp(aTarget, aPoint, aMcc->Time()); if (aOutEventStatuses) { (*aOutEventStatuses)[1] = status; } @@ -135,11 +146,12 @@ Tap(const RefPtr& aTarget, int aX, int aY, MockContentControllerD template void -TapAndCheckStatus(const RefPtr& aTarget, int aX, int aY, - MockContentControllerDelayed* aMcc, TimeDuration aTapLength) +TapAndCheckStatus(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, MockContentControllerDelayed* aMcc, + TimeDuration aTapLength) { nsEventStatus statuses[2]; - Tap(aTarget, aX, aY, aMcc, aTapLength, &statuses); + Tap(aTarget, aPoint, aMcc, aTapLength, &statuses); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]); } @@ -148,8 +160,8 @@ template void Pan(const RefPtr& aTarget, MockContentControllerDelayed* aMcc, - const ScreenPoint& aTouchStart, - const ScreenPoint& aTouchEnd, + const ScreenIntPoint& aTouchStart, + const ScreenIntPoint& aTouchEnd, bool aKeepFingerDown = false, nsTArray* aAllowedTouchBehaviors = nullptr, nsEventStatus (*aOutEventStatuses)[4] = nullptr, @@ -173,7 +185,9 @@ Pan(const RefPtr& aTarget, } // Make sure the move is large enough to not be handled as a tap - nsEventStatus status = TouchDown(aTarget, aTouchStart.x, aTouchStart.y + OVERCOME_TOUCH_TOLERANCE, aMcc->Time(), aOutInputBlockId); + nsEventStatus status = TouchDown(aTarget, + ScreenIntPoint(aTouchStart.x, aTouchStart.y + OVERCOME_TOUCH_TOLERANCE), + aMcc->Time(), aOutInputBlockId); if (aOutEventStatuses) { (*aOutEventStatuses)[0] = status; } @@ -190,14 +204,14 @@ Pan(const RefPtr& aTarget, } } - status = TouchMove(aTarget, aTouchStart.x, aTouchStart.y, aMcc->Time()); + status = TouchMove(aTarget, aTouchStart, aMcc->Time()); if (aOutEventStatuses) { (*aOutEventStatuses)[1] = status; } aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); - status = TouchMove(aTarget, aTouchEnd.x, aTouchEnd.y, aMcc->Time()); + status = TouchMove(aTarget, aTouchEnd, aMcc->Time()); if (aOutEventStatuses) { (*aOutEventStatuses)[2] = status; } @@ -205,7 +219,7 @@ Pan(const RefPtr& aTarget, aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); if (!aKeepFingerDown) { - status = TouchUp(aTarget, aTouchEnd.x, aTouchEnd.y, aMcc->Time()); + status = TouchUp(aTarget, aTouchEnd, aMcc->Time()); } else { status = nsEventStatus_eIgnore; } @@ -232,7 +246,7 @@ Pan(const RefPtr& aTarget, nsEventStatus (*aOutEventStatuses)[4] = nullptr, uint64_t* aOutInputBlockId = nullptr) { - ::Pan(aTarget, aMcc, ScreenPoint(10, aTouchStartY), ScreenPoint(10, aTouchEndY), + ::Pan(aTarget, aMcc, ScreenIntPoint(10, aTouchStartY), ScreenIntPoint(10, aTouchEndY), aKeepFingerDown, aAllowedTouchBehaviors, aOutEventStatuses, aOutInputBlockId); } @@ -279,19 +293,20 @@ ApzcPanNoFling(const RefPtr& aApzc, template void PinchWithPinchInput(const RefPtr& aTarget, - int aFocusX, int aFocusY, int aSecondFocusX, int aSecondFocusY, float aScale, + const ScreenIntPoint& aFocus, + const ScreenIntPoint& aSecondFocus, float aScale, nsEventStatus (*aOutEventStatuses)[3] = nullptr) { nsEventStatus actualStatus = aTarget->ReceiveInputEvent( CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_START, - aFocusX, aFocusY, 10.0, 10.0), + aFocus, 10.0, 10.0), nullptr); if (aOutEventStatuses) { (*aOutEventStatuses)[0] = actualStatus; } actualStatus = aTarget->ReceiveInputEvent( CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE, - aSecondFocusX, aSecondFocusY, 10.0 * aScale, 10.0), + aSecondFocus, 10.0 * aScale, 10.0), nullptr); if (aOutEventStatuses) { (*aOutEventStatuses)[1] = actualStatus; @@ -300,7 +315,7 @@ PinchWithPinchInput(const RefPtr& aTarget, CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_END, // note: negative values here tell APZC // not to turn the pinch into a pan - aFocusX, aFocusY, -1.0, -1.0), + aFocus, -1.0, -1.0), nullptr); if (aOutEventStatuses) { (*aOutEventStatuses)[2] = actualStatus; @@ -310,11 +325,11 @@ PinchWithPinchInput(const RefPtr& aTarget, template void PinchWithPinchInputAndCheckStatus(const RefPtr& aTarget, - int aFocusX, int aFocusY, float aScale, + const ScreenIntPoint& aFocus, float aScale, bool aShouldTriggerPinch) { nsEventStatus statuses[3]; // scalebegin, scale, scaleend - PinchWithPinchInput(aTarget, aFocusX, aFocusY, aFocusX, aFocusY, aScale, &statuses); + PinchWithPinchInput(aTarget, aFocus, aFocus, aScale, &statuses); nsEventStatus expectedStatus = aShouldTriggerPinch ? nsEventStatus_eConsumeNoDefault @@ -326,7 +341,7 @@ PinchWithPinchInputAndCheckStatus(const RefPtr& aTarget, template void PinchWithTouchInput(const RefPtr& aTarget, - int aFocusX, int aFocusY, float aScale, + const ScreenIntPoint& aFocus, float aScale, int& inputId, nsTArray* aAllowedTouchBehaviors = nullptr, nsEventStatus (*aOutEventStatuses)[4] = nullptr, @@ -345,8 +360,8 @@ PinchWithTouchInput(const RefPtr& aTarget, } MultiTouchInput mtiStart = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0); - mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX, aFocusY)); - mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX, aFocusY)); + mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocus)); + mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocus)); nsEventStatus status = aTarget->ReceiveInputEvent(mtiStart, aOutInputBlockId); if (aOutEventStatuses) { (*aOutEventStatuses)[0] = status; @@ -360,24 +375,24 @@ PinchWithTouchInput(const RefPtr& aTarget, } MultiTouchInput mtiMove1 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); - mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLength, aFocusY)); - mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLength, aFocusY)); + mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocus.x - pinchLength, aFocus.y)); + mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocus.x + pinchLength, aFocus.y)); status = aTarget->ReceiveInputEvent(mtiMove1, nullptr); if (aOutEventStatuses) { (*aOutEventStatuses)[1] = status; } MultiTouchInput mtiMove2 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0); - mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLengthScaled, aFocusY)); - mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLengthScaled, aFocusY)); + mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocus.x - pinchLengthScaled, aFocus.y)); + mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocus.x + pinchLengthScaled, aFocus.y)); status = aTarget->ReceiveInputEvent(mtiMove2, nullptr); if (aOutEventStatuses) { (*aOutEventStatuses)[2] = status; } MultiTouchInput mtiEnd = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0); - mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLengthScaled, aFocusY)); - mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLengthScaled, aFocusY)); + mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocus.x - pinchLengthScaled, aFocus.y)); + mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocus.x + pinchLengthScaled, aFocus.y)); status = aTarget->ReceiveInputEvent(mtiEnd, nullptr); if (aOutEventStatuses) { (*aOutEventStatuses)[3] = status; @@ -389,12 +404,12 @@ PinchWithTouchInput(const RefPtr& aTarget, template void PinchWithTouchInputAndCheckStatus(const RefPtr& aTarget, - int aFocusX, int aFocusY, float aScale, + const ScreenIntPoint& aFocus, float aScale, int& inputId, bool aShouldTriggerPinch, nsTArray* aAllowedTouchBehaviors) { nsEventStatus statuses[4]; // down, move, move, up - PinchWithTouchInput(aTarget, aFocusX, aFocusY, aScale, inputId, aAllowedTouchBehaviors, &statuses); + PinchWithTouchInput(aTarget, aFocus, aScale, inputId, aAllowedTouchBehaviors, &statuses); nsEventStatus expectedMoveStatus = aShouldTriggerPinch ? nsEventStatus_eConsumeDoDefault @@ -406,12 +421,13 @@ PinchWithTouchInputAndCheckStatus(const RefPtr& aTarget, template void -DoubleTap(const RefPtr& aTarget, int aX, int aY, MockContentControllerDelayed* aMcc, +DoubleTap(const RefPtr& aTarget, const ScreenIntPoint& aPoint, + MockContentControllerDelayed* aMcc, nsEventStatus (*aOutEventStatuses)[4] = nullptr, uint64_t (*aOutInputBlockIds)[2] = nullptr) { uint64_t blockId; - nsEventStatus status = TouchDown(aTarget, aX, aY, aMcc->Time(), &blockId); + nsEventStatus status = TouchDown(aTarget, aPoint, aMcc->Time(), &blockId); if (aOutEventStatuses) { (*aOutEventStatuses)[0] = status; } @@ -426,12 +442,12 @@ DoubleTap(const RefPtr& aTarget, int aX, int aY, MockContentContr SetDefaultAllowedTouchBehavior(aTarget, blockId); } - status = TouchUp(aTarget, aX, aY, aMcc->Time()); + status = TouchUp(aTarget, aPoint, aMcc->Time()); if (aOutEventStatuses) { (*aOutEventStatuses)[1] = status; } aMcc->AdvanceByMillis(10); - status = TouchDown(aTarget, aX, aY, aMcc->Time(), &blockId); + status = TouchDown(aTarget, aPoint, aMcc->Time(), &blockId); if (aOutEventStatuses) { (*aOutEventStatuses)[2] = status; } @@ -444,7 +460,7 @@ DoubleTap(const RefPtr& aTarget, int aX, int aY, MockContentContr SetDefaultAllowedTouchBehavior(aTarget, blockId); } - status = TouchUp(aTarget, aX, aY, aMcc->Time()); + status = TouchUp(aTarget, aPoint, aMcc->Time()); if (aOutEventStatuses) { (*aOutEventStatuses)[3] = status; } @@ -452,11 +468,12 @@ DoubleTap(const RefPtr& aTarget, int aX, int aY, MockContentContr template void -DoubleTapAndCheckStatus(const RefPtr& aTarget, int aX, int aY, - MockContentControllerDelayed* aMcc, uint64_t (*aOutInputBlockIds)[2] = nullptr) +DoubleTapAndCheckStatus(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, MockContentControllerDelayed* aMcc, + uint64_t (*aOutInputBlockIds)[2] = nullptr) { nsEventStatus statuses[4]; - DoubleTap(aTarget, aX, aY, aMcc, &statuses, aOutInputBlockIds); + DoubleTap(aTarget, aPoint, aMcc, &statuses, aOutInputBlockIds); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[2]); diff --git a/gfx/layers/apz/test/gtest/TestBasic.cpp b/gfx/layers/apz/test/gtest/TestBasic.cpp index 648c8eeaaf..1bc9fc9fc1 100644 --- a/gfx/layers/apz/test/gtest/TestBasic.cpp +++ b/gfx/layers/apz/test/gtest/TestBasic.cpp @@ -22,7 +22,7 @@ TEST_F(APZCBasicTester, Overzoom) { EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); - PinchWithPinchInputAndCheckStatus(apzc, 50, 50, 0.5, true); + PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(50, 50), 0.5, true); fm = apzc->GetFrameMetrics(); EXPECT_EQ(0.8f, fm.GetZoom().ToScaleFactor().scale); @@ -295,8 +295,8 @@ TEST_F(APZCBasicTester, OverScroll_Bug1152051b) { // to schedule a new one since we're still overscrolled. We don't pan because // panning can trigger functions that clear the overscroll animation state // in other ways. - TouchDown(apzc, 10, 10, mcc->Time(), nullptr); - TouchUp(apzc, 10, 10, mcc->Time()); + TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time(), nullptr); + TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time()); // Sample the second overscroll animation to its end. // If the ending of the first overscroll animation fails to clear state diff --git a/gfx/layers/apz/test/gtest/TestEventRegions.cpp b/gfx/layers/apz/test/gtest/TestEventRegions.cpp index 3cf8d7741b..bb20749be4 100644 --- a/gfx/layers/apz/test/gtest/TestEventRegions.cpp +++ b/gfx/layers/apz/test/gtest/TestEventRegions.cpp @@ -186,18 +186,18 @@ TEST_F(APZEventRegionsTester, HitRegionImmediateResponse) { // Tap in the exposed hit regions of each of the layers once and ensure // the clicks are dispatched right away - Tap(manager, 10, 10, mcc, tapDuration); + Tap(manager, ScreenIntPoint(10, 10), mcc, tapDuration); mcc->RunThroughDelayedTasks(); // this runs the tap event check.Call("Tapped on left"); - Tap(manager, 110, 110, mcc, tapDuration); + Tap(manager, ScreenIntPoint(110, 110), mcc, tapDuration); mcc->RunThroughDelayedTasks(); // this runs the tap event check.Call("Tapped on bottom"); - Tap(manager, 110, 10, mcc, tapDuration); + Tap(manager, ScreenIntPoint(110, 10), mcc, tapDuration); mcc->RunThroughDelayedTasks(); // this runs the tap event check.Call("Tapped on root"); // Now tap on the dispatch-to-content region where the layers overlap - Tap(manager, 10, 110, mcc, tapDuration); + Tap(manager, ScreenIntPoint(10, 110), mcc, tapDuration); mcc->RunThroughDelayedTasks(); // this runs the main-thread timeout check.Call("Tap pending on d-t-c region"); mcc->RunThroughDelayedTasks(); // this runs the tap event @@ -205,7 +205,7 @@ TEST_F(APZEventRegionsTester, HitRegionImmediateResponse) { // Now let's do that again, but simulate a main-thread response uint64_t inputBlockId = 0; - Tap(manager, 10, 110, mcc, tapDuration, nullptr, &inputBlockId); + Tap(manager, ScreenIntPoint(10, 110), mcc, tapDuration, nullptr, &inputBlockId); nsTArray targets; targets.AppendElement(left->GetGuid()); manager->SetTargetAPZC(inputBlockId, targets); @@ -221,7 +221,7 @@ TEST_F(APZEventRegionsTester, HitRegionAccumulatesChildren) { // content controller, which indicates the input events got routed correctly // to the APZC. EXPECT_CALL(*mcc, HandleSingleTap(_, _, rootApzc->GetGuid())).Times(1); - Tap(manager, 10, 160, mcc, TimeDuration::FromMilliseconds(100)); + Tap(manager, ScreenIntPoint(10, 160), mcc, TimeDuration::FromMilliseconds(100)); } TEST_F(APZEventRegionsTester, Obscuration) { @@ -260,7 +260,7 @@ TEST_F(APZEventRegionsTester, Bug1117712) { // These touch events should hit the dispatch-to-content region of layers[3] // and so get queued with that APZC as the tentative target. uint64_t inputBlockId = 0; - Tap(manager, 55, 5, mcc, TimeDuration::FromMilliseconds(100), nullptr, &inputBlockId); + Tap(manager, ScreenIntPoint(55, 5), mcc, TimeDuration::FromMilliseconds(100), nullptr, &inputBlockId); // But now we tell the APZ that really it hit layers[2], and expect the tap // to be delivered at the correct coordinates. EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(55, 5), 0, apzc2->GetGuid())).Times(1); diff --git a/gfx/layers/apz/test/gtest/TestGestureDetector.cpp b/gfx/layers/apz/test/gtest/TestGestureDetector.cpp index 26814efdc2..d47fd9ea10 100644 --- a/gfx/layers/apz/test/gtest/TestGestureDetector.cpp +++ b/gfx/layers/apz/test/gtest/TestGestureDetector.cpp @@ -210,12 +210,12 @@ protected: // Deliver a tap to abort the fling. Ensure that we get a HandleSingleTap // call out of it if and only if the fling is slow. EXPECT_CALL(*mcc, HandleSingleTap(_, 0, apzc->GetGuid())).Times(tapCallsExpected); - Tap(apzc, 10, 10, mcc, 0); + Tap(apzc, ScreenIntPoint(10, 10), mcc, 0); while (mcc->RunThroughDelayedTasks()); // Deliver another tap, to make sure that taps are flowing properly once // the fling is aborted. - Tap(apzc, 100, 100, mcc, 0); + Tap(apzc, ScreenIntPoint(100, 100), mcc, 0); while (mcc->RunThroughDelayedTasks()); // Verify that we didn't advance any further after the fling was aborted, in either case. @@ -247,7 +247,7 @@ protected: EXPECT_GT(finalPoint.y, point.y); // Now we put our finger down to stop the fling - TouchDown(apzc, 10, 10, mcc->Time(), &blockId); + TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time(), &blockId); // Re-sample to make sure it hasn't moved apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(10)); @@ -264,7 +264,7 @@ protected: EXPECT_EQ(finalPoint.y, point.y); // clean up - TouchUp(apzc, 10, 10, mcc->Time()); + TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time()); apzc->AssertStateIsReset(); } @@ -300,7 +300,7 @@ TEST_F(APZCGestureDetectorTester, ShortPress) { } check.Call("pre-tap"); - TapAndCheckStatus(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100)); + TapAndCheckStatus(apzc, ScreenIntPoint(10, 10), mcc, TimeDuration::FromMilliseconds(100)); check.Call("post-tap"); apzc->AssertStateIsReset(); @@ -320,7 +320,7 @@ TEST_F(APZCGestureDetectorTester, MediumPress) { } check.Call("pre-tap"); - TapAndCheckStatus(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(400)); + TapAndCheckStatus(apzc, ScreenIntPoint(10, 10), mcc, TimeDuration::FromMilliseconds(400)); check.Call("post-tap"); apzc->AssertStateIsReset(); @@ -333,7 +333,7 @@ protected: uint64_t blockId = 0; - nsEventStatus status = TouchDown(apzc, 10, 10, mcc->Time(), &blockId); + nsEventStatus status = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time(), &blockId); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) { @@ -376,7 +376,7 @@ protected: // Finally, simulate lifting the finger. Since the long-press wasn't // prevent-defaulted, we should get a long-tap-up event. check.Call("preHandleSingleTap"); - status = TouchUp(apzc, 10, 10, mcc->Time()); + status = TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time()); mcc->RunThroughDelayedTasks(); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); check.Call("postHandleSingleTap"); @@ -394,7 +394,7 @@ protected: touchEndY = 50; uint64_t blockId = 0; - nsEventStatus status = TouchDown(apzc, touchX, touchStartY, mcc->Time(), &blockId); + nsEventStatus status = TouchDown(apzc, ScreenIntPoint(touchX, touchStartY), mcc->Time(), &blockId); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) { @@ -436,7 +436,7 @@ protected: EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(touchX, touchEndY), 0, apzc->GetGuid())).Times(0); - status = TouchUp(apzc, touchX, touchEndY, mcc->Time()); + status = TouchUp(apzc, ScreenIntPoint(touchX, touchEndY), mcc->Time()); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); ParentLayerPoint pointOut; @@ -482,7 +482,7 @@ TEST_F(APZCGestureDetectorTester, DoubleTap) { EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); uint64_t blockIds[2]; - DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds); + DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), mcc, &blockIds); // responses to the two touchstarts apzc->ContentReceivedInputBlock(blockIds[0], false); @@ -499,7 +499,7 @@ TEST_F(APZCGestureDetectorTester, DoubleTapNotZoomable) { EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0); uint64_t blockIds[2]; - DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds); + DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), mcc, &blockIds); // responses to the two touchstarts apzc->ContentReceivedInputBlock(blockIds[0], false); @@ -516,7 +516,7 @@ TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultFirstOnly) { EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0); uint64_t blockIds[2]; - DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds); + DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), mcc, &blockIds); // responses to the two touchstarts apzc->ContentReceivedInputBlock(blockIds[0], true); @@ -533,7 +533,7 @@ TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultBoth) { EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0); uint64_t blockIds[2]; - DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds); + DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), mcc, &blockIds); // responses to the two touchstarts apzc->ContentReceivedInputBlock(blockIds[0], true); @@ -549,7 +549,7 @@ TEST_F(APZCGestureDetectorTester, TapFollowedByPinch) { EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); - Tap(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100)); + Tap(apzc, ScreenIntPoint(10, 10), mcc, TimeDuration::FromMilliseconds(100)); int inputId = 0; MultiTouchInput mti; @@ -571,7 +571,7 @@ TEST_F(APZCGestureDetectorTester, TapFollowedByMultipleTouches) { EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); - Tap(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100)); + Tap(apzc, ScreenIntPoint(10, 10), mcc, TimeDuration::FromMilliseconds(100)); int inputId = 0; MultiTouchInput mti; diff --git a/gfx/layers/apz/test/gtest/TestHitTesting.cpp b/gfx/layers/apz/test/gtest/TestHitTesting.cpp index 0aba33bbac..6232d9b0f4 100644 --- a/gfx/layers/apz/test/gtest/TestHitTesting.cpp +++ b/gfx/layers/apz/test/gtest/TestHitTesting.cpp @@ -465,12 +465,12 @@ TEST_F(APZHitTestingTester, Bug1148350) { EXPECT_CALL(check, Call("Tapped with interleaved transform")); } - Tap(manager, 100, 100, mcc, TimeDuration::FromMilliseconds(100)); + Tap(manager, ScreenIntPoint(100, 100), mcc, TimeDuration::FromMilliseconds(100)); mcc->RunThroughDelayedTasks(); check.Call("Tapped without transform"); uint64_t blockId; - TouchDown(manager, 100, 100, mcc->Time(), &blockId); + TouchDown(manager, ScreenIntPoint(100, 100), mcc->Time(), &blockId); if (gfxPrefs::TouchActionEnabled()) { SetDefaultAllowedTouchBehavior(manager, blockId); } @@ -480,7 +480,7 @@ TEST_F(APZHitTestingTester, Bug1148350) { layers[0]->SetBaseTransform(Matrix4x4::Translation(0, 50, 0)); manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); - TouchUp(manager, 100, 100, mcc->Time()); + TouchUp(manager, ScreenIntPoint(100, 100), mcc->Time()); mcc->RunThroughDelayedTasks(); check.Call("Tapped with interleaved transform"); } diff --git a/gfx/layers/apz/test/gtest/TestPinching.cpp b/gfx/layers/apz/test/gtest/TestPinching.cpp index 47be99ce82..818d5942b3 100644 --- a/gfx/layers/apz/test/gtest/TestPinching.cpp +++ b/gfx/layers/apz/test/gtest/TestPinching.cpp @@ -44,9 +44,11 @@ protected: int touchInputId = 0; if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) { - PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 1.25, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors); + PinchWithTouchInputAndCheckStatus(apzc, ScreenIntPoint(250, 300), 1.25, + touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors); } else { - PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 1.25, aShouldTriggerPinch); + PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(250, 300), 1.25, + aShouldTriggerPinch); } FrameMetrics fm = apzc->GetFrameMetrics(); @@ -72,9 +74,11 @@ protected: // the visible area of the document in CSS pixels is x=930 y=5 w=50 h=100 if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) { - PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 0.5, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors); + PinchWithTouchInputAndCheckStatus(apzc, ScreenIntPoint(250, 300), 0.5, + touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors); } else { - PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 0.5, aShouldTriggerPinch); + PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(250, 300), 0.5, + aShouldTriggerPinch); } fm = apzc->GetFrameMetrics(); @@ -142,7 +146,8 @@ TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault) { int touchInputId = 0; uint64_t blockId = 0; - PinchWithTouchInput(apzc, 250, 300, 1.25, touchInputId, nullptr, nullptr, &blockId); + PinchWithTouchInput(apzc, ScreenIntPoint(250, 300), 1.25, touchInputId, + nullptr, nullptr, &blockId); // Send the prevent-default notification for the touch block apzc->ContentReceivedInputBlock(blockId, true); @@ -162,7 +167,8 @@ TEST_F(APZCPinchTester, Panning_TwoFinger_ZoomDisabled) { MakeApzcUnzoomable(); nsEventStatus statuses[3]; // scalebegin, scale, scaleend - PinchWithPinchInput(apzc, 250, 350, 200, 300, 10, &statuses); + PinchWithPinchInput(apzc, ScreenIntPoint(250, 350), ScreenIntPoint(200, 300), + 10, &statuses); FrameMetrics fm = apzc->GetFrameMetrics(); diff --git a/gfx/layers/apz/test/gtest/TestOverscrollHandoff.cpp b/gfx/layers/apz/test/gtest/TestScrollHandoff.cpp similarity index 87% rename from gfx/layers/apz/test/gtest/TestOverscrollHandoff.cpp rename to gfx/layers/apz/test/gtest/TestScrollHandoff.cpp index edb86f1a68..4fa9b26ccc 100644 --- a/gfx/layers/apz/test/gtest/TestOverscrollHandoff.cpp +++ b/gfx/layers/apz/test/gtest/TestScrollHandoff.cpp @@ -8,12 +8,12 @@ #include "APZTestCommon.h" #include "InputUtils.h" -class APZOverscrollHandoffTester : public APZCTreeManagerTester { +class APZScrollHandoffTester : public APZCTreeManagerTester { protected: UniquePtr registration; TestAsyncPanZoomController* rootApzc; - void CreateOverscrollHandoffLayerTree1() { + void CreateScrollHandoffLayerTree1() { const char* layerTreeSyntax = "c(t)"; nsIntRegion layerVisibleRegion[] = { nsIntRegion(IntRect(0, 0, 100, 100)), @@ -28,7 +28,7 @@ protected: rootApzc = ApzcOf(root); } - void CreateOverscrollHandoffLayerTree2() { + void CreateScrollHandoffLayerTree2() { const char* layerTreeSyntax = "c(c(t))"; nsIntRegion layerVisibleRegion[] = { nsIntRegion(IntRect(0, 0, 100, 100)), @@ -48,7 +48,7 @@ protected: rootApzc = ApzcOf(root); } - void CreateOverscrollHandoffLayerTree3() { + void CreateScrollHandoffLayerTree3() { const char* layerTreeSyntax = "c(c(t)c(t))"; nsIntRegion layerVisibleRegion[] = { nsIntRegion(IntRect(0, 0, 100, 100)), // root @@ -126,9 +126,9 @@ protected: // Here we test that if the processing of a touch block is deferred while we // wait for content to send a prevent-default message, overscroll is still // handed off correctly when the block is processed. -TEST_F(APZOverscrollHandoffTester, DeferredInputEventProcessing) { +TEST_F(APZScrollHandoffTester, DeferredInputEventProcessing) { // Set up the APZC tree. - CreateOverscrollHandoffLayerTree1(); + CreateScrollHandoffLayerTree1(); TestAsyncPanZoomController* childApzc = ApzcOf(layers[1]); @@ -154,9 +154,9 @@ TEST_F(APZOverscrollHandoffTester, DeferredInputEventProcessing) { // one has been queued, overscroll handoff for the first block follows // the original layer structure while overscroll handoff for the second block // follows the new layer structure. -TEST_F(APZOverscrollHandoffTester, LayerStructureChangesWhileEventsArePending) { +TEST_F(APZScrollHandoffTester, LayerStructureChangesWhileEventsArePending) { // Set up an initial APZC tree. - CreateOverscrollHandoffLayerTree1(); + CreateScrollHandoffLayerTree1(); TestAsyncPanZoomController* childApzc = ApzcOf(layers[1]); @@ -170,7 +170,7 @@ TEST_F(APZOverscrollHandoffTester, LayerStructureChangesWhileEventsArePending) { // Modify the APZC tree to insert a new APZC 'middle' into the handoff chain // between the child and the root. - CreateOverscrollHandoffLayerTree2(); + CreateScrollHandoffLayerTree2(); RefPtr middle = layers[1]; childApzc->SetWaitForMainThread(); TestAsyncPanZoomController* middleApzc = ApzcOf(middle); @@ -202,11 +202,11 @@ TEST_F(APZOverscrollHandoffTester, LayerStructureChangesWhileEventsArePending) { // Test that putting a second finger down on an APZC while a down-chain APZC // is overscrolled doesn't result in being stuck in overscroll. -TEST_F(APZOverscrollHandoffTester, StuckInOverscroll_Bug1073250) { +TEST_F(APZScrollHandoffTester, StuckInOverscroll_Bug1073250) { // Enable overscrolling. SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); - CreateOverscrollHandoffLayerTree1(); + CreateScrollHandoffLayerTree1(); TestAsyncPanZoomController* child = ApzcOf(layers[1]); @@ -239,11 +239,11 @@ TEST_F(APZOverscrollHandoffTester, StuckInOverscroll_Bug1073250) { // This is almost exactly like StuckInOverscroll_Bug1073250, except the // APZC receiving the input events for the first touch block is the child // (and thus not the same APZC that overscrolls, which is the parent). -TEST_F(APZOverscrollHandoffTester, StuckInOverscroll_Bug1231228) { +TEST_F(APZScrollHandoffTester, StuckInOverscroll_Bug1231228) { // Enable overscrolling. SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); - CreateOverscrollHandoffLayerTree1(); + CreateScrollHandoffLayerTree1(); TestAsyncPanZoomController* child = ApzcOf(layers[1]); @@ -273,17 +273,50 @@ TEST_F(APZOverscrollHandoffTester, StuckInOverscroll_Bug1231228) { EXPECT_FALSE(rootApzc->IsOverscrolled()); } +TEST_F(APZScrollHandoffTester, StuckInOverscroll_Bug1240202a) { + // Enable overscrolling. + SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); + + CreateScrollHandoffLayerTree1(); + + TestAsyncPanZoomController* child = ApzcOf(layers[1]); + + // Pan, causing the parent APZC to overscroll. + Pan(manager, mcc, 60, 90, true /* keep finger down */); + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_TRUE(rootApzc->IsOverscrolled()); + + // Lift the finger, triggering an overscroll animation + // (but don't allow it to run). + TouchUp(manager, ScreenIntPoint(10, 90), mcc->Time()); + + // Put the finger down again, interrupting the animation + // and entering the TOUCHING state. + TouchDown(manager, ScreenIntPoint(10, 90), mcc->Time()); + + // Lift the finger once again. + TouchUp(manager, ScreenIntPoint(10, 90), mcc->Time()); + + // Allow any animations to run their course. + child->AdvanceAnimationsUntilEnd(); + rootApzc->AdvanceAnimationsUntilEnd(); + + // Make sure nothing is overscrolled. + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); +} + // Test that flinging in a direction where one component of the fling goes into // overscroll but the other doesn't, results in just the one component being // handed off to the parent, while the original APZC continues flinging in the // other direction. -TEST_F(APZOverscrollHandoffTester, PartialFlingHandoff) { - CreateOverscrollHandoffLayerTree1(); +TEST_F(APZScrollHandoffTester, PartialFlingHandoff) { + CreateScrollHandoffLayerTree1(); // Fling up and to the left. The child APZC has room to scroll up, but not // to the left, so the horizontal component of the fling should be handed // off to the parent APZC. - Pan(manager, mcc, ScreenPoint(90, 90), ScreenPoint(55, 55)); + Pan(manager, mcc, ScreenIntPoint(90, 90), ScreenIntPoint(55, 55)); RefPtr parent = ApzcOf(root); RefPtr child = ApzcOf(layers[1]); @@ -300,9 +333,9 @@ TEST_F(APZOverscrollHandoffTester, PartialFlingHandoff) { // Here we test that if two flings are happening simultaneously, overscroll // is handed off correctly for each. -TEST_F(APZOverscrollHandoffTester, SimultaneousFlings) { +TEST_F(APZScrollHandoffTester, SimultaneousFlings) { // Set up an initial APZC tree. - CreateOverscrollHandoffLayerTree3(); + CreateScrollHandoffLayerTree3(); RefPtr parent1 = ApzcOf(layers[1]); RefPtr child1 = ApzcOf(layers[2]); @@ -330,7 +363,7 @@ TEST_F(APZOverscrollHandoffTester, SimultaneousFlings) { parent2->AssertStateIsFling(); } -TEST_F(APZOverscrollHandoffTester, Scrollgrab) { +TEST_F(APZScrollHandoffTester, Scrollgrab) { // Set up the layer tree CreateScrollgrabLayerTree(); @@ -345,7 +378,7 @@ TEST_F(APZOverscrollHandoffTester, Scrollgrab) { EXPECT_EQ(15, childApzc->GetFrameMetrics().GetScrollOffset().y); } -TEST_F(APZOverscrollHandoffTester, ScrollgrabFling) { +TEST_F(APZScrollHandoffTester, ScrollgrabFling) { // Set up the layer tree CreateScrollgrabLayerTree(); @@ -359,20 +392,20 @@ TEST_F(APZOverscrollHandoffTester, ScrollgrabFling) { childApzc->AssertStateIsReset(); } -TEST_F(APZOverscrollHandoffTester, ScrollgrabFlingAcceleration1) { +TEST_F(APZScrollHandoffTester, ScrollgrabFlingAcceleration1) { CreateScrollgrabLayerTree(true /* make parent scrollable */); TestFlingAcceleration(); } -TEST_F(APZOverscrollHandoffTester, ScrollgrabFlingAcceleration2) { +TEST_F(APZScrollHandoffTester, ScrollgrabFlingAcceleration2) { CreateScrollgrabLayerTree(false /* do not make parent scrollable */); TestFlingAcceleration(); } -TEST_F(APZOverscrollHandoffTester, ImmediateHandoffDisallowed_Pan) { +TEST_F(APZScrollHandoffTester, ImmediateHandoffDisallowed_Pan) { SCOPED_GFX_PREF(APZAllowImmediateHandoff, bool, false); - CreateOverscrollHandoffLayerTree1(); + CreateScrollHandoffLayerTree1(); RefPtr parentApzc = ApzcOf(root); RefPtr childApzc = ApzcOf(layers[1]); @@ -394,10 +427,10 @@ TEST_F(APZOverscrollHandoffTester, ImmediateHandoffDisallowed_Pan) { EXPECT_EQ(10, parentApzc->GetFrameMetrics().GetScrollOffset().y); } -TEST_F(APZOverscrollHandoffTester, ImmediateHandoffDisallowed_Fling) { +TEST_F(APZScrollHandoffTester, ImmediateHandoffDisallowed_Fling) { SCOPED_GFX_PREF(APZAllowImmediateHandoff, bool, false); - CreateOverscrollHandoffLayerTree1(); + CreateScrollHandoffLayerTree1(); RefPtr parentApzc = ApzcOf(root); RefPtr childApzc = ApzcOf(layers[1]); diff --git a/gfx/layers/apz/test/gtest/moz.build b/gfx/layers/apz/test/gtest/moz.build index 56a3422bb8..c15e172cc3 100644 --- a/gfx/layers/apz/test/gtest/moz.build +++ b/gfx/layers/apz/test/gtest/moz.build @@ -9,9 +9,9 @@ UNIFIED_SOURCES += [ 'TestEventRegions.cpp', 'TestGestureDetector.cpp', 'TestHitTesting.cpp', - 'TestOverscrollHandoff.cpp', 'TestPanning.cpp', 'TestPinching.cpp', + 'TestScrollHandoff.cpp', 'TestTreeManager.cpp', ] diff --git a/gfx/layers/apz/util/CheckerboardReportService.cpp b/gfx/layers/apz/util/CheckerboardReportService.cpp new file mode 100644 index 0000000000..19acd6c1b7 --- /dev/null +++ b/gfx/layers/apz/util/CheckerboardReportService.cpp @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "CheckerboardReportService.h" + +#include "gfxPrefs.h" // for gfxPrefs +#include "jsapi.h" // for JS_Now +#include "MainThreadUtils.h" // for NS_IsMainThread +#include "mozilla/Assertions.h" // for MOZ_ASSERT +#include "mozilla/ClearOnShutdown.h" // for ClearOnShutdown +#include "mozilla/dom/CheckerboardReportServiceBinding.h" // for dom::CheckerboardReports +#include "nsContentUtils.h" // for nsContentUtils + +namespace mozilla { +namespace layers { + +/*static*/ StaticRefPtr CheckerboardEventStorage::sInstance; + +/*static*/ already_AddRefed +CheckerboardEventStorage::GetInstance() +{ + // The instance in the parent process does all the work, so if this is getting + // called in the child process something is likely wrong. + MOZ_ASSERT(XRE_IsParentProcess()); + + MOZ_ASSERT(NS_IsMainThread()); + if (!sInstance) { + sInstance = new CheckerboardEventStorage(); + ClearOnShutdown(&sInstance); + } + RefPtr instance = sInstance.get(); + return instance.forget(); +} + +void +CheckerboardEventStorage::ReportCheckerboard(uint32_t aSeverity, const std::string& aLog) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (aSeverity == 0) { + // This code assumes all checkerboard reports have a nonzero severity. + return; + } + + CheckerboardReport severe(aSeverity, JS_Now(), aLog); + CheckerboardReport recent; + + // First look in the "severe" reports to see if the new one belongs in that + // list. + for (int i = 0; i < SEVERITY_MAX_INDEX; i++) { + if (mCheckerboardReports[i].mSeverity >= severe.mSeverity) { + continue; + } + // The new one deserves to be in the "severe" list. Take the one getting + // bumped off the list, and put it in |recent| for possible insertion into + // the recents list. + recent = mCheckerboardReports[SEVERITY_MAX_INDEX - 1]; + + // Shuffle the severe list down, insert the new one. + for (int j = SEVERITY_MAX_INDEX - 1; j > i; j--) { + mCheckerboardReports[j] = mCheckerboardReports[j - 1]; + } + mCheckerboardReports[i] = severe; + severe.mSeverity = 0; // mark |severe| as inserted + break; + } + + // If |severe.mSeverity| is nonzero, the incoming report didn't get inserted + // into the severe list; put it into |recent| for insertion into the recent + // list. + if (severe.mSeverity) { + MOZ_ASSERT(recent.mSeverity == 0, "recent should be empty here"); + recent = severe; + } // else |recent| may hold a report that got knocked out of the severe list. + + if (recent.mSeverity == 0) { + // Nothing to be inserted into the recent list. + return; + } + + // If it wasn't in the "severe" list, add it to the "recent" list. + for (int i = SEVERITY_MAX_INDEX; i < RECENT_MAX_INDEX; i++) { + if (mCheckerboardReports[i].mTimestamp >= recent.mTimestamp) { + continue; + } + // |recent| needs to be inserted at |i|. Shuffle the remaining ones down + // and insert it. + for (int j = RECENT_MAX_INDEX - 1; j > i; j--) { + mCheckerboardReports[j] = mCheckerboardReports[j - 1]; + } + mCheckerboardReports[i] = recent; + break; + } +} + +void +CheckerboardEventStorage::GetReports(nsTArray& aOutReports) +{ + MOZ_ASSERT(NS_IsMainThread()); + + for (int i = 0; i < RECENT_MAX_INDEX; i++) { + CheckerboardReport& r = mCheckerboardReports[i]; + if (r.mSeverity == 0) { + continue; + } + dom::CheckerboardReport report; + report.mSeverity.Construct() = r.mSeverity; + report.mTimestamp.Construct() = r.mTimestamp / 1000; // micros to millis + report.mLog.Construct() = NS_ConvertUTF8toUTF16(r.mLog.c_str(), r.mLog.size()); + report.mReason.Construct() = (i < SEVERITY_MAX_INDEX) + ? dom::CheckerboardReason::Severe + : dom::CheckerboardReason::Recent; + aOutReports.AppendElement(report); + } +} + +} // namespace layers + +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CheckerboardReportService, mParent) +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CheckerboardReportService, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CheckerboardReportService, Release) + +/*static*/ bool +CheckerboardReportService::IsEnabled(JSContext* aCtx, JSObject* aGlobal) +{ + // Only allow this in the parent process + if (!XRE_IsParentProcess()) { + return false; + } + + return nsContentUtils::IsSpecificAboutPage(aGlobal, "about:checkerboard"); +} + +/*static*/ already_AddRefed +CheckerboardReportService::Constructor(const dom::GlobalObject& aGlobal, ErrorResult& aRv) +{ + RefPtr ces = new CheckerboardReportService(aGlobal.GetAsSupports()); + return ces.forget(); +} + +CheckerboardReportService::CheckerboardReportService(nsISupports* aParent) + : mParent(aParent) +{ +} + +JSObject* +CheckerboardReportService::WrapObject(JSContext* aCtx, JS::Handle aGivenProto) +{ + return CheckerboardReportServiceBinding::Wrap(aCtx, this, aGivenProto); +} + +nsISupports* +CheckerboardReportService::GetParentObject() +{ + return mParent; +} + +void +CheckerboardReportService::GetReports(nsTArray& aOutReports) +{ + RefPtr instance = + mozilla::layers::CheckerboardEventStorage::GetInstance(); + MOZ_ASSERT(instance); + instance->GetReports(aOutReports); +} + +bool +CheckerboardReportService::IsRecordingEnabled() const +{ + return gfxPrefs::APZRecordCheckerboarding(); +} + +void +CheckerboardReportService::SetRecordingEnabled(bool aEnabled) +{ + gfxPrefs::SetAPZRecordCheckerboarding(aEnabled); +} + +} // namespace dom +} // namespace mozilla diff --git a/gfx/layers/apz/util/CheckerboardReportService.h b/gfx/layers/apz/util/CheckerboardReportService.h new file mode 100644 index 0000000000..ee86bef54a --- /dev/null +++ b/gfx/layers/apz/util/CheckerboardReportService.h @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_dom_CheckerboardReportService_h +#define mozilla_dom_CheckerboardReportService_h + +#include + +#include "js/TypeDecls.h" // for JSContext, JSObject +#include "mozilla/ErrorResult.h" // for ErrorResult +#include "mozilla/StaticPtr.h" // for StaticRefPtr +#include "nsCOMPtr.h" // for nsCOMPtr +#include "nsISupports.h" // for NS_INLINE_DECL_REFCOUNTING +#include "nsWrapperCache.h" // for nsWrapperCache + +namespace mozilla { + +namespace dom { +struct CheckerboardReport; +} + +namespace layers { + +// CheckerboardEventStorage is a singleton that stores info on checkerboard +// events, so that they can be accessed from about:checkerboard and visualized. +// Note that this class is NOT threadsafe, and all methods must be called on +// the main thread. +class CheckerboardEventStorage +{ + NS_INLINE_DECL_REFCOUNTING(CheckerboardEventStorage) + +public: + /** + * Get the singleton instance. + */ + static already_AddRefed GetInstance(); + + /** + * Save a checkerboard event log, optionally dropping older ones that were + * less severe or less recent. Zero-severity reports may be ignored entirely. + */ + void ReportCheckerboard(uint32_t aSeverity, const std::string& aLog); + + /** + * Get the stored checkerboard reports. + */ + void GetReports(nsTArray& aOutReports); + +private: + /* Stuff for refcounted singleton */ + CheckerboardEventStorage() {} + virtual ~CheckerboardEventStorage() {} + + static StaticRefPtr sInstance; + +private: + /** + * Struct that this class uses internally to store a checkerboard report. + */ + struct CheckerboardReport { + uint32_t mSeverity; // if 0, this report is empty + int64_t mTimestamp; // microseconds since epoch, as from JS_Now() + std::string mLog; + + CheckerboardReport() + : mSeverity(0) + , mTimestamp(0) + {} + + CheckerboardReport(uint32_t aSeverity, int64_t aTimestamp, + const std::string& aLog) + : mSeverity(aSeverity) + , mTimestamp(aTimestamp) + , mLog(aLog) + {} + }; + + // The first 5 (indices 0-4) are the most severe ones in decreasing order + // of severity; the next 5 (indices 5-9) are the most recent ones that are + // not already in the "severe" list. + static const int SEVERITY_MAX_INDEX = 5; + static const int RECENT_MAX_INDEX = 10; + CheckerboardReport mCheckerboardReports[RECENT_MAX_INDEX]; +}; + +} // namespace layers + +namespace dom { + +class GlobalObject; + +/** + * CheckerboardReportService is a wrapper object that allows access to the + * stuff in CheckerboardEventStorage (above). We need this wrapper for proper + * garbage/cycle collection, since this can be accessed from JS. + */ +class CheckerboardReportService : public nsWrapperCache +{ +public: + /** + * Check if the given page is allowed to access this object via the WebIDL + * bindings. It only returns true if the page is about:checkerboard. + */ + static bool IsEnabled(JSContext* aCtx, JSObject* aGlobal); + + /* + * Other standard WebIDL binding glue. + */ + + static already_AddRefed + Constructor(const dom::GlobalObject& aGlobal, ErrorResult& aRv); + + explicit CheckerboardReportService(nsISupports* aSupports); + + JSObject* WrapObject(JSContext* aCtx, JS::Handle aGivenProto) override; + + nsISupports* GetParentObject(); + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(CheckerboardReportService) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(CheckerboardReportService) + +public: + /* + * The methods exposed via the webidl. + */ + void GetReports(nsTArray& aOutReports); + bool IsRecordingEnabled() const; + void SetRecordingEnabled(bool aEnabled); + +private: + virtual ~CheckerboardReportService() {} + + nsCOMPtr mParent; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_layers_CheckerboardReportService_h */ diff --git a/gfx/layers/apz/util/ScrollInputMethods.h b/gfx/layers/apz/util/ScrollInputMethods.h new file mode 100644 index 0000000000..ba599cd8b1 --- /dev/null +++ b/gfx/layers/apz/util/ScrollInputMethods.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_layers_ScrollInputMethods_h +#define mozilla_layers_ScrollInputMethods_h + +namespace mozilla { +namespace layers { + +/** + * An enumeration that lists various input methods used to trigger scrolling. + * Used as the values for the SCROLL_INPUT_METHODS telemetry histogram. + */ +enum class ScrollInputMethod { + + // === Driven by APZ === + + ApzTouch, // touch events + ApzWheelPixel, // wheel events, pixel scrolling mode + ApzWheelLine, // wheel events, line scrolling mode + ApzWheelPage, // wheel events, page scrolling mode + ApzPanGesture, // pan gesture events (generally triggered by trackpad) + ApzScrollbarDrag, // dragging the scrollbar + + // === Driven by the main thread === + + // Keyboard + MainThreadScrollLine, // line scrolling + // (generally triggered by up/down arrow keys) + MainThreadScrollCharacter, // character scrolling + // (generally triggered by left/right arrow keys) + MainThreadScrollPage, // page scrolling + // (generally triggered by PageUp/PageDown keys) + MainThreadCompleteScroll, // scrolling to the end of the scroll range + // (generally triggered by Home/End keys) + MainThreadScrollCaretIntoView, // scrolling to bring the caret into view + // after moving the caret via the keyboard + + // Touch + MainThreadTouch, // touch events + + // Scrollbar + MainThreadScrollbarDrag, // dragging the scrollbar + MainThreadScrollbarButtonClick, // clicking the buttons at the ends of the + // scrollback track + MainThreadScrollbarTrackClick, // clicking the scrollbar track above or + // below the thumb + + // Autoscrolling + MainThreadAutoscrolling, // autoscrolling + + // New input methods can be added at the end, up to a maximum of 64. + // They should only be added at the end, to preserve the numerical values + // of the existing enumerators. +}; + +} // namespace layers +} // namespace mozilla + +#endif /* mozilla_layers_ScrollInputMethods_h */ diff --git a/gfx/layers/basic/BasicCompositor.cpp b/gfx/layers/basic/BasicCompositor.cpp index a016a16fa3..241dfb4e14 100644 --- a/gfx/layers/basic/BasicCompositor.cpp +++ b/gfx/layers/basic/BasicCompositor.cpp @@ -34,6 +34,7 @@ class DataTextureSourceBasic : public DataTextureSource , public TextureSourceBasic { public: + virtual const char* Name() const override { return "DataTextureSourceBasic"; } virtual TextureSourceBasic* AsSourceBasic() override { return this; } @@ -201,6 +202,11 @@ DrawSurfaceWithTextureCoords(DrawTarget *aDest, SourceSurface *aMask, const Matrix* aMaskTransform) { + if (!aSource) { + gfxWarning() << "DrawSurfaceWithTextureCoords problem " << gfx::hexa(aSource) << " and " << gfx::hexa(aMask); + return; + } + // Convert aTextureCoords into aSource's coordinate space gfxRect sourceRect(aTextureCoords.x * aSource->GetSize().width, aTextureCoords.y * aSource->GetSize().height, @@ -422,6 +428,9 @@ BasicCompositor::DrawQuad(const gfx::Rect& aRect, if (aEffectChain.mSecondaryEffects[EffectTypes::MASK]) { EffectMask *effectMask = static_cast(aEffectChain.mSecondaryEffects[EffectTypes::MASK].get()); sourceMask = effectMask->mMaskTexture->AsSourceBasic()->GetSurface(dest); + if (!sourceMask) { + gfxWarning() << "Invalid sourceMask effect"; + } MOZ_ASSERT(effectMask->mMaskTransform.Is2D(), "How did we end up with a 3D transform here?!"); MOZ_ASSERT(!effectMask->mIs3D); maskTransform = effectMask->mMaskTransform.As2D(); @@ -456,15 +465,17 @@ BasicCompositor::DrawQuad(const gfx::Rect& aRect, static_cast(aEffectChain.mPrimaryEffect.get()); TextureSourceBasic* source = texturedEffect->mTexture->AsSourceBasic(); - if (texturedEffect->mPremultiplied) { + if (source && texturedEffect->mPremultiplied) { DrawSurfaceWithTextureCoords(dest, aRect, source->GetSurface(dest), texturedEffect->mTextureCoords, texturedEffect->mFilter, DrawOptions(aOpacity, blendMode), sourceMask, &maskTransform); - } else { - RefPtr srcData = source->GetSurface(dest)->GetDataSurface(); + } else if (source) { + SourceSurface* srcSurf = source->GetSurface(dest); + if (srcSurf) { + RefPtr srcData = srcSurf->GetDataSurface(); // Yes, we re-create the premultiplied data every time. // This might be better with a cache, eventually. @@ -476,7 +487,11 @@ BasicCompositor::DrawQuad(const gfx::Rect& aRect, texturedEffect->mFilter, DrawOptions(aOpacity, blendMode), sourceMask, &maskTransform); + } + } else { + gfxDevCrash(LogReason::IncompatibleBasicTexturedEffect) << "Bad for basic with " << texturedEffect->mTexture->Name() << " and " << gfx::hexa(sourceMask); } + break; } case EffectTypes::YCBCR: { diff --git a/gfx/layers/basic/BasicCompositor.h b/gfx/layers/basic/BasicCompositor.h index 882c0283e9..fcb7e68040 100644 --- a/gfx/layers/basic/BasicCompositor.h +++ b/gfx/layers/basic/BasicCompositor.h @@ -23,6 +23,8 @@ public: , mSize(aRect.Size()) { } + virtual const char* Name() const override { return "BasicCompositingRenderTarget"; } + virtual gfx::IntSize GetSize() const override { return mSize; } void BindRenderTarget(); diff --git a/gfx/layers/basic/BasicContainerLayer.cpp b/gfx/layers/basic/BasicContainerLayer.cpp index d73131e9b1..c43b95c95f 100644 --- a/gfx/layers/basic/BasicContainerLayer.cpp +++ b/gfx/layers/basic/BasicContainerLayer.cpp @@ -109,7 +109,7 @@ BasicContainerLayer::ChildrenPartitionVisibleRegion(const gfx::IntRect& aInRect) return false; nsIntPoint offset(int32_t(transform._31), int32_t(transform._32)); - gfx::IntRect rect = aInRect.Intersect(GetEffectiveVisibleRegion().ToUnknownRegion().GetBounds() + offset); + gfx::IntRect rect = aInRect.Intersect(GetLocalVisibleRegion().ToUnknownRegion().GetBounds() + offset); nsIntRegion covered; for (Layer* l = mFirstChild; l; l = l->GetNextSibling()) { @@ -121,7 +121,7 @@ BasicContainerLayer::ChildrenPartitionVisibleRegion(const gfx::IntRect& aInRect) ThebesMatrix(childTransform).HasNonIntegerTranslation() || l->GetEffectiveOpacity() != 1.0) return false; - nsIntRegion childRegion = l->GetEffectiveVisibleRegion().ToUnknownRegion(); + nsIntRegion childRegion = l->GetLocalVisibleRegion().ToUnknownRegion(); childRegion.MoveBy(int32_t(childTransform._31), int32_t(childTransform._32)); childRegion.And(childRegion, rect); if (l->GetClipRect()) { diff --git a/gfx/layers/basic/BasicLayerManager.cpp b/gfx/layers/basic/BasicLayerManager.cpp index 5f3ae03cdd..c43b39bb23 100644 --- a/gfx/layers/basic/BasicLayerManager.cpp +++ b/gfx/layers/basic/BasicLayerManager.cpp @@ -123,11 +123,22 @@ BasicLayerManager::PushGroupForLayer(gfxContext* aContext, Layer* aLayer, const group.mMaskSurface = GetMaskForLayer(aLayer, &group.mMaskTransform); return group; } + aContext->Restore(); } Matrix maskTransform; RefPtr maskSurf = GetMaskForLayer(aLayer, &maskTransform); + if (maskSurf) { + // The returned transform will transform the mask to device space on the + // destination. Since the User->Device space transform will be applied + // to the mask by PopGroupAndBlend we need to adjust the transform to + // transform the mask to user space. + Matrix currentTransform = ToMatrix(group.mFinalTarget->CurrentMatrix()); + currentTransform.Invert(); + maskTransform = maskTransform * currentTransform; + } + if (aLayer->CanUseOpaqueSurface() && ((didCompleteClip && aRegion.GetNumRects() == 1) || !aContext->CurrentMatrix().HasNonIntegerTranslation())) { @@ -172,8 +183,12 @@ BasicLayerManager::PopGroupForLayer(PushedGroup &group) RefPtr src = sourceDT->Snapshot(); if (group.mMaskSurface) { - dt->SetTransform(group.mMaskTransform * Matrix::Translation(-group.mFinalTarget->GetDeviceOffset())); - dt->MaskSurface(SurfacePattern(src, ExtendMode::CLAMP, Matrix::Translation(group.mGroupOffset.x, group.mGroupOffset.y)), + Point finalOffset = group.mFinalTarget->GetDeviceOffset(); + dt->SetTransform(group.mMaskTransform * Matrix::Translation(-finalOffset)); + Matrix surfTransform = group.mMaskTransform; + surfTransform.Invert(); + dt->MaskSurface(SurfacePattern(src, ExtendMode::CLAMP, surfTransform * + Matrix::Translation(group.mGroupOffset.x, group.mGroupOffset.y)), group.mMaskSurface, Point(0, 0), DrawOptions(group.mOpacity, group.mOperator)); } else { // For now this is required since our group offset is in device space of the final target, @@ -245,7 +260,7 @@ public: // Set the opaque rect to match the bounds of the visible region. void AnnotateOpaqueRect() { - const nsIntRegion visibleRegion = mLayer->GetEffectiveVisibleRegion().ToUnknownRegion(); + const nsIntRegion visibleRegion = mLayer->GetLocalVisibleRegion().ToUnknownRegion(); const IntRect& bounds = visibleRegion.GetBounds(); DrawTarget *dt = mTarget->GetDrawTarget(); @@ -432,7 +447,7 @@ MarkLayersHidden(Layer* aLayer, const IntRect& aClipRect, return; } - nsIntRegion region = aLayer->GetEffectiveVisibleRegion().ToUnknownRegion(); + nsIntRegion region = aLayer->GetLocalVisibleRegion().ToUnknownRegion(); IntRect r = region.GetBounds(); TransformIntRect(r, transform, ToOutsideIntRect); r.IntersectRect(r, aDirtyRect); @@ -846,7 +861,6 @@ Transform3D(RefPtr aSource, aDestRect.RoundOut(); // Create a surface the size of the transformed object. - RefPtr dest = aDest->CurrentSurface(); RefPtr destImage = new gfxImageSurface(IntSize(aDestRect.width, aDestRect.height), SurfaceFormat::A8R8G8B8_UINT32); @@ -918,7 +932,7 @@ BasicLayerManager::FlushGroup(PaintLayerContext& aPaintContext, bool aNeedsClipT if (!mTransactionIncomplete) { if (aNeedsClipToVisibleRegion) { gfxUtils::ClipToRegion(aPaintContext.mTarget, - aPaintContext.mLayer->GetEffectiveVisibleRegion().ToUnknownRegion()); + aPaintContext.mLayer->GetLocalVisibleRegion().ToUnknownRegion()); } CompositionOp op = GetEffectiveOperator(aPaintContext.mLayer); @@ -1029,7 +1043,7 @@ BasicLayerManager::PaintLayer(gfxContext* aTarget, paintLayerContext.Apply2DTransform(); - const nsIntRegion visibleRegion = aLayer->GetEffectiveVisibleRegion().ToUnknownRegion(); + const nsIntRegion visibleRegion = aLayer->GetLocalVisibleRegion().ToUnknownRegion(); // If needsGroup is true, we'll clip to the visible region after we've popped the group if (needsClipToVisibleRegion && !needsGroup) { gfxUtils::ClipToRegion(aTarget, visibleRegion); @@ -1050,7 +1064,7 @@ BasicLayerManager::PaintLayer(gfxContext* aTarget, if (is2D) { if (needsGroup) { PushedGroup pushedGroup = - PushGroupForLayer(aTarget, aLayer, aLayer->GetEffectiveVisibleRegion().ToUnknownRegion()); + PushGroupForLayer(aTarget, aLayer, aLayer->GetLocalVisibleRegion().ToUnknownRegion()); PaintSelfOrChildren(paintLayerContext, pushedGroup.mGroupTarget); PopGroupForLayer(pushedGroup); } else { diff --git a/gfx/layers/basic/BasicLayers.h b/gfx/layers/basic/BasicLayers.h index 6d6c8c013a..46e8735ee0 100644 --- a/gfx/layers/basic/BasicLayers.h +++ b/gfx/layers/basic/BasicLayers.h @@ -140,7 +140,7 @@ public: struct PushedGroup { - PushedGroup() : mFinalTarget(nullptr), mNeedsClipToVisibleRegion(false) {} + PushedGroup() : mFinalTarget(nullptr), mNeedsClipToVisibleRegion(false), mOperator(gfx::CompositionOp::OP_COUNT), mOpacity(0.0f){} gfxContext* mFinalTarget; RefPtr mGroupTarget; nsIntRegion mVisibleRegion; diff --git a/gfx/layers/basic/BasicPaintedLayer.cpp b/gfx/layers/basic/BasicPaintedLayer.cpp index 69001159ad..551ce97a07 100644 --- a/gfx/layers/basic/BasicPaintedLayer.cpp +++ b/gfx/layers/basic/BasicPaintedLayer.cpp @@ -63,7 +63,7 @@ BasicPaintedLayer::PaintThebes(gfxContext* aContext, mValidRegion.SetEmpty(); mContentClient->Clear(); - nsIntRegion toDraw = IntersectWithClip(GetEffectiveVisibleRegion().ToUnknownRegion(), aContext); + nsIntRegion toDraw = IntersectWithClip(GetLocalVisibleRegion().ToUnknownRegion(), aContext); RenderTraceInvalidateStart(this, "FFFF00", toDraw.GetBounds()); @@ -169,7 +169,7 @@ BasicPaintedLayer::Validate(LayerManager::DrawPaintedLayerCallback aCallback, // from RGB to RGBA, because we might need to repaint with // subpixel AA) state.mRegionToInvalidate.And(state.mRegionToInvalidate, - GetEffectiveVisibleRegion().ToUnknownRegion()); + GetLocalVisibleRegion().ToUnknownRegion()); SetAntialiasingFlags(this, target); RenderTraceInvalidateStart(this, "FFFF00", state.mRegionToDraw.GetBounds()); diff --git a/gfx/layers/basic/GrallocTextureHostBasic.cpp b/gfx/layers/basic/GrallocTextureHostBasic.cpp index 0a571a5575..5591cd17e5 100644 --- a/gfx/layers/basic/GrallocTextureHostBasic.cpp +++ b/gfx/layers/basic/GrallocTextureHostBasic.cpp @@ -82,7 +82,7 @@ NeedsConvertFromYUVtoRGB565(int aHalFormat) GrallocTextureHostBasic::GrallocTextureHostBasic( TextureFlags aFlags, - const NewSurfaceDescriptorGralloc& aDescriptor) + const SurfaceDescriptorGralloc& aDescriptor) : TextureHost(aFlags) , mGrallocHandle(aDescriptor) , mSize(0, 0) diff --git a/gfx/layers/basic/GrallocTextureHostBasic.h b/gfx/layers/basic/GrallocTextureHostBasic.h index b1bcece152..d72f67e86a 100644 --- a/gfx/layers/basic/GrallocTextureHostBasic.h +++ b/gfx/layers/basic/GrallocTextureHostBasic.h @@ -24,7 +24,7 @@ class GrallocTextureHostBasic : public TextureHost { public: GrallocTextureHostBasic(TextureFlags aFlags, - const NewSurfaceDescriptorGralloc& aDescriptor); + const SurfaceDescriptorGralloc& aDescriptor); virtual void SetCompositor(Compositor* aCompositor) override; @@ -66,7 +66,7 @@ public: protected: RefPtr mCompositor; RefPtr mTextureSource; - NewSurfaceDescriptorGralloc mGrallocHandle; + SurfaceDescriptorGralloc mGrallocHandle; // gralloc buffer size. gfx::IntSize mSize; // Size reported by TextureClient, can be different in some cases (video?), diff --git a/gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp index f76e62488a..d3341a200b 100644 --- a/gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp +++ b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp @@ -5,6 +5,7 @@ #include "MacIOSurfaceTextureHostBasic.h" #include "mozilla/gfx/MacIOSurface.h" +#include "MacIOSurfaceHelpers.h" namespace mozilla { namespace layers { @@ -33,7 +34,9 @@ MacIOSurfaceTextureSourceBasic::GetSize() const gfx::SurfaceFormat MacIOSurfaceTextureSourceBasic::GetFormat() const { - return mSurface->HasAlpha() ? gfx::SurfaceFormat::R8G8B8A8 : gfx::SurfaceFormat::B8G8R8X8; + // Set the format the same way as CreateSourceSurfaceFromMacIOSurface. + return mSurface->GetFormat() == gfx::SurfaceFormat::NV12 + ? gfx::SurfaceFormat::B8G8R8X8 : gfx::SurfaceFormat::B8G8R8A8; } MacIOSurfaceTextureHostBasic::MacIOSurfaceTextureHostBasic( @@ -51,7 +54,7 @@ gfx::SourceSurface* MacIOSurfaceTextureSourceBasic::GetSurface(gfx::DrawTarget* aTarget) { if (!mSourceSurface) { - mSourceSurface = mSurface->GetAsSurface(); + mSourceSurface = CreateSourceSurfaceFromMacIOSurface(mSurface); } return mSourceSurface; } diff --git a/gfx/layers/basic/MacIOSurfaceTextureHostBasic.h b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.h index 19a8a70780..3dd6afb468 100644 --- a/gfx/layers/basic/MacIOSurfaceTextureHostBasic.h +++ b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.h @@ -31,6 +31,8 @@ public: MacIOSurface* aSurface); virtual ~MacIOSurfaceTextureSourceBasic(); + virtual const char* Name() const override { return "MacIOSurfaceTextureSourceBasic"; } + virtual TextureSourceBasic* AsSourceBasic() override { return this; } virtual gfx::IntSize GetSize() const override; diff --git a/gfx/layers/basic/TextureHostBasic.cpp b/gfx/layers/basic/TextureHostBasic.cpp index 07949f70a0..3207ffa781 100644 --- a/gfx/layers/basic/TextureHostBasic.cpp +++ b/gfx/layers/basic/TextureHostBasic.cpp @@ -30,9 +30,9 @@ CreateTextureHostBasic(const SurfaceDescriptor& aDesc, } #endif #ifdef MOZ_WIDGET_GONK - if (aDesc.type() == SurfaceDescriptor::TNewSurfaceDescriptorGralloc) { - const NewSurfaceDescriptorGralloc& desc = - aDesc.get_NewSurfaceDescriptorGralloc(); + if (aDesc.type() == SurfaceDescriptor::TSurfaceDescriptorGralloc) { + const SurfaceDescriptorGralloc& desc = + aDesc.get_SurfaceDescriptorGralloc(); return MakeAndAddRef(aFlags, desc); } #endif diff --git a/gfx/layers/basic/X11BasicCompositor.h b/gfx/layers/basic/X11BasicCompositor.h index 4e3cf037d8..48d2f8e81c 100644 --- a/gfx/layers/basic/X11BasicCompositor.h +++ b/gfx/layers/basic/X11BasicCompositor.h @@ -22,6 +22,8 @@ class X11DataTextureSourceBasic : public DataTextureSource public: X11DataTextureSourceBasic() {}; + virtual const char* Name() const override { return "X11DataTextureSourceBasic"; } + virtual bool Update(gfx::DataSourceSurface* aSurface, nsIntRegion* aDestRegion = nullptr, gfx::IntPoint* aSrcOffset = nullptr) override; diff --git a/gfx/layers/basic/X11TextureSourceBasic.h b/gfx/layers/basic/X11TextureSourceBasic.h index 92c9f6c63d..f813560e04 100644 --- a/gfx/layers/basic/X11TextureSourceBasic.h +++ b/gfx/layers/basic/X11TextureSourceBasic.h @@ -24,6 +24,8 @@ class X11TextureSourceBasic public: X11TextureSourceBasic(BasicCompositor* aCompositor, gfxXlibSurface* aSurface); + virtual const char* Name() const override { return "X11TextureSourceBasic"; } + virtual X11TextureSourceBasic* AsSourceBasic() override { return this; } virtual gfx::IntSize GetSize() const override; diff --git a/gfx/layers/client/ClientPaintedLayer.cpp b/gfx/layers/client/ClientPaintedLayer.cpp index 1c590eeeaf..4df9b6bf9b 100644 --- a/gfx/layers/client/ClientPaintedLayer.cpp +++ b/gfx/layers/client/ClientPaintedLayer.cpp @@ -75,7 +75,7 @@ ClientPaintedLayer::PaintThebes() // from RGB to RGBA, because we might need to repaint with // subpixel AA) state.mRegionToInvalidate.And(state.mRegionToInvalidate, - GetEffectiveVisibleRegion().ToUnknownRegion()); + GetLocalVisibleRegion().ToUnknownRegion()); bool didUpdate = false; RotatedContentBuffer::DrawIterator iter; diff --git a/gfx/layers/client/ImageClient.cpp b/gfx/layers/client/ImageClient.cpp index 7286f57a05..785a8d16f1 100644 --- a/gfx/layers/client/ImageClient.cpp +++ b/gfx/layers/client/ImageClient.cpp @@ -30,6 +30,7 @@ #include "nsISupportsImpl.h" // for Image::Release, etc #include "nsRect.h" // for mozilla::gfx::IntRect #include "mozilla/gfx/2D.h" +#include "ClientLayerManager.h" #ifdef MOZ_WIDGET_GONK #include "GrallocImages.h" #endif @@ -155,7 +156,15 @@ ImageClientSingle::UpdateImage(ImageContainer* aContainer, uint32_t aContentFlag OverlayImage* overlayImage = static_cast(image); OverlaySource source; if (overlayImage->GetSidebandStream().IsValid()) { - source.handle() = OverlayHandle(overlayImage->GetSidebandStream()); + // Duplicate GonkNativeHandle::NhObj for ipc, + // since ParamTraits::Write() absorbs native_handle_t. + RefPtr nhObj = overlayImage->GetSidebandStream().GetDupNhObj(); + GonkNativeHandle handle(nhObj); + if (!handle.IsValid()) { + gfxWarning() << "ImageClientSingle::UpdateImage failed in GetDupNhObj"; + return false; + } + source.handle() = OverlayHandle(handle); } else { source.handle() = OverlayHandle(overlayImage->GetOverlayId()); } diff --git a/gfx/layers/client/TextureClient.h b/gfx/layers/client/TextureClient.h index 5021a5deae..a798e9d844 100644 --- a/gfx/layers/client/TextureClient.h +++ b/gfx/layers/client/TextureClient.h @@ -373,8 +373,14 @@ public: */ already_AddRefed GetAsSurface() { Lock(OpenMode::OPEN_READ); - RefPtr surf = BorrowDrawTarget()->Snapshot(); - RefPtr data = surf->GetDataSurface(); + RefPtr data; + RefPtr dt = BorrowDrawTarget(); + if (dt) { + RefPtr surf = dt->Snapshot(); + if (surf) { + data = surf->GetDataSurface(); + } + } Unlock(); return data.forget(); } diff --git a/gfx/layers/composite/AsyncCompositionManager.cpp b/gfx/layers/composite/AsyncCompositionManager.cpp index b54ca0539d..dca83db949 100644 --- a/gfx/layers/composite/AsyncCompositionManager.cpp +++ b/gfx/layers/composite/AsyncCompositionManager.cpp @@ -82,7 +82,7 @@ WalkTheTree(Layer* aLayer, aHasRemote = true; if (const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(ref->GetReferentId())) { if (Layer* referent = state->mRoot) { - if (!ref->GetVisibleRegion().IsEmpty()) { + if (!ref->GetLocalVisibleRegion().IsEmpty()) { dom::ScreenOrientationInternal chromeOrientation = aTargetConfig.orientation(); dom::ScreenOrientationInternal contentOrientation = state->mTargetConfig.orientation(); if (!IsSameDimension(chromeOrientation, contentOrientation) && @@ -690,7 +690,7 @@ AsyncCompositionManager::RecordShadowTransforms(Layer* aLayer) if (!apzc) { continue; } - gfx::Matrix4x4 shadowTransform = aLayer->AsLayerComposite()->GetShadowTransform(); + gfx::Matrix4x4 shadowTransform = aLayer->AsLayerComposite()->GetShadowBaseTransform(); if (!shadowTransform.Is2D()) { continue; } @@ -1423,7 +1423,7 @@ AsyncCompositionManager::TransformShadowTree(TimeStamp aCurrentFrame, LayerComposite* rootComposite = root->AsLayerComposite(); - gfx::Matrix4x4 trans = rootComposite->GetShadowTransform(); + gfx::Matrix4x4 trans = rootComposite->GetShadowBaseTransform(); trans *= gfx::Matrix4x4::From2D(mWorldTransform); rootComposite->SetShadowTransform(trans); diff --git a/gfx/layers/composite/ContainerLayerComposite.cpp b/gfx/layers/composite/ContainerLayerComposite.cpp index b750491fff..66b29d5258 100755 --- a/gfx/layers/composite/ContainerLayerComposite.cpp +++ b/gfx/layers/composite/ContainerLayerComposite.cpp @@ -97,7 +97,7 @@ static void DrawLayerInfo(const RenderTargetIntRect& aClipRect, template static gfx::IntRect ContainerVisibleRect(ContainerT* aContainer) { - gfx::IntRect surfaceRect = aContainer->GetEffectiveVisibleRegion().ToUnknownRegion().GetBounds(); + gfx::IntRect surfaceRect = aContainer->GetLocalVisibleRegion().ToUnknownRegion().GetBounds(); return surfaceRect; } @@ -109,12 +109,12 @@ static void PrintUniformityInfo(Layer* aLayer) } // Don't want to print a log for smaller layers - if (aLayer->GetEffectiveVisibleRegion().GetBounds().width < 300 || - aLayer->GetEffectiveVisibleRegion().GetBounds().height < 300) { + if (aLayer->GetLocalVisibleRegion().GetBounds().width < 300 || + aLayer->GetLocalVisibleRegion().GetBounds().height < 300) { return; } - Matrix4x4 transform = aLayer->AsLayerComposite()->GetShadowTransform(); + Matrix4x4 transform = aLayer->AsLayerComposite()->GetShadowBaseTransform(); if (!transform.Is2D()) { return; } @@ -237,7 +237,7 @@ ContainerRenderVR(ContainerT* aContainer, IntRectToRect(static_cast(layer)->GetBounds()); } else { layerBounds = - IntRectToRect(layer->GetEffectiveVisibleRegion().ToUnknownRegion().GetBounds()); + IntRectToRect(layer->GetLocalVisibleRegion().ToUnknownRegion().GetBounds()); } const gfx::Matrix4x4 childTransform = layer->GetEffectiveTransform(); layerBounds = childTransform.TransformBounds(layerBounds); @@ -569,8 +569,8 @@ RenderLayers(ContainerT* aContainer, if (layerToRender->HasLayerBeenComposited()) { // Composer2D will compose this layer so skip GPU composition - // this time & reset composition flag for next composition phase - layerToRender->SetLayerComposited(false); + // this time. The flag will be reset for the next composition phase + // at the beginning of LayerManagerComposite::Rener(). gfx::IntRect clearRect = layerToRender->GetClearRect(); if (!clearRect.IsEmpty()) { // Clear layer's visible rect on FrameBuffer with transparent pixels @@ -630,7 +630,7 @@ CreateOrRecycleTarget(ContainerT* aContainer, Compositor* compositor = aManager->GetCompositor(); SurfaceInitMode mode = INIT_MODE_CLEAR; gfx::IntRect surfaceRect = ContainerVisibleRect(aContainer); - if (aContainer->GetEffectiveVisibleRegion().GetNumRects() == 1 && + if (aContainer->GetLocalVisibleRegion().GetNumRects() == 1 && (aContainer->GetContentFlags() & Layer::CONTENT_OPAQUE)) { mode = INIT_MODE_NONE; @@ -655,7 +655,7 @@ CreateTemporaryTargetAndCopyFromBackground(ContainerT* aContainer, LayerManagerComposite* aManager) { Compositor* compositor = aManager->GetCompositor(); - gfx::IntRect visibleRect = aContainer->GetEffectiveVisibleRegion().ToUnknownRegion().GetBounds(); + gfx::IntRect visibleRect = aContainer->GetLocalVisibleRegion().ToUnknownRegion().GetBounds(); RefPtr previousTarget = compositor->GetCurrentRenderTarget(); gfx::IntRect surfaceRect = gfx::IntRect(visibleRect.x, visibleRect.y, visibleRect.width, visibleRect.height); @@ -723,7 +723,7 @@ ContainerRender(ContainerT* aContainer, return; } - gfx::Rect visibleRect(aContainer->GetEffectiveVisibleRegion().ToUnknownRegion().GetBounds()); + gfx::Rect visibleRect(aContainer->GetLocalVisibleRegion().ToUnknownRegion().GetBounds()); RefPtr compositor = aManager->GetCompositor(); #ifdef MOZ_DUMP_PAINTING if (gfxEnv::DumpCompositorTextures()) { diff --git a/gfx/layers/composite/LayerManagerComposite.cpp b/gfx/layers/composite/LayerManagerComposite.cpp index 11aee67c2f..2420db26d6 100644 --- a/gfx/layers/composite/LayerManagerComposite.cpp +++ b/gfx/layers/composite/LayerManagerComposite.cpp @@ -217,14 +217,54 @@ IntersectMaybeRects(const Maybe& aRect1, const Maybe& aRect2 return aRect2; } +/** + * Get accumulated transform of from the context creating layer to the + * given layer. + */ +static Matrix4x4 +GetAccTransformIn3DContext(Layer* aLayer) { + Matrix4x4 transform = aLayer->GetLocalTransform(); + for (Layer* layer = aLayer->GetParent(); + layer && layer->Extend3DContext(); + layer = layer->GetParent()) { + transform = transform * layer->GetLocalTransform(); + } + return transform; +} + void LayerManagerComposite::PostProcessLayers(Layer* aLayer, nsIntRegion& aOpaqueRegion, LayerIntRegion& aVisibleRegion, const Maybe& aClipFromAncestors) { + if (aLayer->Extend3DContext()) { + // For layers participating 3D rendering context, their visible + // region should be empty (invisible), so we pass through them + // without doing anything. + + // Direct children of the establisher may have a clip, becaue the + // item containing it; ex. of nsHTMLScrollFrame, may give it one. + Maybe layerClip = + aLayer->AsLayerComposite()->GetShadowClipRect(); + Maybe ancestorClipForChildren = + IntersectMaybeRects(layerClip, aClipFromAncestors); + MOZ_ASSERT(!layerClip || !aLayer->Combines3DTransformWithAncestors(), + "Only direct children of the establisher could have a clip"); + + for (Layer* child = aLayer->GetLastChild(); + child; + child = child->GetPrevSibling()) { + PostProcessLayers(child, aOpaqueRegion, aVisibleRegion, + ancestorClipForChildren); + } + return; + } + nsIntRegion localOpaque; - Matrix4x4 transform = aLayer->GetLocalTransform(); + // Treat layers on the path to the root of the 3D rendering context as + // a giant layer if it is a leaf. + Matrix4x4 transform = GetAccTransformIn3DContext(aLayer); Matrix transform2d; Maybe integerTranslation; // If aLayer has a simple transform (only an integer translation) then we @@ -242,6 +282,9 @@ LayerManagerComposite::PostProcessLayers(Layer* aLayer, // from our ancestors. LayerComposite* composite = aLayer->AsLayerComposite(); Maybe layerClip = composite->GetShadowClipRect(); + MOZ_ASSERT(!layerClip || !aLayer->Combines3DTransformWithAncestors(), + "The layer with a clip should not participate " + "a 3D rendering context"); Maybe outsideClip = IntersectMaybeRects(layerClip, aClipFromAncestors); @@ -282,8 +325,12 @@ LayerManagerComposite::PostProcessLayers(Layer* aLayer, // - They recalculate their visible regions, taking ancestorClipForChildren // into account, and accumulate them into descendantsVisibleRegion. LayerIntRegion descendantsVisibleRegion; + bool hasPreserve3DChild = false; for (Layer* child = aLayer->GetLastChild(); child; child = child->GetPrevSibling()) { PostProcessLayers(child, localOpaque, descendantsVisibleRegion, ancestorClipForChildren); + if (child->Extend3DContext()) { + hasPreserve3DChild = true; + } } // Recalculate our visible region. @@ -291,7 +338,7 @@ LayerManagerComposite::PostProcessLayers(Layer* aLayer, // If we have descendants, throw away the visible region stored on this // layer, and use the region accumulated by our descendants instead. - if (aLayer->GetFirstChild()) { + if (aLayer->GetFirstChild() && !hasPreserve3DChild) { visible = descendantsVisibleRegion; } @@ -422,11 +469,9 @@ LayerManagerComposite::UpdateAndRender() mInvalidRegion.SetEmpty(); } - // Update cached layer tree information. - mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot()); - if (invalid.IsEmpty() && !mWindowOverlayChanged) { // Composition requested, but nothing has changed. Don't do any work. + mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot()); return; } @@ -450,6 +495,9 @@ LayerManagerComposite::UpdateAndRender() #endif mGeometryChanged = false; mWindowOverlayChanged = false; + + // Update cached layer tree information. + mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot()); } already_AddRefed @@ -757,6 +805,21 @@ LayerManagerComposite::PopGroupForLayerEffects(RefPtr a Matrix4x4()); } +// Used to clear the 'mLayerComposited' flag at the beginning of each Render(). +static void +ClearLayerFlags(Layer* aLayer) { + if (!aLayer) { + return; + } + if (aLayer->AsLayerComposite()) { + aLayer->AsLayerComposite()->SetLayerComposited(false); + } + for (Layer* child = aLayer->GetFirstChild(); child; + child = child->GetNextSibling()) { + ClearLayerFlags(child); + } +} + void LayerManagerComposite::Render(const nsIntRegion& aInvalidRegion) { @@ -768,6 +831,8 @@ LayerManagerComposite::Render(const nsIntRegion& aInvalidRegion) return; } + ClearLayerFlags(mRoot); + // At this time, it doesn't really matter if these preferences change // during the execution of the function; we should be safe in all // permutations. However, may as well just get the values onces and @@ -1038,7 +1103,7 @@ LayerManagerComposite::RenderToPresentationSurface() EGLSurface surface = mirrorScreen->GetEGLSurface(); if (surface == LOCAL_EGL_NO_SURFACE) { // Create GLContext - RefPtr gl = gl::GLContextProvider::CreateForWindow(mirrorScreenWidget); + RefPtr gl = gl::GLContextProvider::CreateForWindow(mirrorScreenWidget, false); mirrorScreenWidget->SetNativeData(NS_NATIVE_OPENGL_CONTEXT, reinterpret_cast(gl.get())); surface = mirrorScreen->GetEGLSurface(); @@ -1185,7 +1250,7 @@ LayerManagerComposite::ComputeRenderIntegrityInternal(Layer* aLayer, } // See if there's any incomplete rendering - nsIntRegion incompleteRegion = aLayer->GetEffectiveVisibleRegion().ToUnknownRegion(); + nsIntRegion incompleteRegion = aLayer->GetLocalVisibleRegion().ToUnknownRegion(); incompleteRegion.Sub(incompleteRegion, paintedLayer->GetValidRegion()); if (!incompleteRegion.IsEmpty()) { diff --git a/gfx/layers/composite/LayerManagerComposite.h b/gfx/layers/composite/LayerManagerComposite.h index 2e0a34acd9..c74b7ec278 100644 --- a/gfx/layers/composite/LayerManagerComposite.h +++ b/gfx/layers/composite/LayerManagerComposite.h @@ -480,7 +480,7 @@ public: float GetShadowOpacity() { return mShadowOpacity; } const Maybe& GetShadowClipRect() { return mShadowClipRect; } const LayerIntRegion& GetShadowVisibleRegion() { return mShadowVisibleRegion; } - const gfx::Matrix4x4& GetShadowTransform() { return mShadowTransform; } + const gfx::Matrix4x4& GetShadowBaseTransform() { return mShadowTransform; } bool GetShadowTransformSetByAnimation() { return mShadowTransformSetByAnimation; } bool HasLayerBeenComposited() { return mLayerComposited; } gfx::IntRect GetClearRect() { return mClearRect; } @@ -569,7 +569,7 @@ RenderWithAllMasks(Layer* aLayer, Compositor* aCompositor, // into. The final mask gets rendered into the original render target. // Calculate the size of the intermediate surfaces. - gfx::Rect visibleRect(aLayer->GetEffectiveVisibleRegion().ToUnknownRegion().GetBounds()); + gfx::Rect visibleRect(aLayer->GetLocalVisibleRegion().ToUnknownRegion().GetBounds()); gfx::Matrix4x4 transform = aLayer->GetEffectiveTransform(); // TODO: Use RenderTargetIntRect and TransformBy here gfx::IntRect surfaceRect = diff --git a/gfx/layers/composite/PaintedLayerComposite.cpp b/gfx/layers/composite/PaintedLayerComposite.cpp index 87dd79b3ae..06f09db847 100644 --- a/gfx/layers/composite/PaintedLayerComposite.cpp +++ b/gfx/layers/composite/PaintedLayerComposite.cpp @@ -112,7 +112,7 @@ PaintedLayerComposite::RenderLayer(const gfx::IntRect& aClipRect) mBuffer->GetLayer() == this, "buffer is corrupted"); - const nsIntRegion visibleRegion = GetEffectiveVisibleRegion().ToUnknownRegion(); + const nsIntRegion visibleRegion = GetLocalVisibleRegion().ToUnknownRegion(); #ifdef MOZ_DUMP_PAINTING if (gfxEnv::DumpCompositorTextures()) { diff --git a/gfx/layers/composite/TextureHost.cpp b/gfx/layers/composite/TextureHost.cpp index 78cd74c359..75aa85b30f 100644 --- a/gfx/layers/composite/TextureHost.cpp +++ b/gfx/layers/composite/TextureHost.cpp @@ -212,7 +212,7 @@ TextureHost::Create(const SurfaceDescriptor& aDesc, case SurfaceDescriptor::TSurfaceDescriptorSharedGLTexture: return CreateTextureHostOGL(aDesc, aDeallocator, aFlags); - case SurfaceDescriptor::TNewSurfaceDescriptorGralloc: + case SurfaceDescriptor::TSurfaceDescriptorGralloc: case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: if (aBackend == LayersBackend::LAYERS_OPENGL) { return CreateTextureHostOGL(aDesc, aDeallocator, aFlags); @@ -374,6 +374,13 @@ TextureSource::~TextureSource() MOZ_COUNT_DTOR(TextureSource); } +const char* +TextureSource::Name() const +{ + MOZ_CRASH("TextureSource without class name"); + return "TextureSource"; +} + BufferTextureHost::BufferTextureHost(const BufferDescriptor& aDesc, TextureFlags aFlags) : TextureHost(aFlags) diff --git a/gfx/layers/composite/TextureHost.h b/gfx/layers/composite/TextureHost.h index 4de69a787a..4c4a882246 100644 --- a/gfx/layers/composite/TextureHost.h +++ b/gfx/layers/composite/TextureHost.h @@ -88,6 +88,8 @@ public: virtual ~TextureSource(); + virtual const char* Name() const = 0; + /** * Should be overridden in order to deallocate the data that is associated * with the rendering backend, such as GL textures. @@ -233,6 +235,8 @@ public: : mUpdateSerial(0) {} + virtual const char* Name() const override { return "DataTextureSource"; } + virtual DataTextureSource* AsDataTextureSource() override { return this; } /** @@ -724,6 +728,8 @@ public: {} virtual ~CompositingRenderTarget() {} + virtual const char* Name() const override { return "CompositingRenderTarget"; } + #ifdef MOZ_DUMP_PAINTING virtual already_AddRefed Dump(Compositor* aCompositor) { return nullptr; } #endif diff --git a/gfx/layers/composite/X11TextureHost.h b/gfx/layers/composite/X11TextureHost.h index 4ac586f01b..7c6cc7d72b 100644 --- a/gfx/layers/composite/X11TextureHost.h +++ b/gfx/layers/composite/X11TextureHost.h @@ -21,6 +21,8 @@ public: // Called when the underlying X surface has been changed. // Useful for determining whether to rebind a GLXPixmap to a texture. virtual void Updated() = 0; + + virtual const char* Name() const override { return "X11TextureSource"; } }; // TextureHost for Xlib-backed TextureSources. diff --git a/gfx/layers/d3d11/TextureD3D11.h b/gfx/layers/d3d11/TextureD3D11.h index f4bc9f6958..1c91a3c06e 100644 --- a/gfx/layers/d3d11/TextureD3D11.h +++ b/gfx/layers/d3d11/TextureD3D11.h @@ -253,6 +253,7 @@ public: virtual ~DataTextureSourceD3D11(); + virtual const char* Name() const override { return "DataTextureSourceD3D11"; } // DataTextureSource @@ -408,6 +409,8 @@ public: const gfx::IntPoint& aOrigin, DXGI_FORMAT aFormatOverride = DXGI_FORMAT_UNKNOWN); + virtual const char* Name() const override { return "CompositingRenderTargetD3D11"; } + virtual TextureSourceD3D11* AsSourceD3D11() override { return this; } void BindRenderTarget(ID3D11DeviceContext* aContext); diff --git a/gfx/layers/d3d9/TextureD3D9.h b/gfx/layers/d3d9/TextureD3D9.h index 83a1b0b0a4..4e341463a1 100644 --- a/gfx/layers/d3d9/TextureD3D9.h +++ b/gfx/layers/d3d9/TextureD3D9.h @@ -112,6 +112,8 @@ public: virtual ~DataTextureSourceD3D9(); + virtual const char* Name() const override { return "DataTextureSourceD3D9"; } + // DataTextureSource virtual bool Update(gfx::DataSourceSurface* aSurface, @@ -398,6 +400,8 @@ public: const gfx::IntRect& aRect); virtual ~CompositingRenderTargetD3D9(); + virtual const char* Name() const override { return "CompositingRenderTargetD3D9"; } + virtual TextureSourceD3D9* AsSourceD3D9() override { MOZ_ASSERT(mTexture, diff --git a/gfx/layers/ipc/APZChild.cpp b/gfx/layers/ipc/APZChild.cpp new file mode 100644 index 0000000000..4e5fe7fe28 --- /dev/null +++ b/gfx/layers/ipc/APZChild.cpp @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=4 ts=8 et 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/. */ + +#include "mozilla/layers/APZChild.h" + +#include "mozilla/dom/TabChild.h" +#include "mozilla/layers/APZCCallbackHelper.h" + +namespace mozilla { +namespace layers { + +/** + * There are cases where we try to create the APZChild before the corresponding + * TabChild has been created, we use an observer for the "tab-child-created" + * topic to set the TabChild in the APZChild when it has been created. + */ +class TabChildCreatedObserver : public nsIObserver +{ +public: + TabChildCreatedObserver(APZChild* aAPZChild, const dom::TabId& aTabId) + : mAPZChild(aAPZChild), + mTabId(aTabId) + {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + +private: + virtual ~TabChildCreatedObserver() + {} + + // TabChildCreatedObserver is owned by mAPZChild, and mAPZChild outlives its + // TabChildCreatedObserver, so the raw pointer is fine. + APZChild* mAPZChild; + dom::TabId mTabId; +}; + +NS_IMPL_ISUPPORTS(TabChildCreatedObserver, nsIObserver) + +NS_IMETHODIMP +TabChildCreatedObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + MOZ_ASSERT(strcmp(aTopic, "tab-child-created") == 0); + + nsCOMPtr tabChild(do_QueryInterface(aSubject)); + NS_ENSURE_TRUE(tabChild, NS_ERROR_FAILURE); + + dom::TabChild* browser = static_cast(tabChild.get()); + if (browser->GetTabId() == mTabId) { + mAPZChild->SetBrowser(browser); + } + return NS_OK; +} + +APZChild* +APZChild::Create(const dom::TabId& aTabId) +{ + RefPtr browser = dom::TabChild::FindTabChild(aTabId); + nsAutoPtr apz(new APZChild); + if (browser) { + apz->SetBrowser(browser); + } else { + RefPtr observer = + new TabChildCreatedObserver(apz, aTabId); + nsCOMPtr os = services::GetObserverService(); + if (!os || + NS_FAILED(os->AddObserver(observer, "tab-child-created", false))) { + return nullptr; + } + apz->SetObserver(observer); + } + + return apz.forget(); +} + +APZChild::~APZChild() +{ + if (mObserver) { + nsCOMPtr os = services::GetObserverService(); + os->RemoveObserver(mObserver, "tab-child-created"); + } else { + mBrowser->SetAPZChild(nullptr); + } +} + +bool +APZChild::RecvUpdateFrame(const FrameMetrics& aFrameMetrics) +{ + return mBrowser->UpdateFrame(aFrameMetrics); +} + +bool +APZChild::RecvRequestFlingSnap(const FrameMetrics::ViewID& aScrollId, + const mozilla::CSSPoint& aDestination) +{ + APZCCallbackHelper::RequestFlingSnap(aScrollId, aDestination); + return true; +} + +bool +APZChild::RecvAcknowledgeScrollUpdate(const ViewID& aScrollId, + const uint32_t& aScrollGeneration) +{ + APZCCallbackHelper::AcknowledgeScrollUpdate(aScrollId, aScrollGeneration); + return true; +} + +bool +APZChild::RecvHandleDoubleTap(const CSSPoint& aPoint, + const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid) +{ + mBrowser->HandleDoubleTap(aPoint, aModifiers, aGuid); + return true; +} + +bool +APZChild::RecvHandleSingleTap(const CSSPoint& aPoint, + const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid, + const bool& aCallTakeFocusForClickFromTap) +{ + mBrowser->HandleSingleTap(aPoint, aModifiers, aGuid, + aCallTakeFocusForClickFromTap); + return true; +} + +bool +APZChild::RecvHandleLongTap(const CSSPoint& aPoint, + const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) +{ + mBrowser->HandleLongTap(aPoint, aModifiers, aGuid, aInputBlockId); + return true; +} + +bool +APZChild::RecvNotifyAPZStateChange(const ViewID& aViewId, + const APZStateChange& aChange, + const int& aArg) +{ + return mBrowser->NotifyAPZStateChange(aViewId, aChange, aArg); +} + +bool +APZChild::RecvNotifyFlushComplete() +{ + APZCCallbackHelper::NotifyFlushComplete(); + return true; +} + +void +APZChild::SetObserver(nsIObserver* aObserver) +{ + MOZ_ASSERT(!mBrowser); + mObserver = aObserver; +} + +void +APZChild::SetBrowser(dom::TabChild* aBrowser) +{ + MOZ_ASSERT(!mBrowser); + if (mObserver) { + nsCOMPtr os = services::GetObserverService(); + os->RemoveObserver(mObserver, "tab-child-created"); + mObserver = nullptr; + } + mBrowser = aBrowser; + mBrowser->SetAPZChild(this); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/APZChild.h b/gfx/layers/ipc/APZChild.h new file mode 100644 index 0000000000..1c01c03702 --- /dev/null +++ b/gfx/layers/ipc/APZChild.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=4 ts=8 et 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/. */ + +#ifndef mozilla_layers_APZChild_h +#define mozilla_layers_APZChild_h + +#include "mozilla/layers/PAPZChild.h" + +class nsIObserver; + +namespace mozilla { + +namespace dom { +class TabChild; +} // namespace dom + +namespace layers { + +class APZChild final : public PAPZChild +{ +public: + static APZChild* Create(const dom::TabId& aTabId); + + ~APZChild(); + + virtual bool RecvUpdateFrame(const FrameMetrics& frame) override; + + virtual bool RecvRequestFlingSnap(const ViewID& aScrollID, + const CSSPoint& aDestination) override; + + virtual bool RecvAcknowledgeScrollUpdate(const ViewID& aScrollId, + const uint32_t& aScrollGeneration) override; + + virtual bool RecvHandleDoubleTap(const CSSPoint& aPoint, + const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid) override; + + virtual bool RecvHandleSingleTap(const CSSPoint& aPoint, + const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid, + const bool& aCallTakeFocusForClickFromTap) override; + + virtual bool RecvHandleLongTap(const CSSPoint& aPoint, + const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) override; + + virtual bool RecvNotifyAPZStateChange(const ViewID& aViewId, + const APZStateChange& aChange, + const int& aArg) override; + + virtual bool RecvNotifyFlushComplete() override; + + void SetBrowser(dom::TabChild* aBrowser); + +private: + APZChild() {}; + + void SetObserver(nsIObserver* aObserver); + + RefPtr mBrowser; + RefPtr mObserver; +}; + +} // namespace layers + +} // namespace mozilla + +#endif // mozilla_layers_APZChild_h diff --git a/gfx/layers/ipc/CompositorParent.cpp b/gfx/layers/ipc/CompositorParent.cpp index 1a1d9b0643..087b70d402 100644 --- a/gfx/layers/ipc/CompositorParent.cpp +++ b/gfx/layers/ipc/CompositorParent.cpp @@ -25,6 +25,7 @@ #include "mozilla/AutoRestore.h" // for AutoRestore #include "mozilla/ClearOnShutdown.h" // for ClearOnShutdown #include "mozilla/DebugOnly.h" // for DebugOnly +#include "mozilla/dom/ContentParent.h" #include "mozilla/gfx/2D.h" // for DrawTarget #include "mozilla/gfx/Point.h" // for IntSize #include "mozilla/gfx/Rect.h" // for IntSize @@ -43,7 +44,9 @@ #include "mozilla/layers/LayerManagerComposite.h" #include "mozilla/layers/LayersTypes.h" #include "mozilla/layers/PLayerTransactionParent.h" +#include "mozilla/layers/RemoteContentController.h" #include "mozilla/layers/ShadowLayersManager.h" // for ShadowLayersManager +#include "mozilla/layout/RenderFrameParent.h" #include "mozilla/media/MediaSystemResourceService.h" // for MediaSystemResourceService #include "mozilla/mozalloc.h" // for operator new, etc #include "mozilla/Telemetry.h" @@ -75,6 +78,11 @@ #ifdef MOZ_WIDGET_GONK #include "GeckoTouchDispatcher.h" +#include "nsScreenManagerGonk.h" +#endif + +#ifdef MOZ_ANDROID_APZ +#include "AndroidBridge.h" #endif #include "LayerScope.h" @@ -286,13 +294,20 @@ CompositorVsyncScheduler::Observer::Destroy() CompositorVsyncScheduler::CompositorVsyncScheduler(CompositorParent* aCompositorParent, nsIWidget* aWidget) : mCompositorParent(aCompositorParent) , mLastCompose(TimeStamp::Now()) - , mCurrentCompositeTask(nullptr) , mIsObservingVsync(false) , mNeedsComposite(0) , mVsyncNotificationsSkipped(0) , mCurrentCompositeTaskMonitor("CurrentCompositeTaskMonitor") + , mCurrentCompositeTask(nullptr) , mSetNeedsCompositeMonitor("SetNeedsCompositeMonitor") , mSetNeedsCompositeTask(nullptr) +#ifdef MOZ_WIDGET_GONK +#if ANDROID_VERSION >= 19 + , mDisplayEnabled(false) + , mSetDisplayMonitor("SetDisplayMonitor") + , mSetDisplayTask(nullptr) +#endif +#endif { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aWidget != nullptr); @@ -300,6 +315,11 @@ CompositorVsyncScheduler::CompositorVsyncScheduler(CompositorParent* aCompositor mCompositorVsyncDispatcher = aWidget->GetCompositorVsyncDispatcher(); #ifdef MOZ_WIDGET_GONK GeckoTouchDispatcher::GetInstance()->SetCompositorVsyncScheduler(this); + +#if ANDROID_VERSION >= 19 + RefPtr screenManager = nsScreenManagerGonk::GetInstance(); + screenManager->SetCompositorVsyncScheduler(this); +#endif #endif // mAsapScheduling is set on the main thread during init, @@ -317,6 +337,54 @@ CompositorVsyncScheduler::~CompositorVsyncScheduler() mCompositorVsyncDispatcher = nullptr; } +#ifdef MOZ_WIDGET_GONK +#if ANDROID_VERSION >= 19 +void +CompositorVsyncScheduler::SetDisplay(bool aDisplayEnable) +{ + // SetDisplay() is usually called from nsScreenManager at main thread. Post + // to compositor thread if needs. + if (!CompositorParent::IsInCompositorThread()) { + MOZ_ASSERT(NS_IsMainThread()); + MonitorAutoLock lock(mSetDisplayMonitor); + mSetDisplayTask = NewRunnableMethod(this, + &CompositorVsyncScheduler::SetDisplay, + aDisplayEnable); + ScheduleTask(mSetDisplayTask, 0); + return; + } else { + MonitorAutoLock lock(mSetDisplayMonitor); + mSetDisplayTask = nullptr; + } + + if (mDisplayEnabled == aDisplayEnable) { + return; + } + + mDisplayEnabled = aDisplayEnable; + if (!mDisplayEnabled) { + CancelCurrentSetNeedsCompositeTask(); + CancelCurrentCompositeTask(); + } +} + +void +CompositorVsyncScheduler::CancelSetDisplayTask() +{ + MOZ_ASSERT(CompositorParent::IsInCompositorThread()); + MonitorAutoLock lock(mSetDisplayMonitor); + if (mSetDisplayTask) { + mSetDisplayTask->Cancel(); + mSetDisplayTask = nullptr; + } + + // CancelSetDisplayTask is only be called in clean-up process, so + // mDisplayEnabled could be false there. + mDisplayEnabled = false; +} +#endif //ANDROID_VERSION >= 19 +#endif //MOZ_WIDGET_GONK + void CompositorVsyncScheduler::Destroy() { @@ -324,6 +392,12 @@ CompositorVsyncScheduler::Destroy() UnobserveVsync(); mVsyncObserver->Destroy(); mVsyncObserver = nullptr; + +#ifdef MOZ_WIDGET_GONK +#if ANDROID_VERSION >= 19 + CancelSetDisplayTask(); +#endif +#endif CancelCurrentSetNeedsCompositeTask(); CancelCurrentCompositeTask(); } @@ -396,6 +470,15 @@ CompositorVsyncScheduler::SetNeedsComposite() mSetNeedsCompositeTask = nullptr; } +#ifdef MOZ_WIDGET_GONK +#if ANDROID_VERSION >= 19 + // Skip composition when display off. + if (!mDisplayEnabled) { + return; + } +#endif +#endif + mNeedsComposite++; if (!mIsObservingVsync && mNeedsComposite) { ObserveVsync(); @@ -588,6 +671,7 @@ CompositorParent::CompositorParent(nsIWidget* aWidget, , mLastPluginUpdateLayerTreeId(0) , mPluginUpdateResponsePending(false) , mDeferPluginWindows(false) + , mPluginWindowsHidden(false) #endif { MOZ_ASSERT(NS_IsMainThread()); @@ -801,7 +885,7 @@ CompositorParent::Invalidate() { if (mLayerManager && mLayerManager->GetRoot()) { mLayerManager->AddInvalidRegion( - mLayerManager->GetRoot()->GetVisibleRegion().ToUnknownRegion().GetBounds()); + mLayerManager->GetRoot()->GetLocalVisibleRegion().ToUnknownRegion().GetBounds()); } } @@ -1568,23 +1652,57 @@ CompositorParent::RecvNotifyChildCreated(const uint64_t& child) } void -CompositorParent::NotifyChildCreated(const uint64_t& aChild) +CompositorParent::NotifyChildCreated(uint64_t aChild) { sIndirectLayerTreesLock->AssertCurrentThreadOwns(); sIndirectLayerTrees[aChild].mParent = this; sIndirectLayerTrees[aChild].mLayerManager = mLayerManager; } +/* static */ bool +CompositorParent::UpdateRemoteContentController(uint64_t aLayersId, + dom::ContentParent* aContent, + const dom::TabId& aTabId, + dom::TabParent* aTopLevel) +{ + MOZ_ASSERT(NS_IsMainThread()); + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + LayerTreeState& state = sIndirectLayerTrees[aLayersId]; + // RemoteContentController needs to know the layers id and the top level + // TabParent, so we pass that to its constructor here and then set up the + // PAPZ protocol by calling SendPAPZConstructor (and pass in the tab id for + // the PBrowser that it corresponds to). + RefPtr controller = + new RemoteContentController(aLayersId, aTopLevel); + if (!aContent->SendPAPZConstructor(controller, aTabId)) { + return false; + } + state.mController = controller; + return true; +} + bool CompositorParent::RecvAdoptChild(const uint64_t& child) { - MonitorAutoLock lock(*sIndirectLayerTreesLock); - NotifyChildCreated(child); - if (sIndirectLayerTrees[child].mLayerTree) { - sIndirectLayerTrees[child].mLayerTree->mLayerManager = mLayerManager; + RefPtr controller; + { + MonitorAutoLock lock(*sIndirectLayerTreesLock); + NotifyChildCreated(child); + if (sIndirectLayerTrees[child].mLayerTree) { + sIndirectLayerTrees[child].mLayerTree->mLayerManager = mLayerManager; + } + if (sIndirectLayerTrees[child].mRoot) { + sIndirectLayerTrees[child].mRoot->AsLayerComposite()->SetLayerManager(mLayerManager); + } + controller = sIndirectLayerTrees[child].mController; } - if (sIndirectLayerTrees[child].mRoot) { - sIndirectLayerTrees[child].mRoot->AsLayerComposite()->SetLayerManager(mLayerManager); + + // Calling ChildAdopted on controller will acquire a lock, to avoid a + // potential deadlock between that lock and sIndirectLayerTreesLock we + // release sIndirectLayerTreesLock first before calling ChildAdopted. + if (mApzcTreeManager && controller) { + controller->ChildAdopted(); } return true; } @@ -2134,6 +2252,14 @@ CompositorParent::UpdatePluginWindowState(uint64_t aId) return false; } + // If the plugin windows were hidden but now are not, we need to force + // update the metrics to make sure they are visible again. + if (mPluginWindowsHidden) { + PLUGINS_LOG("[%" PRIu64 "] re-showing", aId); + mPluginWindowsHidden = false; + pluginMetricsChanged = true; + } + if (!lts.mPluginData.Length()) { // We will pass through here in cases where the previous shadow layer // tree contained visible plugins and the new tree does not. All we need @@ -2224,6 +2350,7 @@ CompositorParent::HideAllPluginWindows() } mDeferPluginWindows = true; mPluginUpdateResponsePending = true; + mPluginWindowsHidden = true; Unused << SendHideAllPlugins((uintptr_t)GetWidget()); ScheduleComposition(); } diff --git a/gfx/layers/ipc/CompositorParent.h b/gfx/layers/ipc/CompositorParent.h index 5bb56b7cff..470c5b5149 100644 --- a/gfx/layers/ipc/CompositorParent.h +++ b/gfx/layers/ipc/CompositorParent.h @@ -25,6 +25,7 @@ #include "mozilla/Monitor.h" // for Monitor #include "mozilla/RefPtr.h" // for RefPtr #include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/dom/ipc/IdType.h" #include "mozilla/gfx/Point.h" // for IntSize #include "mozilla/ipc/ProtocolUtils.h" #include "mozilla/layers/GeckoContentController.h" @@ -54,6 +55,7 @@ class Compositor; class CompositorParent; class LayerManagerComposite; class LayerTransactionParent; +class PAPZParent; struct ScopedLayerTreeRegistration { @@ -100,6 +102,20 @@ class CompositorVsyncScheduler public: explicit CompositorVsyncScheduler(CompositorParent* aCompositorParent, nsIWidget* aWidget); + +#ifdef MOZ_WIDGET_GONK + // emulator-ics never trigger the display on/off, so compositor will always + // skip composition request at that device. Only check the display status + // with kk device and upon. +#if ANDROID_VERSION >= 19 + // SetDisplay() and CancelSetDisplayTask() are used for the display on/off. + // It will clear all composition related task and flag, and skip another + // composition task during the display off. That could prevent the problem + // that compositor might show the old content at the first frame of display on. + void SetDisplay(bool aDisplayEnable); +#endif +#endif + bool NotifyVsync(TimeStamp aVsyncTimestamp); void SetNeedsComposite(); void OnForceComposeToTarget(); @@ -126,7 +142,7 @@ public: return mExpectedComposeStartTime; } #endif - + private: virtual ~CompositorVsyncScheduler(); @@ -136,6 +152,11 @@ private: void DispatchTouchEvents(TimeStamp aVsyncTimestamp); void DispatchVREvents(TimeStamp aVsyncTimestamp); void CancelCurrentSetNeedsCompositeTask(); +#ifdef MOZ_WIDGET_GONK +#if ANDROID_VERSION >= 19 + void CancelSetDisplayTask(); +#endif +#endif class Observer final : public VsyncObserver { @@ -153,7 +174,6 @@ private: CompositorParent* mCompositorParent; TimeStamp mLastCompose; - CancelableTask* mCurrentCompositeTask; #ifdef COMPOSITOR_PERFORMANCE_WARNING TimeStamp mExpectedComposeStartTime; @@ -167,9 +187,18 @@ private: RefPtr mVsyncObserver; mozilla::Monitor mCurrentCompositeTaskMonitor; + CancelableTask* mCurrentCompositeTask; mozilla::Monitor mSetNeedsCompositeMonitor; CancelableTask* mSetNeedsCompositeTask; + +#ifdef MOZ_WIDGET_GONK +#if ANDROID_VERSION >= 19 + bool mDisplayEnabled; + mozilla::Monitor mSetDisplayMonitor; + CancelableTask* mSetDisplayTask; +#endif +#endif }; class CompositorUpdateObserver @@ -256,7 +285,7 @@ public: static void SetShadowProperties(Layer* aLayer); - void NotifyChildCreated(const uint64_t& aChild); + void NotifyChildCreated(uint64_t aChild); void AsyncRender(); @@ -418,6 +447,22 @@ public: nsIWidget* GetWidget() { return mWidget; } + void ForceComposeToTarget(gfx::DrawTarget* aTarget, const gfx::IntRect* aRect = nullptr); + + /** + * Creates a new RemoteContentController for aTabId. Should only be called on + * the main thread. + * + * aLayersId The layers id for the browser corresponding to aTabId. + * aContentParent The ContentParent for the process that the TabChild for + * aTabId lives in. + * aBrowserParent The toplevel TabParent for aTabId. + */ + static bool UpdateRemoteContentController(uint64_t aLayersId, + dom::ContentParent* aContentParent, + const dom::TabId& aTabId, + dom::TabParent* aBrowserParent); + protected: // Protected destructor, to discourage deletion outside of Release(): virtual ~CompositorParent(); @@ -432,7 +477,6 @@ protected: virtual bool DeallocPLayerTransactionParent(PLayerTransactionParent* aLayers) override; virtual void ScheduleTask(CancelableTask*, int); void CompositeToTarget(gfx::DrawTarget* aTarget, const gfx::IntRect* aRect = nullptr); - void ForceComposeToTarget(gfx::DrawTarget* aTarget, const gfx::IntRect* aRect = nullptr); void SetEGLSurfaceSize(int width, int height); @@ -501,6 +545,9 @@ protected: // indicates if plugin window visibility and metric updates are currently // being defered due to a scroll operation. bool mDeferPluginWindows; + // indicates if the plugin windows were hidden, and need to be made + // visible again even if their geometry has not changed. + bool mPluginWindowsHidden; #endif DISALLOW_EVIL_CONSTRUCTORS(CompositorParent); diff --git a/gfx/layers/ipc/LayerTransactionParent.cpp b/gfx/layers/ipc/LayerTransactionParent.cpp index a422891c79..e6c0def0cb 100644 --- a/gfx/layers/ipc/LayerTransactionParent.cpp +++ b/gfx/layers/ipc/LayerTransactionParent.cpp @@ -725,7 +725,7 @@ LayerTransactionParent::RecvGetAnimationTransform(PLayerParent* aParent, // from the shadow transform by undoing the translations in // AsyncCompositionManager::SampleValue. - Matrix4x4 transform = layer->AsLayerComposite()->GetShadowTransform(); + Matrix4x4 transform = layer->AsLayerComposite()->GetShadowBaseTransform(); if (ContainerLayer* c = layer->AsContainerLayer()) { // Undo the scale transform applied by AsyncCompositionManager::SampleValue transform.PostScale(1.0f/c->GetInheritedXScale(), diff --git a/gfx/layers/ipc/LayersSurfaces.ipdlh b/gfx/layers/ipc/LayersSurfaces.ipdlh index 6db0d7835d..d206f4b4e1 100644 --- a/gfx/layers/ipc/LayersSurfaces.ipdlh +++ b/gfx/layers/ipc/LayersSurfaces.ipdlh @@ -93,7 +93,7 @@ struct SurfaceDescriptorSharedGLTexture { bool hasAlpha; }; -struct NewSurfaceDescriptorGralloc { +struct SurfaceDescriptorGralloc { MaybeMagicGrallocBufferHandle buffer; bool isOpaque; }; @@ -138,7 +138,7 @@ union SurfaceDescriptor { SurfaceTextureDescriptor; EGLImageDescriptor; SurfaceDescriptorMacIOSurface; - NewSurfaceDescriptorGralloc; + SurfaceDescriptorGralloc; SurfaceDescriptorSharedGLTexture; null_t; }; diff --git a/gfx/layers/ipc/PAPZ.ipdl b/gfx/layers/ipc/PAPZ.ipdl new file mode 100644 index 0000000000..35de6602a8 --- /dev/null +++ b/gfx/layers/ipc/PAPZ.ipdl @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* 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/. */ + +include "mozilla/GfxMessageUtils.h"; + +include protocol PContent; + +using mozilla::CSSPoint from "Units.h"; +using CSSRect from "Units.h"; +using struct mozilla::layers::FrameMetrics from "FrameMetrics.h"; +using struct mozilla::layers::ScrollableLayerGuid from "FrameMetrics.h"; +using mozilla::layers::FrameMetrics::ViewID from "FrameMetrics.h"; +using mozilla::layers::MaybeZoomConstraints from "FrameMetrics.h"; +using mozilla::layers::TouchBehaviorFlags from "mozilla/layers/APZUtils.h"; +using mozilla::layers::GeckoContentController::APZStateChange from "mozilla/layers/GeckoContentController.h"; +using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h"; +using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h"; +using mozilla::layers::AsyncDragMetrics from "mozilla/layers/AsyncDragMetrics.h"; +using mozilla::Modifiers from "mozilla/EventForwards.h"; +using class nsRegion from "nsRegion.h"; + +namespace mozilla { +namespace layers { + + +/** + * If APZ is enabled then one PAPZ will be opened per PBrowser between the + * process where the PBrowser child actor lives and the main process (the + * PBrowser parent actor doesn't necessarily live in the main process, for + * example with nested browsers). This will typically be set up when the layers + * id is allocated for the PBrowser. + * + * Opened through PContent and runs on the main thread in both parent and child. + */ +sync protocol PAPZ +{ + manager PContent; + +parent: + async UpdateHitRegion(nsRegion aRegion); + + /** + * Instructs the TabParent to forward a request to zoom to a rect given in + * CSS pixels. This rect is relative to the document. + */ + async ZoomToRect(uint32_t aPresShellId, ViewID aViewId, CSSRect aRect, uint32_t aFlags); + + /** + * We know for sure that content has either preventDefaulted or not + * preventDefaulted. This applies to an entire batch of touch events. It is + * expected that, if there are any DOM touch listeners, touch events will be + * batched and only processed for panning and zooming if content does not + * preventDefault. + */ + async ContentReceivedInputBlock(ScrollableLayerGuid aGuid, uint64_t aInputBlockId, bool aPreventDefault); + + /** + * Notifies the APZ code of the results of the gecko hit-test for a + * particular input block. Each target corresponds to one touch point in the + * touch event. + */ + async SetTargetAPZC(uint64_t aInputBlockId, ScrollableLayerGuid[] aTargets); + + // Start an APZ drag on a scrollbar + async StartScrollbarDrag(AsyncDragMetrics aDragMetrics); + + /** + * Notifies the APZ code of the allowed touch-behaviours for a particular + * input block. Each item in the aFlags array corresponds to one touch point + * in the touch event. + */ + async SetAllowedTouchBehavior(uint64_t aInputBlockId, TouchBehaviorFlags[] aFlags); + + /** + * Updates the zoom constraints for a scrollable frame in this tab. + * The zoom controller code lives on the parent side and so this allows it to + * have up-to-date zoom constraints. + */ + async UpdateZoomConstraints(uint32_t aPresShellId, ViewID aViewId, + MaybeZoomConstraints aConstraints); + +child: + async UpdateFrame(FrameMetrics frame); + + // The following methods correspond to functions on the GeckoContentController + // interface in gfx/layers/apz/public/GeckoContentController.h. Refer to documentation + // in that file for these functions. + async RequestFlingSnap(ViewID aScrollID, CSSPoint aDestination); + async AcknowledgeScrollUpdate(ViewID aScrollId, uint32_t aScrollGeneration); + async HandleDoubleTap(CSSPoint aPoint, Modifiers aModifiers, ScrollableLayerGuid aGuid); + async HandleSingleTap(CSSPoint aPoint, Modifiers aModifiers, ScrollableLayerGuid aGuid, bool aCallTakeFocusForClickFromTap); + async HandleLongTap(CSSPoint point, Modifiers aModifiers, ScrollableLayerGuid aGuid, uint64_t aInputBlockId); + async NotifyAPZStateChange(ViewID aViewId, APZStateChange aChange, int aArg); + async NotifyFlushComplete(); + + async __delete__(); +}; + +} // layers +} // mozilla diff --git a/gfx/layers/ipc/PCompositor.ipdl b/gfx/layers/ipc/PCompositor.ipdl index c124db4c0a..94e44e5ffa 100644 --- a/gfx/layers/ipc/PCompositor.ipdl +++ b/gfx/layers/ipc/PCompositor.ipdl @@ -7,16 +7,18 @@ include LayersSurfaces; include LayersMessages; +include protocol PBrowser; include protocol PLayer; include protocol PLayerTransaction; include "mozilla/GfxMessageUtils.h"; -include "nsRegion.h"; using struct mozilla::null_t from "ipc/IPCMessageUtils.h"; using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h"; using struct mozilla::layers::FrameMetrics from "FrameMetrics.h"; using mozilla::layers::FrameMetrics::ViewID from "FrameMetrics.h"; +using mozilla::layers::MaybeZoomConstraints from "FrameMetrics.h"; using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::TouchBehaviorFlags from "mozilla/layers/APZUtils.h"; using mozilla::CrossProcessMutexHandle from "mozilla/ipc/CrossProcessMutex.h"; using mozilla::ipc::SharedMemoryBasic::Handle from "mozilla/ipc/SharedMemoryBasic.h"; using mozilla::LayoutDeviceIntPoint from "Units.h"; diff --git a/gfx/layers/ipc/RemoteContentController.cpp b/gfx/layers/ipc/RemoteContentController.cpp new file mode 100644 index 0000000000..3c176d1393 --- /dev/null +++ b/gfx/layers/ipc/RemoteContentController.cpp @@ -0,0 +1,384 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* 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/. */ + +#include "mozilla/layers/RemoteContentController.h" + +#include "base/message_loop.h" +#include "base/task.h" +#include "MainThreadUtils.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/TabParent.h" +#include "mozilla/layers/APZCTreeManager.h" +#include "mozilla/layers/APZThreadUtils.h" +#include "mozilla/layers/CompositorParent.h" +#include "mozilla/layout/RenderFrameParent.h" +#include "mozilla/unused.h" +#include "Units.h" +#ifdef MOZ_WIDGET_ANDROID +#include "AndroidBridge.h" +#endif + +namespace mozilla { +namespace layers { + +RemoteContentController::RemoteContentController(uint64_t aLayersId, + dom::TabParent* aBrowserParent) + : mUILoop(MessageLoop::current()) + , mLayersId(aLayersId) + , mBrowserParent(aBrowserParent) + , mMutex("RemoteContentController") +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +RemoteContentController::~RemoteContentController() +{ + if (mBrowserParent) { + Unused << PAPZParent::Send__delete__(this); + } +} + +void +RemoteContentController::RequestContentRepaint(const FrameMetrics& aFrameMetrics) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (CanSend()) { + Unused << SendUpdateFrame(aFrameMetrics); + } +} + +void +RemoteContentController::RequestFlingSnap(const FrameMetrics::ViewID& aScrollId, + const mozilla::CSSPoint& aDestination) +{ + if (MessageLoop::current() != mUILoop) { + // We have to send this message from the "UI thread" (main + // thread). + mUILoop->PostTask( + FROM_HERE, + NewRunnableMethod(this, &RemoteContentController::RequestFlingSnap, + aScrollId, aDestination)); + return; + } + if (CanSend()) { + Unused << SendRequestFlingSnap(aScrollId, aDestination); + } +} + +void +RemoteContentController::AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId, + const uint32_t& aScrollGeneration) +{ + if (MessageLoop::current() != mUILoop) { + // We have to send this message from the "UI thread" (main + // thread). + mUILoop->PostTask( + FROM_HERE, + NewRunnableMethod(this, &RemoteContentController::AcknowledgeScrollUpdate, + aScrollId, aScrollGeneration)); + return; + } + if (CanSend()) { + Unused << SendAcknowledgeScrollUpdate(aScrollId, aScrollGeneration); + } +} + +void +RemoteContentController::HandleDoubleTap(const CSSPoint& aPoint, + Modifiers aModifiers, + const ScrollableLayerGuid& aGuid) +{ + if (MessageLoop::current() != mUILoop) { + // We have to send this message from the "UI thread" (main + // thread). + mUILoop->PostTask( + FROM_HERE, + NewRunnableMethod(this, &RemoteContentController::HandleDoubleTap, + aPoint, aModifiers, aGuid)); + return; + } + if (CanSend()) { + Unused << SendHandleDoubleTap(aPoint, aModifiers, aGuid); + } +} + +void +RemoteContentController::HandleSingleTap(const CSSPoint& aPoint, + Modifiers aModifiers, + const ScrollableLayerGuid& aGuid) +{ + if (MessageLoop::current() != mUILoop) { + // We have to send this message from the "UI thread" (main + // thread). + mUILoop->PostTask( + FROM_HERE, + NewRunnableMethod(this, &RemoteContentController::HandleSingleTap, + aPoint, aModifiers, aGuid)); + return; + } + + bool callTakeFocusForClickFromTap; + layout::RenderFrameParent* frame; + if (mBrowserParent && (frame = mBrowserParent->GetRenderFrame()) && + mLayersId == frame->GetLayersId()) { + // Avoid going over IPC and back for calling TakeFocusForClickFromTap, + // since the right RenderFrameParent is living in this process. + frame->TakeFocusForClickFromTap(); + callTakeFocusForClickFromTap = false; + } else { + callTakeFocusForClickFromTap = true; + } + + if (CanSend()) { + Unused << SendHandleSingleTap(aPoint, aModifiers, aGuid, + callTakeFocusForClickFromTap); + } +} + +void +RemoteContentController::HandleLongTap(const CSSPoint& aPoint, + Modifiers aModifiers, + const ScrollableLayerGuid& aGuid, + uint64_t aInputBlockId) +{ + if (MessageLoop::current() != mUILoop) { + // We have to send this message from the "UI thread" (main + // thread). + mUILoop->PostTask( + FROM_HERE, + NewRunnableMethod(this, &RemoteContentController::HandleLongTap, + aPoint, aModifiers, aGuid, aInputBlockId)); + return; + } + if (CanSend()) { + Unused << SendHandleLongTap(aPoint, aModifiers, aGuid, aInputBlockId); + } +} + +void +RemoteContentController::PostDelayedTask(Task* aTask, int aDelayMs) +{ +#ifdef MOZ_ANDROID_APZ + AndroidBridge::Bridge()->PostTaskToUiThread(aTask, aDelayMs); +#else + (MessageLoop::current() ? MessageLoop::current() : mUILoop)-> + PostDelayedTask(FROM_HERE, aTask, aDelayMs); +#endif +} + +bool +RemoteContentController::GetTouchSensitiveRegion(CSSRect* aOutRegion) +{ + MutexAutoLock lock(mMutex); + if (mTouchSensitiveRegion.IsEmpty()) { + return false; + } + + *aOutRegion = CSSRect::FromAppUnits(mTouchSensitiveRegion.GetBounds()); + return true; +} + +void +RemoteContentController::NotifyAPZStateChange(const ScrollableLayerGuid& aGuid, + APZStateChange aChange, + int aArg) +{ + if (MessageLoop::current() != mUILoop) { + mUILoop->PostTask( + FROM_HERE, + NewRunnableMethod(this, &RemoteContentController::NotifyAPZStateChange, + aGuid, aChange, aArg)); + return; + } + if (CanSend()) { + Unused << SendNotifyAPZStateChange(aGuid.mScrollId, aChange, aArg); + } +} + +void +RemoteContentController::NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, + const nsString& aEvent) +{ + if (MessageLoop::current() != mUILoop) { + mUILoop->PostTask( + FROM_HERE, + NewRunnableMethod(this, &RemoteContentController::NotifyMozMouseScrollEvent, + aScrollId, aEvent)); + return; + } + + if (mBrowserParent) { + Unused << mBrowserParent->SendMouseScrollTestEvent(mLayersId, aScrollId, aEvent); + } +} + +void +RemoteContentController::NotifyFlushComplete() +{ + MOZ_ASSERT(NS_IsMainThread()); + if (CanSend()) { + Unused << SendNotifyFlushComplete(); + } +} + +bool +RemoteContentController::RecvUpdateHitRegion(const nsRegion& aRegion) +{ + MutexAutoLock lock(mMutex); + mTouchSensitiveRegion = aRegion; + return true; +} + +bool +RemoteContentController::RecvZoomToRect(const uint32_t& aPresShellId, + const ViewID& aViewId, + const CSSRect& aRect, + const uint32_t& aFlags) +{ + if (RefPtr apzcTreeManager = GetApzcTreeManager()) { + apzcTreeManager->ZoomToRect(ScrollableLayerGuid(mLayersId, aPresShellId, aViewId), + aRect, aFlags); + } + return true; +} + +bool +RemoteContentController::RecvContentReceivedInputBlock(const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, + const bool& aPreventDefault) +{ + if (aGuid.mLayersId != mLayersId) { + // Guard against bad data from hijacked child processes + NS_ERROR("Unexpected layers id in RecvContentReceivedInputBlock; dropping message..."); + return false; + } + if (RefPtr apzcTreeManager = GetApzcTreeManager()) { + APZThreadUtils::RunOnControllerThread(NewRunnableMethod( + apzcTreeManager.get(), &APZCTreeManager::ContentReceivedInputBlock, + aInputBlockId, aPreventDefault)); + } + return true; +} + +bool +RemoteContentController::RecvStartScrollbarDrag(const AsyncDragMetrics& aDragMetrics) +{ + if (RefPtr apzcTreeManager = GetApzcTreeManager()) { + ScrollableLayerGuid guid(mLayersId, aDragMetrics.mPresShellId, + aDragMetrics.mViewId); + + APZThreadUtils::RunOnControllerThread( + NewRunnableMethod(apzcTreeManager.get(), + &APZCTreeManager::StartScrollbarDrag, + guid, aDragMetrics)); + } + return true; +} + +bool +RemoteContentController::RecvSetTargetAPZC(const uint64_t& aInputBlockId, + nsTArray&& aTargets) +{ + for (size_t i = 0; i < aTargets.Length(); i++) { + if (aTargets[i].mLayersId != mLayersId) { + // Guard against bad data from hijacked child processes + NS_ERROR("Unexpected layers id in SetTargetAPZC; dropping message..."); + return false; + } + } + if (RefPtr apzcTreeManager = GetApzcTreeManager()) { + // need a local var to disambiguate between the SetTargetAPZC overloads. + void (APZCTreeManager::*setTargetApzcFunc)(uint64_t, const nsTArray&) + = &APZCTreeManager::SetTargetAPZC; + APZThreadUtils::RunOnControllerThread(NewRunnableMethod( + apzcTreeManager.get(), setTargetApzcFunc, + aInputBlockId, aTargets)); + } + return true; +} + +bool +RemoteContentController::RecvSetAllowedTouchBehavior(const uint64_t& aInputBlockId, + nsTArray&& aFlags) +{ + if (RefPtr apzcTreeManager = GetApzcTreeManager()) { + APZThreadUtils::RunOnControllerThread(NewRunnableMethod( + apzcTreeManager.get(), &APZCTreeManager::SetAllowedTouchBehavior, + aInputBlockId, Move(aFlags))); + } + return true; +} + +bool +RemoteContentController::RecvUpdateZoomConstraints(const uint32_t& aPresShellId, + const ViewID& aViewId, + const MaybeZoomConstraints& aConstraints) +{ + if (RefPtr apzcTreeManager = GetApzcTreeManager()) { + apzcTreeManager->UpdateZoomConstraints(ScrollableLayerGuid(mLayersId, aPresShellId, aViewId), + aConstraints); + } + return true; +} + +void +RemoteContentController::ActorDestroy(ActorDestroyReason aWhy) +{ + { + MutexAutoLock lock(mMutex); + mApzcTreeManager = nullptr; + } + mBrowserParent = nullptr; +} + +// TODO: Remove once upgraded to GCC 4.8+ on linux. Calling a static member +// function (like PAPZParent::Send__delete__) in a lambda leads to a bogus +// error: "'this' was not captured for this lambda function". +// +// (see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51494) +static void +DeletePAPZParent(PAPZParent* aPAPZ) +{ + Unused << PAPZParent::Send__delete__(aPAPZ); +} + +void +RemoteContentController::Destroy() +{ + RefPtr controller = this; + NS_DispatchToMainThread(NS_NewRunnableFunction([controller] { + if (controller->CanSend()) { + DeletePAPZParent(controller); + } + })); +} + +void +RemoteContentController::ChildAdopted() +{ + // Clear the cached APZCTreeManager. + MutexAutoLock lock(mMutex); + mApzcTreeManager = nullptr; +} + +already_AddRefed +RemoteContentController::GetApzcTreeManager() +{ + // We can't get a ref to the APZCTreeManager until after the child is + // created and the static getter knows which CompositorParent is + // instantiated with this layers ID. That's why try to fetch it when + // we first need it and cache the result. + MutexAutoLock lock(mMutex); + if (!mApzcTreeManager) { + mApzcTreeManager = CompositorParent::GetAPZCTreeManager(mLayersId); + } + RefPtr apzcTreeManager(mApzcTreeManager); + return apzcTreeManager.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/RemoteContentController.h b/gfx/layers/ipc/RemoteContentController.h new file mode 100644 index 0000000000..471104d505 --- /dev/null +++ b/gfx/layers/ipc/RemoteContentController.h @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* 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/. */ + +#ifndef mozilla_layers_RemoteContentController_h +#define mozilla_layers_RemoteContentController_h + +#include "mozilla/layers/GeckoContentController.h" +#include "mozilla/layers/PAPZParent.h" + +namespace mozilla { + +namespace dom { +class TabParent; +} + +namespace layers { + +class APZCTreeManager; + +/** + * RemoteContentController uses the PAPZ protocol to implement a + * GeckoContentController for a browser living in a remote process. + * Most of the member functions can be called on any thread, exceptions are + * annotated in comments. The PAPZ protocol runs on the main thread (so all the + * Recv* member functions do too). + */ +class RemoteContentController : public GeckoContentController + , public PAPZParent +{ + using GeckoContentController::APZStateChange; + +public: + explicit RemoteContentController(uint64_t aLayersId, + dom::TabParent* aBrowserParent); + + virtual ~RemoteContentController(); + + // Needs to be called on the main thread. + virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) override; + + virtual void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId, + const mozilla::CSSPoint& aDestination) override; + + virtual void AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId, + const uint32_t& aScrollGeneration) override; + + virtual void HandleDoubleTap(const CSSPoint& aPoint, + Modifiers aModifiers, + const ScrollableLayerGuid& aGuid) override; + + virtual void HandleSingleTap(const CSSPoint& aPoint, + Modifiers aModifiers, + const ScrollableLayerGuid& aGuid) override; + + virtual void HandleLongTap(const CSSPoint& aPoint, + Modifiers aModifiers, + const ScrollableLayerGuid& aGuid, + uint64_t aInputBlockId) override; + + virtual void PostDelayedTask(Task* aTask, int aDelayMs) override; + + virtual bool GetTouchSensitiveRegion(CSSRect* aOutRegion) override; + + virtual void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid, + APZStateChange aChange, + int aArg) override; + + virtual void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, + const nsString& aEvent) override; + + // Needs to be called on the main thread. + virtual void NotifyFlushComplete() override; + + virtual bool RecvUpdateHitRegion(const nsRegion& aRegion) override; + + virtual bool RecvZoomToRect(const uint32_t& aPresShellId, + const ViewID& aViewId, + const CSSRect& aRect, + const uint32_t& aFlags) override; + + virtual bool RecvContentReceivedInputBlock(const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, + const bool& aPreventDefault) override; + + virtual bool RecvStartScrollbarDrag(const AsyncDragMetrics& aDragMetrics) override; + + virtual bool RecvSetTargetAPZC(const uint64_t& aInputBlockId, + nsTArray&& aTargets) override; + + virtual bool RecvSetAllowedTouchBehavior(const uint64_t& aInputBlockId, + nsTArray&& aFlags) override; + + virtual bool RecvUpdateZoomConstraints(const uint32_t& aPresShellId, + const ViewID& aViewId, + const MaybeZoomConstraints& aConstraints) override; + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + virtual void Destroy() override; + + virtual void ChildAdopted() override; + +private: + bool CanSend() + { + MOZ_ASSERT(NS_IsMainThread()); + return !!mBrowserParent; + } + already_AddRefed GetApzcTreeManager(); + + MessageLoop* mUILoop; + uint64_t mLayersId; + RefPtr mBrowserParent; + + // Mutex protecting members below accessed from multiple threads. + mozilla::Mutex mMutex; + + RefPtr mApzcTreeManager; + nsRegion mTouchSensitiveRegion; +}; + +} // namespace layers + +} // namespace mozilla + +#endif // mozilla_layers_RemoteContentController_h diff --git a/gfx/layers/ipc/ShadowLayerUtilsGralloc.cpp b/gfx/layers/ipc/ShadowLayerUtilsGralloc.cpp index 33530ff36d..1e3ff8ea67 100644 --- a/gfx/layers/ipc/ShadowLayerUtilsGralloc.cpp +++ b/gfx/layers/ipc/ShadowLayerUtilsGralloc.cpp @@ -231,8 +231,8 @@ android::sp GetGraphicBufferFromDesc(SurfaceDescriptor aDesc) { MaybeMagicGrallocBufferHandle handle; - if (aDesc.type() == SurfaceDescriptor::TNewSurfaceDescriptorGralloc) { - handle = aDesc.get_NewSurfaceDescriptorGralloc().buffer(); + if (aDesc.type() == SurfaceDescriptor::TSurfaceDescriptorGralloc) { + handle = aDesc.get_SurfaceDescriptorGralloc().buffer(); } return GetGraphicBufferFrom(handle); } diff --git a/gfx/layers/ipc/SharedBufferManagerParent.cpp b/gfx/layers/ipc/SharedBufferManagerParent.cpp index b13056e050..88f873973d 100644 --- a/gfx/layers/ipc/SharedBufferManagerParent.cpp +++ b/gfx/layers/ipc/SharedBufferManagerParent.cpp @@ -279,7 +279,7 @@ void SharedBufferManagerParent::DropGrallocBufferSync(SharedBufferManagerParent* /*static*/ void SharedBufferManagerParent::DropGrallocBuffer(ProcessId id, mozilla::layers::SurfaceDescriptor aDesc) { - if (aDesc.type() != SurfaceDescriptor::TNewSurfaceDescriptorGralloc) { + if (aDesc.type() != SurfaceDescriptor::TSurfaceDescriptorGralloc) { return; } @@ -312,8 +312,8 @@ void SharedBufferManagerParent::DropGrallocBufferImpl(mozilla::layers::SurfaceDe #ifdef MOZ_HAVE_SURFACEDESCRIPTORGRALLOC int64_t key = -1; MaybeMagicGrallocBufferHandle handle; - if (aDesc.type() == SurfaceDescriptor::TNewSurfaceDescriptorGralloc) { - handle = aDesc.get_NewSurfaceDescriptorGralloc().buffer(); + if (aDesc.type() == SurfaceDescriptor::TSurfaceDescriptorGralloc) { + handle = aDesc.get_SurfaceDescriptorGralloc().buffer(); } else { return; } diff --git a/gfx/layers/ipc/ThreadSafeRefcountingWithMainThreadDestruction.h b/gfx/layers/ipc/ThreadSafeRefcountingWithMainThreadDestruction.h index e9bf032918..3e1e620d4e 100644 --- a/gfx/layers/ipc/ThreadSafeRefcountingWithMainThreadDestruction.h +++ b/gfx/layers/ipc/ThreadSafeRefcountingWithMainThreadDestruction.h @@ -5,6 +5,7 @@ #ifndef THREADSAFEREFCOUNTINGWITHMAINTHREADDESTRUCTION_H_ #define THREADSAFEREFCOUNTINGWITHMAINTHREADDESTRUCTION_H_ +#include "base/message_loop.h" #include "MainThreadUtils.h" #include "nsThreadUtils.h" diff --git a/gfx/layers/moz.build b/gfx/layers/moz.build index 2cae4f7c2a..b5fb76e4c2 100644 --- a/gfx/layers/moz.build +++ b/gfx/layers/moz.build @@ -92,6 +92,10 @@ EXPORTS.gfxipc += [ 'ipc/ShadowLayerUtils.h', ] +EXPORTS.mozilla.dom += [ + 'apz/util/CheckerboardReportService.h', +] + EXPORTS.mozilla.layers += [ 'apz/public/GeckoContentController.h', # exporting things from apz/src is temporary until we extract a @@ -109,6 +113,7 @@ EXPORTS.mozilla.layers += [ 'apz/util/ChromeProcessController.h', 'apz/util/DoubleTapToZoom.h', 'apz/util/InputAPZContext.h', + 'apz/util/ScrollInputMethods.h', 'apz/util/ScrollLinkedEffectDetector.h', 'AsyncCanvasRenderer.h', 'AtomicRefCountedWithFinalize.h', @@ -146,6 +151,7 @@ EXPORTS.mozilla.layers += [ 'D3D9SurfaceImage.h', 'Effects.h', 'ImageDataSerializer.h', + 'ipc/APZChild.h', 'ipc/AsyncTransactionTracker.h', 'ipc/CompositableForwarder.h', 'ipc/CompositableTransactionParent.h', @@ -162,6 +168,7 @@ EXPORTS.mozilla.layers += [ 'ipc/LayerAnimationUtils.h', 'ipc/LayerTransactionChild.h', 'ipc/LayerTransactionParent.h', + 'ipc/RemoteContentController.h', 'ipc/ShadowLayerChild.h', 'ipc/ShadowLayers.h', 'ipc/ShadowLayersManager.h', @@ -207,6 +214,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': 'opengl/GLManager.h', ] EXPORTS += [ + 'MacIOSurfaceHelpers.h', 'MacIOSurfaceImage.h', ] UNIFIED_SOURCES += [ @@ -214,6 +222,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': ] SOURCES += [ 'ipc/ShadowLayerUtilsMac.cpp', + 'MacIOSurfaceHelpers.cpp', 'MacIOSurfaceImage.cpp', ] @@ -255,6 +264,7 @@ UNIFIED_SOURCES += [ 'apz/src/InputBlockState.cpp', 'apz/src/InputQueue.cpp', 'apz/src/OverscrollHandoffState.cpp', + 'apz/src/PotentialCheckerboardDurationTracker.cpp', 'apz/src/TouchCounter.cpp', 'apz/src/WheelScrollAnimation.cpp', 'apz/testutil/APZTestData.cpp', @@ -262,6 +272,7 @@ UNIFIED_SOURCES += [ 'apz/util/APZCCallbackHelper.cpp', 'apz/util/APZEventState.cpp', 'apz/util/APZThreadUtils.cpp', + 'apz/util/CheckerboardReportService.cpp', 'apz/util/ChromeProcessController.cpp', 'apz/util/DoubleTapToZoom.cpp', 'apz/util/InputAPZContext.cpp', @@ -319,6 +330,7 @@ UNIFIED_SOURCES += [ 'GLImages.cpp', 'ImageDataSerializer.cpp', 'ImageLayers.cpp', + 'ipc/APZChild.cpp', 'ipc/AsyncTransactionTracker.cpp', 'ipc/CompositableTransactionParent.cpp', 'ipc/CompositorBench.cpp', @@ -333,6 +345,7 @@ UNIFIED_SOURCES += [ 'ipc/LayerAnimationUtils.cpp', 'ipc/LayerTransactionChild.cpp', 'ipc/LayerTransactionParent.cpp', + 'ipc/RemoteContentController.cpp', 'ipc/ShadowLayerChild.cpp', 'ipc/ShadowLayerParent.cpp', 'ipc/ShadowLayers.cpp', @@ -388,6 +401,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': IPDL_SOURCES = [ 'ipc/LayersMessages.ipdlh', 'ipc/LayersSurfaces.ipdlh', + 'ipc/PAPZ.ipdl', 'ipc/PCompositable.ipdl', 'ipc/PCompositor.ipdl', 'ipc/PImageBridge.ipdl', @@ -416,7 +430,7 @@ if CONFIG['MOZ_ENABLE_D3D10_LAYER']: DEFINES['MOZ_ENABLE_D3D10_LAYER'] = True if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': - if CONFIG['ANDROID_VERSION'] > '17': + if CONFIG['ANDROID_VERSION'] >= '17': includes = [ 'frameworks/av/include/media/stagefright', 'frameworks/native/include/media/openmax', diff --git a/gfx/layers/opengl/CompositingRenderTargetOGL.h b/gfx/layers/opengl/CompositingRenderTargetOGL.h index 3b6b055c52..cca94410a0 100644 --- a/gfx/layers/opengl/CompositingRenderTargetOGL.h +++ b/gfx/layers/opengl/CompositingRenderTargetOGL.h @@ -77,6 +77,8 @@ public: ~CompositingRenderTargetOGL(); + virtual const char* Name() const override { return "CompositingRenderTargetOGL"; } + /** * Create a render target around the default FBO, for rendering straight to * the window. diff --git a/gfx/layers/opengl/GrallocTextureClient.cpp b/gfx/layers/opengl/GrallocTextureClient.cpp index 35cbb09d4c..8a116902c7 100644 --- a/gfx/layers/opengl/GrallocTextureClient.cpp +++ b/gfx/layers/opengl/GrallocTextureClient.cpp @@ -142,7 +142,7 @@ GrallocTextureData::Forget(ISurfaceAllocator* aAllocator) bool GrallocTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { - aOutDescriptor = NewSurfaceDescriptorGralloc(mGrallocHandle, gfx::IsOpaque(mFormat)); + aOutDescriptor = SurfaceDescriptorGralloc(mGrallocHandle, gfx::IsOpaque(mFormat)); return true; } diff --git a/gfx/layers/opengl/GrallocTextureHost.cpp b/gfx/layers/opengl/GrallocTextureHost.cpp index 28cea35c5c..17ea0e7177 100644 --- a/gfx/layers/opengl/GrallocTextureHost.cpp +++ b/gfx/layers/opengl/GrallocTextureHost.cpp @@ -99,7 +99,7 @@ TextureTargetForAndroidPixelFormat(android::PixelFormat aFormat) } GrallocTextureHostOGL::GrallocTextureHostOGL(TextureFlags aFlags, - const NewSurfaceDescriptorGralloc& aDescriptor) + const SurfaceDescriptorGralloc& aDescriptor) : TextureHost(aFlags) , mGrallocHandle(aDescriptor) , mSize(0, 0) diff --git a/gfx/layers/opengl/GrallocTextureHost.h b/gfx/layers/opengl/GrallocTextureHost.h index a258a1e427..e3a7d08cf8 100644 --- a/gfx/layers/opengl/GrallocTextureHost.h +++ b/gfx/layers/opengl/GrallocTextureHost.h @@ -20,7 +20,7 @@ class GrallocTextureHostOGL : public TextureHost friend class GrallocBufferActor; public: GrallocTextureHostOGL(TextureFlags aFlags, - const NewSurfaceDescriptorGralloc& aDescriptor); + const SurfaceDescriptorGralloc& aDescriptor); virtual ~GrallocTextureHostOGL(); @@ -63,7 +63,7 @@ public: private: void DestroyEGLImage(); - NewSurfaceDescriptorGralloc mGrallocHandle; + SurfaceDescriptorGralloc mGrallocHandle; RefPtr mGLTextureSource; RefPtr mCompositor; // Size reported by the GraphicBuffer diff --git a/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp index 2735a0b221..dc7c8ebae9 100644 --- a/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp +++ b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp @@ -5,6 +5,7 @@ #include "MacIOSurfaceTextureClientOGL.h" #include "mozilla/gfx/MacIOSurface.h" +#include "MacIOSurfaceHelpers.h" namespace mozilla { namespace layers { @@ -53,7 +54,7 @@ MacIOSurfaceTextureData::GetFormat() const already_AddRefed MacIOSurfaceTextureData::GetAsSurface() { - RefPtr surf = mSurface->GetAsSurface(); + RefPtr surf = CreateSourceSurfaceFromMacIOSurface(mSurface); return surf->GetDataSurface(); } diff --git a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h index f408244acc..48250e1a9d 100644 --- a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h +++ b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h @@ -28,6 +28,8 @@ public: MacIOSurface* aSurface); virtual ~MacIOSurfaceTextureSourceOGL(); + virtual const char* Name() const override { return "MacIOSurfaceTextureSourceOGL"; } + virtual TextureSourceOGL* AsSourceOGL() override { return this; } virtual void BindTexture(GLenum activetex, gfx::Filter aFilter) override; diff --git a/gfx/layers/opengl/TextureHostOGL.cpp b/gfx/layers/opengl/TextureHostOGL.cpp index 2dee2b0f74..b9a4a9e5fb 100644 --- a/gfx/layers/opengl/TextureHostOGL.cpp +++ b/gfx/layers/opengl/TextureHostOGL.cpp @@ -87,9 +87,9 @@ CreateTextureHostOGL(const SurfaceDescriptor& aDesc, #endif #ifdef MOZ_WIDGET_GONK - case SurfaceDescriptor::TNewSurfaceDescriptorGralloc: { - const NewSurfaceDescriptorGralloc& desc = - aDesc.get_NewSurfaceDescriptorGralloc(); + case SurfaceDescriptor::TSurfaceDescriptorGralloc: { + const SurfaceDescriptorGralloc& desc = + aDesc.get_SurfaceDescriptorGralloc(); result = new GrallocTextureHostOGL(aFlags, desc); break; } diff --git a/gfx/layers/opengl/TextureHostOGL.h b/gfx/layers/opengl/TextureHostOGL.h index efdc19a7ce..5d6334a1fb 100644 --- a/gfx/layers/opengl/TextureHostOGL.h +++ b/gfx/layers/opengl/TextureHostOGL.h @@ -142,6 +142,7 @@ public: , mIterating(false) {} + virtual const char* Name() const override { return "TextureImageTextureSourceOGL"; } // DataTextureSource virtual bool Update(gfx::DataSourceSurface* aSurface, @@ -237,6 +238,8 @@ public: ~GLTextureSource(); + virtual const char* Name() const override { return "GLTextureSource"; } + virtual GLTextureSource* AsGLTextureSource() override { return this; } virtual TextureSourceOGL* AsSourceOGL() override { return this; } @@ -344,6 +347,8 @@ public: GLenum aWrapMode, gfx::IntSize aSize); + virtual const char* Name() const override { return "SurfaceTextureSource"; } + virtual TextureSourceOGL* AsSourceOGL() { return this; } virtual void BindTexture(GLenum activetex, gfx::Filter aFilter) override; @@ -436,6 +441,8 @@ public: GLenum aWrapMode, gfx::IntSize aSize); + virtual const char* Name() const override { return "EGLImageTextureSource"; } + virtual TextureSourceOGL* AsSourceOGL() override { return this; } virtual void BindTexture(GLenum activetex, gfx::Filter aFilter) override; diff --git a/gfx/src/gfxCrashReporterUtils.cpp b/gfx/src/gfxCrashReporterUtils.cpp index 0eb30ba928..da002c35bb 100644 --- a/gfx/src/gfxCrashReporterUtils.cpp +++ b/gfx/src/gfxCrashReporterUtils.cpp @@ -26,7 +26,6 @@ #include "nsIObserverService.h" // for nsIObserverService #include "nsIRunnable.h" // for nsIRunnable #include "nsISupports.h" -#include "nsString.h" // for nsAutoCString, nsCString, etc #include "nsTArray.h" // for nsTArray #include "nsThreadUtils.h" // for NS_DispatchToMainThread, etc #include "nscore.h" // for NS_IMETHOD, NS_IMETHODIMP, etc @@ -86,7 +85,7 @@ public: class AppendAppNotesRunnable : public nsCancelableRunnable { public: - explicit AppendAppNotesRunnable(nsAutoCString aFeatureStr) + explicit AppendAppNotesRunnable(const nsACString& aFeatureStr) : mFeatureString(aFeatureStr) { } @@ -97,7 +96,7 @@ public: } private: - nsCString mFeatureString; + nsAutoCString mFeatureString; }; void @@ -118,17 +117,29 @@ ScopedGfxFeatureReporter::WriteAppNote(char statusChar) if (!gFeaturesAlreadyReported->Contains(featureString)) { gFeaturesAlreadyReported->AppendElement(featureString); - nsCOMPtr r = new AppendAppNotesRunnable(featureString); - NS_DispatchToMainThread(r); + AppNote(featureString); } } +void +ScopedGfxFeatureReporter::AppNote(const nsACString& aMessage) +{ + if (NS_IsMainThread()) { + CrashReporter::AppendAppNotesToCrashReport(aMessage); + } else { + nsCOMPtr r = new AppendAppNotesRunnable(aMessage); + NS_DispatchToMainThread(r); + } +} + } // end namespace mozilla #else namespace mozilla { void ScopedGfxFeatureReporter::WriteAppNote(char) {} +void ScopedGfxFeatureReporter::AppNote(const nsACString&) {} + } // namespace mozilla #endif diff --git a/gfx/src/gfxCrashReporterUtils.h b/gfx/src/gfxCrashReporterUtils.h index a11b661f03..efc5ab1ab7 100644 --- a/gfx/src/gfxCrashReporterUtils.h +++ b/gfx/src/gfxCrashReporterUtils.h @@ -6,6 +6,7 @@ #ifndef gfxCrashReporterUtils_h__ #define gfxCrashReporterUtils_h__ +#include "nsString.h" namespace mozilla { @@ -23,16 +24,18 @@ namespace mozilla { class ScopedGfxFeatureReporter { public: - explicit ScopedGfxFeatureReporter(const char *aFeature, bool force = false) + explicit ScopedGfxFeatureReporter(const char *aFeature, bool aForce = false) : mFeature(aFeature), mStatusChar('-') { - WriteAppNote(force ? '!' : '?'); + WriteAppNote(aForce ? '!' : '?'); } ~ScopedGfxFeatureReporter() { WriteAppNote(mStatusChar); } void SetSuccessful() { mStatusChar = '+'; } + static void AppNote(const nsACString& aMessage); + class AppNoteWritingRunnable; protected: diff --git a/gfx/tests/gtest/TestTreeTraversal.cpp b/gfx/tests/gtest/TestTreeTraversal.cpp index 731b8ba856..d8ebec01ef 100644 --- a/gfx/tests/gtest/TestTreeTraversal.cpp +++ b/gfx/tests/gtest/TestTreeTraversal.cpp @@ -121,15 +121,15 @@ TEST(TreeTraversal, DepthFirstSearchValueExists) } RefPtr root = nodeList[0]; - nodeList[0]->AddChild(nodeList[1]); nodeList[0]->AddChild(nodeList[4]); - nodeList[1]->AddChild(nodeList[2]); + nodeList[0]->AddChild(nodeList[1]); nodeList[1]->AddChild(nodeList[3]); - nodeList[4]->AddChild(nodeList[5]); + nodeList[1]->AddChild(nodeList[2]); nodeList[4]->AddChild(nodeList[6]); + nodeList[4]->AddChild(nodeList[5]); nodeList[6]->AddChild(nodeList[7]); - nodeList[7]->AddChild(nodeList[8]); nodeList[7]->AddChild(nodeList[9]); + nodeList[7]->AddChild(nodeList[8]); RefPtr foundNode = DepthFirstSearch(root.get(), [&visitCount](SearchTestNode* aNode) @@ -185,33 +185,156 @@ TEST(TreeTraversal, DepthFirstSearchValueDoesNotExist) } RefPtr root = nodeList[0]; - nodeList[0]->AddChild(nodeList[1]); nodeList[0]->AddChild(nodeList[4]); - nodeList[1]->AddChild(nodeList[2]); + nodeList[0]->AddChild(nodeList[1]); nodeList[1]->AddChild(nodeList[3]); - nodeList[4]->AddChild(nodeList[5]); + nodeList[1]->AddChild(nodeList[2]); nodeList[4]->AddChild(nodeList[6]); + nodeList[4]->AddChild(nodeList[5]); nodeList[6]->AddChild(nodeList[7]); - nodeList[7]->AddChild(nodeList[8]); nodeList[7]->AddChild(nodeList[9]); + nodeList[7]->AddChild(nodeList[8]); RefPtr foundNode = DepthFirstSearch(root.get(), [&visitCount](SearchTestNode* aNode) { aNode->SetActualTraversalRank(visitCount); - visitCount++; + visitCount++; return aNode->GetType() == SearchNodeType::Needle; }); for (int i = 0; i < 10; i++) { - ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), nodeList[i]->GetActualTraversalRank()) << "Node at index " << i << " was hit out of order."; } - ASSERT_EQ(foundNode.get(), nullptr) + ASSERT_EQ(foundNode.get(), nullptr) + << "Search found something that should not exist."; +} + +TEST(TreeTraversal, DepthFirstSearchPostOrderNull) +{ + RefPtr nullNode; + RefPtr result = DepthFirstSearchPostOrder(nullNode.get(), + [](SearchTestNode* aNode) + { + return aNode->GetType() == SearchNodeType::Needle; + }); + ASSERT_EQ(result.get(), nullptr) << "Null root did not return null search result."; +} + +TEST(TreeTraversal, DepthFirstSearchPostOrderValueExists) +{ + int visitCount = 0; + size_t expectedNeedleTraversalRank = 7; + RefPtr needleNode; + std::vector> nodeList; + for (size_t i = 0; i < 10; i++) + { + if (i == expectedNeedleTraversalRank) { + needleNode = new SearchTestNode(SearchNodeType::Needle, i); + nodeList.push_back(needleNode); + } else if (i < expectedNeedleTraversalRank) { + nodeList.push_back(new SearchTestNode(SearchNodeType::Hay, i)); + } else { + nodeList.push_back(new SearchTestNode(SearchNodeType::Hay)); + } + } + + RefPtr root = nodeList[9]; + nodeList[9]->AddChild(nodeList[8]); + nodeList[9]->AddChild(nodeList[2]); + nodeList[2]->AddChild(nodeList[1]); + nodeList[2]->AddChild(nodeList[0]); + nodeList[8]->AddChild(nodeList[7]); + nodeList[8]->AddChild(nodeList[6]); + nodeList[6]->AddChild(nodeList[5]); + nodeList[5]->AddChild(nodeList[4]); + nodeList[5]->AddChild(nodeList[3]); + + RefPtr foundNode = DepthFirstSearchPostOrder(root.get(), + [&visitCount](SearchTestNode* aNode) + { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + + for (size_t i = 0; i < nodeList.size(); i++) + { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } + + ASSERT_EQ(foundNode, needleNode) << "Search did not return expected node."; + ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle) + << "Returned node does not match expected value (something odd happened)."; +} + +TEST(TreeTraversal, DepthFirstSearchPostOrderRootIsNeedle) +{ + RefPtr root = new SearchTestNode(SearchNodeType::Needle, 0); + RefPtr childNode1= new SearchTestNode(SearchNodeType::Hay); + RefPtr childNode2 = new SearchTestNode(SearchNodeType::Hay); + int visitCount = 0; + RefPtr result = DepthFirstSearchPostOrder(root.get(), + [&visitCount](SearchTestNode* aNode) + { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + ASSERT_EQ(result, root) << "Search starting at needle did not return needle."; + ASSERT_EQ(root->GetExpectedTraversalRank(), root->GetActualTraversalRank()) + << "Search starting at needle did not return needle."; + ASSERT_EQ(childNode1->GetExpectedTraversalRank(), + childNode1->GetActualTraversalRank()) + << "Search starting at needle continued past needle."; + ASSERT_EQ(childNode2->GetExpectedTraversalRank(), + childNode2->GetActualTraversalRank()) + << "Search starting at needle continued past needle."; +} + +TEST(TreeTraversal, DepthFirstSearchPostOrderValueDoesNotExist) +{ + int visitCount = 0; + std::vector> nodeList; + for (int i = 0; i < 10; i++) + { + nodeList.push_back(new SearchTestNode(SearchNodeType::Hay, i)); + } + + RefPtr root = nodeList[9]; + nodeList[9]->AddChild(nodeList[8]); + nodeList[9]->AddChild(nodeList[2]); + nodeList[2]->AddChild(nodeList[1]); + nodeList[2]->AddChild(nodeList[0]); + nodeList[8]->AddChild(nodeList[7]); + nodeList[8]->AddChild(nodeList[6]); + nodeList[6]->AddChild(nodeList[5]); + nodeList[5]->AddChild(nodeList[4]); + nodeList[5]->AddChild(nodeList[3]); + + RefPtr foundNode = DepthFirstSearchPostOrder(root.get(), + [&visitCount](SearchTestNode* aNode) + { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + + for (int i = 0; i < 10; i++) + { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } + + ASSERT_EQ(foundNode.get(), nullptr) << "Search found something that should not exist."; } @@ -243,10 +366,10 @@ TEST(TreeTraversal, BreadthFirstSearchRootIsNeedle) ASSERT_EQ(root->GetExpectedTraversalRank(), root->GetActualTraversalRank()) << "Search starting at needle did not return needle."; ASSERT_EQ(childNode1->GetExpectedTraversalRank(), - childNode1->GetActualTraversalRank()) + childNode1->GetActualTraversalRank()) << "Search starting at needle continued past needle."; ASSERT_EQ(childNode2->GetExpectedTraversalRank(), - childNode2->GetActualTraversalRank()) + childNode2->GetActualTraversalRank()) << "Search starting at needle continued past needle."; } @@ -283,7 +406,7 @@ TEST(TreeTraversal, BreadthFirstSearchValueExists) [&visitCount](SearchTestNode* aNode) { aNode->SetActualTraversalRank(visitCount); - visitCount++; + visitCount++; return aNode->GetType() == SearchNodeType::Needle; }); @@ -324,7 +447,7 @@ TEST(TreeTraversal, BreadthFirstSearchValueDoesNotExist) [&visitCount](SearchTestNode* aNode) { aNode->SetActualTraversalRank(visitCount); - visitCount++; + visitCount++; return aNode->GetType() == SearchNodeType::Needle; }); @@ -359,24 +482,24 @@ TEST(TreeTraversal, ForEachNodeAllEligible) } RefPtr root = nodeList[0]; - nodeList[0]->AddChild(nodeList[1]); nodeList[0]->AddChild(nodeList[4]); - nodeList[1]->AddChild(nodeList[2]); + nodeList[0]->AddChild(nodeList[1]); nodeList[1]->AddChild(nodeList[3]); - nodeList[4]->AddChild(nodeList[5]); + nodeList[1]->AddChild(nodeList[2]); nodeList[4]->AddChild(nodeList[6]); + nodeList[4]->AddChild(nodeList[5]); nodeList[6]->AddChild(nodeList[7]); - nodeList[7]->AddChild(nodeList[8]); nodeList[7]->AddChild(nodeList[9]); + nodeList[7]->AddChild(nodeList[8]); ForEachNode(root.get(), [&visitCount](ForEachTestNode* aNode) { aNode->SetActualTraversalRank(visitCount); - visitCount++; + visitCount++; return aNode->GetType() == ForEachNodeType::Continue - ? TraversalFlag::Continue : TraversalFlag::Skip; + ? TraversalFlag::Continue : TraversalFlag::Skip; }); for (size_t i = 0; i < nodeList.size(); i++) @@ -404,21 +527,21 @@ TEST(TreeTraversal, ForEachNodeSomeIneligibleNodes) expectedSkippedNodeList.push_back(new ForEachTestNode(ForEachNodeType::Skip)); RefPtr root = expectedVisitedNodeList[0]; - expectedVisitedNodeList[0]->AddChild(expectedVisitedNodeList[1]); expectedVisitedNodeList[0]->AddChild(expectedVisitedNodeList[2]); - expectedVisitedNodeList[1]->AddChild(expectedSkippedNodeList[0]); + expectedVisitedNodeList[0]->AddChild(expectedVisitedNodeList[1]); expectedVisitedNodeList[1]->AddChild(expectedSkippedNodeList[1]); + expectedVisitedNodeList[1]->AddChild(expectedSkippedNodeList[0]); expectedVisitedNodeList[2]->AddChild(expectedVisitedNodeList[3]); - expectedVisitedNodeList[3]->AddChild(expectedSkippedNodeList[2]); expectedVisitedNodeList[3]->AddChild(expectedSkippedNodeList[3]); + expectedVisitedNodeList[3]->AddChild(expectedSkippedNodeList[2]); ForEachNode(root.get(), [&visitCount](ForEachTestNode* aNode) { aNode->SetActualTraversalRank(visitCount); - visitCount++; + visitCount++; return aNode->GetType() == ForEachNodeType::Continue - ? TraversalFlag::Continue : TraversalFlag::Skip; + ? TraversalFlag::Continue : TraversalFlag::Skip; }); for (size_t i = 0; i < expectedVisitedNodeList.size(); i++) @@ -448,9 +571,9 @@ TEST(TreeTraversal, ForEachNodeIneligibleRoot) [&visitCount](ForEachTestNode* aNode) { aNode->SetActualTraversalRank(visitCount); - visitCount++; + visitCount++; return aNode->GetType() == ForEachNodeType::Continue - ? TraversalFlag::Continue : TraversalFlag::Skip; + ? TraversalFlag::Continue : TraversalFlag::Skip; }); ASSERT_EQ(root->GetExpectedTraversalRank(), root->GetActualTraversalRank()) @@ -476,23 +599,23 @@ TEST(TreeTraversal, ForEachNodeLeavesIneligible) } RefPtr root = nodeList[0]; - nodeList[0]->AddChild(nodeList[1]); nodeList[0]->AddChild(nodeList[2]); - nodeList[2]->AddChild(nodeList[3]); + nodeList[0]->AddChild(nodeList[1]); nodeList[2]->AddChild(nodeList[4]); - nodeList[4]->AddChild(nodeList[5]); + nodeList[2]->AddChild(nodeList[3]); nodeList[4]->AddChild(nodeList[6]); + nodeList[4]->AddChild(nodeList[5]); nodeList[6]->AddChild(nodeList[7]); - nodeList[7]->AddChild(nodeList[8]); nodeList[7]->AddChild(nodeList[9]); + nodeList[7]->AddChild(nodeList[8]); ForEachNode(root.get(), [&visitCount](ForEachTestNode* aNode) { aNode->SetActualTraversalRank(visitCount); - visitCount++; + visitCount++; return aNode->GetType() == ForEachNodeType::Continue - ? TraversalFlag::Continue : TraversalFlag::Skip; + ? TraversalFlag::Continue : TraversalFlag::Skip; }); for (size_t i = 0; i < nodeList.size(); i++) @@ -513,22 +636,22 @@ TEST(TreeTraversal, ForEachNodeLambdaReturnsVoid) } RefPtr root = nodeList[0]; - nodeList[0]->AddChild(nodeList[1]); nodeList[0]->AddChild(nodeList[4]); - nodeList[1]->AddChild(nodeList[2]); + nodeList[0]->AddChild(nodeList[1]); nodeList[1]->AddChild(nodeList[3]); - nodeList[4]->AddChild(nodeList[5]); + nodeList[1]->AddChild(nodeList[2]); nodeList[4]->AddChild(nodeList[6]); + nodeList[4]->AddChild(nodeList[5]); nodeList[6]->AddChild(nodeList[7]); - nodeList[7]->AddChild(nodeList[8]); nodeList[7]->AddChild(nodeList[9]); + nodeList[7]->AddChild(nodeList[8]); ForEachNode(root.get(), [&visitCount](ForEachTestNode* aNode) { aNode->SetActualTraversalRank(visitCount); - visitCount++; + visitCount++; }); for (size_t i = 0; i < nodeList.size(); i++) diff --git a/gfx/thebes/gfx2DGlue.h b/gfx/thebes/gfx2DGlue.h index fabdb7c38d..2d3267b6c0 100644 --- a/gfx/thebes/gfx2DGlue.h +++ b/gfx/thebes/gfx2DGlue.h @@ -46,11 +46,6 @@ inline Point ToPoint(const gfxPoint &aPoint) return Point(Float(aPoint.x), Float(aPoint.y)); } -inline IntMargin ToIntMargin(const nsIntMargin& aMargin) -{ - return IntMargin(aMargin.top, aMargin.right, aMargin.bottom, aMargin.left); -} - inline Size ToSize(const gfxSize &aSize) { return Size(Float(aSize.width), Float(aSize.height)); diff --git a/gfx/thebes/gfxBlur.cpp b/gfx/thebes/gfxBlur.cpp index 119af1532d..2f4e1fc05b 100644 --- a/gfx/thebes/gfxBlur.cpp +++ b/gfx/thebes/gfxBlur.cpp @@ -511,14 +511,13 @@ CreateBoxShadow(SourceSurface* aBlurMask, const Color& aShadowColor) } static already_AddRefed -GetBlur(DrawTarget& aDT, +GetBlur(gfxContext* aDestinationCtx, const IntSize& aRectSize, const IntSize& aBlurRadius, RectCornerRadii* aCornerRadii, const Color& aShadowColor, IntMargin& aExtendDestBy, - IntMargin& aSlice, - gfxContext* aDestinationCtx) + IntMargin& aSlice) { if (!gBlurCache) { gBlurCache = new BlurCache(); @@ -536,9 +535,11 @@ GetBlur(DrawTarget& aDT, minSize = aRectSize; } + DrawTarget& destDT = *aDestinationCtx->GetDrawTarget(); + BlurCacheData* cached = gBlurCache->Lookup(minSize, aBlurRadius, aCornerRadii, aShadowColor, - aDT.GetBackendType()); + destDT.GetBackendType()); if (cached && !useDestRect) { // See CreateBlurMask() for these values aExtendDestBy = cached->mExtendDest; @@ -548,7 +549,8 @@ GetBlur(DrawTarget& aDT, } RefPtr blurMask = - CreateBlurMask(minSize, aCornerRadii, aBlurRadius, aExtendDestBy, aSlice, aDT); + CreateBlurMask(minSize, aCornerRadii, aBlurRadius, aExtendDestBy, aSlice, + destDT); if (!blurMask) { return nullptr; @@ -563,7 +565,8 @@ GetBlur(DrawTarget& aDT, // Since we're just going to paint the actual rect to the destination aSlice.SizeTo(0, 0, 0, 0); } else { - CacheBlur(aDT, minSize, aBlurRadius, aCornerRadii, aShadowColor, aExtendDestBy, boxShadow); + CacheBlur(destDT, minSize, aBlurRadius, aCornerRadii, aShadowColor, + aExtendDestBy, boxShadow); } return boxShadow.forget(); } @@ -701,22 +704,21 @@ gfxAlphaBoxBlur::BlurRectangle(gfxContext* aDestinationCtx, const gfxRect& aDirtyRect, const gfxRect& aSkipRect) { - DrawTarget& destDrawTarget = *aDestinationCtx->GetDrawTarget(); IntSize blurRadius = CalculateBlurRadius(aBlurStdDev); IntRect rect = RoundedToInt(ToRect(aRect)); IntMargin extendDestBy; IntMargin slice; - RefPtr boxShadow = GetBlur(destDrawTarget, + RefPtr boxShadow = GetBlur(aDestinationCtx, rect.Size(), blurRadius, aCornerRadii, aShadowColor, - extendDestBy, slice, - aDestinationCtx); + extendDestBy, slice); if (!boxShadow) { return; } + DrawTarget& destDrawTarget = *aDestinationCtx->GetDrawTarget(); destDrawTarget.PushClipRect(ToRect(aDirtyRect)); // Copy the right parts from boxShadow into destDrawTarget. The middle parts @@ -921,7 +923,7 @@ gfxAlphaBoxBlur::GetInsetBlur(IntMargin& aExtendDestBy, const bool& aHasBorderRadius, const Point aShadowOffset, bool& aMovedOffset, - gfxContext* aDestinationCtx) + DrawTarget* aDestDrawTarget) { if (!gBlurCache) { gBlurCache = new BlurCache(); @@ -948,13 +950,12 @@ gfxAlphaBoxBlur::GetInsetBlur(IntMargin& aExtendDestBy, aMovedOffset = true; } - DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); BlurCacheData* cached = gBlurCache->LookupInsetBoxShadow(outerRect.Size(), innerRect.Size(), aBlurRadius, aSpreadRadius, &aInnerClipRadii, aShadowColor, aHasBorderRadius, - destDrawTarget->GetBackendType()); + aDestDrawTarget->GetBackendType()); if (cached && !useDestRect) { aExtendDestBy = cached->mExtendDest; // Need to extend it twice: once for the outer rect and once for the inner rect. @@ -1011,7 +1012,7 @@ gfxAlphaBoxBlur::GetInsetBlur(IntMargin& aExtendDestBy, CacheInsetBlur(outerRect.Size(), innerRect.Size(), aBlurRadius, aSpreadRadius, &aInnerClipRadii, aShadowColor, - aHasBorderRadius, destDrawTarget->GetBackendType(), + aHasBorderRadius, aDestDrawTarget->GetBackendType(), aExtendDestBy, minInsetBlur); } @@ -1041,6 +1042,8 @@ gfxAlphaBoxBlur::BlurInsetBox(gfxContext* aDestinationCtx, const Rect aSkipRect, const Point aShadowOffset) { + DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); + // Blur inset shadows ALWAYS have a 0 spread radius. if ((aBlurRadius.width <= 0 && aBlurRadius.height <= 0)) { FillDestinationPath(aDestinationCtx, aDestinationRect, aShadowClipRect, @@ -1056,7 +1059,7 @@ gfxAlphaBoxBlur::BlurInsetBox(gfxContext* aDestinationCtx, aBlurRadius, aSpreadRadius, aInnerClipRadii, aShadowColor, aHasBorderRadius, aShadowOffset, - didMoveOffset, aDestinationCtx); + didMoveOffset, destDrawTarget); if (!minInsetBlur) { return; } @@ -1073,7 +1076,6 @@ gfxAlphaBoxBlur::BlurInsetBox(gfxContext* aDestinationCtx, Rect dstInner = dstOuter; dstInner.Deflate(Margin(slice)); - DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); if (dstOuter.Size() == srcOuter.Size()) { destDrawTarget->DrawSurface(minInsetBlur, dstOuter, srcOuter); } else { diff --git a/gfx/thebes/gfxBlur.h b/gfx/thebes/gfxBlur.h index 27c293b446..2210949b67 100644 --- a/gfx/thebes/gfxBlur.h +++ b/gfx/thebes/gfxBlur.h @@ -48,6 +48,7 @@ namespace mozilla { class gfxAlphaBoxBlur { typedef mozilla::gfx::Color Color; + typedef mozilla::gfx::DrawTarget DrawTarget; typedef mozilla::gfx::RectCornerRadii RectCornerRadii; public: @@ -89,7 +90,8 @@ public: return mContext; } - already_AddRefed DoBlur(mozilla::gfx::DrawTarget* aDT, mozilla::gfx::IntPoint* aTopLeft); + already_AddRefed + DoBlur(DrawTarget* aDT, mozilla::gfx::IntPoint* aTopLeft); /** * Does the actual blurring/spreading and mask applying. Users of this @@ -176,7 +178,7 @@ protected: const bool& aHasBorderRadius, const mozilla::gfx::Point aShadowOffset, bool& aMovedOffset, - gfxContext* aDestinationCtx); + DrawTarget* aDestDrawTarget); /** * The context of the temporary alpha surface. diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp index b34c9803ca..65423126f1 100644 --- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -10,11 +10,13 @@ #include "mozilla/layers/SharedBufferManagerChild.h" #include "mozilla/layers/ISurfaceAllocator.h" // for GfxMemoryImageReporter #include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" #include "mozilla/Logging.h" #include "mozilla/Services.h" #include "prprf.h" +#include "gfxCrashReporterUtils.h" #include "gfxPlatform.h" #include "gfxPrefs.h" #include "gfxEnv.h" @@ -44,6 +46,10 @@ #include "gfxAndroidPlatform.h" #endif +#ifdef XP_WIN +#include "mozilla/WindowsVersion.h" +#endif + #include "nsGkAtoms.h" #include "gfxPlatformFontList.h" #include "gfxContext.h" @@ -119,6 +125,7 @@ class mozilla::gl::SkiaGLGlue : public GenericAtomicRefCounted { #include "mozilla/Attributes.h" #include "mozilla/Mutex.h" +#include "nsAlgorithm.h" #include "nsIGfxInfo.h" #include "nsIXULRuntime.h" #include "VsyncSource.h" @@ -190,18 +197,18 @@ public: explicit CrashStatsLogForwarder(const char* aKey); virtual void Log(const std::string& aString) override; virtual void CrashAction(LogReason aReason) override; + virtual bool UpdateStringsVector(const std::string& aString) override; - virtual std::vector > StringsVectorCopy() override; + virtual LoggingRecord LoggingRecordCopy() override; void SetCircularBufferSize(uint32_t aCapacity); private: - // Helpers for the Log() - bool UpdateStringsVector(const std::string& aString); + // Helper for the Log() void UpdateCrashReport(); private: - std::vector > mBuffer; + LoggingRecord mBuffer; nsCString mCrashCriticalKey; uint32_t mMaxCapacity; int32_t mIndex; @@ -225,8 +232,8 @@ void CrashStatsLogForwarder::SetCircularBufferSize(uint32_t aCapacity) mBuffer.reserve(static_cast(aCapacity)); } -std::vector > -CrashStatsLogForwarder::StringsVectorCopy() +LoggingRecord +CrashStatsLogForwarder::LoggingRecordCopy() { MutexAutoLock lock(mMutex); return mBuffer; @@ -248,9 +255,12 @@ CrashStatsLogForwarder::UpdateStringsVector(const std::string& aString) MOZ_ASSERT(index >= 0 && index < (int32_t)mMaxCapacity); MOZ_ASSERT(index <= mIndex && index <= (int32_t)mBuffer.size()); + bool ignored; + double tStamp = (TimeStamp::NowLoRes()-TimeStamp::ProcessCreation(ignored)).ToSecondsSigDigits(); + // Checking for index >= mBuffer.size(), rather than index == mBuffer.size() // just out of paranoia, but we know index <= mBuffer.size(). - std::pair newEntry(mIndex,aString); + LoggingRecordEntry newEntry(mIndex,aString,tStamp); if (index >= static_cast(mBuffer.size())) { mBuffer.push_back(newEntry); } else { @@ -262,8 +272,14 @@ CrashStatsLogForwarder::UpdateStringsVector(const std::string& aString) void CrashStatsLogForwarder::UpdateCrashReport() { std::stringstream message; - for(std::vector >::iterator it = mBuffer.begin(); it != mBuffer.end(); ++it) { - message << "|[" << (*it).first << "]" << (*it).second; + if (XRE_IsParentProcess()) { + for(LoggingRecord::iterator it = mBuffer.begin(); it != mBuffer.end(); ++it) { + message << "|[" << Get<0>(*it) << "]" << Get<1>(*it) << " (t=" << Get<2>(*it) << ") "; + } + } else { + for(LoggingRecord::iterator it = mBuffer.begin(); it != mBuffer.end(); ++it) { + message << "|[C" << Get<0>(*it) << "]" << Get<1>(*it) << " (t=" << Get<2>(*it) << ") "; + } } #ifdef MOZ_CRASHREPORTER @@ -278,6 +294,27 @@ void CrashStatsLogForwarder::UpdateCrashReport() } } +class LogForwarderEvent : public nsRunnable +{ + virtual ~LogForwarderEvent() {} + + NS_DECL_ISUPPORTS_INHERITED + + explicit LogForwarderEvent(const nsCString& aMessage) : mMessage(aMessage) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread() && XRE_IsContentProcess()); + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + cc->SendGraphicsError(mMessage); + return NS_OK; + } + +protected: + nsCString mMessage; +}; + +NS_IMPL_ISUPPORTS_INHERITED0(LogForwarderEvent, nsRunnable); + void CrashStatsLogForwarder::Log(const std::string& aString) { MutexAutoLock lock(mMutex); @@ -285,6 +322,18 @@ void CrashStatsLogForwarder::Log(const std::string& aString) if (UpdateStringsVector(aString)) { UpdateCrashReport(); } + + // Add it to the parent strings + if (!XRE_IsParentProcess()) { + nsCString stringToSend(aString.c_str()); + if (NS_IsMainThread()) { + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + cc->SendGraphicsError(stringToSend); + } else { + nsCOMPtr r1 = new LogForwarderEvent(stringToSend); + NS_DispatchToMainThread(r1); + } + } } class CrashTelemetryEvent : public nsRunnable @@ -511,6 +560,44 @@ gfxPlatform::Init() auto fwd = new CrashStatsLogForwarder("GraphicsCriticalError"); fwd->SetCircularBufferSize(gfxPrefs::GfxLoggingCrashLength()); + // Drop a note in the crash report if we end up forcing an option that could + // destabilize things. New items should be appended at the end (of an existing + // or in a new section), so that we don't have to know the version to interpret + // these cryptic strings. + { + nsAutoCString forcedPrefs; + // D2D prefs + forcedPrefs.AppendPrintf("FP(D%d%d%d", + gfxPrefs::Direct2DDisabled(), + gfxPrefs::Direct2DForceEnabled(), + gfxPrefs::DirectWriteFontRenderingForceEnabled()); + // Layers prefs + forcedPrefs.AppendPrintf("-L%d%d%d%d%d%d", + gfxPrefs::LayersAMDSwitchableGfxEnabled(), + gfxPrefs::LayersAccelerationDisabled(), + gfxPrefs::LayersAccelerationForceEnabled(), + gfxPrefs::LayersD3D11DisableWARP(), + gfxPrefs::LayersD3D11ForceWARP(), + gfxPrefs::LayersOffMainThreadCompositionForceEnabled()); + // WebGL prefs + forcedPrefs.AppendPrintf("-W%d%d%d%d%d%d%d%d", + gfxPrefs::WebGLANGLEForceD3D11(), + gfxPrefs::WebGLANGLEForceWARP(), + gfxPrefs::WebGLDisabled(), + gfxPrefs::WebGLDisableANGLE(), + gfxPrefs::WebGLDXGLEnabled(), + gfxPrefs::WebGLForceEnabled(), + gfxPrefs::WebGLForceLayersReadback(), + gfxPrefs::WebGLForceMSAA()); + // Prefs that don't fit into any of the other sections + forcedPrefs.AppendPrintf("-T%d%d%d%d) ", + gfxPrefs::AndroidRGB16Force(), + gfxPrefs::CanvasAzureAccelerated(), + gfxPrefs::DisableGralloc(), + gfxPrefs::ForceShmemTiles()); + ScopedGfxFeatureReporter::AppNote(forcedPrefs); + } + mozilla::gfx::Config cfg; cfg.mLogForwarder = fwd; cfg.mMaxTextureSize = gfxPrefs::MaxTextureSize(); @@ -1060,9 +1147,10 @@ gfxPlatform::ComputeTileSize() if (gfxPrefs::LayersTilesAdjust()) { gfx::IntSize screenSize = GetScreenSize(); if (screenSize.width > 0) { + // Choose a size so that there are between 2 and 4 tiles per screen width. // FIXME: we should probably make sure this is within the max texture size, // but I think everything should at least support 1024 - w = h = std::max(std::min(NextPowerOfTwo(screenSize.width) / 2, 1024), 256); + w = h = clamped(NextPowerOfTwo(screenSize.width) / 4, 256, 1024); } #ifdef MOZ_WIDGET_GONK @@ -1107,6 +1195,16 @@ gfxPlatform::SupportsAzureContentForDrawTarget(DrawTarget* aTarget) return false; } +#ifdef USE_SKIA_GPU + // Skia content rendering doesn't support GPU acceleration, so we can't + // use the same backend if the current backend is accelerated. + if ((aTarget->GetType() == DrawTargetType::HARDWARE_RASTER) + && (aTarget->GetBackendType() == BackendType::SKIA)) + { + return false; + } +#endif + return SupportsAzureContentForType(aTarget->GetBackendType()); } @@ -1919,7 +2017,11 @@ InitLayersAccelerationPrefs() } else if (gfxInfo) { if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, &status))) { if (status == nsIGfxInfo::FEATURE_STATUS_OK) { - sLayersSupportsD3D9 = true; + if (sPrefBrowserTabsRemoteAutostart && !IsVistaOrLater()) { + gfxWarning() << "Disallowing D3D9 on Windows XP with E10S - see bug 1237770"; + } else { + sLayersSupportsD3D9 = true; + } } } if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, &status))) { @@ -1945,8 +2047,8 @@ InitLayersAccelerationPrefs() #endif NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, &status))) { - if (status == nsIGfxInfo::FEATURE_STATUS_OK) { - sLayersSupportsHardwareVideoDecoding = true; + if (status == nsIGfxInfo::FEATURE_STATUS_OK || gfxPrefs::HardwareVideoDecodingForceEnabled()) { + sLayersSupportsHardwareVideoDecoding = true; } } @@ -2064,8 +2166,7 @@ gfxPlatform::UsesOffMainThreadCompositing() result = sPrefBrowserTabsRemoteAutostart || gfxPrefs::LayersOffMainThreadCompositionEnabled() || - gfxPrefs::LayersOffMainThreadCompositionForceEnabled() || - gfxPrefs::LayersOffMainThreadCompositionTestingEnabled(); + gfxPrefs::LayersOffMainThreadCompositionForceEnabled(); #if defined(MOZ_WIDGET_GTK) // Linux users who chose OpenGL are being grandfathered in to OMTC result |= gfxPrefs::LayersAccelerationForceEnabled(); @@ -2191,7 +2292,11 @@ gfxPlatform::AsyncPanZoomEnabled() return false; } #endif +#ifdef MOZ_ANDROID_APZ + return true; +#else return gfxPrefs::AsyncPanZoomEnabledDoNotUseDirectly(); +#endif } /*virtual*/ bool diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index a496708417..77e412aaf2 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -148,6 +148,7 @@ private: DECL_GFX_PREF(Live, "apz.content_response_timeout", APZContentResponseTimeout, int32_t, 300); DECL_GFX_PREF(Live, "apz.danger_zone_x", APZDangerZoneX, int32_t, 50); DECL_GFX_PREF(Live, "apz.danger_zone_y", APZDangerZoneY, int32_t, 100); + DECL_GFX_PREF(Live, "apz.displayport_expiry_ms", APZDisplayPortExpiryTime, uint32_t, 15000); DECL_GFX_PREF(Live, "apz.drag.enabled", APZDragEnabled, bool, false); DECL_GFX_PREF(Live, "apz.enlarge_displayport_when_clipped", APZEnlargeDisplayPortWhenClipped, bool, false); DECL_GFX_PREF(Live, "apz.fling_accel_base_mult", APZFlingAccelBaseMultiplier, float, 1.0f); @@ -159,7 +160,6 @@ private: DECL_GFX_PREF(Once, "apz.fling_curve_function_y2", APZCurveFunctionY2, float, 1.0f); DECL_GFX_PREF(Live, "apz.fling_curve_threshold_inches_per_ms", APZCurveThreshold, float, -1.0f); DECL_GFX_PREF(Live, "apz.fling_friction", APZFlingFriction, float, 0.002f); - DECL_GFX_PREF(Live, "apz.fling_repaint_interval", APZFlingRepaintInterval, int32_t, 75); DECL_GFX_PREF(Live, "apz.fling_stop_on_tap_threshold", APZFlingStopOnTapThreshold, float, 0.05f); DECL_GFX_PREF(Live, "apz.fling_stopped_threshold", APZFlingStoppedThreshold, float, 0.01f); DECL_GFX_PREF(Live, "apz.highlight_checkerboarded_areas", APZHighlightCheckerboardedAreas, bool, false); @@ -174,10 +174,8 @@ private: DECL_GFX_PREF(Live, "apz.overscroll.stop_distance_threshold", APZOverscrollStopDistanceThreshold, float, 5.0f); DECL_GFX_PREF(Live, "apz.overscroll.stop_velocity_threshold", APZOverscrollStopVelocityThreshold, float, 0.01f); DECL_GFX_PREF(Live, "apz.overscroll.stretch_factor", APZOverscrollStretchFactor, float, 0.5f); - DECL_GFX_PREF(Live, "apz.pan_repaint_interval", APZPanRepaintInterval, int32_t, 250); DECL_GFX_PREF(Live, "apz.printtree", APZPrintTree, bool, false); DECL_GFX_PREF(Live, "apz.record_checkerboarding", APZRecordCheckerboarding, bool, false); - DECL_GFX_PREF(Live, "apz.smooth_scroll_repaint_interval", APZSmoothScrollRepaintInterval, int32_t, 75); DECL_GFX_PREF(Live, "apz.test.logging_enabled", APZTestLoggingEnabled, bool, false); DECL_GFX_PREF(Live, "apz.touch_move_tolerance", APZTouchMoveTolerance, float, 0.0); DECL_GFX_PREF(Live, "apz.touch_start_tolerance", APZTouchStartTolerance, float, 1.0f/4.5f); @@ -249,7 +247,7 @@ private: DECL_GFX_PREF(Live, "gfx.layerscope.enabled", LayerScopeEnabled, bool, false); DECL_GFX_PREF(Live, "gfx.layerscope.port", LayerScopePort, int32_t, 23456); // Note that "gfx.logging.level" is defined in Logging.h - DECL_GFX_PREF(Once, "gfx.logging.crash.length", GfxLoggingCrashLength, uint32_t, 6); + DECL_GFX_PREF(Once, "gfx.logging.crash.length", GfxLoggingCrashLength, uint32_t, 16); // The maximums here are quite conservative, we can tighten them if problems show up. DECL_GFX_PREF(Once, "gfx.max-alloc-size", MaxAllocSize, int32_t, (int32_t)500000000); DECL_GFX_PREF(Once, "gfx.max-texture-size", MaxTextureSize, int32_t, (int32_t)32767); @@ -350,7 +348,6 @@ private: DECL_GFX_PREF(Once, "layers.offmainthreadcomposition.enabled", LayersOffMainThreadCompositionEnabled, bool, false); DECL_GFX_PREF(Once, "layers.offmainthreadcomposition.force-enabled", LayersOffMainThreadCompositionForceEnabled, bool, false); DECL_GFX_PREF(Live, "layers.offmainthreadcomposition.frame-rate", LayersCompositionFrameRate, int32_t,-1); - DECL_GFX_PREF(Once, "layers.offmainthreadcomposition.testing.enabled", LayersOffMainThreadCompositionTestingEnabled, bool, false); DECL_GFX_PREF(Live, "layers.orientation.sync.timeout", OrientationSyncMillis, uint32_t, (uint32_t)0); DECL_GFX_PREF(Once, "layers.overzealous-gralloc-unlocking", OverzealousGrallocUnlocking, bool, false); DECL_GFX_PREF(Once, "layers.prefer-d3d9", LayersPreferD3D9, bool, false); @@ -443,6 +440,9 @@ private: DECL_GFX_PREF(Live, "webgl.webgl2-compat-mode", WebGL2CompatMode, bool, false); + DECL_GFX_PREF(Once, "media.hardware-video-decoding.force-enabled", + HardwareVideoDecodingForceEnabled, bool, false); + // WARNING: // Please make sure that you've added your new preference to the list above in alphabetical order. // Please do not just append it to the end of the list. diff --git a/js/src/jit-test/tests/gc/bug-1242812.js b/js/src/jit-test/tests/gc/bug-1242812.js new file mode 100644 index 0000000000..61253bec1e --- /dev/null +++ b/js/src/jit-test/tests/gc/bug-1242812.js @@ -0,0 +1,5 @@ +if (!('oomTest' in this)) + quit(); + +var lfcode = new Array(); +oomTest(() => { let a = [2147483651]; [-1, 0, 1, 31, 32].sort(); }); diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build index fb57071f50..e487bb64fb 100644 --- a/js/src/jsapi-tests/moz.build +++ b/js/src/jsapi-tests/moz.build @@ -30,6 +30,7 @@ UNIFIED_SOURCES += [ 'testException.cpp', 'testExternalStrings.cpp', 'testFindSCCs.cpp', + 'testForceLexicalInitialization.cpp', 'testForOfIterator.cpp', 'testForwardSetProperty.cpp', 'testFreshGlobalEvalRedefinition.cpp', diff --git a/js/src/jsapi-tests/testForceLexicalInitialization.cpp b/js/src/jsapi-tests/testForceLexicalInitialization.cpp new file mode 100644 index 0000000000..48e4a65004 --- /dev/null +++ b/js/src/jsapi-tests/testForceLexicalInitialization.cpp @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + */ +/* 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/. */ + +#include "jsapi-tests/tests.h" +#include "vm/ScopeObject.h" +#include "jsfriendapi.h" + +BEGIN_TEST(testForceLexicalInitialization) +{ + // Attach an uninitialized lexical to a scope and ensure that it's + // set to undefined + RootedGlobalObject g(cx, cx->global()); + Rooted scope(cx, ClonedBlockObject::createGlobal(cx, g)); + + RootedValue uninitialized(cx, MagicValue(JS_UNINITIALIZED_LEXICAL)); + RootedPropertyName name(cx, Atomize(cx, "foopi", 4)->asPropertyName()); + RootedId id(cx, NameToId(name)); + unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT; + + NativeDefineProperty(cx, scope, id, uninitialized, nullptr, nullptr, attrs); + + // Verify that "foopi" is uninitialized + const Value v = scope->getSlot(scope->lookup(cx, id)->slot()); + CHECK(v.isMagic(JS_UNINITIALIZED_LEXICAL)); + + ForceLexicalInitialization(cx, scope); + + // Verify that "foopi" has been initialized to undefined + const Value v2 = scope->getSlot(scope->lookup(cx, id)->slot()); + CHECK(v2.isUndefined()); + + return true; +} +END_TEST(testForceLexicalInitialization) diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index f03f7f0023..e579d8e34f 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -918,6 +918,28 @@ JS::FormatStackDump(JSContext* cx, char* buf, bool showArgs, bool showLocals, bo return buf; } +extern JS_FRIEND_API(bool) +JS::ForceLexicalInitialization(JSContext *cx, HandleObject obj) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, obj); + + bool initializedAny = false; + NativeObject* nobj = &obj->as(); + + for (Shape::Range r(nobj->lastProperty()); !r.empty(); r.popFront()) { + Shape* s = &r.front(); + Value v = nobj->getSlot(s->slot()); + if (s->hasSlot() && v.isMagic() && v.whyMagic() == JS_UNINITIALIZED_LEXICAL) { + nobj->setSlot(s->slot(), UndefinedValue()); + initializedAny = true; + } + + } + return initializedAny; +} + struct DumpHeapTracer : public JS::CallbackTracer, public WeakMapTracer { const char* prefix; diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index b97a0b9ebe..4e619c2380 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -210,6 +210,13 @@ namespace JS { extern JS_FRIEND_API(char*) FormatStackDump(JSContext* cx, char* buf, bool showArgs, bool showLocals, bool showThisProps); +/** + * Set all of the uninitialized lexicals on an object to undefined. Return + * true if any lexicals were initialized and false otherwise. + * */ +extern JS_FRIEND_API(bool) +ForceLexicalInitialization(JSContext *cx, HandleObject obj); + } // namespace JS /** diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index c4a050a576..736d33418e 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -664,6 +664,7 @@ ReadEvalPrintLoop(JSContext* cx, FILE* in, FILE* out, bool compileOnly) */ int startline = lineno; typedef Vector CharBuffer; + RootedObject globalLexical(cx, &cx->global()->lexicalScope()); CharBuffer buffer(cx); do { ScheduleWatchdog(cx->runtime(), -1); @@ -699,6 +700,17 @@ ReadEvalPrintLoop(JSContext* cx, FILE* in, FILE* out, bool compileOnly) // Catch the error, report it, and keep going. JS_ReportPendingException(cx); } + // If a let or const fail to initialize they will remain in an unusable + // without further intervention. This call cleans up the global scope, + // setting uninitialized lexicals to undefined so that they may still + // be used. This behavior is _only_ acceptable in the context of the repl. + if (JS::ForceLexicalInitialization(cx, globalLexical)) { + fputs("Warning: According to the standard, after the above exception,\n" + "Warning: the global bindings should be permanently uninitialized.\n" + "Warning: We have non-standard-ly initialized them to `undefined`" + "for you.\nWarning: This nicety only happens in the JS shell.\n", + stderr); + } } while (!hitEOF && !sr->quitting); fprintf(out, "\n"); diff --git a/js/xpconnect/src/XPCLocale.cpp b/js/xpconnect/src/XPCLocale.cpp index 220d76fbd5..4fa6bc80ac 100644 --- a/js/xpconnect/src/XPCLocale.cpp +++ b/js/xpconnect/src/XPCLocale.cpp @@ -17,6 +17,7 @@ #include "nsComponentManagerUtils.h" #include "nsServiceManagerUtils.h" #include "mozilla/dom/EncodingUtils.h" +#include "mozilla/Preferences.h" #include "nsIUnicodeDecoder.h" #include "xpcpublic.h" @@ -252,6 +253,15 @@ xpc_LocalizeRuntime(JSRuntime* rt) JS_SetLocaleCallbacks(rt, new XPCLocaleCallbacks()); // Set the default locale. + + // Check a pref to see if we should use US English locale regardless + // of the system locale. + if (Preferences::GetBool("javascript.use_us_english_locale", false)) { + return JS_SetDefaultLocale(rt, "en-US"); + } + + // No pref has been found, so get the default locale from the + // application's locale. nsCOMPtr localeService = do_GetService(NS_LOCALESERVICE_CONTRACTID); if (!localeService) diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 622449c152..09484f7e4f 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -16,6 +16,7 @@ #include "mozilla/gfx/PathHelpers.h" #include "mozilla/Likely.h" #include "mozilla/Maybe.h" +#include "mozilla/unused.h" #include "mozilla/MemoryReporting.h" #include "nsCharTraits.h" #include "nsFontMetrics.h" @@ -1169,9 +1170,48 @@ nsLayoutUtils::SetDisplayPortMargins(nsIContent* aContent, } } + nsIFrame* frame = GetScrollFrameFromContent(aContent); + nsIScrollableFrame* scrollableFrame = frame ? frame->GetScrollTargetFrame() : nullptr; + if (!scrollableFrame) { + return true; + } + + scrollableFrame->TriggerDisplayPortExpiration(); + // Display port margins changing means that the set of visible images may - // have drastically changed. Schedule an update. - aPresShell->ScheduleImageVisibilityUpdate(); + // have drastically changed. Check if we should schedule an update. + nsRect oldDisplayPort; + bool hadDisplayPort = + scrollableFrame->GetDisplayPortAtLastImageVisibilityUpdate(&oldDisplayPort); + + nsRect newDisplayPort; + Unused << GetDisplayPort(aContent, &newDisplayPort); + + bool needImageVisibilityUpdate = !hadDisplayPort; + // Check if the total size has changed by a large factor. + if (!needImageVisibilityUpdate) { + if ((newDisplayPort.width > 2 * oldDisplayPort.width) || + (oldDisplayPort.width > 2 * newDisplayPort.width) || + (newDisplayPort.height > 2 * oldDisplayPort.height) || + (oldDisplayPort.height > 2 * newDisplayPort.height)) { + needImageVisibilityUpdate = true; + } + } + // Check if it's moved by a significant amount. + if (!needImageVisibilityUpdate) { + if (nsRect* baseData = static_cast(aContent->GetProperty(nsGkAtoms::DisplayPortBase))) { + nsRect base = *baseData; + if ((std::abs(newDisplayPort.X() - oldDisplayPort.X()) > base.width) || + (std::abs(newDisplayPort.XMost() - oldDisplayPort.XMost()) > base.width) || + (std::abs(newDisplayPort.Y() - oldDisplayPort.Y()) > base.height) || + (std::abs(newDisplayPort.YMost() - oldDisplayPort.YMost()) > base.height)) { + needImageVisibilityUpdate = true; + } + } + } + if (needImageVisibilityUpdate) { + aPresShell->ScheduleImageVisibilityUpdate(); + } return true; } @@ -1206,6 +1246,13 @@ nsLayoutUtils::HasCriticalDisplayPort(nsIContent* aContent) return GetCriticalDisplayPort(aContent, nullptr); } +void +nsLayoutUtils::RemoveDisplayPort(nsIContent* aContent) +{ + aContent->DeleteProperty(nsGkAtoms::DisplayPort); + aContent->DeleteProperty(nsGkAtoms::DisplayPortMargins); +} + nsContainerFrame* nsLayoutUtils::LastContinuationWithChild(nsContainerFrame* aFrame) { @@ -3056,18 +3103,13 @@ nsLayoutUtils::CalculateAndSetDisplayPortMargins(nsIScrollableFrame* aScrollFram void nsLayoutUtils::MaybeCreateDisplayPort(nsDisplayListBuilder& aBuilder, - nsIFrame* aScrollFrame, - nsRect aDisplayPortBase) { + nsIFrame* aScrollFrame) { nsIContent* content = aScrollFrame->GetContent(); nsIScrollableFrame* scrollableFrame = do_QueryFrame(aScrollFrame); if (!content || !scrollableFrame) { return; } - // Set the base rect. Note that this will not influence 'haveDisplayPort', - // which is based on either the whole rect or margins being set. - SetDisplayPortBase(content, aDisplayPortBase); - bool haveDisplayPort = HasDisplayPort(content); // We perform an optimization where we ensure that at least one @@ -3129,6 +3171,37 @@ nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(nsIFrame* aFra } } +void +nsLayoutUtils::ExpireDisplayPortOnAsyncScrollableAncestor(nsIFrame* aFrame) +{ + nsIFrame* frame = aFrame; + while (frame) { + frame = nsLayoutUtils::GetCrossDocParentFrame(frame); + if (!frame) { + break; + } + nsIScrollableFrame* scrollAncestor = GetAsyncScrollableAncestorFrame(frame); + if (!scrollAncestor) { + break; + } + frame = do_QueryFrame(scrollAncestor); + MOZ_ASSERT(frame); + MOZ_ASSERT(scrollAncestor->WantAsyncScroll() || + frame->PresContext()->PresShell()->GetRootScrollFrame() == frame); + if (nsLayoutUtils::AsyncPanZoomEnabled(frame) && + nsLayoutUtils::HasDisplayPort(frame->GetContent())) { + scrollAncestor->TriggerDisplayPortExpiration(); + // Stop after the first trigger. If it failed, there's no point in + // continuing because all the rest of the frames we encounter are going + // to be ancestors of |scrollAncestor| which will keep its displayport. + // If the trigger succeeded, we stop because when the trigger executes + // it will call this function again to trigger the next ancestor up the + // chain. + break; + } + } +} + nsresult nsLayoutUtils::PaintFrame(nsRenderingContext* aRenderingContext, nsIFrame* aFrame, const nsRegion& aDirtyRegion, nscolor aBackstop, @@ -3180,18 +3253,12 @@ nsLayoutUtils::PaintFrame(nsRenderingContext* aRenderingContext, nsIFrame* aFram } nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame(); - bool usingDisplayPort = false; - nsRect displayport; if (rootScrollFrame && !aFrame->GetParent()) { nsIScrollableFrame* rootScrollableFrame = presShell->GetRootScrollFrameAsScrollable(); MOZ_ASSERT(rootScrollableFrame); - displayport = aFrame->GetVisualOverflowRectRelativeToSelf(); - usingDisplayPort = rootScrollableFrame->DecideScrollableLayer(&builder, - &displayport, /* aAllowCreateDisplayPort = */ true); - - if (!gfxPrefs::LayoutUseContainersForRootFrames()) { - usingDisplayPort = false; - } + nsRect displayPortBase = aFrame->GetVisualOverflowRectRelativeToSelf(); + Unused << rootScrollableFrame->DecideScrollableLayer(&builder, &displayPortBase, + /* aAllowCreateDisplayPort = */ true); } nsDisplayList hoistedScrollItemStorage; @@ -3207,11 +3274,7 @@ nsLayoutUtils::PaintFrame(nsRenderingContext* aRenderingContext, nsIFrame* aFram // |ignoreViewportScrolling| and |usingDisplayPort| are persistent // document-rendering state. We rely on PresShell to flush // retained layers as needed when that persistent state changes. - if (!usingDisplayPort) { - visibleRegion = aFrame->GetVisualOverflowRectRelativeToSelf(); - } else { - visibleRegion = displayport; - } + visibleRegion = aFrame->GetVisualOverflowRectRelativeToSelf(); } else { visibleRegion = aDirtyRegion; } diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index d458a879a5..89133f2e30 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -241,6 +241,11 @@ public: */ static bool HasCriticalDisplayPort(nsIContent* aContent); + /** + * Remove the displayport for the given element. + */ + static void RemoveDisplayPort(nsIContent* aContent); + /** * Use heuristics to figure out the child list that * aChildFrame is currently in. @@ -2740,14 +2745,10 @@ public: * displayport yet (as tracked by |aBuilder|), calculate and set a * displayport. * - * If this function creates a displayport, it computes margins and stores - * |aDisplayPortBase| as the base rect. - * * This is intended to be called during display list building. */ static void MaybeCreateDisplayPort(nsDisplayListBuilder& aBuilder, - nsIFrame* aScrollFrame, - nsRect aDisplayPortBase); + nsIFrame* aScrollFrame); static nsIScrollableFrame* GetAsyncScrollableAncestorFrame(nsIFrame* aTarget); @@ -2757,6 +2758,12 @@ public: */ static void SetZeroMarginDisplayPortOnAsyncScrollableAncestors(nsIFrame* aFrame, RepaintMode aRepaintMode); + /** + * Finds the closest ancestor async scrollable frame from aFrame that has a + * displayport and attempts to trigger the displayport expiry on that + * ancestor. + */ + static void ExpireDisplayPortOnAsyncScrollableAncestor(nsIFrame* aFrame); static bool IsOutlineStyleAutoEnabled(); diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 87487b8259..73529e807a 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -5595,6 +5595,7 @@ PresShell::MarkImagesInSubtreeVisible(nsIFrame* aFrame, const nsRect& aRect) nsIScrollableFrame* scrollFrame = do_QueryFrame(aFrame); if (scrollFrame) { + scrollFrame->NotifyImageVisibilityUpdate(); nsRect displayPort; bool usingDisplayport = nsLayoutUtils::GetDisplayPortForVisibilityTesting( @@ -5706,10 +5707,6 @@ PresShell::UpdateImageVisibility() if (IgnoringViewportScrolling()) { builder.SetIgnoreScrollFrame(rootScroll); - // The ExpandRectToNearlyVisible that the root scroll frame would do gets short - // circuited due to us ignoring the root scroll frame, so we do it here. - nsIScrollableFrame* rootScrollable = do_QueryFrame(rootScroll); - updateRect = rootScrollable->ExpandRectToNearlyVisible(updateRect); } } builder.IgnorePaintSuppression(); diff --git a/layout/base/tests/test_scroll_snapping.html b/layout/base/tests/test_scroll_snapping.html index c17b5eeb7e..1ba862ee1c 100644 --- a/layout/base/tests/test_scroll_snapping.html +++ b/layout/base/tests/test_scroll_snapping.html @@ -9,8 +9,8 @@

-
-
+
+
@@ -574,7 +574,7 @@ var testCases = [
   },
 
   {
-    "description"             : "Ensure that when paging down/up that the farthest snap point before the destination is selected and prioritized over a snap point that is past the destination, even if the snap point past the destination is closer to the destination.  Setup - two snap points between current position and destination and one snap point past the destination which is closer than any of the other points. Scrollable rect size is 250px. (Page Down)",
+    "description"             : "Ensure that when paging down/up that the farthest snap point before the destination is selected and prioritized over a snap point that is past the destination, even if the snap point past the destination is closer to the destination.  Setup - two snap points between current position and destination and one snap point past the destination which is closer than any of the other points. Scrollable rect size is 500px. (Page Down)",
     "scrollSnapType"          : "mandatory",
     "scrollSnapPointsX"       : "none",
     "scrollSnapPointsY"       : "none",
@@ -589,7 +589,7 @@ var testCases = [
   },
 
   {
-    "description"             : "Ensure that when paging down/up that the farthest snap point before the destination is selected and prioritized over a snap point that is past the destination, even if the snap point past the destination is closer to the destination.  Setup - two snap points between current position and destination and one snap point past the destination which is closer than any of the other points. Scrollable rect size is 250px. (Page Up)",
+    "description"             : "Ensure that when paging down/up that the farthest snap point before the destination is selected and prioritized over a snap point that is past the destination, even if the snap point past the destination is closer to the destination.  Setup - two snap points between current position and destination and one snap point past the destination which is closer than any of the other points. Scrollable rect size is 500px. (Page Up)",
     "scrollSnapType"          : "mandatory",
     "scrollSnapPointsX"       : "none",
     "scrollSnapPointsY"       : "none",
@@ -604,7 +604,7 @@ var testCases = [
   },
 
   {
-    "description"             : "Ensure that when paging down/up that the closest snap point past the destination is selected when no snap points exist between the starting position and the destination.  Additionally, a snap point closer to the destination than the one past the snap point, but not in the scrolling direction, must not be selected.  Setup - Two snap points beyond the destination and one snap point in the opposite direction of scrolling which is closest to the destination. Scrollable rect size is 250px. (Page Down)",
+    "description"             : "Ensure that when paging down/up that the closest snap point past the destination is selected when no snap points exist between the starting position and the destination.  Additionally, a snap point closer to the destination than the one past the snap point, but not in the scrolling direction, must not be selected.  Setup - Two snap points beyond the destination and one snap point in the opposite direction of scrolling which is closest to the destination. Scrollable rect size is 500px. (Page Down)",
     "scrollSnapType"          : "mandatory",
     "scrollSnapPointsX"       : "none",
     "scrollSnapPointsY"       : "none",
@@ -619,7 +619,7 @@ var testCases = [
   },
 
   {
-    "description"             : "Ensure that when paging down/up that the closest snap point past the destination is selected when no snap points exist between the starting position and the destination.  Additionally, a snap point closer to the destination than the one past the snap point, but not in the scrolling direction, must not be selected.  Setup - Two snap points beyond the destination and one snap point in the opposite direction of scrolling which is closest to the destination. Scrollable rect size is 250px. (Page Up)",
+    "description"             : "Ensure that when paging down/up that the closest snap point past the destination is selected when no snap points exist between the starting position and the destination.  Additionally, a snap point closer to the destination than the one past the snap point, but not in the scrolling direction, must not be selected.  Setup - Two snap points beyond the destination and one snap point in the opposite direction of scrolling which is closest to the destination. Scrollable rect size is 500px. (Page Up)",
     "scrollSnapType"          : "mandatory",
     "scrollSnapPointsX"       : "none",
     "scrollSnapPointsY"       : "none",
@@ -687,6 +687,25 @@ var lastScrollTop;
 var lastScrollLeft;
 var stopFrameCount;
 
+// The tests should work the same way when all the values for the scroll
+// container are provided in percentages. To assert that, we just duplicate all
+// the test cases and replace the pixel values related to the scroll container
+// with percentage values, based on its clientWidth/Height sespectively.
+function addPercentageTests() {
+  var width = sc.clientWidth;
+  var height = sc.clientHeight;
+  var pxRegexp = /(\d+)px/;
+  var rewriteW = (_, w) => (parseInt(w, 10) / width * 100) + "%";
+  var rewriteH = (_, h) => (parseInt(h, 10) / height * 100) + "%";
+  testCases = testCases.concat(testCases.map(testCase => Object.assign({}, testCase, {
+    description: "With Percentages: " + testCase.description,
+    scrollSnapPointsX: testCase.scrollSnapPointsX.replace(pxRegexp, rewriteW),
+    scrollSnapPointsY: testCase.scrollSnapPointsY.replace(pxRegexp, rewriteH),
+    scrollSnapDestination: testCase.scrollSnapDestination
+      .replace(pxRegexp, rewriteW).replace(pxRegexp, rewriteH),
+  })));
+}
+
 function initTest() {
   var testCase = testCases[step];
   sc.style.scrollSnapType = testCase.scrollSnapType;
@@ -726,6 +745,8 @@ function testScrollSnapping() {
   sc = document.getElementById("sc");
   sd = document.getElementById("sd");
 
+  addPercentageTests();
+
   initTest();
 }
 
diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp
index 0bbe0cf59d..30006fd6fe 100644
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -248,7 +248,6 @@ static void Shutdown();
 #include "nsITelephonyService.h"
 #include "nsIVoicemailService.h"
 
-#include "mozilla/dom/FakeTVService.h"
 #include "mozilla/dom/TVServiceFactory.h"
 #include "mozilla/dom/TVTypes.h"
 #include "nsITVService.h"
@@ -396,8 +395,6 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITelephonyService,
                                          NS_CreateTelephonyService)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIVoicemailService,
                                          NS_CreateVoicemailService)
-NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(FakeTVService,
-                                         TVServiceFactory::CreateFakeTVService)
 NS_GENERIC_FACTORY_CONSTRUCTOR(TVTunerData)
 NS_GENERIC_FACTORY_CONSTRUCTOR(TVChannelData)
 NS_GENERIC_FACTORY_CONSTRUCTOR(TVProgramData)
@@ -866,7 +863,6 @@ NS_DEFINE_NAMED_CID(NS_SYNTHVOICEREGISTRY_CID);
 #ifdef ACCESSIBILITY
 NS_DEFINE_NAMED_CID(NS_ACCESSIBILITY_SERVICE_CID);
 #endif
-NS_DEFINE_NAMED_CID(FAKE_TV_SERVICE_CID);
 NS_DEFINE_NAMED_CID(TV_TUNER_DATA_CID);
 NS_DEFINE_NAMED_CID(TV_CHANNEL_DATA_CID);
 NS_DEFINE_NAMED_CID(TV_PROGRAM_DATA_CID);
@@ -1173,7 +1169,6 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = {
   { &kTELEPHONY_SERVICE_CID, false, nullptr, nsITelephonyServiceConstructor },
   { &kNS_MOBILE_CONNECTION_SERVICE_CID, false, NULL, nsIMobileConnectionServiceConstructor },
   { &kNS_VOICEMAIL_SERVICE_CID, false, nullptr, nsIVoicemailServiceConstructor },
-  { &kFAKE_TV_SERVICE_CID, false, nullptr, FakeTVServiceConstructor },
   { &kTV_TUNER_DATA_CID, false, nullptr, TVTunerDataConstructor },
   { &kTV_CHANNEL_DATA_CID, false, nullptr, TVChannelDataConstructor },
   { &kTV_PROGRAM_DATA_CID, false, nullptr, TVProgramDataConstructor },
@@ -1341,7 +1336,6 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = {
   { "@mozilla.org/accessibleRetrieval;1", &kNS_ACCESSIBILITY_SERVICE_CID },
 #endif
   { TELEPHONY_SERVICE_CONTRACTID, &kTELEPHONY_SERVICE_CID },
-  { FAKE_TV_SERVICE_CONTRACTID, &kFAKE_TV_SERVICE_CID },
   { TV_TUNER_DATA_CONTRACTID, &kTV_TUNER_DATA_CID },
   { TV_CHANNEL_DATA_CONTRACTID, &kTV_CHANNEL_DATA_CID },
   { TV_PROGRAM_DATA_CONTRACTID, &kTV_PROGRAM_DATA_CID },
diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp
index 4ded186cd3..a2b260a83b 100644
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -1155,6 +1155,12 @@ nsIFrame::Extend3DContext() const
     return false;
   }
 
+  // Opacity can only be only the root or leaves of a preserve-3d context
+  // as it requires flattening.
+  if (HasOpacity() && Combines3DTransformWithAncestors()) {
+    return false;
+  }
+
   nsRect temp;
   return !nsFrame::ShouldApplyOverflowClipping(this, disp) &&
          !GetClipPropClipRect(disp, &temp, GetSize()) &&
@@ -1979,6 +1985,28 @@ WrapSeparatorTransform(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
   }
 }
 
+static void
+CreateOpacityItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+                  nsDisplayList& aList, bool aItemForEventsOnly)
+{
+  // Don't clip nsDisplayOpacity items. We clip their descendants instead.
+  // The clip we would set on an element with opacity would clip
+  // all descendant content, but some should not be clipped.
+  // We clear both regular clips and scroll clips. If this item's animated
+  // geometry root has async scrolling, then the async scroll transform will
+  // be applied on the opacity's descendants (because that's where the
+  // scroll clip will be). However, this won't work if the opacity item is
+  // inactive, which is why we record the pre-clear scroll clip here.
+  const DisplayItemScrollClip* scrollClipForSameAGRChildren =
+    aBuilder->ClipState().GetCurrentInnermostScrollClip();
+  DisplayListClipState::AutoSaveRestore opacityClipState(aBuilder);
+  opacityClipState.ClearIncludingScrollClip();
+  aList.AppendNewToTop(
+      new (aBuilder) nsDisplayOpacity(aBuilder, aFrame, &aList,
+                                      scrollClipForSameAGRChildren,
+                                      aItemForEventsOnly));
+}
+
 void
 nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder,
                                              const nsRect&         aDirtyRect,
@@ -2205,6 +2233,8 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder,
     clipState.Restore();
   }
 
+  bool is3DContextRoot = Extend3DContext() && !Combines3DTransformWithAncestors();
+
   /* If there are any SVG effects, wrap the list up in an SVG effects item
    * (which also handles CSS group opacity). Note that we create an SVG effects
    * item even if resultList is empty, since a filter can produce graphical
@@ -2217,26 +2247,16 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder,
     resultList.AppendNewToTop(
         new (aBuilder) nsDisplaySVGEffects(aBuilder, this, &resultList));
   }
-  /* Else, if the list is non-empty and there is CSS group opacity without SVG
-   * effects, wrap it up in an opacity item.
-   */
-  else if (useOpacity && !resultList.IsEmpty()) {
-    // Don't clip nsDisplayOpacity items. We clip their descendants instead.
-    // The clip we would set on an element with opacity would clip
-    // all descendant content, but some should not be clipped.
-    // We clear both regular clips and scroll clips. If this item's animated
-    // geometry root has async scrolling, then the async scroll transform will
-    // be applied on the opacity's descendants (because that's where the
-    // scroll clip will be). However, this won't work if the opacity item is
-    // inactive, which is why we record the pre-clear scroll clip here.
-    const DisplayItemScrollClip* scrollClipForSameAGRChildren =
-      aBuilder->ClipState().GetCurrentInnermostScrollClip();
-    DisplayListClipState::AutoSaveRestore opacityClipState(aBuilder);
-    opacityClipState.ClearIncludingScrollClip();
-    resultList.AppendNewToTop(
-        new (aBuilder) nsDisplayOpacity(aBuilder, this, &resultList,
-                                        scrollClipForSameAGRChildren,
-                                        opacityItemForEventsOnly));
+  else if (useOpacity && !resultList.IsEmpty() && !is3DContextRoot) {
+    /* If this element is the root of a preserve-3d context, then we want
+     * to make sure any opacity items are on the outside of the transform
+     * so that they don't interfere with the chain of nsDisplayTransforms.
+     * Opacity on preserve-3d leaves need to be inside the transform for the
+     * same reason, and we do this in the general case as well to preserve
+     * existing behaviour.
+     */
+    CreateOpacityItem(aBuilder, this, resultList, opacityItemForEventsOnly);
+    useOpacity = false;
   }
 
   /* If we're going to apply a transformation and don't have preserve-3d set, wrap
@@ -2310,6 +2330,12 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder,
           GetContainingBlock()->GetContent()->GetPrimaryFrame(), &resultList));
     }
 
+    /* If we need an opacity item, but didn't do it earlier, add it now on the
+     * outside of the transform.
+     */
+    if (useOpacity && !usingSVGEffects) {
+      CreateOpacityItem(aBuilder, this, resultList, opacityItemForEventsOnly);
+    }
   }
 
   /* If we have sticky positioning, wrap it in a sticky position item.
diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp
index 779eac62a0..0000cecd30 100644
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1847,6 +1847,8 @@ ScrollFrameHelper::ScrollFrameHelper(nsContainerFrame* aOuter,
   , mLastPos(-1, -1)
   , mScrollPosForLayerPixelAlignment(-1, -1)
   , mLastUpdateImagesPos(-1, -1)
+  , mHadDisplayPortAtLastImageUpdate(false)
+  , mDisplayPortAtLastImageUpdate()
   , mNeverHasVerticalScrollbar(false)
   , mNeverHasHorizontalScrollbar(false)
   , mHasVerticalScrollbar(false)
@@ -1866,6 +1868,7 @@ ScrollFrameHelper::ScrollFrameHelper(nsContainerFrame* aOuter,
   , mHasBeenScrolledRecently(false)
   , mCollapsedResizer(false)
   , mWillBuildScrollableLayer(false)
+  , mIsScrollParent(false)
   , mIsScrollableLayerInRootContainer(false)
   , mHasBeenScrolled(false)
   , mIgnoreMomentumScroll(false)
@@ -1913,6 +1916,10 @@ ScrollFrameHelper::~ScrollFrameHelper()
     mScrollActivityTimer->Cancel();
     mScrollActivityTimer = nullptr;
   }
+  if (mDisplayPortExpiryTimer) {
+    mDisplayPortExpiryTimer->Cancel();
+    mDisplayPortExpiryTimer = nullptr;
+  }
 }
 
 /*
@@ -1997,8 +2004,12 @@ ScrollFrameHelper::CompleteAsyncScroll(const nsRect &aRange, nsIAtom* aOrigin)
 }
 
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+struct PluginSearchCtx {
+  nsIFrame* outer;
+  bool begin;
+};
 static void
-NotifyPluginFramesCallback(nsISupports* aSupports, void* aFlag)
+NotifyPluginFramesCallback(nsISupports* aSupports, void* aCtx)
 {
   nsCOMPtr content = do_QueryInterface(aSupports);
   if (content) {
@@ -2006,16 +2017,22 @@ NotifyPluginFramesCallback(nsISupports* aSupports, void* aFlag)
     if (frame) {
       nsPluginFrame* plugin = do_QueryFrame(frame);
       if (plugin) {
-        plugin->SetScrollVisibility(aFlag != nullptr);
+        PluginSearchCtx* pCtx = static_cast(aCtx);
+        // Check to be sure this plugin is contained within a subframe of
+        // the nsGfxScrollFrame that initiated this callback.
+        if (nsLayoutUtils::IsAncestorFrameCrossDoc(pCtx->outer, plugin, nullptr)) {
+          plugin->SetScrollVisibility(pCtx->begin);
+        }
       }
     }
   }
 }
+
 static bool
-NotifyPluginSubframesCallback(nsIDocument* aDocument, void* aFlag)
+NotifyPluginSubframesCallback(nsIDocument* aDocument, void* aCtx)
 {
   aDocument->EnumerateActivityObservers(NotifyPluginFramesCallback,
-                                        aFlag);
+                                        aCtx);
   return true;
 }
 #endif
@@ -2030,11 +2047,11 @@ ScrollFrameHelper::NotifyPluginFrames(AsyncScrollEventType aEvent)
   if (XRE_IsContentProcess()) {
     if (aEvent != mAsyncScrollEvent) {
       nsPresContext* presContext = mOuter->PresContext();
-      bool begin = (aEvent == BEGIN_DOM);
+      PluginSearchCtx ctx = { mOuter, (aEvent == BEGIN_DOM) };
       presContext->Document()->EnumerateActivityObservers(NotifyPluginFramesCallback,
-                                                          (void*)begin);
+                                                          (void*)&ctx);
       presContext->Document()->EnumerateSubDocuments(NotifyPluginSubframesCallback,
-                                                     (void*)begin);
+                                                     (void*)&ctx);
 
       mAsyncScrollEvent = aEvent;
     }
@@ -2142,7 +2159,7 @@ ScrollFrameHelper::ScrollToWithOrigin(nsPoint aScrollPosition,
           mAsyncScroll = nullptr;
         }
 
-        if (nsLayoutUtils::AsyncPanZoomEnabled(mOuter)) {
+        if (nsLayoutUtils::AsyncPanZoomEnabled(mOuter) && WantAsyncScroll()) {
           if (mApzSmoothScrollDestination == Some(mDestination) &&
               mScrollGeneration == sScrollGenerationCounter) {
             // If we already sent APZ a smooth-scroll request to this
@@ -2326,6 +2343,43 @@ bool ScrollFrameHelper::IsAlwaysActive() const
           styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN);
 }
 
+/*static*/ void
+RemoveDisplayPortCallback(nsITimer* aTimer, void* aClosure)
+{
+  ScrollFrameHelper* helper = static_cast(aClosure);
+
+  // This function only ever gets called from the expiry timer, so it must
+  // be non-null here. Set it to null here so that we don't keep resetting
+  // it unnecessarily in MarkRecentlyScrolled().
+  MOZ_ASSERT(helper->mDisplayPortExpiryTimer);
+  helper->mDisplayPortExpiryTimer = nullptr;
+
+  if (!helper->AllowDisplayPortExpiration() || helper->mIsScrollParent) {
+    // If this is a scroll parent for some other scrollable frame, don't
+    // expire the displayport because it would break scroll handoff. Once the
+    // descendant scrollframes have their displayports expired, they will
+    // trigger the displayport expiration on this scrollframe as well, and
+    // mIsScrollParent will presumably be false when that kicks in.
+    return;
+  }
+
+  // Remove the displayport from this scrollframe if it's been a while
+  // since it's scrolled, except if it needs to be always active. Note that
+  // there is one scrollframe that doesn't fall under this general rule, and
+  // that is the one that nsLayoutUtils::MaybeCreateDisplayPort decides to put
+  // a displayport on (i.e. the first scrollframe that WantAsyncScroll()s).
+  // If that scrollframe is this one, we remove the displayport anyway, and
+  // as part of the next paint MaybeCreateDisplayPort will put another
+  // displayport back on it. Although the displayport will "flicker" off and
+  // back on, the layer itself should never disappear, because this all
+  // happens between actual painting. If the displayport is reset to a
+  // different position that's ok; this scrollframe hasn't been scrolled
+  // recently and so the reset should be correct.
+  nsLayoutUtils::RemoveDisplayPort(helper->mOuter->GetContent());
+  nsLayoutUtils::ExpireDisplayPortOnAsyncScrollableAncestor(helper->mOuter);
+  helper->mOuter->SchedulePaint();
+}
+
 void ScrollFrameHelper::MarkNotRecentlyScrolled()
 {
   if (!mHasBeenScrolledRecently)
@@ -2349,6 +2403,47 @@ void ScrollFrameHelper::MarkRecentlyScrolled()
     }
     gScrollFrameActivityTracker->AddObject(this);
   }
+
+  // If we just scrolled and there's a displayport expiry timer in place,
+  // reset the timer.
+  ResetDisplayPortExpiryTimer();
+}
+
+void ScrollFrameHelper::ResetDisplayPortExpiryTimer()
+{
+  if (mDisplayPortExpiryTimer) {
+    mDisplayPortExpiryTimer->InitWithFuncCallback(
+      RemoveDisplayPortCallback, this,
+      gfxPrefs::APZDisplayPortExpiryTime(), nsITimer::TYPE_ONE_SHOT);
+  }
+}
+
+bool ScrollFrameHelper::AllowDisplayPortExpiration()
+{
+  if (IsAlwaysActive()) {
+    return false;
+  }
+  if (mIsRoot && mOuter->PresContext()->IsRoot()) {
+    return false;
+  }
+  return true;
+}
+
+void ScrollFrameHelper::TriggerDisplayPortExpiration()
+{
+  if (!AllowDisplayPortExpiration()) {
+    return;
+  }
+
+  if (!gfxPrefs::APZDisplayPortExpiryTime()) {
+    // a zero time disables the expiry
+    return;
+  }
+
+  if (!mDisplayPortExpiryTimer) {
+    mDisplayPortExpiryTimer = do_CreateInstance("@mozilla.org/timer;1");
+  }
+  ResetDisplayPortExpiryTimer();
 }
 
 void ScrollFrameHelper::ScrollVisual()
@@ -2482,6 +2577,23 @@ ScrollFrameHelper::ScheduleSyntheticMouseMove()
         ScrollActivityCallback, this, 100, nsITimer::TYPE_ONE_SHOT);
 }
 
+void
+ScrollFrameHelper::NotifyImageVisibilityUpdate()
+{
+  mLastUpdateImagesPos = GetScrollPosition();
+  mHadDisplayPortAtLastImageUpdate =
+    nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &mDisplayPortAtLastImageUpdate);
+}
+
+bool
+ScrollFrameHelper::GetDisplayPortAtLastImageVisibilityUpdate(nsRect* aDisplayPort)
+{
+  if (mHadDisplayPortAtLastImageUpdate) {
+    *aDisplayPort = mDisplayPortAtLastImageUpdate;
+  }
+  return mHadDisplayPortAtLastImageUpdate;
+}
+
 void
 ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange, nsIAtom* aOrigin)
 {
@@ -2534,10 +2646,6 @@ ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange, nsIAtom* aOri
     needImageVisibilityUpdate = true;
   }
 
-  if (needImageVisibilityUpdate) {
-    presContext->PresShell()->ScheduleImageVisibilityUpdate();
-  }
-
   // notify the listeners.
   for (uint32_t i = 0; i < mListeners.Length(); i++) {
     mListeners[i]->ScrollPositionWillChange(pt.x, pt.y);
@@ -2568,9 +2676,17 @@ ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange, nsIAtom* aOri
 
     if (!displayPort.IsEqualEdges(oldDisplayPort)) {
       mOuter->SchedulePaint();
+
+      if (needImageVisibilityUpdate) {
+        presContext->PresShell()->ScheduleImageVisibilityUpdate();
+      }
     }
   } else {
     mOuter->SchedulePaint();
+
+    if (needImageVisibilityUpdate) {
+      presContext->PresShell()->ScheduleImageVisibilityUpdate();
+    }
   }
 
   if (mOuter->ChildrenHavePerspective()) {
@@ -2681,11 +2797,10 @@ struct HoveredStateComparator
 
 void
 ScrollFrameHelper::AppendScrollPartsTo(nsDisplayListBuilder*   aBuilder,
-                                           const nsRect&           aDirtyRect,
-                                           const nsDisplayListSet& aLists,
-                                           bool                    aUsingDisplayPort,
-                                           bool                    aCreateLayer,
-                                           bool                    aPositioned)
+                                       const nsRect&           aDirtyRect,
+                                       const nsDisplayListSet& aLists,
+                                       bool                    aCreateLayer,
+                                       bool                    aPositioned)
 {
   nsITheme* theme = mOuter->PresContext()->GetTheme();
   if (theme &&
@@ -2739,9 +2854,12 @@ ScrollFrameHelper::AppendScrollPartsTo(nsDisplayListBuilder*   aBuilder,
     }
 
     // The display port doesn't necessarily include the scrollbars, so just
-    // include all of the scrollbars if we have a display port.
-    nsRect dirty = aUsingDisplayPort ?
-      scrollParts[i]->GetVisualOverflowRectRelativeToParent() : aDirtyRect;
+    // include all of the scrollbars if we are in a RCD-RSF. We only do
+    // this for the root scrollframe of the root content document, which is
+    // zoomable, and where the scrollbar sizes are bounded by the widget.
+    nsRect dirty = mIsRoot && mOuter->PresContext()->IsRootContentDocument()
+                   ? scrollParts[i]->GetVisualOverflowRectRelativeToParent()
+                   : aDirtyRect;
     nsDisplayListBuilder::AutoBuildingDisplayList
       buildingForChild(aBuilder, scrollParts[i],
                        dirty + mOuter->GetOffsetTo(scrollParts[i]), true);
@@ -2898,7 +3016,7 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                     const nsDisplayListSet& aLists)
 {
   if (aBuilder->IsForImageVisibility()) {
-    mLastUpdateImagesPos = GetScrollPosition();
+    NotifyImageVisibilityUpdate();
   }
 
   mOuter->DisplayBorderBackgroundOutline(aBuilder, aLists);
@@ -2917,6 +3035,39 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
     }
   }
 
+  // It's safe to get this value before the DecideScrollableLayer call below
+  // because that call cannot create a displayport for root scroll frames,
+  // and hence it cannot create an ignore scroll frame.
+  bool ignoringThisScrollFrame =
+    aBuilder->GetIgnoreScrollFrame() == mOuter || IsIgnoringViewportClipping();
+
+  // Overflow clipping can never clip frames outside our subtree, so there
+  // is no need to worry about whether we are a moving frame that might clip
+  // non-moving frames.
+  // Not all our descendants will be clipped by overflow clipping, but all
+  // the ones that aren't clipped will be out of flow frames that have already
+  // had dirty rects saved for them by their parent frames calling
+  // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our
+  // dirty rect here.
+  nsRect dirtyRect = aDirtyRect;
+  if (!ignoringThisScrollFrame) {
+    dirtyRect = dirtyRect.Intersect(mScrollPort);
+  }
+
+  Unused << DecideScrollableLayer(aBuilder, &dirtyRect,
+              /* aAllowCreateDisplayPort = */ !mIsRoot);
+
+  bool usingDisplayPort = aBuilder->IsPaintingToWindow() &&
+    nsLayoutUtils::HasDisplayPort(mOuter->GetContent());
+
+  if (aBuilder->IsForImageVisibility()) {
+    // We expand the dirty rect to catch images just outside of the scroll port.
+    // We use the dirty rect instead of the whole scroll port to prevent
+    // too much expansion in the presence of very large (bigger than the
+    // viewport) scroll ports.
+    dirtyRect = ExpandRectToNearlyVisible(dirtyRect);
+  }
+
   // We put non-overlay scrollbars in their own layers when this is the root
   // scroll frame and we are a toplevel content document. In this situation,
   // the scrollbar(s) would normally be assigned their own layer anyway, since
@@ -2928,10 +3079,7 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
   bool createLayersForScrollbars = mIsRoot &&
     mOuter->PresContext()->IsRootContentDocument();
 
-  if (aBuilder->GetIgnoreScrollFrame() == mOuter || IsIgnoringViewportClipping()) {
-    bool usingDisplayPort = aBuilder->IsPaintingToWindow() &&
-      nsLayoutUtils::HasDisplayPort(mOuter->GetContent());
-
+  if (ignoringThisScrollFrame) {
     // Root scrollframes have FrameMetrics and clipping on their container
     // layers, so don't apply clipping again.
     mAddClipRectToLayer = false;
@@ -2943,7 +3091,7 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
 
     if (addScrollBars) {
       // Add classic scrollbars.
-      AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, usingDisplayPort,
+      AppendScrollPartsTo(aBuilder, aDirtyRect, aLists,
                           createLayersForScrollbars, false);
     }
 
@@ -2951,11 +3099,11 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
     // The scrolled frame shouldn't have its own background/border, so we
     // can just pass aLists directly.
     mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame,
-                                     aDirtyRect, aLists);
+                                     dirtyRect, aLists);
 
     if (addScrollBars) {
       // Add overlay scrollbars.
-      AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, usingDisplayPort,
+      AppendScrollPartsTo(aBuilder, aDirtyRect, aLists,
                           createLayersForScrollbars, true);
     }
 
@@ -2967,22 +3115,6 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
   mAddClipRectToLayer =
     !(mIsRoot && mOuter->PresContext()->PresShell()->GetIsViewportOverridden());
 
-  // Overflow clipping can never clip frames outside our subtree, so there
-  // is no need to worry about whether we are a moving frame that might clip
-  // non-moving frames.
-  // Not all our descendants will be clipped by overflow clipping, but all
-  // the ones that aren't clipped will be out of flow frames that have already
-  // had dirty rects saved for them by their parent frames calling
-  // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our
-  // dirty rect here.
-  nsRect dirtyRect = aDirtyRect.Intersect(mScrollPort);
-
-  Unused << DecideScrollableLayer(aBuilder, &dirtyRect,
-              /* aAllowCreateDisplayPort = */ !mIsRoot);
-
-  bool usingDisplayPort = aBuilder->IsPaintingToWindow() &&
-    nsLayoutUtils::HasDisplayPort(mOuter->GetContent());
-
   // Whether we might want to build a scrollable layer for this scroll frame
   // at some point in the future. This controls whether we add the information
   // to the layer tree (a scroll info layer if necessary, and add the right
@@ -3010,17 +3142,9 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
   // Note that this does not apply for overlay scrollbars; those are drawn
   // in the positioned-elements layer on top of everything else by the call
   // to AppendScrollPartsTo(..., true) further down.
-  AppendScrollPartsTo(aBuilder, aDirtyRect, aLists, usingDisplayPort,
+  AppendScrollPartsTo(aBuilder, aDirtyRect, aLists,
                       createLayersForScrollbars, false);
 
-  if (aBuilder->IsForImageVisibility()) {
-    // We expand the dirty rect to catch images just outside of the scroll port.
-    // We use the dirty rect instead of the whole scroll port to prevent
-    // too much expansion in the presence of very large (bigger than the
-    // viewport) scroll ports.
-    dirtyRect = ExpandRectToNearlyVisible(dirtyRect);
-  }
-
   const nsStyleDisplay* disp = mOuter->StyleDisplay();
   if (disp && (disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_SCROLL)) {
     aBuilder->AddToWillChangeBudget(mOuter, GetScrollPositionClampingScrollPortSize());
@@ -3103,7 +3227,7 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
       }
 
       DisplayListClipState::AutoSaveRestore clipStateForScrollClip(aBuilder);
-      if (usingDisplayPort) {
+      if (mWillBuildScrollableLayer) {
         if (mClipAllDescendants) {
           clipStateForScrollClip.TurnClipIntoScrollClipForContentDescendants(aBuilder, sf);
         } else {
@@ -3132,7 +3256,7 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
       }
 
       DisplayListClipState::AutoSaveRestore clipStateForScrollClip(aBuilder);
-      if (usingDisplayPort) {
+      if (mWillBuildScrollableLayer) {
         if (mClipAllDescendants) {
           clipStateForScrollClip.TurnClipIntoScrollClipForContentDescendants(aBuilder, sf);
         } else {
@@ -3145,6 +3269,9 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                            clipNonCaret, scrollClipNonCaret);
     }
 
+    if (aBuilder->IsPaintingToWindow()) {
+      mIsScrollParent = idSetter.ShouldForceLayerForScrollParent();
+    }
     if (idSetter.ShouldForceLayerForScrollParent() &&
         !gfxPrefs::LayoutUseContainersForRootFrames())
     {
@@ -3175,7 +3302,7 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
     }
   }
 
-  if (mWillBuildScrollableLayer && !gfxPrefs::LayoutUseContainersForRootFrames()) {
+  if (mWillBuildScrollableLayer) {
     aBuilder->ForceLayerForScrollParent();
   }
 
@@ -3211,7 +3338,7 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
     }
   }
   // Now display overlay scrollbars and the resizer, if we have one.
-  AppendScrollPartsTo(aBuilder, aDirtyRect, scrolledContent, usingDisplayPort,
+  AppendScrollPartsTo(aBuilder, aDirtyRect, scrolledContent,
                       createLayersForScrollbars, true);
   scrolledContent.MoveTo(aLists);
 }
@@ -3231,21 +3358,48 @@ ScrollFrameHelper::DecideScrollableLayer(nsDisplayListBuilder* aBuilder,
     wasUsingDisplayPort = nsLayoutUtils::HasDisplayPort(content);
 
     if (aAllowCreateDisplayPort) {
+      nsLayoutUtils::MaybeCreateDisplayPort(*aBuilder, mOuter);
+
       nsRect displayportBase = *aDirtyRect;
       nsPresContext* pc = mOuter->PresContext();
       if (mIsRoot && (pc->IsRootContentDocument() || !pc->GetParentPresContext())) {
         displayportBase =
           nsRect(nsPoint(0, 0), nsLayoutUtils::CalculateCompositionSizeForFrame(mOuter));
       } else {
-        // Restrict the dirty rect to the scrollport, and make it relative to the
-        // scrollport for the displayport base.
+        // Make the displayport base equal to the dirty rect restricted to
+        // the scrollport and the root composition bounds, relative to the
+        // scrollport.
         displayportBase = aDirtyRect->Intersect(mScrollPort);
+
+        // Only restrict to the root composition bounds if necessary,
+        // as the required coordinate transformation is expensive.
+        if (wasUsingDisplayPort) {
+          const nsPresContext* rootPresContext =
+            pc->GetToplevelContentDocumentPresContext();
+          if (!rootPresContext) {
+            rootPresContext = pc->GetRootPresContext();
+          }
+          if (rootPresContext) {
+            const nsIPresShell* const rootPresShell = rootPresContext->PresShell();
+            nsIFrame* rootFrame = rootPresShell->GetRootScrollFrame();
+            if (!rootFrame) {
+              rootFrame = rootPresShell->GetRootFrame();
+            }
+            if (rootFrame) {
+              nsRect rootCompBounds =
+                nsRect(nsPoint(0, 0), nsLayoutUtils::CalculateCompositionSizeForFrame(rootFrame));
+
+              nsLayoutUtils::TransformRect(rootFrame, mOuter, rootCompBounds);
+
+              displayportBase = displayportBase.Intersect(rootCompBounds);
+            }
+          }
+        }
+
         displayportBase -= mScrollPort.TopLeft();
       }
 
-      // Provide the value of the display port base rect, and possibly create a
-      // display port if there isn't one already.
-      nsLayoutUtils::MaybeCreateDisplayPort(*aBuilder, mOuter, displayportBase);
+      nsLayoutUtils::SetDisplayPortBase(mOuter->GetContent(), displayportBase);
     }
 
     // If we don't have aAllowCreateDisplayPort == true then should have already
@@ -3256,9 +3410,16 @@ ScrollFrameHelper::DecideScrollableLayer(nsDisplayListBuilder* aBuilder,
     usingDisplayPort =
       nsLayoutUtils::GetDisplayPort(content, &displayPort, RelativeTo::ScrollFrame);
 
-    // Override the dirty rectangle if the displayport has been set.
     if (usingDisplayPort) {
+      // Override the dirty rectangle if the displayport has been set.
       *aDirtyRect = displayPort;
+    } else if (mIsRoot) {
+      // The displayPort getter takes care of adjusting for resolution. So if
+      // we have resolution but no displayPort then we need to adjust for
+      // resolution here.
+      nsIPresShell* presShell = mOuter->PresContext()->PresShell();
+      *aDirtyRect = aDirtyRect->RemoveResolution(
+        presShell->ScaleToResolution() ? presShell->GetResolution () : 1.0f);
     }
   }
 
@@ -5794,7 +5955,7 @@ ScrollFrameHelper::GetSnapPointForDestination(nsIScrollableFrame::ScrollUnit aUn
   }
   if (styles.mScrollSnapPointsY.GetUnit() != eStyleUnit_None) {
     nscoord interval = nsRuleNode::ComputeCoordPercentCalc(styles.mScrollSnapPointsY,
-                                                           scrollPortSize.width);
+                                                           scrollPortSize.height);
     calcSnapPoints.AddHorizontalEdgeInterval(scrollRange, interval, destPos.y);
   }
 
diff --git a/layout/generic/nsGfxScrollFrame.h b/layout/generic/nsGfxScrollFrame.h
index a8aeb02ad7..6426233e16 100644
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -78,7 +78,6 @@ public:
   void AppendScrollPartsTo(nsDisplayListBuilder*   aBuilder,
                            const nsRect&           aDirtyRect,
                            const nsDisplayListSet& aLists,
-                           bool                    aUsingDisplayPort,
                            bool                    aCreateLayer,
                            bool                    aPositioned);
 
@@ -368,6 +367,12 @@ public:
   bool DecideScrollableLayer(nsDisplayListBuilder* aBuilder,
                              nsRect* aDirtyRect,
                              bool aAllowCreateDisplayPort);
+  void NotifyImageVisibilityUpdate();
+  bool GetDisplayPortAtLastImageVisibilityUpdate(nsRect* aDisplayPort);
+
+  bool AllowDisplayPortExpiration();
+  void TriggerDisplayPortExpiration();
+  void ResetDisplayPortExpiryTimer();
 
   void ScheduleSyntheticMouseMove();
   static void ScrollActivityCallback(nsITimer *aTimer, void* anInstance);
@@ -460,11 +465,16 @@ public:
 
   // The scroll position where we last updated image visibility.
   nsPoint mLastUpdateImagesPos;
+  bool mHadDisplayPortAtLastImageUpdate;
+  nsRect mDisplayPortAtLastImageUpdate;
 
   nsRect mPrevScrolledRect;
 
   FrameMetrics::ViewID mScrollParentID;
 
+  // Timer to remove the displayport some time after scrolling has stopped
+  nsCOMPtr mDisplayPortExpiryTimer;
+
   bool mNeverHasVerticalScrollbar:1;
   bool mNeverHasHorizontalScrollbar:1;
   bool mHasVerticalScrollbar:1;
@@ -506,6 +516,11 @@ public:
   // a scrollable layer. Used for asynchronous scrolling.
   bool mWillBuildScrollableLayer:1;
 
+  // If true, the scroll frame is an ancestor of other scrolling frames, so
+  // we shouldn't expire the displayport on this scrollframe unless those
+  // descendant scrollframes also have their displayports removed.
+  bool mIsScrollParent:1;
+
   // Whether we are the root scroll frame that is used for containerful
   // scrolling with a display port. If true, the scrollable frame
   // shouldn't attach frame metrics to its layers because the container
@@ -850,6 +865,15 @@ public:
                                      bool aAllowCreateDisplayPort) override {
     return mHelper.DecideScrollableLayer(aBuilder, aDirtyRect, aAllowCreateDisplayPort);
   }
+  virtual void NotifyImageVisibilityUpdate() override {
+    mHelper.NotifyImageVisibilityUpdate();
+  }
+  virtual bool GetDisplayPortAtLastImageVisibilityUpdate(nsRect* aDisplayPort) override {
+    return mHelper.GetDisplayPortAtLastImageVisibilityUpdate(aDisplayPort);
+  }
+  void TriggerDisplayPortExpiration() override {
+    mHelper.TriggerDisplayPortExpiration();
+  }
 
   // nsIStatefulFrame
   NS_IMETHOD SaveState(nsPresState** aState) override {
@@ -1317,7 +1341,15 @@ public:
                                      bool aAllowCreateDisplayPort) override {
     return mHelper.DecideScrollableLayer(aBuilder, aDirtyRect, aAllowCreateDisplayPort);
   }
-
+  virtual void NotifyImageVisibilityUpdate() override {
+    mHelper.NotifyImageVisibilityUpdate();
+  }
+  virtual bool GetDisplayPortAtLastImageVisibilityUpdate(nsRect* aDisplayPort) override {
+    return mHelper.GetDisplayPortAtLastImageVisibilityUpdate(aDisplayPort);
+  }
+  void TriggerDisplayPortExpiration() override {
+    mHelper.TriggerDisplayPortExpiration();
+  }
 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
diff --git a/layout/generic/nsIScrollableFrame.h b/layout/generic/nsIScrollableFrame.h
index 3313bb0439..e97f953c08 100644
--- a/layout/generic/nsIScrollableFrame.h
+++ b/layout/generic/nsIScrollableFrame.h
@@ -441,6 +441,25 @@ public:
   virtual bool DecideScrollableLayer(nsDisplayListBuilder* aBuilder,
                                      nsRect* aDirtyRect,
                                      bool aAllowCreateDisplayPort) = 0;
+
+  /**
+   * Notification that this scroll frame is getting its image visibility updated.
+   */
+  virtual void NotifyImageVisibilityUpdate() = 0;
+
+  /**
+   * Returns true if this scroll frame had a display port at the last image
+   * visibility update and fills in aDisplayPort with that displayport. Returns
+   * false otherwise, and doesn't touch aDisplayPort.
+   */
+  virtual bool GetDisplayPortAtLastImageVisibilityUpdate(nsRect* aDisplayPort) = 0;
+
+  /**
+   * This is called when a descendant scrollframe's has its displayport expired.
+   * This function will check to see if this scrollframe may safely expire its
+   * own displayport and schedule a timer to do that if it is safe.
+   */
+  virtual void TriggerDisplayPortExpiration() = 0;
 };
 
 #endif
diff --git a/layout/generic/nsSubDocumentFrame.cpp b/layout/generic/nsSubDocumentFrame.cpp
index b97fb19e1f..062a6d7286 100644
--- a/layout/generic/nsSubDocumentFrame.cpp
+++ b/layout/generic/nsSubDocumentFrame.cpp
@@ -424,9 +424,10 @@ nsSubDocumentFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
     if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) {
       nsIScrollableFrame* rootScrollableFrame = presShell->GetRootScrollFrameAsScrollable();
       MOZ_ASSERT(rootScrollableFrame);
+      // Use a copy, so the dirty rect doesn't get modified to the display port.
+      nsRect copy = dirty;
       haveDisplayPort = rootScrollableFrame->DecideScrollableLayer(aBuilder,
-                          &dirty, /* aAllowCreateDisplayPort = */ true);
-
+                          ©, /* aAllowCreateDisplayPort = */ true);
       if (!gfxPrefs::LayoutUseContainersForRootFrames()) {
         haveDisplayPort = false;
       }
@@ -435,23 +436,11 @@ nsSubDocumentFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
       if (ignoreViewportScrolling) {
         savedIgnoreScrollFrame = aBuilder->GetIgnoreScrollFrame();
         aBuilder->SetIgnoreScrollFrame(rootScrollFrame);
-
-        if (aBuilder->IsForImageVisibility()) {
-          // The ExpandRectToNearlyVisible that the root scroll frame would do gets short
-          // circuited due to us ignoring the root scroll frame, so we do it here.
-          nsIScrollableFrame* rootScrollableFrame = do_QueryFrame(rootScrollFrame);
-          dirty = rootScrollableFrame->ExpandRectToNearlyVisible(dirty);
-        }
       }
     }
 
     aBuilder->EnterPresShell(subdocRootFrame,
                              pointerEventsNone && !passPointerEventsToChildren);
-
-    if (!haveDisplayPort) {
-      // Remove nsPresShell resolution
-      dirty = dirty.RemoveResolution(presShell->ScaleToResolution() ? presShell->GetResolution () : 1.0f);
-    }
   } else {
     dirty = aDirtyRect;
   }
diff --git a/layout/ipc/PRenderFrame.ipdl b/layout/ipc/PRenderFrame.ipdl
index a9d9434ad4..c078b06f96 100644
--- a/layout/ipc/PRenderFrame.ipdl
+++ b/layout/ipc/PRenderFrame.ipdl
@@ -32,6 +32,8 @@ parent:
 
     async UpdateHitRegion(nsRegion aRegion);
 
+    sync TakeFocusForClickFromTap();
+
     async __delete__();
 };
 
diff --git a/layout/ipc/RenderFrameParent.cpp b/layout/ipc/RenderFrameParent.cpp
index b6225bba61..7e11716588 100644
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -15,6 +15,8 @@
 #include "mozilla/BrowserElementParent.h"
 #include "mozilla/EventForwards.h"  // for Modifiers
 #include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/layers/APZCTreeManager.h"
 #include "mozilla/layers/APZThreadUtils.h"
@@ -34,10 +36,6 @@
 #include "ClientLayerManager.h"
 #include "FrameLayerBuilder.h"
 
-#ifdef MOZ_ANDROID_APZ
-#include "AndroidBridge.h"
-#endif
-
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
@@ -88,190 +86,6 @@ GetFrom(nsFrameLoader* aFrameLoader)
   return nsContentUtils::LayerManagerForDocument(doc);
 }
 
-class RemoteContentController : public GeckoContentController {
-public:
-  explicit RemoteContentController(RenderFrameParent* aRenderFrame)
-    : mUILoop(MessageLoop::current())
-    , mRenderFrame(aRenderFrame)
-  { }
-
-  virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) override
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    if (mRenderFrame) {
-      TabParent* browser = TabParent::GetFrom(mRenderFrame->Manager());
-      browser->UpdateFrame(aFrameMetrics);
-    }
-  }
-
-  virtual void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
-                                const mozilla::CSSPoint& aDestination) override
-  {
-    if (MessageLoop::current() != mUILoop) {
-      // We have to send this message from the "UI thread" (main
-      // thread).
-      mUILoop->PostTask(
-        FROM_HERE,
-        NewRunnableMethod(this, &RemoteContentController::RequestFlingSnap,
-                          aScrollId, aDestination));
-      return;
-    }
-    if (mRenderFrame) {
-      TabParent* browser = TabParent::GetFrom(mRenderFrame->Manager());
-      browser->RequestFlingSnap(aScrollId, aDestination);
-    }
-  }
-
-  virtual void AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
-                                       const uint32_t& aScrollGeneration) override
-  {
-    if (MessageLoop::current() != mUILoop) {
-      // We have to send this message from the "UI thread" (main
-      // thread).
-      mUILoop->PostTask(
-        FROM_HERE,
-        NewRunnableMethod(this, &RemoteContentController::AcknowledgeScrollUpdate,
-                          aScrollId, aScrollGeneration));
-      return;
-    }
-    if (mRenderFrame) {
-      TabParent* browser = TabParent::GetFrom(mRenderFrame->Manager());
-      browser->AcknowledgeScrollUpdate(aScrollId, aScrollGeneration);
-    }
-  }
-
-  virtual void HandleDoubleTap(const CSSPoint& aPoint,
-                               Modifiers aModifiers,
-                               const ScrollableLayerGuid& aGuid) override
-  {
-    if (MessageLoop::current() != mUILoop) {
-      // We have to send this message from the "UI thread" (main
-      // thread).
-      mUILoop->PostTask(
-        FROM_HERE,
-        NewRunnableMethod(this, &RemoteContentController::HandleDoubleTap,
-                          aPoint, aModifiers, aGuid));
-      return;
-    }
-    if (mRenderFrame) {
-      TabParent* browser = TabParent::GetFrom(mRenderFrame->Manager());
-      browser->HandleDoubleTap(aPoint, aModifiers, aGuid);
-    }
-  }
-
-  virtual void HandleSingleTap(const CSSPoint& aPoint,
-                               Modifiers aModifiers,
-                               const ScrollableLayerGuid& aGuid) override
-  {
-    if (MessageLoop::current() != mUILoop) {
-      // We have to send this message from the "UI thread" (main
-      // thread).
-      mUILoop->PostTask(
-        FROM_HERE,
-        NewRunnableMethod(this, &RemoteContentController::HandleSingleTap,
-                          aPoint, aModifiers, aGuid));
-      return;
-    }
-    if (mRenderFrame) {
-      mRenderFrame->TakeFocusForClickFromTap();
-      TabParent* browser = TabParent::GetFrom(mRenderFrame->Manager());
-      browser->HandleSingleTap(aPoint, aModifiers, aGuid);
-    }
-  }
-
-  virtual void HandleLongTap(const CSSPoint& aPoint,
-                             Modifiers aModifiers,
-                             const ScrollableLayerGuid& aGuid,
-                             uint64_t aInputBlockId) override
-  {
-    if (MessageLoop::current() != mUILoop) {
-      // We have to send this message from the "UI thread" (main
-      // thread).
-      mUILoop->PostTask(
-        FROM_HERE,
-        NewRunnableMethod(this, &RemoteContentController::HandleLongTap,
-                          aPoint, aModifiers, aGuid, aInputBlockId));
-      return;
-    }
-    if (mRenderFrame) {
-      TabParent* browser = TabParent::GetFrom(mRenderFrame->Manager());
-      browser->HandleLongTap(aPoint, aModifiers, aGuid, aInputBlockId);
-    }
-  }
-
-  void ClearRenderFrame() { mRenderFrame = nullptr; }
-
-  virtual void PostDelayedTask(Task* aTask, int aDelayMs) override
-  {
-#ifdef MOZ_ANDROID_APZ
-    AndroidBridge::Bridge()->PostTaskToUiThread(aTask, aDelayMs);
-#else
-    (MessageLoop::current() ? MessageLoop::current() : mUILoop)->
-       PostDelayedTask(FROM_HERE, aTask, aDelayMs);
-#endif
-  }
-
-  virtual bool GetTouchSensitiveRegion(CSSRect* aOutRegion) override
-  {
-    if (mTouchSensitiveRegion.IsEmpty())
-      return false;
-
-    *aOutRegion = CSSRect::FromAppUnits(mTouchSensitiveRegion.GetBounds());
-    return true;
-  }
-
-  virtual void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
-                                    APZStateChange aChange,
-                                    int aArg) override
-  {
-    if (MessageLoop::current() != mUILoop) {
-      mUILoop->PostTask(
-        FROM_HERE,
-        NewRunnableMethod(this, &RemoteContentController::NotifyAPZStateChange,
-                          aGuid, aChange, aArg));
-      return;
-    }
-    if (mRenderFrame) {
-      TabParent* browser = TabParent::GetFrom(mRenderFrame->Manager());
-      browser->NotifyAPZStateChange(aGuid.mScrollId, aChange, aArg);
-    }
-  }
-
-  void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent) override {
-    if (MessageLoop::current() != mUILoop) {
-      mUILoop->PostTask(
-        FROM_HERE,
-        NewRunnableMethod(this, &RemoteContentController::NotifyMozMouseScrollEvent, aScrollId, aEvent));
-      return;
-    }
-
-    if (mRenderFrame) {
-      TabParent* browser = TabParent::GetFrom(mRenderFrame->Manager());
-      browser->NotifyMouseScrollTestEvent(aScrollId, aEvent);
-    }
-  }
-
-  void NotifyFlushComplete() override {
-    MOZ_ASSERT(NS_IsMainThread());
-    if (mRenderFrame) {
-      TabParent* browser = TabParent::GetFrom(mRenderFrame->Manager());
-      browser->NotifyFlushComplete();
-    }
-  }
-
-  // Methods used by RenderFrameParent to set fields stored here.
-
-  void SetTouchSensitiveRegion(const nsRegion& aRegion)
-  {
-    mTouchSensitiveRegion = aRegion;
-  }
-private:
-  MessageLoop* mUILoop;
-  RenderFrameParent* mRenderFrame;
-
-  nsRegion mTouchSensitiveRegion;
-};
-
 RenderFrameParent::RenderFrameParent(nsFrameLoader* aFrameLoader,
                                      TextureFactoryIdentifier* aTextureFactoryIdentifier,
                                      uint64_t* aId,
@@ -299,44 +113,25 @@ RenderFrameParent::RenderFrameParent(nsFrameLoader* aFrameLoader,
     *aTextureFactoryIdentifier = TextureFactoryIdentifier();
   }
 
+  TabParent* browser = TabParent::GetFrom(mFrameLoader);
   if (XRE_IsParentProcess()) {
     // Our remote frame will push layers updates to the compositor,
     // and we'll keep an indirect reference to that tree.
-    *aId = mLayersId = CompositorParent::AllocateLayerTreeId();
+    browser->Manager()->AsContentParent()->AllocateLayerTreeId(browser, aId);
+    mLayersId = *aId;
     if (lm && lm->GetBackendType() == LayersBackend::LAYERS_CLIENT) {
       ClientLayerManager *clientManager =
         static_cast(lm.get());
       clientManager->GetRemoteRenderer()->SendNotifyChildCreated(mLayersId);
     }
-    if (mAsyncPanZoomEnabled) {
-      mContentController = new RemoteContentController(this);
-      CompositorParent::SetControllerForLayerTree(mLayersId, mContentController);
-    }
   } else if (XRE_IsContentProcess()) {
-    ContentChild::GetSingleton()->SendAllocateLayerTreeId(aId);
+    ContentChild::GetSingleton()->SendAllocateLayerTreeId(browser->Manager()->ChildID(), browser->GetTabId(), aId);
     mLayersId = *aId;
     CompositorChild::Get()->SendNotifyChildCreated(mLayersId);
   }
   *aSuccess = true;
 }
 
-APZCTreeManager*
-RenderFrameParent::GetApzcTreeManager()
-{
-  // We can't get a ref to the APZCTreeManager until after the child is
-  // created and the static getter knows which CompositorParent is
-  // instantiated with this layers ID. That's why try to fetch it when
-  // we first need it and cache the result.
-  // Note: the IsParentProcess check is to deal with nested content process
-  // scenarios, since in those cases we can have RenderFrameParent instances
-  // in a child process, but the APZC machinery is not in that process. Bug
-  // 1020199 should fix this more comprehensively.
-  if (!mApzcTreeManager && mAsyncPanZoomEnabled && XRE_IsParentProcess()) {
-    mApzcTreeManager = CompositorParent::GetAPZCTreeManager(mLayersId);
-  }
-  return mApzcTreeManager.get();
-}
-
 RenderFrameParent::~RenderFrameParent()
 {}
 
@@ -418,9 +213,6 @@ RenderFrameParent::OwnerContentChanged(nsIContent* aContent)
       static_cast(lm.get());
     clientManager->GetRemoteRenderer()->SendAdoptChild(mLayersId);
   }
-  // The APZCTreeManager associated with this RenderFrameParent may have changed
-  // so reset it and let GetApzcTreeManager() pick it up again.
-  mApzcTreeManager = nullptr;
 }
 
 void
@@ -432,12 +224,6 @@ RenderFrameParent::ActorDestroy(ActorDestroyReason why)
     } else {
       CompositorParent::DeallocateLayerTreeId(mLayersId);
     }
-    if (mContentController) {
-      // Stop our content controller from requesting repaints of our
-      // content.
-      mContentController->ClearRenderFrame();
-      // TODO: notify the compositor?
-    }
   }
 
   mFrameLoader = nullptr;
@@ -454,13 +240,6 @@ bool
 RenderFrameParent::RecvUpdateHitRegion(const nsRegion& aRegion)
 {
   mTouchRegion = aRegion;
-  if (mContentController) {
-    // Tell the content controller about the touch-sensitive region, so
-    // that it can provide it to APZ. This is required for APZ to do
-    // correct hit testing for a remote 'mozpasspointerevents' iframe
-    // until bug 928833 is fixed.
-    mContentController->SetTouchSensitiveRegion(aRegion);
-  }
   return true;
 }
 
@@ -504,98 +283,12 @@ RenderFrameParent::BuildDisplayList(nsDisplayListBuilder* aBuilder,
     new (aBuilder) nsDisplayRemote(aBuilder, aFrame, this));
 }
 
-void
-RenderFrameParent::ZoomToRect(uint32_t aPresShellId, ViewID aViewId,
-                              const CSSRect& aRect,
-                              const uint32_t aFlags)
-{
-  if (GetApzcTreeManager()) {
-    GetApzcTreeManager()->ZoomToRect(ScrollableLayerGuid(mLayersId, aPresShellId, aViewId),
-                                     aRect, aFlags);
-  }
-}
-
-void
-RenderFrameParent::ContentReceivedInputBlock(const ScrollableLayerGuid& aGuid,
-                                             uint64_t aInputBlockId,
-                                             bool aPreventDefault)
-{
-  if (aGuid.mLayersId != mLayersId) {
-    // Guard against bad data from hijacked child processes
-    NS_ERROR("Unexpected layers id in ContentReceivedInputBlock; dropping message...");
-    return;
-  }
-  if (GetApzcTreeManager()) {
-    APZThreadUtils::RunOnControllerThread(NewRunnableMethod(
-        GetApzcTreeManager(), &APZCTreeManager::ContentReceivedInputBlock,
-        aInputBlockId, aPreventDefault));
-  }
-}
-
-void
-RenderFrameParent::SetTargetAPZC(uint64_t aInputBlockId,
-                                 const nsTArray& aTargets)
-{
-  for (size_t i = 0; i < aTargets.Length(); i++) {
-    if (aTargets[i].mLayersId != mLayersId) {
-      // Guard against bad data from hijacked child processes
-      NS_ERROR("Unexpected layers id in SetTargetAPZC; dropping message...");
-      return;
-    }
-  }
-  if (GetApzcTreeManager()) {
-    // need a local var to disambiguate between the SetTargetAPZC overloads.
-    void (APZCTreeManager::*setTargetApzcFunc)(uint64_t, const nsTArray&)
-        = &APZCTreeManager::SetTargetAPZC;
-    APZThreadUtils::RunOnControllerThread(NewRunnableMethod(
-        GetApzcTreeManager(), setTargetApzcFunc,
-        aInputBlockId, aTargets));
-  }
-}
-
-void
-RenderFrameParent::SetAllowedTouchBehavior(uint64_t aInputBlockId,
-                                           const nsTArray& aFlags)
-{
-  if (GetApzcTreeManager()) {
-    APZThreadUtils::RunOnControllerThread(NewRunnableMethod(
-        GetApzcTreeManager(), &APZCTreeManager::SetAllowedTouchBehavior,
-        aInputBlockId, aFlags));
-  }
-}
-
-void
-RenderFrameParent::UpdateZoomConstraints(uint32_t aPresShellId,
-                                         ViewID aViewId,
-                                         const Maybe& aConstraints)
-{
-  if (GetApzcTreeManager()) {
-    GetApzcTreeManager()->UpdateZoomConstraints(ScrollableLayerGuid(mLayersId, aPresShellId, aViewId),
-                                                aConstraints);
-  }
-}
-
 bool
 RenderFrameParent::HitTest(const nsRect& aRect)
 {
   return mTouchRegion.Contains(aRect);
 }
 
-void
-RenderFrameParent::StartScrollbarDrag(const AsyncDragMetrics& aDragMetrics)
-{
-  if (GetApzcTreeManager()) {
-    uint64_t layersId = GetLayersId();
-    ScrollableLayerGuid guid(layersId, aDragMetrics.mPresShellId,
-                             aDragMetrics.mViewId);
-
-    APZThreadUtils::RunOnControllerThread(
-      NewRunnableMethod(GetApzcTreeManager(),
-                        &APZCTreeManager::StartScrollbarDrag,
-                        guid, aDragMetrics));
-  }
-}
-
 void
 RenderFrameParent::GetTextureFactoryIdentifier(TextureFactoryIdentifier* aTextureFactoryIdentifier)
 {
@@ -629,6 +322,13 @@ RenderFrameParent::TakeFocusForClickFromTap()
                         nsIFocusManager::FLAG_NOSCROLL);
 }
 
+bool
+RenderFrameParent::RecvTakeFocusForClickFromTap()
+{
+  TakeFocusForClickFromTap();
+  return true;
+}
+
 } // namespace layout
 } // namespace mozilla
 
diff --git a/layout/ipc/RenderFrameParent.h b/layout/ipc/RenderFrameParent.h
index 3d4bed00b9..a4955af933 100644
--- a/layout/ipc/RenderFrameParent.h
+++ b/layout/ipc/RenderFrameParent.h
@@ -33,8 +33,6 @@ struct ScrollableLayerGuid;
 
 namespace layout {
 
-class RemoteContentController;
-
 class RenderFrameParent : public PRenderFrameParent
 {
   typedef mozilla::layers::AsyncDragMetrics AsyncDragMetrics;
@@ -79,24 +77,8 @@ public:
 
   void OwnerContentChanged(nsIContent* aContent);
 
-  void ZoomToRect(uint32_t aPresShellId, ViewID aViewId, const CSSRect& aRect, const uint32_t aFlags);
-
-  void ContentReceivedInputBlock(const ScrollableLayerGuid& aGuid,
-                                 uint64_t aInputBlockId,
-                                 bool aPreventDefault);
-  void SetTargetAPZC(uint64_t aInputBlockId,
-                     const nsTArray& aTargets);
-  void SetAllowedTouchBehavior(uint64_t aInputBlockId,
-                               const nsTArray& aFlags);
-
-  void UpdateZoomConstraints(uint32_t aPresShellId,
-                             ViewID aViewId,
-                             const Maybe& aConstraints);
-
   bool HitTest(const nsRect& aRect);
 
-  void StartScrollbarDrag(const AsyncDragMetrics& aDragMetrics);
-
   void GetTextureFactoryIdentifier(TextureFactoryIdentifier* aTextureFactoryIdentifier);
 
   inline uint64_t GetLayersId() { return mLayersId; }
@@ -110,6 +92,8 @@ protected:
 
   virtual bool RecvUpdateHitRegion(const nsRegion& aRegion) override;
 
+  virtual bool RecvTakeFocusForClickFromTap() override;
+
 private:
   void TriggerRepaint();
   void DispatchEventForPanZoomController(const InputEvent& aEvent);
@@ -123,13 +107,6 @@ private:
 
   RefPtr mFrameLoader;
   RefPtr mContainer;
-  // When our scrolling behavior is ASYNC_PAN_ZOOM, we have a nonnull
-  // APZCTreeManager. It's used to manipulate the shadow layer tree
-  // on the compositor thread.
-  RefPtr mApzcTreeManager;
-  RefPtr mContentController;
-
-  layers::APZCTreeManager* GetApzcTreeManager();
 
   // True after Destroy() has been called, which is triggered
   // originally by nsFrameLoader::Destroy().  After this point, we can
diff --git a/layout/reftests/transform-3d/preserve3d-7-ref.html b/layout/reftests/transform-3d/preserve3d-7-ref.html
new file mode 100644
index 0000000000..cb17ddba5a
--- /dev/null
+++ b/layout/reftests/transform-3d/preserve3d-7-ref.html
@@ -0,0 +1,11 @@
+
+
+
+
+  
+
+
+
+
+ + diff --git a/layout/reftests/transform-3d/preserve3d-7a.html b/layout/reftests/transform-3d/preserve3d-7a.html new file mode 100644 index 0000000000..d962d4fd4f --- /dev/null +++ b/layout/reftests/transform-3d/preserve3d-7a.html @@ -0,0 +1,9 @@ + + + + +
+
+
+ + diff --git a/layout/xul/nsSliderFrame.cpp b/layout/xul/nsSliderFrame.cpp index cb7a6e9e04..b7090bd88d 100644 --- a/layout/xul/nsSliderFrame.cpp +++ b/layout/xul/nsSliderFrame.cpp @@ -1192,13 +1192,21 @@ nsSliderFrame::IsEventOverThumb(WidgetGUIEvent* aEvent) return false; } - bool isHorizontal = IsHorizontal(); nsRect thumbRect = thumbFrame->GetRect(); +#if defined(MOZ_WIDGET_GTK) + /* Scrollbar track can have padding, so it's better to check that eventPoint + * is inside of actual thumb, not just its one axis. The part of the scrollbar + * track adjacent to thumb can actually receive events in GTK3 */ + return eventPoint.x >= thumbRect.x && eventPoint.x < thumbRect.XMost() && + eventPoint.y >= thumbRect.y && eventPoint.y < thumbRect.YMost(); +#else + bool isHorizontal = IsHorizontal(); nscoord eventPos = isHorizontal ? eventPoint.x : eventPoint.y; nscoord thumbStart = isHorizontal ? thumbRect.x : thumbRect.y; nscoord thumbEnd = isHorizontal ? thumbRect.XMost() : thumbRect.YMost(); return eventPos >= thumbStart && eventPos < thumbEnd; +#endif } NS_IMETHODIMP diff --git a/mfbt/Vector.h b/mfbt/Vector.h index 902791c477..1a7ddef311 100644 --- a/mfbt/Vector.h +++ b/mfbt/Vector.h @@ -283,6 +283,7 @@ class Vector final : private AllocPolicy MOZ_WARN_UNUSED_RESULT bool growStorageBy(size_t aIncr); MOZ_WARN_UNUSED_RESULT bool convertToHeapStorage(size_t aNewCap); + MOZ_WARN_UNUSED_RESULT bool maybeCheckSimulatedOOM(size_t aRequestedSize); /* magic constants */ @@ -937,6 +938,23 @@ Vector::initCapacity(size_t aRequest) return true; } +template +inline bool +Vector::maybeCheckSimulatedOOM(size_t aRequestedSize) +{ + if (aRequestedSize <= N) { + return true; + } + +#ifdef DEBUG + if (aRequestedSize <= mReserved) { + return true; + } +#endif + + return allocPolicy().checkSimulatedOOM(); +} + template inline bool Vector::reserve(size_t aRequest) @@ -946,10 +964,8 @@ Vector::reserve(size_t aRequest) if (MOZ_UNLIKELY(!growStorageBy(aRequest - mLength))) { return false; } - } else if (aRequest > N) { - if (!allocPolicy().checkSimulatedOOM()) { - return false; - } + } else if (!maybeCheckSimulatedOOM(aRequest)) { + return false; } #ifdef DEBUG if (aRequest > mReserved) { @@ -988,10 +1004,8 @@ Vector::growBy(size_t aIncr) if (MOZ_UNLIKELY(!growStorageBy(aIncr))) { return false; } - } else if (aIncr + mLength > N) { - if (!allocPolicy().checkSimulatedOOM()) { - return false; - } + } else if (!maybeCheckSimulatedOOM(mLength + aIncr)) { + return false; } MOZ_ASSERT(mLength + aIncr <= mCapacity); T* newend = endNoCheck() + aIncr; @@ -1014,10 +1028,8 @@ Vector::growByUninitialized(size_t aIncr) if (MOZ_UNLIKELY(!growStorageBy(aIncr))) { return false; } - } else if (aIncr + mLength > N) { - if (!allocPolicy().checkSimulatedOOM()) { - return false; - } + } else if (!maybeCheckSimulatedOOM(mLength + aIncr)) { + return false; } infallibleGrowByUninitialized(aIncr); return true; @@ -1121,9 +1133,8 @@ Vector::appendN(const T& aT, size_t aNeeded) if (MOZ_UNLIKELY(!growStorageBy(aNeeded))) { return false; } - } else if (mLength + aNeeded > N) { - if (!allocPolicy().checkSimulatedOOM()) - return false; + } else if (!maybeCheckSimulatedOOM(mLength + aNeeded)) { + return false; } #ifdef DEBUG if (mLength + aNeeded > mReserved) { @@ -1208,8 +1219,7 @@ Vector::append(const U* aInsBegin, const U* aInsEnd) if (MOZ_UNLIKELY(!growStorageBy(aNeeded))) { return false; } - } else if (mLength + aNeeded > N) { - if (!allocPolicy().checkSimulatedOOM()) + } else if (!maybeCheckSimulatedOOM(mLength + aNeeded)) { return false; } #ifdef DEBUG @@ -1242,8 +1252,7 @@ Vector::append(U&& aU) if (MOZ_UNLIKELY(!growStorageBy(1))) { return false; } - } else if (mLength + 1 > N) { - if (!allocPolicy().checkSimulatedOOM()) + } else if (!maybeCheckSimulatedOOM(mLength + 1)) { return false; } #ifdef DEBUG diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index ad88f6013b..721fcfa59f 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -320,6 +320,7 @@ pref("media.play-stand-alone", true); pref("media.block-play-until-visible", false); pref("media.hardware-video-decoding.enabled", true); +pref("media.hardware-video-decoding.force-enabled", false); pref("media.decoder.heuristic.dormant.enabled", true); pref("media.decoder.heuristic.dormant.timeout", 60000); @@ -592,6 +593,7 @@ pref("apz.content_response_timeout", 300); pref("apz.drag.enabled", false); pref("apz.danger_zone_x", 50); pref("apz.danger_zone_y", 100); +pref("apz.displayport_expiry_ms", 15000); pref("apz.enlarge_displayport_when_clipped", false); pref("apz.fling_accel_base_mult", "1.0"); pref("apz.fling_accel_interval_ms", 500); @@ -637,14 +639,10 @@ pref("apz.y_skate_highmem_adjust", "0.0"); pref("apz.zoom_animation_duration_ms", 250); #ifdef XP_MACOSX -pref("apz.fling_repaint_interval", 16); -pref("apz.smooth_scroll_repaint_interval", 16); pref("apz.pan_repaint_interval", 16); pref("apz.x_skate_size_multiplier", "2.5"); pref("apz.y_skate_size_multiplier", "3.5"); #else -pref("apz.fling_repaint_interval", 75); -pref("apz.smooth_scroll_repaint_interval", 75); pref("apz.pan_repaint_interval", 250); pref("apz.x_skate_size_multiplier", "1.5"); pref("apz.y_skate_size_multiplier", "2.5"); @@ -4688,10 +4686,6 @@ pref("layers.tiled-drawtarget.enabled", true); pref("layers.tiles.edge-padding", true); #endif -// same effect as layers.offmainthreadcomposition.enabled, but specifically for -// use with tests. -pref("layers.offmainthreadcomposition.testing.enabled", false); - // Whether to animate simple opacity and transforms on the compositor #ifdef RELEASE_BUILD pref("layers.offmainthreadcomposition.async-animations", false); diff --git a/security/certverifier/moz.build b/security/certverifier/moz.build index f6a6f6180a..18afd6ce60 100644 --- a/security/certverifier/moz.build +++ b/security/certverifier/moz.build @@ -36,6 +36,7 @@ if CONFIG['_MSC_VER']: # -Wall with Visual C++ enables too many problematic warnings CXXFLAGS += [ '-wd4355', # 'this' used in base member initializer list + '-wd4464', # relative include path contains '..' '-wd4480', # nonstandard extension used: specifying underlying type for # enum 'enum' '-wd4481', # nonstandard extension used: override specifier 'keyword' diff --git a/security/manager/ssl/ScopedNSSTypes.h b/security/manager/ssl/ScopedNSSTypes.h index 7859a3221c..08c63458f7 100644 --- a/security/manager/ssl/ScopedNSSTypes.h +++ b/security/manager/ssl/ScopedNSSTypes.h @@ -67,14 +67,6 @@ MapSECStatus(SECStatus rv) return mozilla::psm::GetXPCOMFromNSSError(PR_GetError()); } -#ifdef _MSC_VER -// C4061: enumerator 'symbol' in switch of enum 'symbol' is not explicitly -// handled. -#define MOZ_NON_EXHAUSTIVE_SWITCH __pragma(warning(suppress:4061)) switch -#else -#define MOZ_NON_EXHAUSTIVE_SWITCH switch -#endif - // Alphabetical order by NSS type MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, @@ -208,7 +200,13 @@ public: private: nsresult SetLength(SECOidTag hashType) { - MOZ_NON_EXHAUSTIVE_SWITCH (hashType) +#ifdef _MSC_VER +#pragma warning(push) + // C4061: enumerator 'symbol' in switch of enum 'symbol' is not + // explicitly handled. +#pragma warning(disable:4061) +#endif + switch (hashType) { case SEC_OID_SHA1: item.len = SHA1_LENGTH; break; case SEC_OID_SHA256: item.len = SHA256_LENGTH; break; @@ -217,6 +215,9 @@ private: default: return NS_ERROR_INVALID_ARG; } +#ifdef _MSC_VER +#pragma warning(pop) +#endif return NS_OK; } diff --git a/security/pkix/include/pkix/Input.h b/security/pkix/include/pkix/Input.h index 8ff9bb0924..e09526fb49 100644 --- a/security/pkix/include/pkix/Input.h +++ b/security/pkix/include/pkix/Input.h @@ -25,7 +25,7 @@ #ifndef mozilla_pkix_Input_h #define mozilla_pkix_Input_h -#include +#include #include "pkix/Result.h" #include "stdint.h" @@ -133,7 +133,7 @@ inline bool InputsAreEqual(const Input& a, const Input& b) { return a.GetLength() == b.GetLength() && - !std::memcmp(a.UnsafeGetData(), b.UnsafeGetData(), a.GetLength()); + std::equal(a.UnsafeGetData(), a.UnsafeGetData() + a.GetLength(), b.UnsafeGetData()); } // An Reader is a cursor/iterator through the contents of an Input, designed to @@ -205,7 +205,7 @@ public: if (static_cast(end - input) != N) { return false; } - if (memcmp(input, toMatch, N)) { + if (!std::equal(input, end, toMatch)) { return false; } input = end; @@ -221,7 +221,7 @@ public: if (toMatch.GetLength() != remaining) { return false; } - if (std::memcmp(input, toMatch.UnsafeGetData(), remaining)) { + if (!std::equal(input, end, toMatch.UnsafeGetData())) { return false; } input = end; diff --git a/security/pkix/test/lib/pkixtestutil.h b/security/pkix/test/lib/pkixtestutil.h index 896ab5eb9b..5a445cc177 100644 --- a/security/pkix/test/lib/pkixtestutil.h +++ b/security/pkix/test/lib/pkixtestutil.h @@ -28,6 +28,7 @@ #include #include // Some Mozilla-supported compilers lack #include +#include #include "pkix/pkixtypes.h" #include "../../lib/ScopedPtr.h" diff --git a/security/pkix/warnings.mozbuild b/security/pkix/warnings.mozbuild index a36efd570e..3fdc10bd74 100644 --- a/security/pkix/warnings.mozbuild +++ b/security/pkix/warnings.mozbuild @@ -17,6 +17,7 @@ elif CONFIG['_MSC_VER']: '-Wall', + '-wd4464', # relative include path contains '..' '-wd4514', # 'function': unreferenced inline function has been removed '-wd4668', # warning C4668: 'X' is not defined as a preprocessor macro, # replacing with '0' for '#if/#elif'. diff --git a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp index bf7f3cfece..9aeb62fb80 100644 --- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp +++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp @@ -83,16 +83,24 @@ SandboxBroker::SetSecurityLevelForContentProcess(int32_t aSandboxLevel) sandbox::IntegrityLevel initialIntegrityLevel; sandbox::IntegrityLevel delayedIntegrityLevel; - if (aSandboxLevel > 2) { + // The setting of these levels is pretty arbitrary, but they are a useful (if + // crude) tool while we are tightening the policy. Gaps are left to try and + // avoid changing their meaning. + if (aSandboxLevel >= 20) { jobLevel = sandbox::JOB_LOCKDOWN; accessTokenLevel = sandbox::USER_LOCKDOWN; initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_UNTRUSTED; - } else if (aSandboxLevel == 2) { + } else if (aSandboxLevel >= 10) { jobLevel = sandbox::JOB_RESTRICTED; accessTokenLevel = sandbox::USER_LIMITED; initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + } else if (aSandboxLevel == 2) { + jobLevel = sandbox::JOB_INTERACTIVE; + accessTokenLevel = sandbox::USER_INTERACTIVE; + initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; } else if (aSandboxLevel == 1) { jobLevel = sandbox::JOB_NONE; accessTokenLevel = sandbox::USER_NON_ADMIN; @@ -120,7 +128,7 @@ SandboxBroker::SetSecurityLevelForContentProcess(int32_t aSandboxLevel) result = mPolicy->SetDelayedIntegrityLevel(delayedIntegrityLevel); ret = ret && (sandbox::SBOX_ALL_OK == result); - if (aSandboxLevel > 1) { + if (aSandboxLevel > 2) { result = mPolicy->SetAlternateDesktop(true); ret = ret && (sandbox::SBOX_ALL_OK == result); } diff --git a/services/sync/modules/constants.js b/services/sync/modules/constants.js index 059d3fa5fb..5b30d41716 100644 --- a/services/sync/modules/constants.js +++ b/services/sync/modules/constants.js @@ -94,10 +94,10 @@ SCORE_UPDATE_DELAY: 100, // observed spurious idle/back events and short enough to pre-empt user activity. IDLE_OBSERVER_BACK_DELAY: 100, -// Number of records to upload in a single POST (multiple POSTS if exceeded) -// FIXME: Record size limit is 256k (new cluster), so this can be quite large! -// (Bug 569295) +// Max number of records or bytes to upload in a single POST - we'll do multiple POSTS if either +// MAX_UPLOAD_RECORDS or MAX_UPLOAD_BYTES is hit) MAX_UPLOAD_RECORDS: 100, +MAX_UPLOAD_BYTES: 1024 * 1023, // just under 1MB MAX_HISTORY_UPLOAD: 5000, MAX_HISTORY_DOWNLOAD: 5000, diff --git a/services/sync/modules/engines.js b/services/sync/modules/engines.js index 46ea7724df..7938137ff5 100644 --- a/services/sync/modules/engines.js +++ b/services/sync/modules/engines.js @@ -1418,13 +1418,7 @@ SyncEngine.prototype = { // collection we'll upload let up = new Collection(this.engineURL, null, this.service); - let count = 0; - - // Upload what we've got so far in the collection - let doUpload = Utils.bind2(this, function(desc) { - this._log.info("Uploading " + desc + " of " + modifiedIDs.length + - " records"); - let resp = up.post(); + let handleResponse = resp => { if (!resp.success) { this._log.debug("Uploading records failed: " + resp); resp.failureCode = ENGINE_UPLOAD_FAIL; @@ -1447,32 +1441,29 @@ SyncEngine.prototype = { let id = resp.obj.success[key]; delete this._modified[id]; } + } - up.clearRecords(); - }); + let postQueue = up.newPostQueue(this._log, handleResponse); for (let id of modifiedIDs) { + let out; + let ok = false; try { - let out = this._createRecord(id); + out = this._createRecord(id); if (this._log.level <= Log.Level.Trace) this._log.trace("Outgoing: " + out); out.encrypt(this.service.collectionKeys.keyForCollection(this.name)); - up.pushData(out); + ok = true; } catch (ex if !Async.isShutdownException(ex)) { this._log.warn("Error creating record", ex); } - - // Partial upload - if ((++count % MAX_UPLOAD_RECORDS) == 0) - doUpload((count - MAX_UPLOAD_RECORDS) + " - " + count + " out"); - + if (ok) { + postQueue.enqueue(out); + } this._store._sleep(0); } - - // Final upload - if (count % MAX_UPLOAD_RECORDS > 0) - doUpload(count >= MAX_UPLOAD_RECORDS ? "last batch" : "all"); + postQueue.flush(); } }, diff --git a/services/sync/modules/record.js b/services/sync/modules/record.js index a301ec6ee1..99f1bffdd5 100644 --- a/services/sync/modules/record.js +++ b/services/sync/modules/record.js @@ -600,14 +600,6 @@ Collection.prototype = { this._rebuildURL(); }, - pushData: function Coll_pushData(data) { - this._data.push(data); - }, - - clearRecords: function Coll_clearRecords() { - this._data = []; - }, - set recordHandler(onRecord) { // Save this because onProgress is called with this as the ChannelListener let coll = this; @@ -629,4 +621,82 @@ Collection.prototype = { } }; }, + + // This object only supports posting via the postQueue object. + post() { + throw new Error("Don't directly post to a collection - use newPostQueue instead"); + }, + + newPostQueue(log, postCallback) { + let poster = data => { + return Resource.prototype.post.call(this, data); + } + return new PostQueue(poster, log, postCallback); + }, }; + +/* A helper to manage the posting of records while respecting the various + size limits. +*/ +function PostQueue(poster, log, postCallback) { + // The "post" function we should use when it comes time to do the post. + this.poster = poster; + this.log = log; + + // The callback we make with the response when we do get around to making the + // post (which could be during any of the enqueue() calls or the final flush()) + // This callback may be called multiple times and must not add new items to + // the queue. + this.postCallback = postCallback; + + // The string where we are capturing the stringified version of the records + // queued so far. It will always be invalid JSON as it is always missing the + // close bracket. + this.queued = ""; + + // The number of records we've queued so far. + this.numQueued = 0; +} + +PostQueue.prototype = { + enqueue(record) { + // We want to ensure the record has a .toJSON() method defined - even + // though JSON.stringify() would implicitly call it, the stringify might + // still work even if it isn't defined, which isn't what we want. + let jsonRepr = record.toJSON(); + if (!jsonRepr) { + throw new Error("You must only call this with objects that explicitly support JSON"); + } + let bytes = JSON.stringify(jsonRepr); + // Note that we purposely don't check if a single record would exceed our + // limit - we still attempt the post and if it sees a 413 like we think it + // will, we just let that do whatever it does (which is probably cause + // ongoing sync failures for that engine - bug 1241356 exists to fix this) + // (Note that counter-intuitively, the post of the oversized record will + // not happen here but on the next .enqueue/.flush.) + + // Do a flush if we can't add this record without exceeding our limits. + let newLength = this.queued.length + bytes.length + 1; // extra 1 for trailing "]" + if (this.numQueued >= MAX_UPLOAD_RECORDS || newLength >= MAX_UPLOAD_BYTES) { + this.log.trace("PostQueue flushing"); // flush logs more info... + // We need to write the queue out before handling this one. + this.flush(); + } + // Either a ',' or a '[' depending on whether this is the first record. + this.queued += this.numQueued ? "," : "["; + this.queued += bytes; + this.numQueued++; + }, + + flush() { + if (!this.queued) { + // nothing queued. + return; + } + this.log.info(`Posting ${this.numQueued} records of ${this.queued.length+1} bytes`); + let queued = this.queued + "]"; + this.queued = ""; + this.numQueued = 0; + this.postCallback(this.poster(queued)); + }, +} diff --git a/services/sync/tests/unit/test_syncengine_sync.js b/services/sync/tests/unit/test_syncengine_sync.js index 6a6d047bf5..078aeb426c 100644 --- a/services/sync/tests/unit/test_syncengine_sync.js +++ b/services/sync/tests/unit/test_syncengine_sync.js @@ -1477,6 +1477,99 @@ add_test(function test_uploadOutgoing_MAX_UPLOAD_RECORDS() { } }); +add_test(function test_uploadOutgoing_MAX_UPLOAD_BYTES() { + _("SyncEngine._uploadOutgoing uploads in batches of MAX_UPLOAD_BYTES"); + + Service.identity.username = "foo"; + let collection = new ServerCollection(); + + // Let's count how many times the client posts to the server + let uploadCounts = []; + collection.post = (function(orig) { + return function(data) { + uploadCounts.push(JSON.parse(data).length); + return orig.call(this, data); + }; + }(collection.post)); + + let engine = makeRotaryEngine(); + + // A helper function that calculates the overhead of a record as uploaded + // to the server - it returns the size of a record with an empty string. + // This is so we can calculate exactly how many records we can fit into a + // batch (ie, we expect the record size that's actually uploaded to be the + // result of this function + the length of the data) + let calculateRecordOverhead = function() { + engine._store.items["string-no-x"] = ""; + let x = engine._createRecord("string-no-x"); + x.encrypt(Service.collectionKeys.keyForCollection(engine.name)); + delete engine._store.items["string-no-x"]; + return JSON.stringify(x).length; + } + + let allIds = []; + // Create a bunch of records (and server side handlers) - make 20 that will + // fit inside our byte limit. + let fullItemSize = (MAX_UPLOAD_BYTES - 2) / 20; + // fullItemSize includes the "," between records and quote characters (as we + // will use strings) + let itemSize = fullItemSize - calculateRecordOverhead() - (3 * 20); + // Add 21 of this size - the first 20 should fit in the first batch. + for (let i = 0; i < 21; i++) { + let id = 'string-no-' + i; + engine._store.items[id] = "X".repeat(itemSize); + engine._tracker.addChangedID(id, 0); + collection.insert(id); + allIds.push(id); + } + // Now a single large item that's greater than MAX_UPLOAD_BYTES. This should + // cause the 1 item that didn't fit in the previous batch to be uploaded + // by itself, then this large one by itself. + engine._store.items["large-item"] = "Y".repeat(MAX_UPLOAD_BYTES*2); + engine._tracker.addChangedID("large-item", 0); + collection.insert("large-item"); + allIds.push("large-item"); + // And a few more small items - these should all be uploaded together. + for (let i = 0; i < 20; i++) { + let id = 'small-no-' + i; + engine._store.items[id] = "ZZZZ"; + engine._tracker.addChangedID(id, 0); + collection.insert(id); + allIds.push(id); + } + + let meta_global = Service.recordManager.set(engine.metaURL, + new WBORecord(engine.metaURL)); + meta_global.payload.engines = {rotary: {version: engine.version, + syncID: engine.syncID}}; + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler() + }); + + let syncTesting = new SyncTestingInfrastructure(server); + + try { + + // Confirm initial environment. + do_check_eq(uploadCounts.length, 0); + + engine._syncStartup(); + engine._uploadOutgoing(); + + // Ensure all records have been uploaded. + for (let checkId of allIds) { + do_check_true(!!collection.payload(checkId)); + } + + // Ensure that the uploads were performed in the batch sizes we expect. + Assert.deepEqual(uploadCounts, [20, 1, 1, 20]); + + } finally { + cleanAndGo(server); + } +}); + add_test(function test_syncFinish_noDelete() { _("SyncEngine._syncFinish resets tracker's score"); diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/securable-module.js b/services/sync/tps/extensions/mozmill/resource/stdlib/securable-module.js index bfa7ef5a9f..8fc17f0121 100644 --- a/services/sync/tps/extensions/mozmill/resource/stdlib/securable-module.js +++ b/services/sync/tps/extensions/mozmill/resource/stdlib/securable-module.js @@ -40,6 +40,8 @@ const Cu = Components.utils; const Cr = Components.results; + Cu.import("resource://gre/modules/NetUtil.jsm"); + var exports = {}; var ios = Cc['@mozilla.org/network/io-service;1'] @@ -313,17 +315,23 @@ else baseURI = ios.newURI(base, null, null); var newURI = ios.newURI(path, null, baseURI); - var channel = ios.newChannelFromURI(newURI); + var channel = NetUtil.newChannel({ + uri: newURI, + loadUsingSystemPrincipal: true + }); try { - channel.open().close(); + channel.open2().close(); } catch (e if e.result == Cr.NS_ERROR_FILE_NOT_FOUND) { return null; } return newURI.spec; }, getFile: function getFile(path) { - var channel = ios.newChannel(path, null, null); - var iStream = channel.open(); + var channel = NetUtil.newChannel({ + uri: path, + loadUsingSystemPrincipal: true + }); + var iStream = channel.open2(); var ciStream = Cc["@mozilla.org/intl/converter-input-stream;1"]. createInstance(Ci.nsIConverterInputStream); var bufLen = 0x8000; diff --git a/services/sync/tps/extensions/tps/resource/modules/addons.jsm b/services/sync/tps/extensions/tps/resource/modules/addons.jsm index 8dae75edeb..1570b42b18 100644 --- a/services/sync/tps/extensions/tps/resource/modules/addons.jsm +++ b/services/sync/tps/extensions/tps/resource/modules/addons.jsm @@ -9,7 +9,7 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import("resource://gre/modules/AddonManager.jsm"); Cu.import("resource://gre/modules/addons/AddonRepository.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/NetUtil.jsm"); Cu.import("resource://services-common/async.js"); Cu.import("resource://services-sync/addonutils.js"); Cu.import("resource://services-sync/util.js"); @@ -20,15 +20,11 @@ const STATE_ENABLED = 1; const STATE_DISABLED = 2; function GetFileAsText(file) { - let channel = Services.io.newChannel2(file, - null, - null, - null, // aLoadingNode - Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_NORMAL, - Ci.nsIContentPolicy.TYPE_OTHER); - let inputStream = channel.open(); + let channel = NetUtil.newChannel({ + uri: file, + loadUsingSystemPrincipal: true + }); + let inputStream = channel.open2(); if (channel instanceof Ci.nsIHttpChannel && channel.responseStatus != 200) { return ""; diff --git a/storage/.eslintrc b/storage/.eslintrc new file mode 100644 index 0000000000..e7e9d8ce59 --- /dev/null +++ b/storage/.eslintrc @@ -0,0 +1,5 @@ +{ + "extends": [ + "../toolkit/.eslintrc" + ] +} diff --git a/storage/mozIStorageAsyncConnection.idl b/storage/mozIStorageAsyncConnection.idl index 0013f842ef..976f54d9a0 100644 --- a/storage/mozIStorageAsyncConnection.idl +++ b/storage/mozIStorageAsyncConnection.idl @@ -36,7 +36,12 @@ interface mozIStorageAsyncConnection : nsISupports { * - value: |null| * * @throws NS_ERROR_NOT_SAME_THREAD - * If is called on a thread other than the one that opened it. + * If called on a thread other than the one that opened it. The + * callback will not be dispatched. + * @throws NS_ERROR_NOT_INITIALIZED + * If called on a connection that has already been closed or was + * never properly opened. The callback will still be dispatched + * to the main thread despite the returned error. */ void asyncClose([optional] in mozIStorageCompletionCallback aCallback); diff --git a/storage/mozStorageConnection.cpp b/storage/mozStorageConnection.cpp index fc508f41d8..f7f6c0752a 100644 --- a/storage/mozStorageConnection.cpp +++ b/storage/mozStorageConnection.cpp @@ -18,6 +18,7 @@ #include "mozilla/CondVar.h" #include "mozilla/Attributes.h" #include "mozilla/ErrorNames.h" +#include "mozilla/unused.h" #include "mozIStorageAggregateFunction.h" #include "mozIStorageCompletionCallback.h" @@ -1184,13 +1185,78 @@ Connection::AsyncClose(mozIStorageCompletionCallback *aCallback) return NS_ERROR_NOT_SAME_THREAD; } - // It's possible to get here with a null mDBConn but a non-null async - // execution target if OpenAsyncDatabase failed somehow, so don't exit early - // in that case. + // The two relevant factors at this point are whether we have a database + // connection and whether we have an async execution thread. Here's what the + // states mean and how we handle them: + // + // - (mDBConn && asyncThread): The expected case where we are either an + // async connection or a sync connection that has been used asynchronously. + // Either way the caller must call us and not Close(). Nothing surprising + // about this. We'll dispatch AsyncCloseConnection to the already-existing + // async thread. + // + // - (mDBConn && !asyncThread): A somewhat unusual case where the caller + // opened the connection synchronously and was planning to use it + // asynchronously, but never got around to using it asynchronously before + // needing to shutdown. This has been observed to happen for the cookie + // service in a case where Firefox shuts itself down almost immediately + // after startup (for unknown reasons). In the Firefox shutdown case, + // we may also fail to create a new async execution thread if one does not + // already exist. (nsThreadManager will refuse to create new threads when + // it has already been told to shutdown.) As such, we need to handle a + // failure to create the async execution thread by falling back to + // synchronous Close() and also dispatching the completion callback because + // at least Places likes to spin a nested event loop that depends on the + // callback being invoked. + // + // Note that we have considered not trying to spin up the async execution + // thread in this case if it does not already exist, but the overhead of + // thread startup (if successful) is significantly less expensive than the + // worst-case potential I/O hit of synchronously closing a database when we + // could close it asynchronously. + // + // - (!mDBConn && asyncThread): This happens in some but not all cases where + // OpenAsyncDatabase encountered a problem opening the database. If it + // happened in all cases AsyncInitDatabase would just shut down the thread + // directly and we would avoid this case. But it doesn't, so for simplicity + // and consistency AsyncCloseConnection knows how to handle this and we + // act like this was the (mDBConn && asyncThread) case in this method. + // + // - (!mDBConn && !asyncThread): The database was never successfully opened or + // Close() or AsyncClose() has already been called (at least) once. This is + // undeniably a misuse case by the caller. We could optimize for this + // case by adding an additional check of mAsyncExecutionThread without using + // getAsyncExecutionTarget() to avoid wastefully creating a thread just to + // shut it down. But this complicates the method for broken caller code + // whereas we're still correct and safe without the special-case. nsIEventTarget *asyncThread = getAsyncExecutionTarget(); - if (!mDBConn && !asyncThread) - return NS_ERROR_NOT_INITIALIZED; + // Create our callback event if we were given a callback. This will + // eventually be dispatched in all cases, even if we fall back to Close() and + // the database wasn't open and we return an error. The rationale is that + // no existing consumer checks our return value and several of them like to + // spin nested event loops until the callback fires. Given that, it seems + // preferable for us to dispatch the callback in all cases. (Except the + // wrong thread misuse case we bailed on up above. But that's okay because + // that is statically wrong whereas these edge cases are dynamic.) + nsCOMPtr completeEvent; + if (aCallback) { + completeEvent = newCompletionEvent(aCallback); + } + + if (!asyncThread) { + // We were unable to create an async thread, so we need to fall back to + // using normal Close(). Since there is no async thread, Close() will + // not complain about that. (Close() may, however, complain if the + // connection is closed, but that's okay.) + if (completeEvent) { + // Closing the database is more important than returning an error code + // about a failure to dispatch, especially because all existing native + // callers ignore our return value. + Unused << NS_DispatchToMainThread(completeEvent.forget()); + } + return Close(); + } // setClosedState nullifies our connection pointer, so we take a raw pointer // off it, to pass it through the close procedure. @@ -1198,12 +1264,6 @@ Connection::AsyncClose(mozIStorageCompletionCallback *aCallback) nsresult rv = setClosedState(); NS_ENSURE_SUCCESS(rv, rv); - // Create our callback event if we were given a callback. - nsCOMPtr completeEvent; - if (aCallback) { - completeEvent = newCompletionEvent(aCallback); - } - // Create and dispatch our close event to the background thread. nsCOMPtr closeEvent; { @@ -1512,7 +1572,7 @@ Connection::ExecuteAsync(mozIStorageBaseStatement **aStatements, { nsTArray stmts(aNumStatements); for (uint32_t i = 0; i < aNumStatements; i++) { - nsCOMPtr stmt = + nsCOMPtr stmt = do_QueryInterface(aStatements[i]); // Obtain our StatementData. diff --git a/storage/test/unit/head_storage.js b/storage/test/unit/head_storage.js index 44106149bf..8971c1f77a 100644 --- a/storage/test/unit/head_storage.js +++ b/storage/test/unit/head_storage.js @@ -16,6 +16,8 @@ do_get_profile(); var dirSvc = Cc["@mozilla.org/file/directory_service;1"]. getService(Ci.nsIProperties); +var gDBConn = null; + function getTestDB() { var db = dirSvc.get("ProfD", Ci.nsIFile); @@ -39,6 +41,17 @@ function getFakeDB() return do_get_file("fakeDB.sqlite"); } +/** + * Delete the test database file. + */ +function deleteTestDB() +{ + print("*** Storage Tests: Trying to remove file!"); + var dbFile = getTestDB(); + if (dbFile.exists()) + try { dbFile.remove(false); } catch (e) { /* stupid windows box */ } +} + function cleanup() { // close the connection @@ -50,10 +63,7 @@ function cleanup() gDBConn = null; // removing test db - print("*** Storage Tests: Trying to remove file!"); - var dbFile = getTestDB(); - if (dbFile.exists()) - try { dbFile.remove(false); } catch(e) { /* stupid windows box */ } + deleteTestDB(); } /** @@ -66,7 +76,7 @@ function asyncCleanup() // close the connection print("*** Storage Tests: Trying to asyncClose!"); - getOpenedDatabase().asyncClose(function() { closed = true; }); + getOpenedDatabase().asyncClose(function () { closed = true; }); let curThread = Components.classes["@mozilla.org/thread-manager;1"] .getService().currentThread; @@ -78,10 +88,7 @@ function asyncCleanup() gDBConn = null; // removing test db - print("*** Storage Tests: Trying to remove file!"); - var dbFile = getTestDB(); - if (dbFile.exists()) - try { dbFile.remove(false); } catch(e) { /* stupid windows box */ } + deleteTestDB(); } function getService() @@ -89,8 +96,6 @@ function getService() return Cc["@mozilla.org/storage/service;1"].getService(Ci.mozIStorageService); } -var gDBConn = null; - /** * Get a connection to the test database. Creates and caches the connection * if necessary, otherwise reuses the existing cached connection. This @@ -156,7 +161,7 @@ function createAsyncStatement(aSQL) * can be used to do this concisely. * * Example: - * expectError(Cr.NS_ERROR_INVALID_ARG, function() explodingFunction()); + * expectError(Cr.NS_ERROR_INVALID_ARG, () => explodingFunction()); * * @param aErrorCode * The error code to expect from invocation of aFunction. @@ -169,7 +174,7 @@ function expectError(aErrorCode, aFunction) try { aFunction(); } - catch(e) { + catch (e) { if (e.result != aErrorCode) { do_throw("Got an exception, but the result code was not the expected " + "one. Expected " + aErrorCode + ", got " + e.result); @@ -208,7 +213,7 @@ function verifyQuery(aSQLString, aBind, aResults) do_check_eq(stmt.VALUE_TYPE_NULL, valType); do_check_true(stmt.getIsNull(iCol)); } - else if (typeof(expectedVal) == "number") { + else if (typeof expectedVal == "number") { if (Math.floor(expectedVal) == expectedVal) { do_check_eq(stmt.VALUE_TYPE_INTEGER, valType); do_check_eq(expectedVal, stmt.getInt32(iCol)); @@ -218,7 +223,7 @@ function verifyQuery(aSQLString, aBind, aResults) do_check_eq(expectedVal, stmt.getDouble(iCol)); } } - else if (typeof(expectedVal) == "string") { + else if (typeof expectedVal == "string") { do_check_eq(stmt.VALUE_TYPE_TEXT, valType); do_check_eq(expectedVal, stmt.getUTF8String(iCol)); } @@ -262,5 +267,107 @@ function getTableRowCount(aTableName) return currentRows; } -cleanup(); +//////////////////////////////////////////////////////////////////////////////// +//// Promise-Returning Functions +function asyncClone(db, readOnly) { + let deferred = Promise.defer(); + db.asyncClone(readOnly, function (status, db2) { + if (Components.isSuccessCode(status)) { + deferred.resolve(db2); + } else { + deferred.reject(status); + } + }); + return deferred.promise; +} + +function asyncClose(db) { + let deferred = Promise.defer(); + db.asyncClose(function (status) { + if (Components.isSuccessCode(status)) { + deferred.resolve(); + } else { + deferred.reject(status); + } + }); + return deferred.promise; +} + +function openAsyncDatabase(file, options) { + let deferred = Promise.defer(); + let properties; + if (options) { + properties = Cc["@mozilla.org/hash-property-bag;1"]. + createInstance(Ci.nsIWritablePropertyBag); + for (let k in options) { + properties.setProperty(k, options[k]); + } + } + getService().openAsyncDatabase(file, properties, function (status, db) { + if (Components.isSuccessCode(status)) { + deferred.resolve(db.QueryInterface(Ci.mozIStorageAsyncConnection)); + } else { + deferred.reject(status); + } + }); + return deferred.promise; +} + +function executeAsync(statement, onResult) { + let deferred = Promise.defer(); + statement.executeAsync({ + handleError: function (error) { + deferred.reject(error); + }, + handleResult: function (result) { + if (onResult) { + onResult(result); + } + }, + handleCompletion: function (result) { + deferred.resolve(result); + } + }); + return deferred.promise; +} + +function executeMultipleStatementsAsync(db, statements, onResult) { + let deferred = Promise.defer(); + db.executeAsync(statements, statements.length, { + handleError: function (error) { + deferred.reject(error); + }, + handleResult: function (result) { + if (onResult) { + onResult(result); + } + }, + handleCompletion: function (result) { + deferred.resolve(result); + } + }); + return deferred.promise; +} + +function executeSimpleSQLAsync(db, query, onResult) { + let deferred = Promise.defer(); + db.executeSimpleSQLAsync(query, { + handleError(error) { + deferred.reject(error); + }, + handleResult(result) { + if (onResult) { + onResult(result); + } else { + do_throw("No results were expected"); + } + }, + handleCompletion(result) { + deferred.resolve(result); + } + }); + return deferred.promise; +} + +cleanup(); diff --git a/storage/test/unit/test_bug-365166.js b/storage/test/unit/test_bug-365166.js index d8c3448767..ebcdc69005 100644 --- a/storage/test/unit/test_bug-365166.js +++ b/storage/test/unit/test_bug-365166.js @@ -1,6 +1,3 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/licenses/publicdomain/ */ - // Testcase for bug 365166 - crash [@ strlen] calling // mozIStorageStatement::getColumnName of a statement created with // "PRAGMA user_version" or "PRAGMA schema_version" @@ -16,7 +13,7 @@ function run_test() { var file = getTestDB(); var storageService = Components.classes["@mozilla.org/storage/service;1"]. getService(Components.interfaces.mozIStorageService); - var conn = storageService.openDatabase(file); + var conn = storageService.openDatabase(file); var statement = conn.createStatement(sql); try { // This shouldn't crash: diff --git a/storage/test/unit/test_bug-393952.js b/storage/test/unit/test_bug-393952.js index dd3b7f7205..f3c61f62bd 100644 --- a/storage/test/unit/test_bug-393952.js +++ b/storage/test/unit/test_bug-393952.js @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // Testcase for bug 393952: crash when I try to VACUUM and one of the tables -// has a UNIQUE text column. StorageUnicodeFunctions::likeFunction() +// has a UNIQUE text column. StorageUnicodeFunctions::likeFunction() // needs to handle null aArgv[0] and aArgv[1] function setup() @@ -32,7 +32,7 @@ function run_test() for (var i = 0; i < tests.length; i++) tests[i](); - + cleanup(); } diff --git a/storage/test/unit/test_bug-429521.js b/storage/test/unit/test_bug-429521.js index 2dd090d1fb..a9eafc1c2f 100644 --- a/storage/test/unit/test_bug-429521.js +++ b/storage/test/unit/test_bug-429521.js @@ -2,48 +2,45 @@ * 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/. */ -function setup() -{ - getOpenedDatabase().createTable("t1", "x TEXT"); +function setup() { + getOpenedDatabase().createTable("t1", "x TEXT"); - var stmt = createStatement("INSERT INTO t1 (x) VALUES ('/mozilla.org/20070129_1/Europe/Berlin')"); - stmt.execute(); - stmt.finalize(); + var stmt = createStatement("INSERT INTO t1 (x) VALUES ('/mozilla.org/20070129_1/Europe/Berlin')"); + stmt.execute(); + stmt.finalize(); } -function test_bug429521() -{ - var stmt = createStatement( - "SELECT DISTINCT(zone) FROM ("+ - "SELECT x AS zone FROM t1 WHERE x LIKE '/mozilla.org%'" + - ");"); +function test_bug429521() { + var stmt = createStatement( + "SELECT DISTINCT(zone) FROM (" + + "SELECT x AS zone FROM t1 WHERE x LIKE '/mozilla.org%'" + + ");"); - print("*** test_bug429521: started"); + print("*** test_bug429521: started"); - try { - while (stmt.executeStep()) { - print("*** test_bug429521: step() Read wrapper.row.zone"); + try { + while (stmt.executeStep()) { + print("*** test_bug429521: step() Read wrapper.row.zone"); - // BUG: the print commands after the following statement - // are never executed. Script stops immediately. - var tzId = stmt.row.zone; + // BUG: the print commands after the following statement + // are never executed. Script stops immediately. + var tzId = stmt.row.zone; - print("*** test_bug429521: step() Read wrapper.row.zone finished"); - } - } catch (e) { - print("*** test_bug429521: " + e); + print("*** test_bug429521: step() Read wrapper.row.zone finished"); } + } catch (e) { + print("*** test_bug429521: " + e); + } - print("*** test_bug429521: finished"); + print("*** test_bug429521: finished"); - stmt.finalize(); + stmt.finalize(); } -function run_test() -{ +function run_test() { setup(); test_bug429521(); - + cleanup(); } diff --git a/storage/test/unit/test_bug-444233.js b/storage/test/unit/test_bug-444233.js index 5410909585..eb315f9342 100644 --- a/storage/test/unit/test_bug-444233.js +++ b/storage/test/unit/test_bug-444233.js @@ -3,49 +3,49 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ function setup() { - // Create the table - getOpenedDatabase().createTable("test_bug444233", - "id INTEGER PRIMARY KEY, value TEXT"); + // Create the table + getOpenedDatabase().createTable("test_bug444233", + "id INTEGER PRIMARY KEY, value TEXT"); - // Insert dummy data, using wrapper methods - var stmt = createStatement("INSERT INTO test_bug444233 (value) VALUES (:value)"); - stmt.params.value = "value1" - stmt.execute(); - stmt.finalize(); - - stmt = createStatement("INSERT INTO test_bug444233 (value) VALUES (:value)"); - stmt.params.value = "value2" - stmt.execute(); - stmt.finalize(); + // Insert dummy data, using wrapper methods + var stmt = createStatement("INSERT INTO test_bug444233 (value) VALUES (:value)"); + stmt.params.value = "value1"; + stmt.execute(); + stmt.finalize(); + + stmt = createStatement("INSERT INTO test_bug444233 (value) VALUES (:value)"); + stmt.params.value = "value2"; + stmt.execute(); + stmt.finalize(); } function test_bug444233() { - print("*** test_bug444233: started"); - - // Check that there are 2 results - var stmt = createStatement("SELECT COUNT(*) AS number FROM test_bug444233"); - do_check_true(stmt.executeStep()); - do_check_eq(2, stmt.row.number); - stmt.reset(); - stmt.finalize(); + print("*** test_bug444233: started"); - print("*** test_bug444233: doing delete"); - - // Now try to delete using IN - // Cheating since we know ids are 1,2 - try { - var ids = [1, 2]; - stmt = createStatement("DELETE FROM test_bug444233 WHERE id IN (:ids)"); - stmt.params.ids = ids; - } catch (e) { - print("*** test_bug444233: successfully caught exception"); - } - stmt.finalize(); + // Check that there are 2 results + var stmt = createStatement("SELECT COUNT(*) AS number FROM test_bug444233"); + do_check_true(stmt.executeStep()); + do_check_eq(2, stmt.row.number); + stmt.reset(); + stmt.finalize(); + + print("*** test_bug444233: doing delete"); + + // Now try to delete using IN + // Cheating since we know ids are 1,2 + try { + var ids = [1, 2]; + stmt = createStatement("DELETE FROM test_bug444233 WHERE id IN (:ids)"); + stmt.params.ids = ids; + } catch (e) { + print("*** test_bug444233: successfully caught exception"); + } + stmt.finalize(); } function run_test() { - setup(); - test_bug444233(); - cleanup(); + setup(); + test_bug444233(); + cleanup(); } diff --git a/storage/test/unit/test_chunk_growth.js b/storage/test/unit/test_chunk_growth.js index 4d5fcc9da3..2cf91fe166 100644 --- a/storage/test/unit/test_chunk_growth.js +++ b/storage/test/unit/test_chunk_growth.js @@ -1,34 +1,31 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - // This file tests SQLITE_FCNTL_CHUNK_SIZE behaves as expected function run_sql(d, sql) { - var stmt = d.createStatement(sql) - stmt.execute() + var stmt = d.createStatement(sql); + stmt.execute(); stmt.finalize(); } -function new_file(name) -{ +function new_file(name) { var file = dirSvc.get("ProfD", Ci.nsIFile); file.append(name); return file; } function get_size(name) { - return new_file(name).fileSize + return new_file(name).fileSize; } -function run_test() -{ +function run_test() { const filename = "chunked.sqlite"; const CHUNK_SIZE = 512 * 1024; var d = getDatabase(new_file(filename)); try { d.setGrowthIncrement(CHUNK_SIZE, ""); - } catch (e if e.result == Cr.NS_ERROR_FILE_TOO_BIG) { + } catch (e) { + if (e.result != Cr.NS_ERROR_FILE_TOO_BIG) { + throw e; + } print("Too little free space to set CHUNK_SIZE!"); return; } @@ -39,17 +36,17 @@ function run_test() * While writing ensure that the file size growth in chunksize set above. */ const str1024 = new Array(1024).join("T"); - for(var i = 0; i < 32; i++) { + for (var i = 0; i < 32; i++) { run_sql(d, "INSERT INTO bloat VALUES('" + str1024 + "')"); - var size = get_size(filename) + var size = get_size(filename); // Must not grow in small increments. do_check_true(size == orig_size || size >= CHUNK_SIZE); } /* In addition to growing in chunk-size increments, the db * should shrink in chunk-size increments too. */ - run_sql(d, "DELETE FROM bloat") - run_sql(d, "VACUUM") - do_check_true(get_size(filename) >= CHUNK_SIZE) + run_sql(d, "DELETE FROM bloat"); + run_sql(d, "VACUUM"); + do_check_true(get_size(filename) >= CHUNK_SIZE); } diff --git a/storage/test/unit/test_connection_asyncClose.js b/storage/test/unit/test_connection_asyncClose.js new file mode 100644 index 0000000000..36b283fcef --- /dev/null +++ b/storage/test/unit/test_connection_asyncClose.js @@ -0,0 +1,125 @@ +/* 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/. */ + +/* + * Thorough branch coverage for asyncClose. + * + * Coverage of asyncClose by connection state at time of AsyncClose invocation: + * - (asyncThread && mDBConn) => AsyncCloseConnection used, actually closes + * - test_asyncClose_does_not_complete_before_statements + * - test_double_asyncClose_throws + * - test_asyncClose_does_not_throw_without_callback + * - (asyncThread && !mDBConn) => AsyncCloseConnection used, although no close + * is required. Note that this is only possible in the event that + * openAsyncDatabase was used and we failed to open the database. + * Additionally, the async connection will never be exposed to the caller and + * AsyncInitDatabase will be the one to (automatically) call AsyncClose. + * - test_asyncClose_failed_open + * - (!asyncThread && mDBConn) => Close() invoked, actually closes + * - test_asyncClose_on_sync_db + * - (!asyncThread && !mDBConn) => Close() invoked, no close needed, errors. + * This happens if the database has already been closed. + * - test_double_asyncClose_throws + */ + + +/** + * Sanity check that our close indeed happens after asynchronously executed + * statements scheduled during the same turn of the event loop. Note that we + * just care that the statement says it completed without error, we're not + * worried that the close will happen and then the statement will magically + * complete. + */ +add_task(function* test_asyncClose_does_not_complete_before_statements() { + let db = getService().openDatabase(getTestDB()); + let stmt = db.createStatement("SELECT * FROM sqlite_master"); + // Issue the executeAsync but don't yield for it... + let asyncStatementPromise = executeAsync(stmt); + stmt.finalize(); + + // Issue the close. (And now the order of yielding doesn't matter.) + // Branch coverage: (asyncThread && mDBConn) + yield asyncClose(db); + equal((yield asyncStatementPromise), + Ci.mozIStorageStatementCallback.REASON_FINISHED); +}); + +/** + * Open an async database (ensures the async thread is created) and then invoke + * AsyncClose() twice without yielding control flow. The first will initiate + * the actual async close after calling setClosedState which synchronously + * impacts what the second call will observe. The second call will then see the + * async thread is not available and fall back to invoking Close() which will + * notice the mDBConn is already gone. + */ +add_task(function test_double_asyncClose_throws() { + let db = yield openAsyncDatabase(getTestDB()); + + // (Don't yield control flow yet, save the promise for after we make the + // second call.) + // Branch coverage: (asyncThread && mDBConn) + let realClosePromise = yield asyncClose(db); + try { + // Branch coverage: (!asyncThread && !mDBConn) + db.asyncClose(); + ok(false, "should have thrown"); + } catch (e) { + equal(e.result, Cr.NS_ERROR_NOT_INITIALIZED); + } + + yield realClosePromise; +}); + +/** + * Create a sync db connection and never take it asynchronous and then call + * asyncClose on it. This will bring the async thread to life to perform the + * shutdown to avoid blocking the main thread, although we won't be able to + * tell the difference between this happening and the method secretly shunting + * to close(). + */ +add_task(function* test_asyncClose_on_sync_db() { + let db = getService().openDatabase(getTestDB()); + + // Branch coverage: (!asyncThread && mDBConn) + yield asyncClose(db); + ok(true, 'closed sync connection asynchronously'); +}); + +/** + * Fail to asynchronously open a DB in order to get an async thread existing + * without having an open database and asyncClose invoked. As per the file + * doc-block, note that asyncClose will automatically be invoked by the + * AsyncInitDatabase when it fails to open the database. We will never be + * provided with a reference to the connection and so cannot call AsyncClose on + * it ourselves. + */ +add_task(function* test_asyncClose_failed_open() { + // This will fail and the promise will be rejected. + let openPromise = openAsyncDatabase(getFakeDB()); + yield openPromise.then( + () => { + ok(false, 'we should have failed to open the db; this test is broken!'); + }, + () => { + ok(true, 'correctly failed to open db; bg asyncClose should happen'); + } + ); + // (NB: we are unable to observe the thread shutdown, but since we never open + // a database, this test is not going to interfere with other tests so much.) +}); + +// THE TEST BELOW WANTS TO BE THE LAST TEST WE RUN. DO NOT MAKE IT SAD. +/** + * Verify that asyncClose without a callback does not explode. Without a + * callback the shutdown is not actually observable, so we run this test last + * in order to avoid weird overlaps. + */ +add_task(function test_asyncClose_does_not_throw_without_callback() { + let db = yield openAsyncDatabase(getTestDB()); + // Branch coverage: (asyncThread && mDBConn) + db.asyncClose(); + ok(true, 'if we shutdown cleanly and do not crash, then we succeeded'); +}); +// OBEY SHOUTING UPPER-CASE COMMENTS. +// ADD TESTS ABOVE THE FORMER TEST, NOT BELOW IT. diff --git a/storage/test/unit/test_connection_executeAsync.js b/storage/test/unit/test_connection_executeAsync.js index 2111362ce1..e56d98e555 100644 --- a/storage/test/unit/test_connection_executeAsync.js +++ b/storage/test/unit/test_connection_executeAsync.js @@ -5,6 +5,10 @@ /* * This file tests the functionality of mozIStorageConnection::executeAsync for * both mozIStorageStatement and mozIStorageAsyncStatement. + * + * A single database connection is used for the entirety of the test, which is + * a legacy thing, but we otherwise use the modern promise-based driver and + * async helpers. */ const INTEGER = 1; @@ -12,9 +16,12 @@ const TEXT = "this is test text"; const REAL = 3.23; const BLOB = [1, 2]; -function test_create_and_add() -{ - getOpenedDatabase().executeSimpleSQL( +add_task(function* test_first_create_and_add() { + // synchronously open the database and let gDBConn hold onto it because we + // use this database + let db = getOpenedDatabase(); + // synchronously set up our table *that will be used for the rest of the file* + db.executeSimpleSQL( "CREATE TABLE test (" + "id INTEGER, " + "string TEXT, " + @@ -25,7 +32,7 @@ function test_create_and_add() ); let stmts = []; - stmts[0] = getOpenedDatabase().createStatement( + stmts[0] = db.createStatement( "INSERT INTO test (id, string, number, nuller, blober) VALUES (?, ?, ?, ?, ?)" ); stmts[0].bindByIndex(0, INTEGER); @@ -41,65 +48,54 @@ function test_create_and_add() stmts[1].bindByIndex(2, null); stmts[1].bindBlobByIndex(3, BLOB, BLOB.length); - getOpenedDatabase().executeAsync(stmts, stmts.length, { - handleResult: function(aResultSet) - { - dump("handleResult("+aResultSet+")\n"); - do_throw("unexpected results obtained!"); - }, - handleError: function(aError) - { - dump("handleError("+aError.result+")\n"); - do_throw("unexpected error!"); - }, - handleCompletion: function(aReason) - { - dump("handleCompletion("+aReason+")\n"); - do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason); + // asynchronously execute the statements + let execResult = yield executeMultipleStatementsAsync( + db, + stmts, + function(aResultSet) { + ok(false, 'we only did inserts so we should not have gotten results!'); + }); + equal(Ci.mozIStorageStatementCallback.REASON_FINISHED, execResult, + 'execution should have finished successfully.'); - // Check that the result is in the table - let stmt = getOpenedDatabase().createStatement( - "SELECT string, number, nuller, blober FROM test WHERE id = ?" - ); - stmt.bindByIndex(0, INTEGER); - try { - do_check_true(stmt.executeStep()); - do_check_eq(TEXT, stmt.getString(0)); - do_check_eq(REAL, stmt.getDouble(1)); - do_check_true(stmt.getIsNull(2)); - let count = { value: 0 }; - let blob = { value: null }; - stmt.getBlob(3, count, blob); - do_check_eq(BLOB.length, count.value); - for (let i = 0; i < BLOB.length; i++) - do_check_eq(BLOB[i], blob.value[i]); - } - finally { - stmt.finalize(); - } + // Check that the result is in the table + let stmt = db.createStatement( + "SELECT string, number, nuller, blober FROM test WHERE id = ?" + ); + stmt.bindByIndex(0, INTEGER); + try { + do_check_true(stmt.executeStep()); + do_check_eq(TEXT, stmt.getString(0)); + do_check_eq(REAL, stmt.getDouble(1)); + do_check_true(stmt.getIsNull(2)); + let count = { value: 0 }; + let blob = { value: null }; + stmt.getBlob(3, count, blob); + do_check_eq(BLOB.length, count.value); + for (let i = 0; i < BLOB.length; i++) + do_check_eq(BLOB[i], blob.value[i]); + } + finally { + stmt.finalize(); + } - // Make sure we have two rows in the table - stmt = getOpenedDatabase().createStatement( - "SELECT COUNT(1) FROM test" - ); - try { - do_check_true(stmt.executeStep()); - do_check_eq(2, stmt.getInt32(0)); - } - finally { - stmt.finalize(); - } + // Make sure we have two rows in the table + stmt = db.createStatement( + "SELECT COUNT(1) FROM test" + ); + try { + do_check_true(stmt.executeStep()); + do_check_eq(2, stmt.getInt32(0)); + } + finally { + stmt.finalize(); + } - // Run the next test. - run_next_test(); - } - }); stmts[0].finalize(); stmts[1].finalize(); -} +}); -function test_multiple_bindings_on_statements() -{ +add_task(function* test_last_multiple_bindings_on_statements() { // This tests to make sure that we pass all the statements multiply bound // parameters when we call executeAsync. const AMOUNT_TO_ADD = 5; @@ -144,117 +140,32 @@ function test_multiple_bindings_on_statements() } // Execute asynchronously. - getOpenedDatabase().executeAsync(stmts, stmts.length, { - handleResult: function(aResultSet) - { - do_throw("Unexpected call to handleResult!"); - }, - handleError: function(aError) - { - print("Error code " + aError.result + " with message '" + - aError.message + "' returned."); - do_throw("Unexpected error!"); - }, - handleCompletion: function(aReason) - { - print("handleCompletion(" + aReason + - ") for test_multiple_bindings_on_statements"); - do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason); + let execResult = yield executeMultipleStatementsAsync( + db, + stmts, + function(aResultSet) { + ok(false, 'we only did inserts so we should not have gotten results!'); + }); + equal(Ci.mozIStorageStatementCallback.REASON_FINISHED, execResult, + 'execution should have finished successfully.'); - // Check to make sure we added all of our rows. - try { - do_check_true(countStmt.executeStep()); - do_check_eq(currentRows + (ITERATIONS * AMOUNT_TO_ADD), - countStmt.row.count); - } - finally { - countStmt.finalize(); - } - - // Run the next test. - run_next_test(); - } - }); - stmts.forEach(function(stmt) stmt.finalize()); -} - -function test_asyncClose_does_not_complete_before_statements() -{ - let stmt = createStatement("SELECT * FROM sqlite_master"); - let executed = false; - stmt.executeAsync({ - handleResult: function(aResultSet) - { - }, - handleError: function(aError) - { - print("Error code " + aError.result + " with message '" + - aError.message + "' returned."); - do_throw("Unexpected error!"); - }, - handleCompletion: function(aReason) - { - print("handleCompletion(" + aReason + - ") for test_asyncClose_does_not_complete_before_statements"); - do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason); - executed = true; - } - }); - stmt.finalize(); - - getOpenedDatabase().asyncClose(function() { - // Ensure that the statement executed to completion. - do_check_true(executed); - - // Reset gDBConn so that later tests will get a new connection object. - gDBConn = null; - run_next_test(); - }); -} - -function test_asyncClose_does_not_throw_no_callback() -{ - getOpenedDatabase().asyncClose(); - - // Reset gDBConn so that later tests will get a new connection object. - gDBConn = null; - run_next_test(); -} - -function test_double_asyncClose_throws() -{ - let conn = getOpenedDatabase(); - conn.asyncClose(); + // Check to make sure we added all of our rows. try { - conn.asyncClose(); - do_throw("should have thrown"); - // There is a small race condition here, which can cause either of - // Cr.NS_ERROR_NOT_INITIALIZED or Cr.NS_ERROR_UNEXPECTED to be thrown. - } catch (e if "result" in e && e.result == Cr.NS_ERROR_NOT_INITIALIZED) { - do_print("NS_ERROR_NOT_INITIALIZED"); - } catch (e if "result" in e && e.result == Cr.NS_ERROR_UNEXPECTED) { - do_print("NS_ERROR_UNEXPECTED"); - } catch (e) { + do_check_true(countStmt.executeStep()); + do_check_eq(currentRows + (ITERATIONS * AMOUNT_TO_ADD), + countStmt.row.count); + } + finally { + countStmt.finalize(); } - // Reset gDBConn so that later tests will get a new connection object. + stmts.forEach(stmt => stmt.finalize()); + + // we are the last test using this connection and since it has gone async + // we *must* call asyncClose on it. + yield asyncClose(db); gDBConn = null; - run_next_test(); -} +}); -//////////////////////////////////////////////////////////////////////////////// -//// Test Runner - -[ - test_create_and_add, - test_multiple_bindings_on_statements, - test_asyncClose_does_not_complete_before_statements, - test_asyncClose_does_not_throw_no_callback, - test_double_asyncClose_throws, -].forEach(add_test); - -function run_test() -{ - cleanup(); - run_next_test(); -} +// If you add a test down here you will need to move the asyncClose or clean +// things up a little more. diff --git a/storage/test/unit/test_connection_executeSimpleSQLAsync.js b/storage/test/unit/test_connection_executeSimpleSQLAsync.js index ad7c94b4c6..142cc8e143 100644 --- a/storage/test/unit/test_connection_executeSimpleSQLAsync.js +++ b/storage/test/unit/test_connection_executeSimpleSQLAsync.js @@ -11,51 +11,7 @@ const INTEGER = 1; const TEXT = "this is test text"; const REAL = 3.23; -function asyncClose(db) { - let deferred = Promise.defer(); - db.asyncClose(function(status) { - if (Components.isSuccessCode(status)) { - deferred.resolve(); - } else { - deferred.reject(status); - } - }); - return deferred.promise; -} - -function openAsyncDatabase(file) { - let deferred = Promise.defer(); - getService().openAsyncDatabase(file, null, function(status, db) { - if (Components.isSuccessCode(status)) { - deferred.resolve(db.QueryInterface(Ci.mozIStorageAsyncConnection)); - } else { - deferred.reject(status); - } - }); - return deferred.promise; -} - -function executeSimpleSQLAsync(db, query, onResult) { - let deferred = Promise.defer(); - db.executeSimpleSQLAsync(query, { - handleError: function(error) { - deferred.reject(error); - }, - handleResult: function(result) { - if (onResult) { - onResult(result); - } else { - do_throw("No results were expected"); - } - }, - handleCompletion: function(result) { - deferred.resolve(result); - } - }); - return deferred.promise; -} - -add_task(function test_create_and_add() { +add_task(function* test_create_and_add() { let adb = yield openAsyncDatabase(getTestDB()); let completion = yield executeSimpleSQLAsync(adb, @@ -73,7 +29,7 @@ add_task(function test_create_and_add() { completion = yield executeSimpleSQLAsync(adb, "SELECT string, number FROM test WHERE id = 1", - function(aResultSet) { + function (aResultSet) { result = aResultSet.getNextRow(); do_check_eq(2, result.numEntries); do_check_eq(TEXT, result.getString(0)); @@ -86,7 +42,7 @@ add_task(function test_create_and_add() { result = null; yield executeSimpleSQLAsync(adb, "SELECT COUNT(0) FROM test", - function(aResultSet) { + function (aResultSet) { result = aResultSet.getNextRow(); do_check_eq(1, result.getInt32(0)); }); @@ -97,12 +53,12 @@ add_task(function test_create_and_add() { }); -add_task(function test_asyncClose_does_not_complete_before_statement() { +add_task(function* test_asyncClose_does_not_complete_before_statement() { let adb = yield openAsyncDatabase(getTestDB()); let executed = false; let reason = yield executeSimpleSQLAsync(adb, "SELECT * FROM test", - function(aResultSet) { + function (aResultSet) { let result = aResultSet.getNextRow(); do_check_neq(result, null); @@ -121,11 +77,3 @@ add_task(function test_asyncClose_does_not_complete_before_statement() { yield asyncClose(adb); }); - -//////////////////////////////////////////////////////////////////////////////// -//// Test Runner - -function run_test() -{ - run_next_test(); -} diff --git a/storage/test/unit/test_js_helpers.js b/storage/test/unit/test_js_helpers.js index 74a8f7288a..6e908317f9 100644 --- a/storage/test/unit/test_js_helpers.js +++ b/storage/test/unit/test_js_helpers.js @@ -23,7 +23,7 @@ function test_params_enumerate() let index = 0; for (let name in stmt.params) { if (name == "QueryInterface") - continue; + continue; do_check_eq(name, expected[index++]); } } @@ -123,5 +123,5 @@ function run_test() ); // Run the tests. - tests.forEach(function(test) test()); + tests.forEach(test => test()); } diff --git a/storage/test/unit/test_levenshtein.js b/storage/test/unit/test_levenshtein.js index bcdd74a824..ced141abd8 100644 --- a/storage/test/unit/test_levenshtein.js +++ b/storage/test/unit/test_levenshtein.js @@ -30,8 +30,7 @@ function check_levenshtein(db, s, t, expectedDistance) try { do_check_true(stmt.executeStep()); do_check_eq(expectedDistance, stmt.row.result); - } - finally { + } finally { stmt.reset(); stmt.finalize(); } @@ -56,7 +55,7 @@ function testLevenshtein(db) check_levenshtein(db, "foo", null, null); check_levenshtein(db, null, "bar", null); check_levenshtein(db, null, null, null); - + // The levenshteinDistance function allocates temporary memory on the stack // if it can. Test some strings long enough to force a heap allocation. var dots1000 = Array(1001).join("."); diff --git a/storage/test/unit/test_like.js b/storage/test/unit/test_like.js index 6c8e45e628..b42f2eb27f 100644 --- a/storage/test/unit/test_like.js +++ b/storage/test/unit/test_like.js @@ -93,7 +93,7 @@ function test_like_2() stmt.reset(); stmt.finalize(); } - + function test_like_3() { var stmt = createStatement("SELECT x FROM t1 WHERE x LIKE ?;"); @@ -107,7 +107,7 @@ function test_like_3() stmt.reset(); stmt.finalize(); } - + function test_like_4() { var stmt = createStatement("SELECT x FROM t1 WHERE x LIKE ?;"); @@ -153,7 +153,7 @@ function test_like_6() stmt.reset(); stmt.finalize(); } - + function test_like_7() { var stmt = createStatement("SELECT x FROM t1 WHERE x LIKE ?;"); @@ -185,17 +185,18 @@ function test_like_8() stmt.reset(); stmt.finalize(); } - -var tests = [test_count, test_like_1, test_like_2, test_like_3, test_like_4, + +var tests = [test_count, test_like_1, test_like_2, test_like_3, test_like_4, test_like_5, test_like_6, test_like_7, test_like_8]; function run_test() { setup(); - for (var i = 0; i < tests.length; i++) + for (var i = 0; i < tests.length; i++) { tests[i](); - + } + cleanup(); } diff --git a/storage/test/unit/test_like_escape.js b/storage/test/unit/test_like_escape.js index e033df5bab..414f6237cf 100644 --- a/storage/test/unit/test_like_escape.js +++ b/storage/test/unit/test_like_escape.js @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const LATIN1_AE = "\xc6"; -const LATIN1_ae = "\xe6"; +const LATIN1_ae = "\xe6"; function setup() { @@ -19,7 +19,7 @@ function setup() stmt.execute(); stmt.finalize(); } - + function test_escape_for_like_ascii() { var stmt = createStatement("SELECT x FROM t1 WHERE x LIKE ?1 ESCAPE '/'"); @@ -27,7 +27,7 @@ function test_escape_for_like_ascii() // verify that we escaped / _ and % do_check_eq(paramForLike, "oo//bar/_baz/%20chees"); // prepend and append with % for "contains" - stmt.bindByIndex(0, "%" + paramForLike + "%"); + stmt.bindByIndex(0, "%" + paramForLike + "%"); stmt.executeStep(); do_check_eq("foo/bar_baz%20cheese", stmt.getString(0)); stmt.finalize(); @@ -52,8 +52,9 @@ function run_test() { setup(); - for (var i = 0; i < tests.length; i++) + for (var i = 0; i < tests.length; i++) { tests[i](); - + } + cleanup(); } diff --git a/storage/test/unit/test_locale_collation.js b/storage/test/unit/test_locale_collation.js index 9bddbea979..402fd8b458 100644 --- a/storage/test/unit/test_locale_collation.js +++ b/storage/test/unit/test_locale_collation.js @@ -140,24 +140,25 @@ function localeCompare(aCollation) var strength; switch (aCollation) { - case "locale": - strength = Ci.nsICollation.kCollationCaseInSensitive; - break; - case "locale_case_sensitive": - strength = Ci.nsICollation.kCollationAccentInsenstive; - break; - case "locale_accent_sensitive": - strength = Ci.nsICollation.kCollationCaseInsensitiveAscii; - break; - case "locale_case_accent_sensitive": - strength = Ci.nsICollation.kCollationCaseSensitive; - break; - default: - do_throw("Error in test: unknown collation '" + aCollation + "'"); - break; + case "locale": + strength = Ci.nsICollation.kCollationCaseInSensitive; + break; + case "locale_case_sensitive": + strength = Ci.nsICollation.kCollationAccentInsenstive; + break; + case "locale_accent_sensitive": + strength = Ci.nsICollation.kCollationCaseInsensitiveAscii; + break; + case "locale_case_accent_sensitive": + strength = Ci.nsICollation.kCollationCaseSensitive; + break; + default: + do_throw("Error in test: unknown collation '" + aCollation + "'"); + break; } - return function (aStr1, aStr2) - gLocaleCollation.compareString(strength, aStr1, aStr2); + return function (aStr1, aStr2) { + return gLocaleCollation.compareString(strength, aStr1, aStr2); + }; } /** @@ -179,8 +180,9 @@ function readTestData() let line = {}; let lines = []; - while (istream.readLine(line)) - lines.push(line.value); + while (istream.readLine(line)) { + lines.push(line.value); + } istream.close(); return lines; @@ -255,42 +257,42 @@ function setup() let gTests = [ { desc: "Case and accent sensitive UTF-8", - run: function () runUtf8Test("locale_case_accent_sensitive") + run: () => runUtf8Test("locale_case_accent_sensitive") }, { desc: "Case sensitive, accent insensitive UTF-8", - run: function () runUtf8Test("locale_case_sensitive") + run: () => runUtf8Test("locale_case_sensitive") }, { desc: "Case insensitive, accent sensitive UTF-8", - run: function () runUtf8Test("locale_accent_sensitive") + run: () => runUtf8Test("locale_accent_sensitive") }, { desc: "Case and accent insensitive UTF-8", - run: function () runUtf8Test("locale") + run: () => runUtf8Test("locale") }, { desc: "Case and accent sensitive UTF-16", - run: function () runUtf16Test("locale_case_accent_sensitive") + run: () => runUtf16Test("locale_case_accent_sensitive") }, { desc: "Case sensitive, accent insensitive UTF-16", - run: function () runUtf16Test("locale_case_sensitive") + run: () => runUtf16Test("locale_case_sensitive") }, { desc: "Case insensitive, accent sensitive UTF-16", - run: function () runUtf16Test("locale_accent_sensitive") + run: () => runUtf16Test("locale_accent_sensitive") }, { desc: "Case and accent insensitive UTF-16", - run: function () runUtf16Test("locale") + run: () => runUtf16Test("locale") }, ]; diff --git a/storage/test/unit/test_sqlite_secure_delete.js b/storage/test/unit/test_sqlite_secure_delete.js index b797f03d21..86509f8b33 100644 --- a/storage/test/unit/test_sqlite_secure_delete.js +++ b/storage/test/unit/test_sqlite_secure_delete.js @@ -34,8 +34,7 @@ function getFileContents(aFile) //////////////////////////////////////////////////////////////////////////////// //// Tests -function test_delete_removes_data() -{ +add_test(function test_delete_removes_data() { const TEST_STRING = "SomeRandomStringToFind"; let file = getTestDB(); @@ -74,14 +73,7 @@ function test_delete_removes_data() do_check_eq(-1, contents.indexOf(TEST_STRING)); run_next_test(); -} - -//////////////////////////////////////////////////////////////////////////////// -//// Test Runner - -[ - test_delete_removes_data, - ].forEach(add_test); +}); function run_test() { diff --git a/storage/test/unit/test_statement_executeAsync.js b/storage/test/unit/test_statement_executeAsync.js index 861246459d..592e576ccc 100644 --- a/storage/test/unit/test_statement_executeAsync.js +++ b/storage/test/unit/test_statement_executeAsync.js @@ -53,15 +53,15 @@ function execAsync(aStmt, aOptions, aResults) if (aResults == null) { resultsExpected = 0; } - else if (typeof(aResults) == "number") { + else if (typeof aResults == "number") { resultsExpected = aResults; } - else if (typeof(aResults) == "function") { + else if (typeof aResults == "function") { resultsChecker = aResults; } else { // array resultsExpected = aResults.length; - resultsChecker = function(aResultNum, aTup, aCaller) { + resultsChecker = function (aResultNum, aTup, aCaller) { aResults[aResultNum](aTup, aCaller); }; } @@ -83,7 +83,7 @@ function execAsync(aStmt, aOptions, aResults) let completed = false; let listener = { - handleResult: function(aResultSet) + handleResult(aResultSet) { let row, resultsSeenThisCall = 0; while ((row = aResultSet.getNextRow()) != null) { @@ -96,13 +96,13 @@ function execAsync(aStmt, aOptions, aResults) if (!resultsSeenThisCall) do_throw("handleResult invoked with 0 result rows!"); }, - handleError: function(aError) + handleError(aError) { if (errorCodeSeen != false) do_throw("handleError called when we already had an error!"); errorCodeSeen = aError.result; }, - handleCompletion: function(aReason) + handleCompletion(aReason) { if (completed) // paranoia check do_throw("Received a second handleCompletion notification!", caller); @@ -226,8 +226,7 @@ function test_get_data() ); stmt.bindByIndex(0, INTEGER); execAsync(stmt, {}, [ - function(tuple) - { + function (tuple) { do_check_neq(null, tuple); // Check that it's what we expect @@ -254,7 +253,7 @@ function test_get_data() do_check_eq(BLOB.length, blobByName.length); var blobByIndex = tuple.getResultByIndex(3); do_check_eq(BLOB.length, blobByIndex.length); - for (var i = 0; i < BLOB.length; i++) { + for (let i = 0; i < BLOB.length; i++) { do_check_eq(BLOB[i], blobByName[i]); do_check_eq(BLOB[i], blobByIndex[i]); } @@ -262,7 +261,7 @@ function test_get_data() var blob = { value: null }; tuple.getBlob(3, count, blob); do_check_eq(BLOB.length, count.value); - for (var i = 0; i < BLOB.length; i++) + for (let i = 0; i < BLOB.length; i++) do_check_eq(BLOB[i], blob.value[i]); do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_BLOB, tuple.getTypeOfIndex(3)); @@ -283,7 +282,7 @@ function test_tuple_out_of_bounds() "SELECT string FROM test" ); execAsync(stmt, {}, [ - function(tuple) { + function (tuple) { do_check_neq(null, tuple); // Check all out of bounds - should throw @@ -367,19 +366,13 @@ function test_partial_listener_works() ); stmt.bindByIndex(0, 0); stmt.executeAsync({ - handleResult: function(aResultSet) - { - } + handleResult(aResultSet) {} }); stmt.executeAsync({ - handleError: function(aError) - { - } + handleError(aError) {} }); stmt.executeAsync({ - handleCompletion: function(aReason) - { - } + handleCompletion(aReason) {} }); stmt.finalize(); @@ -416,7 +409,7 @@ function test_double_cancellation() let pendingStatement = execAsync(stmt, {cancel: true}); // And cancel again - expect an exception expectError(Cr.NS_ERROR_UNEXPECTED, - function() pendingStatement.cancel()); + () => pendingStatement.cancel()); stmt.finalize(); run_next_test(); @@ -467,8 +460,9 @@ function test_finalized_statement_does_not_crash() // we are concerned about a crash here; an error is fine. try { stmt.executeAsync(); + } catch (ex) { + // Do nothing. } - catch (ex) {} // Run the next test. run_next_test(); @@ -628,10 +622,10 @@ function test_bind_out_of_bounds_sync_immediate() // Check variant binding. expectError(Cr.NS_ERROR_INVALID_ARG, - function() bp.bindByIndex(1, INTEGER)); + () => bp.bindByIndex(1, INTEGER)); // Check blob binding. expectError(Cr.NS_ERROR_INVALID_ARG, - function() bp.bindBlobByIndex(1, BLOB, BLOB.length)); + () => bp.bindBlobByIndex(1, BLOB, BLOB.length)); stmt.finalize(); run_next_test(); @@ -675,10 +669,10 @@ function test_bind_no_such_name_sync_immediate() // Check variant binding. expectError(Cr.NS_ERROR_INVALID_ARG, - function() bp.bindByName("doesnotexist", INTEGER)); + () => bp.bindByName("doesnotexist", INTEGER)); // Check blob binding. expectError(Cr.NS_ERROR_INVALID_ARG, - function() bp.bindBlobByName("doesnotexist", BLOB, BLOB.length)); + () => bp.bindBlobByName("doesnotexist", BLOB, BLOB.length)); stmt.finalize(); run_next_test(); @@ -751,7 +745,7 @@ function test_bind_params_already_locked() // We should get an error after we call addParams and try to bind again. expectError(Cr.NS_ERROR_UNEXPECTED, - function() bp.bindByName("int", INTEGER)); + () => bp.bindByName("int", INTEGER)); stmt.finalize(); run_next_test(); @@ -774,7 +768,7 @@ function test_bind_params_array_already_locked() // We should get an error after we have bound the array to the statement. expectError(Cr.NS_ERROR_UNEXPECTED, - function() array.addParams(bp2)); + () => array.addParams(bp2)); stmt.finalize(); run_next_test(); @@ -796,7 +790,7 @@ function test_no_binding_params_from_locked_array() // We should not be able to get a new BindingParams object after we have bound // to the statement. expectError(Cr.NS_ERROR_UNEXPECTED, - function() array.newBindingParams()); + () => array.newBindingParams()); stmt.finalize(); run_next_test(); @@ -816,7 +810,7 @@ function test_not_right_owning_array() // We should not be able to add bp to array2 since it was created from array1. expectError(Cr.NS_ERROR_UNEXPECTED, - function() array2.addParams(bp)); + () => array2.addParams(bp)); stmt.finalize(); run_next_test(); @@ -841,7 +835,7 @@ function test_not_right_owning_statement() // We should not be able to bind array1 since it was created from stmt1. expectError(Cr.NS_ERROR_UNEXPECTED, - function() stmt2.bindParameters(array1)); + () => stmt2.bindParameters(array1)); stmt1.finalize(); stmt2.finalize(); @@ -860,7 +854,7 @@ function test_bind_empty_array() // We should not be able to bind this array to the statement because it is // empty. expectError(Cr.NS_ERROR_UNEXPECTED, - function() stmt.bindParameters(paramsArray)); + () => stmt.bindParameters(paramsArray)); stmt.finalize(); run_next_test(); @@ -905,14 +899,13 @@ let testPass = TEST_PASS_SYNC; * @return a statement of the type under test per testPass. */ function makeTestStatement(aSQL) { - if (testPass == TEST_PASS_SYNC) + if (testPass == TEST_PASS_SYNC) { return getOpenedDatabase().createStatement(aSQL); - else - return getOpenedDatabase().createAsyncStatement(aSQL); + } + return getOpenedDatabase().createAsyncStatement(aSQL); } -var tests = -[ +var tests = [ test_illegal_sql_async_deferred, test_create_table, test_add_data, diff --git a/storage/test/unit/test_statement_wrapper_automatically.js b/storage/test/unit/test_statement_wrapper_automatically.js index fcaf53304d..58b27dd2d8 100644 --- a/storage/test/unit/test_statement_wrapper_automatically.js +++ b/storage/test/unit/test_statement_wrapper_automatically.js @@ -129,12 +129,9 @@ function insertAndCheckMultipleParams(aVal) */ function printValDesc(aVal) { - try - { + try { var toSource = aVal.toSource(); - } - catch (exc) - { + } catch (ex) { toSource = ""; } print("Testing value: toString=" + aVal + @@ -163,7 +160,7 @@ function run_test() print("Single parameter"); insertAndCheckSingleParam(val); print("Multiple parameters"); - insertAndCheckMultipleParams(val) + insertAndCheckMultipleParams(val); }); cleanup(); diff --git a/storage/test/unit/test_storage_aggregates.js b/storage/test/unit/test_storage_aggregates.js index 5269a163a3..400aba836b 100644 --- a/storage/test/unit/test_storage_aggregates.js +++ b/storage/test/unit/test_storage_aggregates.js @@ -11,7 +11,7 @@ function setup() getOpenedDatabase().createTable("function_tests", "id INTEGER PRIMARY KEY"); var stmt = createStatement("INSERT INTO function_tests (id) VALUES(?1)"); - for(var i = 0; i < testNums.length; ++i) { + for (let i = 0; i < testNums.length; ++i) { stmt.bindByIndex(0, testNums[i]); stmt.execute(); } @@ -23,17 +23,17 @@ var testSquareAndSumFunction = { calls: 0, _sas: 0, - reset: function() { + reset() { this.calls = 0; - this._sas = 0; + this._sas = 0; }, - onStep: function(val) { + onStep(val) { ++this.calls; this._sas += val.getInt32(0) * val.getInt32(0); }, - onFinal: function() { + onFinal() { var retval = this._sas; this._sas = 0; // Prepare for next group return retval; @@ -79,7 +79,9 @@ function test_aggregate_no_aliases() function test_aggregate_call() { var stmt = createStatement("SELECT test_sas_aggr(id) FROM function_tests"); - while(stmt.executeStep()); + while (stmt.executeStep()) { + // Do nothing. + } do_check_eq(testNums.length, testSquareAndSumFunction.calls); testSquareAndSumFunction.reset(); stmt.finalize(); @@ -88,7 +90,7 @@ function test_aggregate_call() function test_aggregate_result() { var sas = 0; - for(var i = 0; i < testNums.length; ++i) { + for (var i = 0; i < testNums.length; ++i) { sas += testNums[i] * testNums[i]; } var stmt = createStatement("SELECT test_sas_aggr(id) FROM function_tests"); diff --git a/storage/test/unit/test_storage_connection.js b/storage/test/unit/test_storage_connection.js index be1cacfb90..d12ecc9f6b 100644 --- a/storage/test/unit/test_storage_connection.js +++ b/storage/test/unit/test_storage_connection.js @@ -7,70 +7,7 @@ //////////////////////////////////////////////////////////////////////////////// //// Test Functions -function asyncClone(db, readOnly) { - let deferred = Promise.defer(); - db.asyncClone(readOnly, function(status, db2) { - if (Components.isSuccessCode(status)) { - deferred.resolve(db2); - } else { - deferred.reject(status); - } - }); - return deferred.promise; -} - -function asyncClose(db) { - let deferred = Promise.defer(); - db.asyncClose(function(status) { - if (Components.isSuccessCode(status)) { - deferred.resolve(); - } else { - deferred.reject(status); - } - }); - return deferred.promise; -} - -function openAsyncDatabase(file, options) { - let deferred = Promise.defer(); - let properties; - if (options) { - properties = Cc["@mozilla.org/hash-property-bag;1"]. - createInstance(Ci.nsIWritablePropertyBag); - for (let k in options) { - properties.setProperty(k, options[k]); - } - } - getService().openAsyncDatabase(file, properties, function(status, db) { - if (Components.isSuccessCode(status)) { - deferred.resolve(db.QueryInterface(Ci.mozIStorageAsyncConnection)); - } else { - deferred.reject(status); - } - }); - return deferred.promise; -} - -function executeAsync(statement, onResult) { - let deferred = Promise.defer(); - statement.executeAsync({ - handleError: function(error) { - deferred.reject(error); - }, - handleResult: function(result) { - if (onResult) { - onResult(result); - } - }, - handleCompletion: function(result) { - deferred.resolve(result); - } - }); - return deferred.promise; -} - -add_task(function test_connectionReady_open() -{ +add_task(function* test_connectionReady_open() { // there doesn't seem to be a way for the connection to not be ready (unless // we close it with mozIStorageConnection::Close(), but we don't for this). // It can only fail if GetPath fails on the database file, or if we run out @@ -80,8 +17,7 @@ add_task(function test_connectionReady_open() do_check_true(msc.connectionReady); }); -add_task(function test_connectionReady_closed() -{ +add_task(function* test_connectionReady_closed() { // This also tests mozIStorageConnection::Close() var msc = getOpenedDatabase(); @@ -90,26 +26,22 @@ add_task(function test_connectionReady_closed() gDBConn = null; // this is so later tests don't start to fail. }); -add_task(function test_databaseFile() -{ +add_task(function* test_databaseFile() { var msc = getOpenedDatabase(); do_check_true(getTestDB().equals(msc.databaseFile)); }); -add_task(function test_tableExists_not_created() -{ +add_task(function* test_tableExists_not_created() { var msc = getOpenedDatabase(); do_check_false(msc.tableExists("foo")); }); -add_task(function test_indexExists_not_created() -{ +add_task(function* test_indexExists_not_created() { var msc = getOpenedDatabase(); do_check_false(msc.indexExists("foo")); }); -add_task(function test_temp_tableExists_and_indexExists() -{ +add_task(function* test_temp_tableExists_and_indexExists() { var msc = getOpenedDatabase(); msc.executeSimpleSQL("CREATE TEMP TABLE test_temp(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)"); do_check_true(msc.tableExists("test_temp")); @@ -121,22 +53,19 @@ add_task(function test_temp_tableExists_and_indexExists() msc.executeSimpleSQL("DROP TABLE test_temp"); }); -add_task(function test_createTable_not_created() -{ +add_task(function* test_createTable_not_created() { var msc = getOpenedDatabase(); msc.createTable("test", "id INTEGER PRIMARY KEY, name TEXT"); do_check_true(msc.tableExists("test")); }); -add_task(function test_indexExists_created() -{ +add_task(function* test_indexExists_created() { var msc = getOpenedDatabase(); msc.executeSimpleSQL("CREATE INDEX name_ind ON test (name)"); do_check_true(msc.indexExists("name_ind")); }); -add_task(function test_createTable_already_created() -{ +add_task(function* test_createTable_already_created() { var msc = getOpenedDatabase(); do_check_true(msc.tableExists("test")); try { @@ -147,8 +76,7 @@ add_task(function test_createTable_already_created() } }); -add_task(function test_attach_createTable_tableExists_indexExists() -{ +add_task(function* test_attach_createTable_tableExists_indexExists() { var msc = getOpenedDatabase(); var file = do_get_file("storage_attach.sqlite", true); var msc2 = getDatabase(file); @@ -160,7 +88,10 @@ add_task(function test_attach_createTable_tableExists_indexExists() try { msc.createTable("sample.test", "id INTEGER PRIMARY KEY, name TEXT"); do_throw("We shouldn't get here!"); - } catch (e if e.result == Components.results.NS_ERROR_FAILURE) { + } catch (e) { + if (e.result != Components.results.NS_ERROR_FAILURE) { + throw e; + } // we expect to fail because this table should exist already. } @@ -170,24 +101,25 @@ add_task(function test_attach_createTable_tableExists_indexExists() msc.executeSimpleSQL("DETACH DATABASE sample"); msc2.close(); - try { file.remove(false); } catch(e) { } + try { + file.remove(false); + } catch (e) { + // Do nothing. + } }); -add_task(function test_lastInsertRowID() -{ +add_task(function* test_lastInsertRowID() { var msc = getOpenedDatabase(); msc.executeSimpleSQL("INSERT INTO test (name) VALUES ('foo')"); do_check_eq(1, msc.lastInsertRowID); }); -add_task(function test_transactionInProgress_no() -{ +add_task(function* test_transactionInProgress_no() { var msc = getOpenedDatabase(); do_check_false(msc.transactionInProgress); }); -add_task(function test_transactionInProgress_yes() -{ +add_task(function* test_transactionInProgress_yes() { var msc = getOpenedDatabase(); msc.beginTransaction(); do_check_true(msc.transactionInProgress); @@ -200,8 +132,7 @@ add_task(function test_transactionInProgress_yes() do_check_false(msc.transactionInProgress); }); -add_task(function test_commitTransaction_no_transaction() -{ +add_task(function* test_commitTransaction_no_transaction() { var msc = getOpenedDatabase(); do_check_false(msc.transactionInProgress); try { @@ -212,8 +143,7 @@ add_task(function test_commitTransaction_no_transaction() } }); -add_task(function test_rollbackTransaction_no_transaction() -{ +add_task(function* test_rollbackTransaction_no_transaction() { var msc = getOpenedDatabase(); do_check_false(msc.transactionInProgress); try { @@ -224,47 +154,47 @@ add_task(function test_rollbackTransaction_no_transaction() } }); -add_task(function test_get_schemaVersion_not_set() -{ +add_task(function* test_get_schemaVersion_not_set() { do_check_eq(0, getOpenedDatabase().schemaVersion); }); -add_task(function test_set_schemaVersion() -{ +add_task(function* test_set_schemaVersion() { var msc = getOpenedDatabase(); const version = 1; msc.schemaVersion = version; do_check_eq(version, msc.schemaVersion); }); -add_task(function test_set_schemaVersion_same() -{ +add_task(function* test_set_schemaVersion_same() { var msc = getOpenedDatabase(); const version = 1; msc.schemaVersion = version; // should still work ok do_check_eq(version, msc.schemaVersion); }); -add_task(function test_set_schemaVersion_negative() -{ +add_task(function* test_set_schemaVersion_negative() { var msc = getOpenedDatabase(); const version = -1; msc.schemaVersion = version; do_check_eq(version, msc.schemaVersion); }); -add_task(function test_createTable(){ +add_task(function* test_createTable() { var temp = getTestDB().parent; temp.append("test_db_table"); try { var con = getService().openDatabase(temp); - con.createTable("a",""); + con.createTable("a", ""); } catch (e) { - if (temp.exists()) try { - temp.remove(false); - } catch (e2) {} - do_check_true(e.result==Cr.NS_ERROR_NOT_INITIALIZED || - e.result==Cr.NS_ERROR_FAILURE); + if (temp.exists()) { + try { + temp.remove(false); + } catch (e2) { + // Do nothing. + } + } + do_check_true(e.result == Cr.NS_ERROR_NOT_INITIALIZED || + e.result == Cr.NS_ERROR_FAILURE); } finally { if (con) { con.close(); @@ -272,8 +202,7 @@ add_task(function test_createTable(){ } }); -add_task(function test_defaultSynchronousAtNormal() -{ +add_task(function* test_defaultSynchronousAtNormal() { var msc = getOpenedDatabase(); var stmt = createStatement("PRAGMA synchronous;"); try { @@ -287,14 +216,12 @@ add_task(function test_defaultSynchronousAtNormal() }); // must be ran before executeAsync tests -add_task(function test_close_does_not_spin_event_loop() -{ +add_task(function* test_close_does_not_spin_event_loop() { // We want to make sure that the event loop on the calling thread does not // spin when close is called. let event = { ran: false, - run: function() - { + run() { this.ran = true; }, }; @@ -315,8 +242,7 @@ add_task(function test_close_does_not_spin_event_loop() gDBConn = null; }); -add_task(function test_asyncClose_succeeds_with_finalized_async_statement() -{ +add_task(function* test_asyncClose_succeeds_with_finalized_async_statement() { // XXX this test isn't perfect since we can't totally control when events will // run. If this paticular function fails randomly, it means we have a // real bug. @@ -333,7 +259,7 @@ add_task(function test_asyncClose_succeeds_with_finalized_async_statement() gDBConn = null; }); -add_task(function test_close_then_release_statement() { +add_task(function* test_close_then_release_statement() { // Testing the behavior in presence of a bad client that finalizes // statements after the database has been closed (typically by // letting the gc finalize the statement). @@ -346,7 +272,7 @@ add_task(function test_close_then_release_statement() { gDBConn = null; }); -add_task(function test_asyncClose_then_release_statement() { +add_task(function* test_asyncClose_then_release_statement() { // Testing the behavior in presence of a bad client that finalizes // statements after the database has been async closed (typically by // letting the gc finalize the statement). @@ -359,8 +285,7 @@ add_task(function test_asyncClose_then_release_statement() { gDBConn = null; }); -add_task(function test_close_fails_with_async_statement_ran() -{ +add_task(function* test_close_fails_with_async_statement_ran() { let deferred = Promise.defer(); let stmt = createStatement("SELECT * FROM test"); stmt.executeAsync(); @@ -376,7 +301,7 @@ add_task(function test_close_fails_with_async_statement_ran() } finally { // Clean up after ourselves. - db.asyncClose(function() { + db.asyncClose(function () { // Reset gDBConn so that later tests will get a new connection object. gDBConn = null; deferred.resolve(); @@ -385,8 +310,7 @@ add_task(function test_close_fails_with_async_statement_ran() yield deferred.promise; }); -add_task(function test_clone_optional_param() -{ +add_task(function* test_clone_optional_param() { let db1 = getService().openUnsharedDatabase(getTestDB()); let db2 = db1.clone(); do_check_true(db2.connectionReady); @@ -409,7 +333,7 @@ add_task(function test_clone_optional_param() db2.close(); }); -function standardAsyncTest(promisedDB, name, shouldInit = false) { +function* standardAsyncTest(promisedDB, name, shouldInit = false) { do_print("Performing standard async test " + name); let adb = yield promisedDB; @@ -434,13 +358,12 @@ function standardAsyncTest(promisedDB, name, shouldInit = false) { do_print("Extracting data"); stmt = adb.createAsyncStatement("SELECT * FROM test"); let found = false; - yield executeAsync(stmt, function(result) { + yield executeAsync(stmt, function (results) { do_print("Data has been extracted"); - - for (let row = result.getNextRow(); row != null; row = result.getNextRow()) { + for (let row = results.getNextRow(); row != null; row = results.getNextRow()) { if (row.getResultByName("name") == name) { found = true; - break; + break; } } }); @@ -451,7 +374,7 @@ function standardAsyncTest(promisedDB, name, shouldInit = false) { do_print("Standard async test " + name + " complete"); } -add_task(function test_open_async() { +add_task(function* test_open_async() { yield standardAsyncTest(openAsyncDatabase(getTestDB(), null), "default"); yield standardAsyncTest(openAsyncDatabase(getTestDB()), "no optional arg"); yield standardAsyncTest(openAsyncDatabase(getTestDB(), @@ -492,7 +415,7 @@ add_task(function test_open_async() { }); -add_task(function test_async_open_with_shared_cache() { +add_task(function* test_async_open_with_shared_cache() { do_print("Testing that opening with a shared cache doesn't break stuff"); let adb = yield openAsyncDatabase(getTestDB(), {shared: true}); @@ -505,13 +428,12 @@ add_task(function test_async_open_with_shared_cache() { do_print("Extracting data"); stmt = adb.createAsyncStatement("SELECT * FROM test"); let found = false; - yield executeAsync(stmt, function(result) { + yield executeAsync(stmt, function (results) { do_print("Data has been extracted"); - - for (let row = result.getNextRow(); row != null; row = result.getNextRow()) { + for (let row = results.getNextRow(); row != null; row = results.getNextRow()) { if (row.getResultByName("name") == "clockworker") { found = true; - break; + break; } } }); @@ -520,8 +442,7 @@ add_task(function test_async_open_with_shared_cache() { yield asyncClose(adb); }); -add_task(function test_clone_trivial_async() -{ +add_task(function* test_clone_trivial_async() { let db1 = getService().openDatabase(getTestDB()); do_print("Opened adb1"); do_check_true(db1 instanceof Ci.mozIStorageAsyncConnection); @@ -533,8 +454,7 @@ add_task(function test_clone_trivial_async() yield asyncClose(adb2); }); -add_task(function test_clone_no_optional_param_async() -{ +add_task(function* test_clone_no_optional_param_async() { "use strict"; do_print("Testing async cloning"); let adb1 = yield openAsyncDatabase(getTestDB(), null); @@ -560,13 +480,12 @@ add_task(function test_clone_no_optional_param_async() do_print("Extracting data from clone db"); stmt = adb2.createAsyncStatement("SELECT * FROM test"); let found = false; - yield executeAsync(stmt, function(result) { + yield executeAsync(stmt, function (results) { do_print("Data has been extracted"); - - for (let row = result.getNextRow(); row != null; row = result.getNextRow()) { + for (let row = results.getNextRow(); row != null; row = results.getNextRow()) { if (row.getResultByName("name") == "yoric") { found = true; - break; + break; } } }); @@ -580,8 +499,7 @@ add_task(function test_clone_no_optional_param_async() do_print("Second db closed"); }); -add_task(function test_clone_readonly() -{ +add_task(function* test_clone_readonly() { let db1 = getService().openUnsharedDatabase(getTestDB()); let db2 = db1.clone(true); do_check_true(db2.connectionReady); @@ -589,7 +507,7 @@ add_task(function test_clone_readonly() // A write statement should fail here. let stmt = db2.createStatement("INSERT INTO test (name) VALUES (:name)"); stmt.params.name = "reed"; - expectError(Cr.NS_ERROR_FILE_READ_ONLY, function() stmt.execute()); + expectError(Cr.NS_ERROR_FILE_READ_ONLY, () => stmt.execute()); stmt.finalize(); // And a read statement should succeed. @@ -601,8 +519,7 @@ add_task(function test_clone_readonly() db2.close(); }); -add_task(function test_clone_shared_readonly() -{ +add_task(function* test_clone_shared_readonly() { let db1 = getService().openDatabase(getTestDB()); let db2 = db1.clone(true); do_check_true(db2.connectionReady); @@ -614,7 +531,7 @@ add_task(function test_clone_shared_readonly() // for. Our IDL comments will have to be updated when this starts to // work again. stmt.execute(); - // expectError(Components.results.NS_ERROR_FILE_READ_ONLY, function() stmt.execute()); + // expectError(Components.results.NS_ERROR_FILE_READ_ONLY, () => stmt.execute()); stmt.finalize(); // And a read statement should succeed. @@ -626,28 +543,25 @@ add_task(function test_clone_shared_readonly() db2.close(); }); -add_task(function test_close_clone_fails() -{ +add_task(function* test_close_clone_fails() { let calls = [ "openDatabase", "openUnsharedDatabase", ]; - calls.forEach(function(methodName) { + calls.forEach(function (methodName) { let db = getService()[methodName](getTestDB()); db.close(); - expectError(Cr.NS_ERROR_NOT_INITIALIZED, function() db.clone()); + expectError(Cr.NS_ERROR_NOT_INITIALIZED, () => db.clone()); }); }); -add_task(function test_memory_clone_fails() -{ +add_task(function* test_memory_clone_fails() { let db = getService().openSpecialDatabase("memory"); db.close(); - expectError(Cr.NS_ERROR_NOT_INITIALIZED, function() db.clone()); + expectError(Cr.NS_ERROR_NOT_INITIALIZED, () => db.clone()); }); -add_task(function test_clone_copies_functions() -{ +add_task(function* test_clone_copies_functions() { const FUNC_NAME = "test_func"; let calls = [ "openDatabase", @@ -657,15 +571,15 @@ add_task(function test_clone_copies_functions() "createFunction", "createAggregateFunction", ]; - calls.forEach(function(methodName) { - [true, false].forEach(function(readOnly) { - functionMethods.forEach(function(functionMethod) { + calls.forEach(function (methodName) { + [true, false].forEach(function (readOnly) { + functionMethods.forEach(function (functionMethod) { let db1 = getService()[methodName](getTestDB()); // Create a function for db1. db1[functionMethod](FUNC_NAME, 1, { - onFunctionCall: function() 0, - onStep: function() 0, - onFinal: function() 0, + onFunctionCall: () => 0, + onStep: () => 0, + onFinal: () => 0, }); // Clone it, and make sure the function exists still. @@ -680,20 +594,19 @@ add_task(function test_clone_copies_functions() }); }); -add_task(function test_clone_copies_overridden_functions() -{ +add_task(function* test_clone_copies_overridden_functions() { const FUNC_NAME = "lower"; function test_func() { this.called = false; } test_func.prototype = { - onFunctionCall: function() { + onFunctionCall() { this.called = true; }, - onStep: function() { + onStep() { this.called = true; }, - onFinal: function() 0, + onFinal: () => 0, }; let calls = [ @@ -704,9 +617,9 @@ add_task(function test_clone_copies_overridden_functions() "createFunction", "createAggregateFunction", ]; - calls.forEach(function(methodName) { - [true, false].forEach(function(readOnly) { - functionMethods.forEach(function(functionMethod) { + calls.forEach(function (methodName) { + [true, false].forEach(function (readOnly) { + functionMethods.forEach(function (functionMethod) { let db1 = getService()[methodName](getTestDB()); // Create a function for db1. let func = new test_func(); @@ -726,8 +639,7 @@ add_task(function test_clone_copies_overridden_functions() }); }); -add_task(function test_clone_copies_pragmas() -{ +add_task(function* test_clone_copies_pragmas() { const PRAGMAS = [ { name: "cache_size", value: 500, copied: true }, { name: "temp_store", value: 2, copied: true }, @@ -769,8 +681,7 @@ add_task(function test_clone_copies_pragmas() db2.close(); }); -add_task(function test_readonly_clone_copies_pragmas() -{ +add_task(function* test_readonly_clone_copies_pragmas() { const PRAGMAS = [ { name: "cache_size", value: 500, copied: true }, { name: "temp_store", value: 2, copied: true }, @@ -812,8 +723,7 @@ add_task(function test_readonly_clone_copies_pragmas() db2.close(); }); -add_task(function test_getInterface() -{ +add_task(function* test_getInterface() { let db = getOpenedDatabase(); let target = db.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIEventTarget); @@ -826,7 +736,6 @@ add_task(function test_getInterface() }); -function run_test() -{ +function run_test() { run_next_test(); } diff --git a/storage/test/unit/test_storage_function.js b/storage/test/unit/test_storage_function.js index 0eaa2ca31e..6532709ec7 100644 --- a/storage/test/unit/test_storage_function.js +++ b/storage/test/unit/test_storage_function.js @@ -11,7 +11,7 @@ function setup() getOpenedDatabase().createTable("function_tests", "id INTEGER PRIMARY KEY"); var stmt = createStatement("INSERT INTO function_tests (id) VALUES(?1)"); - for(var i = 0; i < testNums.length; ++i) { + for (let i = 0; i < testNums.length; ++i) { stmt.bindByIndex(0, testNums[i]); stmt.execute(); } @@ -22,7 +22,7 @@ function setup() var testSquareFunction = { calls: 0, - onFunctionCall: function(val) { + onFunctionCall(val) { ++this.calls; return val.getInt32(0) * val.getInt32(0); } @@ -62,7 +62,9 @@ function test_function_aliases() function test_function_call() { var stmt = createStatement("SELECT test_square(id) FROM function_tests"); - while(stmt.executeStep()); + while (stmt.executeStep()) { + // Do nothing. + } do_check_eq(testNums.length, testSquareFunction.calls); testSquareFunction.calls = 0; stmt.finalize(); @@ -72,7 +74,7 @@ function test_function_result() { var stmt = createStatement("SELECT test_square(42) FROM function_tests"); stmt.executeStep(); - do_check_eq(42*42, stmt.getInt32(0)); + do_check_eq(42 * 42, stmt.getInt32(0)); testSquareFunction.calls = 0; stmt.finalize(); } diff --git a/storage/test/unit/test_storage_progresshandler.js b/storage/test/unit/test_storage_progresshandler.js index 495162e71e..c06a57e830 100644 --- a/storage/test/unit/test_storage_progresshandler.js +++ b/storage/test/unit/test_storage_progresshandler.js @@ -11,9 +11,9 @@ function setup() msc.beginTransaction(); var stmt = createStatement("INSERT INTO handler_tests (id, num) VALUES(?1, ?2)"); - for(var i = 0; i < 100; ++i) { + for (let i = 0; i < 100; ++i) { stmt.bindByIndex(0, i); - stmt.bindByIndex(1, Math.floor(Math.random()*1000)); + stmt.bindByIndex(1, Math.floor(Math.random() * 1000)); stmt.execute(); } stmt.reset(); @@ -25,7 +25,7 @@ var testProgressHandler = { calls: 0, abort: false, - onProgress: function(comm) { + onProgress(comm) { ++this.calls; return this.abort; } @@ -59,7 +59,9 @@ function test_handler_call() // Some long-executing request var stmt = createStatement( "SELECT SUM(t1.num * t2.num) FROM handler_tests AS t1, handler_tests AS t2"); - while(stmt.executeStep()); + while (stmt.executeStep()) { + // Do nothing. + } do_check_true(testProgressHandler.calls > 0); stmt.finalize(); } @@ -75,7 +77,9 @@ function test_handler_abort() const SQLITE_INTERRUPT = 9; try { - while(stmt.executeStep()); + while (stmt.executeStep()) { + // Do nothing. + } do_throw("We shouldn't get here!"); } catch (e) { do_check_eq(Cr.NS_ERROR_ABORT, e.result); diff --git a/storage/test/unit/test_storage_service.js b/storage/test/unit/test_storage_service.js index 6d4b210d00..9cf46620ed 100644 --- a/storage/test/unit/test_storage_service.js +++ b/storage/test/unit/test_storage_service.js @@ -97,7 +97,7 @@ function test_backup_new_filename() { var backup = getService().backupDatabaseFile(getTestDB(), BACKUP_FILE_NAME); do_check_eq(BACKUP_FILE_NAME, backup.leafName); - + backup.remove(false); } @@ -107,7 +107,7 @@ function test_backup_new_folder() parentDir.append("test_storage_temp"); if (parentDir.exists()) parentDir.remove(true); - parentDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755); + parentDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); do_check_true(parentDir.exists()); var backup = getService().backupDatabaseFile(getTestDB(), BACKUP_FILE_NAME, @@ -133,9 +133,10 @@ var tests = [ function run_test() { - for (var i = 0; i < tests.length; i++) + for (var i = 0; i < tests.length; i++) { tests[i](); - + } + cleanup(); } diff --git a/storage/test/unit/test_storage_service_unshared.js b/storage/test/unit/test_storage_service_unshared.js index 92c11a66bb..70efb2a438 100644 --- a/storage/test/unit/test_storage_service_unshared.js +++ b/storage/test/unit/test_storage_service_unshared.js @@ -29,7 +29,7 @@ function run_test() { for (var i = 0; i < tests.length; i++) tests[i](); - + cleanup(); } diff --git a/storage/test/unit/test_storage_statement.js b/storage/test/unit/test_storage_statement.js index 221a8d0664..7e2841f9a3 100644 --- a/storage/test/unit/test_storage_statement.js +++ b/storage/test/unit/test_storage_statement.js @@ -86,7 +86,7 @@ function test_getColumnIndex_different_case() do_check_eq(Cr.NS_ERROR_INVALID_ARG, e.result); } try { - do_check_eq(1, stmt.getColumnIndex("Id")); + do_check_eq(1, stmt.getColumnIndex("Id")); do_throw("should not get here"); } catch (e) { do_check_eq(Cr.NS_ERROR_INVALID_ARG, e.result); @@ -190,9 +190,10 @@ function run_test() { setup(); - for (var i = 0; i < tests.length; i++) + for (var i = 0; i < tests.length; i++) { tests[i](); - + } + cleanup(); } diff --git a/storage/test/unit/test_storage_value_array.js b/storage/test/unit/test_storage_value_array.js index f632dfe667..dbeb7bb56a 100644 --- a/storage/test/unit/test_storage_value_array.js +++ b/storage/test/unit/test_storage_value_array.js @@ -8,14 +8,14 @@ function setup() { getOpenedDatabase().createTable("test", "id INTEGER PRIMARY KEY, name TEXT," + "number REAL, nuller NULL, blobber BLOB"); - + var stmt = createStatement("INSERT INTO test (name, number, blobber) " + "VALUES (?1, ?2, ?3)"); stmt.bindByIndex(0, "foo"); stmt.bindByIndex(1, 2.34); stmt.bindBlobByIndex(2, [], 0); stmt.execute(); - + stmt.bindByIndex(0, ""); stmt.bindByIndex(1, 1.23); stmt.bindBlobByIndex(2, [1, 2], 2); @@ -30,7 +30,7 @@ function test_getIsNull_for_null() var stmt = createStatement("SELECT nuller, blobber FROM test WHERE id = ?1"); stmt.bindByIndex(0, 1); do_check_true(stmt.executeStep()); - + do_check_true(stmt.getIsNull(0)); // null field do_check_true(stmt.getIsNull(1)); // data is null if size is 0 stmt.reset(); @@ -202,9 +202,10 @@ function run_test() { setup(); - for (var i = 0; i < tests.length; i++) + for (var i = 0; i < tests.length; i++) { tests[i](); - + } + cleanup(); } diff --git a/storage/test/unit/test_telemetry_vfs.js b/storage/test/unit/test_telemetry_vfs.js index b8c7ff8790..0822fe3e7e 100644 --- a/storage/test/unit/test_telemetry_vfs.js +++ b/storage/test/unit/test_telemetry_vfs.js @@ -5,8 +5,8 @@ // Make sure that there are telemetry entries created by sqlite io function run_sql(d, sql) { - var stmt = d.createStatement(sql) - stmt.execute() + var stmt = d.createStatement(sql); + stmt.execute(); stmt.finalize(); } @@ -24,7 +24,7 @@ function run_test() const file = new_file("telemetry.sqlite"); var d = getDatabase(file); run_sql(d, "CREATE TABLE bloat(data varchar)"); - run_sql(d, "DROP TABLE bloat") - do_check_true(read_hgram.snapshot().sum > old_sum) + run_sql(d, "DROP TABLE bloat"); + do_check_true(read_hgram.snapshot().sum > old_sum); } diff --git a/storage/test/unit/test_unicode.js b/storage/test/unit/test_unicode.js index a918122bcf..8aa8f01296 100644 --- a/storage/test/unit/test_unicode.js +++ b/storage/test/unit/test_unicode.js @@ -95,9 +95,10 @@ function run_test() { setup(); - for (var i = 0; i < tests.length; i++) + for (var i = 0; i < tests.length; i++) { tests[i](); - + } + cleanup(); } diff --git a/storage/test/unit/test_vacuum.js b/storage/test/unit/test_vacuum.js index 96b84f9210..e284f78c72 100644 --- a/storage/test/unit/test_vacuum.js +++ b/storage/test/unit/test_vacuum.js @@ -87,6 +87,239 @@ function run_test() run_next_test(); } +const TESTS = [ + + function test_common_vacuum() + { + print("\n*** Test that a VACUUM correctly happens and all notifications are fired."); + // Wait for VACUUM begin. + let beginVacuumReceived = false; + Services.obs.addObserver(function onVacuum(aSubject, aTopic, aData) { + Services.obs.removeObserver(onVacuum, aTopic); + beginVacuumReceived = true; + }, "test-begin-vacuum", false); + + // Wait for heavy IO notifications. + let heavyIOTaskBeginReceived = false; + let heavyIOTaskEndReceived = false; + Services.obs.addObserver(function onVacuum(aSubject, aTopic, aData) { + if (heavyIOTaskBeginReceived && heavyIOTaskEndReceived) { + Services.obs.removeObserver(onVacuum, aTopic); + } + + if (aData == "vacuum-begin") { + heavyIOTaskBeginReceived = true; + } + else if (aData == "vacuum-end") { + heavyIOTaskEndReceived = true; + } + }, "heavy-io-task", false); + + // Wait for VACUUM end. + Services.obs.addObserver(function onVacuum(aSubject, aTopic, aData) { + Services.obs.removeObserver(onVacuum, aTopic); + print("Check we received onBeginVacuum"); + do_check_true(beginVacuumReceived); + print("Check we received heavy-io-task notifications"); + do_check_true(heavyIOTaskBeginReceived); + do_check_true(heavyIOTaskEndReceived); + print("Received onEndVacuum"); + run_next_test(); + }, "test-end-vacuum", false); + + synthesize_idle_daily(); + }, + + function test_skipped_if_recent_vacuum() + { + print("\n*** Test that a VACUUM is skipped if it was run recently."); + Services.prefs.setIntPref("storage.vacuum.last.testVacuum.sqlite", + parseInt(Date.now() / 1000)); + + // Wait for VACUUM begin. + let vacuumObserver = { + gotNotification: false, + observe: function VO_observe(aSubject, aTopic, aData) { + this.gotNotification = true; + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) + }; + Services.obs.addObserver(vacuumObserver, "test-begin-vacuum", false); + + // Check after a couple seconds that no VACUUM has been run. + do_timeout(2000, function () { + print("Check VACUUM did not run."); + do_check_false(vacuumObserver.gotNotification); + Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum"); + run_next_test(); + }); + + synthesize_idle_daily(); + }, + + function test_page_size_change() + { + print("\n*** Test that a VACUUM changes page_size"); + + // We did setup the database with a small page size, the previous vacuum + // should have updated it. + print("Check that page size was updated."); + let conn = getDatabase(new_db_file("testVacuum")); + let stmt = conn.createStatement("PRAGMA page_size"); + try { + while (stmt.executeStep()) { + do_check_eq(stmt.row.page_size, conn.defaultPageSize); + } + } + finally { + stmt.finalize(); + } + + run_next_test(); + }, + + function test_skipped_optout_vacuum() + { + print("\n*** Test that a VACUUM is skipped if the participant wants to opt-out."); + Services.obs.notifyObservers(null, "test-options", "opt-out"); + + // Wait for VACUUM begin. + let vacuumObserver = { + gotNotification: false, + observe: function VO_observe(aSubject, aTopic, aData) { + this.gotNotification = true; + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) + }; + Services.obs.addObserver(vacuumObserver, "test-begin-vacuum", false); + + // Check after a couple seconds that no VACUUM has been run. + do_timeout(2000, function () { + print("Check VACUUM did not run."); + do_check_false(vacuumObserver.gotNotification); + Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum"); + run_next_test(); + }); + + synthesize_idle_daily(); + }, + + /* Changing page size on WAL is not supported till Bug 634374 is properly fixed. + function test_page_size_change_with_wal() + { + print("\n*** Test that a VACUUM changes page_size with WAL mode"); + Services.obs.notifyObservers(null, "test-options", "wal"); + + // Set a small page size. + let conn = getDatabase(new_db_file("testVacuum2")); + conn.executeSimpleSQL("PRAGMA page_size = 1024"); + let stmt = conn.createStatement("PRAGMA page_size"); + try { + while (stmt.executeStep()) { + do_check_eq(stmt.row.page_size, 1024); + } + } + finally { + stmt.finalize(); + } + + // Use WAL journal mode. + conn.executeSimpleSQL("PRAGMA journal_mode = WAL"); + stmt = conn.createStatement("PRAGMA journal_mode"); + try { + while (stmt.executeStep()) { + do_check_eq(stmt.row.journal_mode, "wal"); + } + } + finally { + stmt.finalize(); + } + + // Wait for VACUUM end. + let vacuumObserver = { + observe: function VO_observe(aSubject, aTopic, aData) { + Services.obs.removeObserver(this, aTopic); + print("Check page size has been updated."); + let stmt = conn.createStatement("PRAGMA page_size"); + try { + while (stmt.executeStep()) { + do_check_eq(stmt.row.page_size, Ci.mozIStorageConnection.DEFAULT_PAGE_SIZE); + } + } + finally { + stmt.finalize(); + } + + print("Check journal mode has been restored."); + stmt = conn.createStatement("PRAGMA journal_mode"); + try { + while (stmt.executeStep()) { + do_check_eq(stmt.row.journal_mode, "wal"); + } + } + finally { + stmt.finalize(); + } + + run_next_test(); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) + } + Services.obs.addObserver(vacuumObserver, "test-end-vacuum", false); + + synthesize_idle_daily(); + }, + */ + + function test_memory_database_crash() + { + print("\n*** Test that we don't crash trying to vacuum a memory database"); + Services.obs.notifyObservers(null, "test-options", "memory"); + + // Wait for VACUUM begin. + let vacuumObserver = { + gotNotification: false, + observe: function VO_observe(aSubject, aTopic, aData) { + this.gotNotification = true; + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) + }; + Services.obs.addObserver(vacuumObserver, "test-begin-vacuum", false); + + // Check after a couple seconds that no VACUUM has been run. + do_timeout(2000, function () { + print("Check VACUUM did not run."); + do_check_false(vacuumObserver.gotNotification); + Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum"); + run_next_test(); + }); + + synthesize_idle_daily(); + }, + + /* Changing page size on WAL is not supported till Bug 634374 is properly fixed. + function test_wal_restore_fail() + { + print("\n*** Test that a failing WAL restoration notifies failure"); + Services.obs.notifyObservers(null, "test-options", "wal-fail"); + + // Wait for VACUUM end. + let vacuumObserver = { + observe: function VO_observe(aSubject, aTopic, aData) { + Services.obs.removeObserver(vacuumObserver, "test-end-vacuum"); + print("Check WAL restoration failed."); + do_check_false(aData); + run_next_test(); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) + } + Services.obs.addObserver(vacuumObserver, "test-end-vacuum", false); + + synthesize_idle_daily(); + }, + */ +]; + function run_next_test() { if (TESTS.length == 0) { @@ -100,236 +333,3 @@ function run_next_test() do_execute_soon(TESTS.shift()); } } - -const TESTS = [ - -function test_common_vacuum() -{ - print("\n*** Test that a VACUUM correctly happens and all notifications are fired."); - // Wait for VACUUM begin. - let beginVacuumReceived = false; - Services.obs.addObserver(function onVacuum(aSubject, aTopic, aData) { - Services.obs.removeObserver(onVacuum, aTopic); - beginVacuumReceived = true; - }, "test-begin-vacuum", false); - - // Wait for heavy IO notifications. - let heavyIOTaskBeginReceived = false; - let heavyIOTaskEndReceived = false; - Services.obs.addObserver(function onVacuum(aSubject, aTopic, aData) { - if (heavyIOTaskBeginReceived && heavyIOTaskEndReceived) { - Services.obs.removeObserver(onVacuum, aTopic); - } - - if (aData == "vacuum-begin") { - heavyIOTaskBeginReceived = true; - } - else if (aData == "vacuum-end") { - heavyIOTaskEndReceived = true; - } - }, "heavy-io-task", false); - - // Wait for VACUUM end. - Services.obs.addObserver(function onVacuum(aSubject, aTopic, aData) { - Services.obs.removeObserver(onVacuum, aTopic); - print("Check we received onBeginVacuum"); - do_check_true(beginVacuumReceived); - print("Check we received heavy-io-task notifications"); - do_check_true(heavyIOTaskBeginReceived); - do_check_true(heavyIOTaskEndReceived); - print("Received onEndVacuum"); - run_next_test(); - }, "test-end-vacuum", false); - - synthesize_idle_daily(); -}, - -function test_skipped_if_recent_vacuum() -{ - print("\n*** Test that a VACUUM is skipped if it was run recently."); - Services.prefs.setIntPref("storage.vacuum.last.testVacuum.sqlite", - parseInt(Date.now() / 1000)); - - // Wait for VACUUM begin. - let vacuumObserver = { - gotNotification: false, - observe: function VO_observe(aSubject, aTopic, aData) { - this.gotNotification = true; - }, - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) - } - Services.obs.addObserver(vacuumObserver, "test-begin-vacuum", false); - - // Check after a couple seconds that no VACUUM has been run. - do_timeout(2000, function () { - print("Check VACUUM did not run."); - do_check_false(vacuumObserver.gotNotification); - Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum"); - run_next_test(); - }); - - synthesize_idle_daily(); -}, - -function test_page_size_change() -{ - print("\n*** Test that a VACUUM changes page_size"); - - // We did setup the database with a small page size, the previous vacuum - // should have updated it. - print("Check that page size was updated."); - let conn = getDatabase(new_db_file("testVacuum")); - let stmt = conn.createStatement("PRAGMA page_size"); - try { - while (stmt.executeStep()) { - do_check_eq(stmt.row.page_size, conn.defaultPageSize); - } - } - finally { - stmt.finalize(); - } - - run_next_test(); -}, - -function test_skipped_optout_vacuum() -{ - print("\n*** Test that a VACUUM is skipped if the participant wants to opt-out."); - Services.obs.notifyObservers(null, "test-options", "opt-out"); - - // Wait for VACUUM begin. - let vacuumObserver = { - gotNotification: false, - observe: function VO_observe(aSubject, aTopic, aData) { - this.gotNotification = true; - }, - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) - } - Services.obs.addObserver(vacuumObserver, "test-begin-vacuum", false); - - // Check after a couple seconds that no VACUUM has been run. - do_timeout(2000, function () { - print("Check VACUUM did not run."); - do_check_false(vacuumObserver.gotNotification); - Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum"); - run_next_test(); - }); - - synthesize_idle_daily(); -}, - -/* Changing page size on WAL is not supported till Bug 634374 is properly fixed. -function test_page_size_change_with_wal() -{ - print("\n*** Test that a VACUUM changes page_size with WAL mode"); - Services.obs.notifyObservers(null, "test-options", "wal"); - - // Set a small page size. - let conn = getDatabase(new_db_file("testVacuum2")); - conn.executeSimpleSQL("PRAGMA page_size = 1024"); - let stmt = conn.createStatement("PRAGMA page_size"); - try { - while (stmt.executeStep()) { - do_check_eq(stmt.row.page_size, 1024); - } - } - finally { - stmt.finalize(); - } - - // Use WAL journal mode. - conn.executeSimpleSQL("PRAGMA journal_mode = WAL"); - stmt = conn.createStatement("PRAGMA journal_mode"); - try { - while (stmt.executeStep()) { - do_check_eq(stmt.row.journal_mode, "wal"); - } - } - finally { - stmt.finalize(); - } - - // Wait for VACUUM end. - let vacuumObserver = { - observe: function VO_observe(aSubject, aTopic, aData) { - Services.obs.removeObserver(this, aTopic); - print("Check page size has been updated."); - let stmt = conn.createStatement("PRAGMA page_size"); - try { - while (stmt.executeStep()) { - do_check_eq(stmt.row.page_size, Ci.mozIStorageConnection.DEFAULT_PAGE_SIZE); - } - } - finally { - stmt.finalize(); - } - - print("Check journal mode has been restored."); - stmt = conn.createStatement("PRAGMA journal_mode"); - try { - while (stmt.executeStep()) { - do_check_eq(stmt.row.journal_mode, "wal"); - } - } - finally { - stmt.finalize(); - } - - run_next_test(); - }, - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) - } - Services.obs.addObserver(vacuumObserver, "test-end-vacuum", false); - - synthesize_idle_daily(); -}, -*/ - -function test_memory_database_crash() -{ - print("\n*** Test that we don't crash trying to vacuum a memory database"); - Services.obs.notifyObservers(null, "test-options", "memory"); - - // Wait for VACUUM begin. - let vacuumObserver = { - gotNotification: false, - observe: function VO_observe(aSubject, aTopic, aData) { - this.gotNotification = true; - }, - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) - } - Services.obs.addObserver(vacuumObserver, "test-begin-vacuum", false); - - // Check after a couple seconds that no VACUUM has been run. - do_timeout(2000, function () { - print("Check VACUUM did not run."); - do_check_false(vacuumObserver.gotNotification); - Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum"); - run_next_test(); - }); - - synthesize_idle_daily(); -}, - -/* Changing page size on WAL is not supported till Bug 634374 is properly fixed. -function test_wal_restore_fail() -{ - print("\n*** Test that a failing WAL restoration notifies failure"); - Services.obs.notifyObservers(null, "test-options", "wal-fail"); - - // Wait for VACUUM end. - let vacuumObserver = { - observe: function VO_observe(aSubject, aTopic, aData) { - Services.obs.removeObserver(vacuumObserver, "test-end-vacuum"); - print("Check WAL restoration failed."); - do_check_false(aData); - run_next_test(); - }, - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) - } - Services.obs.addObserver(vacuumObserver, "test-end-vacuum", false); - - synthesize_idle_daily(); -}, -*/ -]; diff --git a/storage/test/unit/vacuumParticipant.js b/storage/test/unit/vacuumParticipant.js index 3e0be1e3de..d6af4152a7 100644 --- a/storage/test/unit/vacuumParticipant.js +++ b/storage/test/unit/vacuumParticipant.js @@ -43,8 +43,12 @@ vacuumParticipant.prototype = classID: Components.ID("{52aa0b22-b82f-4e38-992a-c3675a3355d2}"), contractID: "@unit.test.com/test-vacuum-participant;1", - get expectedDatabasePageSize() this._dbConn.defaultPageSize, - get databaseConnection() this._dbConn, + get expectedDatabasePageSize() { + return this._dbConn.defaultPageSize; + }, + get databaseConnection() { + return this._dbConn; + }, _grant: true, onBeginVacuum: function TVP_onBeginVacuum() @@ -72,15 +76,17 @@ vacuumParticipant.prototype = else if (aData == "wal") { try { this._dbConn.close(); + } catch (e) { + // Do nothing. } - catch(e) {} this._dbConn = getDatabase(new_db_file("testVacuum2")); } else if (aData == "wal-fail") { try { this._dbConn.close(); + } catch (e) { + // Do nothing. } - catch(e) {} this._dbConn = getDatabase(new_db_file("testVacuum3")); // Use WAL journal mode. this._dbConn.executeSimpleSQL("PRAGMA journal_mode = WAL"); @@ -92,8 +98,9 @@ vacuumParticipant.prototype = else if (aData == "memory") { try { this._dbConn.asyncClose(); + } catch (e) { + // Do nothing. } - catch(e) {} this._dbConn = Cc["@mozilla.org/storage/service;1"]. getService(Ci.mozIStorageService). openSpecialDatabase("memory"); @@ -102,14 +109,15 @@ vacuumParticipant.prototype = Services.obs.removeObserver(this, "test-options"); try { this._dbConn.asyncClose(); + } catch (e) { + // Do nothing. } - catch(e) {} } }, QueryInterface: XPCOMUtils.generateQI([ - Ci.mozIStorageVacuumParticipant - , Ci.nsIObserver + Ci.mozIStorageVacuumParticipant, + Ci.nsIObserver, ]) }; diff --git a/storage/test/unit/xpcshell.ini b/storage/test/unit/xpcshell.ini index 12986c5dca..2a0a72a581 100644 --- a/storage/test/unit/xpcshell.ini +++ b/storage/test/unit/xpcshell.ini @@ -17,6 +17,7 @@ support-files = [test_chunk_growth.js] # Bug 676981: test fails consistently on Android fail-if = os == "android" +[test_connection_asyncClose.js] [test_connection_executeAsync.js] [test_connection_executeSimpleSQLAsync.js] [test_js_helpers.js] diff --git a/testing/mozharness/configs/unittests/linux_unittest.py b/testing/mozharness/configs/unittests/linux_unittest.py index 9db5a83fc5..e0f0dc4dae 100644 --- a/testing/mozharness/configs/unittests/linux_unittest.py +++ b/testing/mozharness/configs/unittests/linux_unittest.py @@ -202,7 +202,6 @@ config = { 'MOZ_DISABLE_CONTEXT_SHARING_GLX': '1'}, 'options': ['--setpref=browser.tabs.remote=true', '--setpref=browser.tabs.remote.autostart=true', - '--setpref=layers.offmainthreadcomposition.testing.enabled=true', '--setpref=layers.async-pan-zoom.enabled=true', 'tests/reftest/tests/layout/reftests/reftest-sanity/reftest.list']}, "reftest-no-accel": ['--setpref=layers.acceleration.force-enabled=disabled', @@ -211,7 +210,6 @@ config = { 'MOZ_DISABLE_CONTEXT_SHARING_GLX': '1'}, 'options': ['--setpref=browser.tabs.remote=true', '--setpref=browser.tabs.remote.autostart=true', - '--setpref=layers.offmainthreadcomposition.testing.enabled=true', '--setpref=layers.async-pan-zoom.enabled=true', 'tests/reftest/tests/testing/crashtest/crashtests.list']}, }, diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index f66d2acb91..24f1451679 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -88,14 +88,41 @@ "kind": "boolean", "description": "blocklist.xml has been loaded synchronously *** No longer needed (bug 1156565). Delete histogram and accumulation code! ***" }, - "CHECKERBOARDED_CSSPIXELS_MS": { + "CHECKERBOARD_DURATION": { "alert_emails": ["kgupta@mozilla.com"], - "bug_numbers": [1221694], + "bug_numbers": [1238040], "expires_in_version": "55", "kind": "exponential", - "high": "3840 * 2160 * 16", + "high": "100000", "n_buckets": 50, - "description": "Magnitude of checkerboarding in CSSPixel-milliseconds per scrollable frame per composite" + "description": "Duration of a checkerboard event in milliseconds" + }, + "CHECKERBOARD_PEAK": { + "alert_emails": ["kgupta@mozilla.com"], + "bug_numbers": [1238040], + "expires_in_version": "55", + "kind": "exponential", + "high": "3840 * 2160 * 8", + "n_buckets": 50, + "description": "Peak number of CSS pixels checkerboarded during a checkerboard event (the high value is the size of a 4k display with max APZ zooming)" + }, + "CHECKERBOARD_POTENTIAL_DURATION": { + "alert_emails": ["kgupta@mozilla.com"], + "bug_numbers": [1238040], + "expires_in_version": "55", + "kind": "exponential", + "high": "1000000", + "n_buckets": 50, + "description": "Duration of a chunk of time (in ms) that could reasonably have had checkerboarding" + }, + "CHECKERBOARD_SEVERITY": { + "alert_emails": ["kgupta@mozilla.com"], + "bug_numbers": [1238040], + "expires_in_version": "55", + "kind": "exponential", + "high": "1073741824", + "n_buckets": 50, + "description": "Opaque measure of the severity of a checkerboard event" }, "COMPOSITE_TIME" : { "expires_in_version": "never", @@ -305,6 +332,14 @@ "n_buckets": 50, "description": "Time spent running JS GC sweep phase (ms)" }, + "GC_COMPACT_MS": { + "alert_emails": ["dev-telemetry-gc-alerts@mozilla.org"], + "expires_in_version": "never", + "kind": "exponential", + "high": "10000", + "n_buckets": 50, + "description": "Time spent running JS GC compact phase (ms)" + }, "GC_MARK_ROOTS_MS": { "expires_in_version": "never", "kind": "linear", @@ -7959,16 +7994,14 @@ "releaseChannelCollection": "opt-in", "description": "Number of times the sharing feature has been enabled and disabled" }, - "E10S_AUTOSTART": { + "E10S_STATUS": { + "alert_emails": ["firefox-dev@mozilla.org"], "expires_in_version": "never", - "kind": "boolean", - "description": "Whether a session is set to autostart e10s windows" - }, - "E10S_AUTOSTART_STATUS": { - "expires_in_version": "40", "kind": "enumerated", - "n_values": 6, - "description": "Why e10s is enabled or disabled (0=ENABLED_BY_USER, 1=ENABLED_BY_DEFAULT, 2=DISABLED_BY_USER, 3=DISABLED_IN_SAFE_MODE, 4=DISABLED_FOR_ACCESSIBILITY, 5=DISABLED_FOR_MAC_GFX)" + "n_values": 12, + "releaseChannelCollection": "opt-out", + "bug_numbers": [1241294], + "description": "Why e10s is enabled or disabled (0=ENABLED_BY_USER, 1=ENABLED_BY_DEFAULT, 2=DISABLED_BY_USER, 3=DISABLED_IN_SAFE_MODE, 4=DISABLED_FOR_ACCESSIBILITY, 5=DISABLED_FOR_MAC_GFX, 6=DISABLED_FOR_BIDI, 7=DISABLED_FOR_ADDONS)" }, "E10S_WINDOW": { "expires_in_version": "never", @@ -9327,11 +9360,13 @@ "kind": "boolean", "description": "Attempt to determine prevalence of scroll-linked effects on the web." }, - "YOUTUBE_EMBED_SEEN": { - "alert_emails": ["cpeterson@mozilla.com"], - "expires_in_version": "48", - "kind": "flag", - "description": "Flag activated whenever a youtube flash embed is seen during a session." + "SCROLL_INPUT_METHODS": { + "alert_emails": ["botond@mozilla.com"], + "bug_numbers": [1238137], + "expires_in_version": "50", + "kind": "enumerated", + "n_values": 64, + "description": "Count of scroll actions triggered by different input methods. See gfx/layers/apz/util/ScrollInputMethods.h for a list of possible values and their meanings." }, "WEB_NOTIFICATION_CLICKED": { "alert_emails": ["firefox-dev@mozilla.org"], diff --git a/toolkit/content/aboutSupport.js b/toolkit/content/aboutSupport.js index dd6c0fe743..2a16fae2da 100644 --- a/toolkit/content/aboutSupport.js +++ b/toolkit/content/aboutSupport.js @@ -48,8 +48,23 @@ var snapshotFormatters = { if (data.updateChannel) $("updatechannel-box").textContent = data.updateChannel; - $("multiprocess-box").textContent = stringBundle().formatStringFromName("multiProcessStatus", - [data.numRemoteWindows, data.numTotalWindows, data.remoteAutoStart], 3); + let statusStrName = ".unknown"; + + // Whitelist of known values with string descriptions: + switch (data.autoStartStatus) { + case 0: + case 1: + case 2: + case 4: + case 5: + case 6: + case 7: + statusStrName = "." + data.autoStartStatus; + } + + let statusText = stringBundle().GetStringFromName("multiProcessStatus" + statusStrName); + $("multiprocess-box").textContent = stringBundle().formatStringFromName("multiProcessWindows", + [data.numRemoteWindows, data.numTotalWindows, statusText], 3); $("safemode-box").textContent = data.safeMode; }, diff --git a/toolkit/locales/en-US/chrome/global/aboutSupport.properties b/toolkit/locales/en-US/chrome/global/aboutSupport.properties index 143ad12da5..fdd76bc2b0 100644 --- a/toolkit/locales/en-US/chrome/global/aboutSupport.properties +++ b/toolkit/locales/en-US/chrome/global/aboutSupport.properties @@ -96,9 +96,18 @@ canSandboxContent = Content Process Sandboxing canSandboxMedia = Media Plugin Sandboxing # LOCALIZATION NOTE %1$S and %2$S will be replaced with the number of remote and the total number -# of windows, respectively, while %3$S will indicate whether windows are remote by default ('true' -# or 'false') -multiProcessStatus = %1$S/%2$S (default: %3$S) +# of windows, respectively, while %3$S will be replaced with one of the status strings below, +# which contains a description of the multi-process preference and status. +# Note: multiProcessStatus.3 doesn't exist because status=3 was deprecated. +multiProcessWindows = %1$S/%2$S (%3$S) +multiProcessStatus.0 = Enabled by user +multiProcessStatus.1 = Enabled by default +multiProcessStatus.2 = Disabled +multiProcessStatus.4 = Disabled by accessibility tools +multiProcessStatus.5 = Disabled by lack of graphics hardware acceleration +multiProcessStatus.6 = Disabled by unsupported text input +multiProcessStatus.7 = Disabled by add-ons +multiProcessStatus.unknown = Unknown status asyncPanZoom = Asynchronous Pan/Zoom apzNone = none diff --git a/toolkit/modules/Troubleshoot.jsm b/toolkit/modules/Troubleshoot.jsm index 96bd90bcaf..2c01dc0a1e 100644 --- a/toolkit/modules/Troubleshoot.jsm +++ b/toolkit/modules/Troubleshoot.jsm @@ -233,6 +233,16 @@ let dataProviders = { data.remoteAutoStart = Services.appinfo.browserTabsRemoteAutostart; + try { + let e10sStatus = Cc["@mozilla.org/supports-PRUint64;1"] + .createInstance(Ci.nsISupportsPRUint64); + let appinfo = Services.appinfo.QueryInterface(Ci.nsIObserver); + appinfo.observe(e10sStatus, "getE10SBlocked", ""); + data.autoStartStatus = e10sStatus.data; + } catch (e) { + data.autoStartStatus = -1; + } + done(data); }, diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index 495bfb0b71..72ec035272 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -100,6 +100,8 @@ #ifdef XP_WIN #include "nsIWinAppHelper.h" #include +#include +#include #include "cairo/cairo-features.h" #include "mozilla/WindowsVersion.h" @@ -143,6 +145,7 @@ #include "mozilla/LateWriteChecks.h" #include +#include #ifdef XP_UNIX #include @@ -186,6 +189,14 @@ #include "jprof.h" #endif +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#include "nsICrashReporter.h" +#define NS_CRASHREPORTER_CONTRACTID "@mozilla.org/toolkit/crash-reporter;1" +#include "nsIPrefService.h" +#include "nsIMemoryInfoDumper.h" +#endif + #include "base/command_line.h" #ifdef MOZ_ENABLE_TESTS #include "GTestRunner.h" @@ -579,8 +590,7 @@ ProcessDDE(nsINativeAppSupport* aNative, bool aWait) /** * Determines if there is support for showing the profile manager * - * @return currently true in all environments - * XXX: This was false for Metro. Possibly remove this logic. + * @return true in all environments */ static bool CanShowProfileManager() @@ -619,9 +629,9 @@ static void SetUpSandboxEnvironment() { // A low integrity temp only currently makes sense for Vista and later, e10s - // and sandbox pref level 1. + // and sandbox pref level >= 1. if (!IsVistaOrLater() || !BrowserTabsRemoteAutostart() || - Preferences::GetInt("security.sandbox.content.level") != 1) { + Preferences::GetInt("security.sandbox.content.level") < 1) { return; } @@ -976,17 +986,17 @@ nsXULAppInfo::GetProcessID(uint32_t* aResult) } static bool gBrowserTabsRemoteAutostart = false; -static nsString gBrowserTabsRemoteDisabledReason; +static uint64_t gBrowserTabsRemoteStatus = 0; static bool gBrowserTabsRemoteAutostartInitialized = false; NS_IMETHODIMP nsXULAppInfo::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) { if (!nsCRT::strcmp(aTopic, "getE10SBlocked")) { - nsCOMPtr ret = do_QueryInterface(aSubject); + nsCOMPtr ret = do_QueryInterface(aSubject); if (!ret) return NS_ERROR_FAILURE; - ret->SetData(gBrowserTabsRemoteDisabledReason); + ret->SetData(gBrowserTabsRemoteStatus); return NS_OK; } @@ -2824,6 +2834,32 @@ static void RestoreStateForAppInitiatedRestart() } } +#ifdef MOZ_CRASHREPORTER +// When we first initialize the crash reporter we don't have a profile, +// so we set the minidump path to $TEMP. Once we have a profile, +// we set it to $PROFILE/minidumps, creating the directory +// if needed. +static void MakeOrSetMinidumpPath(nsIFile* profD) +{ + nsCOMPtr dumpD; + profD->Clone(getter_AddRefs(dumpD)); + + if(dumpD) { + bool fileExists; + //XXX: do some more error checking here + dumpD->Append(NS_LITERAL_STRING("minidumps")); + dumpD->Exists(&fileExists); + if(!fileExists) { + dumpD->Create(nsIFile::DIRECTORY_TYPE, 0700); + } + + nsAutoString pathStr; + if(NS_SUCCEEDED(dumpD->GetPath(pathStr))) + CrashReporter::SetMinidumpPath(pathStr); + } +} +#endif + const nsXREAppData* gAppData = nullptr; #ifdef MOZ_WIDGET_GTK @@ -2847,18 +2883,24 @@ static void MOZ_gdk_display_close(GdkDisplay *display) #if CLEANUP_MEMORY // XXX wallpaper for bug 417163: don't close the Display if we're using the // Qt theme because we crash (in Qt code) when using jemalloc. - bool theme_is_qt = false; + bool skip_display_close = false; GtkSettings* settings = gtk_settings_get_for_screen(gdk_display_get_default_screen(display)); gchar *theme_name; g_object_get(settings, "gtk-theme-name", &theme_name, nullptr); if (theme_name) { - theme_is_qt = strcmp(theme_name, "Qt") == 0; - if (theme_is_qt) + skip_display_close = strcmp(theme_name, "Qt") == 0; + if (skip_display_close) NS_WARNING("wallpaper bug 417163 for Qt theme"); g_free(theme_name); } +#if (MOZ_WIDGET_GTK == 3) + // A workaround for https://bugzilla.gnome.org/show_bug.cgi?id=703257 + if (gtk_check_version(3,9,8) != NULL) + skip_display_close = true; +#endif + // Get a (new) Pango context that holds a reference to the fontmap that // GTK has been using. gdk_pango_context_get() must be called while GTK // has a default display. @@ -2871,7 +2913,7 @@ static void MOZ_gdk_display_close(GdkDisplay *display) // like Pango and cairo. But if cairo shutdown is buggy, we should // shut down cairo first otherwise it may crash because of dangling // references to Display objects (see bug 469831). - if (!theme_is_qt) + if (!skip_display_close) gdk_display_close(display); } @@ -2908,7 +2950,7 @@ static void MOZ_gdk_display_close(GdkDisplay *display) FcFini(); if (buggyCairoShutdown) { - if (!theme_is_qt) + if (!skip_display_close) gdk_display_close(display); } #else // not CLEANUP_MEMORY @@ -2917,7 +2959,42 @@ static void MOZ_gdk_display_close(GdkDisplay *display) (void) display; #endif } -#endif // MOZ_WIDGET_GTK2 + +static const char* detectDisplay(void) +{ + bool tryX11 = false; + bool tryWayland = false; + bool tryBroadway = false; + + // Honor user backend selection + const char *backend = PR_GetEnv("GDK_BACKEND"); + if (!backend || strstr(backend, "*")) { + // Try all backends + tryX11 = true; + tryWayland = true; + tryBroadway = true; + } else if (backend) { + if (strstr(backend, "x11")) + tryX11 = true; + if (strstr(backend, "wayland")) + tryWayland = true; + if (strstr(backend, "broadway")) + tryBroadway = true; + } + + const char *display_name; + if (tryX11 && (display_name = PR_GetEnv("DISPLAY"))) { + return display_name; + } else if (tryWayland && (display_name = PR_GetEnv("WAYLAND_DISPLAY"))) { + return display_name; + } else if (tryBroadway && (display_name = PR_GetEnv("BROADWAY_DISPLAY"))) { + return display_name; + } + + PR_fprintf(PR_STDERR, "Error: GDK_BACKEND does not match available displays\n"); + return nullptr; +} +#endif // MOZ_WIDGET_GTK /** * NSPR will search for the "nspr_use_zone_allocator" symbol throughout @@ -3665,12 +3742,12 @@ XREMain::XRE_mainStartup(bool* aExitFlag) #if defined(MOZ_WIDGET_GTK) // display_name is owned by gdk. const char *display_name = gdk_get_display_arg_name(); + bool saveDisplayArg = false; if (display_name) { - SaveWordToEnv("DISPLAY", nsDependentCString(display_name)); + saveDisplayArg = true; } else { - display_name = PR_GetEnv("DISPLAY"); + display_name = detectDisplay(); if (!display_name) { - PR_fprintf(PR_STDERR, "Error: no display specified\n"); return 1; } } @@ -3681,16 +3758,19 @@ XREMain::XRE_mainStartup(bool* aExitFlag) XInitThreads(); #endif #if defined(MOZ_WIDGET_GTK) - { - mGdkDisplay = gdk_display_open(display_name); - if (!mGdkDisplay) { - PR_fprintf(PR_STDERR, "Error: cannot open display: %s\n", display_name); - return 1; + mGdkDisplay = gdk_display_open(display_name); + if (!mGdkDisplay) { + PR_fprintf(PR_STDERR, "Error: cannot open display: %s\n", display_name); + return 1; + } + gdk_display_manager_set_default_display (gdk_display_manager_get(), + mGdkDisplay); + if (GDK_IS_X11_DISPLAY(mGdkDisplay)) { + if (saveDisplayArg) { + SaveWordToEnv("DISPLAY", nsDependentCString(display_name)); } - gdk_display_manager_set_default_display (gdk_display_manager_get(), - mGdkDisplay); - if (!GDK_IS_X11_DISPLAY(mGdkDisplay)) - mDisableRemote = true; + } else { + mDisableRemote = true; } #endif #ifdef MOZ_ENABLE_XREMOTE @@ -3866,6 +3946,13 @@ XREMain::XRE_mainStartup(bool* aExitFlag) mozilla::Telemetry::SetProfileDir(mProfD); +#ifdef MOZ_CRASHREPORTER + if (mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER) + MakeOrSetMinidumpPath(mProfD); + + CrashReporter::SetProfileDirectory(mProfD); +#endif + nsAutoCString version; BuildVersion(version); @@ -3992,6 +4079,35 @@ XREMain::XRE_mainRun() rv = mScopedXPCOM->SetWindowCreator(mNativeApp); NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); +#ifdef MOZ_CRASHREPORTER + // tell the crash reporter to also send the release channel + nsCOMPtr prefs = do_GetService("@mozilla.org/preferences-service;1", &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr defaultPrefBranch; + rv = prefs->GetDefaultBranch(nullptr, getter_AddRefs(defaultPrefBranch)); + + if (NS_SUCCEEDED(rv)) { + nsXPIDLCString sval; + rv = defaultPrefBranch->GetCharPref("app.update.channel", getter_Copies(sval)); + if (NS_SUCCEEDED(rv)) { + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ReleaseChannel"), + sval); + } + } + } + // Needs to be set after xpcom initialization. + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("FramePoisonBase"), + nsPrintfCString("%.16llx", uint64_t(gMozillaPoisonBase))); + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("FramePoisonSize"), + nsPrintfCString("%lu", uint32_t(gMozillaPoisonSize))); + +#ifdef XP_WIN + PR_CreateThread(PR_USER_THREAD, AnnotateSystemManufacturer_ThreadStart, 0, + PR_PRIORITY_LOW, PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, 0); +#endif + +#endif + if (mStartOffline) { nsCOMPtr io (do_GetService("@mozilla.org/network/io-service;1")); NS_ENSURE_TRUE(io, NS_ERROR_FAILURE); @@ -4017,6 +4133,14 @@ XREMain::XRE_mainRun() file->AppendNative(NS_LITERAL_CSTRING("override.ini")); nsINIParser parser; nsresult rv = parser.Init(file); + // if override.ini doesn't exist, also check for distribution.ini + if (NS_FAILED(rv)) { + bool persistent; + mDirProvider.GetFile(XRE_APP_DISTRIBUTION_DIR, &persistent, + getter_AddRefs(file)); + file->AppendNative(NS_LITERAL_CSTRING("distribution.ini")); + rv = parser.Init(file); + } if (NS_SUCCEEDED(rv)) { nsAutoCString buf; rv = parser.GetString("XRE", "EnableProfileMigrator", buf); @@ -4096,6 +4220,19 @@ XREMain::XRE_mainRun() mDirProvider.DoStartup(); + OverrideDefaultLocaleIfNeeded(); + +#ifdef MOZ_CRASHREPORTER + nsCString userAgentLocale; + // Try a localized string first. This pref is always a localized string in + // Fennec, and might be elsewhere, too. + if (NS_SUCCEEDED(Preferences::GetLocalizedCString("general.useragent.locale", &userAgentLocale))) { + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("useragent_locale"), userAgentLocale); + } else if (NS_SUCCEEDED(Preferences::GetCString("general.useragent.locale", &userAgentLocale))) { + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("useragent_locale"), userAgentLocale); + } +#endif + appStartup->GetShuttingDown(&mShuttingDown); nsCOMPtr cmdLine; @@ -4522,19 +4659,7 @@ XRE_IsContentProcess() return XRE_GetProcessType() == GeckoProcessType_Content; } -static void -LogE10sBlockedReason(const char *reason) { - gBrowserTabsRemoteDisabledReason.Assign(NS_ConvertASCIItoUTF16(reason)); - - nsAutoString msg(NS_LITERAL_STRING("==================\nE10s has been blocked from running because:\n")); - msg.Append(gBrowserTabsRemoteDisabledReason); - msg.AppendLiteral("\n==================\n"); - nsCOMPtr console(do_GetService("@mozilla.org/consoleservice;1")); - if (console) { - console->LogStringMessage(msg.get()); - } -} - +// If you add anything to this enum, please update about:support to reflect it enum { kE10sEnabledByUser = 0, kE10sEnabledByDefault = 1, @@ -4542,6 +4667,8 @@ enum { // kE10sDisabledInSafeMode = 3, was removed in bug 1172491. kE10sDisabledForAccessibility = 4, kE10sDisabledForMacGfx = 5, + kE10sDisabledForBidi = 6, + kE10sDisabledForAddons = 7, }; #ifdef XP_WIN @@ -4597,6 +4724,31 @@ mozilla::BrowserTabsRemoteAutostart() } #endif // XP_WIN + /** + * Avoids enabling e10s for certain locales that require bidi selection, + * which currently doesn't work well with e10s. + */ + bool disabledForBidi = false; + + nsAutoCString locale; + nsCOMPtr registry = + mozilla::services::GetXULChromeRegistryService(); + if (registry) { + registry->GetSelectedLocale(NS_LITERAL_CSTRING("global"), locale); + } + + int32_t index = locale.FindChar('-'); + if (index >= 0) { + locale.Truncate(index); + } + + if (locale.EqualsLiteral("ar") || + locale.EqualsLiteral("fa") || + locale.EqualsLiteral("he") || + locale.EqualsLiteral("ur")) { + disabledForBidi = true; + } + bool optInPref = Preferences::GetBool("browser.tabs.remote.autostart", false); bool trialPref = Preferences::GetBool("browser.tabs.remote.autostart.2", false); bool prefEnabled = optInPref || trialPref; @@ -4608,20 +4760,22 @@ mozilla::BrowserTabsRemoteAutostart() } else { status = kE10sDisabledByUser; } -#ifdef E10S_TESTING_ONLY - bool e10sAllowed = true; -#else - // When running tests with 'layers.offmainthreadcomposition.testing.enabled', e10s must be - // allowed because these tests must be allowed to run remotely. - // We are also allowing e10s to be enabled on Beta (which doesn't have E10S_TESTING_ONLY defined. - bool e10sAllowed = Preferences::GetDefaultCString("app.update.channel").EqualsLiteral("beta") || - gfxPrefs::GetSingleton().LayersOffMainThreadCompositionTestingEnabled(); + bool addonsCanDisable = Preferences::GetBool("extensions.e10sBlocksEnabling", false); + bool disabledByAddons = Preferences::GetBool("extensions.e10sBlockedByAddons", false); + +#ifdef MOZ_CRASHREPORTER + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AddonsShouldHaveBlockedE10s"), + disabledByAddons ? NS_LITERAL_CSTRING("1") + : NS_LITERAL_CSTRING("0")); #endif - if (e10sAllowed && prefEnabled) { + if (prefEnabled) { if (disabledForA11y) { status = kE10sDisabledForAccessibility; - LogE10sBlockedReason("An accessibility tool is or was active. See bug 1198459."); + } else if (disabledForBidi) { + status = kE10sDisabledForBidi; + } else if (addonsCanDisable && disabledByAddons) { + status = kE10sDisabledForAddons; } else { gBrowserTabsRemoteAutostart = true; } @@ -4659,9 +4813,7 @@ mozilla::BrowserTabsRemoteAutostart() if (accelDisabled) { gBrowserTabsRemoteAutostart = false; - status = kE10sDisabledForMacGfx; - LogE10sBlockedReason("Hardware acceleration is disabled"); } } #endif // defined(XP_MACOSX) @@ -4673,8 +4825,9 @@ mozilla::BrowserTabsRemoteAutostart() status = kE10sEnabledByUser; } - mozilla::Telemetry::Accumulate(mozilla::Telemetry::E10S_AUTOSTART, gBrowserTabsRemoteAutostart); - mozilla::Telemetry::Accumulate(mozilla::Telemetry::E10S_AUTOSTART_STATUS, status); + gBrowserTabsRemoteStatus = status; + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::E10S_STATUS, status); if (Preferences::GetBool("browser.enabledE10SFromPrompt", false)) { mozilla::Telemetry::Accumulate(mozilla::Telemetry::E10S_STILL_ACCEPTED_FROM_PROMPT, gBrowserTabsRemoteAutostart); @@ -4738,3 +4891,15 @@ SetupErrorHandling(const char* progname) // Unbuffer stdout, needed for tinderbox tests. setbuf(stdout, 0); } + +void +OverrideDefaultLocaleIfNeeded() { + // Read pref to decide whether to override default locale with US English. + if (mozilla::Preferences::GetBool("javascript.use_us_english_locale", false)) { + // Set the application-wide C-locale. Needed to resist fingerprinting + // of Date.toLocaleFormat(). We use the locale to "C.UTF-8" if possible, + // to avoid interfering with non-ASCII keyboard input on some Linux desktops. + // Otherwise fall back to the "C" locale, which is available on all platforms. + setlocale(LC_ALL, "C.UTF-8") || setlocale(LC_ALL, "C"); + } +} diff --git a/toolkit/xre/nsAppRunner.h b/toolkit/xre/nsAppRunner.h index 9b2cb135b3..c7048e0c87 100644 --- a/toolkit/xre/nsAppRunner.h +++ b/toolkit/xre/nsAppRunner.h @@ -93,6 +93,9 @@ NS_LockProfilePath(nsIFile* aPath, nsIFile* aTempPath, void WriteConsoleLog(); +void +OverrideDefaultLocaleIfNeeded(); + #ifdef XP_WIN void UseParentConsole(); diff --git a/toolkit/xre/nsEmbedFunctions.cpp b/toolkit/xre/nsEmbedFunctions.cpp index 99688a9ab8..9ffee1d686 100644 --- a/toolkit/xre/nsEmbedFunctions.cpp +++ b/toolkit/xre/nsEmbedFunctions.cpp @@ -618,6 +618,8 @@ XRE_InitChildProcess(int aArgc, mozilla::sandboxing::InitLoggingIfRequired(); #endif + OverrideDefaultLocaleIfNeeded(); + // Run the UI event loop on the main thread. uiMessageLoop.MessageLoop::Run(); diff --git a/uriloader/base/nsDocLoader.cpp b/uriloader/base/nsDocLoader.cpp index 6f8af253a7..46e2f0fe37 100644 --- a/uriloader/base/nsDocLoader.cpp +++ b/uriloader/base/nsDocLoader.cpp @@ -29,7 +29,7 @@ #include "nsITransport.h" #include "nsISocketTransport.h" - +#include "nsIDocShell.h" #include "nsIDOMDocument.h" #include "nsIDocument.h" #include "nsPresContext.h" @@ -602,7 +602,14 @@ nsDocLoader::OnStopRequest(nsIRequest *aRequest, // load. This will handle removing the request from our hashtable as needed. // if (mIsLoadingDocument) { - DocLoaderIsEmpty(true); + nsCOMPtr ds = do_QueryInterface(static_cast(this)); + bool doNotFlushLayout = false; + if (ds) { + // Don't do unexpected layout flushes while we're in process of restoring + // a document from the bfcache. + ds->GetRestoringDocument(&doNotFlushLayout); + } + DocLoaderIsEmpty(!doNotFlushLayout); } return NS_OK; diff --git a/widget/GfxInfoBase.cpp b/widget/GfxInfoBase.cpp index d596244722..5e49ba62a1 100644 --- a/widget/GfxInfoBase.cpp +++ b/widget/GfxInfoBase.cpp @@ -1066,7 +1066,7 @@ NS_IMETHODIMP GfxInfoBase::GetFailures(uint32_t* failureCount, // and the strings in it should be small as well (the error messages in the // code.) The second copy happens with the Clone() calls. Technically, // we don't need the mutex lock after the StringVectorCopy() call. - std::vector > loggedStrings = logForwarder->StringsVectorCopy(); + LoggingRecord loggedStrings = logForwarder->LoggingRecordCopy(); *failureCount = loggedStrings.size(); if (*failureCount != 0) { @@ -1084,11 +1084,11 @@ NS_IMETHODIMP GfxInfoBase::GetFailures(uint32_t* failureCount, } /* copy over the failure messages into the array we just allocated */ - std::vector >::const_iterator it; + LoggingRecord::const_iterator it; uint32_t i=0; for(it = loggedStrings.begin() ; it != loggedStrings.end(); ++it, i++) { - (*failures)[i] = (char*)nsMemory::Clone((*it).second.c_str(), (*it).second.size() + 1); - if (indices) (*indices)[i] = (*it).first; + (*failures)[i] = (char*)nsMemory::Clone(Get<1>(*it).c_str(), Get<1>(*it).size() + 1); + if (indices) (*indices)[i] = Get<0>(*it); if (!(*failures)[i]) { /* I'm too afraid to use an inline function... */ diff --git a/widget/PuppetWidget.cpp b/widget/PuppetWidget.cpp index c1fd7aee3c..62f3fdae90 100644 --- a/widget/PuppetWidget.cpp +++ b/widget/PuppetWidget.cpp @@ -12,7 +12,7 @@ #include "mozilla/dom/TabChild.h" #include "mozilla/Hal.h" #include "mozilla/IMEStateManager.h" -#include "mozilla/layers/CompositorChild.h" +#include "mozilla/layers/APZChild.h" #include "mozilla/layers/PLayerTransactionChild.h" #include "mozilla/Preferences.h" #include "mozilla/TextComposition.h" @@ -510,7 +510,7 @@ PuppetWidget::SetConfirmedTargetAPZC(uint64_t aInputBlockId, const nsTArray& aTargets) const { if (mTabChild) { - mTabChild->SendSetTargetAPZC(aInputBlockId, aTargets); + mTabChild->SetTargetAPZC(aInputBlockId, aTargets); } } @@ -1254,7 +1254,7 @@ uint32_t PuppetWidget::GetMaxTouchPoints() const void PuppetWidget::StartAsyncScrollbarDrag(const AsyncDragMetrics& aDragMetrics) { - mTabChild->SendStartScrollbarDrag(aDragMetrics); + mTabChild->StartScrollbarDrag(aDragMetrics); } PuppetScreen::PuppetScreen(void *nativeScreen) @@ -1427,7 +1427,7 @@ PuppetWidget::ZoomToRect(const uint32_t& aPresShellId, return; } - mTabChild->SendZoomToRect(aPresShellId, aViewId, aRect, aFlags); + mTabChild->ZoomToRect(aPresShellId, aViewId, aRect, aFlags); } } // namespace widget diff --git a/widget/gonk/HwcComposer2D.cpp b/widget/gonk/HwcComposer2D.cpp index 6b109e8bb2..9d427c3294 100644 --- a/widget/gonk/HwcComposer2D.cpp +++ b/widget/gonk/HwcComposer2D.cpp @@ -280,7 +280,7 @@ HwcComposer2D::PrepareLayerList(Layer* aLayer, bool fillColor = false; - const nsIntRegion visibleRegion = aLayer->GetEffectiveVisibleRegion().ToUnknownRegion(); + const nsIntRegion visibleRegion = aLayer->GetLocalVisibleRegion().ToUnknownRegion(); if (visibleRegion.IsEmpty()) { return true; } diff --git a/widget/gonk/nativewindow/FakeSurfaceComposer.cpp b/widget/gonk/nativewindow/FakeSurfaceComposer.cpp index 1b34761a9d..f6cdf29765 100644 --- a/widget/gonk/nativewindow/FakeSurfaceComposer.cpp +++ b/widget/gonk/nativewindow/FakeSurfaceComposer.cpp @@ -15,19 +15,37 @@ * limitations under the License. */ +#define LOG_TAG "FakeSurfaceComposer" +//#define LOG_NDEBUG 0 + #include #include #include +#include #include +#include +#include #include #include +#include +#include #if ANDROID_VERSION >= 21 #include #endif +#include "../libdisplay/GonkDisplay.h" +#include "../nsScreenManagerGonk.h" #include "FakeSurfaceComposer.h" +#include "gfxPrefs.h" +#include "MainThreadUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/layers/CompositorParent.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" + +using namespace mozilla; namespace android { @@ -46,6 +64,32 @@ FakeSurfaceComposer::~FakeSurfaceComposer() { } +status_t FakeSurfaceComposer::onTransact(uint32_t code, const Parcel& data, + Parcel* reply, uint32_t flags) +{ + switch (code) { + case CREATE_CONNECTION: + case CREATE_DISPLAY: + case SET_TRANSACTION_STATE: + case CAPTURE_SCREEN: + { + // codes that require permission check + IPCThreadState* ipc = IPCThreadState::self(); + const int pid = ipc->getCallingPid(); + const int uid = ipc->getCallingUid(); + // Accept request only when uid is root. + if (uid != AID_ROOT) { + ALOGE("Permission Denial: " + "can't access SurfaceFlinger pid=%d, uid=%d", pid, uid); + return PERMISSION_DENIED; + } + break; + } + } + + return BnSurfaceComposer::onTransact(code, data, reply, flags); +} + sp FakeSurfaceComposer::createConnection() { return nullptr; @@ -57,20 +101,86 @@ sp FakeSurfaceComposer::createGraphicBufferAlloc() return gba; } +class DestroyDisplayRunnable : public nsRunnable { +public: + DestroyDisplayRunnable(FakeSurfaceComposer* aComposer, ssize_t aIndex) + : mComposer(aComposer), mIndex(aIndex) { } + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); + Mutex::Autolock _l(mComposer->mStateLock); + RefPtr screenManager = + nsScreenManagerGonk::GetInstance(); + screenManager->RemoveScreen(GonkDisplay::DISPLAY_VIRTUAL); + mComposer->mDisplays.removeItemsAt(mIndex); + return NS_OK; + } + sp mComposer; + ssize_t mIndex; +}; + sp FakeSurfaceComposer::createDisplay(const String8& displayName, bool secure) { +#if ANDROID_VERSION >= 19 + class DisplayToken : public BBinder { + sp composer; + virtual ~DisplayToken() { + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); + // no more references, this display must be terminated + Mutex::Autolock _l(composer->mStateLock); + ssize_t idx = composer->mDisplays.indexOfKey(this); + if (idx >= 0) { + nsCOMPtr task(new DestroyDisplayRunnable(composer.get(), idx)); + NS_DispatchToMainThread(task); + } + } + public: + DisplayToken(const sp& composer) + : composer(composer) { + } + }; + + sp token = new DisplayToken(this); + + Mutex::Autolock _l(mStateLock); + DisplayDeviceState info(HWC_DISPLAY_VIRTUAL); + info.displayName = displayName; + info.displayId = GonkDisplay::DISPLAY_VIRTUAL; + info.isSecure = secure; + mDisplays.add(token, info); + return token; +#else return nullptr; +#endif } #if ANDROID_VERSION >= 19 void FakeSurfaceComposer::destroyDisplay(const sp& display) { + Mutex::Autolock _l(mStateLock); + + ssize_t idx = mDisplays.indexOfKey(display); + if (idx < 0) { + ALOGW("destroyDisplay: invalid display token"); + return; + } + + nsCOMPtr task(new DestroyDisplayRunnable(this, idx)); + NS_DispatchToMainThread(task); } #endif -sp FakeSurfaceComposer::getBuiltInDisplay(int32_t id) { - return nullptr; +sp FakeSurfaceComposer::getBuiltInDisplay(int32_t id) +{ + // support only primary display + if (uint32_t(id) != HWC_DISPLAY_PRIMARY) { + return NULL; + } + + if (!mPrimaryDisplay.get()) { + mPrimaryDisplay = new BBinder(); + } + return mPrimaryDisplay; } void FakeSurfaceComposer::setTransactionState( @@ -78,6 +188,79 @@ void FakeSurfaceComposer::setTransactionState( const Vector& displays, uint32_t flags) { + Mutex::Autolock _l(mStateLock); + size_t count = displays.size(); + for (size_t i=0 ; iasBinder() != s.surface->asBinder()) { + disp.surface = s.surface; + flags |= eDisplayTransactionNeeded; + } + } + if (what & DisplayState::eLayerStackChanged) { + if (disp.layerStack != s.layerStack) { + disp.layerStack = s.layerStack; + flags |= eDisplayTransactionNeeded; + } + } + if (what & DisplayState::eDisplayProjectionChanged) { + if (disp.orientation != s.orientation) { + disp.orientation = s.orientation; + flags |= eDisplayTransactionNeeded; + } + if (disp.frame != s.frame) { + disp.frame = s.frame; + flags |= eDisplayTransactionNeeded; + } + if (disp.viewport != s.viewport) { + disp.viewport = s.viewport; + flags |= eDisplayTransactionNeeded; + } + } +#if ANDROID_VERSION >= 21 + if (what & DisplayState::eDisplaySizeChanged) { + if (disp.width != s.width) { + disp.width = s.width; + flags |= eDisplayTransactionNeeded; + } + if (disp.height != s.height) { + disp.height = s.height; + flags |= eDisplayTransactionNeeded; + } + } +#endif + + if (what & DisplayState::eSurfaceChanged) { + nsCOMPtr runnable = + NS_NewRunnableFunction([&]() { + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); + RefPtr screenManager = nsScreenManagerGonk::GetInstance(); + screenManager->AddScreen(GonkDisplay::DISPLAY_VIRTUAL, disp.surface.get()); + }); + NS_DispatchToMainThread(runnable, NS_DISPATCH_SYNC); + } + + return flags; } void FakeSurfaceComposer::bootFinished() @@ -93,6 +276,127 @@ sp FakeSurfaceComposer::createDisplayEventConnection() return nullptr; } +// --------------------------------------------------------------------------- +// Capture screen into an IGraphiBufferProducer +// --------------------------------------------------------------------------- + +class Barrier { +public: + inline Barrier() : state(CLOSED) { } + inline ~Barrier() { } + + // Release any threads waiting at the Barrier. + // Provides release semantics: preceding loads and stores will be visible + // to other threads before they wake up. + void open() { + Mutex::Autolock _l(lock); + state = OPENED; + cv.broadcast(); + } + + // Reset the Barrier, so wait() will block until open() has been called. + void close() { + Mutex::Autolock _l(lock); + state = CLOSED; + } + + // Wait until the Barrier is OPEN. + // Provides acquire semantics: no subsequent loads or stores will occur + // until wait() returns. + void wait() const { + Mutex::Autolock _l(lock); + while (state == CLOSED) { + cv.wait(lock); + } + } +private: + enum { OPENED, CLOSED }; + mutable Mutex lock; + mutable Condition cv; + volatile int state; +}; + +/* The code below is here to handle b/8734824 + * + * We create a IGraphicBufferProducer wrapper that forwards all calls + * to the calling binder thread, where they are executed. This allows + * the calling thread to be reused (on the other side) and not + * depend on having "enough" binder threads to handle the requests. + * + */ + +class GraphicProducerWrapper : public BBinder, public MessageHandler { + sp impl; + sp looper; + status_t result; + bool exitPending; + bool exitRequested; + mutable Barrier barrier; + volatile int32_t memoryBarrier; + uint32_t code; + Parcel const* data; + Parcel* reply; + + enum { + MSG_API_CALL, + MSG_EXIT + }; + + /* + * this is called by our "fake" BpGraphicBufferProducer. We package the + * data and reply Parcel and forward them to the calling thread. + */ + virtual status_t transact(uint32_t code, + const Parcel& data, Parcel* reply, uint32_t flags) { + this->code = code; + this->data = &data; + this->reply = reply; + android_atomic_acquire_store(0, &memoryBarrier); + if (exitPending) { + // if we've exited, we run the message synchronously right here + handleMessage(Message(MSG_API_CALL)); + } else { + barrier.close(); + looper->sendMessage(this, Message(MSG_API_CALL)); + barrier.wait(); + } + return NO_ERROR; + } + + /* + * here we run on the binder calling thread. All we've got to do is + * call the real BpGraphicBufferProducer. + */ + virtual void handleMessage(const Message& message) { + android_atomic_release_load(&memoryBarrier); + if (message.what == MSG_API_CALL) { + impl->asBinder()->transact(code, data[0], reply); + barrier.open(); + } else if (message.what == MSG_EXIT) { + exitRequested = true; + } + } + +public: + GraphicProducerWrapper(const sp& impl) : + impl(impl), looper(new Looper(true)), result(NO_ERROR), + exitPending(false), exitRequested(false) { + } + + status_t waitForResponse() { + do { + looper->pollOnce(-1); + } while (!exitRequested); + return result; + } + + void exit(status_t result) { + this->result = result; + exitPending = true; + looper->sendMessage(this, Message(MSG_EXIT)); + } +}; + status_t FakeSurfaceComposer::captureScreen(const sp& display , const sp& producer @@ -111,7 +415,117 @@ FakeSurfaceComposer::captureScreen(const sp& display #endif ) { - return INVALID_OPERATION; + if (display == 0 || producer == 0) { + return BAD_VALUE; + } + + // Limit only to primary display + if (display != mPrimaryDisplay) { + return BAD_VALUE; + } + + // this creates a "fake" BBinder which will serve as a "fake" remote + // binder to receive the marshaled calls and forward them to the + // real remote (a BpGraphicBufferProducer) + sp wrapper = new GraphicProducerWrapper(producer); + // the asInterface() call below creates our "fake" BpGraphicBufferProducer + // which does the marshaling work forwards to our "fake remote" above. + sp fakeProducer = IGraphicBufferProducer::asInterface(wrapper); + + nsCOMPtr runnable = + NS_NewRunnableFunction([&]() { + captureScreenImp(fakeProducer, reqWidth, reqHeight, wrapper.get()); + }); + NS_DispatchToMainThread(runnable); + + status_t result = wrapper->waitForResponse(); + + return result; +} + +class RunnableCallTask : public Task { +public: + explicit RunnableCallTask(nsIRunnable* aRunnable) + : mRunnable(aRunnable) {} + + void Run() override + { + mRunnable->Run(); + } +protected: + nsCOMPtr mRunnable; +}; + +void +FakeSurfaceComposer::captureScreenImp(const sp& producer, + uint32_t reqWidth, + uint32_t reqHeight, + const sp& wrapper) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(wrapper.get()); + + RefPtr screen = nsScreenManagerGonk::GetPrimaryScreen(); + + // get screen geometry + nsIntRect screenBounds = screen->GetNaturalBounds().ToUnknownRect(); + const uint32_t hw_w = screenBounds.width; + const uint32_t hw_h = screenBounds.height; + + if (reqWidth > hw_w || reqHeight > hw_h) { + ALOGE("size mismatch (%d, %d) > (%d, %d)", + reqWidth, reqHeight, hw_w, hw_h); + static_cast(producer->asBinder().get())->exit(BAD_VALUE); + return; + } + + reqWidth = (!reqWidth) ? hw_w : reqWidth; + reqHeight = (!reqHeight) ? hw_h : reqHeight; + + nsCOMPtr runnable = + NS_NewRunnableFunction([screen, reqWidth, reqHeight, producer, wrapper]() { + // create a surface (because we're a producer, and we need to + // dequeue/queue a buffer) + sp sur = new Surface(producer); + ANativeWindow* window = sur.get(); + // The closure makes screen const and we can't call forget() on it. + RefPtr screenAlias = screen; + + if (native_window_api_connect(window, NATIVE_WINDOW_API_EGL) != NO_ERROR) { + static_cast(producer->asBinder().get())->exit(BAD_VALUE); + NS_ReleaseOnMainThread(screenAlias.forget()); + return; + } + uint32_t usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN | + GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE; + + int err = 0; + err = native_window_set_buffers_dimensions(window, reqWidth, reqHeight); + err |= native_window_set_scaling_mode(window, NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW); + err |= native_window_set_buffers_format(window, HAL_PIXEL_FORMAT_RGBA_8888); + err |= native_window_set_usage(window, usage); + + status_t result = NO_ERROR; + if (err == NO_ERROR) { + ANativeWindowBuffer* buffer; + result = native_window_dequeue_buffer_and_wait(window, &buffer); + if (result == NO_ERROR) { + nsresult rv = screen->MakeSnapshot(buffer); + if (rv != NS_OK) { + result = INVALID_OPERATION; + } + window->queueBuffer(window, buffer, -1); + } + } else { + result = BAD_VALUE; + } + native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL); + static_cast(producer->asBinder().get())->exit(result); + NS_ReleaseOnMainThread(screenAlias.forget()); + }); + + mozilla::layers::CompositorParent::CompositorLoop()->PostTask( + FROM_HERE, new RunnableCallTask(runnable)); } #if ANDROID_VERSION >= 21 @@ -123,7 +537,27 @@ FakeSurfaceComposer::setPowerMode(const sp& display, int mode) status_t FakeSurfaceComposer::getDisplayConfigs(const sp& display, Vector* configs) { - return INVALID_OPERATION; + if (configs == NULL) { + return BAD_VALUE; + } + + // Limit DisplayConfigs only to primary display + if (!display.get() || display != mPrimaryDisplay) { + return NAME_NOT_FOUND; + } + + configs->clear(); + DisplayInfo info = DisplayInfo(); + + nsCOMPtr runnable = + NS_NewRunnableFunction([&]() { + MOZ_ASSERT(NS_IsMainThread()); + getPrimaryDisplayInfo(&info); + }); + NS_DispatchToMainThread(runnable, NS_DISPATCH_SYNC); + + configs->push_back(info); + return NO_ERROR; } status_t @@ -135,7 +569,11 @@ FakeSurfaceComposer::getDisplayStats(const sp& display, DisplayStatInfo int FakeSurfaceComposer::getActiveConfig(const sp& display) { - return INVALID_OPERATION; + // Only support primary display. + if (display.get() && (display == mPrimaryDisplay)) { + return 0; + } + return INVALID_OPERATION; } status_t @@ -169,8 +607,95 @@ FakeSurfaceComposer::unblank(const sp& display) status_t FakeSurfaceComposer::getDisplayInfo(const sp& display, DisplayInfo* info) { - return INVALID_OPERATION; + if (info == NULL) { + return BAD_VALUE; + } + + // Limit DisplayConfigs only to primary display + if (!display.get() || display != mPrimaryDisplay) { + return NAME_NOT_FOUND; + } + + nsCOMPtr runnable = + NS_NewRunnableFunction([&]() { + MOZ_ASSERT(NS_IsMainThread()); + getPrimaryDisplayInfo(info); + }); + NS_DispatchToMainThread(runnable, NS_DISPATCH_SYNC); + + return NO_ERROR; } #endif +#define VSYNC_EVENT_PHASE_OFFSET_NS 0 +#define SF_VSYNC_EVENT_PHASE_OFFSET_NS 0 + +void +FakeSurfaceComposer::getPrimaryDisplayInfo(DisplayInfo* info) +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); + + // Implementation mimic android SurfaceFlinger::getDisplayConfigs(). + + class Density { + static int getDensityFromProperty(char const* propName) { + char property[PROPERTY_VALUE_MAX]; + int density = 0; + if (property_get(propName, property, NULL) > 0) { + density = atoi(property); + } + return density; + } + public: + static int getEmuDensity() { + return getDensityFromProperty("qemu.sf.lcd_density"); } + static int getBuildDensity() { + return getDensityFromProperty("ro.sf.lcd_density"); } + }; + + RefPtr screen = nsScreenManagerGonk::GetPrimaryScreen(); + + float xdpi = screen->GetDpi(); + float ydpi = screen->GetDpi(); + int fps = 60; // XXX set a value from hwc hal + nsIntRect screenBounds = screen->GetNaturalBounds().ToUnknownRect(); + + // The density of the device is provided by a build property + float density = Density::getBuildDensity() / 160.0f; + if (density == 0) { + // the build doesn't provide a density -- this is wrong! + // use xdpi instead + ALOGE("ro.sf.lcd_density must be defined as a build property"); + density = xdpi / 160.0f; + } + info->density = density; + info->orientation = screen->EffectiveScreenRotation(); + + info->w = screenBounds.width; + info->h = screenBounds.height; + info->xdpi = xdpi; + info->ydpi = ydpi; + info->fps = fps; +#if ANDROID_VERSION >= 21 + info->appVsyncOffset = VSYNC_EVENT_PHASE_OFFSET_NS; + + // This is how far in advance a buffer must be queued for + // presentation at a given time. If you want a buffer to appear + // on the screen at time N, you must submit the buffer before + // (N - presentationDeadline). + // + // Normally it's one full refresh period (to give SF a chance to + // latch the buffer), but this can be reduced by configuring a + // DispSync offset. Any additional delays introduced by the hardware + // composer or panel must be accounted for here. + // + // We add an additional 1ms to allow for processing time and + // differences between the ideal and actual refresh rate. + info->presentationDeadline = + (1e9 / fps) - SF_VSYNC_EVENT_PHASE_OFFSET_NS + 1000000; +#endif + // All non-virtual displays are currently considered secure. + info->secure = true; +} + }; // namespace android diff --git a/widget/gonk/nativewindow/FakeSurfaceComposer.h b/widget/gonk/nativewindow/FakeSurfaceComposer.h index ab98656cb9..97a7174447 100644 --- a/widget/gonk/nativewindow/FakeSurfaceComposer.h +++ b/widget/gonk/nativewindow/FakeSurfaceComposer.h @@ -22,18 +22,33 @@ #include #include +#include #include +#include #include #include +#include +#include +#include + +class nsIWidget; namespace android { // --------------------------------------------------------------------------- +class GraphicProducerWrapper; class IGraphicBufferAlloc; +enum { + eTransactionNeeded = 0x01, + eTraversalNeeded = 0x02, + eDisplayTransactionNeeded = 0x04, + eTransactionMask = 0x07 +}; + class FakeSurfaceComposer : public BinderService, public BnSurfaceComposer { @@ -74,6 +89,12 @@ private: // We're reference counted, never destroy FakeSurfaceComposer directly virtual ~FakeSurfaceComposer(); + /* ------------------------------------------------------------------------ + * IBinder interface + */ + virtual status_t onTransact(uint32_t code, const Parcel& data, + Parcel* reply, uint32_t flags); + /* ------------------------------------------------------------------------ * ISurfaceComposer interface */ @@ -102,7 +123,50 @@ private: virtual void unblank(const sp& display); virtual status_t getDisplayInfo(const sp& display, DisplayInfo* info); #endif + void getPrimaryDisplayInfo(DisplayInfo* info); + /* ------------------------------------------------------------------------ + * Transactions + */ + uint32_t setDisplayStateLocked(const DisplayState& s); + + void captureScreenImp(const sp& producer, + uint32_t reqWidth, + uint32_t reqHeight, + const sp& wrapper); + + sp mPrimaryDisplay; + + struct DisplayDeviceState { + enum { + NO_LAYER_STACK = 0xFFFFFFFF, + }; + DisplayDeviceState() + : type(-1), displayId(-1), width(0), height(0) { + } + DisplayDeviceState(int type) + : type(type), displayId(-1), layerStack(NO_LAYER_STACK), orientation(0), width(0), height(0) { + viewport.makeInvalid(); + frame.makeInvalid(); + } + bool isValid() const { return type >= 0; } + int type; + int displayId; + sp surface; + uint32_t layerStack; + Rect viewport; + Rect frame; + uint8_t orientation; + uint32_t width, height; + String8 displayName; + bool isSecure; + }; + + // access must be protected by mStateLock + mutable Mutex mStateLock; + DefaultKeyedVector, DisplayDeviceState> mDisplays; + + friend class DestroyDisplayRunnable; }; // --------------------------------------------------------------------------- diff --git a/widget/gonk/nativewindow/moz.build b/widget/gonk/nativewindow/moz.build index d8a5edba55..6b3fe4c64e 100644 --- a/widget/gonk/nativewindow/moz.build +++ b/widget/gonk/nativewindow/moz.build @@ -91,19 +91,25 @@ if CONFIG['MOZ_B2G_CAMERA'] or CONFIG['MOZ_OMX_DECODER'] or CONFIG['MOZ_WEBRTC'] 'GonkNativeWindowICS.cpp', ] -if CONFIG['MOZ_B2G_CAMERA'] or CONFIG['MOZ_OMX_DECODER']: - if CONFIG['ANDROID_VERSION'] >= '18': - SOURCES += [ - 'FakeSurfaceComposer.cpp', - ] +if CONFIG['ANDROID_VERSION'] >= '18': + SOURCES += [ + 'FakeSurfaceComposer.cpp', + ] include('/ipc/chromium/chromium-config.mozbuild') +if CONFIG['ANDROID_VERSION'] >= '18': + LOCAL_INCLUDES += [ + '%' + '%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [ + 'frameworks/native/opengl/include', + ] + ] + # Suppress some GCC warnings being treated as errors: # - about attributes on forward declarations for types that are already # defined, which complains about an important MOZ_EXPORT for android::AString if CONFIG['GNU_CC']: - CXXFLAGS += ['-Wno-error=attributes'] + CXXFLAGS += ['-Wno-error=attributes', '-Wno-overloaded-virtual'] FINAL_LIBRARY = 'xul' diff --git a/widget/gonk/nsAppShell.cpp b/widget/gonk/nsAppShell.cpp index 25ea3b89f2..c14dc988dd 100644 --- a/widget/gonk/nsAppShell.cpp +++ b/widget/gonk/nsAppShell.cpp @@ -562,17 +562,17 @@ private: void GeckoInputReaderPolicy::setDisplayInfo() { - static_assert(nsIScreen::ROTATION_0_DEG == - DISPLAY_ORIENTATION_0, + static_assert(static_cast(nsIScreen::ROTATION_0_DEG) == + static_cast(DISPLAY_ORIENTATION_0), "Orientation enums not matched!"); - static_assert(nsIScreen::ROTATION_90_DEG == - DISPLAY_ORIENTATION_90, + static_assert(static_cast(nsIScreen::ROTATION_90_DEG) == + static_cast(DISPLAY_ORIENTATION_90), "Orientation enums not matched!"); - static_assert(nsIScreen::ROTATION_180_DEG == - DISPLAY_ORIENTATION_180, + static_assert(static_cast(nsIScreen::ROTATION_180_DEG) == + static_cast(DISPLAY_ORIENTATION_180), "Orientation enums not matched!"); - static_assert(nsIScreen::ROTATION_270_DEG == - DISPLAY_ORIENTATION_270, + static_assert(static_cast(nsIScreen::ROTATION_270_DEG) == + static_cast(DISPLAY_ORIENTATION_270), "Orientation enums not matched!"); RefPtr screen = nsScreenManagerGonk::GetPrimaryScreen(); @@ -848,7 +848,9 @@ nsAppShell::nsAppShell() , mPowerKeyChecked(false) { gAppShell = this; - Preferences::SetCString("b2g.safe_mode", "unset"); + if (XRE_IsParentProcess()) { + Preferences::SetCString("b2g.safe_mode", "unset"); + } } nsAppShell::~nsAppShell() @@ -935,8 +937,13 @@ nsAppShell::CheckPowerKey() // If Power is pressed while we startup, mark safe mode. // Consumers of the b2g.safe_mode preference need to listen on this // preference change to prevent startup races. - Preferences::SetCString("b2g.safe_mode", - (powerState == AKEY_STATE_DOWN) ? "yes" : "no"); + nsCOMPtr prefSetter = + NS_NewRunnableFunction([powerState] () -> void { + Preferences::SetCString("b2g.safe_mode", + (powerState == AKEY_STATE_DOWN) ? "yes" : "no"); + }); + NS_DispatchToMainThread(prefSetter.forget()); + mPowerKeyChecked = true; } diff --git a/widget/gonk/nsScreenManagerGonk.cpp b/widget/gonk/nsScreenManagerGonk.cpp index 458228c6ac..828b6de08d 100644 --- a/widget/gonk/nsScreenManagerGonk.cpp +++ b/widget/gonk/nsScreenManagerGonk.cpp @@ -15,6 +15,8 @@ #include "android/log.h" #include "GLContext.h" +#include "gfxPrefs.h" +#include "gfxUtils.h" #include "mozilla/MouseEvents.h" #include "mozilla/TouchEvents.h" #include "mozilla/Hal.h" @@ -25,12 +27,14 @@ #include "HwcComposer2D.h" #include "VsyncSource.h" #include "nsWindow.h" +#include "mozilla/ClearOnShutdown.h" #include "mozilla/layers/CompositorParent.h" #include "mozilla/Services.h" #include "mozilla/ProcessPriorityManager.h" #include "nsIdleService.h" #include "nsIObserverService.h" #include "nsAppShell.h" +#include "nsProxyRelease.h" #include "nsTArray.h" #include "pixelflinger/format.h" #include "nsIDisplayInfo.h" @@ -93,7 +97,7 @@ displayEnabledCallback(bool enabled) screenManager->DisplayEnabled(enabled); } -} // anonymous namespace +} // namespace static uint32_t SurfaceFormatToColorDepth(int32_t aSurfaceFormat) @@ -127,6 +131,8 @@ nsScreenGonk::nsScreenGonk(uint32_t aId, , mEGLDisplay(EGL_NO_DISPLAY) , mEGLSurface(EGL_NO_SURFACE) , mGLContext(nullptr) + , mFramebuffer(nullptr) + , mMappedBuffer(nullptr) { if (mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_WIDTH, &mVirtualBounds.width) || mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_HEIGHT, &mVirtualBounds.height) || @@ -252,9 +258,6 @@ nsScreenGonk::GetSurfaceFormat() ANativeWindow* nsScreenGonk::GetNativeWindow() { - if (IsPrimaryScreen()) { - StopBootAnimation(); - } return mNativeWindow.get(); } @@ -369,6 +372,126 @@ nsScreenGonk::BringToTop(nsWindow* aWindow) mTopWindows.InsertElementAt(0, aWindow); } +static gralloc_module_t const* +gralloc_module() +{ + hw_module_t const *module; + if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module)) { + return nullptr; + } + return reinterpret_cast(module); +} + +static SurfaceFormat +HalFormatToSurfaceFormat(int aHalFormat) +{ + switch (aHalFormat) { + case HAL_PIXEL_FORMAT_RGBA_8888: + // Needs RB swap + return SurfaceFormat::B8G8R8A8; + case HAL_PIXEL_FORMAT_RGBX_8888: + // Needs RB swap + return SurfaceFormat::B8G8R8X8; + case HAL_PIXEL_FORMAT_BGRA_8888: + return SurfaceFormat::B8G8R8A8; + case HAL_PIXEL_FORMAT_RGB_565: + return SurfaceFormat::R5G6B5_UINT16; + default: + MOZ_CRASH("Unhandled HAL pixel format"); + return SurfaceFormat::UNKNOWN; // not reached + } +} + +static bool +NeedsRBSwap(int aHalFormat) +{ + switch (aHalFormat) { + case HAL_PIXEL_FORMAT_RGBA_8888: + return true; + case HAL_PIXEL_FORMAT_RGBX_8888: + return true; + case HAL_PIXEL_FORMAT_BGRA_8888: + return false; + case HAL_PIXEL_FORMAT_RGB_565: + return false; + default: + MOZ_CRASH("Unhandled HAL pixel format"); + return false; // not reached + } +} + +already_AddRefed +nsScreenGonk::StartRemoteDrawing() +{ + MOZ_ASSERT(CompositorParent::IsInCompositorThread()); + MOZ_ASSERT(!mFramebuffer); + MOZ_ASSERT(!mMappedBuffer); + + mFramebuffer = DequeueBuffer(); + int width = mFramebuffer->width, height = mFramebuffer->height; + if (gralloc_module()->lock(gralloc_module(), mFramebuffer->handle, + GRALLOC_USAGE_SW_READ_NEVER | + GRALLOC_USAGE_SW_WRITE_OFTEN | + GRALLOC_USAGE_HW_FB, + 0, 0, width, height, + reinterpret_cast(&mMappedBuffer))) { + EndRemoteDrawing(); + return nullptr; + } + SurfaceFormat format = HalFormatToSurfaceFormat(GetSurfaceFormat()); + mFramebufferTarget = Factory::CreateDrawTargetForData( + BackendType::CAIRO, + mMappedBuffer, + IntSize(width, height), + mFramebuffer->stride * gfx::BytesPerPixel(format), + format); + if (!mFramebufferTarget) { + MOZ_CRASH("nsWindow::StartRemoteDrawing failed in CreateDrawTargetForData"); + } + if (!mBackBuffer || + mBackBuffer->GetSize() != mFramebufferTarget->GetSize() || + mBackBuffer->GetFormat() != mFramebufferTarget->GetFormat()) { + mBackBuffer = mFramebufferTarget->CreateSimilarDrawTarget( + mFramebufferTarget->GetSize(), mFramebufferTarget->GetFormat()); + } + RefPtr buffer(mBackBuffer); + return buffer.forget(); +} + +void +nsScreenGonk::EndRemoteDrawing() +{ + MOZ_ASSERT(CompositorParent::IsInCompositorThread()); + + if (mFramebufferTarget && mFramebuffer) { + IntSize size = mFramebufferTarget->GetSize(); + Rect rect(0, 0, size.width, size.height); + RefPtr source = mBackBuffer->Snapshot(); + mFramebufferTarget->DrawSurface(source, rect, rect); + + // Convert from BGR to RGB + // XXX this is a temporary solution. It consumes extra cpu cycles, + // it should not be used on product device. + if (NeedsRBSwap(GetSurfaceFormat())) { + LOGE("Very slow composition path, it should not be used on product!!!"); + SurfaceFormat format = HalFormatToSurfaceFormat(GetSurfaceFormat()); + gfxUtils::ConvertBGRAtoRGBA( + mMappedBuffer, + mFramebuffer->stride * mFramebuffer->height * gfx::BytesPerPixel(format)); + } + } + if (mMappedBuffer) { + MOZ_ASSERT(mFramebuffer); + gralloc_module()->unlock(gralloc_module(), mFramebuffer->handle); + mMappedBuffer = nullptr; + } + if (mFramebuffer) { + QueueBuffer(mFramebuffer); + } + mFramebuffer = nullptr; + mFramebufferTarget = nullptr; +} + ANativeWindowBuffer* nsScreenGonk::DequeueBuffer() { @@ -402,6 +525,63 @@ nsScreenGonk::QueueBuffer(ANativeWindowBuffer* buf) #endif } +nsresult +nsScreenGonk::MakeSnapshot(ANativeWindowBuffer* aBuffer) +{ + MOZ_ASSERT(CompositorParent::IsInCompositorThread()); + MOZ_ASSERT(aBuffer); + + layers::CompositorParent* compositorParent = mCompositorParent; + if (!compositorParent) { + return NS_ERROR_FAILURE; + } + + int width = aBuffer->width, height = aBuffer->height; + uint8_t* mappedBuffer = nullptr; + if (gralloc_module()->lock(gralloc_module(), aBuffer->handle, + GRALLOC_USAGE_SW_READ_OFTEN | + GRALLOC_USAGE_SW_WRITE_OFTEN, + 0, 0, width, height, + reinterpret_cast(&mappedBuffer))) { + return NS_ERROR_FAILURE; + } + + SurfaceFormat format = HalFormatToSurfaceFormat(GetSurfaceFormat()); + RefPtr mTarget = + Factory::CreateDrawTargetForData( + BackendType::CAIRO, + mappedBuffer, + IntSize(width, height), + aBuffer->stride * gfx::BytesPerPixel(format), + format); + if (!mTarget) { + return NS_ERROR_FAILURE; + } + + gfx::IntRect rect = GetRect().ToUnknownRect(); + compositorParent->ForceComposeToTarget(mTarget, &rect); + + // Convert from BGR to RGB + // XXX this is a temporary solution. It consumes extra cpu cycles, + if (NeedsRBSwap(GetSurfaceFormat())) { + LOGE("Slow path of making Snapshot!!!"); + SurfaceFormat format = HalFormatToSurfaceFormat(GetSurfaceFormat()); + gfxUtils::ConvertBGRAtoRGBA( + mappedBuffer, + aBuffer->stride * aBuffer->height * gfx::BytesPerPixel(format)); + mappedBuffer = nullptr; + } + gralloc_module()->unlock(gralloc_module(), aBuffer->handle); + return NS_OK; +} + +void +nsScreenGonk::SetCompositorParent(layers::CompositorParent* aCompositorParent) +{ + MOZ_ASSERT(NS_IsMainThread()); + mCompositorParent = aCompositorParent; +} + #if ANDROID_VERSION >= 17 android::DisplaySurface* nsScreenGonk::GetDisplaySurface() @@ -459,7 +639,7 @@ nsScreenGonk::GetGLContext() } static void -UpdateMirroringWidgetSync(RefPtr&& aScreen, nsWindow* aWindow) +UpdateMirroringWidgetSync(nsMainThreadPtrHandle&& aScreen, nsWindow* aWindow) { MOZ_ASSERT(CompositorParent::IsInCompositorThread()); already_AddRefed window(aWindow); @@ -486,10 +666,12 @@ nsScreenGonk::EnableMirroring() MOZ_ASSERT(static_cast(window)->GetScreen() == this); // Update mMirroringWidget on compositor thread + nsMainThreadPtrHandle primary = + nsMainThreadPtrHandle(new nsMainThreadPtrHolder(primaryScreen, false)); CompositorParent::CompositorLoop()->PostTask( FROM_HERE, NewRunnableFunction(&UpdateMirroringWidgetSync, - primaryScreen, + primary, window.forget().take())); mIsMirroring = true; @@ -510,10 +692,12 @@ nsScreenGonk::DisableMirroring() NS_ENSURE_TRUE(ret, false); // Update mMirroringWidget on compositor thread + nsMainThreadPtrHandle primary = + nsMainThreadPtrHandle(new nsMainThreadPtrHolder(primaryScreen, false)); CompositorParent::CompositorLoop()->PostTask( FROM_HERE, NewRunnableFunction(&UpdateMirroringWidgetSync, - primaryScreen, + primary, nullptr)); return true; } @@ -527,7 +711,7 @@ nsScreenGonk::SetMirroringScreen(nsScreenGonk* aScreen) if (mMirroringScreen) { return false; } - mMirroringScreen = mMirroringScreen; + mMirroringScreen = aScreen; return true; } @@ -570,6 +754,9 @@ NS_IMPL_ISUPPORTS(nsScreenManagerGonk, nsIScreenManager) nsScreenManagerGonk::nsScreenManagerGonk() : mInitialized(false) +#if ANDROID_VERSION >= 19 + , mDisplayEnabled(false) +#endif { } @@ -577,19 +764,33 @@ nsScreenManagerGonk::~nsScreenManagerGonk() { } +static StaticRefPtr sScreenManagerGonk; + /* static */ already_AddRefed nsScreenManagerGonk::GetInstance() { - nsCOMPtr manager; - manager = do_GetService("@mozilla.org/gfx/screenmanager;1"); - MOZ_ASSERT(manager); - return already_AddRefed( - static_cast(manager.forget().take())); + MOZ_ASSERT(NS_IsMainThread()); + + // Avoid creating nsScreenManagerGonk from content process. + if (!XRE_IsParentProcess()) { + MOZ_CRASH("Non-chrome processes should not get here."); + } + + // Avoid creating multiple nsScreenManagerGonk instance inside main process. + if (!sScreenManagerGonk) { + sScreenManagerGonk = new nsScreenManagerGonk(); + ClearOnShutdown(&sScreenManagerGonk); + } + + RefPtr screenMgr = sScreenManagerGonk.get(); + return screenMgr.forget(); } /* static */ already_AddRefed< nsScreenGonk> nsScreenManagerGonk::GetPrimaryScreen() { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr manager = nsScreenManagerGonk::GetInstance(); nsCOMPtr screen; manager->GetPrimaryScreen(getter_AddRefs(screen)); @@ -618,6 +819,19 @@ nsScreenManagerGonk::Initialize() void nsScreenManagerGonk::DisplayEnabled(bool aEnabled) { + MOZ_ASSERT(NS_IsMainThread()); + +#if ANDROID_VERSION >= 19 + /* Bug 1244044 + * This function could be called before |mCompositorVsyncScheduler| is set. + * To avoid this issue, keep the value stored in |mDisplayEnabled|. + */ + mDisplayEnabled = aEnabled; + if (mCompositorVsyncScheduler) { + mCompositorVsyncScheduler->SetDisplay(mDisplayEnabled); + } +#endif + VsyncControl(aEnabled); NS_DispatchToMainThread(aEnabled ? mScreenOnEvent : mScreenOffEvent); } @@ -850,3 +1064,17 @@ nsScreenManagerGonk::RemoveScreen(GonkDisplay::DisplayType aDisplayType) } return NS_OK; } + +#if ANDROID_VERSION >= 19 +void +nsScreenManagerGonk::SetCompositorVsyncScheduler(mozilla::layers::CompositorVsyncScheduler *aObserver) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // We assume on b2g that there is only 1 CompositorParent + MOZ_ASSERT(mCompositorVsyncScheduler == nullptr); + MOZ_ASSERT(aObserver); + mCompositorVsyncScheduler = aObserver; + mCompositorVsyncScheduler->SetDisplay(mDisplayEnabled); +} +#endif diff --git a/widget/gonk/nsScreenManagerGonk.h b/widget/gonk/nsScreenManagerGonk.h index 625df8a8a6..23a2f24b41 100644 --- a/widget/gonk/nsScreenManagerGonk.h +++ b/widget/gonk/nsScreenManagerGonk.h @@ -21,6 +21,7 @@ #include "hardware/hwcomposer.h" #include "libdisplay/GonkDisplay.h" +#include "mozilla/Atomics.h" #include "mozilla/Hal.h" #include "mozilla/Mutex.h" #include "nsBaseScreen.h" @@ -42,6 +43,10 @@ namespace mozilla { namespace gl { class GLContext; } +namespace layers { +class CompositorVsyncScheduler; +class CompositorParent; +} } enum class NotifyDisplayChangedEvent : int8_t { @@ -54,6 +59,8 @@ class nsScreenGonk : public nsBaseScreen typedef mozilla::hal::ScreenConfiguration ScreenConfiguration; typedef mozilla::GonkDisplay GonkDisplay; typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect; + typedef mozilla::layers::CompositorParent CompositorParent; + typedef mozilla::gfx::DrawTarget DrawTarget; public: nsScreenGonk(uint32_t aId, @@ -82,8 +89,11 @@ public: ScreenConfiguration GetConfiguration(); bool IsPrimaryScreen(); - ANativeWindowBuffer* DequeueBuffer(); - bool QueueBuffer(ANativeWindowBuffer* buf); + already_AddRefed StartRemoteDrawing(); + void EndRemoteDrawing(); + + nsresult MakeSnapshot(ANativeWindowBuffer* aBuffer); + void SetCompositorParent(CompositorParent* aCompositorParent); #if ANDROID_VERSION >= 17 android::DisplaySurface* GetDisplaySurface(); @@ -122,6 +132,9 @@ public: nsWindow* GetMirroringWidget(); // Primary screen only protected: + ANativeWindowBuffer* DequeueBuffer(); + bool QueueBuffer(ANativeWindowBuffer* buf); + uint32_t mId; NotifyDisplayChangedEvent mEventVisibility; int32_t mColorDepth; @@ -138,6 +151,7 @@ protected: #endif bool mIsMirroring; // Non-primary screen only RefPtr mMirroringScreen; // Primary screen only + mozilla::Atomic mCompositorParent; // Accessed and updated only on compositor thread GonkDisplay::DisplayType mDisplayType; @@ -145,6 +159,26 @@ protected: hwc_surface_t mEGLSurface; RefPtr mGLContext; RefPtr mMirroringWidget; // Primary screen only + + // If we're using a BasicCompositor, these fields are temporarily + // set during frame composition. They wrap the hardware + // framebuffer. + RefPtr mFramebufferTarget; + ANativeWindowBuffer* mFramebuffer; + /** + * Points to a mapped gralloc buffer between calls to lock and unlock. + * Should be null outside of the lock-unlock pair. + */ + uint8_t* mMappedBuffer; + // If we're using a BasicCompositor, this is our window back + // buffer. The gralloc framebuffer driver expects us to draw the + // entire framebuffer on every frame, but gecko expects the + // windowing system to be tracking buffer updates for invalidated + // regions. We get stuck holding that bag. + // + // Only accessed on the compositor thread, except during + // destruction. + RefPtr mBackBuffer; }; class nsScreenManagerGonk final : public nsIScreenManager @@ -170,6 +204,10 @@ public: nsresult RemoveScreen(GonkDisplay::DisplayType aDisplayType); +#if ANDROID_VERSION >= 19 + void SetCompositorVsyncScheduler(mozilla::layers::CompositorVsyncScheduler* aObserver); +#endif + protected: ~nsScreenManagerGonk(); void VsyncControl(bool aEnabled); @@ -180,6 +218,11 @@ protected: nsTArray> mScreens; RefPtr mScreenOnEvent; RefPtr mScreenOffEvent; + +#if ANDROID_VERSION >= 19 + bool mDisplayEnabled; + RefPtr mCompositorVsyncScheduler; +#endif }; #endif /* nsScreenManagerGonk_h___ */ diff --git a/widget/gonk/nsWidgetFactory.cpp b/widget/gonk/nsWidgetFactory.cpp index ddd116f566..1c75255445 100644 --- a/widget/gonk/nsWidgetFactory.cpp +++ b/widget/gonk/nsWidgetFactory.cpp @@ -50,7 +50,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init) } NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindow) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsScreenManagerGonk) +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsScreenManagerGonk, nsScreenManagerGonk::GetInstance) NS_GENERIC_FACTORY_CONSTRUCTOR(PuppetScreenManager) NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceGonk, nsIdleServiceGonk::GetInstance) diff --git a/widget/gonk/nsWindow.cpp b/widget/gonk/nsWindow.cpp index e114096ca1..13ed137f8a 100644 --- a/widget/gonk/nsWindow.cpp +++ b/widget/gonk/nsWindow.cpp @@ -25,7 +25,6 @@ #include "mozilla/ClearOnShutdown.h" #include "gfxContext.h" #include "gfxPlatform.h" -#include "gfxUtils.h" #include "GLContextProvider.h" #include "GLContext.h" #include "GLContextEGL.h" @@ -66,8 +65,6 @@ static nsWindow *gFocusedWindow = nullptr; NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget) nsWindow::nsWindow() - : mFramebuffer(nullptr) - , mMappedBuffer(nullptr) { RefPtr screenManager = nsScreenManagerGonk::GetInstance(); screenManager->Initialize(); @@ -623,116 +620,17 @@ nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen*) return NS_OK; } -static gralloc_module_t const* -gralloc_module() -{ - hw_module_t const *module; - if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module)) { - return nullptr; - } - return reinterpret_cast(module); -} - -static SurfaceFormat -HalFormatToSurfaceFormat(int aHalFormat) -{ - switch (aHalFormat) { - case HAL_PIXEL_FORMAT_RGBA_8888: - // Needs RB swap - return SurfaceFormat::B8G8R8A8; - case HAL_PIXEL_FORMAT_RGBX_8888: - // Needs RB swap - return SurfaceFormat::B8G8R8X8; - case HAL_PIXEL_FORMAT_BGRA_8888: - return SurfaceFormat::B8G8R8A8; - case HAL_PIXEL_FORMAT_RGB_565: - return SurfaceFormat::R5G6B5_UINT16; - default: - MOZ_CRASH("Unhandled HAL pixel format"); - return SurfaceFormat::UNKNOWN; // not reached - } -} - -static bool -NeedsRBSwap(int aHalFormat) -{ - switch (aHalFormat) { - case HAL_PIXEL_FORMAT_RGBA_8888: - return true; - case HAL_PIXEL_FORMAT_RGBX_8888: - return true; - case HAL_PIXEL_FORMAT_BGRA_8888: - return false; - case HAL_PIXEL_FORMAT_RGB_565: - return false; - default: - MOZ_CRASH("Unhandled HAL pixel format"); - return false; // not reached - } -} - - already_AddRefed nsWindow::StartRemoteDrawing() { - mFramebuffer = mScreen->DequeueBuffer(); - int width = mFramebuffer->width, height = mFramebuffer->height; - if (gralloc_module()->lock(gralloc_module(), mFramebuffer->handle, - GRALLOC_USAGE_SW_READ_NEVER | - GRALLOC_USAGE_SW_WRITE_OFTEN | - GRALLOC_USAGE_HW_FB, - 0, 0, width, height, - reinterpret_cast(&mMappedBuffer))) { - EndRemoteDrawing(); - return nullptr; - } - SurfaceFormat format = HalFormatToSurfaceFormat(mScreen->GetSurfaceFormat()); - mFramebufferTarget = Factory::CreateDrawTargetForData( - BackendType::CAIRO, - mMappedBuffer, - IntSize(width, height), - mFramebuffer->stride * gfx::BytesPerPixel(format), - format); - if (!mFramebufferTarget) { - MOZ_CRASH("nsWindow::StartRemoteDrawing failed in CreateDrawTargetForData"); - } - if (!mBackBuffer || - mBackBuffer->GetSize() != mFramebufferTarget->GetSize() || - mBackBuffer->GetFormat() != mFramebufferTarget->GetFormat()) { - mBackBuffer = mFramebufferTarget->CreateSimilarDrawTarget( - mFramebufferTarget->GetSize(), mFramebufferTarget->GetFormat()); - } - RefPtr buffer(mBackBuffer); + RefPtr buffer = mScreen->StartRemoteDrawing(); return buffer.forget(); } void nsWindow::EndRemoteDrawing() { - if (mFramebufferTarget && mFramebuffer) { - IntSize size = mFramebufferTarget->GetSize(); - Rect rect(0, 0, size.width, size.height); - RefPtr source = mBackBuffer->Snapshot(); - mFramebufferTarget->DrawSurface(source, rect, rect); - - // Convert from BGR to RGB - // XXX this is a temporary solution. It consumes extra cpu cycles, - // it should not be used on product device. - if (NeedsRBSwap(mScreen->GetSurfaceFormat())) { - LOGE("Very slow composition path, it should not be used on product!!!"); - SurfaceFormat format = HalFormatToSurfaceFormat(mScreen->GetSurfaceFormat()); - gfxUtils::ConvertBGRAtoRGBA( - mMappedBuffer, - mFramebuffer->stride * mFramebuffer->height * gfx::BytesPerPixel(format)); - mMappedBuffer = nullptr; - gralloc_module()->unlock(gralloc_module(), mFramebuffer->handle); - } - } - if (mFramebuffer) { - mScreen->QueueBuffer(mFramebuffer); - } - mFramebuffer = nullptr; - mFramebufferTarget = nullptr; + mScreen->EndRemoteDrawing(); } float @@ -789,8 +687,11 @@ nsWindow::GetLayerManager(PLayerTransactionChild* aShadowManager, } CreateCompositor(); - if (mCompositorParent && mScreen->IsPrimaryScreen()) { - mComposer2D->SetCompositorParent(mCompositorParent); + if (mCompositorParent) { + mScreen->SetCompositorParent(mCompositorParent); + if (mScreen->IsPrimaryScreen()) { + mComposer2D->SetCompositorParent(mCompositorParent); + } } MOZ_ASSERT(mLayerManager); return mLayerManager; @@ -799,13 +700,22 @@ nsWindow::GetLayerManager(PLayerTransactionChild* aShadowManager, void nsWindow::DestroyCompositor() { - if (mCompositorParent && mScreen->IsPrimaryScreen()) { - // Unset CompositorParent - mComposer2D->SetCompositorParent(nullptr); + if (mCompositorParent) { + mScreen->SetCompositorParent(nullptr); + if (mScreen->IsPrimaryScreen()) { + // Unset CompositorParent + mComposer2D->SetCompositorParent(nullptr); + } } nsBaseWidget::DestroyCompositor(); } +CompositorParent* +nsWindow::NewCompositorParent(int aSurfaceWidth, int aSurfaceHeight) +{ + return new CompositorParent(this, true, aSurfaceWidth, aSurfaceHeight); +} + void nsWindow::BringToTop() { diff --git a/widget/gonk/nsWindow.h b/widget/gonk/nsWindow.h index b7ef11d4d1..e27a50cc4c 100644 --- a/widget/gonk/nsWindow.h +++ b/widget/gonk/nsWindow.h @@ -39,7 +39,6 @@ class nsWindow : public nsBaseWidget { public: nsWindow(); - virtual ~nsWindow(); NS_DECL_ISUPPORTS_INHERITED @@ -117,6 +116,8 @@ public: bool* aAllowRetaining = nullptr); virtual void DestroyCompositor(); + virtual CompositorParent* NewCompositorParent(int aSurfaceWidth, int aSurfaceHeight); + NS_IMETHOD_(void) SetInputContext(const InputContext& aContext, const InputContextAction& aAction); NS_IMETHOD_(InputContext) GetInputContext(); @@ -137,25 +138,8 @@ protected: bool mVisible; InputContext mInputContext; nsCOMPtr mIdleService; - // If we're using a BasicCompositor, these fields are temporarily - // set during frame composition. They wrap the hardware - // framebuffer. - RefPtr mFramebufferTarget; - ANativeWindowBuffer* mFramebuffer; - /** - * Points to a mapped gralloc buffer between calls to lock and unlock. - * Should be null outside of the lock-unlock pair. - */ - uint8_t* mMappedBuffer; - // If we're using a BasicCompositor, this is our window back - // buffer. The gralloc framebuffer driver expects us to draw the - // entire framebuffer on every frame, but gecko expects the - // windowing system to be tracking buffer updates for invalidated - // regions. We get stuck holding that bag. - // - // Only accessed on the compositor thread, except during - // destruction. - RefPtr mBackBuffer; + + virtual ~nsWindow(); void BringToTop(); diff --git a/widget/gtk/gtk3drawing.c b/widget/gtk/gtk3drawing.c index 089ae36f6d..765084fde0 100644 --- a/widget/gtk/gtk3drawing.c +++ b/widget/gtk/gtk3drawing.c @@ -1123,7 +1123,21 @@ moz_gtk_scrollbar_button_paint(cairo_t *cr, GdkRectangle* rect, } else { gtk_style_context_add_class(style, GTK_STYLE_CLASS_TOP); } - + + /* Scrollbar button has to be inset by trough_border because its DOM element + * is filling width of vertical scrollbar's track (or height in case + * of horizontal scrollbars). */ + + MozGtkScrollbarMetrics metrics; + moz_gtk_get_scrollbar_metrics(&metrics); + if (flags & MOZ_GTK_STEPPER_VERTICAL) { + rect->x += metrics.trough_border; + rect->width = metrics.slider_width; + } else { + rect->y += metrics.trough_border; + rect->height = metrics.slider_width; + } + gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height); gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height); diff --git a/widget/gtk/mozgtk/mozgtk.c b/widget/gtk/mozgtk/mozgtk.c index 6331d99767..8c983cdb14 100644 --- a/widget/gtk/mozgtk/mozgtk.c +++ b/widget/gtk/mozgtk/mozgtk.c @@ -255,6 +255,7 @@ STUB(gtk_im_context_simple_new) STUB(gtk_im_multicontext_get_type) STUB(gtk_im_multicontext_new) STUB(gtk_info_bar_get_type) +STUB(gtk_info_bar_get_content_area) STUB(gtk_info_bar_new) STUB(gtk_init) STUB(gtk_invisible_new) @@ -302,6 +303,7 @@ STUB(gtk_paper_size_get_display_name) STUB(gtk_paper_size_get_height) STUB(gtk_paper_size_get_name) STUB(gtk_paper_size_get_width) +STUB(gtk_paper_size_is_custom) STUB(gtk_paper_size_is_equal) STUB(gtk_paper_size_new) STUB(gtk_paper_size_new_custom) @@ -318,6 +320,7 @@ STUB(gtk_print_job_send) STUB(gtk_print_job_set_source_file) STUB(gtk_print_run_page_setup_dialog) STUB(gtk_print_settings_copy) +STUB(gtk_print_settings_foreach) STUB(gtk_print_settings_get) STUB(gtk_print_settings_get_duplex) STUB(gtk_print_settings_get_n_copies) @@ -383,6 +386,7 @@ STUB(gtk_settings_get_for_screen) STUB(gtk_socket_add_id) STUB(gtk_socket_get_id) STUB(gtk_socket_get_type) +STUB(gtk_socket_get_plug_window) STUB(gtk_socket_new) STUB(gtk_spin_button_new) STUB(gtk_statusbar_new) diff --git a/widget/gtk/nsLookAndFeel.cpp b/widget/gtk/nsLookAndFeel.cpp index e57932ddbc..134ec6d2cf 100644 --- a/widget/gtk/nsLookAndFeel.cpp +++ b/widget/gtk/nsLookAndFeel.cpp @@ -1208,15 +1208,15 @@ nsLookAndFeel::Init() gtk_widget_path_free(path); // GtkInfoBar - path = gtk_widget_path_new(); - gtk_widget_path_append_type(path, GTK_TYPE_WINDOW); - gtk_widget_path_append_type(path, GTK_TYPE_INFO_BAR); - style = create_context(path); + GtkWidget* infoBar = gtk_info_bar_new(); + GtkWidget* infoBarContent = gtk_info_bar_get_content_area(GTK_INFO_BAR(infoBar)); + GtkWidget* infoBarLabel = gtk_label_new(nullptr); + gtk_container_add(GTK_CONTAINER(parent), infoBar); + gtk_container_add(GTK_CONTAINER(infoBarContent), infoBarLabel); + style = gtk_widget_get_style_context(infoBarLabel); gtk_style_context_add_class(style, GTK_STYLE_CLASS_INFO); gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color); sInfoBarText = GDK_RGBA_TO_NS_RGBA(color); - g_object_unref(style); - gtk_widget_path_free(path); #endif // Some themes have a unified menu bar, and support window dragging on it gboolean supports_menubar_drag = FALSE; diff --git a/widget/gtk/nsNativeThemeGTK.cpp b/widget/gtk/nsNativeThemeGTK.cpp index 5ee8e67ac3..ce6034e5bd 100644 --- a/widget/gtk/nsNativeThemeGTK.cpp +++ b/widget/gtk/nsNativeThemeGTK.cpp @@ -1028,6 +1028,7 @@ nsNativeThemeGTK::GetExtraSizeForWidget(nsIFrame* aFrame, uint8_t aWidgetType, aExtra->left = left; break; } + return false; } case NS_THEME_FOCUS_OUTLINE: { @@ -1054,6 +1055,7 @@ nsNativeThemeGTK::GetExtraSizeForWidget(nsIFrame* aFrame, uint8_t aWidgetType, } else { aExtra->bottom = extra; } + return false; } default: return false; @@ -1110,7 +1112,7 @@ nsNativeThemeGTK::DrawWidgetBackground(nsRenderingContext* aContext, nsIntRect overflowRect(widgetRect); nsIntMargin extraSize; if (GetExtraSizeForWidget(aFrame, aWidgetType, &extraSize)) { - overflowRect.Inflate(gfx::ToIntMargin(extraSize)); + overflowRect.Inflate(extraSize); } // This is the rectangle that will actually be drawn, in gdk pixels @@ -1219,11 +1221,21 @@ nsNativeThemeGTK::GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame, aResult->top = aResult->left = aResult->right = aResult->bottom = 0; switch (aWidgetType) { case NS_THEME_SCROLLBAR_VERTICAL: - case NS_THEME_SCROLLBAR_HORIZONTAL: + case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL: { MozGtkScrollbarMetrics metrics; moz_gtk_get_scrollbar_metrics(&metrics); - aResult->top = aResult->left = aResult->right = aResult->bottom = metrics.trough_border; + /* Top and bottom border for whole vertical scrollbar, top and bottom + * border for horizontal track - to correctly position thumb element */ + aResult->top = aResult->bottom = metrics.trough_border; + } + break; + case NS_THEME_SCROLLBAR_HORIZONTAL: + case NS_THEME_SCROLLBAR_TRACK_VERTICAL: + { + MozGtkScrollbarMetrics metrics; + moz_gtk_get_scrollbar_metrics(&metrics); + aResult->left = aResult->right = metrics.trough_border; } break; case NS_THEME_TOOLBOX: @@ -1261,6 +1273,7 @@ nsNativeThemeGTK::GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame, // will need to fall through and use the default case as before. if (IsRegularMenuItem(aFrame)) break; + MOZ_FALLTHROUGH; default: { GtkThemeWidgetType gtkWidgetType; @@ -1272,6 +1285,12 @@ nsNativeThemeGTK::GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame, } } } + + gint scale = nsScreenGtk::GetGtkMonitorScaleFactor(); + aResult->top *= scale; + aResult->right *= scale; + aResult->bottom *= scale; + aResult->left *= scale; return NS_OK; } @@ -1696,7 +1715,7 @@ nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext, if (aFrame && aFrame->GetWritingMode().IsVertical()) { return false; } - // fall through + MOZ_FALLTHROUGH; case NS_THEME_BUTTON: case NS_THEME_BUTTON_FOCUS: @@ -1749,6 +1768,8 @@ nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext, case NS_THEME_SCROLLBAR_BUTTON_RIGHT: case NS_THEME_SCROLLBAR_HORIZONTAL: case NS_THEME_SCROLLBAR_VERTICAL: + case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL: + case NS_THEME_SCROLLBAR_TRACK_VERTICAL: case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL: case NS_THEME_SCROLLBAR_THUMB_VERTICAL: case NS_THEME_NUMBER_INPUT: diff --git a/widget/gtk/nsPrintOptionsGTK.h b/widget/gtk/nsPrintOptionsGTK.h index 37f3783960..d558bb8001 100644 --- a/widget/gtk/nsPrintOptionsGTK.h +++ b/widget/gtk/nsPrintOptionsGTK.h @@ -13,7 +13,7 @@ namespace mozilla { namespace embedding { - struct PrintData; + class PrintData; } // namespace embedding } // namespace mozilla diff --git a/widget/gtk/nsPrintSettingsGTK.cpp b/widget/gtk/nsPrintSettingsGTK.cpp index cd86df6340..538b833e73 100644 --- a/widget/gtk/nsPrintSettingsGTK.cpp +++ b/widget/gtk/nsPrintSettingsGTK.cpp @@ -51,15 +51,11 @@ nsPrintSettingsGTK::nsPrintSettingsGTK() : // The aim here is to set up the objects enough that silent printing works well. // These will be replaced anyway if the print dialog is used. mPrintSettings = gtk_print_settings_new(); - mPageSetup = gtk_page_setup_new(); - InitUnwriteableMargin(); + GtkPageSetup* pageSetup = gtk_page_setup_new(); + SetGtkPageSetup(pageSetup); + g_object_unref(pageSetup); SetOutputFormat(nsIPrintSettings::kOutputFormatNative); - - GtkPaperSize* defaultPaperSize = gtk_paper_size_new(nullptr); - mPaperSize = moz_gtk_paper_size_copy_to_new_custom(defaultPaperSize); - gtk_paper_size_free(defaultPaperSize); - SaveNewPageSize(); } /** --------------------------------------------------- @@ -78,7 +74,6 @@ nsPrintSettingsGTK::~nsPrintSettingsGTK() g_object_unref(mGTKPrinter); mGTKPrinter = nullptr; } - gtk_paper_size_free(mPaperSize); } /** --------------------------------------------------- @@ -161,15 +156,16 @@ nsPrintSettingsGTK::SetGtkPageSetup(GtkPageSetup *aPageSetup) mPageSetup = (GtkPageSetup*) g_object_ref(aPageSetup); InitUnwriteableMargin(); - // We make a custom copy of the GtkPaperSize so it can be mutable. If a - // GtkPaperSize wasn't made as custom, its properties are immutable. - GtkPaperSize* newPaperSize = gtk_page_setup_get_paper_size(aPageSetup); - if (newPaperSize) { // Yes, this can be null - gtk_paper_size_free(mPaperSize); - mPaperSize = moz_gtk_paper_size_copy_to_new_custom(newPaperSize); + // If the paper size is not custom, then we make a custom copy of the + // GtkPaperSize, so it can be mutable. If a GtkPaperSize wasn't made as + // custom, its properties are immutable. + GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(aPageSetup); + if (!gtk_paper_size_is_custom(paperSize)) { + GtkPaperSize* customPaperSize = + moz_gtk_paper_size_copy_to_new_custom(paperSize); + gtk_page_setup_set_paper_size(mPageSetup, customPaperSize); + gtk_paper_size_free(customPaperSize); } - // If newPaperSize was not null, we must update our twin too (GtkPrintSettings). - // If newPaperSize was null, we must set this object to use mPaperSize. SaveNewPageSize(); } @@ -183,12 +179,18 @@ nsPrintSettingsGTK::SetGtkPrintSettings(GtkPrintSettings *aPrintSettings) mPrintSettings = (GtkPrintSettings*) g_object_ref(aPrintSettings); - GtkPaperSize* newPaperSize = gtk_print_settings_get_paper_size(aPrintSettings); - if (newPaperSize) { - gtk_paper_size_free(mPaperSize); - mPaperSize = moz_gtk_paper_size_copy_to_new_custom(newPaperSize); + GtkPaperSize* paperSize = gtk_print_settings_get_paper_size(aPrintSettings); + if (paperSize) { + GtkPaperSize* customPaperSize = + moz_gtk_paper_size_copy_to_new_custom(paperSize); + gtk_paper_size_free(paperSize); + gtk_page_setup_set_paper_size(mPageSetup, customPaperSize); + gtk_paper_size_free(customPaperSize); + } else { + // paperSize was null, and so we add the paper size in the GtkPageSetup to + // the settings. + SaveNewPageSize(); } - SaveNewPageSize(); } /** --------------------------------------------------- @@ -440,7 +442,7 @@ nsPrintSettingsGTK::GetPrinterName(char16_t * *aPrinter) return NS_OK; } } - *aPrinter = ToNewUnicode(nsDependentCString(gtkPrintName)); + *aPrinter = UTF8ToNewUnicode(nsDependentCString(gtkPrintName)); return NS_OK; } @@ -500,7 +502,9 @@ NS_IMETHODIMP nsPrintSettingsGTK::GetPaperName(char16_t * *aPaperName) { NS_ENSURE_ARG_POINTER(aPaperName); - *aPaperName = ToNewUnicode(NS_ConvertUTF8toUTF16(gtk_paper_size_get_name(mPaperSize))); + const gchar* name = + gtk_paper_size_get_name(gtk_page_setup_get_paper_size(mPageSetup)); + *aPaperName = ToNewUnicode(NS_ConvertUTF8toUTF16(name)); return NS_OK; } NS_IMETHODIMP @@ -514,19 +518,20 @@ nsPrintSettingsGTK::SetPaperName(const char16_t * aPaperName) else if (gtkPaperName.EqualsIgnoreCase("legal")) gtkPaperName.AssignLiteral(GTK_PAPER_NAME_LEGAL); + GtkPaperSize* oldPaperSize = gtk_page_setup_get_paper_size(mPageSetup); + gdouble width = gtk_paper_size_get_width(oldPaperSize, GTK_UNIT_INCH); + gdouble height = gtk_paper_size_get_height(oldPaperSize, GTK_UNIT_INCH); + // Try to get the display name from the name so our paper size fits in the Page Setup dialog. GtkPaperSize* paperSize = gtk_paper_size_new(gtkPaperName.get()); - char* displayName = strdup(gtk_paper_size_get_display_name(paperSize)); + GtkPaperSize* customPaperSize = + gtk_paper_size_new_custom(gtkPaperName.get(), + gtk_paper_size_get_display_name(paperSize), + width, height, GTK_UNIT_INCH); gtk_paper_size_free(paperSize); - paperSize = gtk_paper_size_new_custom(gtkPaperName.get(), displayName, - gtk_paper_size_get_width(mPaperSize, GTK_UNIT_INCH), - gtk_paper_size_get_height(mPaperSize, GTK_UNIT_INCH), - GTK_UNIT_INCH); - - free(displayName); - gtk_paper_size_free(mPaperSize); - mPaperSize = paperSize; + gtk_page_setup_set_paper_size(mPageSetup, customPaperSize); + gtk_paper_size_free(customPaperSize); SaveNewPageSize(); return NS_OK; } @@ -543,8 +548,8 @@ nsPrintSettingsGTK::GetGTKUnit(int16_t aGeckoUnit) void nsPrintSettingsGTK::SaveNewPageSize() { - gtk_print_settings_set_paper_size(mPrintSettings, mPaperSize); - gtk_page_setup_set_paper_size(mPageSetup, mPaperSize); + gtk_print_settings_set_paper_size(mPrintSettings, + gtk_page_setup_get_paper_size(mPageSetup)); } void @@ -625,15 +630,18 @@ NS_IMETHODIMP nsPrintSettingsGTK::GetPaperWidth(double *aPaperWidth) { NS_ENSURE_ARG_POINTER(aPaperWidth); - *aPaperWidth = gtk_paper_size_get_width(mPaperSize, GetGTKUnit(mPaperSizeUnit)); + GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup); + *aPaperWidth = + gtk_paper_size_get_width(paperSize, GetGTKUnit(mPaperSizeUnit)); return NS_OK; } NS_IMETHODIMP nsPrintSettingsGTK::SetPaperWidth(double aPaperWidth) { - gtk_paper_size_set_size(mPaperSize, + GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup); + gtk_paper_size_set_size(paperSize, aPaperWidth, - gtk_paper_size_get_height(mPaperSize, GetGTKUnit(mPaperSizeUnit)), + gtk_paper_size_get_height(paperSize, GetGTKUnit(mPaperSizeUnit)), GetGTKUnit(mPaperSizeUnit)); SaveNewPageSize(); return NS_OK; @@ -643,14 +651,17 @@ NS_IMETHODIMP nsPrintSettingsGTK::GetPaperHeight(double *aPaperHeight) { NS_ENSURE_ARG_POINTER(aPaperHeight); - *aPaperHeight = gtk_paper_size_get_height(mPaperSize, GetGTKUnit(mPaperSizeUnit)); + GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup); + *aPaperHeight = + gtk_paper_size_get_height(paperSize, GetGTKUnit(mPaperSizeUnit)); return NS_OK; } NS_IMETHODIMP nsPrintSettingsGTK::SetPaperHeight(double aPaperHeight) { - gtk_paper_size_set_size(mPaperSize, - gtk_paper_size_get_width(mPaperSize, GetGTKUnit(mPaperSizeUnit)), + GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup); + gtk_paper_size_set_size(paperSize, + gtk_paper_size_get_width(paperSize, GetGTKUnit(mPaperSizeUnit)), aPaperHeight, GetGTKUnit(mPaperSizeUnit)); SaveNewPageSize(); @@ -662,9 +673,10 @@ nsPrintSettingsGTK::SetPaperSizeUnit(int16_t aPaperSizeUnit) { // Convert units internally. e.g. they might have set the values while we're still in mm but // they change to inch just afterwards, expecting that their sizes are in inches. - gtk_paper_size_set_size(mPaperSize, - gtk_paper_size_get_width(mPaperSize, GetGTKUnit(mPaperSizeUnit)), - gtk_paper_size_get_height(mPaperSize, GetGTKUnit(mPaperSizeUnit)), + GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup); + gtk_paper_size_set_size(paperSize, + gtk_paper_size_get_width(paperSize, GetGTKUnit(mPaperSizeUnit)), + gtk_paper_size_get_height(paperSize, GetGTKUnit(mPaperSizeUnit)), GetGTKUnit(aPaperSizeUnit)); SaveNewPageSize(); @@ -675,8 +687,9 @@ nsPrintSettingsGTK::SetPaperSizeUnit(int16_t aPaperSizeUnit) NS_IMETHODIMP nsPrintSettingsGTK::GetEffectivePageSize(double *aWidth, double *aHeight) { - *aWidth = NS_INCHES_TO_INT_TWIPS(gtk_paper_size_get_width(mPaperSize, GTK_UNIT_INCH)); - *aHeight = NS_INCHES_TO_INT_TWIPS(gtk_paper_size_get_height(mPaperSize, GTK_UNIT_INCH)); + GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup); + *aWidth = NS_INCHES_TO_INT_TWIPS(gtk_paper_size_get_width(paperSize, GTK_UNIT_INCH)); + *aHeight = NS_INCHES_TO_INT_TWIPS(gtk_paper_size_get_height(paperSize, GTK_UNIT_INCH)); GtkPageOrientation gtkOrient = gtk_page_setup_get_orientation(mPageSetup); diff --git a/widget/gtk/nsPrintSettingsGTK.h b/widget/gtk/nsPrintSettingsGTK.h index 8c45df0564..51712f90b8 100644 --- a/widget/gtk/nsPrintSettingsGTK.h +++ b/widget/gtk/nsPrintSettingsGTK.h @@ -133,13 +133,12 @@ protected: /** * On construction: - * - mPrintSettings, mPageSetup and mPaperSize are just new objects with defaults determined by GTK. + * - mPrintSettings and mPageSetup are just new objects with defaults determined by GTK. * - mGTKPrinter is nullptr!!! Remember to be careful when accessing this property. */ GtkPageSetup* mPageSetup; GtkPrintSettings* mPrintSettings; GtkPrinter* mGTKPrinter; - GtkPaperSize* mPaperSize; bool mPrintSelectionOnly; }; diff --git a/xpcom/ds/Tokenizer.cpp b/xpcom/ds/Tokenizer.cpp index 81d6490e3b..6fa9ff56a5 100644 --- a/xpcom/ds/Tokenizer.cpp +++ b/xpcom/ds/Tokenizer.cpp @@ -260,12 +260,12 @@ Tokenizer::Parse(Token& aToken) const state = PARSE_WORD; } else if (IsNumber(*next)) { state = PARSE_INTEGER; + } else if (strchr(mWhitespaces, *next)) { // not UTF-8 friendly? + state = PARSE_WS; } else if (*next == '\r') { state = PARSE_CRLF; } else if (*next == '\n') { state = PARSE_LF; - } else if (strchr(mWhitespaces, *next)) { // not UTF-8 friendly? - state = PARSE_WS; } else { state = PARSE_CHAR; } diff --git a/xpcom/ds/nsAtomTable.cpp b/xpcom/ds/nsAtomTable.cpp index 366da496b2..2e92368f06 100644 --- a/xpcom/ds/nsAtomTable.cpp +++ b/xpcom/ds/nsAtomTable.cpp @@ -26,8 +26,12 @@ using namespace mozilla; -#if defined(__clang__) || defined(__GNUC__) +#if defined(__clang__) # pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +#elif MOZ_IS_GCC +# if MOZ_GCC_VERSION_AT_LEAST(4, 7, 0) +# pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +# endif #endif /** diff --git a/xpcom/tests/gtest/TestTokenizer.cpp b/xpcom/tests/gtest/TestTokenizer.cpp index 55bab6e4bd..3040400200 100644 --- a/xpcom/tests/gtest/TestTokenizer.cpp +++ b/xpcom/tests/gtest/TestTokenizer.cpp @@ -526,6 +526,21 @@ TEST(Tokenizer, SkipWhites) EXPECT_TRUE(p.CheckEOF()); } +TEST(Tokenizer, SkipCustomWhites) +{ + Tokenizer p("Text1 \n\r\t.Text2 \n\r\t.", " \n\r\t."); + + EXPECT_TRUE(p.CheckWord("Text1")); + p.SkipWhites(); + EXPECT_TRUE(p.CheckWord("Text2")); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckEOF()); +} + TEST(Tokenizer, IntegerReading) { #define INT_6_BITS 64U