From 56aad8a83e06f601f81bc9998b3d0235c8ab7c38 Mon Sep 17 00:00:00 2001 From: Roy Tam Date: Thu, 25 Jul 2019 21:33:11 +0800 Subject: [PATCH] import change from rmottola/Arctic-Fox: - Bug 1149987 - Part 2: Make ErrorResult unassignable; r=bzbarsky (32661559b) - Bug 1149987 - Part 3: Give ErrorResult a move constructor and a move assignment operator; (27f4c6125) - Bug 1149987 - Part 4: Do not attempt to delete ErrorResult::mMessage when deserializing the object from IPDL; r=bzbarsky (0f9dcc603) - Bug 1110485 P0 Add an ErrorResult constructor that takes nsresult. (72a779666) - Bug 1110485 P1 Refactor Cache IPC requests to use a separate actor. (a7e4c1959) - Bug 1127914 - Part 1 - Duplicate keyed histograms for double submission. (78673277f) - Bug 1127914 - Part 2 - Duplicate normal histograms for double submission. (55c302057) - Bug 1127914 - Part 3 - Submit duplicate histogram data for 'non-classic' telemetry sessions. r=vladan (bb3e49c43) - Bug 1120362 - Part 1 - Enable snapshotting and clearing subsession histograms. (14378a6e5) - Bug 1120362 - Part 2 - Enable snapshotting and clearing keyed subsession histograms. r=vladan (c0e0bfb3e) - partial apply of Bug 1119281 - Fix missing telemetry client id (ae0dc0194) - Bug 1122047 - Part 1 - Sketch out Telemetry environment module. (0419391b0) - Bug 1122047 - Part 2 - Make TelemetryPing shutdown properly on delayed initialization (0102cef09) - Bug 1122061 - Give TelemetryPing a common API for sending pings. (999cb825d) - Bug 1122061 - Move TelemetrySession tests out of test_telemetryPing.js. (2d5b61de1) - Bug 1120362 - Part 3 - Reset subsession histograms on telemetry payload collections. r=vladan (0d3f04df1) - Bug 1120362 - Part 4 - Start new telemetry subsessions on local midnight. r=vladan (93eb9ca21) - Bug 1120363 - Break up Telemetry sessions on environment changes. (a7c8d70c7) - Bug 1122052 - Remove duplicated data from TelemetrySession. (bb905d602) - Bug 1122050 - Remove persona and experiment data from TelemetrySession. (40ca59a9e) - Bug 1134268 - Part 1 - Fix and order Telemetry shutdown for TelemetryPing and TelemetrySession. r=yoric (30d0f0656) - Bug 1134268 - Part 2 - Fixup TelemetryEnvironment shutdown if the module wasnt initialized. r=vladan (ec2875fea) - Bug 1135076 - Missing histograms in childPayloads. r=vladan (9f317cf9d) - Bug 1134279 - Make TelemetryPing and TelemetrySession code use the "FHR enabled" & "Telemetry enabled" prefs properly. r=vladan (4050d7f24) - Bug 1128768: Part 1 - Modify IPC to allow retrieval of topmost routing id on the stack; (cd2e8a2f0) - Bug 1129249 - Add a "restyle" feature to profiler and split the style label in Cleopatra based on the restyleSource, r=dholbert,mstange (b37df94d1) - Bug 1150684: Remove XPCOM.h from IOInterposer.h (5b7e1cef3) - Bug 1093934 - Create a XPCOM library that can be used to support standalone WebRTC. (9ec8a819f) - Merge branch 'master' of https://github.com/rmottola/Arctic-Fox (d0f05eea4) - Bug 1128768: Part 2,3,4 - Refactor hang annotation code; (f5086aba9) (with xpcom/threads/ fixes for my tele-removed tree) - Bug 1128768: Part 5 - Update plugin code to retrieve SWF file for hang annotations; (774a47aec) - Bug 1110485 P2 Remove 'P' prefix from non-protocol IPC types in Cache API. r=baku (ea29a10cf) - Bug 1110485 P3 Move Fetch IPC PHeaderEntry type to Cache. Rename HeadesEntry. (9eba0aca0) - Bug 1110485 P4 Keep Cache Actors alive during async operations. (eb75f2316) - Bug 1110485 P5 Replace useless DBSchema class type with namespace. (159b902db) - Bug 1110485 P6 Remove useless cache::FileUtils type (1bdf00fc3) - Bug 1110485 P7 Rename DeleteCache() to DeleteCacheId() better distinguish it from CacheDelete(). (5199f9d6f) - Bug 1110485 P8 Correctly set the Feature on the stream control child actor. (c8673cb13) - Bug 1150691 Fix Cache API race with storage invalidation. (2723dff50) - Bug 1151892 Refactor Cache Manager Context usage to be more sane and fix shutdown assert. r=ehsan (ea96381cf) - Bug 1136331 - OdinMonkey: allow stdlib calls in heap expressions (2fc5e2bfd) - Bug 1141439 - Exit with an error code instead of falling through the REMOTE_NOT_FOUND code path when the X-remote returns an explicit command line handler error. (afcf9b1aa) - Bug 1135825: Add missing MOZ_OVERRIDE annotation in RTCIdentityProviderRegistrar.h (e8beec4e8) - (Bug 1135138 is not merged due to broken build) --- dom/bindings/BindingUtils.cpp | 57 +- dom/bindings/ErrorResult.h | 62 +- dom/cache/ActorChild.cpp | 2 + dom/cache/AutoUtils.cpp | 620 +++++++----- dom/cache/AutoUtils.h | 139 +-- dom/cache/Cache.cpp | 344 +------ dom/cache/Cache.h | 34 +- dom/cache/CacheChild.cpp | 139 +-- dom/cache/CacheChild.h | 43 +- dom/cache/CacheInitData.ipdlh | 24 - dom/cache/CacheOpChild.cpp | 248 +++++ dom/cache/CacheOpChild.h | 80 ++ dom/cache/CacheOpParent.cpp | 287 ++++++ dom/cache/CacheOpParent.h | 87 ++ dom/cache/CacheParent.cpp | 285 +----- dom/cache/CacheParent.h | 67 +- dom/cache/CachePushStreamChild.h | 11 +- dom/cache/CacheStorage.cpp | 367 ++----- dom/cache/CacheStorage.h | 58 +- dom/cache/CacheStorageChild.cpp | 109 +- dom/cache/CacheStorageChild.h | 37 +- dom/cache/CacheStorageParent.cpp | 402 +------- dom/cache/CacheStorageParent.h | 78 +- dom/cache/CacheStreamControlChild.cpp | 33 +- dom/cache/CacheStreamControlChild.h | 7 +- dom/cache/CacheStreamControlParent.cpp | 8 +- dom/cache/CacheStreamControlParent.h | 6 +- dom/cache/CacheTypes.ipdlh | 245 +++++ dom/cache/Context.cpp | 10 +- dom/cache/Context.h | 3 + dom/cache/DBAction.cpp | 7 +- dom/cache/DBAction.h | 1 - dom/cache/DBSchema.cpp | 260 ++--- dom/cache/DBSchema.h | 177 ++-- dom/cache/FetchPut.cpp | 70 +- dom/cache/FetchPut.h | 31 +- dom/cache/FileUtils.cpp | 51 +- dom/cache/FileUtils.h | 61 +- dom/cache/IPCUtils.h | 37 + dom/cache/Manager.cpp | 675 ++++++------- dom/cache/Manager.h | 135 ++- dom/cache/PCache.ipdl | 27 +- dom/cache/PCacheOp.ipdl | 29 + dom/cache/PCacheStorage.ipdl | 23 +- dom/cache/PCacheTypes.ipdlh | 96 -- dom/cache/PrincipalVerifier.cpp | 38 +- dom/cache/PrincipalVerifier.h | 14 +- dom/cache/ReadStream.cpp | 49 +- dom/cache/ReadStream.h | 15 +- dom/cache/SavedTypes.h | 6 +- dom/cache/StreamControl.cpp | 12 + dom/cache/StreamControl.h | 11 +- dom/cache/StreamUtils.cpp | 151 --- dom/cache/StreamUtils.h | 39 - dom/cache/TypeUtils.cpp | 79 +- dom/cache/TypeUtils.h | 41 +- dom/cache/Types.h | 4 +- dom/cache/moz.build | 10 +- dom/fetch/FetchIPCUtils.h | 51 - dom/fetch/InternalHeaders.cpp | 15 +- dom/fetch/InternalHeaders.h | 6 +- dom/fetch/PHeaders.ipdlh | 15 - dom/fetch/moz.build | 7 - .../webrtc/RTCIdentityProviderRegistrar.h | 2 +- dom/plugins/base/nsPluginPlayPreviewInfo.cpp | 17 +- dom/plugins/base/nsPluginPlayPreviewInfo.h | 12 + dom/plugins/ipc/PluginInstanceParent.cpp | 40 +- dom/plugins/ipc/PluginInstanceParent.h | 23 +- dom/plugins/ipc/PluginModuleParent.cpp | 178 +++- dom/plugins/ipc/PluginModuleParent.h | 16 +- ipc/glue/MessageChannel.cpp | 16 + ipc/glue/MessageChannel.h | 8 + js/src/asmjs/AsmJSValidate.cpp | 20 +- js/src/jit-test/tests/asm.js/testResize.js | 9 +- layout/base/RestyleTracker.cpp | 20 +- layout/base/RestyleTracker.h | 17 +- mfbt/Assertions.h | 4 +- mfbt/RefPtr.h | 1 + mfbt/Types.h | 2 +- .../passwordmgr/test/unit/test_telemetry.js | 6 +- toolkit/components/telemetry/Histograms.json | 6 + toolkit/components/telemetry/Telemetry.cpp | 406 ++++++-- toolkit/components/telemetry/Telemetry.h | 12 +- .../telemetry/TelemetryEnvironment.jsm | 143 +++ .../components/telemetry/TelemetryFile.jsm | 8 +- .../components/telemetry/TelemetryPing.jsm | 563 +++++++++-- .../components/telemetry/TelemetrySession.jsm | 793 +++++++++++---- .../components/telemetry/ThreadHangStats.h | 197 ++++ .../components/telemetry/docs/main-ping.rst | 4 +- toolkit/components/telemetry/moz.build | 1 + toolkit/components/telemetry/nsITelemetry.idl | 37 +- .../components/telemetry/tests/unit/head.js | 10 + .../tests/unit/test_TelemetryEnvironment.js | 72 ++ .../tests/unit/test_TelemetryPing.js | 207 +++- .../tests/unit/test_TelemetrySendOldPings.js | 164 +-- .../tests/unit/test_TelemetrySession.js | 953 ++++++++++++++++++ .../telemetry/tests/unit/test_nsITelemetry.js | 136 ++- .../telemetry/tests/unit/xpcshell.ini | 2 + .../components/terminator/nsTerminator.cpp | 4 +- .../xpcshell/test_TelemetryTimestamps.js | 2 +- .../test/browser/browser_bug557956.js | 7 +- toolkit/xre/nsAppRunner.cpp | 11 +- tools/profiler/GoannaProfiler.h | 6 +- tools/profiler/GoannaProfilerImpl.h | 26 + tools/profiler/moz.build | 103 ++ tools/profiler/platform.cpp | 7 + xpcom/base/nsDebugImpl.cpp | 8 +- xpcom/base/nsIMemoryReporter.idl | 2 +- xpcom/build/FileLocation.cpp | 32 +- xpcom/build/FileLocation.h | 8 + xpcom/build/IOInterposer.h | 1 - xpcom/build/LateWriteChecks.cpp | 1 + xpcom/build/MainThreadIOLogger.cpp | 1 + xpcom/build/ServiceList.h | 4 + xpcom/build/Services.cpp | 11 +- xpcom/components/ManifestParser.h | 2 + xpcom/components/nsCategoryManager.cpp | 6 + xpcom/components/nsComponentManager.cpp | 52 +- xpcom/components/nsComponentManager.h | 4 + xpcom/ds/nsObserverList.cpp | 2 + xpcom/ds/nsObserverService.cpp | 4 + xpcom/glue/BlockingResourceBase.cpp | 6 +- xpcom/glue/nsISupportsImpl.h | 14 +- xpcom/libxpcomrt/XPCOMRTInit.cpp | 190 ++++ xpcom/libxpcomrt/XPCOMRTInit.h | 15 + xpcom/libxpcomrt/XPCOMRTModule.inc | 8 + xpcom/libxpcomrt/XPCOMRTStubs.cpp | 47 + xpcom/libxpcomrt/docs/index.rst | 40 + xpcom/libxpcomrt/moz.build | 153 +++ xpcom/moz.build | 1 + xpcom/string/nsSubstring.cpp | 4 +- xpcom/string/nsTSubstring.cpp | 8 +- xpcom/threads/BackgroundHangMonitor.cpp | 33 + xpcom/threads/BackgroundHangMonitor.h | 16 + xpcom/threads/HangAnnotations.cpp | 266 +++++ xpcom/threads/HangAnnotations.h | 104 ++ xpcom/threads/HangMonitor.cpp | 12 - xpcom/threads/HangMonitor.h | 54 - xpcom/threads/LazyIdleThread.cpp | 4 + xpcom/threads/moz.build | 2 + xpcom/threads/nsThread.cpp | 27 +- xpcom/threads/nsTimerImpl.cpp | 2 + 142 files changed, 7623 insertions(+), 3927 deletions(-) create mode 100644 dom/cache/CacheOpChild.cpp create mode 100644 dom/cache/CacheOpChild.h create mode 100644 dom/cache/CacheOpParent.cpp create mode 100644 dom/cache/CacheOpParent.h create mode 100644 dom/cache/CacheTypes.ipdlh create mode 100644 dom/cache/PCacheOp.ipdl delete mode 100644 dom/cache/PCacheTypes.ipdlh delete mode 100644 dom/cache/StreamUtils.cpp delete mode 100644 dom/cache/StreamUtils.h delete mode 100644 dom/fetch/FetchIPCUtils.h delete mode 100644 dom/fetch/PHeaders.ipdlh create mode 100644 toolkit/components/telemetry/TelemetryEnvironment.jsm create mode 100644 toolkit/components/telemetry/ThreadHangStats.h create mode 100644 toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js create mode 100644 toolkit/components/telemetry/tests/unit/test_TelemetrySession.js create mode 100644 xpcom/libxpcomrt/XPCOMRTInit.cpp create mode 100644 xpcom/libxpcomrt/XPCOMRTInit.h create mode 100644 xpcom/libxpcomrt/XPCOMRTModule.inc create mode 100644 xpcom/libxpcomrt/XPCOMRTStubs.cpp create mode 100644 xpcom/libxpcomrt/docs/index.rst create mode 100644 xpcom/libxpcomrt/moz.build create mode 100644 xpcom/threads/HangAnnotations.cpp create mode 100644 xpcom/threads/HangAnnotations.h diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index 828fb3bfe4..38657ad652 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -13,6 +13,7 @@ #include "mozilla/FloatingPoint.h" #include "mozilla/Assertions.h" #include "mozilla/Preferences.h" +#include "mozilla/unused.h" #include "AccessCheck.h" #include "jsfriendapi.h" @@ -147,6 +148,9 @@ ErrorResult::ThrowErrorWithMessage(va_list ap, const dom::ErrNum errorNumber, message->mArgs.AppendElement(*va_arg(ap, nsString*)); } mMessage = message; +#ifdef DEBUG + mHasMessage = true; +#endif } void @@ -154,6 +158,7 @@ ErrorResult::SerializeMessage(IPC::Message* aMsg) const { using namespace IPC; MOZ_ASSERT(mMessage); + MOZ_ASSERT(mHasMessage); WriteParam(aMsg, mMessage->mArgs); WriteParam(aMsg, mMessage->mErrorNumber); } @@ -167,10 +172,11 @@ ErrorResult::DeserializeMessage(const IPC::Message* aMsg, void** aIter) !ReadParam(aMsg, aIter, &readMessage->mErrorNumber)) { return false; } - if (mMessage) { - delete mMessage; - } + MOZ_ASSERT(!mHasMessage); mMessage = readMessage.forget(); +#ifdef DEBUG + mHasMessage = true; +#endif return true; } @@ -196,6 +202,7 @@ void ErrorResult::ReportErrorWithMessage(JSContext* aCx) { MOZ_ASSERT(mMessage, "ReportErrorWithMessage() can be called only once"); + MOZ_ASSERT(mHasMessage); Message* message = mMessage; const uint32_t argCount = message->mArgs.Length(); @@ -218,6 +225,9 @@ ErrorResult::ClearMessage() if (IsErrorWithMessage()) { delete mMessage; mMessage = nullptr; +#ifdef DEBUG + mHasMessage = false; +#endif } } @@ -229,6 +239,9 @@ ErrorResult::ThrowJSException(JSContext* cx, JS::Handle exn) if (IsErrorWithMessage()) { delete mMessage; +#ifdef DEBUG + mHasMessage = false; +#endif } // Make sure mJSException is initialized _before_ we try to root it. But @@ -375,6 +388,44 @@ ErrorResult::SuppressException() mResult = NS_OK; } +ErrorResult& +ErrorResult::operator=(ErrorResult&& aRHS) +{ +#ifdef DEBUG + mMightHaveUnreportedJSException = aRHS.mMightHaveUnreportedJSException; + aRHS.mMightHaveUnreportedJSException = false; +#endif + if (aRHS.IsErrorWithMessage()) { + mMessage = aRHS.mMessage; + aRHS.mMessage = nullptr; +#ifdef DEBUG + mHasMessage = aRHS.mHasMessage; + aRHS.mHasMessage = false; +#endif + } else if (aRHS.IsJSException()) { + JSContext* cx = nsContentUtils::GetDefaultJSContextForThread(); + MOZ_ASSERT(cx); + mJSException.setUndefined(); + if (!js::AddRawValueRoot(cx, &mJSException, "ErrorResult::mJSException")) { + MOZ_CRASH("Could not root mJSException, we're about to OOM"); + } + mJSException = aRHS.mJSException; + aRHS.mJSException.setUndefined(); + js::RemoveRawValueRoot(cx, &aRHS.mJSException); + } else { + // Null out the union on both sides for hygiene purposes. + mMessage = aRHS.mMessage = nullptr; +#ifdef DEBUG + mHasMessage = aRHS.mHasMessage = false; +#endif + } + // Note: It's important to do this last, since this affects the condition + // checks above! + mResult = aRHS.mResult; + aRHS.mResult = NS_OK; + return *this; +} + namespace dom { bool diff --git a/dom/bindings/ErrorResult.h b/dom/bindings/ErrorResult.h index a9d6c5a09a..816f6b00df 100644 --- a/dom/bindings/ErrorResult.h +++ b/dom/bindings/ErrorResult.h @@ -17,6 +17,7 @@ #include "nscore.h" #include "nsStringGlue.h" #include "mozilla/Assertions.h" +#include "mozilla/Move.h" namespace IPC { class Message; @@ -47,6 +48,7 @@ public: #ifdef DEBUG mMightHaveUnreportedJSException = false; + mHasMessage = false; #endif } @@ -54,19 +56,25 @@ public: ~ErrorResult() { MOZ_ASSERT_IF(IsErrorWithMessage(), !mMessage); MOZ_ASSERT(!mMightHaveUnreportedJSException); + MOZ_ASSERT(!mHasMessage); } #endif + ErrorResult(ErrorResult&& aRHS) + { + *this = Move(aRHS); + } + ErrorResult& operator=(ErrorResult&& aRHS); + + explicit ErrorResult(nsresult aRv) + : ErrorResult() + { + AssignErrorCode(aRv); + } + void Throw(nsresult rv) { MOZ_ASSERT(NS_FAILED(rv), "Please don't try throwing success"); - MOZ_ASSERT(rv != NS_ERROR_TYPE_ERR, "Use ThrowTypeError()"); - MOZ_ASSERT(rv != NS_ERROR_RANGE_ERR, "Use ThrowRangeError()"); - MOZ_ASSERT(!IsErrorWithMessage(), "Don't overwrite errors with message"); - MOZ_ASSERT(rv != NS_ERROR_DOM_JS_EXCEPTION, "Use ThrowJSException()"); - MOZ_ASSERT(!IsJSException(), "Don't overwrite JS exceptions"); - MOZ_ASSERT(rv != NS_ERROR_XPC_NOT_ENOUGH_ARGS, "Use ThrowNotEnoughArgsError()"); - MOZ_ASSERT(!IsNotEnoughArgsError(), "Don't overwrite not enough args error"); - mResult = rv; + AssignErrorCode(rv); } // Use SuppressException when you want to suppress any exception that might be @@ -147,14 +155,7 @@ public: // Throw() here because people can easily pass success codes to // this. void operator=(nsresult rv) { - MOZ_ASSERT(rv != NS_ERROR_TYPE_ERR, "Use ThrowTypeError()"); - MOZ_ASSERT(rv != NS_ERROR_RANGE_ERR, "Use ThrowRangeError()"); - MOZ_ASSERT(!IsErrorWithMessage(), "Don't overwrite errors with message"); - MOZ_ASSERT(rv != NS_ERROR_DOM_JS_EXCEPTION, "Use ThrowJSException()"); - MOZ_ASSERT(!IsJSException(), "Don't overwrite JS exceptions"); - MOZ_ASSERT(rv != NS_ERROR_XPC_NOT_ENOUGH_ARGS, "Use ThrowNotEnoughArgsError()"); - MOZ_ASSERT(!IsNotEnoughArgsError(), "Don't overwrite not enough args error"); - mResult = rv; + AssignErrorCode(rv); } bool Failed() const { @@ -166,6 +167,24 @@ public: } private: + friend struct IPC::ParamTraits; + void SerializeMessage(IPC::Message* aMsg) const; + bool DeserializeMessage(const IPC::Message* aMsg, void** aIter); + + void ThrowErrorWithMessage(va_list ap, const dom::ErrNum errorNumber, + nsresult errorType); + + void AssignErrorCode(nsresult aRv) { + MOZ_ASSERT(aRv != NS_ERROR_TYPE_ERR, "Use ThrowTypeError()"); + MOZ_ASSERT(aRv != NS_ERROR_RANGE_ERR, "Use ThrowRangeError()"); + MOZ_ASSERT(!IsErrorWithMessage(), "Don't overwrite errors with message"); + MOZ_ASSERT(aRv != NS_ERROR_DOM_JS_EXCEPTION, "Use ThrowJSException()"); + MOZ_ASSERT(!IsJSException(), "Don't overwrite JS exceptions"); + MOZ_ASSERT(aRv != NS_ERROR_XPC_NOT_ENOUGH_ARGS, "Use ThrowNotEnoughArgsError()"); + MOZ_ASSERT(!IsNotEnoughArgsError(), "Don't overwrite not enough args error"); + mResult = aRv; + } + nsresult mResult; struct Message; // mMessage is set by ThrowErrorWithMessage and cleared (and deallocated) by @@ -177,21 +196,20 @@ private: JS::Value mJSException; // valid when IsJSException() }; - friend struct IPC::ParamTraits; - void SerializeMessage(IPC::Message* aMsg) const; - bool DeserializeMessage(const IPC::Message* aMsg, void** aIter); - #ifdef DEBUG // Used to keep track of codepaths that might throw JS exceptions, // for assertion purposes. bool mMightHaveUnreportedJSException; + // Used to keep track of whether mMessage has ever been assigned to. + // We need to check this in order to ensure that not attempting to + // delete mMessage in DeserializeMessage doesn't leak memory. + bool mHasMessage; #endif // Not to be implemented, to make sure people always pass this by // reference, not by value. ErrorResult(const ErrorResult&) = delete; - void ThrowErrorWithMessage(va_list ap, const dom::ErrNum errorNumber, - nsresult errorType); + void operator=(const ErrorResult&) = delete; }; /****************************************************************************** diff --git a/dom/cache/ActorChild.cpp b/dom/cache/ActorChild.cpp index 917e4a8174..fd4ba22983 100644 --- a/dom/cache/ActorChild.cpp +++ b/dom/cache/ActorChild.cpp @@ -7,6 +7,7 @@ #include "mozilla/dom/cache/ActorChild.h" #include "mozilla/dom/cache/Feature.h" +#include "nsThreadUtils.h" namespace mozilla { namespace dom { @@ -25,6 +26,7 @@ ActorChild::SetFeature(Feature* aFeature) void ActorChild::RemoveFeature() { + MOZ_ASSERT_IF(!NS_IsMainThread(), mFeature); if (mFeature) { mFeature->RemoveActor(this); mFeature = nullptr; diff --git a/dom/cache/AutoUtils.cpp b/dom/cache/AutoUtils.cpp index ed267eb3ca..ff75acf9fc 100644 --- a/dom/cache/AutoUtils.cpp +++ b/dom/cache/AutoUtils.cpp @@ -7,6 +7,7 @@ #include "mozilla/dom/cache/AutoUtils.h" #include "mozilla/unused.h" +#include "mozilla/dom/cache/CacheParent.h" #include "mozilla/dom/cache/CachePushStreamChild.h" #include "mozilla/dom/cache/CacheStreamControlParent.h" #include "mozilla/dom/cache/ReadStream.h" @@ -21,8 +22,8 @@ namespace { using mozilla::unused; using mozilla::dom::cache::CachePushStreamChild; -using mozilla::dom::cache::PCacheReadStream; -using mozilla::dom::cache::PCacheReadStreamOrVoid; +using mozilla::dom::cache::CacheReadStream; +using mozilla::dom::cache::CacheReadStreamOrVoid; using mozilla::ipc::FileDescriptor; using mozilla::ipc::FileDescriptorSetChild; using mozilla::ipc::FileDescriptorSetParent; @@ -35,7 +36,7 @@ enum CleanupAction }; void -CleanupChildFds(PCacheReadStream& aReadStream, CleanupAction aAction) +CleanupChildFds(CacheReadStream& aReadStream, CleanupAction aAction) { if (aReadStream.fds().type() != OptionalFileDescriptorSet::TPFileDescriptorSetChild) { @@ -59,7 +60,7 @@ CleanupChildFds(PCacheReadStream& aReadStream, CleanupAction aAction) } void -CleanupChildPushStream(PCacheReadStream& aReadStream, CleanupAction aAction) +CleanupChildPushStream(CacheReadStream& aReadStream, CleanupAction aAction) { if (!aReadStream.pushStreamChild()) { return; @@ -78,24 +79,24 @@ CleanupChildPushStream(PCacheReadStream& aReadStream, CleanupAction aAction) } void -CleanupChild(PCacheReadStream& aReadStream, CleanupAction aAction) +CleanupChild(CacheReadStream& aReadStream, CleanupAction aAction) { CleanupChildFds(aReadStream, aAction); CleanupChildPushStream(aReadStream, aAction); } void -CleanupChild(PCacheReadStreamOrVoid& aReadStreamOrVoid, CleanupAction aAction) +CleanupChild(CacheReadStreamOrVoid& aReadStreamOrVoid, CleanupAction aAction) { - if (aReadStreamOrVoid.type() == PCacheReadStreamOrVoid::Tvoid_t) { + if (aReadStreamOrVoid.type() == CacheReadStreamOrVoid::Tvoid_t) { return; } - CleanupChild(aReadStreamOrVoid.get_PCacheReadStream(), aAction); + CleanupChild(aReadStreamOrVoid.get_CacheReadStream(), aAction); } void -CleanupParentFds(PCacheReadStream& aReadStream, CleanupAction aAction) +CleanupParentFds(CacheReadStream& aReadStream, CleanupAction aAction) { if (aReadStream.fds().type() != OptionalFileDescriptorSet::TPFileDescriptorSetParent) { @@ -119,13 +120,13 @@ CleanupParentFds(PCacheReadStream& aReadStream, CleanupAction aAction) } void -CleanupParentFds(PCacheReadStreamOrVoid& aReadStreamOrVoid, CleanupAction aAction) +CleanupParentFds(CacheReadStreamOrVoid& aReadStreamOrVoid, CleanupAction aAction) { - if (aReadStreamOrVoid.type() == PCacheReadStreamOrVoid::Tvoid_t) { + if (aReadStreamOrVoid.type() == CacheReadStreamOrVoid::Tvoid_t) { return; } - CleanupParentFds(aReadStreamOrVoid.get_PCacheReadStream(), aAction); + CleanupParentFds(aReadStreamOrVoid.get_CacheReadStream(), aAction); } } // anonymous namespace @@ -136,171 +137,381 @@ namespace cache { using mozilla::ipc::PBackgroundParent; -AutoChildBase::AutoChildBase(TypeUtils* aTypeUtils) +// -------------------------------------------- + +AutoChildOpArgs::AutoChildOpArgs(TypeUtils* aTypeUtils, + const CacheOpArgs& aOpArgs) : mTypeUtils(aTypeUtils) + , mOpArgs(aOpArgs) , mSent(false) { MOZ_ASSERT(mTypeUtils); } -AutoChildBase::~AutoChildBase() -{ -} - -// -------------------------------------------- - -AutoChildRequest::AutoChildRequest(TypeUtils* aTypeUtils) - : AutoChildBase(aTypeUtils) -{ - mRequestOrVoid = void_t(); -} - -AutoChildRequest::~AutoChildRequest() -{ - if (mRequestOrVoid.type() != PCacheRequestOrVoid::TPCacheRequest) { - return; - } - - CleanupAction action = mSent ? Forget : Delete; - CleanupChild(mRequestOrVoid.get_PCacheRequest().body(), action); -} - -void -AutoChildRequest::Add(InternalRequest* aRequest, BodyAction aBodyAction, - ReferrerAction aReferrerAction, SchemeAction aSchemeAction, - ErrorResult& aRv) -{ - MOZ_ASSERT(!mSent); - MOZ_ASSERT(mRequestOrVoid.type() == PCacheRequestOrVoid::Tvoid_t); - mRequestOrVoid = PCacheRequest(); - mTypeUtils->ToPCacheRequest(mRequestOrVoid.get_PCacheRequest(), aRequest, - aBodyAction, aReferrerAction, aSchemeAction, aRv); -} - -const PCacheRequest& -AutoChildRequest::SendAsRequest() -{ - MOZ_ASSERT(mRequestOrVoid.type() == PCacheRequestOrVoid::TPCacheRequest); - return mRequestOrVoid.get_PCacheRequest(); -} - -const PCacheRequestOrVoid& -AutoChildRequest::SendAsRequestOrVoid() -{ - return mRequestOrVoid; -} - -// -------------------------------------------- - -AutoChildRequestList::AutoChildRequestList(TypeUtils* aTypeUtils, - uint32_t aCapacity) - : AutoChildBase(aTypeUtils) -{ - mRequestList.SetCapacity(aCapacity); -} - -AutoChildRequestList::~AutoChildRequestList() +AutoChildOpArgs::~AutoChildOpArgs() { CleanupAction action = mSent ? Forget : Delete; - for (uint32_t i = 0; i < mRequestList.Length(); ++i) { - CleanupChild(mRequestList[i].body(), action); + + switch(mOpArgs.type()) { + case CacheOpArgs::TCacheMatchArgs: + { + CacheMatchArgs& args = mOpArgs.get_CacheMatchArgs(); + CleanupChild(args.request().body(), action); + break; + } + case CacheOpArgs::TCacheMatchAllArgs: + { + CacheMatchAllArgs& args = mOpArgs.get_CacheMatchAllArgs(); + if (args.requestOrVoid().type() == CacheRequestOrVoid::Tvoid_t) { + break; + } + CleanupChild(args.requestOrVoid().get_CacheRequest().body(), action); + break; + } + case CacheOpArgs::TCacheAddAllArgs: + { + CacheAddAllArgs& args = mOpArgs.get_CacheAddAllArgs(); + auto& list = args.requestList(); + for (uint32_t i = 0; i < list.Length(); ++i) { + CleanupChild(list[i].body(), action); + } + break; + } + case CacheOpArgs::TCachePutAllArgs: + { + CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs(); + auto& list = args.requestResponseList(); + for (uint32_t i = 0; i < list.Length(); ++i) { + CleanupChild(list[i].request().body(), action); + CleanupChild(list[i].response().body(), action); + } + break; + } + case CacheOpArgs::TCacheDeleteArgs: + { + CacheDeleteArgs& args = mOpArgs.get_CacheDeleteArgs(); + CleanupChild(args.request().body(), action); + break; + } + case CacheOpArgs::TCacheKeysArgs: + { + CacheKeysArgs& args = mOpArgs.get_CacheKeysArgs(); + if (args.requestOrVoid().type() == CacheRequestOrVoid::Tvoid_t) { + break; + } + CleanupChild(args.requestOrVoid().get_CacheRequest().body(), action); + break; + } + case CacheOpArgs::TStorageMatchArgs: + { + StorageMatchArgs& args = mOpArgs.get_StorageMatchArgs(); + CleanupChild(args.request().body(), action); + break; + } + default: + // Other types do not need cleanup + break; } } void -AutoChildRequestList::Add(InternalRequest* aRequest, BodyAction aBodyAction, - ReferrerAction aReferrerAction, - SchemeAction aSchemeAction, ErrorResult& aRv) +AutoChildOpArgs::Add(InternalRequest* aRequest, BodyAction aBodyAction, + ReferrerAction aReferrerAction, SchemeAction aSchemeAction, + ErrorResult& aRv) { MOZ_ASSERT(!mSent); - // The FileDescriptorSetChild asserts in its destructor that all fds have - // been removed. The copy constructor, however, simply duplicates the - // fds without removing any. This means each temporary and copy must be - // explicitly cleaned up. - // - // Avoid a lot of this hassle by making sure we only create one here. On - // error we remove it. + switch(mOpArgs.type()) { + case CacheOpArgs::TCacheMatchArgs: + { + CacheMatchArgs& args = mOpArgs.get_CacheMatchArgs(); + mTypeUtils->ToCacheRequest(args.request(), aRequest, aBodyAction, + aReferrerAction, aSchemeAction, aRv); + break; + } + case CacheOpArgs::TCacheMatchAllArgs: + { + CacheMatchAllArgs& args = mOpArgs.get_CacheMatchAllArgs(); + MOZ_ASSERT(args.requestOrVoid().type() == CacheRequestOrVoid::Tvoid_t); + args.requestOrVoid() = CacheRequest(); + mTypeUtils->ToCacheRequest(args.requestOrVoid().get_CacheRequest(), + aRequest, aBodyAction, aReferrerAction, + aSchemeAction, aRv); + break; + } + case CacheOpArgs::TCacheAddAllArgs: + { + CacheAddAllArgs& args = mOpArgs.get_CacheAddAllArgs(); - PCacheRequest* request = mRequestList.AppendElement(); - mTypeUtils->ToPCacheRequest(*request, aRequest, aBodyAction, aReferrerAction, - aSchemeAction, aRv); - if (aRv.Failed()) { - mRequestList.RemoveElementAt(mRequestList.Length() - 1); + // The FileDescriptorSetChild asserts in its destructor that all fds have + // been removed. The copy constructor, however, simply duplicates the + // fds without removing any. This means each temporary and copy must be + // explicitly cleaned up. + // + // Avoid a lot of this hassle by making sure we only create one here. On + // error we remove it. + CacheRequest& request = *args.requestList().AppendElement(); + + mTypeUtils->ToCacheRequest(request, aRequest, aBodyAction, + aReferrerAction, aSchemeAction, aRv); + if (aRv.Failed()) { + args.requestList().RemoveElementAt(args.requestList().Length() - 1); + } + break; + } + case CacheOpArgs::TCacheDeleteArgs: + { + CacheDeleteArgs& args = mOpArgs.get_CacheDeleteArgs(); + mTypeUtils->ToCacheRequest(args.request(), aRequest, aBodyAction, + aReferrerAction, aSchemeAction, aRv); + break; + } + case CacheOpArgs::TCacheKeysArgs: + { + CacheKeysArgs& args = mOpArgs.get_CacheKeysArgs(); + MOZ_ASSERT(args.requestOrVoid().type() == CacheRequestOrVoid::Tvoid_t); + args.requestOrVoid() = CacheRequest(); + mTypeUtils->ToCacheRequest(args.requestOrVoid().get_CacheRequest(), + aRequest, aBodyAction, aReferrerAction, + aSchemeAction, aRv); + break; + } + case CacheOpArgs::TStorageMatchArgs: + { + StorageMatchArgs& args = mOpArgs.get_StorageMatchArgs(); + mTypeUtils->ToCacheRequest(args.request(), aRequest, aBodyAction, + aReferrerAction, aSchemeAction, aRv); + break; + } + default: + MOZ_CRASH("Cache args type cannot send a Request!"); } } -const nsTArray& -AutoChildRequestList::SendAsRequestList() +void +AutoChildOpArgs::Add(InternalRequest* aRequest, BodyAction aBodyAction, + ReferrerAction aReferrerAction, SchemeAction aSchemeAction, + Response& aResponse, ErrorResult& aRv) +{ + MOZ_ASSERT(!mSent); + + switch(mOpArgs.type()) { + case CacheOpArgs::TCachePutAllArgs: + { + CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs(); + + // The FileDescriptorSetChild asserts in its destructor that all fds have + // been removed. The copy constructor, however, simply duplicates the + // fds without removing any. This means each temporary and copy must be + // explicitly cleaned up. + // + // Avoid a lot of this hassle by making sure we only create one here. On + // error we remove it. + CacheRequestResponse& pair = *args.requestResponseList().AppendElement(); + pair.request().body() = void_t(); + pair.response().body() = void_t(); + + mTypeUtils->ToCacheRequest(pair.request(), aRequest, aBodyAction, + aReferrerAction, aSchemeAction, aRv); + if (!aRv.Failed()) { + mTypeUtils->ToCacheResponse(pair.response(), aResponse, aRv); + } + + if (aRv.Failed()) { + CleanupChild(pair.request().body(), Delete); + args.requestResponseList().RemoveElementAt( + args.requestResponseList().Length() - 1); + } + + break; + } + default: + MOZ_CRASH("Cache args type cannot send a Request/Response pair!"); + } +} + +const CacheOpArgs& +AutoChildOpArgs::SendAsOpArgs() { MOZ_ASSERT(!mSent); mSent = true; - return mRequestList; + return mOpArgs; } // -------------------------------------------- -AutoChildRequestResponse::AutoChildRequestResponse(TypeUtils* aTypeUtils) - : AutoChildBase(aTypeUtils) -{ - // Default IPC-generated constructor does not initialize these correctly - // and we check them later when cleaning up. - mRequestResponse.request().body() = void_t(); - mRequestResponse.response().body() = void_t(); -} - -AutoChildRequestResponse::~AutoChildRequestResponse() -{ - CleanupAction action = mSent ? Forget : Delete; - CleanupChild(mRequestResponse.request().body(), action); - CleanupChild(mRequestResponse.response().body(), action); -} - -void -AutoChildRequestResponse::Add(InternalRequest* aRequest, BodyAction aBodyAction, - ReferrerAction aReferrerAction, - SchemeAction aSchemeAction, ErrorResult& aRv) -{ - MOZ_ASSERT(!mSent); - mTypeUtils->ToPCacheRequest(mRequestResponse.request(), aRequest, aBodyAction, - aReferrerAction, aSchemeAction, aRv); -} - -void -AutoChildRequestResponse::Add(Response& aResponse, ErrorResult& aRv) -{ - MOZ_ASSERT(!mSent); - mTypeUtils->ToPCacheResponse(mRequestResponse.response(), aResponse, aRv); -} - -const CacheRequestResponse& -AutoChildRequestResponse::SendAsRequestResponse() -{ - MOZ_ASSERT(!mSent); - mSent = true; - return mRequestResponse; -} - -// -------------------------------------------- - -AutoParentBase::AutoParentBase(PBackgroundParent* aManager) +AutoParentOpResult::AutoParentOpResult(mozilla::ipc::PBackgroundParent* aManager, + const CacheOpResult& aOpResult) : mManager(aManager) + , mOpResult(aOpResult) , mStreamControl(nullptr) , mSent(false) { MOZ_ASSERT(mManager); } -AutoParentBase::~AutoParentBase() +AutoParentOpResult::~AutoParentOpResult() { - if (!mSent && mStreamControl) { + CleanupAction action = mSent ? Forget : Delete; + + switch (mOpResult.type()) { + case CacheOpResult::TCacheMatchResult: + { + CacheMatchResult& result = mOpResult.get_CacheMatchResult(); + if (result.responseOrVoid().type() == CacheResponseOrVoid::Tvoid_t) { + break; + } + CleanupParentFds(result.responseOrVoid().get_CacheResponse().body(), + action); + break; + } + case CacheOpResult::TCacheMatchAllResult: + { + CacheMatchAllResult& result = mOpResult.get_CacheMatchAllResult(); + for (uint32_t i = 0; i < result.responseList().Length(); ++i) { + CleanupParentFds(result.responseList()[i].body(), action); + } + break; + } + case CacheOpResult::TCacheKeysResult: + { + CacheKeysResult& result = mOpResult.get_CacheKeysResult(); + for (uint32_t i = 0; i < result.requestList().Length(); ++i) { + CleanupParentFds(result.requestList()[i].body(), action); + } + break; + } + case CacheOpResult::TStorageMatchResult: + { + StorageMatchResult& result = mOpResult.get_StorageMatchResult(); + if (result.responseOrVoid().type() == CacheResponseOrVoid::Tvoid_t) { + break; + } + CleanupParentFds(result.responseOrVoid().get_CacheResponse().body(), + action); + break; + } + case CacheOpResult::TStorageOpenResult: + { + StorageOpenResult& result = mOpResult.get_StorageOpenResult(); + if (action == Forget || result.actorParent() == nullptr) { + break; + } + unused << PCacheParent::Send__delete__(result.actorParent()); + } + default: + // other types do not need clean up + break; + } + + if (action == Delete && mStreamControl) { unused << PCacheStreamControlParent::Send__delete__(mStreamControl); } } void -AutoParentBase::SerializeReadStream(const nsID& aId, StreamList* aStreamList, - PCacheReadStream* aReadStreamOut) +AutoParentOpResult::Add(CacheId aOpenedCacheId, Manager* aManager) +{ + MOZ_ASSERT(mOpResult.type() == CacheOpResult::TStorageOpenResult); + MOZ_ASSERT(mOpResult.get_StorageOpenResult().actorParent() == nullptr); + mOpResult.get_StorageOpenResult().actorParent() = + mManager->SendPCacheConstructor(new CacheParent(aManager, aOpenedCacheId)); +} + +void +AutoParentOpResult::Add(const SavedResponse& aSavedResponse, + StreamList* aStreamList) +{ + MOZ_ASSERT(!mSent); + + switch (mOpResult.type()) { + case CacheOpResult::TCacheMatchResult: + { + CacheMatchResult& result = mOpResult.get_CacheMatchResult(); + MOZ_ASSERT(result.responseOrVoid().type() == CacheResponseOrVoid::Tvoid_t); + result.responseOrVoid() = aSavedResponse.mValue; + SerializeResponseBody(aSavedResponse, aStreamList, + &result.responseOrVoid().get_CacheResponse()); + break; + } + case CacheOpResult::TCacheMatchAllResult: + { + CacheMatchAllResult& result = mOpResult.get_CacheMatchAllResult(); + result.responseList().AppendElement(aSavedResponse.mValue); + SerializeResponseBody(aSavedResponse, aStreamList, + &result.responseList().LastElement()); + break; + } + case CacheOpResult::TStorageMatchResult: + { + StorageMatchResult& result = mOpResult.get_StorageMatchResult(); + MOZ_ASSERT(result.responseOrVoid().type() == CacheResponseOrVoid::Tvoid_t); + result.responseOrVoid() = aSavedResponse.mValue; + SerializeResponseBody(aSavedResponse, aStreamList, + &result.responseOrVoid().get_CacheResponse()); + break; + } + default: + MOZ_CRASH("Cache result type cannot handle returning a Response!"); + } +} + +void +AutoParentOpResult::Add(const SavedRequest& aSavedRequest, + StreamList* aStreamList) +{ + MOZ_ASSERT(!mSent); + + switch (mOpResult.type()) { + case CacheOpResult::TCacheKeysResult: + { + CacheKeysResult& result = mOpResult.get_CacheKeysResult(); + result.requestList().AppendElement(aSavedRequest.mValue); + CacheRequest& request = result.requestList().LastElement(); + + if (!aSavedRequest.mHasBodyId) { + request.body() = void_t(); + break; + } + + request.body() = CacheReadStream(); + SerializeReadStream(aSavedRequest.mBodyId, aStreamList, + &request.body().get_CacheReadStream()); + break; + } + default: + MOZ_CRASH("Cache result type cannot handle returning a Request!"); + } +} + +const CacheOpResult& +AutoParentOpResult::SendAsOpResult() +{ + MOZ_ASSERT(!mSent); + mSent = true; + return mOpResult; +} + +void +AutoParentOpResult::SerializeResponseBody(const SavedResponse& aSavedResponse, + StreamList* aStreamList, + CacheResponse* aResponseOut) +{ + MOZ_ASSERT(aResponseOut); + + if (!aSavedResponse.mHasBodyId) { + aResponseOut->body() = void_t(); + return; + } + + aResponseOut->body() = CacheReadStream(); + SerializeReadStream(aSavedResponse.mBodyId, aStreamList, + &aResponseOut->body().get_CacheReadStream()); +} + +void +AutoParentOpResult::SerializeReadStream(const nsID& aId, StreamList* aStreamList, + CacheReadStream* aReadStreamOut) { MOZ_ASSERT(aStreamList); MOZ_ASSERT(aReadStreamOut); @@ -328,139 +539,6 @@ AutoParentBase::SerializeReadStream(const nsID& aId, StreamList* aStreamList, readStream->Serialize(aReadStreamOut); } -// -------------------------------------------- - -AutoParentRequestList::AutoParentRequestList(PBackgroundParent* aManager, - uint32_t aCapacity) - : AutoParentBase(aManager) -{ - mRequestList.SetCapacity(aCapacity); -} - -AutoParentRequestList::~AutoParentRequestList() -{ - CleanupAction action = mSent ? Forget : Delete; - for (uint32_t i = 0; i < mRequestList.Length(); ++i) { - CleanupParentFds(mRequestList[i].body(), action); - } -} - -void -AutoParentRequestList::Add(const SavedRequest& aSavedRequest, - StreamList* aStreamList) -{ - MOZ_ASSERT(!mSent); - - mRequestList.AppendElement(aSavedRequest.mValue); - PCacheRequest& request = mRequestList.LastElement(); - - if (!aSavedRequest.mHasBodyId) { - request.body() = void_t(); - return; - } - - request.body() = PCacheReadStream(); - SerializeReadStream(aSavedRequest.mBodyId, aStreamList, - &request.body().get_PCacheReadStream()); -} - -const nsTArray& -AutoParentRequestList::SendAsRequestList() -{ - MOZ_ASSERT(!mSent); - mSent = true; - return mRequestList; -} - -// -------------------------------------------- - -AutoParentResponseList::AutoParentResponseList(PBackgroundParent* aManager, - uint32_t aCapacity) - : AutoParentBase(aManager) -{ - mResponseList.SetCapacity(aCapacity); -} - -AutoParentResponseList::~AutoParentResponseList() -{ - CleanupAction action = mSent ? Forget : Delete; - for (uint32_t i = 0; i < mResponseList.Length(); ++i) { - CleanupParentFds(mResponseList[i].body(), action); - } -} - -void -AutoParentResponseList::Add(const SavedResponse& aSavedResponse, - StreamList* aStreamList) -{ - MOZ_ASSERT(!mSent); - - mResponseList.AppendElement(aSavedResponse.mValue); - PCacheResponse& response = mResponseList.LastElement(); - - if (!aSavedResponse.mHasBodyId) { - response.body() = void_t(); - return; - } - - response.body() = PCacheReadStream(); - SerializeReadStream(aSavedResponse.mBodyId, aStreamList, - &response.body().get_PCacheReadStream()); -} - -const nsTArray& -AutoParentResponseList::SendAsResponseList() -{ - MOZ_ASSERT(!mSent); - mSent = true; - return mResponseList; -} - -// -------------------------------------------- - -AutoParentResponseOrVoid::AutoParentResponseOrVoid(ipc::PBackgroundParent* aManager) - : AutoParentBase(aManager) -{ - mResponseOrVoid = void_t(); -} - -AutoParentResponseOrVoid::~AutoParentResponseOrVoid() -{ - if (mResponseOrVoid.type() != PCacheResponseOrVoid::TPCacheResponse) { - return; - } - - CleanupAction action = mSent ? Forget : Delete; - CleanupParentFds(mResponseOrVoid.get_PCacheResponse().body(), action); -} - -void -AutoParentResponseOrVoid::Add(const SavedResponse& aSavedResponse, - StreamList* aStreamList) -{ - MOZ_ASSERT(!mSent); - - mResponseOrVoid = aSavedResponse.mValue; - PCacheResponse& response = mResponseOrVoid.get_PCacheResponse(); - - if (!aSavedResponse.mHasBodyId) { - response.body() = void_t(); - return; - } - - response.body() = PCacheReadStream(); - SerializeReadStream(aSavedResponse.mBodyId, aStreamList, - &response.body().get_PCacheReadStream()); -} - -const PCacheResponseOrVoid& -AutoParentResponseOrVoid::SendAsResponseOrVoid() -{ - MOZ_ASSERT(!mSent); - mSent = true; - return mResponseOrVoid; -} - } // namespace cache } // namespace dom } // namespace mozilla diff --git a/dom/cache/AutoUtils.h b/dom/cache/AutoUtils.h index ab092eb3d5..69864591d5 100644 --- a/dom/cache/AutoUtils.h +++ b/dom/cache/AutoUtils.h @@ -8,7 +8,8 @@ #define mozilla_dom_cache_AutoUtils_h #include "mozilla/Attributes.h" -#include "mozilla/dom/cache/PCacheTypes.h" +#include "mozilla/dom/cache/CacheTypes.h" +#include "mozilla/dom/cache/Types.h" #include "mozilla/dom/cache/TypeUtils.h" #include "nsTArray.h" @@ -29,6 +30,7 @@ class InternalRequest; namespace cache { class CacheStreamControlParent; +class Manager; struct SavedRequest; struct SavedResponse; class StreamList; @@ -41,129 +43,56 @@ class StreamList; // Note, these should only be used when *sending* streams across IPC. The // deserialization case is handled by creating a ReadStream object. -class MOZ_STACK_CLASS AutoChildBase +class MOZ_STACK_CLASS AutoChildOpArgs final { -protected: +public: typedef TypeUtils::BodyAction BodyAction; typedef TypeUtils::ReferrerAction ReferrerAction; typedef TypeUtils::SchemeAction SchemeAction; - AutoChildBase(TypeUtils* aTypeUtils); - virtual ~AutoChildBase() = 0; + AutoChildOpArgs(TypeUtils* aTypeUtils, const CacheOpArgs& aOpArgs); + ~AutoChildOpArgs(); + void Add(InternalRequest* aRequest, BodyAction aBodyAction, + ReferrerAction aReferrerAction, SchemeAction aSchemeAction, + ErrorResult& aRv); + void Add(InternalRequest* aRequest, BodyAction aBodyAction, + ReferrerAction aReferrerAction, SchemeAction aSchemeAction, + Response& aResponse, ErrorResult& aRv); + + const CacheOpArgs& SendAsOpArgs(); + +private: TypeUtils* mTypeUtils; + CacheOpArgs mOpArgs; bool mSent; }; -class MOZ_STACK_CLASS AutoChildRequest final : public AutoChildBase +class MOZ_STACK_CLASS AutoParentOpResult final { public: - explicit AutoChildRequest(TypeUtils* aTypeUtils); - ~AutoChildRequest(); - - void Add(InternalRequest* aRequest, BodyAction aBodyAction, - ReferrerAction aReferrerAction, SchemeAction aSchemeAction, - ErrorResult& aRv); - - const PCacheRequest& SendAsRequest(); - const PCacheRequestOrVoid& SendAsRequestOrVoid(); - -private: - PCacheRequestOrVoid mRequestOrVoid; -}; - -class MOZ_STACK_CLASS AutoChildRequestList final : public AutoChildBase -{ -public: - AutoChildRequestList(TypeUtils* aTypeUtils, uint32_t aCapacity); - ~AutoChildRequestList(); - - void Add(InternalRequest* aRequest, BodyAction aBodyAction, - ReferrerAction aReferrerAction, SchemeAction aSchemeAction, - ErrorResult& aRv); - - const nsTArray& SendAsRequestList(); - -private: - // Allocates ~5k inline in the stack-only class - nsAutoTArray mRequestList; -}; - -class MOZ_STACK_CLASS AutoChildRequestResponse final : public AutoChildBase -{ -public: - explicit AutoChildRequestResponse(TypeUtils* aTypeUtils); - ~AutoChildRequestResponse(); - - void Add(InternalRequest* aRequest, BodyAction aBodyAction, - ReferrerAction aReferrerAction, SchemeAction aSchemeAction, - ErrorResult& aRv); - void Add(Response& aResponse, ErrorResult& aRv); - - const CacheRequestResponse& SendAsRequestResponse(); - -private: - CacheRequestResponse mRequestResponse; -}; - -class MOZ_STACK_CLASS AutoParentBase -{ -protected: - explicit AutoParentBase(mozilla::ipc::PBackgroundParent* aManager); - virtual ~AutoParentBase() = 0; - - void SerializeReadStream(const nsID& aId, StreamList* aStreamList, - PCacheReadStream* aReadStreamOut); - - mozilla::ipc::PBackgroundParent* mManager; - CacheStreamControlParent* mStreamControl; - bool mSent; -}; - -class MOZ_STACK_CLASS AutoParentRequestList final : public AutoParentBase -{ -public: - AutoParentRequestList(mozilla::ipc::PBackgroundParent* aManager, - uint32_t aCapacity); - ~AutoParentRequestList(); + AutoParentOpResult(mozilla::ipc::PBackgroundParent* aManager, + const CacheOpResult& aOpResult); + ~AutoParentOpResult(); + void Add(CacheId aOpenedCacheId, Manager* aManager); + void Add(const SavedResponse& aSavedResponse, StreamList* aStreamList); void Add(const SavedRequest& aSavedRequest, StreamList* aStreamList); - const nsTArray& SendAsRequestList(); + const CacheOpResult& SendAsOpResult(); private: - // Allocates ~5k inline in the stack-only class - nsAutoTArray mRequestList; -}; + void SerializeResponseBody(const SavedResponse& aSavedResponse, + StreamList* aStreamList, + CacheResponse* aResponseOut); -class MOZ_STACK_CLASS AutoParentResponseList final : public AutoParentBase -{ -public: - AutoParentResponseList(mozilla::ipc::PBackgroundParent* aManager, - uint32_t aCapacity); - ~AutoParentResponseList(); + void SerializeReadStream(const nsID& aId, StreamList* aStreamList, + CacheReadStream* aReadStreamOut); - void Add(const SavedResponse& aSavedResponse, StreamList* aStreamList); - - const nsTArray& SendAsResponseList(); - -private: - // Allocates ~4k inline in the stack-only class - nsAutoTArray mResponseList; -}; - -class MOZ_STACK_CLASS AutoParentResponseOrVoid final : public AutoParentBase -{ -public: - explicit AutoParentResponseOrVoid(mozilla::ipc::PBackgroundParent* aManager); - ~AutoParentResponseOrVoid(); - - void Add(const SavedResponse& aSavedResponse, StreamList* aStreamList); - - const PCacheResponseOrVoid& SendAsResponseOrVoid(); - -private: - PCacheResponseOrVoid mResponseOrVoid; + mozilla::ipc::PBackgroundParent* mManager; + CacheOpResult mOpResult; + CacheStreamControlParent* mStreamControl; + bool mSent; }; } // namespace cache diff --git a/dom/cache/Cache.cpp b/dom/cache/Cache.cpp index c017705152..7a69510bb4 100644 --- a/dom/cache/Cache.cpp +++ b/dom/cache/Cache.cpp @@ -16,7 +16,6 @@ #include "mozilla/dom/cache/CacheChild.h" #include "mozilla/dom/cache/CachePushStreamChild.h" #include "mozilla/dom/cache/ReadStream.h" -#include "mozilla/dom/cache/TypeUtils.h" #include "mozilla/ErrorResult.h" #include "mozilla/Preferences.h" #include "mozilla/unused.h" @@ -79,17 +78,7 @@ using mozilla::dom::workers::WorkerPrivate; NS_IMPL_CYCLE_COLLECTING_ADDREF(mozilla::dom::cache::Cache); NS_IMPL_CYCLE_COLLECTING_RELEASE(mozilla::dom::cache::Cache); -NS_IMPL_CYCLE_COLLECTION_CLASS(mozilla::dom::cache::Cache) -NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(mozilla::dom::cache::Cache) - tmp->DisconnectFromActor(); - NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal, mRequestPromises) - NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER -NS_IMPL_CYCLE_COLLECTION_UNLINK_END -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(mozilla::dom::cache::Cache) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal, mRequestPromises) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END -NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(mozilla::dom::cache::Cache) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(mozilla::dom::cache::Cache, mGlobal); NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Cache) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY @@ -111,31 +100,22 @@ Cache::Match(const RequestOrUSVString& aRequest, { MOZ_ASSERT(mActor); - nsRefPtr promise = Promise::Create(mGlobal, aRv); - if (!promise) { - return nullptr; - } - nsRefPtr ir = ToInternalRequest(aRequest, IgnoreBody, aRv); - if (aRv.Failed()) { + if (NS_WARN_IF(aRv.Failed())) { return nullptr; } - AutoChildRequest request(this); + CacheQueryParams params; + ToCacheQueryParams(params, aOptions); - request.Add(ir, IgnoreBody, PassThroughReferrer, IgnoreInvalidScheme, aRv); - if (aRv.Failed()) { + AutoChildOpArgs args(this, CacheMatchArgs(CacheRequest(), params)); + + args.Add(ir, IgnoreBody, PassThroughReferrer, IgnoreInvalidScheme, aRv); + if (NS_WARN_IF(aRv.Failed())) { return nullptr; } - PCacheQueryParams params; - ToPCacheQueryParams(params, aOptions); - - RequestId requestId = AddRequestPromise(promise, aRv); - - unused << mActor->SendMatch(requestId, request.SendAsRequest(), params); - - return promise.forget(); + return ExecuteOp(args, aRv); } already_AddRefed @@ -144,12 +124,10 @@ Cache::MatchAll(const Optional& aRequest, { MOZ_ASSERT(mActor); - nsRefPtr promise = Promise::Create(mGlobal, aRv); - if (!promise) { - return nullptr; - } + CacheQueryParams params; + ToCacheQueryParams(params, aOptions); - AutoChildRequest request(this); + AutoChildOpArgs args(this, CacheMatchAllArgs(void_t(), params)); if (aRequest.WasPassed()) { nsRefPtr ir = ToInternalRequest(aRequest.Value(), @@ -158,21 +136,13 @@ Cache::MatchAll(const Optional& aRequest, return nullptr; } - request.Add(ir, IgnoreBody, PassThroughReferrer, IgnoreInvalidScheme, aRv); + args.Add(ir, IgnoreBody, PassThroughReferrer, IgnoreInvalidScheme, aRv); if (aRv.Failed()) { return nullptr; } } - PCacheQueryParams params; - ToPCacheQueryParams(params, aOptions); - - RequestId requestId = AddRequestPromise(promise, aRv); - - unused << mActor->SendMatchAll(requestId, request.SendAsRequestOrVoid(), - params); - - return promise.forget(); + return ExecuteOp(args, aRv); } already_AddRefed @@ -184,27 +154,19 @@ Cache::Add(const RequestOrUSVString& aRequest, ErrorResult& aRv) return nullptr; } - nsRefPtr promise = Promise::Create(mGlobal, aRv); - if (!promise) { - return nullptr; - } - nsRefPtr ir = ToInternalRequest(aRequest, ReadBody, aRv); if (aRv.Failed()) { return nullptr; } - AutoChildRequestList requests(this, 1); - requests.Add(ir, ReadBody, ExpandReferrer, NetworkErrorOnInvalidScheme, aRv); + AutoChildOpArgs args(this, CacheAddAllArgs()); + + args.Add(ir, ReadBody, ExpandReferrer, NetworkErrorOnInvalidScheme, aRv); if (aRv.Failed()) { return nullptr; } - RequestId requestId = AddRequestPromise(promise, aRv); - - unused << mActor->SendAddAll(requestId, requests.SendAsRequestList()); - - return promise.forget(); + return ExecuteOp(args, aRv); } already_AddRefed @@ -213,18 +175,18 @@ Cache::AddAll(const Sequence& aRequests, { MOZ_ASSERT(mActor); - nsRefPtr promise = Promise::Create(mGlobal, aRv); - if (!promise) { - return nullptr; - } - // If there is no work to do, then resolve immediately if (aRequests.IsEmpty()) { + nsRefPtr promise = Promise::Create(mGlobal, aRv); + if (!promise) { + return nullptr; + } + promise->MaybeResolve(JS::UndefinedHandleValue); return promise.forget(); } - AutoChildRequestList requests(this, aRequests.Length()); + AutoChildOpArgs args(this, CacheAddAllArgs()); for (uint32_t i = 0; i < aRequests.Length(); ++i) { if (!IsValidPutRequestMethod(aRequests[i], aRv)) { @@ -237,18 +199,13 @@ Cache::AddAll(const Sequence& aRequests, return nullptr; } - requests.Add(ir, ReadBody, ExpandReferrer, NetworkErrorOnInvalidScheme, - aRv); + args.Add(ir, ReadBody, ExpandReferrer, NetworkErrorOnInvalidScheme, aRv); if (aRv.Failed()) { return nullptr; } } - RequestId requestId = AddRequestPromise(promise, aRv); - - unused << mActor->SendAddAll(requestId, requests.SendAsRequestList()); - - return promise.forget(); + return ExecuteOp(args, aRv); } already_AddRefed @@ -261,32 +218,20 @@ Cache::Put(const RequestOrUSVString& aRequest, Response& aResponse, return nullptr; } - nsRefPtr promise = Promise::Create(mGlobal, aRv); - if (!promise) { - return nullptr; - } - nsRefPtr ir = ToInternalRequest(aRequest, ReadBody, aRv); if (aRv.Failed()) { return nullptr; } - AutoChildRequestResponse put(this); - put.Add(ir, ReadBody, PassThroughReferrer, TypeErrorOnInvalidScheme, aRv); + AutoChildOpArgs args(this, CachePutAllArgs()); + + args.Add(ir, ReadBody, PassThroughReferrer, TypeErrorOnInvalidScheme, + aResponse, aRv); if (aRv.Failed()) { return nullptr; } - put.Add(aResponse, aRv); - if (aRv.Failed()) { - return nullptr; - } - - RequestId requestId = AddRequestPromise(promise, aRv); - - unused << mActor->SendPut(requestId, put.SendAsRequestResponse()); - - return promise.forget(); + return ExecuteOp(args, aRv); } already_AddRefed @@ -295,30 +240,22 @@ Cache::Delete(const RequestOrUSVString& aRequest, { MOZ_ASSERT(mActor); - nsRefPtr promise = Promise::Create(mGlobal, aRv); - if (!promise) { - return nullptr; - } - nsRefPtr ir = ToInternalRequest(aRequest, IgnoreBody, aRv); if (aRv.Failed()) { return nullptr; } - AutoChildRequest request(this); - request.Add(ir, IgnoreBody, PassThroughReferrer, IgnoreInvalidScheme, aRv); + CacheQueryParams params; + ToCacheQueryParams(params, aOptions); + + AutoChildOpArgs args(this, CacheDeleteArgs(CacheRequest(), params)); + + args.Add(ir, IgnoreBody, PassThroughReferrer, IgnoreInvalidScheme, aRv); if (aRv.Failed()) { return nullptr; } - PCacheQueryParams params; - ToPCacheQueryParams(params, aOptions); - - RequestId requestId = AddRequestPromise(promise, aRv); - - unused << mActor->SendDelete(requestId, request.SendAsRequest(), params); - - return promise.forget(); + return ExecuteOp(args, aRv); } already_AddRefed @@ -327,12 +264,10 @@ Cache::Keys(const Optional& aRequest, { MOZ_ASSERT(mActor); - nsRefPtr promise = Promise::Create(mGlobal, aRv); - if (!promise) { - return nullptr; - } + CacheQueryParams params; + ToCacheQueryParams(params, aOptions); - AutoChildRequest request(this); + AutoChildOpArgs args(this, CacheKeysArgs(void_t(), params)); if (aRequest.WasPassed()) { nsRefPtr ir = ToInternalRequest(aRequest.Value(), @@ -341,20 +276,13 @@ Cache::Keys(const Optional& aRequest, return nullptr; } - request.Add(ir, IgnoreBody, PassThroughReferrer, IgnoreInvalidScheme, aRv); + args.Add(ir, IgnoreBody, PassThroughReferrer, IgnoreInvalidScheme, aRv); if (aRv.Failed()) { return nullptr; } } - PCacheQueryParams params; - ToPCacheQueryParams(params, aOptions); - - RequestId requestId = AddRequestPromise(promise, aRv); - - unused << mActor->SendKeys(requestId, request.SendAsRequestOrVoid(), params); - - return promise.forget(); + return ExecuteOp(args, aRv); } // static @@ -401,123 +329,6 @@ Cache::DestroyInternal(CacheChild* aActor) mActor = nullptr; } -void -Cache::RecvMatchResponse(RequestId aRequestId, nsresult aRv, - const PCacheResponseOrVoid& aResponse) -{ - // Convert the response immediately if its present. This ensures that - // any stream actors are cleaned up, even if we error out below. - nsRefPtr response; - if (aResponse.type() == PCacheResponseOrVoid::TPCacheResponse) { - response = ToResponse(aResponse); - } - - nsRefPtr promise = RemoveRequestPromise(aRequestId); - - if (NS_FAILED(aRv)) { - promise->MaybeReject(aRv); - return; - } - - if (!response) { - promise->MaybeResolve(JS::UndefinedHandleValue); - return; - } - - promise->MaybeResolve(response); -} - -void -Cache::RecvMatchAllResponse(RequestId aRequestId, nsresult aRv, - const nsTArray& aResponses) -{ - // Convert responses immediately. This ensures that any stream actors are - // cleaned up, even if we error out below. - nsAutoTArray, 256> responses; - responses.SetCapacity(aResponses.Length()); - - for (uint32_t i = 0; i < aResponses.Length(); ++i) { - nsRefPtr response = ToResponse(aResponses[i]); - responses.AppendElement(response.forget()); - } - - nsRefPtr promise = RemoveRequestPromise(aRequestId); - - if (NS_FAILED(aRv)) { - promise->MaybeReject(aRv); - return; - } - - promise->MaybeResolve(responses); -} - -void -Cache::RecvAddAllResponse(RequestId aRequestId, - const mozilla::ErrorResult& aError) -{ - nsRefPtr promise = RemoveRequestPromise(aRequestId); - - if (aError.Failed()) { - // TODO: Remove this const_cast (bug 1152078). - // It is safe for now since this ErrorResult is handed off to us by IPDL - // and is thrown into the trash afterwards. - promise->MaybeReject(const_cast(aError)); - return; - } - - promise->MaybeResolve(JS::UndefinedHandleValue); -} - -void -Cache::RecvPutResponse(RequestId aRequestId, nsresult aRv) -{ - nsRefPtr promise = RemoveRequestPromise(aRequestId); - - if (NS_FAILED(aRv)) { - promise->MaybeReject(aRv); - return; - } - - promise->MaybeResolve(JS::UndefinedHandleValue); -} - -void -Cache::RecvDeleteResponse(RequestId aRequestId, nsresult aRv, bool aSuccess) -{ - nsRefPtr promise = RemoveRequestPromise(aRequestId); - - if (NS_FAILED(aRv)) { - promise->MaybeReject(aRv); - return; - } - - promise->MaybeResolve(aSuccess); -} - -void -Cache::RecvKeysResponse(RequestId aRequestId, nsresult aRv, - const nsTArray& aRequests) -{ - // Convert requests immediately. This ensures that any stream actors are - // cleaned up, even if we error out below. - nsAutoTArray, 256> requests; - requests.SetCapacity(aRequests.Length()); - - for (uint32_t i = 0; i < aRequests.Length(); ++i) { - nsRefPtr request = ToRequest(aRequests[i]); - requests.AppendElement(request.forget()); - } - - nsRefPtr promise = RemoveRequestPromise(aRequestId); - - if (NS_FAILED(aRv)) { - promise->MaybeReject(aRv); - return; - } - - promise->MaybeResolve(requests); -} - nsIGlobalObject* Cache::GetGlobalObject() const { @@ -538,36 +349,12 @@ Cache::CreatePushStream(nsIAsyncInputStream* aStream) NS_ASSERT_OWNINGTHREAD(Cache); MOZ_ASSERT(mActor); MOZ_ASSERT(aStream); - auto actor = mActor->SendPCachePushStreamConstructor( - new CachePushStreamChild(mActor->GetFeature(), aStream)); - MOZ_ASSERT(actor); - return static_cast(actor); -} - -void -Cache::ResolvedCallback(JSContext* aCx, JS::Handle aValue) -{ - // Do nothing. The Promise will automatically drop the ref to us after - // calling the callback. This is what we want as we only registered in order - // to be held alive via the Promise handle. -} - -void -Cache::RejectedCallback(JSContext* aCx, JS::Handle aValue) -{ - // Do nothing. The Promise will automatically drop the ref to us after - // calling the callback. This is what we want as we only registered in order - // to be held alive via the Promise handle. + return mActor->CreatePushStream(aStream); } Cache::~Cache() { - DisconnectFromActor(); -} - -void -Cache::DisconnectFromActor() -{ + NS_ASSERT_OWNINGTHREAD(Cache); if (mActor) { mActor->StartDestroy(); // DestroyInternal() is called synchronously by StartDestroy(). So we @@ -576,43 +363,16 @@ Cache::DisconnectFromActor() } } -RequestId -Cache::AddRequestPromise(Promise* aPromise, ErrorResult& aRv) -{ - MOZ_ASSERT(aPromise); - MOZ_ASSERT(!mRequestPromises.Contains(aPromise)); - - // Register ourself as a promise handler so that the promise will hold us - // alive. This allows the client code to drop the ref to the Cache - // object and just keep their promise. This is fairly common in promise - // chaining code. - aPromise->AppendNativeHandler(this); - - mRequestPromises.AppendElement(aPromise); - - // (Ab)use the promise pointer as our request ID. This is a fast, thread-safe - // way to get a unique ID for the promise to be resolved later. - return reinterpret_cast(aPromise); -} - already_AddRefed -Cache::RemoveRequestPromise(RequestId aRequestId) +Cache::ExecuteOp(AutoChildOpArgs& aOpArgs, ErrorResult& aRv) { - MOZ_ASSERT(aRequestId != INVALID_REQUEST_ID); - - for (uint32_t i = 0; i < mRequestPromises.Length(); ++i) { - nsRefPtr& promise = mRequestPromises.ElementAt(i); - // To be safe, only cast promise pointers to our integer RequestId - // type and never cast an integer to a pointer. - if (aRequestId == reinterpret_cast(promise.get())) { - nsRefPtr ref; - ref.swap(promise); - mRequestPromises.RemoveElementAt(i); - return ref.forget(); - } + nsRefPtr promise = Promise::Create(mGlobal, aRv); + if (!promise) { + return nullptr; } - MOZ_ASSERT_UNREACHABLE("Received response without a matching promise!"); - return nullptr; + + mActor->ExecuteOp(mGlobal, promise, aOpArgs.SendAsOpArgs()); + return promise.forget(); } } // namespace cache diff --git a/dom/cache/Cache.h b/dom/cache/Cache.h index ceae13efaf..7656c539af 100644 --- a/dom/cache/Cache.h +++ b/dom/cache/Cache.h @@ -7,7 +7,6 @@ #ifndef mozilla_dom_cache_Cache_h #define mozilla_dom_cache_Cache_h -#include "mozilla/dom/PromiseNativeHandler.h" #include "mozilla/dom/cache/Types.h" #include "mozilla/dom/cache/TypeUtils.h" #include "nsCOMPtr.h" @@ -33,12 +32,10 @@ template class Sequence; namespace cache { +class AutoChildOpArgs; class CacheChild; -class PCacheRequest; -class PCacheResponse; -class PCacheResponseOrVoid; -class Cache final : public PromiseNativeHandler +class Cache final : public nsISupports , public nsWrapperCache , public TypeUtils { @@ -76,20 +73,6 @@ public: // Called when CacheChild actor is being destroyed void DestroyInternal(CacheChild* aActor); - // methods forwarded from CacheChild - void RecvMatchResponse(RequestId aRequestId, nsresult aRv, - const PCacheResponseOrVoid& aResponse); - void RecvMatchAllResponse(RequestId aRequestId, nsresult aRv, - const nsTArray& aResponses); - void RecvAddAllResponse(RequestId aRequestId, - const mozilla::ErrorResult& aError); - void RecvPutResponse(RequestId aRequestId, nsresult aRv); - - void RecvDeleteResponse(RequestId aRequestId, nsresult aRv, - bool aSuccess); - void RecvKeysResponse(RequestId aRequestId, nsresult aRv, - const nsTArray& aRequests); - // TypeUtils methods virtual nsIGlobalObject* GetGlobalObject() const override; @@ -101,26 +84,17 @@ public: virtual CachePushStreamChild* CreatePushStream(nsIAsyncInputStream* aStream) override; - // PromiseNativeHandler methods - virtual void - ResolvedCallback(JSContext* aCx, JS::Handle aValue) override; - - virtual void - RejectedCallback(JSContext* aCx, JS::Handle aValue) override; - private: ~Cache(); // Called when we're destroyed or CCed. void DisconnectFromActor(); - // TODO: Replace with actor-per-request model during refactor (bug 1110485) - RequestId AddRequestPromise(Promise* aPromise, ErrorResult& aRv); - already_AddRefed RemoveRequestPromise(RequestId aRequestId); + already_AddRefed + ExecuteOp(AutoChildOpArgs& aOpArgs, ErrorResult& aRv); nsCOMPtr mGlobal; CacheChild* mActor; - nsTArray> mRequestPromises; public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS diff --git a/dom/cache/CacheChild.cpp b/dom/cache/CacheChild.cpp index 4facd1ebb7..830958be29 100644 --- a/dom/cache/CacheChild.cpp +++ b/dom/cache/CacheChild.cpp @@ -9,8 +9,8 @@ #include "mozilla/unused.h" #include "mozilla/dom/cache/ActorUtils.h" #include "mozilla/dom/cache/Cache.h" -#include "mozilla/dom/cache/PCachePushStreamChild.h" -#include "mozilla/dom/cache/StreamUtils.h" +#include "mozilla/dom/cache/CacheOpChild.h" +#include "mozilla/dom/cache/CachePushStreamChild.h" namespace mozilla { namespace dom { @@ -32,6 +32,7 @@ DeallocPCacheChild(PCacheChild* aActor) CacheChild::CacheChild() : mListener(nullptr) + , mNumChildActors(0) { MOZ_COUNT_CTOR(cache::CacheChild); } @@ -41,6 +42,7 @@ CacheChild::~CacheChild() MOZ_COUNT_DTOR(cache::CacheChild); NS_ASSERT_OWNINGTHREAD(CacheChild); MOZ_ASSERT(!mListener); + MOZ_ASSERT(!mNumChildActors); } void @@ -60,6 +62,25 @@ CacheChild::ClearListener() mListener = nullptr; } +void +CacheChild::ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise, + const CacheOpArgs& aArgs) +{ + mNumChildActors += 1; + MOZ_ALWAYS_TRUE(SendPCacheOpConstructor( + new CacheOpChild(GetFeature(), aGlobal, aPromise), aArgs)); +} + +CachePushStreamChild* +CacheChild::CreatePushStream(nsIAsyncInputStream* aStream) +{ + mNumChildActors += 1; + auto actor = SendPCachePushStreamConstructor( + new CachePushStreamChild(GetFeature(), aStream)); + MOZ_ASSERT(actor); + return static_cast(actor); +} + void CacheChild::StartDestroy() { @@ -77,6 +98,14 @@ CacheChild::StartDestroy() // Cache listener should call ClearListener() in DestroyInternal() MOZ_ASSERT(!mListener); + // If we have outstanding child actors, then don't destroy ourself yet. + // The child actors should be short lived and we should allow them to complete + // if possible. SendTeardown() will be called when the count drops to zero + // in NoteDeletedActor(). + if (mNumChildActors) { + return; + } + // Start actor destruction from parent process unused << SendTeardown(); } @@ -95,6 +124,21 @@ CacheChild::ActorDestroy(ActorDestroyReason aReason) RemoveFeature(); } +PCacheOpChild* +CacheChild::AllocPCacheOpChild(const CacheOpArgs& aOpArgs) +{ + MOZ_CRASH("CacheOpChild should be manually constructed."); + return nullptr; +} + +bool +CacheChild::DeallocPCacheOpChild(PCacheOpChild* aActor) +{ + delete aActor; + NoteDeletedActor(); + return true; +} + PCachePushStreamChild* CacheChild::AllocPCachePushStreamChild() { @@ -106,96 +150,17 @@ bool CacheChild::DeallocPCachePushStreamChild(PCachePushStreamChild* aActor) { delete aActor; + NoteDeletedActor(); return true; } -bool -CacheChild::RecvMatchResponse(const RequestId& requestId, const nsresult& aRv, - const PCacheResponseOrVoid& aResponse) +void +CacheChild::NoteDeletedActor() { - NS_ASSERT_OWNINGTHREAD(CacheChild); - - AddFeatureToStreamChild(aResponse, GetFeature()); - - nsRefPtr listener = mListener; - if (!listener) { - StartDestroyStreamChild(aResponse); - return true; + mNumChildActors -= 1; + if (!mNumChildActors && !mListener) { + unused << SendTeardown(); } - - listener->RecvMatchResponse(requestId, aRv, aResponse); - return true; -} - -bool -CacheChild::RecvMatchAllResponse(const RequestId& requestId, const nsresult& aRv, - nsTArray&& aResponses) -{ - NS_ASSERT_OWNINGTHREAD(CacheChild); - - AddFeatureToStreamChild(aResponses, GetFeature()); - - nsRefPtr listener = mListener; - if (!listener) { - StartDestroyStreamChild(aResponses); - return true; - } - - listener->RecvMatchAllResponse(requestId, aRv, aResponses); - return true; -} - -bool -CacheChild::RecvAddAllResponse(const RequestId& requestId, - const mozilla::ErrorResult& aError) -{ - NS_ASSERT_OWNINGTHREAD(CacheChild); - nsRefPtr listener = mListener; - if (listener) { - listener->RecvAddAllResponse(requestId, aError); - } - return true; -} - -bool -CacheChild::RecvPutResponse(const RequestId& aRequestId, const nsresult& aRv) -{ - NS_ASSERT_OWNINGTHREAD(CacheChild); - nsRefPtr listener = mListener; - if (listener) { - listener->RecvPutResponse(aRequestId, aRv); - } - return true; -} - -bool -CacheChild::RecvDeleteResponse(const RequestId& requestId, const nsresult& aRv, - const bool& result) -{ - NS_ASSERT_OWNINGTHREAD(CacheChild); - nsRefPtr listener = mListener; - if (listener) { - listener->RecvDeleteResponse(requestId, aRv, result); - } - return true; -} - -bool -CacheChild::RecvKeysResponse(const RequestId& requestId, const nsresult& aRv, - nsTArray&& aRequests) -{ - NS_ASSERT_OWNINGTHREAD(CacheChild); - - AddFeatureToStreamChild(aRequests, GetFeature()); - - nsRefPtr listener = mListener; - if (!listener) { - StartDestroyStreamChild(aRequests); - return true; - } - - listener->RecvKeysResponse(requestId, aRv, aRequests); - return true; } } // namespace cache diff --git a/dom/cache/CacheChild.h b/dom/cache/CacheChild.h index 58ca8ffb12..9c27aaf0f3 100644 --- a/dom/cache/CacheChild.h +++ b/dom/cache/CacheChild.h @@ -10,11 +10,19 @@ #include "mozilla/dom/cache/ActorChild.h" #include "mozilla/dom/cache/PCacheChild.h" +class nsIAsyncInputStream; +class nsIGlobalObject; + namespace mozilla { namespace dom { + +class Promise; + namespace cache { class Cache; +class CacheOpArgs; +class CachePushStreamChild; class CacheChild final : public PCacheChild , public ActorChild @@ -30,6 +38,13 @@ public: // trigger ActorDestroy() if it has not been called yet. void ClearListener(); + void + ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise, + const CacheOpArgs& aArgs); + + CachePushStreamChild* + CreatePushStream(nsIAsyncInputStream* aStream); + // ActorChild methods // Synchronously call ActorDestroy on our Cache listener and then start the @@ -41,35 +56,27 @@ private: virtual void ActorDestroy(ActorDestroyReason aReason) override; + virtual PCacheOpChild* + AllocPCacheOpChild(const CacheOpArgs& aOpArgs) override; + + virtual bool + DeallocPCacheOpChild(PCacheOpChild* aActor) override; + virtual PCachePushStreamChild* AllocPCachePushStreamChild() override; virtual bool DeallocPCachePushStreamChild(PCachePushStreamChild* aActor) override; - virtual bool - RecvMatchResponse(const RequestId& requestId, const nsresult& aRv, - const PCacheResponseOrVoid& aResponse) override; - virtual bool - RecvMatchAllResponse(const RequestId& requestId, const nsresult& aRv, - nsTArray&& responses) override; - virtual bool - RecvAddAllResponse(const RequestId& requestId, - const mozilla::ErrorResult& aError) override; - virtual bool - RecvPutResponse(const RequestId& aRequestId, - const nsresult& aRv) override; - virtual bool - RecvDeleteResponse(const RequestId& requestId, const nsresult& aRv, - const bool& result) override; - virtual bool - RecvKeysResponse(const RequestId& requestId, const nsresult& aRv, - nsTArray&& requests) override; + // utility methods + void + NoteDeletedActor(); // Use a weak ref so actor does not hold DOM object alive past content use. // The Cache object must call ClearListener() to null this before its // destroyed. Cache* MOZ_NON_OWNING_REF mListener; + uint32_t mNumChildActors; NS_DECL_OWNINGTHREAD }; diff --git a/dom/cache/CacheInitData.ipdlh b/dom/cache/CacheInitData.ipdlh index 34da66a034..e69de29bb2 100644 --- a/dom/cache/CacheInitData.ipdlh +++ b/dom/cache/CacheInitData.ipdlh @@ -1,24 +0,0 @@ -/* 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 PBackgroundSharedTypes; - -using mozilla::dom::cache::Namespace from "mozilla/dom/cache/Types.h"; - -namespace mozilla { -namespace dom { -namespace cache { - -// Data needed to initialize a CacheStorage or Cache backend. Don't put -// this with the other types in PCacheTypes.ipdlh since we want to import -// it into PBackground.ipdl. -struct CacheInitData -{ - Namespace namespaceEnum; - PrincipalInfo principalInfo; -}; - -} // namespace cache -} // namespace dom -} // namespace mozilla diff --git a/dom/cache/CacheOpChild.cpp b/dom/cache/CacheOpChild.cpp new file mode 100644 index 0000000000..7d892afa1a --- /dev/null +++ b/dom/cache/CacheOpChild.cpp @@ -0,0 +1,248 @@ +/* -*- 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/cache/CacheOpChild.h" + +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/Request.h" +#include "mozilla/dom/Response.h" +#include "mozilla/dom/cache/Cache.h" +#include "mozilla/dom/cache/CacheChild.h" +#include "mozilla/dom/cache/CacheStreamControlChild.h" + +namespace mozilla { +namespace dom { +namespace cache { + +namespace { + +void +AddFeatureToStreamChild(const CacheReadStream& aReadStream, Feature* aFeature) +{ + MOZ_ASSERT_IF(!NS_IsMainThread(), aFeature); + CacheStreamControlChild* cacheControl = + static_cast(aReadStream.controlChild()); + if (cacheControl) { + cacheControl->SetFeature(aFeature); + } +} + +void +AddFeatureToStreamChild(const CacheResponse& aResponse, Feature* aFeature) +{ + MOZ_ASSERT_IF(!NS_IsMainThread(), aFeature); + + if (aResponse.body().type() == CacheReadStreamOrVoid::Tvoid_t) { + return; + } + + AddFeatureToStreamChild(aResponse.body().get_CacheReadStream(), aFeature); +} + +void +AddFeatureToStreamChild(const CacheRequest& aRequest, Feature* aFeature) +{ + MOZ_ASSERT_IF(!NS_IsMainThread(), aFeature); + + if (aRequest.body().type() == CacheReadStreamOrVoid::Tvoid_t) { + return; + } + + AddFeatureToStreamChild(aRequest.body().get_CacheReadStream(), aFeature); +} + +} // anonymous namespace + +CacheOpChild::CacheOpChild(Feature* aFeature, nsIGlobalObject* aGlobal, + Promise* aPromise) + : mGlobal(aGlobal) + , mPromise(aPromise) +{ + MOZ_ASSERT(mGlobal); + MOZ_ASSERT(mPromise); + + MOZ_ASSERT_IF(!NS_IsMainThread(), aFeature); + SetFeature(aFeature); +} + +CacheOpChild::~CacheOpChild() +{ + NS_ASSERT_OWNINGTHREAD(CacheOpChild); + MOZ_ASSERT(!mPromise); +} + +void +CacheOpChild::ActorDestroy(ActorDestroyReason aReason) +{ + NS_ASSERT_OWNINGTHREAD(CacheOpChild); + + // If the actor was terminated for some unknown reason, then indicate the + // operation is dead. + if (mPromise) { + mPromise->MaybeReject(NS_ERROR_FAILURE); + mPromise = nullptr; + } + + RemoveFeature(); +} + +bool +CacheOpChild::Recv__delete__(const ErrorResult& aRv, + const CacheOpResult& aResult) +{ + NS_ASSERT_OWNINGTHREAD(CacheOpChild); + + if (aRv.Failed()) { + MOZ_ASSERT(aResult.type() == CacheOpResult::Tvoid_t); + // TODO: Remove this const_cast (bug 1152078). + // It is safe for now since this ErrorResult is handed off to us by IPDL + // and is thrown into the trash afterwards. + mPromise->MaybeReject(const_cast(aRv)); + mPromise = nullptr; + return true; + } + + switch (aResult.type()) { + case CacheOpResult::TCacheMatchResult: + { + HandleResponse(aResult.get_CacheMatchResult().responseOrVoid()); + break; + } + case CacheOpResult::TCacheMatchAllResult: + { + HandleResponseList(aResult.get_CacheMatchAllResult().responseList()); + break; + } + case CacheOpResult::TCacheAddAllResult: + case CacheOpResult::TCachePutAllResult: + { + mPromise->MaybeResolve(JS::UndefinedHandleValue); + break; + } + case CacheOpResult::TCacheDeleteResult: + { + mPromise->MaybeResolve(aResult.get_CacheDeleteResult().success()); + break; + } + case CacheOpResult::TCacheKeysResult: + { + HandleRequestList(aResult.get_CacheKeysResult().requestList()); + break; + } + case CacheOpResult::TStorageMatchResult: + { + HandleResponse(aResult.get_StorageMatchResult().responseOrVoid()); + break; + } + case CacheOpResult::TStorageHasResult: + { + mPromise->MaybeResolve(aResult.get_StorageHasResult().success()); + break; + } + case CacheOpResult::TStorageOpenResult: + { + auto actor = static_cast( + aResult.get_StorageOpenResult().actorChild()); + actor->SetFeature(GetFeature()); + nsRefPtr cache = new Cache(mGlobal, actor); + mPromise->MaybeResolve(cache); + break; + } + case CacheOpResult::TStorageDeleteResult: + { + mPromise->MaybeResolve(aResult.get_StorageDeleteResult().success()); + break; + } + case CacheOpResult::TStorageKeysResult: + { + mPromise->MaybeResolve(aResult.get_StorageKeysResult().keyList()); + break; + } + default: + MOZ_CRASH("Unknown Cache op result type!"); + } + + mPromise = nullptr; + + return true; +} + +void +CacheOpChild::StartDestroy() +{ + NS_ASSERT_OWNINGTHREAD(CacheOpChild); + + // Do not cancel on-going operations when Feature calls this. Instead, keep + // the Worker alive until we are done. +} + +nsIGlobalObject* +CacheOpChild::GetGlobalObject() const +{ + return mGlobal; +} + +#ifdef DEBUG +void +CacheOpChild::AssertOwningThread() const +{ + NS_ASSERT_OWNINGTHREAD(CacheOpChild); +} +#endif + +CachePushStreamChild* +CacheOpChild::CreatePushStream(nsIAsyncInputStream* aStream) +{ + MOZ_CRASH("CacheOpChild should never create a push stream actor!"); +} + +void +CacheOpChild::HandleResponse(const CacheResponseOrVoid& aResponseOrVoid) +{ + if (aResponseOrVoid.type() == CacheResponseOrVoid::Tvoid_t) { + mPromise->MaybeResolve(JS::UndefinedHandleValue); + return; + } + + const CacheResponse& cacheResponse = aResponseOrVoid.get_CacheResponse(); + + AddFeatureToStreamChild(cacheResponse, GetFeature()); + nsRefPtr response = ToResponse(cacheResponse); + + mPromise->MaybeResolve(response); +} + +void +CacheOpChild::HandleResponseList(const nsTArray& aResponseList) +{ + nsAutoTArray, 256> responses; + responses.SetCapacity(aResponseList.Length()); + + for (uint32_t i = 0; i < aResponseList.Length(); ++i) { + AddFeatureToStreamChild(aResponseList[i], GetFeature()); + responses.AppendElement(ToResponse(aResponseList[i])); + } + + mPromise->MaybeResolve(responses); +} + +void +CacheOpChild::HandleRequestList(const nsTArray& aRequestList) +{ + nsAutoTArray, 256> requests; + requests.SetCapacity(aRequestList.Length()); + + for (uint32_t i = 0; i < aRequestList.Length(); ++i) { + AddFeatureToStreamChild(aRequestList[i], GetFeature()); + requests.AppendElement(ToRequest(aRequestList[i])); + } + + mPromise->MaybeResolve(requests); +} + +} // namespace cache +} // namespace dom +} // namespace mozilla diff --git a/dom/cache/CacheOpChild.h b/dom/cache/CacheOpChild.h new file mode 100644 index 0000000000..850b5d30fe --- /dev/null +++ b/dom/cache/CacheOpChild.h @@ -0,0 +1,80 @@ +/* -*- 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_cache_CacheOpChild_h +#define mozilla_dom_cache_CacheOpChild_h + +#include "mozilla/dom/cache/ActorChild.h" +#include "mozilla/dom/cache/PCacheOpChild.h" +#include "mozilla/dom/cache/TypeUtils.h" +#include "mozilla/RefPtr.h" + +class nsIGlobalObject; + +namespace mozilla { +namespace dom { + +class Promise; + +namespace cache { + +class CacheOpChild final : public PCacheOpChild + , public ActorChild + , public TypeUtils +{ + friend class CacheChild; + friend class CacheStorageChild; + +private: + // This class must be constructed by CacheChild or CacheStorageChild using + // their ExecuteOp() factory method. + CacheOpChild(Feature* aFeature, nsIGlobalObject* aGlobal, Promise* aPromise); + ~CacheOpChild(); + + // PCacheOpChild methods + virtual void + ActorDestroy(ActorDestroyReason aReason) override; + + virtual bool + Recv__delete__(const ErrorResult& aRv, const CacheOpResult& aResult) override; + + // ActorChild methods + virtual void + StartDestroy() override; + + // TypeUtils methods + virtual nsIGlobalObject* + GetGlobalObject() const override; + +#ifdef DEBUG + virtual void + AssertOwningThread() const override; +#endif + + virtual CachePushStreamChild* + CreatePushStream(nsIAsyncInputStream* aStream) override; + + // Utility methods + void + HandleResponse(const CacheResponseOrVoid& aResponseOrVoid); + + void + HandleResponseList(const nsTArray& aResponseList); + + void + HandleRequestList(const nsTArray& aRequestList); + + nsCOMPtr mGlobal; + RefPtr mPromise; + + NS_DECL_OWNINGTHREAD +}; + +} // namespace cache +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_cache_CacheOpChild_h diff --git a/dom/cache/CacheOpParent.cpp b/dom/cache/CacheOpParent.cpp new file mode 100644 index 0000000000..a03030e520 --- /dev/null +++ b/dom/cache/CacheOpParent.cpp @@ -0,0 +1,287 @@ +/* -*- 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/cache/CacheOpParent.h" + +#include "mozilla/unused.h" +#include "mozilla/dom/cache/AutoUtils.h" +#include "mozilla/dom/cache/CachePushStreamParent.h" +#include "mozilla/dom/cache/ReadStream.h" +#include "mozilla/dom/cache/SavedTypes.h" +#include "mozilla/ipc/FileDescriptorSetParent.h" +#include "mozilla/ipc/InputStreamUtils.h" + +namespace mozilla { +namespace dom { +namespace cache { + +using mozilla::ipc::FileDescriptorSetParent; +using mozilla::ipc::PBackgroundParent; + +CacheOpParent::CacheOpParent(PBackgroundParent* aIpcManager, CacheId aCacheId, + const CacheOpArgs& aOpArgs) + : mIpcManager(aIpcManager) + , mCacheId(aCacheId) + , mNamespace(INVALID_NAMESPACE) + , mOpArgs(aOpArgs) +{ + MOZ_ASSERT(mIpcManager); +} + +CacheOpParent::CacheOpParent(PBackgroundParent* aIpcManager, + Namespace aNamespace, const CacheOpArgs& aOpArgs) + : mIpcManager(aIpcManager) + , mCacheId(INVALID_CACHE_ID) + , mNamespace(aNamespace) + , mOpArgs(aOpArgs) +{ + MOZ_ASSERT(mIpcManager); +} + +CacheOpParent::~CacheOpParent() +{ + NS_ASSERT_OWNINGTHREAD(CacheOpParent); +} + +void +CacheOpParent::Execute(ManagerId* aManagerId) +{ + NS_ASSERT_OWNINGTHREAD(CacheOpParent); + MOZ_ASSERT(!mManager); + MOZ_ASSERT(!mVerifier); + + nsRefPtr manager; + nsresult rv = Manager::GetOrCreate(aManagerId, getter_AddRefs(manager)); + if (NS_WARN_IF(NS_FAILED(rv))) { + unused << Send__delete__(this, ErrorResult(rv), void_t()); + return; + } + + Execute(manager); +} + +void +CacheOpParent::Execute(Manager* aManager) +{ + NS_ASSERT_OWNINGTHREAD(CacheOpParent); + MOZ_ASSERT(!mManager); + MOZ_ASSERT(!mVerifier); + + mManager = aManager; + + // Handle add/addAll op with a FetchPut object + if (mOpArgs.type() == CacheOpArgs::TCacheAddAllArgs) { + MOZ_ASSERT(mCacheId != INVALID_CACHE_ID); + + const CacheAddAllArgs& args = mOpArgs.get_CacheAddAllArgs(); + const nsTArray& list = args.requestList(); + + nsAutoTArray, 256> requestStreamList; + for (uint32_t i = 0; i < list.Length(); ++i) { + requestStreamList.AppendElement(DeserializeCacheStream(list[i].body())); + } + + nsRefPtr fetchPut; + nsresult rv = FetchPut::Create(this, mManager, mCacheId, list, + requestStreamList, getter_AddRefs(fetchPut)); + if (NS_WARN_IF(NS_FAILED(rv))) { + OnOpComplete(ErrorResult(rv), CacheAddAllResult()); + return; + } + + mFetchPutList.AppendElement(fetchPut.forget()); + return; + } + + // Handle put op + if (mOpArgs.type() == CacheOpArgs::TCachePutAllArgs) { + MOZ_ASSERT(mCacheId != INVALID_CACHE_ID); + + const CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs(); + const nsTArray& list = args.requestResponseList(); + + nsAutoTArray, 256> requestStreamList; + nsAutoTArray, 256> responseStreamList; + + for (uint32_t i = 0; i < list.Length(); ++i) { + requestStreamList.AppendElement( + DeserializeCacheStream(list[i].request().body())); + responseStreamList.AppendElement( + DeserializeCacheStream(list[i].response().body())); + } + + mManager->ExecutePutAll(this, mCacheId, args.requestResponseList(), + requestStreamList, responseStreamList); + return; + } + + // Handle all other cache ops + if (mCacheId != INVALID_CACHE_ID) { + MOZ_ASSERT(mNamespace == INVALID_NAMESPACE); + mManager->ExecuteCacheOp(this, mCacheId, mOpArgs); + return; + } + + // Handle all storage ops + MOZ_ASSERT(mNamespace != INVALID_NAMESPACE); + mManager->ExecuteStorageOp(this, mNamespace, mOpArgs); +} + +void +CacheOpParent::WaitForVerification(PrincipalVerifier* aVerifier) +{ + NS_ASSERT_OWNINGTHREAD(CacheOpParent); + MOZ_ASSERT(!mManager); + MOZ_ASSERT(!mVerifier); + + mVerifier = aVerifier; + mVerifier->AddListener(this); +} + +void +CacheOpParent::ActorDestroy(ActorDestroyReason aReason) +{ + NS_ASSERT_OWNINGTHREAD(CacheOpParent); + + if (mVerifier) { + mVerifier->RemoveListener(this); + mVerifier = nullptr; + } + + for (uint32_t i = 0; i < mFetchPutList.Length(); ++i) { + mFetchPutList[i]->ClearListener(); + } + mFetchPutList.Clear(); + + if (mManager) { + mManager->RemoveListener(this); + mManager = nullptr; + } + + mIpcManager = nullptr; +} + +void +CacheOpParent::OnPrincipalVerified(nsresult aRv, ManagerId* aManagerId) +{ + NS_ASSERT_OWNINGTHREAD(CacheOpParent); + + mVerifier->RemoveListener(this); + mVerifier = nullptr; + + if (NS_WARN_IF(NS_FAILED(aRv))) { + unused << Send__delete__(this, ErrorResult(aRv), void_t()); + return; + } + + Execute(aManagerId); +} + +void +CacheOpParent::OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult, + CacheId aOpenedCacheId, + const nsTArray& aSavedResponseList, + const nsTArray& aSavedRequestList, + StreamList* aStreamList) +{ + NS_ASSERT_OWNINGTHREAD(CacheOpParent); + MOZ_ASSERT(mIpcManager); + MOZ_ASSERT(mManager); + + // Never send an op-specific result if we have an error. Instead, send + // void_t() to ensure that we don't leak actors on the child side. + if (aRv.Failed()) { + unused << Send__delete__(this, aRv, void_t()); + aRv.ClearMessage(); // This may contain a TypeError. + return; + } + + // The result must contain the appropriate type at this point. It may + // or may not contain the additional result data yet. For types that + // do not need special processing, it should already be set. If the + // result requires actor-specific operations, then we do that below. + // If the type and data types don't match, then we will trigger an + // assertion in AutoParentOpResult::Add(). + AutoParentOpResult result(mIpcManager, aResult); + + if (aOpenedCacheId != INVALID_CACHE_ID) { + result.Add(aOpenedCacheId, mManager); + } + + for (uint32_t i = 0; i < aSavedResponseList.Length(); ++i) { + result.Add(aSavedResponseList[i], aStreamList); + } + + for (uint32_t i = 0; i < aSavedRequestList.Length(); ++i) { + result.Add(aSavedRequestList[i], aStreamList); + } + + unused << Send__delete__(this, aRv, result.SendAsOpResult()); +} + +void +CacheOpParent::OnFetchPut(FetchPut* aFetchPut, ErrorResult&& aRv) +{ + NS_ASSERT_OWNINGTHREAD(CacheOpParent); + MOZ_ASSERT(aFetchPut); + + aFetchPut->ClearListener(); + MOZ_ALWAYS_TRUE(mFetchPutList.RemoveElement(aFetchPut)); + + OnOpComplete(Move(aRv), CacheAddAllResult()); +} + +already_AddRefed +CacheOpParent::DeserializeCacheStream(const CacheReadStreamOrVoid& aStreamOrVoid) +{ + if (aStreamOrVoid.type() == CacheReadStreamOrVoid::Tvoid_t) { + return nullptr; + } + + nsCOMPtr stream; + const CacheReadStream& readStream = aStreamOrVoid.get_CacheReadStream(); + + // Option 1: A push stream actor was sent for nsPipe data + if (readStream.pushStreamParent()) { + MOZ_ASSERT(!readStream.controlParent()); + CachePushStreamParent* pushStream = + static_cast(readStream.pushStreamParent()); + stream = pushStream->TakeReader(); + MOZ_ASSERT(stream); + return stream.forget(); + } + + // Option 2: One of our own ReadStreams was passed back to us with a stream + // control actor. + stream = ReadStream::Create(readStream); + if (stream) { + return stream.forget(); + } + + // Option 3: A stream was serialized using normal methods. + nsAutoTArray fds; + if (readStream.fds().type() == + OptionalFileDescriptorSet::TPFileDescriptorSetChild) { + + FileDescriptorSetParent* fdSetActor = + static_cast(readStream.fds().get_PFileDescriptorSetParent()); + MOZ_ASSERT(fdSetActor); + + fdSetActor->ForgetFileDescriptors(fds); + MOZ_ASSERT(!fds.IsEmpty()); + + if (!fdSetActor->Send__delete__(fdSetActor)) { + // child process is gone, warn and allow actor to clean up normally + NS_WARNING("Cache failed to delete fd set actor."); + } + } + + return DeserializeInputStream(readStream.params(), fds); +} + +} // namespace cache +} // namespace dom +} // namespace mozilla diff --git a/dom/cache/CacheOpParent.h b/dom/cache/CacheOpParent.h new file mode 100644 index 0000000000..6b6679e29e --- /dev/null +++ b/dom/cache/CacheOpParent.h @@ -0,0 +1,87 @@ +/* -*- 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_cache_CacheOpParent_h +#define mozilla_dom_cache_CacheOpParent_h + +#include "mozilla/dom/cache/FetchPut.h" +#include "mozilla/dom/cache/Manager.h" +#include "mozilla/dom/cache/PCacheOpParent.h" +#include "mozilla/dom/cache/PrincipalVerifier.h" +#include "nsTArray.h" + +namespace mozilla { +namespace ipc { +class PBackgroundParent; +} +namespace dom { +namespace cache { + +class CacheOpParent final : public PCacheOpParent + , public PrincipalVerifier::Listener + , public Manager::Listener + , public FetchPut::Listener +{ + // to allow use of convenience overrides + using Manager::Listener::OnOpComplete; + +public: + CacheOpParent(mozilla::ipc::PBackgroundParent* aIpcManager, CacheId aCacheId, + const CacheOpArgs& aOpArgs); + CacheOpParent(mozilla::ipc::PBackgroundParent* aIpcManager, + Namespace aNamespace, const CacheOpArgs& aOpArgs); + ~CacheOpParent(); + + void + Execute(ManagerId* aManagerId); + + void + Execute(Manager* aManager); + + void + WaitForVerification(PrincipalVerifier* aVerifier); + +private: + // PCacheOpParent methods + virtual void + ActorDestroy(ActorDestroyReason aReason) override; + + // PrincipalVerifier::Listener methods + virtual void + OnPrincipalVerified(nsresult aRv, ManagerId* aManagerId) override; + + // Manager::Listener methods + virtual void + OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult, + CacheId aOpenedCacheId, + const nsTArray& aSavedResponseList, + const nsTArray& aSavedRequestList, + StreamList* aStreamList) override; + + // FetchPut::Listener methods + virtual void + OnFetchPut(FetchPut* aFetchPut, ErrorResult&& aRv) override; + + // utility methods + already_AddRefed + DeserializeCacheStream(const CacheReadStreamOrVoid& aStreamOrVoid); + + mozilla::ipc::PBackgroundParent* mIpcManager; + const CacheId mCacheId; + const Namespace mNamespace; + const CacheOpArgs mOpArgs; + nsRefPtr mManager; + nsRefPtr mVerifier; + nsTArray> mFetchPutList; + + NS_DECL_OWNINGTHREAD +}; + +} // namespace cache +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_cache_CacheOpParent_h diff --git a/dom/cache/CacheParent.cpp b/dom/cache/CacheParent.cpp index c79da5d651..15ca59b2a4 100644 --- a/dom/cache/CacheParent.cpp +++ b/dom/cache/CacheParent.cpp @@ -6,27 +6,14 @@ #include "mozilla/dom/cache/CacheParent.h" -#include "mozilla/DebugOnly.h" -#include "mozilla/dom/cache/AutoUtils.h" +#include "mozilla/dom/cache/CacheOpParent.h" #include "mozilla/dom/cache/CachePushStreamParent.h" -#include "mozilla/dom/cache/CacheStreamControlParent.h" -#include "mozilla/dom/cache/ReadStream.h" -#include "mozilla/dom/cache/SavedTypes.h" -#include "mozilla/dom/cache/StreamList.h" -#include "mozilla/ipc/InputStreamUtils.h" -#include "mozilla/ipc/PBackgroundParent.h" -#include "mozilla/ipc/FileDescriptorSetParent.h" -#include "mozilla/ipc/PFileDescriptorSetParent.h" #include "nsCOMPtr.h" namespace mozilla { namespace dom { namespace cache { -using mozilla::dom::ErrNum; -using mozilla::ipc::FileDescriptorSetParent; -using mozilla::ipc::PFileDescriptorSetParent; - // Declared in ActorUtils.h void DeallocPCacheParent(PCacheParent* aActor) @@ -47,22 +34,48 @@ CacheParent::~CacheParent() { MOZ_COUNT_DTOR(cache::CacheParent); MOZ_ASSERT(!mManager); - MOZ_ASSERT(mFetchPutList.IsEmpty()); } void CacheParent::ActorDestroy(ActorDestroyReason aReason) { MOZ_ASSERT(mManager); - for (uint32_t i = 0; i < mFetchPutList.Length(); ++i) { - mFetchPutList[i]->ClearListener(); - } - mFetchPutList.Clear(); - mManager->RemoveListener(this); mManager->ReleaseCacheId(mCacheId); mManager = nullptr; } +PCacheOpParent* +CacheParent::AllocPCacheOpParent(const CacheOpArgs& aOpArgs) +{ + if (aOpArgs.type() != CacheOpArgs::TCacheMatchArgs && + aOpArgs.type() != CacheOpArgs::TCacheMatchAllArgs && + aOpArgs.type() != CacheOpArgs::TCacheAddAllArgs && + aOpArgs.type() != CacheOpArgs::TCachePutAllArgs && + aOpArgs.type() != CacheOpArgs::TCacheDeleteArgs && + aOpArgs.type() != CacheOpArgs::TCacheKeysArgs) + { + MOZ_CRASH("Invalid operation sent to Cache actor!"); + } + + return new CacheOpParent(Manager(), mCacheId, aOpArgs); +} + +bool +CacheParent::DeallocPCacheOpParent(PCacheOpParent* aActor) +{ + delete aActor; + return true; +} + +bool +CacheParent::RecvPCacheOpConstructor(PCacheOpParent* aActor, + const CacheOpArgs& aOpArgs) +{ + auto actor = static_cast(aActor); + actor->Execute(mManager); + return true; +} + PCachePushStreamParent* CacheParent::AllocPCachePushStreamParent() { @@ -86,238 +99,6 @@ CacheParent::RecvTeardown() return true; } -bool -CacheParent::RecvMatch(const RequestId& aRequestId, const PCacheRequest& aRequest, - const PCacheQueryParams& aParams) -{ - MOZ_ASSERT(mManager); - mManager->CacheMatch(this, aRequestId, mCacheId, aRequest, - aParams); - return true; -} - -bool -CacheParent::RecvMatchAll(const RequestId& aRequestId, - const PCacheRequestOrVoid& aRequest, - const PCacheQueryParams& aParams) -{ - MOZ_ASSERT(mManager); - mManager->CacheMatchAll(this, aRequestId, mCacheId, aRequest, aParams); - return true; -} - -bool -CacheParent::RecvAddAll(const RequestId& aRequestId, - nsTArray&& aRequests) -{ - nsAutoTArray, 256> requestStreams; - requestStreams.SetCapacity(aRequests.Length()); - - for (uint32_t i = 0; i < aRequests.Length(); ++i) { - requestStreams.AppendElement(DeserializeCacheStream(aRequests[i].body())); - } - - nsRefPtr fetchPut; - nsresult rv = FetchPut::Create(this, mManager, aRequestId, mCacheId, - aRequests, requestStreams, - getter_AddRefs(fetchPut)); - if (NS_WARN_IF(NS_FAILED(rv))) { - MOZ_ASSERT(rv != NS_ERROR_TYPE_ERR); - ErrorResult error; - error.Throw(rv); - if (!SendAddAllResponse(aRequestId, error)) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("Cache failed to send AddAll response."); - } - return true; - } - - mFetchPutList.AppendElement(fetchPut.forget()); - - return true; -} - -bool -CacheParent::RecvPut(const RequestId& aRequestId, - const CacheRequestResponse& aPut) -{ - MOZ_ASSERT(mManager); - - nsAutoTArray putList; - putList.AppendElement(aPut); - - nsAutoTArray, 1> requestStreamList; - nsAutoTArray, 1> responseStreamList; - - requestStreamList.AppendElement( - DeserializeCacheStream(aPut.request().body())); - responseStreamList.AppendElement( - DeserializeCacheStream(aPut.response().body())); - - - mManager->CachePutAll(this, aRequestId, mCacheId, putList, requestStreamList, - responseStreamList); - - return true; -} - -bool -CacheParent::RecvDelete(const RequestId& aRequestId, - const PCacheRequest& aRequest, - const PCacheQueryParams& aParams) -{ - MOZ_ASSERT(mManager); - mManager->CacheDelete(this, aRequestId, mCacheId, aRequest, aParams); - return true; -} - -bool -CacheParent::RecvKeys(const RequestId& aRequestId, - const PCacheRequestOrVoid& aRequest, - const PCacheQueryParams& aParams) -{ - MOZ_ASSERT(mManager); - mManager->CacheKeys(this, aRequestId, mCacheId, aRequest, aParams); - return true; -} - -void -CacheParent::OnCacheMatch(RequestId aRequestId, nsresult aRv, - const SavedResponse* aSavedResponse, - StreamList* aStreamList) -{ - AutoParentResponseOrVoid response(Manager()); - - // no match - if (NS_FAILED(aRv) || !aSavedResponse || !aStreamList) { - if (!SendMatchResponse(aRequestId, aRv, response.SendAsResponseOrVoid())) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("Cache failed to send Match response."); - } - return; - } - - if (aSavedResponse) { - response.Add(*aSavedResponse, aStreamList); - } - - if (!SendMatchResponse(aRequestId, aRv, response.SendAsResponseOrVoid())) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("Cache failed to send Match response."); - } -} - -void -CacheParent::OnCacheMatchAll(RequestId aRequestId, nsresult aRv, - const nsTArray& aSavedResponses, - StreamList* aStreamList) -{ - AutoParentResponseList responses(Manager(), aSavedResponses.Length()); - - for (uint32_t i = 0; i < aSavedResponses.Length(); ++i) { - responses.Add(aSavedResponses[i], aStreamList); - } - - if (!SendMatchAllResponse(aRequestId, aRv, responses.SendAsResponseList())) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("Cache failed to send MatchAll response."); - } -} - -void -CacheParent::OnCachePutAll(RequestId aRequestId, nsresult aRv) -{ - if (!SendPutResponse(aRequestId, aRv)) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("Cache failed to send Put response."); - } -} - -void -CacheParent::OnCacheDelete(RequestId aRequestId, nsresult aRv, bool aSuccess) -{ - if (!SendDeleteResponse(aRequestId, aRv, aSuccess)) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("Cache failed to send Delete response."); - } -} - -void -CacheParent::OnCacheKeys(RequestId aRequestId, nsresult aRv, - const nsTArray& aSavedRequests, - StreamList* aStreamList) -{ - AutoParentRequestList requests(Manager(), aSavedRequests.Length()); - - for (uint32_t i = 0; i < aSavedRequests.Length(); ++i) { - requests.Add(aSavedRequests[i], aStreamList); - } - - if (!SendKeysResponse(aRequestId, aRv, requests.SendAsRequestList())) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("Cache failed to send Keys response."); - } -} - -void -CacheParent::OnFetchPut(FetchPut* aFetchPut, RequestId aRequestId, const ErrorResult& aRv) -{ - aFetchPut->ClearListener(); - mFetchPutList.RemoveElement(aFetchPut); - if (!SendAddAllResponse(aRequestId, aRv)) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("Cache failed to send AddAll response."); - } -} - -already_AddRefed -CacheParent::DeserializeCacheStream(const PCacheReadStreamOrVoid& aStreamOrVoid) -{ - if (aStreamOrVoid.type() == PCacheReadStreamOrVoid::Tvoid_t) { - return nullptr; - } - - nsCOMPtr stream; - const PCacheReadStream& readStream = aStreamOrVoid.get_PCacheReadStream(); - - // Option 1: A push stream actor was sent for nsPipe data - if (readStream.pushStreamParent()) { - MOZ_ASSERT(!readStream.controlParent()); - CachePushStreamParent* pushStream = - static_cast(readStream.pushStreamParent()); - stream = pushStream->TakeReader(); - MOZ_ASSERT(stream); - return stream.forget(); - } - - // Option 2: One of our own ReadStreams was passed back to us with a stream - // control actor. - stream = ReadStream::Create(readStream); - if (stream) { - return stream.forget(); - } - - // Option 3: A stream was serialized using normal methods. - nsAutoTArray fds; - if (readStream.fds().type() == - OptionalFileDescriptorSet::TPFileDescriptorSetChild) { - - FileDescriptorSetParent* fdSetActor = - static_cast(readStream.fds().get_PFileDescriptorSetParent()); - MOZ_ASSERT(fdSetActor); - - fdSetActor->ForgetFileDescriptors(fds); - MOZ_ASSERT(!fds.IsEmpty()); - - if (!fdSetActor->Send__delete__(fdSetActor)) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("Cache failed to delete fd set actor."); - } - } - - return DeserializeInputStream(readStream.params(), fds); -} - } // namespace cache } // namespace dom } // namesapce mozilla diff --git a/dom/cache/CacheParent.h b/dom/cache/CacheParent.h index 2aa757de69..72d47364db 100644 --- a/dom/cache/CacheParent.h +++ b/dom/cache/CacheParent.h @@ -7,77 +7,46 @@ #ifndef mozilla_dom_cache_CacheParent_h #define mozilla_dom_cache_CacheParent_h -#include "mozilla/dom/cache/FetchPut.h" -#include "mozilla/dom/cache/Manager.h" #include "mozilla/dom/cache/PCacheParent.h" #include "mozilla/dom/cache/Types.h" -struct nsID; -template class nsRefPtr; - namespace mozilla { namespace dom { namespace cache { -struct SavedResponse; +class Manager; class CacheParent final : public PCacheParent - , public Manager::Listener - , public FetchPut::Listener { public: CacheParent(cache::Manager* aManager, CacheId aCacheId); virtual ~CacheParent(); private: - // PCacheParent method + // PCacheParent methods virtual void ActorDestroy(ActorDestroyReason aReason) override; - virtual PCachePushStreamParent* AllocPCachePushStreamParent() override; - virtual bool DeallocPCachePushStreamParent(PCachePushStreamParent* aActor) override; - virtual bool RecvTeardown() override; - virtual bool - RecvMatch(const RequestId& aRequestId, const PCacheRequest& aRequest, - const PCacheQueryParams& aParams) override; - virtual bool - RecvMatchAll(const RequestId& aRequestId, const PCacheRequestOrVoid& aRequest, - const PCacheQueryParams& aParams) override; - virtual bool - RecvAddAll(const RequestId& aRequestId, - nsTArray&& aRequests) override; - virtual bool - RecvPut(const RequestId& aRequestId, - const CacheRequestResponse& aPut) override; - virtual bool - RecvDelete(const RequestId& aRequestId, const PCacheRequest& aRequest, - const PCacheQueryParams& aParams) override; - virtual bool - RecvKeys(const RequestId& aRequestId, const PCacheRequestOrVoid& aRequest, - const PCacheQueryParams& aParams) override; - // Manager::Listener methods - virtual void OnCacheMatch(RequestId aRequestId, nsresult aRv, - const SavedResponse* aSavedResponse, - StreamList* aStreamList) override; - virtual void OnCacheMatchAll(RequestId aRequestId, nsresult aRv, - const nsTArray& aSavedResponses, - StreamList* aStreamList) override; - virtual void OnCachePutAll(RequestId aRequestId, nsresult aRv) override; - virtual void OnCacheDelete(RequestId aRequestId, nsresult aRv, - bool aSuccess) override; - virtual void OnCacheKeys(RequestId aRequestId, nsresult aRv, - const nsTArray& aSavedRequests, - StreamList* aStreamList) override; + virtual PCacheOpParent* + AllocPCacheOpParent(const CacheOpArgs& aOpArgs) override; - // FetchPut::Listener methods - virtual void OnFetchPut(FetchPut* aFetchPut, RequestId aRequestId, - const mozilla::ErrorResult& aRv) override; + virtual bool + DeallocPCacheOpParent(PCacheOpParent* aActor) override; - already_AddRefed - DeserializeCacheStream(const PCacheReadStreamOrVoid& aStreamOrVoid); + virtual bool + RecvPCacheOpConstructor(PCacheOpParent* actor, + const CacheOpArgs& aOpArgs) override; + + virtual PCachePushStreamParent* + AllocPCachePushStreamParent() override; + + virtual bool + DeallocPCachePushStreamParent(PCachePushStreamParent* aActor) override; + + virtual bool + RecvTeardown() override; nsRefPtr mManager; const CacheId mCacheId; - nsTArray> mFetchPutList; }; } // namespace cache diff --git a/dom/cache/CachePushStreamChild.h b/dom/cache/CachePushStreamChild.h index cfbf175918..03b1d3ed4f 100644 --- a/dom/cache/CachePushStreamChild.h +++ b/dom/cache/CachePushStreamChild.h @@ -20,17 +20,20 @@ namespace cache { class CachePushStreamChild final : public PCachePushStreamChild , public ActorChild { + friend class CacheChild; + public: - CachePushStreamChild(Feature* aFeature, nsIAsyncInputStream* aStream); - ~CachePushStreamChild(); + void Start(); virtual void StartDestroy() override; - void Start(); - private: class Callback; + // This class must be constructed using CacheChild::CreatePushStream() + CachePushStreamChild(Feature* aFeature, nsIAsyncInputStream* aStream); + ~CachePushStreamChild(); + // PCachePushStreamChild methods virtual void ActorDestroy(ActorDestroyReason aReason) override; diff --git a/dom/cache/CacheStorage.cpp b/dom/cache/CacheStorage.cpp index 39cef3d87d..a4ff49ed7a 100644 --- a/dom/cache/CacheStorage.cpp +++ b/dom/cache/CacheStorage.cpp @@ -41,24 +41,26 @@ using mozilla::ipc::PrincipalToPrincipalInfo; NS_IMPL_CYCLE_COLLECTING_ADDREF(mozilla::dom::cache::CacheStorage); NS_IMPL_CYCLE_COLLECTING_RELEASE(mozilla::dom::cache::CacheStorage); -NS_IMPL_CYCLE_COLLECTION_CLASS(mozilla::dom::cache::CacheStorage) -NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(mozilla::dom::cache::CacheStorage) - tmp->DisconnectFromActor(); - NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal, mRequestPromises) - NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER -NS_IMPL_CYCLE_COLLECTION_UNLINK_END -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(mozilla::dom::cache::CacheStorage) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal, mRequestPromises) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END -NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(mozilla::dom::cache::CacheStorage) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(mozilla::dom::cache::CacheStorage, + mGlobal); NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CacheStorage) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIIPCBackgroundChildCreateCallback) + NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback) NS_INTERFACE_MAP_END +// We cannot reference IPC types in a webidl binding implementation header. So +// define this in the .cpp and use heap storage in the mPendingRequests list. +struct CacheStorage::Entry final +{ + nsRefPtr mPromise; + CacheOpArgs mArgs; + // We cannot add the requests until after the actor is present. So store + // the request data separately for now. + nsRefPtr mRequest; +}; + // static already_AddRefed CacheStorage::CreateOnMainThread(Namespace aNamespace, nsIGlobalObject* aGlobal, @@ -175,29 +177,31 @@ CacheStorage::Match(const RequestOrUSVString& aRequest, { NS_ASSERT_OWNINGTHREAD(CacheStorage); + if (mFailedActor) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + nsRefPtr request = ToInternalRequest(aRequest, IgnoreBody, + aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + nsRefPtr promise = Promise::Create(mGlobal, aRv); if (!promise) { return nullptr; } - if (mFailedActor) { - promise->MaybeReject(NS_ERROR_UNEXPECTED); - return promise.forget(); - } + CacheQueryParams params; + ToCacheQueryParams(params, aOptions); - RequestId requestId = AddRequestPromise(promise, aRv); - - Entry entry; - entry.mRequestId = requestId; - entry.mOp = OP_MATCH; - entry.mOptions = aOptions; - entry.mRequest = ToInternalRequest(aRequest, IgnoreBody, aRv); - if (aRv.Failed()) { - return nullptr; - } - - mPendingRequests.AppendElement(entry); + nsAutoPtr entry(new Entry()); + entry->mPromise = promise; + entry->mArgs = StorageMatchArgs(CacheRequest(), params); + entry->mRequest = request; + mPendingRequests.AppendElement(entry.forget()); MaybeRunPendingRequests(); return promise.forget(); @@ -208,23 +212,21 @@ CacheStorage::Has(const nsAString& aKey, ErrorResult& aRv) { NS_ASSERT_OWNINGTHREAD(CacheStorage); + if (mFailedActor) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + nsRefPtr promise = Promise::Create(mGlobal, aRv); if (!promise) { return nullptr; } - if (mFailedActor) { - promise->MaybeReject(NS_ERROR_UNEXPECTED); - return promise.forget(); - } - - RequestId requestId = AddRequestPromise(promise, aRv); - - Entry* entry = mPendingRequests.AppendElement(); - entry->mRequestId = requestId; - entry->mOp = OP_HAS; - entry->mKey = aKey; + nsAutoPtr entry(new Entry()); + entry->mPromise = promise; + entry->mArgs = StorageHasArgs(nsString(aKey)); + mPendingRequests.AppendElement(entry.forget()); MaybeRunPendingRequests(); return promise.forget(); @@ -235,23 +237,21 @@ CacheStorage::Open(const nsAString& aKey, ErrorResult& aRv) { NS_ASSERT_OWNINGTHREAD(CacheStorage); + if (mFailedActor) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + nsRefPtr promise = Promise::Create(mGlobal, aRv); if (!promise) { return nullptr; } - if (mFailedActor) { - promise->MaybeReject(NS_ERROR_UNEXPECTED); - return promise.forget(); - } - - RequestId requestId = AddRequestPromise(promise, aRv); - - Entry* entry = mPendingRequests.AppendElement(); - entry->mRequestId = requestId; - entry->mOp = OP_OPEN; - entry->mKey = aKey; + nsAutoPtr entry(new Entry()); + entry->mPromise = promise; + entry->mArgs = StorageOpenArgs(nsString(aKey)); + mPendingRequests.AppendElement(entry.forget()); MaybeRunPendingRequests(); return promise.forget(); @@ -262,23 +262,21 @@ CacheStorage::Delete(const nsAString& aKey, ErrorResult& aRv) { NS_ASSERT_OWNINGTHREAD(CacheStorage); + if (mFailedActor) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + nsRefPtr promise = Promise::Create(mGlobal, aRv); if (!promise) { return nullptr; } - if (mFailedActor) { - promise->MaybeReject(NS_ERROR_UNEXPECTED); - return promise.forget(); - } - - RequestId requestId = AddRequestPromise(promise, aRv); - - Entry* entry = mPendingRequests.AppendElement(); - entry->mRequestId = requestId; - entry->mOp = OP_DELETE; - entry->mKey = aKey; + nsAutoPtr entry(new Entry()); + entry->mPromise = promise; + entry->mArgs = StorageDeleteArgs(nsString(aKey)); + mPendingRequests.AppendElement(entry.forget()); MaybeRunPendingRequests(); return promise.forget(); @@ -289,22 +287,21 @@ CacheStorage::Keys(ErrorResult& aRv) { NS_ASSERT_OWNINGTHREAD(CacheStorage); + if (mFailedActor) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + nsRefPtr promise = Promise::Create(mGlobal, aRv); if (!promise) { return nullptr; } - if (mFailedActor) { - promise->MaybeReject(NS_ERROR_UNEXPECTED); - return promise.forget(); - } - - RequestId requestId = AddRequestPromise(promise, aRv); - - Entry* entry = mPendingRequests.AppendElement(); - entry->mRequestId = requestId; - entry->mOp = OP_KEYS; + nsAutoPtr entry(new Entry()); + entry->mPromise = promise; + entry->mArgs = StorageKeysArgs(); + mPendingRequests.AppendElement(entry.forget()); MaybeRunPendingRequests(); return promise.forget(); @@ -371,9 +368,8 @@ CacheStorage::ActorFailed() mFeature = nullptr; for (uint32_t i = 0; i < mPendingRequests.Length(); ++i) { - RequestId requestId = mPendingRequests[i].mRequestId; - nsRefPtr promise = RemoveRequestPromise(requestId); - promise->MaybeReject(NS_ERROR_UNEXPECTED); + nsAutoPtr entry(mPendingRequests[i].forget()); + entry->mPromise->MaybeReject(NS_ERROR_UNEXPECTED); } mPendingRequests.Clear(); } @@ -392,117 +388,6 @@ CacheStorage::DestroyInternal(CacheStorageChild* aActor) ActorFailed(); } -void -CacheStorage::RecvMatchResponse(RequestId aRequestId, nsresult aRv, - const PCacheResponseOrVoid& aResponse) -{ - NS_ASSERT_OWNINGTHREAD(CacheStorage); - - // Convert the response immediately if its present. This ensures that - // any stream actors are cleaned up, even if we error out below. - nsRefPtr response; - if (aResponse.type() == PCacheResponseOrVoid::TPCacheResponse) { - response = ToResponse(aResponse); - } - - nsRefPtr promise = RemoveRequestPromise(aRequestId); - - if (NS_FAILED(aRv)) { - promise->MaybeReject(aRv); - return; - } - - // If cache name was specified in the request options and the cache does - // not exist, then an error code will already have been set. If we - // still do not have a response, then we just resolve undefined like a - // normal Cache::Match. - if (!response) { - promise->MaybeResolve(JS::UndefinedHandleValue); - return; - } - - promise->MaybeResolve(response); -} - -void -CacheStorage::RecvHasResponse(RequestId aRequestId, nsresult aRv, bool aSuccess) -{ - NS_ASSERT_OWNINGTHREAD(CacheStorage); - - nsRefPtr promise = RemoveRequestPromise(aRequestId); - - if (NS_FAILED(aRv)) { - promise->MaybeReject(aRv); - return; - - } - - promise->MaybeResolve(aSuccess); -} - -void -CacheStorage::RecvOpenResponse(RequestId aRequestId, nsresult aRv, - CacheChild* aActor) -{ - NS_ASSERT_OWNINGTHREAD(CacheStorage); - - // Unlike most of our async callback Recv*() methods, this one gets back - // an actor. We need to make sure to clean it up in case of error. - - nsRefPtr promise = RemoveRequestPromise(aRequestId); - - if (NS_FAILED(aRv)) { - if (aActor) { - // We cannot use the CacheChild::StartDestroy() method because there - // is no Cache object associated with the actor yet. Instead, just - // send the underlying Teardown message. - unused << aActor->SendTeardown(); - } - promise->MaybeReject(aRv); - return; - } - - if (!aActor) { - promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR); - return; - } - - nsRefPtr cache = new Cache(mGlobal, aActor); - promise->MaybeResolve(cache); -} - -void -CacheStorage::RecvDeleteResponse(RequestId aRequestId, nsresult aRv, - bool aSuccess) -{ - NS_ASSERT_OWNINGTHREAD(CacheStorage); - - nsRefPtr promise = RemoveRequestPromise(aRequestId); - - if (NS_FAILED(aRv)) { - promise->MaybeReject(aRv); - return; - } - - promise->MaybeResolve(aSuccess); -} - -void -CacheStorage::RecvKeysResponse(RequestId aRequestId, nsresult aRv, - const nsTArray& aKeys) -{ - NS_ASSERT_OWNINGTHREAD(CacheStorage); - - nsRefPtr promise = RemoveRequestPromise(aRequestId); - - if (NS_FAILED(aRv)) { - promise->MaybeReject(aRv); - return; - } - - promise->MaybeResolve(aKeys); -} - nsIGlobalObject* CacheStorage::GetGlobalObject() const { @@ -524,32 +409,9 @@ CacheStorage::CreatePushStream(nsIAsyncInputStream* aStream) MOZ_CRASH("CacheStorage should never create a push stream."); } -void -CacheStorage::ResolvedCallback(JSContext* aCx, JS::Handle aValue) -{ - // Do nothing. The Promise will automatically drop the ref to us after - // calling the callback. This is what we want as we only registered in order - // to be held alive via the Promise handle. -} - -void -CacheStorage::RejectedCallback(JSContext* aCx, JS::Handle aValue) -{ - // Do nothing. The Promise will automatically drop the ref to us after - // calling the callback. This is what we want as we only registered in order - // to be held alive via the Promise handle. -} - CacheStorage::~CacheStorage() -{ - DisconnectFromActor(); -} - -void -CacheStorage::DisconnectFromActor() { NS_ASSERT_OWNINGTHREAD(CacheStorage); - if (mActor) { mActor->StartDestroy(); // DestroyInternal() is called synchronously by StartDestroy(). So we @@ -566,89 +428,22 @@ CacheStorage::MaybeRunPendingRequests() } for (uint32_t i = 0; i < mPendingRequests.Length(); ++i) { - // Note, the entry can be modified below due to Request/Response body - // being marked used. - Entry& entry = mPendingRequests[i]; - RequestId requestId = entry.mRequestId; - switch(entry.mOp) { - case OP_MATCH: - { - AutoChildRequest request(this); - ErrorResult rv; - request.Add(entry.mRequest, IgnoreBody, PassThroughReferrer, - IgnoreInvalidScheme, rv); - if (NS_WARN_IF(rv.Failed())) { - nsRefPtr promise = RemoveRequestPromise(requestId); - promise->MaybeReject(rv); - break; - } - - PCacheQueryParams params; - ToPCacheQueryParams(params, entry.mOptions); - - unused << mActor->SendMatch(requestId, request.SendAsRequest(), params); - break; - } - case OP_HAS: - unused << mActor->SendHas(requestId, entry.mKey); - break; - case OP_OPEN: - unused << mActor->SendOpen(requestId, entry.mKey); - break; - case OP_DELETE: - unused << mActor->SendDelete(requestId, entry.mKey); - break; - case OP_KEYS: - unused << mActor->SendKeys(requestId); - break; - default: - MOZ_ASSERT_UNREACHABLE("Unknown pending CacheStorage op."); + ErrorResult rv; + nsAutoPtr entry(mPendingRequests[i].forget()); + AutoChildOpArgs args(this, entry->mArgs); + if (entry->mRequest) { + args.Add(entry->mRequest, IgnoreBody, PassThroughReferrer, + IgnoreInvalidScheme, rv); } + if (rv.Failed()) { + entry->mPromise->MaybeReject(rv); + continue; + } + mActor->ExecuteOp(mGlobal, entry->mPromise, args.SendAsOpArgs()); } mPendingRequests.Clear(); } -RequestId -CacheStorage::AddRequestPromise(Promise* aPromise, ErrorResult& aRv) -{ - NS_ASSERT_OWNINGTHREAD(CacheStorage); - MOZ_ASSERT(aPromise); - MOZ_ASSERT(!mRequestPromises.Contains(aPromise)); - - // Register ourself as a promise handler so that the promise will hold us - // alive. This allows the client code to drop the ref to the CacheStorage - // object and just keep their promise. This is fairly common in promise - // chaining code. - aPromise->AppendNativeHandler(this); - - mRequestPromises.AppendElement(aPromise); - - // (Ab)use the promise pointer as our request ID. This is a fast, thread-safe - // way to get a unique ID for the promise to be resolved later. - return reinterpret_cast(aPromise); -} - -already_AddRefed -CacheStorage::RemoveRequestPromise(RequestId aRequestId) -{ - NS_ASSERT_OWNINGTHREAD(CacheStorage); - MOZ_ASSERT(aRequestId != INVALID_REQUEST_ID); - - for (uint32_t i = 0; i < mRequestPromises.Length(); ++i) { - nsRefPtr& promise = mRequestPromises.ElementAt(i); - // To be safe, only cast promise pointers to our integer RequestId - // type and never cast an integer to a pointer. - if (aRequestId == reinterpret_cast(promise.get())) { - nsRefPtr ref; - ref.swap(promise); - mRequestPromises.RemoveElementAt(i); - return ref.forget(); - } - } - MOZ_ASSERT_UNREACHABLE("Received response without a matching promise!"); - return nullptr; -} - } // namespace cache } // namespace dom } // namespace mozilla diff --git a/dom/cache/CacheStorage.h b/dom/cache/CacheStorage.h index c1f80d5abd..ff227e61e1 100644 --- a/dom/cache/CacheStorage.h +++ b/dom/cache/CacheStorage.h @@ -8,7 +8,6 @@ #define mozilla_dom_cache_CacheStorage_h #include "mozilla/dom/CacheBinding.h" -#include "mozilla/dom/PromiseNativeHandler.h" #include "mozilla/dom/cache/Types.h" #include "mozilla/dom/cache/TypeUtils.h" #include "nsAutoPtr.h" @@ -38,15 +37,12 @@ namespace workers { namespace cache { -class CacheChild; class CacheStorageChild; class Feature; -class PCacheResponseOrVoid; class CacheStorage final : public nsIIPCBackgroundChildCreateCallback , public nsWrapperCache , public TypeUtils - , public PromiseNativeHandler { typedef mozilla::ipc::PBackgroundChild PBackgroundChild; @@ -81,16 +77,6 @@ public: // Called when CacheStorageChild actor is being destroyed void DestroyInternal(CacheStorageChild* aActor); - // Methods forwarded from CacheStorageChild - void RecvMatchResponse(RequestId aRequestId, nsresult aRv, - const PCacheResponseOrVoid& aResponse); - void RecvHasResponse(RequestId aRequestId, nsresult aRv, bool aSuccess); - void RecvOpenResponse(RequestId aRequestId, nsresult aRv, - CacheChild* aActor); - void RecvDeleteResponse(RequestId aRequestId, nsresult aRv, bool aSuccess); - void RecvKeysResponse(RequestId aRequestId, nsresult aRv, - const nsTArray& aKeys); - // TypeUtils methods virtual nsIGlobalObject* GetGlobalObject() const override; #ifdef DEBUG @@ -100,60 +86,24 @@ public: virtual CachePushStreamChild* CreatePushStream(nsIAsyncInputStream* aStream) override; - // PromiseNativeHandler methods - virtual void - ResolvedCallback(JSContext* aCx, JS::Handle aValue) override; - - virtual void - RejectedCallback(JSContext* aCx, JS::Handle aValue) override; - private: CacheStorage(Namespace aNamespace, nsIGlobalObject* aGlobal, const mozilla::ipc::PrincipalInfo& aPrincipalInfo, Feature* aFeature); ~CacheStorage(); - // Called when we're destroyed or CCed. - void DisconnectFromActor(); - void MaybeRunPendingRequests(); - RequestId AddRequestPromise(Promise* aPromise, ErrorResult& aRv); - already_AddRefed RemoveRequestPromise(RequestId aRequestId); - - // Would like to use CacheInitData here, but we cannot because - // its an IPC struct which breaks webidl by including windows.h. const Namespace mNamespace; nsCOMPtr mGlobal; UniquePtr mPrincipalInfo; nsRefPtr mFeature; + + // weak ref cleared in DestroyInternal CacheStorageChild* mActor; - nsTArray> mRequestPromises; - enum Op - { - OP_MATCH, - OP_HAS, - OP_OPEN, - OP_DELETE, - OP_KEYS - }; + struct Entry; + nsTArray> mPendingRequests; - struct Entry - { - RequestId mRequestId; - Op mOp; - // Would prefer to use PCacheRequest/PCacheCacheQueryOptions, but can't - // because they introduce a header dependency on windows.h which - // breaks the bindings build. - nsRefPtr mRequest; - CacheQueryOptions mOptions; - // It would also be nice to union the key with the match args above, - // but VS2013 doesn't like these types in unions because of copy - // constructors. - nsString mKey; - }; - - nsTArray mPendingRequests; bool mFailedActor; public: diff --git a/dom/cache/CacheStorageChild.cpp b/dom/cache/CacheStorageChild.cpp index 3fce81b84e..ae047803a9 100644 --- a/dom/cache/CacheStorageChild.cpp +++ b/dom/cache/CacheStorageChild.cpp @@ -8,8 +8,8 @@ #include "mozilla/unused.h" #include "mozilla/dom/cache/CacheChild.h" +#include "mozilla/dom/cache/CacheOpChild.h" #include "mozilla/dom/cache/CacheStorage.h" -#include "mozilla/dom/cache/StreamUtils.h" namespace mozilla { namespace dom { @@ -24,6 +24,7 @@ DeallocPCacheStorageChild(PCacheStorageChild* aActor) CacheStorageChild::CacheStorageChild(CacheStorage* aListener, Feature* aFeature) : mListener(aListener) + , mNumChildActors(0) { MOZ_COUNT_CTOR(cache::CacheStorageChild); MOZ_ASSERT(mListener); @@ -46,6 +47,15 @@ CacheStorageChild::ClearListener() mListener = nullptr; } +void +CacheStorageChild::ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise, + const CacheOpArgs& aArgs) +{ + mNumChildActors += 1; + unused << SendPCacheOpConstructor( + new CacheOpChild(GetFeature(), aGlobal, aPromise), aArgs); +} + void CacheStorageChild::StartDestroy() { @@ -65,6 +75,14 @@ CacheStorageChild::StartDestroy() // CacheStorage listener should call ClearListener() in DestroyInternal() MOZ_ASSERT(!mListener); + // If we have outstanding child actors, then don't destroy ourself yet. + // The child actors should be short lived and we should allow them to complete + // if possible. SendTeardown() will be called when the count drops to zero + // in NoteDeletedActor(). + if (mNumChildActors) { + return; + } + // Start actor destruction from parent process unused << SendTeardown(); } @@ -83,92 +101,29 @@ CacheStorageChild::ActorDestroy(ActorDestroyReason aReason) RemoveFeature(); } -bool -CacheStorageChild::RecvMatchResponse(const RequestId& aRequestId, - const nsresult& aRv, - const PCacheResponseOrVoid& aResponseOrVoid) +PCacheOpChild* +CacheStorageChild::AllocPCacheOpChild(const CacheOpArgs& aOpArgs) { - NS_ASSERT_OWNINGTHREAD(CacheStorageChild); - - AddFeatureToStreamChild(aResponseOrVoid, GetFeature()); - - nsRefPtr listener = mListener; - if (!listener) { - StartDestroyStreamChild(aResponseOrVoid); - return true; - } - - listener->RecvMatchResponse(aRequestId, aRv, aResponseOrVoid); - - return true; + MOZ_CRASH("CacheOpChild should be manually constructed."); + return nullptr; } bool -CacheStorageChild::RecvHasResponse(const RequestId& aRequestId, - const nsresult& aRv, - const bool& aSuccess) +CacheStorageChild::DeallocPCacheOpChild(PCacheOpChild* aActor) { - NS_ASSERT_OWNINGTHREAD(CacheStorageChild); - nsRefPtr listener = mListener; - if (listener) { - listener->RecvHasResponse(aRequestId, aRv, aSuccess); - } + delete aActor; + NoteDeletedActor(); return true; } -bool -CacheStorageChild::RecvOpenResponse(const RequestId& aRequestId, - const nsresult& aRv, - PCacheChild* aActor) +void +CacheStorageChild::NoteDeletedActor() { - NS_ASSERT_OWNINGTHREAD(CacheStorageChild); - - nsRefPtr listener = mListener; - if (!listener || FeatureNotified()) { - if (aActor) { - unused << aActor->SendTeardown(); - } - return true; + MOZ_ASSERT(mNumChildActors); + mNumChildActors -= 1; + if (!mNumChildActors && !mListener) { + unused << SendTeardown(); } - - CacheChild* cacheChild = static_cast(aActor); - - // Since FeatureNotified() returned false above, we are guaranteed that - // the feature won't try to shutdown the actor until after we create the - // Cache DOM object in the listener's RecvOpenResponse() method. This - // is important because StartShutdown() expects a Cache object listener. - if (cacheChild) { - cacheChild->SetFeature(GetFeature()); - } - - listener->RecvOpenResponse(aRequestId, aRv, cacheChild); - return true; -} - -bool -CacheStorageChild::RecvDeleteResponse(const RequestId& aRequestId, - const nsresult& aRv, - const bool& aResult) -{ - NS_ASSERT_OWNINGTHREAD(CacheStorageChild); - nsRefPtr listener = mListener; - if (listener) { - listener->RecvDeleteResponse(aRequestId, aRv, aResult); - } - return true; -} - -bool -CacheStorageChild::RecvKeysResponse(const RequestId& aRequestId, - const nsresult& aRv, - nsTArray&& aKeys) -{ - NS_ASSERT_OWNINGTHREAD(CacheStorageChild); - nsRefPtr listener = mListener; - if (listener) { - listener->RecvKeysResponse(aRequestId, aRv, aKeys); - } - return true; } } // namespace cache diff --git a/dom/cache/CacheStorageChild.h b/dom/cache/CacheStorageChild.h index 0e8e4235d8..1d0fa194a4 100644 --- a/dom/cache/CacheStorageChild.h +++ b/dom/cache/CacheStorageChild.h @@ -11,10 +11,16 @@ #include "mozilla/dom/cache/Types.h" #include "mozilla/dom/cache/PCacheStorageChild.h" +class nsIGlobalObject; + namespace mozilla { namespace dom { + +class Promise; + namespace cache { +class CacheOpArgs; class CacheStorage; class PCacheChild; class Feature; @@ -27,11 +33,15 @@ public: ~CacheStorageChild(); // Must be called by the associated CacheStorage listener in its - // ActorDestroy() method. Also, CacheStorage must Send__delete__() the + // ActorDestroy() method. Also, CacheStorage must call SendDestroy() on the // actor in its destructor to trigger ActorDestroy() if it has not been // called yet. void ClearListener(); + void + ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise, + const CacheOpArgs& aArgs); + // ActorChild methods // Synchronously call ActorDestroy on our CacheStorage listener and then start @@ -42,26 +52,21 @@ private: // PCacheStorageChild methods virtual void ActorDestroy(ActorDestroyReason aReason) override; - virtual bool RecvMatchResponse(const RequestId& aRequestId, - const nsresult& aRv, - const PCacheResponseOrVoid& response) override; - virtual bool RecvHasResponse(const cache::RequestId& aRequestId, - const nsresult& aRv, - const bool& aSuccess) override; - virtual bool RecvOpenResponse(const cache::RequestId& aRequestId, - const nsresult& aRv, - PCacheChild* aActor) override; - virtual bool RecvDeleteResponse(const cache::RequestId& aRequestId, - const nsresult& aRv, - const bool& aResult) override; - virtual bool RecvKeysResponse(const cache::RequestId& aRequestId, - const nsresult& aRv, - nsTArray&& aKeys) override; + virtual PCacheOpChild* + AllocPCacheOpChild(const CacheOpArgs& aOpArgs) override; + + virtual bool + DeallocPCacheOpChild(PCacheOpChild* aActor) override; + + // utility methods + void + NoteDeletedActor(); // Use a weak ref so actor does not hold DOM object alive past content use. // The CacheStorage object must call ClearListener() to null this before its // destroyed. CacheStorage* MOZ_NON_OWNING_REF mListener; + uint32_t mNumChildActors; NS_DECL_OWNINGTHREAD }; diff --git a/dom/cache/CacheStorageParent.cpp b/dom/cache/CacheStorageParent.cpp index 3faa63d8be..9c85e9c804 100644 --- a/dom/cache/CacheStorageParent.cpp +++ b/dom/cache/CacheStorageParent.cpp @@ -6,28 +6,18 @@ #include "mozilla/dom/cache/CacheStorageParent.h" +#include "mozilla/unused.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/cache/ActorUtils.h" -#include "mozilla/dom/cache/AutoUtils.h" -#include "mozilla/dom/cache/CacheParent.h" -#include "mozilla/dom/cache/CacheStreamControlParent.h" -#include "mozilla/dom/cache/Manager.h" +#include "mozilla/dom/cache/CacheOpParent.h" #include "mozilla/dom/cache/ManagerId.h" -#include "mozilla/dom/cache/ReadStream.h" -#include "mozilla/dom/cache/SavedTypes.h" -#include "mozilla/dom/cache/StreamList.h" #include "mozilla/ipc/PBackgroundParent.h" -#include "mozilla/ipc/InputStreamUtils.h" -#include "mozilla/ipc/PFileDescriptorSetParent.h" -#include "mozilla/DebugOnly.h" -#include "nsCOMPtr.h" namespace mozilla { namespace dom { namespace cache { using mozilla::ipc::PBackgroundParent; -using mozilla::ipc::PFileDescriptorSetParent; using mozilla::ipc::PrincipalInfo; // declared in ActorUtils.h @@ -65,22 +55,60 @@ CacheStorageParent::~CacheStorageParent() { MOZ_COUNT_DTOR(cache::CacheStorageParent); MOZ_ASSERT(!mVerifier); - MOZ_ASSERT(!mManager); } void CacheStorageParent::ActorDestroy(ActorDestroyReason aReason) { if (mVerifier) { - mVerifier->ClearListener(); + mVerifier->RemoveListener(this); mVerifier = nullptr; } +} - if (mManager) { - MOZ_ASSERT(!mActiveRequests.IsEmpty()); - mManager->RemoveListener(this); - mManager = nullptr; +PCacheOpParent* +CacheStorageParent::AllocPCacheOpParent(const CacheOpArgs& aOpArgs) +{ + if (aOpArgs.type() != CacheOpArgs::TStorageMatchArgs && + aOpArgs.type() != CacheOpArgs::TStorageHasArgs && + aOpArgs.type() != CacheOpArgs::TStorageOpenArgs && + aOpArgs.type() != CacheOpArgs::TStorageDeleteArgs && + aOpArgs.type() != CacheOpArgs::TStorageKeysArgs) + { + MOZ_CRASH("Invalid operation sent to CacheStorage actor!"); } + + return new CacheOpParent(Manager(), mNamespace, aOpArgs); +} + +bool +CacheStorageParent::DeallocPCacheOpParent(PCacheOpParent* aActor) +{ + delete aActor; + return true; +} + +bool +CacheStorageParent::RecvPCacheOpConstructor(PCacheOpParent* aActor, + const CacheOpArgs& aOpArgs) +{ + auto actor = static_cast(aActor); + + if (mVerifier) { + MOZ_ASSERT(!mManagerId); + actor->WaitForVerification(mVerifier); + return true; + } + + if (NS_FAILED(mVerifiedStatus)) { + unused << CacheOpParent::Send__delete__(actor, ErrorResult(mVerifiedStatus), + void_t()); + return true; + } + + MOZ_ASSERT(mManagerId); + actor->Execute(mManagerId); + return true; } bool @@ -93,190 +121,11 @@ CacheStorageParent::RecvTeardown() return true; } -bool -CacheStorageParent::RecvMatch(const RequestId& aRequestId, - const PCacheRequest& aRequest, - const PCacheQueryParams& aParams) -{ - if (NS_WARN_IF(NS_FAILED(mVerifiedStatus))) { - if (!SendMatchResponse(aRequestId, mVerifiedStatus, void_t())) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Match response."); - } - return true; - } - - // queue requests if we are still waiting for principal verification - if (!mManagerId) { - Entry* entry = mPendingRequests.AppendElement(); - entry->mOp = OP_MATCH; - entry->mRequestId = aRequestId; - entry->mRequest = aRequest; - entry->mParams = aParams; - return true; - } - - nsRefPtr manager; - nsresult rv = RequestManager(aRequestId, getter_AddRefs(manager)); - if (NS_WARN_IF(NS_FAILED(rv))) { - if (!SendMatchResponse(aRequestId, rv, void_t())) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Match response."); - } - return true; - } - - manager->StorageMatch(this, aRequestId, mNamespace, aRequest, - aParams); - - return true; -} - -bool -CacheStorageParent::RecvHas(const RequestId& aRequestId, const nsString& aKey) -{ - if (NS_WARN_IF(NS_FAILED(mVerifiedStatus))) { - if (!SendHasResponse(aRequestId, mVerifiedStatus, false)) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Has response."); - } - return true; - } - - // queue requests if we are still waiting for principal verification - if (!mManagerId) { - Entry* entry = mPendingRequests.AppendElement(); - entry->mOp = OP_HAS; - entry->mRequestId = aRequestId; - entry->mKey = aKey; - return true; - } - - nsRefPtr manager; - nsresult rv = RequestManager(aRequestId, getter_AddRefs(manager)); - if (NS_WARN_IF(NS_FAILED(rv))) { - if (!SendHasResponse(aRequestId, rv, false)) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Has response."); - } - return true; - } - - manager->StorageHas(this, aRequestId, mNamespace, aKey); - - return true; -} - -bool -CacheStorageParent::RecvOpen(const RequestId& aRequestId, const nsString& aKey) -{ - if (NS_WARN_IF(NS_FAILED(mVerifiedStatus))) { - if (!SendOpenResponse(aRequestId, mVerifiedStatus, nullptr)) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Open response."); - } - return true; - } - - // queue requests if we are still waiting for principal verification - if (!mManagerId) { - Entry* entry = mPendingRequests.AppendElement(); - entry->mOp = OP_OPEN; - entry->mRequestId = aRequestId; - entry->mKey = aKey; - return true; - } - - nsRefPtr manager; - nsresult rv = RequestManager(aRequestId, getter_AddRefs(manager)); - if (NS_WARN_IF(NS_FAILED(rv))) { - if (!SendOpenResponse(aRequestId, rv, nullptr)) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Open response."); - } - return true; - } - - manager->StorageOpen(this, aRequestId, mNamespace, aKey); - - return true; -} - -bool -CacheStorageParent::RecvDelete(const RequestId& aRequestId, - const nsString& aKey) -{ - if (NS_WARN_IF(NS_FAILED(mVerifiedStatus))) { - if (!SendDeleteResponse(aRequestId, mVerifiedStatus, false)) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Delete response."); - } - return true; - } - - // queue requests if we are still waiting for principal verification - if (!mManagerId) { - Entry* entry = mPendingRequests.AppendElement(); - entry->mOp = OP_DELETE; - entry->mRequestId = aRequestId; - entry->mKey = aKey; - return true; - } - - nsRefPtr manager; - nsresult rv = RequestManager(aRequestId, getter_AddRefs(manager)); - if (NS_WARN_IF(NS_FAILED(rv))) { - if (!SendDeleteResponse(aRequestId, rv, false)) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Delete response."); - } - return true; - } - - manager->StorageDelete(this, aRequestId, mNamespace, aKey); - - return true; -} - -bool -CacheStorageParent::RecvKeys(const RequestId& aRequestId) -{ - if (NS_WARN_IF(NS_FAILED(mVerifiedStatus))) { - if (!SendKeysResponse(aRequestId, mVerifiedStatus, nsTArray())) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Keys response."); - } - } - - // queue requests if we are still waiting for principal verification - if (!mManagerId) { - Entry* entry = mPendingRequests.AppendElement(); - entry->mOp = OP_DELETE; - entry->mRequestId = aRequestId; - return true; - } - - nsRefPtr manager; - nsresult rv = RequestManager(aRequestId, getter_AddRefs(manager)); - if (NS_WARN_IF(NS_FAILED(rv))) { - if (!SendKeysResponse(aRequestId, rv, nsTArray())) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Keys response."); - } - return true; - } - - manager->StorageKeys(this, aRequestId, mNamespace); - - return true; -} - void CacheStorageParent::OnPrincipalVerified(nsresult aRv, ManagerId* aManagerId) { MOZ_ASSERT(mVerifier); MOZ_ASSERT(!mManagerId); - MOZ_ASSERT(!mManager); MOZ_ASSERT(NS_SUCCEEDED(mVerifiedStatus)); if (NS_WARN_IF(NS_FAILED(aRv))) { @@ -284,165 +133,8 @@ CacheStorageParent::OnPrincipalVerified(nsresult aRv, ManagerId* aManagerId) } mManagerId = aManagerId; - mVerifier->ClearListener(); + mVerifier->RemoveListener(this); mVerifier = nullptr; - - RetryPendingRequests(); -} - -void -CacheStorageParent::OnStorageMatch(RequestId aRequestId, nsresult aRv, - const SavedResponse* aSavedResponse, - StreamList* aStreamList) -{ - PCacheResponseOrVoid responseOrVoid; - - ReleaseManager(aRequestId); - - AutoParentResponseOrVoid response(Manager()); - - // no match - if (NS_FAILED(aRv) || !aSavedResponse) { - if (!SendMatchResponse(aRequestId, aRv, response.SendAsResponseOrVoid())) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Match response."); - } - return; - } - - if (aSavedResponse) { - response.Add(*aSavedResponse, aStreamList); - } - - if (!SendMatchResponse(aRequestId, aRv, response.SendAsResponseOrVoid())) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Match response."); - } -} - -void -CacheStorageParent::OnStorageHas(RequestId aRequestId, nsresult aRv, - bool aCacheFound) -{ - ReleaseManager(aRequestId); - if (!SendHasResponse(aRequestId, aRv, aCacheFound)) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Has response."); - } -} - -void -CacheStorageParent::OnStorageOpen(RequestId aRequestId, nsresult aRv, - CacheId aCacheId) -{ - if (NS_FAILED(aRv)) { - ReleaseManager(aRequestId); - if (!SendOpenResponse(aRequestId, aRv, nullptr)) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Open response."); - } - return; - } - - MOZ_ASSERT(mManager); - CacheParent* actor = new CacheParent(mManager, aCacheId); - - ReleaseManager(aRequestId); - - PCacheParent* base = Manager()->SendPCacheConstructor(actor); - actor = static_cast(base); - if (!SendOpenResponse(aRequestId, aRv, actor)) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Open response."); - } -} - -void -CacheStorageParent::OnStorageDelete(RequestId aRequestId, nsresult aRv, - bool aCacheDeleted) -{ - ReleaseManager(aRequestId); - if (!SendDeleteResponse(aRequestId, aRv, aCacheDeleted)) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Delete response."); - } -} - -void -CacheStorageParent::OnStorageKeys(RequestId aRequestId, nsresult aRv, - const nsTArray& aKeys) -{ - ReleaseManager(aRequestId); - if (!SendKeysResponse(aRequestId, aRv, aKeys)) { - // child process is gone, warn and allow actor to clean up normally - NS_WARNING("CacheStorage failed to send Keys response."); - } -} - -void -CacheStorageParent::RetryPendingRequests() -{ - MOZ_ASSERT(mManagerId || NS_FAILED(mVerifiedStatus)); - for (uint32_t i = 0; i < mPendingRequests.Length(); ++i) { - const Entry& entry = mPendingRequests[i]; - switch(entry.mOp) { - case OP_MATCH: - RecvMatch(entry.mRequestId, entry.mRequest, entry.mParams); - break; - case OP_HAS: - RecvHas(entry.mRequestId, entry.mKey); - break; - case OP_OPEN: - RecvOpen(entry.mRequestId, entry.mKey); - break; - case OP_DELETE: - RecvDelete(entry.mRequestId, entry.mKey); - break; - case OP_KEYS: - RecvKeys(entry.mRequestId); - break; - default: - MOZ_ASSERT_UNREACHABLE("Pending request within unknown op"); - } - } - mPendingRequests.Clear(); - mPendingRequests.Compact(); -} - -nsresult -CacheStorageParent::RequestManager(RequestId aRequestId, - cache::Manager** aManagerOut) -{ - MOZ_ASSERT(!mActiveRequests.Contains(aRequestId)); - nsRefPtr ref = mManager; - if (!ref) { - MOZ_ASSERT(mActiveRequests.IsEmpty()); - nsresult rv = cache::Manager::GetOrCreate(mManagerId, getter_AddRefs(ref)); - if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - mManager = ref; - } - mActiveRequests.AppendElement(aRequestId); - ref.forget(aManagerOut); - return NS_OK; -} - -void -CacheStorageParent::ReleaseManager(RequestId aRequestId) -{ - // Note that if the child process dies we also clean up the mManager in - // ActorDestroy(). There is no race with this method, however, because - // ActorDestroy removes this object from the Manager's listener list. - // Therefore ReleaseManager() should never be called after ActorDestroy() - // runs. - MOZ_ASSERT(mManager); - MOZ_ASSERT(!mActiveRequests.IsEmpty()); - - MOZ_ALWAYS_TRUE(mActiveRequests.RemoveElement(aRequestId)); - - if (mActiveRequests.IsEmpty()) { - mManager->RemoveListener(this); - mManager = nullptr; - } } } // namespace cache diff --git a/dom/cache/CacheStorageParent.h b/dom/cache/CacheStorageParent.h index ceb621229b..f46e226f3f 100644 --- a/dom/cache/CacheStorageParent.h +++ b/dom/cache/CacheStorageParent.h @@ -7,24 +7,18 @@ #ifndef mozilla_dom_cache_CacheStorageParent_h #define mozilla_dom_cache_CacheStorageParent_h -#include "mozilla/dom/cache/CacheInitData.h" #include "mozilla/dom/cache/PCacheStorageParent.h" -#include "mozilla/dom/cache/Manager.h" #include "mozilla/dom/cache/PrincipalVerifier.h" #include "mozilla/dom/cache/Types.h" -template class nsRefPtr; - namespace mozilla { namespace dom { namespace cache { -class CacheStreamControlParent; class ManagerId; class CacheStorageParent final : public PCacheStorageParent , public PrincipalVerifier::Listener - , public Manager::Listener { public: CacheStorageParent(PBackgroundParent* aManagingActor, Namespace aNamespace, @@ -33,72 +27,30 @@ public: private: // PCacheStorageParent methods - virtual void ActorDestroy(ActorDestroyReason aReason) override; - virtual bool RecvTeardown() override; - virtual bool RecvMatch(const RequestId& aRequestId, - const PCacheRequest& aRequest, - const PCacheQueryParams& aParams) override; - virtual bool RecvHas(const RequestId& aRequestId, - const nsString& aKey) override; - virtual bool RecvOpen(const RequestId& aRequestId, - const nsString& aKey) override; - virtual bool RecvDelete(const RequestId& aRequestId, - const nsString& aKey) override; - virtual bool RecvKeys(const RequestId& aRequestId) override; + virtual void + ActorDestroy(ActorDestroyReason aReason) override; + + virtual PCacheOpParent* + AllocPCacheOpParent(const CacheOpArgs& aOpArgs) override; + + virtual bool + DeallocPCacheOpParent(PCacheOpParent* aActor) override; + + virtual bool + RecvPCacheOpConstructor(PCacheOpParent* actor, + const CacheOpArgs& aOpArgs) override; + + virtual bool + RecvTeardown() override; // PrincipalVerifier::Listener methods virtual void OnPrincipalVerified(nsresult aRv, ManagerId* aManagerId) override; - // Manager::Listener methods - virtual void OnStorageMatch(RequestId aRequestId, nsresult aRv, - const SavedResponse* aResponse, - StreamList* aStreamList) override; - virtual void OnStorageHas(RequestId aRequestId, nsresult aRv, - bool aCacheFound) override; - virtual void OnStorageOpen(RequestId aRequestId, nsresult aRv, - CacheId aCacheId) override; - virtual void OnStorageDelete(RequestId aRequestId, nsresult aRv, - bool aCacheDeleted) override; - virtual void OnStorageKeys(RequestId aRequestId, nsresult aRv, - const nsTArray& aKeys) override; - - CacheStreamControlParent* - SerializeReadStream(CacheStreamControlParent *aStreamControl, const nsID& aId, - StreamList* aStreamList, - PCacheReadStream* aReadStreamOut); - - void RetryPendingRequests(); - - nsresult RequestManager(RequestId aRequestId, cache::Manager** aManagerOut); - void ReleaseManager(RequestId aRequestId); - const Namespace mNamespace; nsRefPtr mVerifier; nsresult mVerifiedStatus; nsRefPtr mManagerId; - nsRefPtr mManager; - - enum Op - { - OP_MATCH, - OP_HAS, - OP_OPEN, - OP_DELETE, - OP_KEYS - }; - - struct Entry - { - Op mOp; - RequestId mRequestId; - nsString mKey; - PCacheRequest mRequest; - PCacheQueryParams mParams; - }; - - nsTArray mPendingRequests; - nsTArray mActiveRequests; }; } // namesapce cache diff --git a/dom/cache/CacheStreamControlChild.cpp b/dom/cache/CacheStreamControlChild.cpp index f5a34ecc99..78b12221dd 100644 --- a/dom/cache/CacheStreamControlChild.cpp +++ b/dom/cache/CacheStreamControlChild.cpp @@ -9,7 +9,7 @@ #include "mozilla/DebugOnly.h" #include "mozilla/unused.h" #include "mozilla/dom/cache/ActorUtils.h" -#include "mozilla/dom/cache/PCacheTypes.h" +#include "mozilla/dom/cache/CacheTypes.h" #include "mozilla/dom/cache/ReadStream.h" #include "mozilla/ipc/FileDescriptorSetChild.h" #include "mozilla/ipc/PBackgroundChild.h" @@ -41,6 +41,7 @@ DeallocPCacheStreamControlChild(PCacheStreamControlChild* aActor) CacheStreamControlChild::CacheStreamControlChild() : mDestroyStarted(false) + , mDestroyDelayed(false) { MOZ_COUNT_CTOR(cache::CacheStreamControlChild); } @@ -63,13 +64,28 @@ CacheStreamControlChild::StartDestroy() } mDestroyStarted = true; + // If any of the streams have started to be read, then wait for them to close + // naturally. + if (HasEverBeenRead()) { + // Note that we are delaying so that we can re-check for active streams + // in NoteClosedAfterForget(). + mDestroyDelayed = true; + return; + } + + // Otherwise, if the streams have not been touched then just pre-emptively + // close them now. This handles the case where someone retrieves a Response + // from the Cache, but never accesses the body. We should not keep the + // Worker alive until that Response is GC'd just because of its ignored + // body stream. + // Begin shutting down all streams. This is the same as if the parent had // asked us to shutdown. So simulate the CloseAll IPC message. RecvCloseAll(); } void -CacheStreamControlChild::SerializeControl(PCacheReadStream* aReadStreamOut) +CacheStreamControlChild::SerializeControl(CacheReadStream* aReadStreamOut) { NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild); aReadStreamOut->controlParent() = nullptr; @@ -77,7 +93,7 @@ CacheStreamControlChild::SerializeControl(PCacheReadStream* aReadStreamOut) } void -CacheStreamControlChild::SerializeFds(PCacheReadStream* aReadStreamOut, +CacheStreamControlChild::SerializeFds(CacheReadStream* aReadStreamOut, const nsTArray& aFds) { NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild); @@ -97,7 +113,7 @@ CacheStreamControlChild::SerializeFds(PCacheReadStream* aReadStreamOut, } void -CacheStreamControlChild::DeserializeFds(const PCacheReadStream& aReadStream, +CacheStreamControlChild::DeserializeFds(const CacheReadStream& aReadStream, nsTArray& aFdsOut) { if (aReadStream.fds().type() != @@ -120,6 +136,15 @@ CacheStreamControlChild::NoteClosedAfterForget(const nsID& aId) { NS_ASSERT_OWNINGTHREAD(CacheStreamControlChild); unused << SendNoteClosed(aId); + + // A stream has closed. If we delayed StartDestry() due to this stream + // being read, then we should check to see if any of the remaining streams + // are active. If none of our other streams have been read, then we can + // proceed with the shutdown now. + if (mDestroyDelayed && !HasEverBeenRead()) { + mDestroyDelayed = false; + RecvCloseAll(); + } } #ifdef DEBUG diff --git a/dom/cache/CacheStreamControlChild.h b/dom/cache/CacheStreamControlChild.h index a45a53c2eb..ff0c6c6e71 100644 --- a/dom/cache/CacheStreamControlChild.h +++ b/dom/cache/CacheStreamControlChild.h @@ -31,14 +31,14 @@ public: // StreamControl methods virtual void - SerializeControl(PCacheReadStream* aReadStreamOut) override; + SerializeControl(CacheReadStream* aReadStreamOut) override; virtual void - SerializeFds(PCacheReadStream* aReadStreamOut, + SerializeFds(CacheReadStream* aReadStreamOut, const nsTArray& aFds) override; virtual void - DeserializeFds(const PCacheReadStream& aReadStream, + DeserializeFds(const CacheReadStream& aReadStream, nsTArray& aFdsOut) override; private: @@ -56,6 +56,7 @@ private: virtual bool RecvCloseAll() override; bool mDestroyStarted; + bool mDestroyDelayed; NS_DECL_OWNINGTHREAD }; diff --git a/dom/cache/CacheStreamControlParent.cpp b/dom/cache/CacheStreamControlParent.cpp index 4e4e4c5aa0..2aac608de5 100644 --- a/dom/cache/CacheStreamControlParent.cpp +++ b/dom/cache/CacheStreamControlParent.cpp @@ -8,7 +8,7 @@ #include "mozilla/DebugOnly.h" #include "mozilla/unused.h" -#include "mozilla/dom/cache/PCacheTypes.h" +#include "mozilla/dom/cache/CacheTypes.h" #include "mozilla/dom/cache/ReadStream.h" #include "mozilla/dom/cache/StreamList.h" #include "mozilla/ipc/FileDescriptorSetParent.h" @@ -45,7 +45,7 @@ CacheStreamControlParent::~CacheStreamControlParent() } void -CacheStreamControlParent::SerializeControl(PCacheReadStream* aReadStreamOut) +CacheStreamControlParent::SerializeControl(CacheReadStream* aReadStreamOut) { NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent); aReadStreamOut->controlChild() = nullptr; @@ -53,7 +53,7 @@ CacheStreamControlParent::SerializeControl(PCacheReadStream* aReadStreamOut) } void -CacheStreamControlParent::SerializeFds(PCacheReadStream* aReadStreamOut, +CacheStreamControlParent::SerializeFds(CacheReadStream* aReadStreamOut, const nsTArray& aFds) { NS_ASSERT_OWNINGTHREAD(CacheStreamControlParent); @@ -73,7 +73,7 @@ CacheStreamControlParent::SerializeFds(PCacheReadStream* aReadStreamOut, } void -CacheStreamControlParent::DeserializeFds(const PCacheReadStream& aReadStream, +CacheStreamControlParent::DeserializeFds(const CacheReadStream& aReadStream, nsTArray& aFdsOut) { if (aReadStream.fds().type() != diff --git a/dom/cache/CacheStreamControlParent.h b/dom/cache/CacheStreamControlParent.h index 6561032ad1..706a98b659 100644 --- a/dom/cache/CacheStreamControlParent.h +++ b/dom/cache/CacheStreamControlParent.h @@ -32,14 +32,14 @@ public: // StreamControl methods virtual void - SerializeControl(PCacheReadStream* aReadStreamOut) override; + SerializeControl(CacheReadStream* aReadStreamOut) override; virtual void - SerializeFds(PCacheReadStream* aReadStreamOut, + SerializeFds(CacheReadStream* aReadStreamOut, const nsTArray& aFds) override; virtual void - DeserializeFds(const PCacheReadStream& aReadStream, + DeserializeFds(const CacheReadStream& aReadStream, nsTArray& aFdsOut) override; private: diff --git a/dom/cache/CacheTypes.ipdlh b/dom/cache/CacheTypes.ipdlh new file mode 100644 index 0000000000..750e7cbbc4 --- /dev/null +++ b/dom/cache/CacheTypes.ipdlh @@ -0,0 +1,245 @@ +/* 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 protocol PCache; +include protocol PCachePushStream; +include protocol PCacheStreamControl; +include InputStreamParams; + +using HeadersGuardEnum from "mozilla/dom/cache/IPCUtils.h"; +using RequestCredentials from "mozilla/dom/cache/IPCUtils.h"; +using RequestMode from "mozilla/dom/cache/IPCUtils.h"; +using RequestCache from "mozilla/dom/cache/IPCUtils.h"; +using RequestContext from "mozilla/dom/cache/IPCUtils.h"; +using ResponseType from "mozilla/dom/cache/IPCUtils.h"; +using mozilla::void_t from "ipc/IPCMessageUtils.h"; +using struct nsID from "nsID.h"; + +namespace mozilla { +namespace dom { +namespace cache { + +struct CacheQueryParams +{ + bool ignoreSearch; + bool ignoreMethod; + bool ignoreVary; + bool prefixMatch; + bool cacheNameSet; + nsString cacheName; +}; + +struct CacheReadStream +{ + nsID id; + OptionalInputStreamParams params; + OptionalFileDescriptorSet fds; + nullable PCacheStreamControl control; + nullable PCachePushStream pushStream; +}; + +union CacheReadStreamOrVoid +{ + void_t; + CacheReadStream; +}; + +struct HeadersEntry +{ + nsCString name; + nsCString value; +}; + +struct CacheRequest +{ + nsCString method; + nsString url; + nsString urlWithoutQuery; + HeadersEntry[] headers; + HeadersGuardEnum headersGuard; + nsString referrer; + RequestMode mode; + RequestCredentials credentials; + CacheReadStreamOrVoid body; + uint32_t contentPolicyType; + RequestContext context; + RequestCache requestCache; +}; + +union CacheRequestOrVoid +{ + void_t; + CacheRequest; +}; + +struct CacheResponse +{ + ResponseType type; + nsString url; + uint32_t status; + nsCString statusText; + HeadersEntry[] headers; + HeadersGuardEnum headersGuard; + CacheReadStreamOrVoid body; + nsCString securityInfo; +}; + +union CacheResponseOrVoid +{ + void_t; + CacheResponse; +}; + +struct CacheRequestResponse +{ + CacheRequest request; + CacheResponse response; +}; + +struct CacheMatchArgs +{ + CacheRequest request; + CacheQueryParams params; +}; + +struct CacheMatchAllArgs +{ + CacheRequestOrVoid requestOrVoid; + CacheQueryParams params; +}; + +struct CacheAddAllArgs +{ + CacheRequest[] requestList; +}; + +struct CachePutAllArgs +{ + CacheRequestResponse[] requestResponseList; +}; + +struct CacheDeleteArgs +{ + CacheRequest request; + CacheQueryParams params; +}; + +struct CacheKeysArgs +{ + CacheRequestOrVoid requestOrVoid; + CacheQueryParams params; +}; + +struct StorageMatchArgs +{ + CacheRequest request; + CacheQueryParams params; +}; + +struct StorageHasArgs +{ + nsString key; +}; + +struct StorageOpenArgs +{ + nsString key; +}; + +struct StorageDeleteArgs +{ + nsString key; +}; + +struct StorageKeysArgs +{ +}; + +union CacheOpArgs +{ + CacheMatchArgs; + CacheMatchAllArgs; + CacheAddAllArgs; + CachePutAllArgs; + CacheDeleteArgs; + CacheKeysArgs; + StorageMatchArgs; + StorageHasArgs; + StorageOpenArgs; + StorageDeleteArgs; + StorageKeysArgs; +}; + +struct CacheMatchResult +{ + CacheResponseOrVoid responseOrVoid; +}; + +struct CacheMatchAllResult +{ + CacheResponse[] responseList; +}; + +struct CacheAddAllResult +{ +}; + +struct CachePutAllResult +{ +}; + +struct CacheDeleteResult +{ + bool success; +}; + +struct CacheKeysResult +{ + CacheRequest[] requestList; +}; + +struct StorageMatchResult +{ + CacheResponseOrVoid responseOrVoid; +}; + +struct StorageHasResult +{ + bool success; +}; + +struct StorageOpenResult +{ + nullable PCache actor; +}; + +struct StorageDeleteResult +{ + bool success; +}; + +struct StorageKeysResult +{ + nsString[] keyList; +}; + +union CacheOpResult +{ + void_t; + CacheMatchResult; + CacheMatchAllResult; + CacheAddAllResult; + CachePutAllResult; + CacheDeleteResult; + CacheKeysResult; + StorageMatchResult; + StorageHasResult; + StorageOpenResult; + StorageDeleteResult; + StorageKeysResult; +}; + +} // namespace cache +} // namespace dom +} // namespace mozilla diff --git a/dom/cache/Context.cpp b/dom/cache/Context.cpp index 503beefc96..9b4d30061c 100644 --- a/dom/cache/Context.cpp +++ b/dom/cache/Context.cpp @@ -732,6 +732,7 @@ Context::Dispatch(nsIEventTarget* aTarget, Action* aAction) MOZ_ASSERT(aTarget); MOZ_ASSERT(aAction); + MOZ_ASSERT(mState != STATE_CONTEXT_CANCELED); if (mState == STATE_CONTEXT_CANCELED) { return; } else if (mState == STATE_CONTEXT_INIT) { @@ -766,11 +767,18 @@ Context::CancelAll() AllowToClose(); } +bool +Context::IsCanceled() const +{ + NS_ASSERT_OWNINGTHREAD(Context); + return mState == STATE_CONTEXT_CANCELED; +} + void Context::Invalidate() { NS_ASSERT_OWNINGTHREAD(Context); - mManager->Invalidate(); + mManager->NoteClosing(); CancelAll(); } diff --git a/dom/cache/Context.h b/dom/cache/Context.h index bbfba99d94..f2abf02e95 100644 --- a/dom/cache/Context.h +++ b/dom/cache/Context.h @@ -126,6 +126,9 @@ public: // Only callable from the thread that created the Context. void CancelAll(); + // True if CancelAll() has been called. + bool IsCanceled() const; + // Like CancelAll(), but also marks the Manager as "invalid". void Invalidate(); diff --git a/dom/cache/DBAction.cpp b/dom/cache/DBAction.cpp index a1afa7e6ea..3bfedce06a 100644 --- a/dom/cache/DBAction.cpp +++ b/dom/cache/DBAction.cpp @@ -14,6 +14,7 @@ #include "nsIFile.h" #include "nsIURI.h" #include "nsNetUtil.h" +#include "nsThreadUtils.h" #include "DBSchema.h" #include "FileUtils.h" @@ -146,7 +147,7 @@ DBAction::OpenConnection(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, int32_t schemaVersion = 0; rv = conn->GetSchemaVersion(&schemaVersion); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - if (schemaVersion > 0 && schemaVersion < DBSchema::kMaxWipeSchemaVersion) { + if (schemaVersion > 0 && schemaVersion < db::kMaxWipeSchemaVersion) { conn = nullptr; rv = WipeDatabase(dbFile, aDBDir); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -154,7 +155,7 @@ DBAction::OpenConnection(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn)); } - rv = DBSchema::InitializeConnection(conn); + rv = db::InitializeConnection(conn); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } conn.forget(aConnOut); @@ -169,7 +170,7 @@ DBAction::WipeDatabase(nsIFile* aDBFile, nsIFile* aDBDir) if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Delete the morgue as well. - rv = FileUtils::BodyDeleteDir(aDBDir); + rv = BodyDeleteDir(aDBDir); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; diff --git a/dom/cache/DBAction.h b/dom/cache/DBAction.h index cb38eb48a4..93ba341c43 100644 --- a/dom/cache/DBAction.h +++ b/dom/cache/DBAction.h @@ -8,7 +8,6 @@ #define mozilla_dom_cache_DBAction_h #include "mozilla/dom/cache/Action.h" -#include "mozilla/dom/cache/CacheInitData.h" #include "mozilla/nsRefPtr.h" #include "nsString.h" diff --git a/dom/cache/DBSchema.cpp b/dom/cache/DBSchema.cpp index 0c74497e1e..a8c3bca1f2 100644 --- a/dom/cache/DBSchema.cpp +++ b/dom/cache/DBSchema.cpp @@ -8,8 +8,10 @@ #include "ipc/IPCMessageUtils.h" #include "mozilla/dom/InternalHeaders.h" -#include "mozilla/dom/cache/PCacheTypes.h" +#include "mozilla/dom/cache/CacheTypes.h" #include "mozilla/dom/cache/SavedTypes.h" +#include "mozilla/dom/cache/Types.h" +#include "mozilla/dom/cache/TypeUtils.h" #include "mozIStorageConnection.h" #include "mozIStorageStatement.h" #include "nsCOMPtr.h" @@ -19,16 +21,21 @@ #include "mozilla/dom/HeadersBinding.h" #include "mozilla/dom/RequestBinding.h" #include "mozilla/dom/ResponseBinding.h" -#include "Types.h" #include "nsIContentPolicy.h" namespace mozilla { namespace dom { namespace cache { +namespace db { -const int32_t DBSchema::kMaxWipeSchemaVersion = 6; -const int32_t DBSchema::kLatestSchemaVersion = 6; -const int32_t DBSchema::kMaxEntriesPerStatement = 255; +const int32_t kMaxWipeSchemaVersion = 6; + +namespace { + +const int32_t kLatestSchemaVersion = 6; +const int32_t kMaxEntriesPerStatement = 255; + +} // anonymous namespace // If any of the static_asserts below fail, it means that you have changed // the corresponding WebIDL enum in a way that may be incompatible with the @@ -140,11 +147,48 @@ static_assert(nsIContentPolicy::TYPE_INVALID == 0 && nsIContentPolicy::TYPE_IMAGESET == 21, "nsContentPolicytType values are as expected"); -using mozilla::void_t; +namespace { + +typedef int32_t EntryId; + +static nsresult QueryAll(mozIStorageConnection* aConn, CacheId aCacheId, + nsTArray& aEntryIdListOut); +static nsresult QueryCache(mozIStorageConnection* aConn, CacheId aCacheId, + const CacheRequest& aRequest, + const CacheQueryParams& aParams, + nsTArray& aEntryIdListOut, + uint32_t aMaxResults = UINT32_MAX); +static nsresult MatchByVaryHeader(mozIStorageConnection* aConn, + const CacheRequest& aRequest, + EntryId entryId, bool* aSuccessOut); +static nsresult DeleteEntries(mozIStorageConnection* aConn, + const nsTArray& aEntryIdList, + nsTArray& aDeletedBodyIdListOut, + uint32_t aPos=0, int32_t aLen=-1); +static nsresult InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId, + const CacheRequest& aRequest, + const nsID* aRequestBodyId, + const CacheResponse& aResponse, + const nsID* aResponseBodyId); +static nsresult ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId, + SavedResponse* aSavedResponseOut); +static nsresult ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId, + SavedRequest* aSavedRequestOut); + +static void AppendListParamsToQuery(nsACString& aQuery, + const nsTArray& aEntryIdList, + uint32_t aPos, int32_t aLen); +static nsresult BindListParamsToQuery(mozIStorageStatement* aState, + const nsTArray& aEntryIdList, + uint32_t aPos, int32_t aLen); +static nsresult BindId(mozIStorageStatement* aState, uint32_t aPos, + const nsID* aId); +static nsresult ExtractId(mozIStorageStatement* aState, uint32_t aPos, + nsID* aIdOut); +} // anonymous namespace -// static nsresult -DBSchema::CreateSchema(mozIStorageConnection* aConn) +CreateSchema(mozIStorageConnection* aConn) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -284,9 +328,8 @@ DBSchema::CreateSchema(mozIStorageConnection* aConn) return rv; } -// static nsresult -DBSchema::InitializeConnection(mozIStorageConnection* aConn) +InitializeConnection(mozIStorageConnection* aConn) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -315,9 +358,8 @@ DBSchema::InitializeConnection(mozIStorageConnection* aConn) return NS_OK; } -// static nsresult -DBSchema::CreateCache(mozIStorageConnection* aConn, CacheId* aCacheIdOut) +CreateCacheId(mozIStorageConnection* aConn, CacheId* aCacheIdOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -345,10 +387,9 @@ DBSchema::CreateCache(mozIStorageConnection* aConn, CacheId* aCacheIdOut) return rv; } -// static nsresult -DBSchema::DeleteCache(mozIStorageConnection* aConn, CacheId aCacheId, - nsTArray& aDeletedBodyIdListOut) +DeleteCacheId(mozIStorageConnection* aConn, CacheId aCacheId, + nsTArray& aDeletedBodyIdListOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -379,10 +420,9 @@ DBSchema::DeleteCache(mozIStorageConnection* aConn, CacheId aCacheId, return rv; } -// static nsresult -DBSchema::IsCacheOrphaned(mozIStorageConnection* aConn, - CacheId aCacheId, bool* aOrphanedOut) +IsCacheOrphaned(mozIStorageConnection* aConn, CacheId aCacheId, + bool* aOrphanedOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -414,13 +454,12 @@ DBSchema::IsCacheOrphaned(mozIStorageConnection* aConn, return rv; } -// static nsresult -DBSchema::CacheMatch(mozIStorageConnection* aConn, CacheId aCacheId, - const PCacheRequest& aRequest, - const PCacheQueryParams& aParams, - bool* aFoundResponseOut, - SavedResponse* aSavedResponseOut) +CacheMatch(mozIStorageConnection* aConn, CacheId aCacheId, + const CacheRequest& aRequest, + const CacheQueryParams& aParams, + bool* aFoundResponseOut, + SavedResponse* aSavedResponseOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -446,19 +485,18 @@ DBSchema::CacheMatch(mozIStorageConnection* aConn, CacheId aCacheId, return rv; } -// static nsresult -DBSchema::CacheMatchAll(mozIStorageConnection* aConn, CacheId aCacheId, - const PCacheRequestOrVoid& aRequestOrVoid, - const PCacheQueryParams& aParams, - nsTArray& aSavedResponsesOut) +CacheMatchAll(mozIStorageConnection* aConn, CacheId aCacheId, + const CacheRequestOrVoid& aRequestOrVoid, + const CacheQueryParams& aParams, + nsTArray& aSavedResponsesOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); nsresult rv; nsAutoTArray matches; - if (aRequestOrVoid.type() == PCacheRequestOrVoid::Tvoid_t) { + if (aRequestOrVoid.type() == CacheRequestOrVoid::Tvoid_t) { rv = QueryAll(aConn, aCacheId, matches); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else { @@ -478,19 +516,18 @@ DBSchema::CacheMatchAll(mozIStorageConnection* aConn, CacheId aCacheId, return rv; } -// static nsresult -DBSchema::CachePut(mozIStorageConnection* aConn, CacheId aCacheId, - const PCacheRequest& aRequest, - const nsID* aRequestBodyId, - const PCacheResponse& aResponse, - const nsID* aResponseBodyId, - nsTArray& aDeletedBodyIdListOut) +CachePut(mozIStorageConnection* aConn, CacheId aCacheId, + const CacheRequest& aRequest, + const nsID* aRequestBodyId, + const CacheResponse& aResponse, + const nsID* aResponseBodyId, + nsTArray& aDeletedBodyIdListOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); - PCacheQueryParams params(false, false, false, false, false, + CacheQueryParams params(false, false, false, false, false, NS_LITERAL_STRING("")); nsAutoTArray matches; nsresult rv = QueryCache(aConn, aCacheId, aRequest, params, matches); @@ -506,12 +543,11 @@ DBSchema::CachePut(mozIStorageConnection* aConn, CacheId aCacheId, return rv; } -// static nsresult -DBSchema::CacheDelete(mozIStorageConnection* aConn, CacheId aCacheId, - const PCacheRequest& aRequest, - const PCacheQueryParams& aParams, - nsTArray& aDeletedBodyIdListOut, bool* aSuccessOut) +CacheDelete(mozIStorageConnection* aConn, CacheId aCacheId, + const CacheRequest& aRequest, + const CacheQueryParams& aParams, + nsTArray& aDeletedBodyIdListOut, bool* aSuccessOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -535,19 +571,18 @@ DBSchema::CacheDelete(mozIStorageConnection* aConn, CacheId aCacheId, return rv; } -// static nsresult -DBSchema::CacheKeys(mozIStorageConnection* aConn, CacheId aCacheId, - const PCacheRequestOrVoid& aRequestOrVoid, - const PCacheQueryParams& aParams, - nsTArray& aSavedRequestsOut) +CacheKeys(mozIStorageConnection* aConn, CacheId aCacheId, + const CacheRequestOrVoid& aRequestOrVoid, + const CacheQueryParams& aParams, + nsTArray& aSavedRequestsOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); nsresult rv; nsAutoTArray matches; - if (aRequestOrVoid.type() == PCacheRequestOrVoid::Tvoid_t) { + if (aRequestOrVoid.type() == CacheRequestOrVoid::Tvoid_t) { rv = QueryAll(aConn, aCacheId, matches); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else { @@ -567,14 +602,13 @@ DBSchema::CacheKeys(mozIStorageConnection* aConn, CacheId aCacheId, return rv; } -// static nsresult -DBSchema::StorageMatch(mozIStorageConnection* aConn, - Namespace aNamespace, - const PCacheRequest& aRequest, - const PCacheQueryParams& aParams, - bool* aFoundResponseOut, - SavedResponse* aSavedResponseOut) +StorageMatch(mozIStorageConnection* aConn, + Namespace aNamespace, + const CacheRequest& aRequest, + const CacheQueryParams& aParams, + bool* aFoundResponseOut, + SavedResponse* aSavedResponseOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -639,11 +673,10 @@ DBSchema::StorageMatch(mozIStorageConnection* aConn, return NS_OK; } -// static nsresult -DBSchema::StorageGetCacheId(mozIStorageConnection* aConn, Namespace aNamespace, - const nsAString& aKey, bool* aFoundCacheOut, - CacheId* aCacheIdOut) +StorageGetCacheId(mozIStorageConnection* aConn, Namespace aNamespace, + const nsAString& aKey, bool* aFoundCacheOut, + CacheId* aCacheIdOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -679,10 +712,9 @@ DBSchema::StorageGetCacheId(mozIStorageConnection* aConn, Namespace aNamespace, return rv; } -// static nsresult -DBSchema::StoragePutCache(mozIStorageConnection* aConn, Namespace aNamespace, - const nsAString& aKey, CacheId aCacheId) +StoragePutCache(mozIStorageConnection* aConn, Namespace aNamespace, + const nsAString& aKey, CacheId aCacheId) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -708,10 +740,9 @@ DBSchema::StoragePutCache(mozIStorageConnection* aConn, Namespace aNamespace, return rv; } -// static nsresult -DBSchema::StorageForgetCache(mozIStorageConnection* aConn, Namespace aNamespace, - const nsAString& aKey) +StorageForgetCache(mozIStorageConnection* aConn, Namespace aNamespace, + const nsAString& aKey) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -734,10 +765,9 @@ DBSchema::StorageForgetCache(mozIStorageConnection* aConn, Namespace aNamespace, return rv; } -// static nsresult -DBSchema::StorageGetKeys(mozIStorageConnection* aConn, Namespace aNamespace, - nsTArray& aKeysOut) +StorageGetKeys(mozIStorageConnection* aConn, Namespace aNamespace, + nsTArray& aKeysOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -762,10 +792,11 @@ DBSchema::StorageGetKeys(mozIStorageConnection* aConn, Namespace aNamespace, return rv; } -// static +namespace { + nsresult -DBSchema::QueryAll(mozIStorageConnection* aConn, CacheId aCacheId, - nsTArray& aEntryIdListOut) +QueryAll(mozIStorageConnection* aConn, CacheId aCacheId, + nsTArray& aEntryIdListOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -790,13 +821,12 @@ DBSchema::QueryAll(mozIStorageConnection* aConn, CacheId aCacheId, return rv; } -// static nsresult -DBSchema::QueryCache(mozIStorageConnection* aConn, CacheId aCacheId, - const PCacheRequest& aRequest, - const PCacheQueryParams& aParams, - nsTArray& aEntryIdListOut, - uint32_t aMaxResults) +QueryCache(mozIStorageConnection* aConn, CacheId aCacheId, + const CacheRequest& aRequest, + const CacheQueryParams& aParams, + nsTArray& aEntryIdListOut, + uint32_t aMaxResults) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -882,11 +912,10 @@ DBSchema::QueryCache(mozIStorageConnection* aConn, CacheId aCacheId, return rv; } -// static nsresult -DBSchema::MatchByVaryHeader(mozIStorageConnection* aConn, - const PCacheRequest& aRequest, - EntryId entryId, bool* aSuccessOut) +MatchByVaryHeader(mozIStorageConnection* aConn, + const CacheRequest& aRequest, + EntryId entryId, bool* aSuccessOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -927,7 +956,8 @@ DBSchema::MatchByVaryHeader(mozIStorageConnection* aConn, rv = state->BindInt32Parameter(0, entryId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - nsRefPtr cachedHeaders = new InternalHeaders(HeadersGuardEnum::None); + nsRefPtr cachedHeaders = + new InternalHeaders(HeadersGuardEnum::None); while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) { nsAutoCString name; @@ -944,7 +974,8 @@ DBSchema::MatchByVaryHeader(mozIStorageConnection* aConn, } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - nsRefPtr queryHeaders = new InternalHeaders(aRequest.headers()); + nsRefPtr queryHeaders = + TypeUtils::ToInternalHeaders(aRequest.headers()); // Assume the vary headers match until we find a conflict bool varyHeadersMatch = true; @@ -993,12 +1024,11 @@ DBSchema::MatchByVaryHeader(mozIStorageConnection* aConn, return rv; } -// static nsresult -DBSchema::DeleteEntries(mozIStorageConnection* aConn, - const nsTArray& aEntryIdList, - nsTArray& aDeletedBodyIdListOut, - uint32_t aPos, int32_t aLen) +DeleteEntries(mozIStorageConnection* aConn, + const nsTArray& aEntryIdList, + nsTArray& aDeletedBodyIdListOut, + uint32_t aPos, int32_t aLen) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -1082,13 +1112,12 @@ DBSchema::DeleteEntries(mozIStorageConnection* aConn, return rv; } -// static nsresult -DBSchema::InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId, - const PCacheRequest& aRequest, - const nsID* aRequestBodyId, - const PCacheResponse& aResponse, - const nsID* aResponseBodyId) +InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId, + const CacheRequest& aRequest, + const nsID* aRequestBodyId, + const CacheResponse& aResponse, + const nsID* aResponseBodyId) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -1209,7 +1238,7 @@ DBSchema::InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId, ), getter_AddRefs(state)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - const nsTArray& requestHeaders = aRequest.headers(); + const nsTArray& requestHeaders = aRequest.headers(); for (uint32_t i = 0; i < requestHeaders.Length(); ++i) { rv = state->BindUTF8StringParameter(0, requestHeaders[i].name()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -1233,7 +1262,7 @@ DBSchema::InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId, ), getter_AddRefs(state)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - const nsTArray& responseHeaders = aResponse.headers(); + const nsTArray& responseHeaders = aResponse.headers(); for (uint32_t i = 0; i < responseHeaders.Length(); ++i) { rv = state->BindUTF8StringParameter(0, responseHeaders[i].name()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -1251,10 +1280,9 @@ DBSchema::InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId, return rv; } -// static nsresult -DBSchema::ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId, - SavedResponse* aSavedResponseOut) +ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId, + SavedResponse* aSavedResponseOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -1334,7 +1362,7 @@ DBSchema::ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId, if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) { - PHeadersEntry header; + HeadersEntry header; rv = state->GetUTF8String(0, header.name()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -1348,10 +1376,9 @@ DBSchema::ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId, return rv; } -// static nsresult -DBSchema::ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId, - SavedRequest* aSavedRequestOut) +ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId, + SavedRequest* aSavedRequestOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConn); @@ -1453,7 +1480,7 @@ DBSchema::ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId, if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) { - PHeadersEntry header; + HeadersEntry header; rv = state->GetUTF8String(0, header.name()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -1467,11 +1494,10 @@ DBSchema::ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId, return rv; } -// static void -DBSchema::AppendListParamsToQuery(nsACString& aQuery, - const nsTArray& aEntryIdList, - uint32_t aPos, int32_t aLen) +AppendListParamsToQuery(nsACString& aQuery, + const nsTArray& aEntryIdList, + uint32_t aPos, int32_t aLen) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT((aPos + aLen) <= aEntryIdList.Length()); @@ -1484,11 +1510,10 @@ DBSchema::AppendListParamsToQuery(nsACString& aQuery, } } -// static nsresult -DBSchema::BindListParamsToQuery(mozIStorageStatement* aState, - const nsTArray& aEntryIdList, - uint32_t aPos, int32_t aLen) +BindListParamsToQuery(mozIStorageStatement* aState, + const nsTArray& aEntryIdList, + uint32_t aPos, int32_t aLen) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT((aPos + aLen) <= aEntryIdList.Length()); @@ -1499,9 +1524,8 @@ DBSchema::BindListParamsToQuery(mozIStorageStatement* aState, return NS_OK; } -// static nsresult -DBSchema::BindId(mozIStorageStatement* aState, uint32_t aPos, const nsID* aId) +BindId(mozIStorageStatement* aState, uint32_t aPos, const nsID* aId) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aState); @@ -1521,9 +1545,8 @@ DBSchema::BindId(mozIStorageStatement* aState, uint32_t aPos, const nsID* aId) return rv; } -// static nsresult -DBSchema::ExtractId(mozIStorageStatement* aState, uint32_t aPos, nsID* aIdOut) +ExtractId(mozIStorageStatement* aState, uint32_t aPos, nsID* aIdOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aState); @@ -1539,6 +1562,9 @@ DBSchema::ExtractId(mozIStorageStatement* aState, uint32_t aPos, nsID* aIdOut) return rv; } +} // anonymouns namespace + +} // namespace db } // namespace cache } // namespace dom } // namespace mozilla diff --git a/dom/cache/DBSchema.h b/dom/cache/DBSchema.h index 37d3217ffd..fdd2d85d39 100644 --- a/dom/cache/DBSchema.h +++ b/dom/cache/DBSchema.h @@ -14,129 +14,100 @@ #include "nsTArrayForwardDeclare.h" class mozIStorageConnection; -class mozIStorageStatement; struct nsID; namespace mozilla { namespace dom { namespace cache { -class PCacheQueryParams; -class PCacheRequest; -class PCacheRequestOrVoid; -class PCacheResponse; +class CacheQueryParams; +class CacheRequest; +class CacheRequestOrVoid; +class CacheResponse; struct SavedRequest; struct SavedResponse; -// TODO: remove static class and use functions in cache namespace (bug 1110485) -class DBSchema final -{ -public: - static nsresult CreateSchema(mozIStorageConnection* aConn); - static nsresult InitializeConnection(mozIStorageConnection* aConn); +namespace db { - static nsresult CreateCache(mozIStorageConnection* aConn, - CacheId* aCacheIdOut); - // TODO: improve naming (confusing with CacheDelete) (bug 1110485) - static nsresult DeleteCache(mozIStorageConnection* aConn, CacheId aCacheId, - nsTArray& aDeletedBodyIdListOut); +nsresult +CreateSchema(mozIStorageConnection* aConn); - // TODO: Consider removing unused IsCacheOrphaned after writing cleanup code. (bug 1110446) - static nsresult IsCacheOrphaned(mozIStorageConnection* aConn, - CacheId aCacheId, bool* aOrphanedOut); +nsresult +InitializeConnection(mozIStorageConnection* aConn); - static nsresult CacheMatch(mozIStorageConnection* aConn, CacheId aCacheId, - const PCacheRequest& aRequest, - const PCacheQueryParams& aParams, - bool* aFoundResponseOut, - SavedResponse* aSavedResponseOut); - static nsresult CacheMatchAll(mozIStorageConnection* aConn, CacheId aCacheId, - const PCacheRequestOrVoid& aRequestOrVoid, - const PCacheQueryParams& aParams, - nsTArray& aSavedResponsesOut); - static nsresult CachePut(mozIStorageConnection* aConn, CacheId aCacheId, - const PCacheRequest& aRequest, - const nsID* aRequestBodyId, - const PCacheResponse& aResponse, - const nsID* aResponseBodyId, - nsTArray& aDeletedBodyIdListOut); - static nsresult CacheDelete(mozIStorageConnection* aConn, CacheId aCacheId, - const PCacheRequest& aRequest, - const PCacheQueryParams& aParams, - nsTArray& aDeletedBodyIdListOut, - bool* aSuccessOut); - static nsresult CacheKeys(mozIStorageConnection* aConn, CacheId aCacheId, - const PCacheRequestOrVoid& aRequestOrVoid, - const PCacheQueryParams& aParams, - nsTArray& aSavedRequestsOut); +nsresult +CreateCacheId(mozIStorageConnection* aConn, CacheId* aCacheIdOut); - static nsresult StorageMatch(mozIStorageConnection* aConn, - Namespace aNamespace, - const PCacheRequest& aRequest, - const PCacheQueryParams& aParams, - bool* aFoundResponseOut, - SavedResponse* aSavedResponseOut); - static nsresult StorageGetCacheId(mozIStorageConnection* aConn, - Namespace aNamespace, const nsAString& aKey, - bool* aFoundCacheOut, CacheId* aCacheIdOut); - static nsresult StoragePutCache(mozIStorageConnection* aConn, - Namespace aNamespace, const nsAString& aKey, - CacheId aCacheId); - static nsresult StorageForgetCache(mozIStorageConnection* aConn, - Namespace aNamespace, - const nsAString& aKey); - static nsresult StorageGetKeys(mozIStorageConnection* aConn, - Namespace aNamespace, - nsTArray& aKeysOut); +nsresult +DeleteCacheId(mozIStorageConnection* aConn, CacheId aCacheId, + nsTArray& aDeletedBodyIdListOut); - // We will wipe out databases with a schema versions less than this. - static const int32_t kMaxWipeSchemaVersion; +// TODO: Consider removing unused IsCacheOrphaned after writing cleanup code. (bug 1110446) +nsresult +IsCacheOrphaned(mozIStorageConnection* aConn, CacheId aCacheId, + bool* aOrphanedOut); -private: - typedef int32_t EntryId; +nsresult +CacheMatch(mozIStorageConnection* aConn, CacheId aCacheId, + const CacheRequest& aRequest, const CacheQueryParams& aParams, + bool* aFoundResponseOut, SavedResponse* aSavedResponseOut); - static nsresult QueryAll(mozIStorageConnection* aConn, CacheId aCacheId, - nsTArray& aEntryIdListOut); - static nsresult QueryCache(mozIStorageConnection* aConn, CacheId aCacheId, - const PCacheRequest& aRequest, - const PCacheQueryParams& aParams, - nsTArray& aEntryIdListOut, - uint32_t aMaxResults = UINT32_MAX); - static nsresult MatchByVaryHeader(mozIStorageConnection* aConn, - const PCacheRequest& aRequest, - EntryId entryId, bool* aSuccessOut); - static nsresult DeleteEntries(mozIStorageConnection* aConn, - const nsTArray& aEntryIdList, - nsTArray& aDeletedBodyIdListOut, - uint32_t aPos=0, int32_t aLen=-1); - static nsresult InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId, - const PCacheRequest& aRequest, - const nsID* aRequestBodyId, - const PCacheResponse& aResponse, - const nsID* aResponseBodyId); - static nsresult ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId, - SavedResponse* aSavedResponseOut); - static nsresult ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId, - SavedRequest* aSavedRequestOut); +nsresult +CacheMatchAll(mozIStorageConnection* aConn, CacheId aCacheId, + const CacheRequestOrVoid& aRequestOrVoid, + const CacheQueryParams& aParams, + nsTArray& aSavedResponsesOut); - static void AppendListParamsToQuery(nsACString& aQuery, - const nsTArray& aEntryIdList, - uint32_t aPos, int32_t aLen); - static nsresult BindListParamsToQuery(mozIStorageStatement* aState, - const nsTArray& aEntryIdList, - uint32_t aPos, int32_t aLen); - static nsresult BindId(mozIStorageStatement* aState, uint32_t aPos, - const nsID* aId); - static nsresult ExtractId(mozIStorageStatement* aState, uint32_t aPos, - nsID* aIdOut); +nsresult +CachePut(mozIStorageConnection* aConn, CacheId aCacheId, + const CacheRequest& aRequest, + const nsID* aRequestBodyId, + const CacheResponse& aResponse, + const nsID* aResponseBodyId, + nsTArray& aDeletedBodyIdListOut); - DBSchema() = delete; - ~DBSchema() = delete; +nsresult +CacheDelete(mozIStorageConnection* aConn, CacheId aCacheId, + const CacheRequest& aRequest, + const CacheQueryParams& aParams, + nsTArray& aDeletedBodyIdListOut, + bool* aSuccessOut); - static const int32_t kLatestSchemaVersion; - static const int32_t kMaxEntriesPerStatement; -}; +nsresult +CacheKeys(mozIStorageConnection* aConn, CacheId aCacheId, + const CacheRequestOrVoid& aRequestOrVoid, + const CacheQueryParams& aParams, + nsTArray& aSavedRequestsOut); +nsresult +StorageMatch(mozIStorageConnection* aConn, + Namespace aNamespace, + const CacheRequest& aRequest, + const CacheQueryParams& aParams, + bool* aFoundResponseOut, + SavedResponse* aSavedResponseOut); + +nsresult +StorageGetCacheId(mozIStorageConnection* aConn, Namespace aNamespace, + const nsAString& aKey, bool* aFoundCacheOut, + CacheId* aCacheIdOut); + +nsresult +StoragePutCache(mozIStorageConnection* aConn, Namespace aNamespace, + const nsAString& aKey, CacheId aCacheId); + +nsresult +StorageForgetCache(mozIStorageConnection* aConn, Namespace aNamespace, + const nsAString& aKey); + +nsresult +StorageGetKeys(mozIStorageConnection* aConn, Namespace aNamespace, + nsTArray& aKeysOut); + +// We will wipe out databases with a schema versions less than this. +extern const int32_t kMaxWipeSchemaVersion; + +} // namespace db } // namespace cache } // namespace dom } // namespace mozilla diff --git a/dom/cache/FetchPut.cpp b/dom/cache/FetchPut.cpp index 561ef75659..78f1ae926b 100644 --- a/dom/cache/FetchPut.cpp +++ b/dom/cache/FetchPut.cpp @@ -16,7 +16,6 @@ #include "mozilla/dom/ResponseBinding.h" #include "mozilla/dom/UnionTypes.h" #include "mozilla/dom/cache/ManagerId.h" -#include "mozilla/dom/cache/PCacheTypes.h" #include "nsContentUtils.h" #include "nsNetUtil.h" #include "nsThreadUtils.h" @@ -98,9 +97,8 @@ private: // static nsresult -FetchPut::Create(Listener* aListener, Manager* aManager, - RequestId aRequestId, CacheId aCacheId, - const nsTArray& aRequests, +FetchPut::Create(Listener* aListener, Manager* aManager, CacheId aCacheId, + const nsTArray& aRequests, const nsTArray>& aRequestStreams, FetchPut** aFetchPutOut) { @@ -115,7 +113,7 @@ FetchPut::Create(Listener* aListener, Manager* aManager, } #endif - nsRefPtr ref = new FetchPut(aListener, aManager, aRequestId, aCacheId, + nsRefPtr ref = new FetchPut(aListener, aManager, aCacheId, aRequests, aRequestStreams); nsresult rv = ref->DispatchToMainThread(); @@ -133,13 +131,11 @@ FetchPut::ClearListener() mListener = nullptr; } -FetchPut::FetchPut(Listener* aListener, Manager* aManager, - RequestId aRequestId, CacheId aCacheId, - const nsTArray& aRequests, +FetchPut::FetchPut(Listener* aListener, Manager* aManager, CacheId aCacheId, + const nsTArray& aRequests, const nsTArray>& aRequestStreams) : mListener(aListener) , mManager(aManager) - , mRequestId(aRequestId) , mCacheId(aCacheId) , mInitiatingThread(NS_GetCurrentThread()) , mStateList(aRequests.Length()) @@ -151,7 +147,7 @@ FetchPut::FetchPut(Listener* aListener, Manager* aManager, for (uint32_t i = 0; i < aRequests.Length(); ++i) { State* s = mStateList.AppendElement(); - s->mPCacheRequest = aRequests[i]; + s->mCacheRequest = aRequests[i]; s->mRequestStream = aRequestStreams[i]; } @@ -211,14 +207,14 @@ FetchPut::DoFetchOnMainThread() nsCOMPtr loadGroup; nsresult rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), principal); if (NS_WARN_IF(NS_FAILED(rv))) { - MaybeSetError(rv); + MaybeSetError(ErrorResult(rv)); MaybeCompleteOnMainThread(); return; } for (uint32_t i = 0; i < mStateList.Length(); ++i) { nsRefPtr internalRequest = - ToInternalRequest(mStateList[i].mPCacheRequest); + ToInternalRequest(mStateList[i].mCacheRequest); // If there is a stream we must clone it so that its still available // to store in the cache later; @@ -240,7 +236,7 @@ FetchPut::DoFetchOnMainThread() mStateList[i].mFetchObserver = new FetchObserver(this); rv = fetchDriver->Fetch(mStateList[i].mFetchObserver); if (NS_WARN_IF(NS_FAILED(rv))) { - MaybeSetError(rv); + MaybeSetError(ErrorResult(rv)); mStateList[i].mFetchObserver = nullptr; mPendingCount -= 1; continue; @@ -258,16 +254,16 @@ FetchPut::FetchComplete(FetchObserver* aObserver, MOZ_ASSERT(NS_IsMainThread()); if (aInternalResponse->IsError() && !mResult.Failed()) { - MaybeSetError(NS_ERROR_FAILURE); + MaybeSetError(ErrorResult(NS_ERROR_FAILURE)); } for (uint32_t i = 0; i < mStateList.Length(); ++i) { if (mStateList[i].mFetchObserver == aObserver) { ErrorResult rv; - ToPCacheResponseWithoutBody(mStateList[i].mPCacheResponse, + ToCacheResponseWithoutBody(mStateList[i].mCacheResponse, *aInternalResponse, rv); if (rv.Failed()) { - mResult = Move(rv); + MaybeSetError(Move(rv)); } else { aInternalResponse->GetBody(getter_AddRefs(mStateList[i].mResponseStream)); } @@ -316,27 +312,27 @@ FetchPut::DoPutOnWorkerThread() for (uint32_t i = 0; i < mStateList.Length(); ++i) { // The spec requires us to catch if content tries to insert a set of // requests that would overwrite each other. - if (MatchInPutList(mStateList[i].mPCacheRequest, putList)) { - MaybeSetError(NS_ERROR_DOM_INVALID_STATE_ERR); + if (MatchInPutList(mStateList[i].mCacheRequest, putList)) { + MaybeSetError(ErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); MaybeNotifyListener(); return; } CacheRequestResponse* entry = putList.AppendElement(); - entry->request() = mStateList[i].mPCacheRequest; - entry->response() = mStateList[i].mPCacheResponse; + entry->request() = mStateList[i].mCacheRequest; + entry->response() = mStateList[i].mCacheResponse; requestStreamList.AppendElement(mStateList[i].mRequestStream.forget()); responseStreamList.AppendElement(mStateList[i].mResponseStream.forget()); } mStateList.Clear(); - mManager->CachePutAll(this, mRequestId, mCacheId, putList, requestStreamList, - responseStreamList); + mManager->ExecutePutAll(this, mCacheId, putList, requestStreamList, + responseStreamList); } // static bool -FetchPut::MatchInPutList(const PCacheRequest& aRequest, +FetchPut::MatchInPutList(const CacheRequest& aRequest, const nsTArray& aPutList) { // This method implements the SW spec QueryCache algorithm against an @@ -351,11 +347,11 @@ FetchPut::MatchInPutList(const PCacheRequest& aRequest, } nsRefPtr requestHeaders = - new InternalHeaders(aRequest.headers()); + ToInternalHeaders(aRequest.headers()); for (uint32_t i = 0; i < aPutList.Length(); ++i) { - const PCacheRequest& cachedRequest = aPutList[i].request(); - const PCacheResponse& cachedResponse = aPutList[i].response(); + const CacheRequest& cachedRequest = aPutList[i].request(); + const CacheResponse& cachedResponse = aPutList[i].response(); // If the URLs don't match, then just skip to the next entry. if (aRequest.url() != cachedRequest.url()) { @@ -363,10 +359,10 @@ FetchPut::MatchInPutList(const PCacheRequest& aRequest, } nsRefPtr cachedRequestHeaders = - new InternalHeaders(cachedRequest.headers()); + ToInternalHeaders(cachedRequest.headers()); nsRefPtr cachedResponseHeaders = - new InternalHeaders(cachedResponse.headers()); + ToInternalHeaders(cachedResponse.headers()); nsAutoTArray varyHeaders; ErrorResult rv; @@ -426,20 +422,25 @@ FetchPut::MatchInPutList(const PCacheRequest& aRequest, } void -FetchPut::OnCachePutAll(RequestId aRequestId, nsresult aRv) +FetchPut::OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult, + CacheId aOpenedCacheId, + const nsTArray& aSavedResponseList, + const nsTArray& aSavedRequestList, + StreamList* aStreamList) { MOZ_ASSERT(mInitiatingThread == NS_GetCurrentThread()); - MaybeSetError(aRv); + MOZ_ASSERT(aResult.type() == CacheOpResult::TCachePutAllResult); + MaybeSetError(Move(aRv)); MaybeNotifyListener(); } void -FetchPut::MaybeSetError(nsresult aRv) +FetchPut::MaybeSetError(ErrorResult&& aRv) { - if (mResult.Failed() || NS_SUCCEEDED(aRv)) { + if (mResult.Failed() || !aRv.Failed()) { return; } - mResult.Throw(aRv); + mResult = Move(aRv); } void @@ -453,8 +454,7 @@ FetchPut::MaybeNotifyListener() // object is removed from CacheParent::mFetchPutList, so make sure that // doesn't happen until this method returns. nsRefPtr kungFuDeathGrip(this); - mListener->OnFetchPut(this, mRequestId, mResult); - mResult.ClearMessage(); // This may contain a TypeError. + mListener->OnFetchPut(this, Move(mResult)); } nsIGlobalObject* diff --git a/dom/cache/FetchPut.h b/dom/cache/FetchPut.h index 8c50ba0abc..a1867482af 100644 --- a/dom/cache/FetchPut.h +++ b/dom/cache/FetchPut.h @@ -11,7 +11,7 @@ #include "mozilla/Attributes.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/cache/Manager.h" -#include "mozilla/dom/cache/PCacheTypes.h" +#include "mozilla/dom/cache/CacheTypes.h" #include "mozilla/dom/cache/Types.h" #include "mozilla/dom/cache/TypeUtils.h" #include "mozilla/nsRefPtr.h" @@ -40,13 +40,12 @@ public: { public: virtual void - OnFetchPut(FetchPut* aFetchPut, RequestId aRequestId, const ErrorResult& aRv) = 0; + OnFetchPut(FetchPut* aFetchPut, ErrorResult&& aRv) = 0; }; static nsresult - Create(Listener* aListener, Manager* aManager, - RequestId aRequestId, CacheId aCacheId, - const nsTArray& aRequests, + Create(Listener* aListener, Manager* aManager, CacheId aCacheId, + const nsTArray& aRequests, const nsTArray>& aRequestStreams, FetchPut** aFetchPutOut); @@ -58,19 +57,18 @@ private: friend class FetchObserver; struct State { - PCacheRequest mPCacheRequest; + CacheRequest mCacheRequest; nsCOMPtr mRequestStream; nsRefPtr mFetchObserver; - PCacheResponse mPCacheResponse; + CacheResponse mCacheResponse; nsCOMPtr mResponseStream; nsRefPtr mRequest; nsRefPtr mResponse; }; - FetchPut(Listener* aListener, Manager* aManager, - RequestId aRequestId, CacheId aCacheId, - const nsTArray& aRequests, + FetchPut(Listener* aListener, Manager* aManager, CacheId aCacheId, + const nsTArray& aRequests, const nsTArray>& aRequestStreams); ~FetchPut(); @@ -83,11 +81,17 @@ private: void MaybeCompleteOnMainThread(); void DoPutOnWorkerThread(); - static bool MatchInPutList(const PCacheRequest& aRequest, + static bool MatchInPutList(const CacheRequest& aRequest, const nsTArray& aPutList); - virtual void OnCachePutAll(RequestId aRequestId, nsresult aRv) override; - void MaybeSetError(nsresult aRv); + virtual void + OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult, + CacheId aOpenedCacheId, + const nsTArray& aSavedResponseList, + const nsTArray& aSavedRequestList, + StreamList* aStreamList) override; + + void MaybeSetError(ErrorResult&& aRv); void MaybeNotifyListener(); // TypeUtils methods @@ -101,7 +105,6 @@ private: Listener* mListener; nsRefPtr mManager; - const RequestId mRequestId; const CacheId mCacheId; nsCOMPtr mInitiatingThread; nsTArray mStateList; diff --git a/dom/cache/FileUtils.cpp b/dom/cache/FileUtils.cpp index 2cbfef755a..665cd0d1c0 100644 --- a/dom/cache/FileUtils.cpp +++ b/dom/cache/FileUtils.cpp @@ -23,11 +23,24 @@ namespace cache { using mozilla::dom::quota::FileInputStream; using mozilla::dom::quota::FileOutputStream; using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT; -using mozilla::unused; + +namespace { + +enum BodyFileType +{ + BODY_FILE_FINAL, + BODY_FILE_TMP +}; + +nsresult +BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, BodyFileType aType, + nsIFile** aBodyFileOut); + +} // anonymous namespace // static nsresult -FileUtils::BodyCreateDir(nsIFile* aBaseDir) +BodyCreateDir(nsIFile* aBaseDir) { MOZ_ASSERT(aBaseDir); @@ -49,7 +62,7 @@ FileUtils::BodyCreateDir(nsIFile* aBaseDir) // static nsresult -FileUtils::BodyDeleteDir(nsIFile* aBaseDir) +BodyDeleteDir(nsIFile* aBaseDir) { MOZ_ASSERT(aBaseDir); @@ -72,8 +85,7 @@ FileUtils::BodyDeleteDir(nsIFile* aBaseDir) // static nsresult -FileUtils::BodyGetCacheDir(nsIFile* aBaseDir, const nsID& aId, - nsIFile** aCacheDirOut) +BodyGetCacheDir(nsIFile* aBaseDir, const nsID& aId, nsIFile** aCacheDirOut) { MOZ_ASSERT(aBaseDir); MOZ_ASSERT(aCacheDirOut); @@ -107,11 +119,11 @@ FileUtils::BodyGetCacheDir(nsIFile* aBaseDir, const nsID& aId, // static nsresult -FileUtils::BodyStartWriteStream(const QuotaInfo& aQuotaInfo, - nsIFile* aBaseDir, nsIInputStream* aSource, - void* aClosure, - nsAsyncCopyCallbackFun aCallback, nsID* aIdOut, - nsISupports** aCopyContextOut) +BodyStartWriteStream(const QuotaInfo& aQuotaInfo, + nsIFile* aBaseDir, nsIInputStream* aSource, + void* aClosure, + nsAsyncCopyCallbackFun aCallback, nsID* aIdOut, + nsISupports** aCopyContextOut) { MOZ_ASSERT(aBaseDir); MOZ_ASSERT(aSource); @@ -168,7 +180,7 @@ FileUtils::BodyStartWriteStream(const QuotaInfo& aQuotaInfo, // static void -FileUtils::BodyCancelWrite(nsIFile* aBaseDir, nsISupports* aCopyContext) +BodyCancelWrite(nsIFile* aBaseDir, nsISupports* aCopyContext) { MOZ_ASSERT(aBaseDir); MOZ_ASSERT(aCopyContext); @@ -182,7 +194,7 @@ FileUtils::BodyCancelWrite(nsIFile* aBaseDir, nsISupports* aCopyContext) // static nsresult -FileUtils::BodyFinalizeWrite(nsIFile* aBaseDir, const nsID& aId) +BodyFinalizeWrite(nsIFile* aBaseDir, const nsID& aId) { MOZ_ASSERT(aBaseDir); @@ -206,8 +218,8 @@ FileUtils::BodyFinalizeWrite(nsIFile* aBaseDir, const nsID& aId) // static nsresult -FileUtils::BodyOpen(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, - const nsID& aId, nsIInputStream** aStreamOut) +BodyOpen(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, const nsID& aId, + nsIInputStream** aStreamOut) { MOZ_ASSERT(aBaseDir); MOZ_ASSERT(aStreamOut); @@ -234,7 +246,7 @@ FileUtils::BodyOpen(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, // static nsresult -FileUtils::BodyDeleteFiles(nsIFile* aBaseDir, const nsTArray& aIdList) +BodyDeleteFiles(nsIFile* aBaseDir, const nsTArray& aIdList) { nsresult rv = NS_OK; @@ -273,10 +285,11 @@ FileUtils::BodyDeleteFiles(nsIFile* aBaseDir, const nsTArray& aIdList) return NS_OK; } -// static +namespace { + nsresult -FileUtils::BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, - BodyFileType aType, nsIFile** aBodyFileOut) +BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, BodyFileType aType, + nsIFile** aBodyFileOut) { MOZ_ASSERT(aBaseDir); MOZ_ASSERT(aBodyFileOut); @@ -304,6 +317,8 @@ FileUtils::BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, return rv; } +} // anonymous namespace + } // namespace cache } // namespace dom } // namespace mozilla diff --git a/dom/cache/FileUtils.h b/dom/cache/FileUtils.h index 33c0391600..f6d7ab6c3d 100644 --- a/dom/cache/FileUtils.h +++ b/dom/cache/FileUtils.h @@ -19,51 +19,36 @@ namespace mozilla { namespace dom { namespace cache { -// TODO: remove static class and use functions in cache namespace (bug 1110485) -class FileUtils final -{ -public: - enum BodyFileType - { - BODY_FILE_FINAL, - BODY_FILE_TMP - }; +nsresult +BodyCreateDir(nsIFile* aBaseDir); - static nsresult BodyCreateDir(nsIFile* aBaseDir); - // Note that this function can only be used during the initialization of the - // database. We're unlikely to be able to delete the DB successfully past - // that point due to the file being in use. - static nsresult BodyDeleteDir(nsIFile* aBaseDir); - static nsresult BodyGetCacheDir(nsIFile* aBaseDir, const nsID& aId, - nsIFile** aCacheDirOut); +// Note that this function can only be used during the initialization of the +// database. We're unlikely to be able to delete the DB successfully past +// that point due to the file being in use. +nsresult +BodyDeleteDir(nsIFile* aBaseDir); - static nsresult - BodyStartWriteStream(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, - nsIInputStream* aSource, void* aClosure, - nsAsyncCopyCallbackFun aCallback, nsID* aIdOut, - nsISupports** aCopyContextOut); +nsresult +BodyGetCacheDir(nsIFile* aBaseDir, const nsID& aId, nsIFile** aCacheDirOut); - static void - BodyCancelWrite(nsIFile* aBaseDir, nsISupports* aCopyContext); +nsresult +BodyStartWriteStream(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, + nsIInputStream* aSource, void* aClosure, + nsAsyncCopyCallbackFun aCallback, nsID* aIdOut, + nsISupports** aCopyContextOut); - static nsresult - BodyFinalizeWrite(nsIFile* aBaseDir, const nsID& aId); +void +BodyCancelWrite(nsIFile* aBaseDir, nsISupports* aCopyContext); - static nsresult - BodyOpen(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, const nsID& aId, - nsIInputStream** aStreamOut); +nsresult +BodyFinalizeWrite(nsIFile* aBaseDir, const nsID& aId); - static nsresult - BodyDeleteFiles(nsIFile* aBaseDir, const nsTArray& aIdList); +nsresult +BodyOpen(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, const nsID& aId, + nsIInputStream** aStreamOut); -private: - static nsresult - BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, BodyFileType aType, - nsIFile** aBodyFileOut); - - FileUtils() = delete; - ~FileUtils() = delete; -}; +nsresult +BodyDeleteFiles(nsIFile* aBaseDir, const nsTArray& aIdList); } // namespace cache } // namespace dom diff --git a/dom/cache/IPCUtils.h b/dom/cache/IPCUtils.h index 48498e0f43..fb843748bf 100644 --- a/dom/cache/IPCUtils.h +++ b/dom/cache/IPCUtils.h @@ -8,10 +8,47 @@ #define mozilla_dom_cache_IPCUtils_h #include "ipc/IPCMessageUtils.h" + +// Fix X11 header brain damage that conflicts with HeadersGuardEnum::None +#undef None + +#include "mozilla/dom/HeadersBinding.h" +#include "mozilla/dom/RequestBinding.h" +#include "mozilla/dom/ResponseBinding.h" #include "mozilla/dom/cache/Types.h" namespace IPC { template<> + struct ParamTraits : + public ContiguousEnumSerializer {}; + template<> + struct ParamTraits : + public ContiguousEnumSerializer {}; + template<> + struct ParamTraits : + public ContiguousEnumSerializer {}; + template<> + struct ParamTraits : + public ContiguousEnumSerializer {}; + template<> + struct ParamTraits : + public ContiguousEnumSerializer {}; + template<> + struct ParamTraits : + public ContiguousEnumSerializer {}; + template<> struct ParamTraits : public ContiguousEnumSerializerResolve(rv); @@ -159,6 +160,7 @@ public: if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } ref = new Manager(aManagerId, ioThread); + ref->Init(); MOZ_ASSERT(!sFactory->mManagerList.Contains(ref)); sFactory->mManagerList.AppendElement(ref); @@ -183,7 +185,7 @@ public: // If there is an invalid Manager finishing up and a new Manager // is created for the same origin, then the new Manager will // be blocked until QuotaManager finishes clearing the origin. - if (manager->IsValid() && *manager->mManagerId == *aManagerId) { + if (!manager->IsClosing() && *manager->mManagerId == *aManagerId) { return manager.forget(); } } @@ -412,16 +414,15 @@ StaticRefPtr Manager::Factory::sBackgroundThread; class Manager::BaseAction : public SyncDBAction { protected: - BaseAction(Manager* aManager, ListenerId aListenerId, RequestId aRequestId) + BaseAction(Manager* aManager, ListenerId aListenerId) : SyncDBAction(DBAction::Existing) , mManager(aManager) , mListenerId(aListenerId) - , mRequestId (aRequestId) { } virtual void - Complete(Listener* aListener, nsresult aRv) = 0; + Complete(Listener* aListener, ErrorResult&& aRv) = 0; virtual void CompleteOnInitiatingThread(nsresult aRv) override @@ -429,7 +430,7 @@ protected: NS_ASSERT_OWNINGTHREAD(Manager::BaseAction); Listener* listener = mManager->GetListener(mListenerId); if (listener) { - Complete(listener, aRv); + Complete(listener, ErrorResult(aRv)); } // ensure we release the manager on the initiating thread @@ -438,7 +439,6 @@ protected: nsRefPtr mManager; const ListenerId mListenerId; - const RequestId mRequestId; }; // ---------------------------------------------------------------------------- @@ -461,7 +461,7 @@ public: mozStorageTransaction trans(aConn, false, mozIStorageConnection::TRANSACTION_IMMEDIATE); - nsresult rv = DBSchema::DeleteCache(aConn, mCacheId, mDeletedBodyIdList); + nsresult rv = db::DeleteCacheId(aConn, mCacheId, mDeletedBodyIdList); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = trans.Commit(); @@ -491,14 +491,11 @@ class Manager::CacheMatchAction final : public Manager::BaseAction { public: CacheMatchAction(Manager* aManager, ListenerId aListenerId, - RequestId aRequestId, CacheId aCacheId, - const PCacheRequest& aRequest, - const PCacheQueryParams& aParams, + CacheId aCacheId, const CacheMatchArgs& aArgs, StreamList* aStreamList) - : BaseAction(aManager, aListenerId, aRequestId) + : BaseAction(aManager, aListenerId) , mCacheId(aCacheId) - , mRequest(aRequest) - , mParams(aParams) + , mArgs(aArgs) , mStreamList(aStreamList) , mFoundResponse(false) { } @@ -507,8 +504,8 @@ public: RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, mozIStorageConnection* aConn) override { - nsresult rv = DBSchema::CacheMatch(aConn, mCacheId, mRequest, mParams, - &mFoundResponse, &mResponse); + nsresult rv = db::CacheMatch(aConn, mCacheId, mArgs.request(), + mArgs.params(), &mFoundResponse, &mResponse); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!mFoundResponse || !mResponse.mHasBodyId) { @@ -516,8 +513,7 @@ public: } nsCOMPtr stream; - rv = FileUtils::BodyOpen(aQuotaInfo, aDBDir, mResponse.mBodyId, - getter_AddRefs(stream)); + rv = BodyOpen(aQuotaInfo, aDBDir, mResponse.mBodyId, getter_AddRefs(stream)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(!stream)) { return NS_ERROR_FILE_NOT_FOUND; } @@ -527,13 +523,14 @@ public: } virtual void - Complete(Listener* aListener, nsresult aRv) override + Complete(Listener* aListener, ErrorResult&& aRv) override { if (!mFoundResponse) { - aListener->OnCacheMatch(mRequestId, aRv, nullptr, nullptr); + aListener->OnOpComplete(Move(aRv), CacheMatchResult(void_t())); } else { mStreamList->Activate(mCacheId); - aListener->OnCacheMatch(mRequestId, aRv, &mResponse, mStreamList); + aListener->OnOpComplete(Move(aRv), CacheMatchResult(void_t()), mResponse, + mStreamList); } mStreamList = nullptr; } @@ -545,8 +542,7 @@ public: private: const CacheId mCacheId; - const PCacheRequest mRequest; - const PCacheQueryParams mParams; + const CacheMatchArgs mArgs; nsRefPtr mStreamList; bool mFoundResponse; SavedResponse mResponse; @@ -558,14 +554,11 @@ class Manager::CacheMatchAllAction final : public Manager::BaseAction { public: CacheMatchAllAction(Manager* aManager, ListenerId aListenerId, - RequestId aRequestId, CacheId aCacheId, - const PCacheRequestOrVoid& aRequestOrVoid, - const PCacheQueryParams& aParams, + CacheId aCacheId, const CacheMatchAllArgs& aArgs, StreamList* aStreamList) - : BaseAction(aManager, aListenerId, aRequestId) + : BaseAction(aManager, aListenerId) , mCacheId(aCacheId) - , mRequestOrVoid(aRequestOrVoid) - , mParams(aParams) + , mArgs(aArgs) , mStreamList(aStreamList) { } @@ -573,8 +566,8 @@ public: RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, mozIStorageConnection* aConn) override { - nsresult rv = DBSchema::CacheMatchAll(aConn, mCacheId, mRequestOrVoid, - mParams, mSavedResponses); + nsresult rv = db::CacheMatchAll(aConn, mCacheId, mArgs.requestOrVoid(), + mArgs.params(), mSavedResponses); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } for (uint32_t i = 0; i < mSavedResponses.Length(); ++i) { @@ -583,9 +576,8 @@ public: } nsCOMPtr stream; - rv = FileUtils::BodyOpen(aQuotaInfo, aDBDir, - mSavedResponses[i].mBodyId, - getter_AddRefs(stream)); + rv = BodyOpen(aQuotaInfo, aDBDir, mSavedResponses[i].mBodyId, + getter_AddRefs(stream)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(!stream)) { return NS_ERROR_FILE_NOT_FOUND; } @@ -596,10 +588,11 @@ public: } virtual void - Complete(Listener* aListener, nsresult aRv) override + Complete(Listener* aListener, ErrorResult&& aRv) override { mStreamList->Activate(mCacheId); - aListener->OnCacheMatchAll(mRequestId, aRv, mSavedResponses, mStreamList); + aListener->OnOpComplete(Move(aRv), CacheMatchAllResult(), mSavedResponses, + mStreamList); mStreamList = nullptr; } @@ -610,8 +603,7 @@ public: private: const CacheId mCacheId; - const PCacheRequestOrVoid mRequestOrVoid; - const PCacheQueryParams mParams; + const CacheMatchAllArgs mArgs; nsRefPtr mStreamList; nsTArray mSavedResponses; }; @@ -625,14 +617,13 @@ class Manager::CachePutAllAction final : public DBAction { public: CachePutAllAction(Manager* aManager, ListenerId aListenerId, - RequestId aRequestId, CacheId aCacheId, + CacheId aCacheId, const nsTArray& aPutList, const nsTArray>& aRequestStreamList, const nsTArray>& aResponseStreamList) : DBAction(DBAction::Existing) , mManager(aManager) , mListenerId(aListenerId) - , mRequestId(aRequestId) , mCacheId(aCacheId) , mList(aPutList.Length()) , mExpectedAsyncCopyCompletions(1) @@ -771,25 +762,25 @@ private: for (uint32_t i = 0; i < mList.Length(); ++i) { Entry& e = mList[i]; if (e.mRequestStream) { - rv = FileUtils::BodyFinalizeWrite(mDBDir, e.mRequestBodyId); + rv = BodyFinalizeWrite(mDBDir, e.mRequestBodyId); if (NS_WARN_IF(NS_FAILED(rv))) { DoResolve(rv); return; } } if (e.mResponseStream) { - rv = FileUtils::BodyFinalizeWrite(mDBDir, e.mResponseBodyId); + rv = BodyFinalizeWrite(mDBDir, e.mResponseBodyId); if (NS_WARN_IF(NS_FAILED(rv))) { DoResolve(rv); return; } } - rv = DBSchema::CachePut(mConn, mCacheId, e.mRequest, - e.mRequestStream ? &e.mRequestBodyId : nullptr, - e.mResponse, - e.mResponseStream ? &e.mResponseBodyId : nullptr, - mDeletedBodyIdList); + rv = db::CachePut(mConn, mCacheId, e.mRequest, + e.mRequestStream ? &e.mRequestBodyId : nullptr, + e.mResponse, + e.mResponseStream ? &e.mResponseBodyId : nullptr, + mDeletedBodyIdList); if (NS_WARN_IF(NS_FAILED(rv))) { DoResolve(rv); return; @@ -817,7 +808,7 @@ private: Listener* listener = mManager->GetListener(mListenerId); mManager = nullptr; if (listener) { - listener->OnCachePutAll(mRequestId, aRv); + listener->OnOpComplete(ErrorResult(aRv), CachePutAllResult()); } } @@ -837,12 +828,12 @@ private: struct Entry { - PCacheRequest mRequest; + CacheRequest mRequest; nsCOMPtr mRequestStream; nsID mRequestBodyId; nsCOMPtr mRequestCopyContext; - PCacheResponse mResponse; + CacheResponse mResponse; nsCOMPtr mResponseStream; nsID mResponseBodyId; nsCOMPtr mResponseCopyContext; @@ -883,10 +874,9 @@ private: nsCOMPtr copyContext; - nsresult rv = FileUtils::BodyStartWriteStream(aQuotaInfo, mDBDir, source, - this, AsyncCopyCompleteFunc, - bodyId, - getter_AddRefs(copyContext)); + nsresult rv = BodyStartWriteStream(aQuotaInfo, mDBDir, source, this, + AsyncCopyCompleteFunc, bodyId, + getter_AddRefs(copyContext)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mBodyIdWrittenList.AppendElement(*bodyId); @@ -907,7 +897,7 @@ private: // May occur on either owning thread or target thread MutexAutoLock lock(mMutex); for (uint32_t i = 0; i < mCopyContextList.Length(); ++i) { - FileUtils::BodyCancelWrite(mDBDir, mCopyContextList[i]); + BodyCancelWrite(mDBDir, mCopyContextList[i]); } mCopyContextList.Clear(); } @@ -946,7 +936,7 @@ private: // Clean up any files we might have written before hitting the error. if (NS_FAILED(aRv)) { - FileUtils::BodyDeleteFiles(mDBDir, mBodyIdWrittenList); + BodyDeleteFiles(mDBDir, mBodyIdWrittenList); } // Must be released on the target thread where it was opened. @@ -966,7 +956,6 @@ private: // initiating thread only nsRefPtr mManager; const ListenerId mListenerId; - const RequestId mRequestId; // Set on initiating thread, read on target thread. State machine guarantees // these are not modified while being read by the target thread. @@ -997,13 +986,10 @@ class Manager::CacheDeleteAction final : public Manager::BaseAction { public: CacheDeleteAction(Manager* aManager, ListenerId aListenerId, - RequestId aRequestId, CacheId aCacheId, - const PCacheRequest& aRequest, - const PCacheQueryParams& aParams) - : BaseAction(aManager, aListenerId, aRequestId) + CacheId aCacheId, const CacheDeleteArgs& aArgs) + : BaseAction(aManager, aListenerId) , mCacheId(aCacheId) - , mRequest(aRequest) - , mParams(aParams) + , mArgs(aArgs) , mSuccess(false) { } @@ -1014,8 +1000,9 @@ public: mozStorageTransaction trans(aConn, false, mozIStorageConnection::TRANSACTION_IMMEDIATE); - nsresult rv = DBSchema::CacheDelete(aConn, mCacheId, mRequest, mParams, - mDeletedBodyIdList, &mSuccess); + nsresult rv = db::CacheDelete(aConn, mCacheId, mArgs.request(), + mArgs.params(), mDeletedBodyIdList, + &mSuccess); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = trans.Commit(); @@ -1028,10 +1015,10 @@ public: } virtual void - Complete(Listener* aListener, nsresult aRv) override + Complete(Listener* aListener, ErrorResult&& aRv) override { mManager->NoteOrphanedBodyIdList(mDeletedBodyIdList); - aListener->OnCacheDelete(mRequestId, aRv, mSuccess); + aListener->OnOpComplete(Move(aRv), CacheDeleteResult(mSuccess)); } virtual bool MatchesCacheId(CacheId aCacheId) const override @@ -1041,8 +1028,7 @@ public: private: const CacheId mCacheId; - const PCacheRequest mRequest; - const PCacheQueryParams mParams; + const CacheDeleteArgs mArgs; bool mSuccess; nsTArray mDeletedBodyIdList; }; @@ -1053,14 +1039,11 @@ class Manager::CacheKeysAction final : public Manager::BaseAction { public: CacheKeysAction(Manager* aManager, ListenerId aListenerId, - RequestId aRequestId, CacheId aCacheId, - const PCacheRequestOrVoid& aRequestOrVoid, - const PCacheQueryParams& aParams, + CacheId aCacheId, const CacheKeysArgs& aArgs, StreamList* aStreamList) - : BaseAction(aManager, aListenerId, aRequestId) + : BaseAction(aManager, aListenerId) , mCacheId(aCacheId) - , mRequestOrVoid(aRequestOrVoid) - , mParams(aParams) + , mArgs(aArgs) , mStreamList(aStreamList) { } @@ -1068,8 +1051,8 @@ public: RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, mozIStorageConnection* aConn) override { - nsresult rv = DBSchema::CacheKeys(aConn, mCacheId, mRequestOrVoid, mParams, - mSavedRequests); + nsresult rv = db::CacheKeys(aConn, mCacheId, mArgs.requestOrVoid(), + mArgs.params(), mSavedRequests); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } for (uint32_t i = 0; i < mSavedRequests.Length(); ++i) { @@ -1078,9 +1061,8 @@ public: } nsCOMPtr stream; - rv = FileUtils::BodyOpen(aQuotaInfo, aDBDir, - mSavedRequests[i].mBodyId, - getter_AddRefs(stream)); + rv = BodyOpen(aQuotaInfo, aDBDir, mSavedRequests[i].mBodyId, + getter_AddRefs(stream)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(!stream)) { return NS_ERROR_FILE_NOT_FOUND; } @@ -1091,10 +1073,11 @@ public: } virtual void - Complete(Listener* aListener, nsresult aRv) override + Complete(Listener* aListener, ErrorResult&& aRv) override { mStreamList->Activate(mCacheId); - aListener->OnCacheKeys(mRequestId, aRv, mSavedRequests, mStreamList); + aListener->OnOpComplete(Move(aRv), CacheKeysResult(), mSavedRequests, + mStreamList); mStreamList = nullptr; } @@ -1105,8 +1088,7 @@ public: private: const CacheId mCacheId; - const PCacheRequestOrVoid mRequestOrVoid; - const PCacheQueryParams mParams; + const CacheKeysArgs mArgs; nsRefPtr mStreamList; nsTArray mSavedRequests; }; @@ -1117,14 +1099,12 @@ class Manager::StorageMatchAction final : public Manager::BaseAction { public: StorageMatchAction(Manager* aManager, ListenerId aListenerId, - RequestId aRequestId, Namespace aNamespace, - const PCacheRequest& aRequest, - const PCacheQueryParams& aParams, + Namespace aNamespace, + const StorageMatchArgs& aArgs, StreamList* aStreamList) - : BaseAction(aManager, aListenerId, aRequestId) + : BaseAction(aManager, aListenerId) , mNamespace(aNamespace) - , mRequest(aRequest) - , mParams(aParams) + , mArgs(aArgs) , mStreamList(aStreamList) , mFoundResponse(false) { } @@ -1133,8 +1113,9 @@ public: RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, mozIStorageConnection* aConn) override { - nsresult rv = DBSchema::StorageMatch(aConn, mNamespace, mRequest, mParams, - &mFoundResponse, &mSavedResponse); + nsresult rv = db::StorageMatch(aConn, mNamespace, mArgs.request(), + mArgs.params(), &mFoundResponse, + &mSavedResponse); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!mFoundResponse || !mSavedResponse.mHasBodyId) { @@ -1142,8 +1123,8 @@ public: } nsCOMPtr stream; - rv = FileUtils::BodyOpen(aQuotaInfo, aDBDir, mSavedResponse.mBodyId, - getter_AddRefs(stream)); + rv = BodyOpen(aQuotaInfo, aDBDir, mSavedResponse.mBodyId, + getter_AddRefs(stream)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(!stream)) { return NS_ERROR_FILE_NOT_FOUND; } @@ -1153,21 +1134,21 @@ public: } virtual void - Complete(Listener* aListener, nsresult aRv) override + Complete(Listener* aListener, ErrorResult&& aRv) override { if (!mFoundResponse) { - aListener->OnStorageMatch(mRequestId, aRv, nullptr, nullptr); + aListener->OnOpComplete(Move(aRv), StorageMatchResult(void_t())); } else { mStreamList->Activate(mSavedResponse.mCacheId); - aListener->OnStorageMatch(mRequestId, aRv, &mSavedResponse, mStreamList); + aListener->OnOpComplete(Move(aRv), StorageMatchResult(void_t()), mSavedResponse, + mStreamList); } mStreamList = nullptr; } private: const Namespace mNamespace; - const PCacheRequest mRequest; - const PCacheQueryParams mParams; + const StorageMatchArgs mArgs; nsRefPtr mStreamList; bool mFoundResponse; SavedResponse mSavedResponse; @@ -1179,11 +1160,10 @@ class Manager::StorageHasAction final : public Manager::BaseAction { public: StorageHasAction(Manager* aManager, ListenerId aListenerId, - RequestId aRequestId, Namespace aNamespace, - const nsAString& aKey) - : BaseAction(aManager, aListenerId, aRequestId) + Namespace aNamespace, const StorageHasArgs& aArgs) + : BaseAction(aManager, aListenerId) , mNamespace(aNamespace) - , mKey(aKey) + , mArgs(aArgs) , mCacheFound(false) { } @@ -1192,19 +1172,19 @@ public: mozIStorageConnection* aConn) override { CacheId cacheId; - return DBSchema::StorageGetCacheId(aConn, mNamespace, mKey, - &mCacheFound, &cacheId); + return db::StorageGetCacheId(aConn, mNamespace, mArgs.key(), + &mCacheFound, &cacheId); } virtual void - Complete(Listener* aListener, nsresult aRv) override + Complete(Listener* aListener, ErrorResult&& aRv) override { - aListener->OnStorageHas(mRequestId, aRv, mCacheFound); + aListener->OnOpComplete(Move(aRv), StorageHasResult(mCacheFound)); } private: const Namespace mNamespace; - const nsString mKey; + const StorageHasArgs mArgs; bool mCacheFound; }; @@ -1214,11 +1194,10 @@ class Manager::StorageOpenAction final : public Manager::BaseAction { public: StorageOpenAction(Manager* aManager, ListenerId aListenerId, - RequestId aRequestId, Namespace aNamespace, - const nsAString& aKey) - : BaseAction(aManager, aListenerId, aRequestId) + Namespace aNamespace, const StorageOpenArgs& aArgs) + : BaseAction(aManager, aListenerId) , mNamespace(aNamespace) - , mKey(aKey) + , mArgs(aArgs) , mCacheId(INVALID_CACHE_ID) { } @@ -1232,17 +1211,17 @@ public: // Look for existing cache bool cacheFound; - nsresult rv = DBSchema::StorageGetCacheId(aConn, mNamespace, mKey, - &cacheFound, &mCacheId); + nsresult rv = db::StorageGetCacheId(aConn, mNamespace, mArgs.key(), + &cacheFound, &mCacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (cacheFound) { return rv; } - rv = DBSchema::CreateCache(aConn, &mCacheId); + rv = db::CreateCacheId(aConn, &mCacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = DBSchema::StoragePutCache(aConn, mNamespace, mKey, mCacheId); + rv = db::StoragePutCache(aConn, mNamespace, mArgs.key(), mCacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = trans.Commit(); @@ -1252,14 +1231,14 @@ public: } virtual void - Complete(Listener* aListener, nsresult aRv) override + Complete(Listener* aListener, ErrorResult&& aRv) override { - aListener->OnStorageOpen(mRequestId, aRv, mCacheId); + aListener->OnOpComplete(Move(aRv), StorageOpenResult(), mCacheId); } private: const Namespace mNamespace; - const nsString mKey; + const StorageOpenArgs mArgs; CacheId mCacheId; }; @@ -1269,11 +1248,10 @@ class Manager::StorageDeleteAction final : public Manager::BaseAction { public: StorageDeleteAction(Manager* aManager, ListenerId aListenerId, - RequestId aRequestId, Namespace aNamespace, - const nsAString& aKey) - : BaseAction(aManager, aListenerId, aRequestId) + Namespace aNamespace, const StorageDeleteArgs& aArgs) + : BaseAction(aManager, aListenerId) , mNamespace(aNamespace) - , mKey(aKey) + , mArgs(aArgs) , mCacheDeleted(false) , mCacheId(INVALID_CACHE_ID) { } @@ -1286,8 +1264,8 @@ public: mozIStorageConnection::TRANSACTION_IMMEDIATE); bool exists; - nsresult rv = DBSchema::StorageGetCacheId(aConn, mNamespace, mKey, &exists, - &mCacheId); + nsresult rv = db::StorageGetCacheId(aConn, mNamespace, mArgs.key(), + &exists, &mCacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!exists) { @@ -1295,7 +1273,7 @@ public: return NS_OK; } - rv = DBSchema::StorageForgetCache(aConn, mNamespace, mKey); + rv = db::StorageForgetCache(aConn, mNamespace, mArgs.key()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = trans.Commit(); @@ -1306,7 +1284,7 @@ public: } virtual void - Complete(Listener* aListener, nsresult aRv) override + Complete(Listener* aListener, ErrorResult&& aRv) override { if (mCacheDeleted) { // If content is referencing this cache, mark it orphaned to be @@ -1314,20 +1292,24 @@ public: if (!mManager->SetCacheIdOrphanedIfRefed(mCacheId)) { // no outstanding references, delete immediately - nsRefPtr context = mManager->CurrentContext(); - context->CancelForCacheId(mCacheId); - nsRefPtr action = - new DeleteOrphanedCacheAction(mManager, mCacheId); - context->Dispatch(mManager->mIOThread, action); + nsRefPtr context = mManager->mContext; + + // TODO: note that we need to check this cache for staleness on startup (bug 1110446) + if (!context->IsCanceled()) { + context->CancelForCacheId(mCacheId); + nsRefPtr action = + new DeleteOrphanedCacheAction(mManager, mCacheId); + context->Dispatch(mManager->mIOThread, action); + } } } - aListener->OnStorageDelete(mRequestId, aRv, mCacheDeleted); + aListener->OnOpComplete(Move(aRv), StorageDeleteResult(mCacheDeleted)); } private: const Namespace mNamespace; - const nsString mKey; + const StorageDeleteArgs mArgs; bool mCacheDeleted; CacheId mCacheId; }; @@ -1338,8 +1320,8 @@ class Manager::StorageKeysAction final : public Manager::BaseAction { public: StorageKeysAction(Manager* aManager, ListenerId aListenerId, - RequestId aRequestId, Namespace aNamespace) - : BaseAction(aManager, aListenerId, aRequestId) + Namespace aNamespace) + : BaseAction(aManager, aListenerId) , mNamespace(aNamespace) { } @@ -1347,16 +1329,16 @@ public: RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, mozIStorageConnection* aConn) override { - return DBSchema::StorageGetKeys(aConn, mNamespace, mKeys); + return db::StorageGetKeys(aConn, mNamespace, mKeys); } virtual void - Complete(Listener* aListener, nsresult aRv) override + Complete(Listener* aListener, ErrorResult&& aRv) override { - if (NS_FAILED(aRv)) { + if (aRv.Failed()) { mKeys.Clear(); } - aListener->OnStorageKeys(mRequestId, aRv, mKeys); + aListener->OnOpComplete(Move(aRv), StorageKeysResult(mKeys)); } private: @@ -1369,6 +1351,50 @@ private: //static Manager::ListenerId Manager::sNextListenerId = 0; +void +Manager::Listener::OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult) +{ + OnOpComplete(Move(aRv), aResult, INVALID_CACHE_ID, nsTArray(), + nsTArray(), nullptr); +} + +void +Manager::Listener::OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult, + CacheId aOpenedCacheId) +{ + OnOpComplete(Move(aRv), aResult, aOpenedCacheId, nsTArray(), + nsTArray(), nullptr); +} + +void +Manager::Listener::OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult, + const SavedResponse& aSavedResponse, + StreamList* aStreamList) +{ + nsAutoTArray responseList; + responseList.AppendElement(aSavedResponse); + OnOpComplete(Move(aRv), aResult, INVALID_CACHE_ID, responseList, + nsTArray(), aStreamList); +} + +void +Manager::Listener::OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult, + const nsTArray& aSavedResponseList, + StreamList* aStreamList) +{ + OnOpComplete(Move(aRv), aResult, INVALID_CACHE_ID, aSavedResponseList, + nsTArray(), aStreamList); +} + +void +Manager::Listener::OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult, + const nsTArray& aSavedRequestList, + StreamList* aStreamList) +{ + OnOpComplete(Move(aRv), aResult, INVALID_CACHE_ID, nsTArray(), + aSavedRequestList, aStreamList); +} + // static nsresult Manager::GetOrCreate(ManagerId* aManagerId, Manager** aManagerOut) @@ -1410,9 +1436,7 @@ Manager::RemoveListener(Listener* aListener) mListeners.RemoveElement(aListener, ListenerEntryListenerComparator()); MOZ_ASSERT(!mListeners.Contains(aListener, ListenerEntryListenerComparator())); - if (mListeners.IsEmpty() && mContext) { - mContext->AllowToClose(); - } + MaybeAllowContextToClose(); } void @@ -1421,28 +1445,33 @@ Manager::RemoveContext(Context* aContext) NS_ASSERT_OWNINGTHREAD(Manager); MOZ_ASSERT(mContext); MOZ_ASSERT(mContext == aContext); + + // Whether the Context destruction was triggered from the Manager going + // idle or the underlying storage being invalidated, we should know we + // are closing before the Conext is destroyed. + MOZ_ASSERT(mClosing); + mContext = nullptr; - // If we're trying to shutdown, then note that we're done. This is the - // delayed case from Manager::Shutdown(). - if (mShuttingDown) { - Factory::Remove(this); - } + // Once the context is gone, we can immediately remove ourself from the + // Factory list. We don't need to block shutdown by staying in the list + // any more. + Factory::Remove(this); } void -Manager::Invalidate() +Manager::NoteClosing() { NS_ASSERT_OWNINGTHREAD(Manager); - // QuotaManager can trigger this more than once. - mValid = false; + // This can be called more than once legitimately through different paths. + mClosing = true; } bool -Manager::IsValid() const +Manager::IsClosing() const { NS_ASSERT_OWNINGTHREAD(Manager); - return mValid; + return mClosing; } void @@ -1474,14 +1503,15 @@ Manager::ReleaseCacheId(CacheId aCacheId) bool orphaned = mCacheIdRefs[i].mOrphaned; mCacheIdRefs.RemoveElementAt(i); // TODO: note that we need to check this cache for staleness on startup (bug 1110446) - if (orphaned && !mShuttingDown && mValid) { - nsRefPtr context = CurrentContext(); + nsRefPtr context = mContext; + if (orphaned && context && !context->IsCanceled()) { context->CancelForCacheId(aCacheId); nsRefPtr action = new DeleteOrphanedCacheAction(this, aCacheId); context->Dispatch(mIOThread, action); } } + MaybeAllowContextToClose(); return; } } @@ -1517,12 +1547,13 @@ Manager::ReleaseBodyId(const nsID& aBodyId) bool orphaned = mBodyIdRefs[i].mOrphaned; mBodyIdRefs.RemoveElementAt(i); // TODO: note that we need to check this body for staleness on startup (bug 1110446) - if (orphaned && !mShuttingDown && mValid) { + nsRefPtr context = mContext; + if (orphaned && context && !context->IsCanceled()) { nsRefPtr action = new DeleteOrphanedBodyAction(aBodyId); - nsRefPtr context = CurrentContext(); context->Dispatch(mIOThread, action); } } + MaybeAllowContextToClose(); return; } } @@ -1553,195 +1584,121 @@ Manager::RemoveStreamList(StreamList* aStreamList) } void -Manager::CacheMatch(Listener* aListener, RequestId aRequestId, CacheId aCacheId, - const PCacheRequest& aRequest, - const PCacheQueryParams& aParams) +Manager::ExecuteCacheOp(Listener* aListener, CacheId aCacheId, + const CacheOpArgs& aOpArgs) { NS_ASSERT_OWNINGTHREAD(Manager); MOZ_ASSERT(aListener); - if (mShuttingDown || !mValid) { - aListener->OnCacheMatch(aRequestId, NS_ERROR_FAILURE, nullptr, nullptr); + MOZ_ASSERT(aOpArgs.type() != CacheOpArgs::TCacheAddAllArgs); + MOZ_ASSERT(aOpArgs.type() != CacheOpArgs::TCachePutAllArgs); + + if (mClosing) { + aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t()); return; } - nsRefPtr context = CurrentContext(); + + nsRefPtr context = mContext; + MOZ_ASSERT(!context->IsCanceled()); + nsRefPtr streamList = new StreamList(this, context); ListenerId listenerId = SaveListener(aListener); - nsRefPtr action = new CacheMatchAction(this, listenerId, aRequestId, - aCacheId, aRequest, aParams, - streamList); + + nsRefPtr action; + switch(aOpArgs.type()) { + case CacheOpArgs::TCacheMatchArgs: + action = new CacheMatchAction(this, listenerId, aCacheId, + aOpArgs.get_CacheMatchArgs(), streamList); + break; + case CacheOpArgs::TCacheMatchAllArgs: + action = new CacheMatchAllAction(this, listenerId, aCacheId, + aOpArgs.get_CacheMatchAllArgs(), + streamList); + break; + case CacheOpArgs::TCacheDeleteArgs: + action = new CacheDeleteAction(this, listenerId, aCacheId, + aOpArgs.get_CacheDeleteArgs()); + break; + case CacheOpArgs::TCacheKeysArgs: + action = new CacheKeysAction(this, listenerId, aCacheId, + aOpArgs.get_CacheKeysArgs(), streamList); + break; + default: + MOZ_CRASH("Unknown Cache operation!"); + } + context->Dispatch(mIOThread, action); } void -Manager::CacheMatchAll(Listener* aListener, RequestId aRequestId, - CacheId aCacheId, const PCacheRequestOrVoid& aRequest, - const PCacheQueryParams& aParams) +Manager::ExecuteStorageOp(Listener* aListener, Namespace aNamespace, + const CacheOpArgs& aOpArgs) { NS_ASSERT_OWNINGTHREAD(Manager); MOZ_ASSERT(aListener); - if (mShuttingDown || !mValid) { - aListener->OnCacheMatchAll(aRequestId, NS_ERROR_FAILURE, - nsTArray(), nullptr); + + if (mClosing) { + aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t()); return; } - nsRefPtr context = CurrentContext(); + + nsRefPtr context = mContext; + MOZ_ASSERT(!context->IsCanceled()); + nsRefPtr streamList = new StreamList(this, context); ListenerId listenerId = SaveListener(aListener); - nsRefPtr action = new CacheMatchAllAction(this, listenerId, aRequestId, - aCacheId, aRequest, aParams, - streamList); + + nsRefPtr action; + switch(aOpArgs.type()) { + case CacheOpArgs::TStorageMatchArgs: + action = new StorageMatchAction(this, listenerId, aNamespace, + aOpArgs.get_StorageMatchArgs(), + streamList); + break; + case CacheOpArgs::TStorageHasArgs: + action = new StorageHasAction(this, listenerId, aNamespace, + aOpArgs.get_StorageHasArgs()); + break; + case CacheOpArgs::TStorageOpenArgs: + action = new StorageOpenAction(this, listenerId, aNamespace, + aOpArgs.get_StorageOpenArgs()); + break; + case CacheOpArgs::TStorageDeleteArgs: + action = new StorageDeleteAction(this, listenerId, aNamespace, + aOpArgs.get_StorageDeleteArgs()); + break; + case CacheOpArgs::TStorageKeysArgs: + action = new StorageKeysAction(this, listenerId, aNamespace); + break; + default: + MOZ_CRASH("Unknown CacheStorage operation!"); + } + context->Dispatch(mIOThread, action); } void -Manager::CachePutAll(Listener* aListener, RequestId aRequestId, CacheId aCacheId, - const nsTArray& aPutList, - const nsTArray>& aRequestStreamList, - const nsTArray>& aResponseStreamList) +Manager::ExecutePutAll(Listener* aListener, CacheId aCacheId, + const nsTArray& aPutList, + const nsTArray>& aRequestStreamList, + const nsTArray>& aResponseStreamList) { NS_ASSERT_OWNINGTHREAD(Manager); MOZ_ASSERT(aListener); - if (mShuttingDown || !mValid) { - aListener->OnCachePutAll(aRequestId, NS_ERROR_FAILURE); + + if (mClosing) { + aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), CachePutAllResult()); return; } + + nsRefPtr context = mContext; + MOZ_ASSERT(!context->IsCanceled()); + ListenerId listenerId = SaveListener(aListener); - nsRefPtr action = new CachePutAllAction(this, listenerId, aRequestId, - aCacheId, aPutList, - aRequestStreamList, + + nsRefPtr action = new CachePutAllAction(this, listenerId, aCacheId, + aPutList, aRequestStreamList, aResponseStreamList); - nsRefPtr context = CurrentContext(); - context->Dispatch(mIOThread, action); -} -void -Manager::CacheDelete(Listener* aListener, RequestId aRequestId, - CacheId aCacheId, const PCacheRequest& aRequest, - const PCacheQueryParams& aParams) -{ - NS_ASSERT_OWNINGTHREAD(Manager); - MOZ_ASSERT(aListener); - if (mShuttingDown || !mValid) { - aListener->OnCacheDelete(aRequestId, NS_ERROR_FAILURE, false); - return; - } - ListenerId listenerId = SaveListener(aListener); - nsRefPtr action = new CacheDeleteAction(this, listenerId, aRequestId, - aCacheId, aRequest, aParams); - nsRefPtr context = CurrentContext(); - context->Dispatch(mIOThread, action); -} - -void -Manager::CacheKeys(Listener* aListener, RequestId aRequestId, - CacheId aCacheId, const PCacheRequestOrVoid& aRequestOrVoid, - const PCacheQueryParams& aParams) -{ - NS_ASSERT_OWNINGTHREAD(Manager); - MOZ_ASSERT(aListener); - if (mShuttingDown || !mValid) { - aListener->OnCacheKeys(aRequestId, NS_ERROR_FAILURE, - nsTArray(), nullptr); - return; - } - nsRefPtr context = CurrentContext(); - nsRefPtr streamList = new StreamList(this, context); - ListenerId listenerId = SaveListener(aListener); - nsRefPtr action = new CacheKeysAction(this, listenerId, aRequestId, - aCacheId, aRequestOrVoid, - aParams, streamList); - context->Dispatch(mIOThread, action); -} - -void -Manager::StorageMatch(Listener* aListener, RequestId aRequestId, - Namespace aNamespace, const PCacheRequest& aRequest, - const PCacheQueryParams& aParams) -{ - NS_ASSERT_OWNINGTHREAD(Manager); - MOZ_ASSERT(aListener); - if (mShuttingDown || !mValid) { - aListener->OnStorageMatch(aRequestId, NS_ERROR_FAILURE, - nullptr, nullptr); - return; - } - nsRefPtr context = CurrentContext(); - nsRefPtr streamList = new StreamList(this, context); - ListenerId listenerId = SaveListener(aListener); - nsRefPtr action = new StorageMatchAction(this, listenerId, aRequestId, - aNamespace, aRequest, - aParams, streamList); - context->Dispatch(mIOThread, action); -} - -void -Manager::StorageHas(Listener* aListener, RequestId aRequestId, - Namespace aNamespace, const nsAString& aKey) -{ - NS_ASSERT_OWNINGTHREAD(Manager); - MOZ_ASSERT(aListener); - if (mShuttingDown || !mValid) { - aListener->OnStorageHas(aRequestId, NS_ERROR_FAILURE, - false); - return; - } - ListenerId listenerId = SaveListener(aListener); - nsRefPtr action = new StorageHasAction(this, listenerId, aRequestId, - aNamespace, aKey); - nsRefPtr context = CurrentContext(); - context->Dispatch(mIOThread, action); -} - -void -Manager::StorageOpen(Listener* aListener, RequestId aRequestId, - Namespace aNamespace, const nsAString& aKey) -{ - NS_ASSERT_OWNINGTHREAD(Manager); - MOZ_ASSERT(aListener); - if (mShuttingDown || !mValid) { - aListener->OnStorageOpen(aRequestId, NS_ERROR_FAILURE, 0); - return; - } - ListenerId listenerId = SaveListener(aListener); - nsRefPtr action = new StorageOpenAction(this, listenerId, aRequestId, - aNamespace, aKey); - nsRefPtr context = CurrentContext(); - context->Dispatch(mIOThread, action); -} - -void -Manager::StorageDelete(Listener* aListener, RequestId aRequestId, - Namespace aNamespace, const nsAString& aKey) -{ - NS_ASSERT_OWNINGTHREAD(Manager); - MOZ_ASSERT(aListener); - if (mShuttingDown || !mValid) { - aListener->OnStorageDelete(aRequestId, NS_ERROR_FAILURE, - false); - return; - } - ListenerId listenerId = SaveListener(aListener); - nsRefPtr action = new StorageDeleteAction(this, listenerId, aRequestId, - aNamespace, aKey); - nsRefPtr context = CurrentContext(); - context->Dispatch(mIOThread, action); -} - -void -Manager::StorageKeys(Listener* aListener, RequestId aRequestId, - Namespace aNamespace) -{ - NS_ASSERT_OWNINGTHREAD(Manager); - MOZ_ASSERT(aListener); - if (mShuttingDown || !mValid) { - aListener->OnStorageKeys(aRequestId, NS_ERROR_FAILURE, - nsTArray()); - return; - } - ListenerId listenerId = SaveListener(aListener); - nsRefPtr action = new StorageKeysAction(this, listenerId, aRequestId, - aNamespace); - nsRefPtr context = CurrentContext(); context->Dispatch(mIOThread, action); } @@ -1750,7 +1707,7 @@ Manager::Manager(ManagerId* aManagerId, nsIThread* aIOThread) , mIOThread(aIOThread) , mContext(nullptr) , mShuttingDown(false) - , mValid(true) + , mClosing(false) { MOZ_ASSERT(mManagerId); MOZ_ASSERT(mIOThread); @@ -1759,8 +1716,8 @@ Manager::Manager(ManagerId* aManagerId, nsIThread* aIOThread) Manager::~Manager() { NS_ASSERT_OWNINGTHREAD(Manager); + MOZ_ASSERT(mClosing); MOZ_ASSERT(!mContext); - Shutdown(); nsCOMPtr ioThread; mIOThread.swap(ioThread); @@ -1772,6 +1729,19 @@ Manager::~Manager() MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable))); } +void +Manager::Init() +{ + NS_ASSERT_OWNINGTHREAD(Manager); + + // Create the context immediately. Since there can at most be one Context + // per Manager now, this lets us cleanly call Factory::Remove() once the + // Context goes away. + nsRefPtr setupAction = new SetupAction(); + nsRefPtr ref = Context::Create(this, setupAction); + mContext = ref; +} + void Manager::Shutdown() { @@ -1784,36 +1754,20 @@ Manager::Shutdown() return; } - // Set a flag to prevent any new requests from coming in and creating - // a new Context. We must ensure all Contexts and IO operations are - // complete before shutdown proceeds. mShuttingDown = true; - // If there is a context, then we must wait for it to complete. Cancel and - // only note that we are done after its cleaned up. + // Note that we are closing to prevent any new requests from coming in and + // creating a new Context. We must ensure all Contexts and IO operations are + // complete before shutdown proceeds. + NoteClosing(); + + // If there is a context, then cancel and only note that we are done after + // its cleaned up. if (mContext) { nsRefPtr context = mContext; context->CancelAll(); return; } - - // Otherwise, note that we are complete immediately - Factory::Remove(this); -} - -already_AddRefed -Manager::CurrentContext() -{ - NS_ASSERT_OWNINGTHREAD(Manager); - nsRefPtr ref = mContext; - if (!ref) { - MOZ_ASSERT(!mShuttingDown); - MOZ_ASSERT(mValid); - nsRefPtr setupAction = new SetupAction(); - ref = Context::Create(this, setupAction); - mContext = ref; - } - return ref.forget(); } Manager::ListenerId @@ -1898,13 +1852,38 @@ Manager::NoteOrphanedBodyIdList(const nsTArray& aDeletedBodyIdList) } } - if (!deleteNowList.IsEmpty()) { + // TODO: note that we need to check these bodies for staleness on startup (bug 1110446) + nsRefPtr context = mContext; + if (!deleteNowList.IsEmpty() && context && !context->IsCanceled()) { nsRefPtr action = new DeleteOrphanedBodyAction(deleteNowList); - nsRefPtr context = CurrentContext(); context->Dispatch(mIOThread, action); } } +void +Manager::MaybeAllowContextToClose() +{ + NS_ASSERT_OWNINGTHREAD(Manager); + + // If we have an active context, but we have no more users of the Manager, + // then let it shut itself down. We must wait for all possible users of + // Cache state information to complete before doing this. Once we allow + // the Context to close we may not reliably get notified of storage + // invalidation. + nsRefPtr context = mContext; + if (context && mListeners.IsEmpty() + && mCacheIdRefs.IsEmpty() + && mBodyIdRefs.IsEmpty()) { + + // Mark this Manager as invalid so that it won't get used again. We don't + // want to start any new operations once we allow the Context to close since + // it may race with the underlying storage getting invalidated. + NoteClosing(); + + context->AllowToClose(); + } +} + } // namespace cache } // namespace dom } // namespace mozilla diff --git a/dom/cache/Manager.h b/dom/cache/Manager.h index b59dc1b245..c6c2f8b39b 100644 --- a/dom/cache/Manager.h +++ b/dom/cache/Manager.h @@ -7,11 +7,10 @@ #ifndef mozilla_dom_cache_Manager_h #define mozilla_dom_cache_Manager_h -#include "mozilla/dom/cache/CacheInitData.h" -#include "mozilla/dom/cache/PCacheStreamControlParent.h" #include "mozilla/dom/cache/Types.h" #include "nsCOMPtr.h" #include "nsISupportsImpl.h" +#include "mozilla/nsRefPtr.h" #include "nsString.h" #include "nsTArray.h" @@ -22,12 +21,11 @@ namespace mozilla { namespace dom { namespace cache { +class CacheOpArgs; +class CacheOpResult; class CacheRequestResponse; class Context; class ManagerId; -class PCacheQueryParams; -class PCacheRequest; -class PCacheRequestOrVoid; struct SavedRequest; struct SavedResponse; class StreamList; @@ -41,23 +39,25 @@ class StreamList; // Cache API. This uniqueness is defined by the ManagerId equality operator. // The uniqueness is enforced by the Manager GetOrCreate() factory method. // -// The Manager object can out live the IPC actors in the case where the child -// process is killed; e.g a child process OOM. The Manager object can -// The Manager object can potentially use non-trivial resources. Long lived -// DOM objects and their actors should not maintain a reference to the Manager -// while idle. Transient DOM objects that may keep a reference for their -// lifetimes. +// The life cycle of Manager objects is somewhat complex. While code may +// hold a strong reference to the Manager, it will invalidate itself once it +// believes it has become completely idle. This is currently determined when +// all of the following conditions occur: // -// For example, once a CacheStorage DOM object is access it will live until its -// global is released. Therefore, CacheStorage should release its Manager -// reference after operations complete and it becomes idle. Cache objects, -// however, can be GC'd once content are done using them and can therefore keep -// their Manager reference alive. Its expected that more operations are -// performed on a Cache object, so keeping the Manager reference will help -// minimize overhead for each reference. +// 1) There are no more Manager::Listener objects registered with the Manager +// by performing a Cache or Storage operation. +// 2) There are no more CacheId references noted via Manager::AddRefCacheId(). +// 3) There are no more BodyId references noted via Manager::AddRefBodyId(). +// +// In order to keep your Manager alive you should perform an operation to set +// a Listener, call AddRefCacheId(), or call AddRefBodyId(). +// +// Even once a Manager becomes invalid, however, it may still continue to +// exist. This is allowed so that any in-progress Actions can gracefully +// complete. // // As an invariant, all Manager objects must cease all IO before shutdown. This -// is enforced by the ShutdownObserver. If content still holds references to +// is enforced by the Manager::Factory. If content still holds references to // Cache DOM objects during shutdown, then all operations will begin rejecting. class Manager final { @@ -84,30 +84,36 @@ public: class Listener { public: - virtual void OnCacheMatch(RequestId aRequestId, nsresult aRv, - const SavedResponse* aResponse, - StreamList* aStreamList) { } - virtual void OnCacheMatchAll(RequestId aRequestId, nsresult aRv, - const nsTArray& aSavedResponses, - StreamList* aStreamList) { } - virtual void OnCachePutAll(RequestId aRequestId, nsresult aRv) { } - virtual void OnCacheDelete(RequestId aRequestId, nsresult aRv, - bool aSuccess) { } - virtual void OnCacheKeys(RequestId aRequestId, nsresult aRv, - const nsTArray& aSavedRequests, - StreamList* aStreamList) { } + // convenience routines + void + OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult); - virtual void OnStorageMatch(RequestId aRequestId, nsresult aRv, - const SavedResponse* aResponse, - StreamList* aStreamList) { } - virtual void OnStorageHas(RequestId aRequestId, nsresult aRv, - bool aCacheFound) { } - virtual void OnStorageOpen(RequestId aRequestId, nsresult aRv, - CacheId aCacheId) { } - virtual void OnStorageDelete(RequestId aRequestId, nsresult aRv, - bool aCacheDeleted) { } - virtual void OnStorageKeys(RequestId aRequestId, nsresult aRv, - const nsTArray& aKeys) { } + void + OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult, + CacheId aOpenedCacheId); + + void + OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult, + const SavedResponse& aSavedResponse, + StreamList* aStreamList); + + void + OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult, + const nsTArray& aSavedResponseList, + StreamList* aStreamList); + + void + OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult, + const nsTArray& aSavedRequestList, + StreamList* aStreamList); + + // interface to be implemented + virtual void + OnOpComplete(ErrorResult&& aRv, const CacheOpResult& aResult, + CacheId aOpenedCacheId, + const nsTArray& aSavedResponseList, + const nsTArray& aSavedRequestList, + StreamList* aStreamList) { } protected: ~Listener() { } @@ -127,8 +133,8 @@ public: // Marks the Manager "invalid". Once the Context completes no new operations // will be permitted with this Manager. New actors will get a new Manager. - void Invalidate(); - bool IsValid() const; + void NoteClosing(); + bool IsClosing() const; // If an actor represents a long term reference to a cache or body stream, // then they must call AddRefCacheId() or AddRefBodyId(). This will @@ -148,35 +154,15 @@ public: void AddStreamList(StreamList* aStreamList); void RemoveStreamList(StreamList* aStreamList); - // TODO: consider moving CacheId up in the argument lists below (bug 1110485) - void CacheMatch(Listener* aListener, RequestId aRequestId, CacheId aCacheId, - const PCacheRequest& aRequest, - const PCacheQueryParams& aParams); - void CacheMatchAll(Listener* aListener, RequestId aRequestId, - CacheId aCacheId, const PCacheRequestOrVoid& aRequestOrVoid, - const PCacheQueryParams& aParams); - void CachePutAll(Listener* aListener, RequestId aRequestId, CacheId aCacheId, - const nsTArray& aPutList, - const nsTArray>& aRequestStreamList, - const nsTArray>& aResponseStreamList); - void CacheDelete(Listener* aListener, RequestId aRequestId, - CacheId aCacheId, const PCacheRequest& aRequest, - const PCacheQueryParams& aParams); - void CacheKeys(Listener* aListener, RequestId aRequestId, - CacheId aCacheId, const PCacheRequestOrVoid& aRequestOrVoid, - const PCacheQueryParams& aParams); + void ExecuteCacheOp(Listener* aListener, CacheId aCacheId, + const CacheOpArgs& aOpArgs); + void ExecutePutAll(Listener* aListener, CacheId aCacheId, + const nsTArray& aPutList, + const nsTArray>& aRequestStreamList, + const nsTArray>& aResponseStreamList); - void StorageMatch(Listener* aListener, RequestId aRequestId, - Namespace aNamespace, const PCacheRequest& aRequest, - const PCacheQueryParams& aParams); - void StorageHas(Listener* aListener, RequestId aRequestId, - Namespace aNamespace, const nsAString& aKey); - void StorageOpen(Listener* aListener, RequestId aRequestId, - Namespace aNamespace, const nsAString& aKey); - void StorageDelete(Listener* aListener, RequestId aRequestId, - Namespace aNamespace, const nsAString& aKey); - void StorageKeys(Listener* aListener, RequestId aRequestId, - Namespace aNamespace); + void ExecuteStorageOp(Listener* aListener, Namespace aNamespace, + const CacheOpArgs& aOpArgs); private: class Factory; @@ -199,6 +185,7 @@ private: Manager(ManagerId* aManagerId, nsIThread* aIOThread); ~Manager(); + void Init(); void Shutdown(); already_AddRefed CurrentContext(); @@ -209,6 +196,8 @@ private: bool SetBodyIdOrphanedIfRefed(const nsID& aBodyId); void NoteOrphanedBodyIdList(const nsTArray& aDeletedBodyIdList); + void MaybeAllowContextToClose(); + nsRefPtr mManagerId; nsCOMPtr mIOThread; @@ -260,7 +249,7 @@ private: nsTArray mStreamLists; bool mShuttingDown; - bool mValid; + bool mClosing; struct CacheIdRefCounter { diff --git a/dom/cache/PCache.ipdl b/dom/cache/PCache.ipdl index b10eb9bc4c..fe82c0e625 100644 --- a/dom/cache/PCache.ipdl +++ b/dom/cache/PCache.ipdl @@ -3,16 +3,13 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ include protocol PBackground; +include protocol PBlob; // FIXME: bug 792908 +include protocol PCacheOp; include protocol PCachePushStream; -include PCacheTypes; +include protocol PCacheStreamControl; include protocol PFileDescriptorSet; -include protocol PBlob; // FIXME: bug 792908 -include protocol PCacheStreamControl; - -using mozilla::dom::cache::RequestId from "mozilla/dom/cache/Types.h"; -using mozilla::ErrorResult from "ipc/ErrorIPCUtils.h"; -include "mozilla/dom/cache/IPCUtils.h"; +include CacheTypes; namespace mozilla { namespace dom { @@ -21,27 +18,15 @@ namespace cache { protocol PCache { manager PBackground; + manages PCacheOp; manages PCachePushStream; parent: + PCacheOp(CacheOpArgs aOpArgs); PCachePushStream(); Teardown(); - Match(RequestId requestId, PCacheRequest request, PCacheQueryParams params); - MatchAll(RequestId requestId, PCacheRequestOrVoid request, PCacheQueryParams params); - AddAll(RequestId requestId, PCacheRequest[] requests); - Put(RequestId requestId, CacheRequestResponse aPut); - Delete(RequestId requestId, PCacheRequest request, PCacheQueryParams params); - Keys(RequestId requestId, PCacheRequestOrVoid request, PCacheQueryParams params); child: - MatchResponse(RequestId requestId, nsresult aRv, PCacheResponseOrVoid aResponse); - MatchAllResponse(RequestId requestId, nsresult aRv, PCacheResponse[] responses); - AddAllResponse(RequestId requestId, ErrorResult aRv); - PutResponse(RequestId requestId, nsresult aRv); - DeleteResponse(RequestId requestId, nsresult aRv, bool success); - KeysResponse(RequestId requestId, nsresult aRv, PCacheRequest[] requests); - -both: __delete__(); }; diff --git a/dom/cache/PCacheOp.ipdl b/dom/cache/PCacheOp.ipdl new file mode 100644 index 0000000000..7349d47632 --- /dev/null +++ b/dom/cache/PCacheOp.ipdl @@ -0,0 +1,29 @@ +/* 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 protocol PCache; +include protocol PCachePushStream; +include protocol PCacheStorage; +include protocol PCacheStreamControl; +include protocol PFileDescriptorSet; + +include CacheTypes; + +using mozilla::ErrorResult from "ipc/ErrorIPCUtils.h"; + +namespace mozilla { +namespace dom { +namespace cache { + +protocol PCacheOp +{ + manager PCache or PCacheStorage; + +child: + __delete__(ErrorResult aRv, CacheOpResult aResult); +}; + +} // namespace cache +} // namespace dom +} // namespace mozilla diff --git a/dom/cache/PCacheStorage.ipdl b/dom/cache/PCacheStorage.ipdl index 030b2fbc84..ef06005b41 100644 --- a/dom/cache/PCacheStorage.ipdl +++ b/dom/cache/PCacheStorage.ipdl @@ -3,14 +3,13 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ include protocol PBackground; +include protocol PBlob; // FIXME: bug 792908 include protocol PCache; -include PCacheTypes; +include protocol PCacheOp; +include protocol PCacheStreamControl; include protocol PFileDescriptorSet; -include protocol PBlob; // FIXME: bug 792908 -include protocol PCacheStreamControl; - -using mozilla::dom::cache::RequestId from "mozilla/dom/cache/IPCUtils.h"; +include CacheTypes; namespace mozilla { namespace dom { @@ -19,23 +18,13 @@ namespace cache { protocol PCacheStorage { manager PBackground; + manages PCacheOp; parent: + PCacheOp(CacheOpArgs aOpArgs); Teardown(); - Match(RequestId aRequestId, PCacheRequest aRequest, - PCacheQueryParams aParams); - Has(RequestId aRequestId, nsString aKey); - Open(RequestId aRequestId, nsString aKey); - Delete(RequestId aRequestId, nsString aKey); - Keys(RequestId aRequestId); child: - MatchResponse(RequestId aRequestId, nsresult aRv, - PCacheResponseOrVoid aResponseOrVoid); - HasResponse(RequestId aRequestId, nsresult aRv, bool aSuccess); - OpenResponse(RequestId aRequestId, nsresult aRv, nullable PCache aActor); - DeleteResponse(RequestId aRequestId, nsresult aRv, bool aSuccess); - KeysResponse(RequestId aRequestId, nsresult aRv, nsString[] aKeys); __delete__(); }; diff --git a/dom/cache/PCacheTypes.ipdlh b/dom/cache/PCacheTypes.ipdlh deleted file mode 100644 index b631af0c20..0000000000 --- a/dom/cache/PCacheTypes.ipdlh +++ /dev/null @@ -1,96 +0,0 @@ -/* 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 protocol PCachePushStream; -include protocol PCacheStreamControl; -include PHeaders; -include InputStreamParams; - -using HeadersGuardEnum from "mozilla/dom/FetchIPCUtils.h"; -using RequestCredentials from "mozilla/dom/FetchIPCUtils.h"; -using RequestMode from "mozilla/dom/FetchIPCUtils.h"; -using RequestCache from "mozilla/dom/FetchIPCUtils.h"; -using RequestContext from "mozilla/dom/FetchIPCUtils.h"; -using mozilla::dom::ResponseType from "mozilla/dom/FetchIPCUtils.h"; -using mozilla::void_t from "ipc/IPCMessageUtils.h"; -using struct nsID from "nsID.h"; - -namespace mozilla { -namespace dom { -namespace cache { - -struct PCacheQueryParams -{ - bool ignoreSearch; - bool ignoreMethod; - bool ignoreVary; - bool prefixMatch; - bool cacheNameSet; - nsString cacheName; -}; - -struct PCacheReadStream -{ - nsID id; - OptionalInputStreamParams params; - OptionalFileDescriptorSet fds; - nullable PCacheStreamControl control; - nullable PCachePushStream pushStream; -}; - -union PCacheReadStreamOrVoid -{ - void_t; - PCacheReadStream; -}; - -struct PCacheRequest -{ - nsCString method; - nsString url; - nsString urlWithoutQuery; - PHeadersEntry[] headers; - HeadersGuardEnum headersGuard; - nsString referrer; - RequestMode mode; - RequestCredentials credentials; - PCacheReadStreamOrVoid body; - uint32_t contentPolicyType; - RequestContext context; - RequestCache requestCache; -}; - -union PCacheRequestOrVoid -{ - void_t; - PCacheRequest; -}; - -struct PCacheResponse -{ - ResponseType type; - nsString url; - uint32_t status; - nsCString statusText; - PHeadersEntry[] headers; - HeadersGuardEnum headersGuard; - PCacheReadStreamOrVoid body; - nsCString securityInfo; -}; - -union PCacheResponseOrVoid -{ - void_t; - PCacheResponse; -}; - -struct CacheRequestResponse -{ - PCacheRequest request; - PCacheResponse response; -}; - -} // namespace cache -} // namespace dom -} // namespace mozilla diff --git a/dom/cache/PrincipalVerifier.cpp b/dom/cache/PrincipalVerifier.cpp index ee387f8780..7f4f40b95b 100644 --- a/dom/cache/PrincipalVerifier.cpp +++ b/dom/cache/PrincipalVerifier.cpp @@ -46,25 +46,35 @@ PrincipalVerifier::CreateAndDispatch(Listener* aListener, } void -PrincipalVerifier::ClearListener() +PrincipalVerifier::AddListener(Listener* aListener) { AssertIsOnBackgroundThread(); - MOZ_ASSERT(mListener); - mListener = nullptr; + MOZ_ASSERT(aListener); + MOZ_ASSERT(!mListenerList.Contains(aListener)); + mListenerList.AppendElement(aListener); +} + +void +PrincipalVerifier::RemoveListener(Listener* aListener) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aListener); + MOZ_ALWAYS_TRUE(mListenerList.RemoveElement(aListener)); } PrincipalVerifier::PrincipalVerifier(Listener* aListener, PBackgroundParent* aActor, const PrincipalInfo& aPrincipalInfo) - : mListener(aListener) - , mActor(BackgroundParent::GetContentParent(aActor)) + : mActor(BackgroundParent::GetContentParent(aActor)) , mPrincipalInfo(aPrincipalInfo) , mInitiatingThread(NS_GetCurrentThread()) , mResult(NS_OK) { AssertIsOnBackgroundThread(); - MOZ_ASSERT(mListener); MOZ_ASSERT(mInitiatingThread); + MOZ_ASSERT(aListener); + + mListenerList.AppendElement(aListener); } PrincipalVerifier::~PrincipalVerifier() @@ -73,7 +83,7 @@ PrincipalVerifier::~PrincipalVerifier() // threads, its a race to see which thread de-refs us last. Therefore // we cannot guarantee which thread we destruct on. - MOZ_ASSERT(!mListener); + MOZ_ASSERT(mListenerList.IsEmpty()); // We should always be able to explicitly release the actor on the main // thread. @@ -172,17 +182,13 @@ void PrincipalVerifier::CompleteOnInitiatingThread() { AssertIsOnBackgroundThread(); - - // This can happen if the listener is destroyed before we finish. For - // example, if the child process OOMs and the actor is destroyed. - if (!mListener) { - return; + ListenerList::ForwardIterator iter(mListenerList); + while (iter.HasMore()) { + iter.GetNext()->OnPrincipalVerified(mResult, mManagerId); } - mListener->OnPrincipalVerified(mResult, mManagerId); - - // The listener must clear their reference in OnPrincipalVerified() - MOZ_ASSERT(!mListener); + // The listener must clear its reference in OnPrincipalVerified() + MOZ_ASSERT(mListenerList.IsEmpty()); } void diff --git a/dom/cache/PrincipalVerifier.h b/dom/cache/PrincipalVerifier.h index 26b6ff2adc..689bf483aa 100644 --- a/dom/cache/PrincipalVerifier.h +++ b/dom/cache/PrincipalVerifier.h @@ -9,6 +9,7 @@ #include "mozilla/ipc/PBackgroundSharedTypes.h" #include "nsThreadUtils.h" +#include "nsTObserverArray.h" namespace mozilla { @@ -26,7 +27,7 @@ class PrincipalVerifier final : public nsRunnable public: // An interface to be implemented by code wishing to use the // PrincipalVerifier. Note, the Listener implementation is responsible - // for calling ClearListener() on the PrincipalVerifier to clear the + // for calling RemoveListener() on the PrincipalVerifier to clear the // weak reference. class Listener { @@ -38,9 +39,11 @@ public: CreateAndDispatch(Listener* aListener, mozilla::ipc::PBackgroundParent* aActor, const mozilla::ipc::PrincipalInfo& aPrincipalInfo); - // The Listener must call ClearListener() when OnPrincipalVerified() is + void AddListener(Listener* aListener); + + // The Listener must call RemoveListener() when OnPrincipalVerified() is // called or when the Listener is destroyed. - void ClearListener(); + void RemoveListener(Listener* aListener); private: PrincipalVerifier(Listener* aListener, mozilla::ipc::PBackgroundParent* aActor, @@ -52,8 +55,9 @@ private: void DispatchToInitiatingThread(nsresult aRv); - // Weak reference cleared by ClearListener() - Listener* mListener; + // Weak reference cleared by RemoveListener() + typedef nsTObserverArray ListenerList; + ListenerList mListenerList; // set in originating thread at construction, but must be accessed and // released on main thread diff --git a/dom/cache/ReadStream.cpp b/dom/cache/ReadStream.cpp index 1d9d3dc495..174386dac5 100644 --- a/dom/cache/ReadStream.cpp +++ b/dom/cache/ReadStream.cpp @@ -9,7 +9,7 @@ #include "mozilla/unused.h" #include "mozilla/dom/cache/CacheStreamControlChild.h" #include "mozilla/dom/cache/CacheStreamControlParent.h" -#include "mozilla/dom/cache/PCacheTypes.h" +#include "mozilla/dom/cache/CacheTypes.h" #include "mozilla/ipc/FileDescriptor.h" #include "mozilla/ipc/InputStreamUtils.h" #include "mozilla/SnappyUncompressInputStream.h" @@ -34,10 +34,10 @@ public: nsIInputStream* aStream); void - Serialize(PCacheReadStreamOrVoid* aReadStreamOut); + Serialize(CacheReadStreamOrVoid* aReadStreamOut); void - Serialize(PCacheReadStream* aReadStreamOut); + Serialize(CacheReadStream* aReadStreamOut); // ReadStream::Controllable methods virtual void @@ -49,6 +49,9 @@ public: virtual bool MatchId(const nsID& aId) const override; + virtual bool + HasEverBeenRead() const override; + // Simulate nsIInputStream methods, but we don't actually inherit from it NS_METHOD Close(); @@ -102,6 +105,7 @@ private: NumStates }; Atomic mState; + Atomic mHasEverBeenRead; NS_INLINE_DECL_THREADSAFE_REFCOUNTING(cache::ReadStream::Inner) }; @@ -192,17 +196,17 @@ ReadStream::Inner::Inner(StreamControl* aControl, const nsID& aId, } void -ReadStream::Inner::Serialize(PCacheReadStreamOrVoid* aReadStreamOut) +ReadStream::Inner::Serialize(CacheReadStreamOrVoid* aReadStreamOut) { MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread); MOZ_ASSERT(aReadStreamOut); - PCacheReadStream stream; + CacheReadStream stream; Serialize(&stream); *aReadStreamOut = stream; } void -ReadStream::Inner::Serialize(PCacheReadStream* aReadStreamOut) +ReadStream::Inner::Serialize(CacheReadStream* aReadStreamOut) { MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread); MOZ_ASSERT(aReadStreamOut); @@ -248,6 +252,13 @@ ReadStream::Inner::MatchId(const nsID& aId) const return mId.Equals(aId); } +bool +ReadStream::Inner::HasEverBeenRead() const +{ + MOZ_ASSERT(NS_GetCurrentThread() == mOwningThread); + return mHasEverBeenRead; +} + NS_IMETHODIMP ReadStream::Inner::Close() { @@ -283,6 +294,8 @@ ReadStream::Inner::Read(char* aBuf, uint32_t aCount, uint32_t* aNumReadOut) Close(); } + mHasEverBeenRead = true; + return rv; } @@ -293,6 +306,10 @@ ReadStream::Inner::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, // stream ops can happen on any thread MOZ_ASSERT(aNumReadOut); + if (aCount) { + mHasEverBeenRead = true; + } + nsresult rv = mSnappyStream->ReadSegments(aWriter, aClosure, aCount, aNumReadOut); @@ -301,6 +318,14 @@ ReadStream::Inner::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, Close(); } + // Verify bytes were actually read before marking as being ever read. For + // example, code can test if the stream supports ReadSegments() by calling + // this method with a dummy callback which doesn't read anything. We don't + // want to trigger on that. + if (*aNumReadOut) { + mHasEverBeenRead = true; + } + return rv; } @@ -390,18 +415,18 @@ NS_IMPL_ISUPPORTS(cache::ReadStream, nsIInputStream, ReadStream); // static already_AddRefed -ReadStream::Create(const PCacheReadStreamOrVoid& aReadStreamOrVoid) +ReadStream::Create(const CacheReadStreamOrVoid& aReadStreamOrVoid) { - if (aReadStreamOrVoid.type() == PCacheReadStreamOrVoid::Tvoid_t) { + if (aReadStreamOrVoid.type() == CacheReadStreamOrVoid::Tvoid_t) { return nullptr; } - return Create(aReadStreamOrVoid.get_PCacheReadStream()); + return Create(aReadStreamOrVoid.get_CacheReadStream()); } // static already_AddRefed -ReadStream::Create(const PCacheReadStream& aReadStream) +ReadStream::Create(const CacheReadStream& aReadStream) { // The parameter may or may not be for a Cache created stream. The way we // tell is by looking at the stream control actor. If the actor exists, @@ -456,13 +481,13 @@ ReadStream::Create(PCacheStreamControlParent* aControl, const nsID& aId, } void -ReadStream::Serialize(PCacheReadStreamOrVoid* aReadStreamOut) +ReadStream::Serialize(CacheReadStreamOrVoid* aReadStreamOut) { mInner->Serialize(aReadStreamOut); } void -ReadStream::Serialize(PCacheReadStream* aReadStreamOut) +ReadStream::Serialize(CacheReadStream* aReadStreamOut) { mInner->Serialize(aReadStreamOut); } diff --git a/dom/cache/ReadStream.h b/dom/cache/ReadStream.h index c2e98f6d49..6db31527fa 100644 --- a/dom/cache/ReadStream.h +++ b/dom/cache/ReadStream.h @@ -21,8 +21,8 @@ namespace mozilla { namespace dom { namespace cache { -class PCacheReadStream; -class PCacheReadStreamOrVoid; +class CacheReadStream; +class CacheReadStreamOrVoid; class PCacheStreamControlParent; // IID for the dom::cache::ReadStream interface @@ -63,6 +63,9 @@ public: virtual bool MatchId(const nsID& aId) const = 0; + virtual bool + HasEverBeenRead() const = 0; + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0; @@ -71,17 +74,17 @@ public: }; static already_AddRefed - Create(const PCacheReadStreamOrVoid& aReadStreamOrVoid); + Create(const CacheReadStreamOrVoid& aReadStreamOrVoid); static already_AddRefed - Create(const PCacheReadStream& aReadStream); + Create(const CacheReadStream& aReadStream); static already_AddRefed Create(PCacheStreamControlParent* aControl, const nsID& aId, nsIInputStream* aStream); - void Serialize(PCacheReadStreamOrVoid* aReadStreamOut); - void Serialize(PCacheReadStream* aReadStreamOut); + void Serialize(CacheReadStreamOrVoid* aReadStreamOut); + void Serialize(CacheReadStream* aReadStreamOut); private: class Inner; diff --git a/dom/cache/SavedTypes.h b/dom/cache/SavedTypes.h index ec95c68d72..9e1f686b4a 100644 --- a/dom/cache/SavedTypes.h +++ b/dom/cache/SavedTypes.h @@ -10,7 +10,7 @@ // NOTE: This cannot be rolled into Types.h because the IPC dependency. // breaks webidl unified builds. -#include "mozilla/dom/cache/PCacheTypes.h" +#include "mozilla/dom/cache/CacheTypes.h" #include "mozilla/dom/cache/Types.h" #include "nsCOMPtr.h" #include "nsID.h" @@ -23,7 +23,7 @@ namespace cache { struct SavedRequest { SavedRequest() : mHasBodyId(false) { mValue.body() = void_t(); } - PCacheRequest mValue; + CacheRequest mValue; bool mHasBodyId; nsID mBodyId; CacheId mCacheId; @@ -32,7 +32,7 @@ struct SavedRequest struct SavedResponse { SavedResponse() : mHasBodyId(false) { mValue.body() = void_t(); } - PCacheResponse mValue; + CacheResponse mValue; bool mHasBodyId; nsID mBodyId; CacheId mCacheId; diff --git a/dom/cache/StreamControl.cpp b/dom/cache/StreamControl.cpp index b021684456..1d6a807df4 100644 --- a/dom/cache/StreamControl.cpp +++ b/dom/cache/StreamControl.cpp @@ -84,6 +84,18 @@ StreamControl::CloseAllReadStreamsWithoutReporting() } } +bool +StreamControl::HasEverBeenRead() const +{ + ReadStreamList::ForwardIterator iter(mReadStreamList); + while (iter.HasMore()) { + if (iter.GetNext()->HasEverBeenRead()) { + return true; + } + } + return false; +} + } // namespace cache } // namespace dom } // namespace mozilla diff --git a/dom/cache/StreamControl.h b/dom/cache/StreamControl.h index 583ef1312f..78ce45de42 100644 --- a/dom/cache/StreamControl.h +++ b/dom/cache/StreamControl.h @@ -20,7 +20,7 @@ namespace ipc { namespace dom { namespace cache { -class PCacheReadStream; +class CacheReadStream; // Abstract class to help implement the stream control Child and Parent actors. // This provides an interface to partly help with serialization of IPC types, @@ -30,14 +30,14 @@ class StreamControl public: // abstract interface that must be implemented by child class virtual void - SerializeControl(PCacheReadStream* aReadStreamOut) = 0; + SerializeControl(CacheReadStream* aReadStreamOut) = 0; virtual void - SerializeFds(PCacheReadStream* aReadStreamOut, + SerializeFds(CacheReadStream* aReadStreamOut, const nsTArray& aFds) = 0; virtual void - DeserializeFds(const PCacheReadStream& aReadStream, + DeserializeFds(const CacheReadStream& aReadStream, nsTArray& aFdsOut) = 0; // inherited implementation of the ReadStream::Controllable list @@ -68,6 +68,9 @@ protected: void CloseAllReadStreamsWithoutReporting(); + bool + HasEverBeenRead() const; + // protected parts of the abstract interface virtual void NoteClosedAfterForget(const nsID& aId) = 0; diff --git a/dom/cache/StreamUtils.cpp b/dom/cache/StreamUtils.cpp deleted file mode 100644 index b0f7eae0ae..0000000000 --- a/dom/cache/StreamUtils.cpp +++ /dev/null @@ -1,151 +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/cache/StreamUtils.h" - -#include "mozilla/unused.h" -#include "mozilla/dom/cache/CacheStreamControlChild.h" -#include "mozilla/dom/cache/PCacheTypes.h" -#include "mozilla/ipc/FileDescriptor.h" -#include "mozilla/ipc/FileDescriptorSetChild.h" - -namespace mozilla { -namespace dom { -namespace cache { - -namespace { - -using mozilla::unused; -using mozilla::void_t; -using mozilla::dom::cache::CacheStreamControlChild; -using mozilla::dom::cache::Feature; -using mozilla::dom::cache::PCacheReadStream; -using mozilla::ipc::FileDescriptor; -using mozilla::ipc::FileDescriptorSetChild; -using mozilla::ipc::OptionalFileDescriptorSet; - -void -StartDestroyStreamChild(const PCacheReadStream& aReadStream) -{ - CacheStreamControlChild* cacheControl = - static_cast(aReadStream.controlChild()); - if (cacheControl) { - cacheControl->StartDestroy(); - } - - if (aReadStream.fds().type() == - OptionalFileDescriptorSet::TPFileDescriptorSetChild) { - nsAutoTArray fds; - - FileDescriptorSetChild* fdSetActor = - static_cast(aReadStream.fds().get_PFileDescriptorSetChild()); - MOZ_ASSERT(fdSetActor); - - fdSetActor->ForgetFileDescriptors(fds); - MOZ_ASSERT(!fds.IsEmpty()); - - unused << fdSetActor->Send__delete__(fdSetActor); - } -} - -void -AddFeatureToStreamChild(const PCacheReadStream& aReadStream, Feature* aFeature) -{ - CacheStreamControlChild* cacheControl = - static_cast(aReadStream.controlChild()); - if (cacheControl) { - cacheControl->SetFeature(aFeature); - } -} - -} // anonymous namespace - -void -StartDestroyStreamChild(const PCacheResponseOrVoid& aResponseOrVoid) -{ - if (aResponseOrVoid.type() == PCacheResponseOrVoid::Tvoid_t) { - return; - } - - StartDestroyStreamChild(aResponseOrVoid.get_PCacheResponse()); -} - -void -StartDestroyStreamChild(const PCacheResponse& aResponse) -{ - if (aResponse.body().type() == PCacheReadStreamOrVoid::Tvoid_t) { - return; - } - - StartDestroyStreamChild(aResponse.body().get_PCacheReadStream()); -} - -void -StartDestroyStreamChild(const nsTArray& aResponses) -{ - for (uint32_t i = 0; i < aResponses.Length(); ++i) { - StartDestroyStreamChild(aResponses[i]); - } -} - -void -StartDestroyStreamChild(const nsTArray& aRequests) -{ - for (uint32_t i = 0; i < aRequests.Length(); ++i) { - if (aRequests[i].body().type() == PCacheReadStreamOrVoid::Tvoid_t) { - continue; - } - StartDestroyStreamChild(aRequests[i].body().get_PCacheReadStream()); - } -} - -void -AddFeatureToStreamChild(const PCacheResponseOrVoid& aResponseOrVoid, - Feature* aFeature) -{ - if (aResponseOrVoid.type() == PCacheResponseOrVoid::Tvoid_t) { - return; - } - - AddFeatureToStreamChild(aResponseOrVoid.get_PCacheResponse(), aFeature); -} - -void -AddFeatureToStreamChild(const PCacheResponse& aResponse, - Feature* aFeature) -{ - if (aResponse.body().type() == PCacheReadStreamOrVoid::Tvoid_t) { - return; - } - - AddFeatureToStreamChild(aResponse.body().get_PCacheReadStream(), aFeature); -} - -void -AddFeatureToStreamChild(const nsTArray& aResponses, - Feature* aFeature) -{ - for (uint32_t i = 0; i < aResponses.Length(); ++i) { - AddFeatureToStreamChild(aResponses[i], aFeature); - } -} - -void -AddFeatureToStreamChild(const nsTArray& aRequests, - Feature* aFeature) -{ - for (uint32_t i = 0; i < aRequests.Length(); ++i) { - if (aRequests[i].body().type() == PCacheReadStreamOrVoid::Tvoid_t) { - continue; - } - AddFeatureToStreamChild(aRequests[i].body().get_PCacheReadStream(), - aFeature); - } -} - -} // namespace cache -} // namespace dom -} // namespace mozilla diff --git a/dom/cache/StreamUtils.h b/dom/cache/StreamUtils.h deleted file mode 100644 index cb8d1bb657..0000000000 --- a/dom/cache/StreamUtils.h +++ /dev/null @@ -1,39 +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_cache_StreamUtils_h -#define mozilla_dom_cache_StreamUtils_h - -#include "nsTArrayForwardDeclare.h" - -namespace mozilla { -namespace dom { -namespace cache { - -class Feature; -class PCacheRequest; -class PCacheResponse; -class PCacheResponseOrVoid; - -void StartDestroyStreamChild(const PCacheResponseOrVoid& aResponseOrVoid); -void StartDestroyStreamChild(const PCacheResponse& aResponse); -void StartDestroyStreamChild(const nsTArray& aResponses); -void StartDestroyStreamChild(const nsTArray& aRequests); - -void AddFeatureToStreamChild(const PCacheResponseOrVoid& aResponseOrVoid, - Feature* aFeature); -void AddFeatureToStreamChild(const PCacheResponse& aResponse, - Feature* aFeature); -void AddFeatureToStreamChild(const nsTArray& aResponses, - Feature* aFeature); -void AddFeatureToStreamChild(const nsTArray& aRequests, - Feature* aFeature); - -} // namespace cache -} // namespace dom -} // namespace mozilla - -#endif // mozilla_dom_cache_StreamUtils_h diff --git a/dom/cache/TypeUtils.cpp b/dom/cache/TypeUtils.cpp index 129d21dcf4..5814fd06e1 100644 --- a/dom/cache/TypeUtils.cpp +++ b/dom/cache/TypeUtils.cpp @@ -12,7 +12,7 @@ #include "mozilla/dom/Request.h" #include "mozilla/dom/Response.h" #include "mozilla/dom/cache/CachePushStreamChild.h" -#include "mozilla/dom/cache/PCacheTypes.h" +#include "mozilla/dom/cache/CacheTypes.h" #include "mozilla/dom/cache/ReadStream.h" #include "mozilla/ipc/BackgroundChild.h" #include "mozilla/ipc/FileDescriptorSetChild.h" @@ -34,7 +34,9 @@ namespace { using mozilla::ErrorResult; using mozilla::unused; using mozilla::void_t; -using mozilla::dom::cache::PCacheReadStream; +using mozilla::dom::InternalHeaders; +using mozilla::dom::cache::CacheReadStream; +using mozilla::dom::cache::HeadersEntry; using mozilla::ipc::BackgroundChild; using mozilla::ipc::FileDescriptor; using mozilla::ipc::PBackgroundChild; @@ -130,7 +132,7 @@ HasVaryStar(mozilla::dom::InternalHeaders* aHeaders) } void -SerializeNormalStream(nsIInputStream* aStream, PCacheReadStream& aReadStreamOut) +SerializeNormalStream(nsIInputStream* aStream, CacheReadStream& aReadStreamOut) { nsAutoTArray fds; SerializeInputStream(aStream, aReadStreamOut.params(), fds); @@ -154,6 +156,20 @@ SerializeNormalStream(nsIInputStream* aStream, PCacheReadStream& aReadStreamOut) } } +void +ToHeadersEntryList(nsTArray& aOut, InternalHeaders* aHeaders) +{ + MOZ_ASSERT(aHeaders); + + nsAutoTArray entryList; + aHeaders->GetEntries(entryList); + + for (uint32_t i = 0; i < entryList.Length(); ++i) { + InternalHeaders::Entry& entry = entryList[i]; + aOut.AppendElement(HeadersEntry(entry.mName, entry.mValue)); + } +} + } // anonymous namespace namespace mozilla { @@ -206,10 +222,10 @@ TypeUtils::ToInternalRequest(const OwningRequestOrUSVString& aIn, } void -TypeUtils::ToPCacheRequest(PCacheRequest& aOut, InternalRequest* aIn, - BodyAction aBodyAction, - ReferrerAction aReferrerAction, - SchemeAction aSchemeAction, ErrorResult& aRv) +TypeUtils::ToCacheRequest(CacheRequest& aOut, InternalRequest* aIn, + BodyAction aBodyAction, + ReferrerAction aReferrerAction, + SchemeAction aSchemeAction, ErrorResult& aRv) { MOZ_ASSERT(aIn); @@ -245,7 +261,7 @@ TypeUtils::ToPCacheRequest(PCacheRequest& aOut, InternalRequest* aIn, nsRefPtr headers = aIn->Headers(); MOZ_ASSERT(headers); - headers->GetPHeaders(aOut.headers()); + ToHeadersEntryList(aOut.headers(), headers); aOut.headersGuard() = headers->Guard(); aOut.mode() = aIn->Mode(); aOut.credentials() = aIn->GetCredentialsMode(); @@ -269,8 +285,8 @@ TypeUtils::ToPCacheRequest(PCacheRequest& aOut, InternalRequest* aIn, } void -TypeUtils::ToPCacheResponseWithoutBody(PCacheResponse& aOut, - InternalResponse& aIn, ErrorResult& aRv) +TypeUtils::ToCacheResponseWithoutBody(CacheResponse& aOut, + InternalResponse& aIn, ErrorResult& aRv) { aOut.type() = aIn.Type(); @@ -295,13 +311,13 @@ TypeUtils::ToPCacheResponseWithoutBody(PCacheResponse& aOut, aRv.ThrowTypeError(MSG_RESPONSE_HAS_VARY_STAR); return; } - headers->GetPHeaders(aOut.headers()); + ToHeadersEntryList(aOut.headers(), headers); aOut.headersGuard() = headers->Guard(); aOut.securityInfo() = aIn.GetSecurityInfo(); } void -TypeUtils::ToPCacheResponse(PCacheResponse& aOut, Response& aIn, ErrorResult& aRv) +TypeUtils::ToCacheResponse(CacheResponse& aOut, Response& aIn, ErrorResult& aRv) { if (aIn.BodyUsed()) { aRv.ThrowTypeError(MSG_FETCH_BODY_CONSUMED_ERROR); @@ -309,7 +325,7 @@ TypeUtils::ToPCacheResponse(PCacheResponse& aOut, Response& aIn, ErrorResult& aR } nsRefPtr ir = aIn.GetInternalResponse(); - ToPCacheResponseWithoutBody(aOut, *ir, aRv); + ToCacheResponseWithoutBody(aOut, *ir, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } @@ -328,8 +344,8 @@ TypeUtils::ToPCacheResponse(PCacheResponse& aOut, Response& aIn, ErrorResult& aR // static void -TypeUtils::ToPCacheQueryParams(PCacheQueryParams& aOut, - const CacheQueryOptions& aIn) +TypeUtils::ToCacheQueryParams(CacheQueryParams& aOut, + const CacheQueryOptions& aIn) { aOut.ignoreSearch() = aIn.mIgnoreSearch; aOut.ignoreMethod() = aIn.mIgnoreMethod; @@ -344,7 +360,7 @@ TypeUtils::ToPCacheQueryParams(PCacheQueryParams& aOut, } already_AddRefed -TypeUtils::ToResponse(const PCacheResponse& aIn) +TypeUtils::ToResponse(const CacheResponse& aIn) { if (aIn.type() == ResponseType::Error) { nsRefPtr error = InternalResponse::NetworkError(); @@ -357,7 +373,7 @@ TypeUtils::ToResponse(const PCacheResponse& aIn) ir->SetUrl(NS_ConvertUTF16toUTF8(aIn.url())); nsRefPtr internalHeaders = - new InternalHeaders(aIn.headers(), aIn.headersGuard()); + ToInternalHeaders(aIn.headers(), aIn.headersGuard()); ErrorResult result; ir->Headers()->SetGuard(aIn.headersGuard(), result); MOZ_ASSERT(!result.Failed()); @@ -392,7 +408,7 @@ TypeUtils::ToResponse(const PCacheResponse& aIn) } already_AddRefed -TypeUtils::ToInternalRequest(const PCacheRequest& aIn) +TypeUtils::ToInternalRequest(const CacheRequest& aIn) { nsRefPtr internalRequest = new InternalRequest(); @@ -409,7 +425,7 @@ TypeUtils::ToInternalRequest(const PCacheRequest& aIn) internalRequest->SetCacheMode(aIn.requestCache()); nsRefPtr internalHeaders = - new InternalHeaders(aIn.headers(), aIn.headersGuard()); + ToInternalHeaders(aIn.headers(), aIn.headersGuard()); ErrorResult result; internalRequest->Headers()->SetGuard(aIn.headersGuard(), result); MOZ_ASSERT(!result.Failed()); @@ -424,13 +440,30 @@ TypeUtils::ToInternalRequest(const PCacheRequest& aIn) } already_AddRefed -TypeUtils::ToRequest(const PCacheRequest& aIn) +TypeUtils::ToRequest(const CacheRequest& aIn) { nsRefPtr internalRequest = ToInternalRequest(aIn); nsRefPtr request = new Request(GetGlobalObject(), internalRequest); return request.forget(); } +// static +already_AddRefed +TypeUtils::ToInternalHeaders(const nsTArray& aHeadersEntryList, + HeadersGuardEnum aGuard) +{ + nsTArray entryList(aHeadersEntryList.Length()); + + for (uint32_t i = 0; i < aHeadersEntryList.Length(); ++i) { + const HeadersEntry& headersEntry = aHeadersEntryList[i]; + entryList.AppendElement(InternalHeaders::Entry(headersEntry.name(), + headersEntry.value())); + } + + nsRefPtr ref = new InternalHeaders(Move(entryList), aGuard); + return ref.forget(); +} + void TypeUtils::CheckAndSetBodyUsed(Request* aRequest, BodyAction aBodyAction, ErrorResult& aRv) @@ -478,7 +511,7 @@ TypeUtils::ToInternalRequest(const nsAString& aIn, ErrorResult& aRv) void TypeUtils::SerializeCacheStream(nsIInputStream* aStream, - PCacheReadStreamOrVoid* aStreamOut, + CacheReadStreamOrVoid* aStreamOut, ErrorResult& aRv) { *aStreamOut = void_t(); @@ -493,7 +526,7 @@ TypeUtils::SerializeCacheStream(nsIInputStream* aStream, return; } - PCacheReadStream readStream; + CacheReadStream readStream; readStream.controlChild() = nullptr; readStream.controlParent() = nullptr; readStream.pushStreamChild() = nullptr; @@ -517,7 +550,7 @@ TypeUtils::SerializeCacheStream(nsIInputStream* aStream, void TypeUtils::SerializePushStream(nsIInputStream* aStream, - PCacheReadStream& aReadStreamOut, + CacheReadStream& aReadStreamOut, ErrorResult& aRv) { nsCOMPtr asyncStream = do_QueryInterface(aStream); diff --git a/dom/cache/TypeUtils.h b/dom/cache/TypeUtils.h index b78fcd8355..ea2f971394 100644 --- a/dom/cache/TypeUtils.h +++ b/dom/cache/TypeUtils.h @@ -9,6 +9,7 @@ #include "mozilla/Attributes.h" #include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/InternalHeaders.h" #include "nsError.h" class nsIGlobalObject; @@ -29,11 +30,12 @@ class Response; namespace cache { class CachePushStreamChild; -class PCacheQueryParams; -class PCacheReadStream; -class PCacheReadStreamOrVoid; -class PCacheRequest; -class PCacheResponse; +class CacheQueryParams; +class CacheReadStream; +class CacheReadStreamOrVoid; +class CacheRequest; +class CacheResponse; +class HeadersEntry; class TypeUtils { @@ -77,28 +79,33 @@ public: ErrorResult& aRv); void - ToPCacheRequest(PCacheRequest& aOut, InternalRequest* aIn, - BodyAction aBodyAction, ReferrerAction aReferrerAction, - SchemeAction aSchemeAction, ErrorResult& aRv); + ToCacheRequest(CacheRequest& aOut, InternalRequest* aIn, + BodyAction aBodyAction, ReferrerAction aReferrerAction, + SchemeAction aSchemeAction, ErrorResult& aRv); void - ToPCacheResponseWithoutBody(PCacheResponse& aOut, InternalResponse& aIn, - ErrorResult& aRv); + ToCacheResponseWithoutBody(CacheResponse& aOut, InternalResponse& aIn, + ErrorResult& aRv); void - ToPCacheResponse(PCacheResponse& aOut, Response& aIn, ErrorResult& aRv); + ToCacheResponse(CacheResponse& aOut, Response& aIn, ErrorResult& aRv); void - ToPCacheQueryParams(PCacheQueryParams& aOut, const CacheQueryOptions& aIn); + ToCacheQueryParams(CacheQueryParams& aOut, const CacheQueryOptions& aIn); already_AddRefed - ToResponse(const PCacheResponse& aIn); + ToResponse(const CacheResponse& aIn); already_AddRefed - ToInternalRequest(const PCacheRequest& aIn); + ToInternalRequest(const CacheRequest& aIn); already_AddRefed - ToRequest(const PCacheRequest& aIn); + ToRequest(const CacheRequest& aIn); + + // static methods + static already_AddRefed + ToInternalHeaders(const nsTArray& aHeadersEntryList, + HeadersGuardEnum aGuard = HeadersGuardEnum::None); private: void @@ -109,11 +116,11 @@ private: ToInternalRequest(const nsAString& aIn, ErrorResult& aRv); void - SerializeCacheStream(nsIInputStream* aStream, PCacheReadStreamOrVoid* aStreamOut, + SerializeCacheStream(nsIInputStream* aStream, CacheReadStreamOrVoid* aStreamOut, ErrorResult& aRv); void - SerializePushStream(nsIInputStream* aStream, PCacheReadStream& aReadStreamOut, + SerializePushStream(nsIInputStream* aStream, CacheReadStream& aReadStreamOut, ErrorResult& aRv); }; diff --git a/dom/cache/Types.h b/dom/cache/Types.h index f10473654c..b67f73c06b 100644 --- a/dom/cache/Types.h +++ b/dom/cache/Types.h @@ -22,9 +22,7 @@ enum Namespace CHROME_ONLY_NAMESPACE, NUMBER_OF_NAMESPACES }; - -typedef uintptr_t RequestId; -static const RequestId INVALID_REQUEST_ID = 0; +static const Namespace INVALID_NAMESPACE = NUMBER_OF_NAMESPACES; typedef int64_t CacheId; static const CacheId INVALID_CACHE_ID = -1; diff --git a/dom/cache/moz.build b/dom/cache/moz.build index 917b6a4026..cd7514fb64 100644 --- a/dom/cache/moz.build +++ b/dom/cache/moz.build @@ -11,6 +11,8 @@ EXPORTS.mozilla.dom.cache += [ 'AutoUtils.h', 'Cache.h', 'CacheChild.h', + 'CacheOpChild.h', + 'CacheOpParent.h', 'CacheParent.h', 'CachePushStreamChild.h', 'CachePushStreamParent.h', @@ -35,7 +37,6 @@ EXPORTS.mozilla.dom.cache += [ 'SavedTypes.h', 'StreamControl.h', 'StreamList.h', - 'StreamUtils.h', 'Types.h', 'TypeUtils.h', ] @@ -46,6 +47,8 @@ UNIFIED_SOURCES += [ 'AutoUtils.cpp', 'Cache.cpp', 'CacheChild.cpp', + 'CacheOpChild.cpp', + 'CacheOpParent.cpp', 'CacheParent.cpp', 'CachePushStreamChild.cpp', 'CachePushStreamParent.cpp', @@ -68,17 +71,16 @@ UNIFIED_SOURCES += [ 'ReadStream.cpp', 'StreamControl.cpp', 'StreamList.cpp', - 'StreamUtils.cpp', 'TypeUtils.cpp', ] IPDL_SOURCES += [ - 'CacheInitData.ipdlh', + 'CacheTypes.ipdlh', 'PCache.ipdl', + 'PCacheOp.ipdl', 'PCachePushStream.ipdl', 'PCacheStorage.ipdl', 'PCacheStreamControl.ipdl', - 'PCacheTypes.ipdlh', ] include('/ipc/chromium/chromium-config.mozbuild') diff --git a/dom/fetch/FetchIPCUtils.h b/dom/fetch/FetchIPCUtils.h deleted file mode 100644 index 830b7d3bde..0000000000 --- a/dom/fetch/FetchIPCUtils.h +++ /dev/null @@ -1,51 +0,0 @@ -/* -*- 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_FetchIPCUtils_h -#define mozilla_dom_FetchIPCUtils_h - -#include "ipc/IPCMessageUtils.h" - -// Fix X11 header brain damage that conflicts with HeadersGuardEnum::None -#undef None - -#include "mozilla/dom/HeadersBinding.h" -#include "mozilla/dom/Request.h" -#include "mozilla/dom/Response.h" - -namespace IPC { - template<> - struct ParamTraits : - public ContiguousEnumSerializer {}; - template<> - struct ParamTraits : - public ContiguousEnumSerializer {}; - template<> - struct ParamTraits : - public ContiguousEnumSerializer {}; - template<> - struct ParamTraits : - public ContiguousEnumSerializer {}; - template<> - struct ParamTraits : - public ContiguousEnumSerializer {}; - template<> - struct ParamTraits : - public ContiguousEnumSerializer {}; -} - -#endif // mozilla_dom_FetchIPCUtils_h diff --git a/dom/fetch/InternalHeaders.cpp b/dom/fetch/InternalHeaders.cpp index 208ea832c1..244e6621c8 100644 --- a/dom/fetch/InternalHeaders.cpp +++ b/dom/fetch/InternalHeaders.cpp @@ -7,7 +7,6 @@ #include "mozilla/dom/InternalHeaders.h" #include "mozilla/ErrorResult.h" -#include "mozilla/dom/PHeaders.h" #include "nsCharSeparatedTokenizer.h" #include "nsContentUtils.h" @@ -17,21 +16,11 @@ namespace mozilla { namespace dom { -InternalHeaders::InternalHeaders(const nsTArray& aHeaders, +InternalHeaders::InternalHeaders(const nsTArray&& aHeaders, HeadersGuardEnum aGuard) : mGuard(aGuard) + , mList(aHeaders) { - for (uint32_t i = 0; i < aHeaders.Length(); ++i) { - mList.AppendElement(Entry(aHeaders[i].name(), aHeaders[i].value())); - } -} - -void -InternalHeaders::GetPHeaders(nsTArray& aPHeadersOut) const -{ - for (uint32_t i = 0; i < mList.Length(); ++i) { - aPHeadersOut.AppendElement(PHeadersEntry(mList[i].mName, mList[i].mValue)); - } } void diff --git a/dom/fetch/InternalHeaders.h b/dom/fetch/InternalHeaders.h index 2977157512..5d632dcb98 100644 --- a/dom/fetch/InternalHeaders.h +++ b/dom/fetch/InternalHeaders.h @@ -24,7 +24,6 @@ namespace dom { template class MozMap; class HeadersOrByteStringSequenceSequenceOrByteStringMozMap; -class PHeadersEntry; class InternalHeaders final { @@ -62,7 +61,7 @@ public: MOZ_ASSERT(!result.Failed()); } - explicit InternalHeaders(const nsTArray& aHeaders, + explicit InternalHeaders(const nsTArray&& aHeaders, HeadersGuardEnum aGuard = HeadersGuardEnum::None); void Append(const nsACString& aName, const nsACString& aValue, @@ -91,9 +90,6 @@ public: static already_AddRefed CORSHeaders(InternalHeaders* aHeaders); - void - GetPHeaders(nsTArray& aPHeadersOut) const; - void GetEntries(nsTArray& aEntries) const; diff --git a/dom/fetch/PHeaders.ipdlh b/dom/fetch/PHeaders.ipdlh deleted file mode 100644 index e7fa0beb95..0000000000 --- a/dom/fetch/PHeaders.ipdlh +++ /dev/null @@ -1,15 +0,0 @@ -/* 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/. */ - -namespace mozilla { -namespace dom { - -struct PHeadersEntry -{ - nsCString name; - nsCString value; -}; - -} // namespace dom -} // namespace mozilla diff --git a/dom/fetch/moz.build b/dom/fetch/moz.build index c63b3344eb..92c25148d6 100644 --- a/dom/fetch/moz.build +++ b/dom/fetch/moz.build @@ -7,7 +7,6 @@ EXPORTS.mozilla.dom += [ 'Fetch.h', 'FetchDriver.h', - 'FetchIPCUtils.h', 'Headers.h', 'InternalHeaders.h', 'InternalRequest.h', @@ -27,12 +26,6 @@ UNIFIED_SOURCES += [ 'Response.cpp', ] -IPDL_SOURCES += [ - 'PHeaders.ipdlh', -] - -include('/ipc/chromium/chromium-config.mozbuild') - LOCAL_INCLUDES += [ '../workers', # For nsDataHandler.h diff --git a/dom/media/webrtc/RTCIdentityProviderRegistrar.h b/dom/media/webrtc/RTCIdentityProviderRegistrar.h index 601f967593..f1fb32729b 100644 --- a/dom/media/webrtc/RTCIdentityProviderRegistrar.h +++ b/dom/media/webrtc/RTCIdentityProviderRegistrar.h @@ -31,7 +31,7 @@ public: // As required nsIGlobalObject* GetParentObject() const; - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto); + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; // setter and getter void Register(RTCIdentityProvider& aIdp); diff --git a/dom/plugins/base/nsPluginPlayPreviewInfo.cpp b/dom/plugins/base/nsPluginPlayPreviewInfo.cpp index fea976aa35..c2a4ae18ed 100644 --- a/dom/plugins/base/nsPluginPlayPreviewInfo.cpp +++ b/dom/plugins/base/nsPluginPlayPreviewInfo.cpp @@ -60,12 +60,13 @@ nsPluginPlayPreviewInfo::GetWhitelist(nsACString& aWhitelist) return NS_OK; } -NS_IMETHODIMP +/* static */ nsresult nsPluginPlayPreviewInfo::CheckWhitelist(const nsACString& aPageURI, const nsACString& aObjectURI, + const nsACString& aWhitelist, bool *_retval) { - if (mWhitelist.Length() == 0) { + if (aWhitelist.Length() == 0) { // Considering empty whitelist as '*' entry. *_retval = true; return NS_OK; @@ -76,8 +77,8 @@ nsPluginPlayPreviewInfo::CheckWhitelist(const nsACString& aPageURI, // where page_url and object_url pattern matches for aPageURI // and aObjectURI, and performs matching as the same time. nsACString::const_iterator start, end; - mWhitelist.BeginReading(start); - mWhitelist.EndReading(end); + aWhitelist.BeginReading(start); + aWhitelist.EndReading(end); nsAutoCString pageURI(aPageURI); nsAutoCString objectURI(aObjectURI); @@ -143,3 +144,11 @@ nsPluginPlayPreviewInfo::CheckWhitelist(const nsACString& aPageURI, *_retval = false; return NS_OK; } + +NS_IMETHODIMP +nsPluginPlayPreviewInfo::CheckWhitelist(const nsACString& aPageURI, + const nsACString& aObjectURI, + bool *_retval) +{ + return CheckWhitelist(aPageURI, aObjectURI, mWhitelist, _retval); +} diff --git a/dom/plugins/base/nsPluginPlayPreviewInfo.h b/dom/plugins/base/nsPluginPlayPreviewInfo.h index bf3b2d0ec8..a28bba593a 100644 --- a/dom/plugins/base/nsPluginPlayPreviewInfo.h +++ b/dom/plugins/base/nsPluginPlayPreviewInfo.h @@ -23,6 +23,18 @@ public: const char* aWhitelist); explicit nsPluginPlayPreviewInfo(const nsPluginPlayPreviewInfo* aSource); + /** This function checks aPageURI and aObjectURI against the whitelist + * specified in aWhitelist. This is public static function because this + * whitelist checking code needs to be accessed without any instances of + * nsIPluginPlayPreviewInfo. In particular, the Shumway whitelist is + * obtained directly from prefs and compared using this code for telemetry + * purposes. + */ + static nsresult CheckWhitelist(const nsACString& aPageURI, + const nsACString& aObjectURI, + const nsACString& aWhitelist, + bool *_retval); + nsCString mMimeType; bool mIgnoreCTP; nsCString mRedirectURL; diff --git a/dom/plugins/ipc/PluginInstanceParent.cpp b/dom/plugins/ipc/PluginInstanceParent.cpp index abe68b2a4d..fef75243c5 100644 --- a/dom/plugins/ipc/PluginInstanceParent.cpp +++ b/dom/plugins/ipc/PluginInstanceParent.cpp @@ -7,6 +7,7 @@ #include "mozilla/DebugOnly.h" #include // for intptr_t +#include "mozilla/Preferences.h" #include "mozilla/Telemetry.h" #include "PluginInstanceParent.h" #include "BrowserStreamParent.h" @@ -21,6 +22,7 @@ #include "gfxContext.h" #include "gfxPlatform.h" #include "gfxSharedImageSurface.h" +#include "nsNetUtil.h" #include "nsNPAPIPluginInstance.h" #include "nsPluginInstanceOwner.h" #include "nsFocusManager.h" @@ -54,6 +56,10 @@ extern const wchar_t* kFlashFullscreenClass; #include #endif // defined(XP_MACOSX) +// This is the pref used to determine whether to use Shumway on a Flash object +// (when Shumway is enabled). +static const char kShumwayWhitelistPref[] = "shumway.swf.whitelist"; + using namespace mozilla::plugins; using namespace mozilla::layers; using namespace mozilla::gl; @@ -104,6 +110,7 @@ PluginInstanceParent::PluginInstanceParent(PluginModuleParent* parent, , mUseSurrogate(true) , mNPP(npp) , mNPNIface(npniface) + , mIsWhitelistedForShumway(false) , mWindowType(NPWindowTypeWindow) , mDrawingModel(kDefaultDrawingModel) #if defined(OS_WIN) @@ -143,9 +150,38 @@ PluginInstanceParent::~PluginInstanceParent() } bool -PluginInstanceParent::Init() +PluginInstanceParent::InitMetadata(const nsACString& aMimeType, + const nsACString& aSrcAttribute) { - return true; + if (aSrcAttribute.IsEmpty()) { + return false; + } + // Ensure that the src attribute is absolute + nsRefPtr owner = GetOwner(); + if (!owner) { + return false; + } + nsCOMPtr baseUri(owner->GetBaseURI()); + nsresult rv = NS_MakeAbsoluteURI(mSrcAttribute, aSrcAttribute, baseUri); + if (NS_FAILED(rv)) { + return false; + } + // Check the whitelist + nsAutoCString baseUrlSpec; + rv = baseUri->GetSpec(baseUrlSpec); + if (NS_FAILED(rv)) { + return false; + } + auto whitelist = Preferences::GetCString(kShumwayWhitelistPref); + // Empty whitelist is interpreted by CheckWhitelist as "allow everything," + // which is not valid for our use case and should be treated as a failure. + if (whitelist.IsEmpty()) { + return false; + } + rv = nsPluginPlayPreviewInfo::CheckWhitelist(baseUrlSpec, mSrcAttribute, + whitelist, + &mIsWhitelistedForShumway); + return NS_SUCCEEDED(rv); } void diff --git a/dom/plugins/ipc/PluginInstanceParent.h b/dom/plugins/ipc/PluginInstanceParent.h index 6ede5f1fe6..afeb190a7b 100644 --- a/dom/plugins/ipc/PluginInstanceParent.h +++ b/dom/plugins/ipc/PluginInstanceParent.h @@ -68,7 +68,8 @@ public: virtual ~PluginInstanceParent(); - bool Init(); + bool InitMetadata(const nsACString& aMimeType, + const nsACString& aSrcAttribute); NPError Destroy(); virtual void ActorDestroy(ActorDestroyReason why) override; @@ -271,6 +272,24 @@ public: return mUseSurrogate; } + void + GetSrcAttribute(nsACString& aOutput) const + { + aOutput = mSrcAttribute; + } + + /** + * This function tells us whether this plugin instance would have been + * whitelisted for Shumway if Shumway had been enabled. This is being used + * for the purpose of gathering telemetry on Flash hangs that could + * potentially be avoided by using Shumway instead. + */ + bool + IsWhitelistedForShumway() const + { + return mIsWhitelistedForShumway; + } + virtual bool AnswerPluginFocusChange(const bool& gotFocus) override; @@ -323,6 +342,8 @@ private: bool mUseSurrogate; NPP mNPP; const NPNetscapeFuncs* mNPNIface; + nsCString mSrcAttribute; + bool mIsWhitelistedForShumway; NPWindowType mWindowType; int16_t mDrawingModel; nsAutoPtr mNotifySink; diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp index 082fa9e228..57be800f06 100755 --- a/dom/plugins/ipc/PluginModuleParent.cpp +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -33,8 +33,10 @@ #include "prsystem.h" #include "GoannaProfiler.h" #include "nsPluginTags.h" +#include "nsUnicharUtils.h" #ifdef XP_WIN +#include "mozilla/plugins/PluginSurfaceParent.h" #include "mozilla/widget/AudioSession.h" #include "nsWindowsHelpers.h" #include "PluginHangUIParent.h" @@ -502,6 +504,7 @@ PluginModuleParent::PluginModuleParent(bool aIsChrome) , mNPPIface(nullptr) , mPlugin(nullptr) , mTaskFactory(this) + , mIsFlashPlugin(false) , mIsStartingAsync(false) , mNPInitialized(false) , mAsyncNewRv(NS_ERROR_NOT_INITIALIZED) @@ -543,17 +546,21 @@ PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath, uint32 , mPluginId(aPluginId) , mChromeTaskFactory(this) , mHangAnnotationFlags(0) + , mHangAnnotatorMutex("PluginModuleChromeParent::mHangAnnotatorMutex") #ifdef XP_WIN , mPluginCpuUsageOnHang() , mHangUIParent(nullptr) , mHangUIEnabled(true) , mIsTimerReset(true) +#ifdef MOZ_CRASHREPORTER + , mCrashReporterMutex("PluginModuleChromeParent::mCrashReporterMutex") + , mCrashReporter(nullptr) +#endif #endif , mInitOnAsyncConnect(false) , mAsyncInitRv(NS_ERROR_NOT_INITIALIZED) , mAsyncInitError(NPERR_NO_ERROR) , mContentParent(nullptr) - , mIsFlashPlugin(false) { NS_ASSERTION(mSubprocess, "Out of memory!"); sInstantiated = true; @@ -736,6 +743,119 @@ GetProcessCpuUsage(const InfallibleTArray& processHandles, #endif // #ifdef XP_WIN +void +PluginModuleChromeParent::OnEnteredCall() +{ + mozilla::ipc::IProtocol* protocol = GetInvokingProtocol(); + MOZ_ASSERT(protocol); + mozilla::MutexAutoLock lock(mHangAnnotatorMutex); + mProtocolCallStack.AppendElement(protocol); +} + +void +PluginModuleChromeParent::OnExitedCall() +{ + mozilla::MutexAutoLock lock(mHangAnnotatorMutex); + MOZ_ASSERT(!mProtocolCallStack.IsEmpty()); + mProtocolCallStack.RemoveElementAt(mProtocolCallStack.Length() - 1); +} + +void +PluginModuleChromeParent::OnEnteredSyncSend() +{ + mozilla::ipc::IProtocol* protocol = GetInvokingProtocol(); + MOZ_ASSERT(protocol); + mozilla::MutexAutoLock lock(mHangAnnotatorMutex); + mProtocolCallStack.AppendElement(protocol); +} + +void +PluginModuleChromeParent::OnExitedSyncSend() +{ + mozilla::MutexAutoLock lock(mHangAnnotatorMutex); + MOZ_ASSERT(!mProtocolCallStack.IsEmpty()); + mProtocolCallStack.RemoveElementAt(mProtocolCallStack.Length() - 1); +} + +/** + * This function converts the topmost routing id on the call stack (as recorded + * by the MessageChannel) into a pointer to a IProtocol object. + */ +mozilla::ipc::IProtocol* +PluginModuleChromeParent::GetInvokingProtocol() +{ + int32_t routingId = GetIPCChannel()->GetTopmostMessageRoutingId(); + // Nothing being routed. No protocol. Just return nullptr. + if (routingId == MSG_ROUTING_NONE) { + return nullptr; + } + // If routingId is MSG_ROUTING_CONTROL then we're dealing with control + // messages that were initiated by the topmost managing protocol, ie. this. + if (routingId == MSG_ROUTING_CONTROL) { + return this; + } + // Otherwise we can look up the protocol object by the routing id. + mozilla::ipc::IProtocol* protocol = Lookup(routingId); + return protocol; +} + +/** + * This function examines the IProtocol object parameter and converts it into + * the PluginInstanceParent object that is associated with that protocol, if + * any. Since PluginInstanceParent manages subprotocols, this function needs + * to determine whether |aProtocol| is a subprotocol, and if so it needs to + * obtain the protocol's manager. + * + * This function needs to be updated if the subprotocols are modified in + * PPluginInstance.ipdl. + */ +PluginInstanceParent* +PluginModuleChromeParent::GetManagingInstance(mozilla::ipc::IProtocol* aProtocol) +{ + MOZ_ASSERT(aProtocol); + mozilla::ipc::MessageListener* listener = + static_cast(aProtocol); + switch (listener->GetProtocolTypeId()) { + case PPluginInstanceMsgStart: + // In this case, aProtocol is the instance itself. Just cast it. + return static_cast(aProtocol); + case PPluginBackgroundDestroyerMsgStart: { + PPluginBackgroundDestroyerParent* actor = + static_cast(aProtocol); + return static_cast(actor->Manager()); + } + case PPluginScriptableObjectMsgStart: { + PPluginScriptableObjectParent* actor = + static_cast(aProtocol); + return static_cast(actor->Manager()); + } + case PBrowserStreamMsgStart: { + PBrowserStreamParent* actor = + static_cast(aProtocol); + return static_cast(actor->Manager()); + } + case PPluginStreamMsgStart: { + PPluginStreamParent* actor = + static_cast(aProtocol); + return static_cast(actor->Manager()); + } + case PStreamNotifyMsgStart: { + PStreamNotifyParent* actor = + static_cast(aProtocol); + return static_cast(actor->Manager()); + } +#ifdef XP_WIN + case PPluginSurfaceMsgStart: { + PPluginSurfaceParent* actor = + static_cast(aProtocol); + return static_cast(actor->Manager()); + } +#endif + default: + return nullptr; + } +} + void PluginModuleChromeParent::EnteredCxxStack() { @@ -778,6 +898,24 @@ PluginModuleChromeParent::AnnotateHang(mozilla::HangMonitor::HangAnnotations& aA aAnnotations.AddAnnotation(NS_LITERAL_STRING("pluginName"), mPluginName); aAnnotations.AddAnnotation(NS_LITERAL_STRING("pluginVersion"), mPluginVersion); + if (mIsFlashPlugin) { + bool isWhitelistedForShumway = false; + { // Scope for lock + mozilla::MutexAutoLock lock(mHangAnnotatorMutex); + if (!mProtocolCallStack.IsEmpty()) { + mozilla::ipc::IProtocol* topProtocol = + mProtocolCallStack.LastElement(); + PluginInstanceParent* instance = + GetManagingInstance(topProtocol); + if (instance) { + isWhitelistedForShumway = + instance->IsWhitelistedForShumway(); + } + } + } + aAnnotations.AddAnnotation(NS_LITERAL_STRING("pluginIsWhitelistedForShumway"), + isWhitelistedForShumway); + } } } @@ -849,8 +987,7 @@ PluginModuleChromeParent::TerminateChildProcess(MessageLoop* aMsgLoop) } bool -PluginModuleParent::GetPluginDetails(nsACString& aPluginName, - nsACString& aPluginVersion) +PluginModuleParent::GetPluginDetails() { nsRefPtr host = nsPluginHost::GetInst(); if (!host) { @@ -860,8 +997,9 @@ PluginModuleParent::GetPluginDetails(nsACString& aPluginName, if (!pluginTag) { return false; } - aPluginName = pluginTag->mName; - aPluginVersion = pluginTag->mVersion; + mPluginName = pluginTag->mName; + mPluginVersion = pluginTag->mVersion; + mIsFlashPlugin = pluginTag->mIsFlashPlugin; return true; } @@ -1892,7 +2030,7 @@ PluginModuleParent::NPP_New(NPMIMEType pluginType, NPP instance, } if (mPluginName.IsEmpty()) { - GetPluginDetails(mPluginName, mPluginVersion); + GetPluginDetails(); /** mTimeBlocked measures the time that the main thread has been blocked * on plugin module initialization. As implemented, this is the sum of * plugin-container launch + toolhelp32 snapshot + NP_Initialize. @@ -1922,6 +2060,15 @@ PluginModuleParent::NPP_New(NPMIMEType pluginType, NPP instance, return NS_PLUGIN_INIT_PENDING; } +class nsCaseInsensitiveUTF8StringArrayComparator +{ +public: + template + bool Equals(const A& a, const B& b) const { + return a.Equals(b.get(), nsCaseInsensitiveUTF8StringComparator()); + } +}; + nsresult PluginModuleParent::NPP_NewInternal(NPMIMEType pluginType, NPP instance, uint16_t mode, @@ -1929,13 +2076,20 @@ PluginModuleParent::NPP_NewInternal(NPMIMEType pluginType, NPP instance, InfallibleTArray& values, NPSavedData* saved, NPError* error) { - PluginInstanceParent* parentInstance = - new PluginInstanceParent(this, instance, - nsDependentCString(pluginType), mNPNIface); + nsCaseInsensitiveUTF8StringArrayComparator comparator; + NS_NAMED_LITERAL_CSTRING(srcAttributeName, "src"); + auto srcAttributeIndex = names.IndexOf(srcAttributeName, 0, comparator); + nsAutoCString srcAttribute; + if (srcAttributeIndex != names.NoIndex) { + srcAttribute = values[srcAttributeIndex]; + } - if (!parentInstance->Init()) { - delete parentInstance; - return NS_ERROR_FAILURE; + nsDependentCString strPluginType(pluginType); + PluginInstanceParent* parentInstance = + new PluginInstanceParent(this, instance, strPluginType, mNPNIface); + + if (mIsFlashPlugin) { + parentInstance->InitMetadata(strPluginType, srcAttribute); } // Release the surrogate reference that was in pdata diff --git a/dom/plugins/ipc/PluginModuleParent.h b/dom/plugins/ipc/PluginModuleParent.h index 8632abf605..87c424f488 100644 --- a/dom/plugins/ipc/PluginModuleParent.h +++ b/dom/plugins/ipc/PluginModuleParent.h @@ -9,7 +9,7 @@ #include "base/process.h" #include "mozilla/FileUtils.h" -#include "mozilla/HangMonitor.h" +#include "mozilla/HangAnnotations.h" #include "mozilla/PluginLibrary.h" #include "mozilla/plugins/ScopedMethodFactory.h" #include "mozilla/plugins/PluginProcessParent.h" @@ -261,6 +261,7 @@ protected: TimeDuration mTimeBlocked; nsCString mPluginName; nsCString mPluginVersion; + bool mIsFlashPlugin; #ifdef MOZ_X11 // Dup of plugin's X socket, used to scope its resources to this @@ -269,7 +270,7 @@ protected: #endif bool - GetPluginDetails(nsACString& aPluginName, nsACString& aPluginVersion); + GetPluginDetails(); friend class mozilla::plugins::PluginAsyncSurrogate; @@ -358,6 +359,11 @@ class PluginModuleChromeParent void CachedSettingChanged(); + void OnEnteredCall() override; + void OnExitedCall() override; + void OnEnteredSyncSend() override; + void OnExitedSyncSend() override; + private: virtual void EnteredCxxStack() override; @@ -365,6 +371,9 @@ private: void ExitedCxxStack() override; + mozilla::ipc::IProtocol* GetInvokingProtocol(); + PluginInstanceParent* GetManagingInstance(mozilla::ipc::IProtocol* aProtocol); + virtual void AnnotateHang(mozilla::HangMonitor::HangAnnotations& aAnnotations) override; @@ -417,6 +426,8 @@ private: kHangUIDontShow = (1u << 3) }; Atomic mHangAnnotationFlags; + mozilla::Mutex mHangAnnotatorMutex; + InfallibleTArray mProtocolCallStack; #ifdef XP_WIN InfallibleTArray mPluginCpuUsageOnHang; PluginHangUIParent *mHangUIParent; @@ -469,7 +480,6 @@ private: NPError mAsyncInitError; dom::ContentParent* mContentParent; nsCOMPtr mOfflineObserver; - bool mIsFlashPlugin; bool mIsBlocklisted; static bool sInstantiated; }; diff --git a/ipc/glue/MessageChannel.cpp b/ipc/glue/MessageChannel.cpp index 43233cab4d..b337fb33d4 100644 --- a/ipc/glue/MessageChannel.cpp +++ b/ipc/glue/MessageChannel.cpp @@ -193,6 +193,11 @@ public: *name = mMessageName; } + int32_t GetRoutingId() const + { + return mMessageRoutingId; + } + private: const char* mMessageName; int32_t mMessageRoutingId; @@ -1826,6 +1831,17 @@ MessageChannel::DumpInterruptStack(const char* const pfx) const } } +int32_t +MessageChannel::GetTopmostMessageRoutingId() const +{ + MOZ_ASSERT(MessageLoop::current() == mWorkerLoop); + if (mCxxStackFrames.empty()) { + return MSG_ROUTING_NONE; + } + const InterruptFrame& frame = mCxxStackFrames.back(); + return frame.GetRoutingId(); +} + bool ParentProcessIsBlocked() { diff --git a/ipc/glue/MessageChannel.h b/ipc/glue/MessageChannel.h index cc81e435ce..40e690e2dd 100644 --- a/ipc/glue/MessageChannel.h +++ b/ipc/glue/MessageChannel.h @@ -135,6 +135,14 @@ class MessageChannel : HasResultCodes return !mCxxStackFrames.empty(); } + /** + * This function is used by hang annotation code to determine which IPDL + * actor is highest in the call stack at the time of the hang. It should + * be called from the main thread when a sync or intr message is about to + * be sent. + */ + int32_t GetTopmostMessageRoutingId() const; + void FlushPendingInterruptQueue(); // Unsound_IsClosed and Unsound_NumQueuedMessages are safe to call from any diff --git a/js/src/asmjs/AsmJSValidate.cpp b/js/src/asmjs/AsmJSValidate.cpp index 4aeb236cb0..2d6364d784 100644 --- a/js/src/asmjs/AsmJSValidate.cpp +++ b/js/src/asmjs/AsmJSValidate.cpp @@ -5160,6 +5160,11 @@ static bool CheckInternalCall(FunctionCompiler& f, ParseNode* callNode, PropertyName* calleeName, RetType retType, MDefinition** def, Type* type) { + if (!f.canCall()) { + return f.fail(callNode, "call expressions may not be nested inside heap expressions " + "when the module contains a change-heap function"); + } + FunctionCompiler::Call call(f, callNode, retType); if (!CheckCallArgs(f, callNode, CheckIsVarType, &call)) @@ -5205,6 +5210,11 @@ CheckFuncPtrTableAgainstExisting(ModuleCompiler& m, ParseNode* usepn, static bool CheckFuncPtrCall(FunctionCompiler& f, ParseNode* callNode, RetType retType, MDefinition** def, Type* type) { + if (!f.canCall()) { + return f.fail(callNode, "function-pointer call expressions may not be nested inside heap " + "expressions when the module contains a change-heap function"); + } + ParseNode* callee = CallCallee(callNode); ParseNode* tableNode = ElemBase(callee); ParseNode* indexExpr = ElemIndex(callee); @@ -5264,6 +5274,11 @@ static bool CheckFFICall(FunctionCompiler& f, ParseNode* callNode, unsigned ffiIndex, RetType retType, MDefinition** def, Type* type) { + if (!f.canCall()) { + return f.fail(callNode, "FFI call expressions may not be nested inside heap " + "expressions when the module contains a change-heap function"); + } + PropertyName* calleeName = CallCallee(callNode)->name(); if (retType == RetType::Float) @@ -6130,11 +6145,6 @@ CheckCoercedCall(FunctionCompiler& f, ParseNode* call, RetType retType, MDefinit { JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed()); - if (!f.canCall()) { - return f.fail(call, "call expressions may not be nested inside heap expressions when " - "the module contains a change-heap function"); - } - if (IsNumericLiteral(f.m(), call)) { AsmJSNumLit literal = ExtractNumericLiteral(f.m(), call); MDefinition* result = f.constant(literal); diff --git a/js/src/jit-test/tests/asm.js/testResize.js b/js/src/jit-test/tests/asm.js/testResize.js index e30685c12c..9487c76366 100644 --- a/js/src/jit-test/tests/asm.js/testResize.js +++ b/js/src/jit-test/tests/asm.js/testResize.js @@ -147,13 +147,18 @@ assertAsmTypeFail('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'function ch(b2) { if // Tests for no calls in heap index expressions -const SETUP = USE_ASM + IMPORT2 + 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff || len(b2) > 0x80000000) return false; i8=new I8(b2); i32=new I32(b2); b=b2; return true }'; +const CHANGE_FUN = 'function ch(b2) { if(len(b2) & 0xffffff || len(b2) <= 0xffffff || len(b2) > 0x80000000) return false; i8=new I8(b2); i32=new I32(b2); b=b2; return true }'; +const SETUP = USE_ASM + IMPORT2 + 'var imul=glob.Math.imul; var ffi=ffis.ffi;' + CHANGE_FUN; asmCompile('glob', 'ffis', 'b', SETUP + 'function f() { i32[0] } return f'); asmCompile('glob', 'ffis', 'b', SETUP + 'function f() { i32[0] = 0 } return f'); asmCompile('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] } return f'); asmCompile('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] = 0 } return f'); + asmCompile('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[(imul(i,i)|0) >> 2] = 0 } return f'); + asmCompile('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] = (imul(i,i)|0) } return f'); +assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[(ffi()|0) >> 2] } return f'); assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[(g()|0) >> 2] } function g() { return 0 } return f'); +assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[(TBL[i&0]()|0) >> 2] } function g() { return 0 } var TBL=[g]; return f'); assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[(g()|0) >> 2] = 0 } function g() { return 0 } return f'); assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] = g()|0 } function g() { return 0 } return f'); assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i32[(g()|0)>>2] >> 2] } function g() { return 0 } return f'); @@ -162,6 +167,8 @@ assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[((i32[i>>2]|0) + (g()|0)) >> 2] } function g() { return 0 } return f'); assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[((i32[i>>2]|0) + (g()|0)) >> 2] = 0 } function g() { return 0 } return f'); assertAsmTypeFail('glob', 'ffis', 'b', SETUP + 'function f() { var i = 0; i32[i >> 2] = (i32[i>>2]|0) + (g()|0) } function g() { return 0 } return f'); +if (isSimdAvailable() && typeof SIMD !== 'undefined') + asmCompile('glob', 'ffis', 'b', USE_ASM + IMPORT2 + 'var i4 = glob.SIMD.int32x4; var add = i4.add;' + CHANGE_FUN + 'function f(i) { i=i|0; i32[i4(i,1,2,i).x >> 2]; i32[add(i4(0,0,0,0),i4(1,1,1,1)).x >> 2]; } return f'); // Tests for constant heap accesses when change-heap is used diff --git a/layout/base/RestyleTracker.cpp b/layout/base/RestyleTracker.cpp index 6d0413733a..a692aa57ee 100644 --- a/layout/base/RestyleTracker.cpp +++ b/layout/base/RestyleTracker.cpp @@ -83,6 +83,9 @@ CollectLaterSiblings(nsISupports* aElement, struct RestyleEnumerateData : RestyleTracker::Hints { nsRefPtr mElement; +#if defined(MOZ_ENABLE_PROFILER_SPS) && !defined(MOZILLA_XPCOMRT_API) + UniquePtr mBacktrace; +#endif }; struct RestyleCollector { @@ -140,7 +143,9 @@ CollectRestyles(nsISupports* aElement, currentRestyle->mElement = element; currentRestyle->mRestyleHint = aData->mRestyleHint; currentRestyle->mChangeHint = aData->mChangeHint; - +#if defined(MOZ_ENABLE_PROFILER_SPS) && !defined(MOZILLA_XPCOMRT_API) + currentRestyle->mBacktrace = Move(aData->mBacktrace); +#endif #ifdef RESTYLE_LOGGING collector->count++; #endif @@ -305,6 +310,12 @@ RestyleTracker::DoProcessRestyles() continue; } +#if defined(MOZ_ENABLE_PROFILER_SPS) && !defined(MOZILLA_XPCOMRT_API) + Maybe profilerRAII; + if (profiler_feature_active("restyle")) { + profilerRAII.emplace("Paint", "Styles", Move(data->mBacktrace)); + } +#endif ProcessOneRestyle(element, data->mRestyleHint, data->mChangeHint); AddRestyleRootsIfAwaitingRestyle(data->mDescendants); } @@ -340,6 +351,13 @@ RestyleTracker::DoProcessRestyles() FrameTagToString(currentRestyle->mElement).get(), index++, collector.count); LOG_RESTYLE_INDENT(); + +#if defined(MOZ_ENABLE_PROFILER_SPS) && !defined(MOZILLA_XPCOMRT_API) + Maybe profilerRAII; + if (profiler_feature_active("restyle")) { + profilerRAII.emplace("Paint", "Styles", Move(currentRestyle->mBacktrace)); + } +#endif ProcessOneRestyle(currentRestyle->mElement, currentRestyle->mRestyleHint, currentRestyle->mChangeHint); diff --git a/layout/base/RestyleTracker.h b/layout/base/RestyleTracker.h index a58ee1db4f..5faf162283 100644 --- a/layout/base/RestyleTracker.h +++ b/layout/base/RestyleTracker.h @@ -16,6 +16,11 @@ #include "nsContainerFrame.h" #include "mozilla/SplayTree.h" #include "mozilla/RestyleLogging.h" +#include "GoannaProfiler.h" + +#if defined(MOZ_ENABLE_PROFILER_SPS) +#include "ProfilerBacktrace.h" +#endif namespace mozilla { @@ -291,6 +296,9 @@ public: // that we called AddPendingRestyle for and found the element this is // the RestyleData for as its nearest restyle root. nsTArray> mDescendants; +#if defined(MOZ_ENABLE_PROFILER_SPS) + UniquePtr mBacktrace; +#endif }; /** @@ -388,8 +396,13 @@ RestyleTracker::AddPendingRestyleToTable(Element* aElement, } if (!existingData) { - mPendingRestyles.Put(aElement, - new RestyleData(aRestyleHint, aMinChangeHint)); + RestyleData* rd = new RestyleData(aRestyleHint, aMinChangeHint); +#if defined(MOZ_ENABLE_PROFILER_SPS) + if (profiler_feature_active("restyle")) { + rd->mBacktrace.reset(profiler_get_backtrace()); + } +#endif + mPendingRestyles.Put(aElement, rd); return false; } diff --git a/mfbt/Assertions.h b/mfbt/Assertions.h index acdd4a23c5..402c371377 100644 --- a/mfbt/Assertions.h +++ b/mfbt/Assertions.h @@ -141,7 +141,7 @@ MOZ_ReportAssertionFailure(const char* aStr, const char* aFilename, int aLine) aStr, aFilename, aLine); #else fprintf(stderr, "Assertion failure: %s, at %s:%d\n", aStr, aFilename, aLine); -#ifdef MOZ_DUMP_ASSERTION_STACK +#if defined (MOZ_DUMP_ASSERTION_STACK) && !defined(MOZILLA_XPCOMRT_API) nsTraceRefcnt::WalkTheStack(stderr); #endif fflush(stderr); @@ -157,7 +157,7 @@ MOZ_ReportCrash(const char* aStr, const char* aFilename, int aLine) "Hit MOZ_CRASH(%s) at %s:%d\n", aStr, aFilename, aLine); #else fprintf(stderr, "Hit MOZ_CRASH(%s) at %s:%d\n", aStr, aFilename, aLine); -#ifdef MOZ_DUMP_ASSERTION_STACK +#if defined(MOZ_DUMP_ASSERTION_STACK) && !defined(MOZILLA_XPCOMRT_API) nsTraceRefcnt::WalkTheStack(stderr); #endif fflush(stderr); diff --git a/mfbt/RefPtr.h b/mfbt/RefPtr.h index 9fad346cdd..f181144d25 100644 --- a/mfbt/RefPtr.h +++ b/mfbt/RefPtr.h @@ -22,6 +22,7 @@ #endif #if defined(MOZILLA_INTERNAL_API) && \ + !defined(MOZILLA_XPCOMRT_API) && \ (defined(DEBUG) || defined(FORCE_BUILD_REFCNT_LOGGING)) #define MOZ_REFCOUNTED_LEAK_CHECKING #endif diff --git a/mfbt/Types.h b/mfbt/Types.h index 3499877a58..e5c3be856a 100644 --- a/mfbt/Types.h +++ b/mfbt/Types.h @@ -89,7 +89,7 @@ * symbols. We add the weak attribute to the import version of the MFBT API * macros to exploit this. */ -# if defined(MOZ_GLUE_IN_PROGRAM) +# if defined(MOZ_GLUE_IN_PROGRAM) && !defined(MOZILLA_XPCOMRT_API) # define MFBT_API __attribute__((weak)) MOZ_IMPORT_API # define MFBT_DATA __attribute__((weak)) MOZ_IMPORT_DATA # else diff --git a/toolkit/components/passwordmgr/test/unit/test_telemetry.js b/toolkit/components/passwordmgr/test/unit/test_telemetry.js index 8f427a88a3..ddae3531cc 100644 --- a/toolkit/components/passwordmgr/test/unit/test_telemetry.js +++ b/toolkit/components/passwordmgr/test/unit/test_telemetry.js @@ -108,10 +108,10 @@ function testHistogram(histogramId, expectedNonZeroRanges) { * the test data that will be used by the following tests. */ add_task(function test_initialize() { - let oldCanRecord = Services.telemetry.canRecord; - Services.telemetry.canRecord = true; + let oldCanRecord = Services.telemetry.canRecordExtended; + Services.telemetry.canRecordExtended = true; do_register_cleanup(function () { - Services.telemetry.canRecord = oldCanRecord; + Services.telemetry.canRecordExtended = oldCanRecord; }); let uniqueNumber = 1; diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 65e610af81..3c5b4a91b6 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -4338,6 +4338,12 @@ "extended_statistics_ok": true, "description": "Number of histograms with total count low errors" }, + "TELEMETRY_DISCARDED_CONTENT_PINGS_COUNT": { + "alert_emails": ["perf-telemetry-alerts@mozilla.com"], + "expires_in_version": "never", + "kind": "count", + "description": "Count of discarded content payloads." + }, "TELEMETRY_FILES_EVICTED": { "alert_emails": ["perf-telemetry-alerts@mozilla.com", "rvitillo@mozilla.com"], "expires_in_version": "never", diff --git a/toolkit/components/telemetry/Telemetry.cpp b/toolkit/components/telemetry/Telemetry.cpp index acef011a3d..2e0aa70476 100644 --- a/toolkit/components/telemetry/Telemetry.cpp +++ b/toolkit/components/telemetry/Telemetry.cpp @@ -41,6 +41,7 @@ #include "nsTHashtable.h" #include "nsHashKeys.h" #include "nsBaseHashtable.h" +#include "nsClassHashtable.h" #include "nsXULAppAPI.h" #include "nsReadableUtils.h" #include "nsThreadUtils.h" @@ -53,6 +54,7 @@ #include "nsReadableUtils.h" #include "plstr.h" #include "nsAppDirectoryServiceDefs.h" +#include "mozilla/BackgroundHangMonitor.h" #include "mozilla/ProcessedStack.h" #include "mozilla/Mutex.h" #include "mozilla/FileUtils.h" @@ -78,6 +80,7 @@ using base::LinearHistogram; using base::StatisticsRecorder; const char KEYED_HISTOGRAM_NAME_SEPARATOR[] = "#"; +const char SUBSESSION_HISTOGRAM_PREFIX[] = "sub#"; enum reflectStatus { REFLECT_OK, @@ -234,7 +237,7 @@ public: */ struct AnnotationInfo { AnnotationInfo(uint32_t aHangIndex, - UniquePtr aAnnotations) + HangAnnotationsPtr aAnnotations) : mHangIndex(aHangIndex) , mAnnotations(Move(aAnnotations)) {} @@ -250,7 +253,7 @@ public: return *this; } uint32_t mHangIndex; - UniquePtr mAnnotations; + HangAnnotationsPtr mAnnotations; private: // Force move constructor @@ -260,7 +263,7 @@ public: size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; void AddHang(const Telemetry::ProcessedStack& aStack, uint32_t aDuration, int32_t aSystemUptime, int32_t aFirefoxUptime, - UniquePtr aAnnotations); + HangAnnotationsPtr aAnnotations); uint32_t GetDuration(unsigned aIndex) const; int32_t GetSystemUptime(unsigned aIndex) const; int32_t GetFirefoxUptime(unsigned aIndex) const; @@ -289,7 +292,7 @@ HangReports::AddHang(const Telemetry::ProcessedStack& aStack, uint32_t aDuration, int32_t aSystemUptime, int32_t aFirefoxUptime, - UniquePtr aAnnotations) { + HangAnnotationsPtr aAnnotations) { HangInfo info = { aDuration, aSystemUptime, aFirefoxUptime }; mHangInfo.push_back(info); if (aAnnotations) { @@ -639,7 +642,8 @@ class TelemetryImpl final public: void InitMemoryReporter(); - static bool CanRecord(); + static bool CanRecordBase(); + static bool CanRecordExtended(); static already_AddRefed CreateTelemetryInstance(); static void ShutdownTelemetry(); static void RecordSlowStatement(const nsACString &sql, const nsACString &dbName, @@ -685,6 +689,10 @@ private: nsresult GetHistogramByName(const nsACString &name, Histogram **ret); bool ShouldReflectHistogram(Histogram *h); void IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs); + nsresult CreateHistogramSnapshots(JSContext *cx, + JS::MutableHandle ret, + bool subsession, + bool clearSubsession); typedef StatisticsRecorder::Histograms::iterator HistogramIterator; struct AddonHistogramInfo { @@ -710,7 +718,8 @@ private: typedef nsBaseHashtableET CharPtrEntryType; typedef AutoHashtable HistogramMapType; HistogramMapType mHistogramMap; - bool mCanRecord; + bool mCanRecordBase; + bool mCanRecordExtended; static TelemetryImpl *sTelemetry; AutoHashtable mPrivateSQL; AutoHashtable mSanitizedSQL; @@ -757,18 +766,22 @@ public: KeyedHistogram(const nsACString &name, const nsACString &expiration, uint32_t histogramType, uint32_t min, uint32_t max, uint32_t bucketCount); - nsresult GetHistogram(const nsCString& name, Histogram** histogram); - Histogram* GetHistogram(const nsCString& name); + nsresult GetHistogram(const nsCString& name, Histogram** histogram, bool subsession); + Histogram* GetHistogram(const nsCString& name, bool subsession); uint32_t GetHistogramType() const { return mHistogramType; } nsresult GetDataset(uint32_t* dataset) const; nsresult GetJSKeys(JSContext* cx, JS::CallArgs& args); - nsresult GetJSSnapshot(JSContext* cx, JS::Handle obj); - void Clear(); + nsresult GetJSSnapshot(JSContext* cx, JS::Handle obj, + bool subsession, bool clearSubsession); + + nsresult Add(const nsCString& key, uint32_t aSample); + void Clear(bool subsession); private: typedef nsBaseHashtableET KeyedHistogramEntry; typedef AutoHashtable KeyedHistogramMapType; KeyedHistogramMapType mHistogramMap; + KeyedHistogramMapType mSubsessionMap; struct ReflectKeysArgs { JSContext* jsContext; @@ -971,6 +984,89 @@ GetHistogramByEnumId(Telemetry::ID id, Histogram **ret) return NS_OK; } +/** + * This clones a histogram |existing| with the id |existingId| to a + * new histogram with the name |newName|. + * For simplicity this is limited to registered histograms. + */ +Histogram* +CloneHistogram(const nsACString& newName, Telemetry::ID existingId, + Histogram& existing) +{ + const TelemetryHistogram &info = gHistograms[existingId]; + Histogram *clone = nullptr; + nsresult rv; + + rv = HistogramGet(PromiseFlatCString(newName).get(), info.expiration(), + info.histogramType, existing.declared_min(), + existing.declared_max(), existing.bucket_count(), + true, &clone); + if (NS_FAILED(rv)) { + return nullptr; + } + + Histogram::SampleSet ss; + existing.SnapshotSample(&ss); + clone->AddSampleSet(ss); + + return clone; +} + +/** + * This clones a histogram with the id |existingId| to a new histogram + * with the name |newName|. + * For simplicity this is limited to registered histograms. + */ +Histogram* +CloneHistogram(const nsACString& newName, Telemetry::ID existingId) +{ + Histogram *existing = nullptr; + nsresult rv = GetHistogramByEnumId(existingId, &existing); + if (NS_FAILED(rv)) { + return nullptr; + } + + return CloneHistogram(newName, existingId, *existing); +} + +Histogram* +GetSubsessionHistogram(Histogram& existing) +{ + Telemetry::ID id; + nsresult rv = TelemetryImpl::GetHistogramEnumId(existing.histogram_name().c_str(), &id); + if (NS_FAILED(rv) || gHistograms[id].keyed) { + return nullptr; + } + + static Histogram* subsession[Telemetry::HistogramCount] = {}; + if (subsession[id]) { + return subsession[id]; + } + + NS_NAMED_LITERAL_CSTRING(prefix, SUBSESSION_HISTOGRAM_PREFIX); + nsDependentCString existingName(gHistograms[id].id()); + if (StringBeginsWith(existingName, prefix)) { + return nullptr; + } + + nsCString subsessionName(prefix); + subsessionName.Append(existingName); + + subsession[id] = CloneHistogram(subsessionName, id, existing); + return subsession[id]; +} + +nsresult +HistogramAdd(Histogram& histogram, int32_t value) +{ + histogram.Add(value); + if (Histogram* subsession = GetSubsessionHistogram(histogram)) { + subsession->Add(value); + } + + return NS_OK; +} + bool FillRanges(JSContext *cx, JS::Handle array, Histogram *h) { @@ -1069,6 +1165,7 @@ JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp) } Histogram *h = static_cast(JS_GetPrivate(obj)); + MOZ_ASSERT(h); Histogram::ClassType type = h->histogram_type(); int32_t value = 1; @@ -1089,8 +1186,8 @@ JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp) } } - if (TelemetryImpl::CanRecord()) { - h->Add(value); + if (TelemetryImpl::CanRecordExtended()) { + HistogramAdd(*h, value); } return true; @@ -1132,8 +1229,28 @@ JSHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp) return false; } + bool onlySubsession = false; + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (args.length() >= 1) { + if (!args[0].isBoolean()) { + JS_ReportError(cx, "Not a boolean"); + return false; + } + + onlySubsession = JS::ToBoolean(args[0]); + } + Histogram *h = static_cast(JS_GetPrivate(obj)); - h->Clear(); + MOZ_ASSERT(h); + if(!onlySubsession) { + h->Clear(); + } + + if (Histogram* subsession = GetSubsessionHistogram(*h)) { + subsession->Clear(); + } + return true; } @@ -1223,17 +1340,7 @@ JSKeyedHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp) } } - Histogram* h = nullptr; - nsresult rv = keyed->GetHistogram(NS_ConvertUTF16toUTF8(key), &h); - if (NS_FAILED(rv)) { - JS_ReportError(cx, "Failed to get histogram"); - return false; - } - - if (TelemetryImpl::CanRecord()) { - h->Add(value); - } - + keyed->Add(NS_ConvertUTF16toUTF8(key), value); return true; } @@ -1255,7 +1362,8 @@ JSKeyedHistogram_Keys(JSContext *cx, unsigned argc, JS::Value *vp) } bool -JSKeyedHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp) +KeyedHistogram_SnapshotImpl(JSContext *cx, unsigned argc, JS::Value *vp, + bool subsession, bool clearSubsession) { JSObject *obj = JS_THIS_OBJECT(cx, vp); if (!obj) { @@ -1276,7 +1384,7 @@ JSKeyedHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp) return false; } - if (!NS_SUCCEEDED(keyed->GetJSSnapshot(cx, snapshot))) { + if (!NS_SUCCEEDED(keyed->GetJSSnapshot(cx, snapshot, subsession, clearSubsession))) { JS_ReportError(cx, "Failed to reflect keyed histograms"); return false; } @@ -1292,7 +1400,7 @@ JSKeyedHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp) } Histogram* h = nullptr; - nsresult rv = keyed->GetHistogram(NS_ConvertUTF16toUTF8(key), &h); + nsresult rv = keyed->GetHistogram(NS_ConvertUTF16toUTF8(key), &h, subsession); if (NS_FAILED(rv)) { JS_ReportError(cx, "Failed to get histogram"); return false; @@ -1317,6 +1425,29 @@ JSKeyedHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp) } } +bool +JSKeyedHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp) +{ + return KeyedHistogram_SnapshotImpl(cx, argc, vp, false, false); +} + +bool +JSKeyedHistogram_SubsessionSnapshot(JSContext *cx, unsigned argc, JS::Value *vp) +{ + return KeyedHistogram_SnapshotImpl(cx, argc, vp, true, false); +} + +bool +JSKeyedHistogram_SnapshotSubsessionAndClear(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (args.length() != 0) { + JS_ReportError(cx, "No key arguments supported for snapshotSubsessionAndClear"); + } + + return KeyedHistogram_SnapshotImpl(cx, argc, vp, true, true); +} + bool JSKeyedHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp) { @@ -1330,7 +1461,19 @@ JSKeyedHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp) return false; } - keyed->Clear(); + bool onlySubsession = false; + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (args.length() >= 1) { + if (!(args[0].isNumber() || args[0].isBoolean())) { + JS_ReportError(cx, "Not a boolean"); + return false; + } + + onlySubsession = JS::ToBoolean(args[0]); + } + + keyed->Clear(onlySubsession); return true; } @@ -1371,6 +1514,8 @@ WrapAndReturnKeyedHistogram(KeyedHistogram *h, JSContext *cx, JS::MutableHandle< return NS_ERROR_FAILURE; if (!(JS_DefineFunction(cx, obj, "add", JSKeyedHistogram_Add, 2, 0) && JS_DefineFunction(cx, obj, "snapshot", JSKeyedHistogram_Snapshot, 1, 0) + && JS_DefineFunction(cx, obj, "subsessionSnapshot", JSKeyedHistogram_SubsessionSnapshot, 1, 0) + && JS_DefineFunction(cx, obj, "snapshotSubsessionAndClear", JSKeyedHistogram_SnapshotSubsessionAndClear, 0, 0) && JS_DefineFunction(cx, obj, "keys", JSKeyedHistogram_Keys, 0, 0) && JS_DefineFunction(cx, obj, "clear", JSKeyedHistogram_Clear, 0, 0) && JS_DefineFunction(cx, obj, "dataset", JSKeyedHistogram_Dataset, 0, 0))) { @@ -1568,7 +1713,7 @@ TelemetryImpl::AsyncFetchTelemetryData(nsIFetchTelemetryDataCallback *aCallback) // We make this check so that GetShutdownTimeFileName() doesn't get // called; calling that function without telemetry enabled violates // assumptions that the write-the-shutdown-timestamp machinery makes. - if (!Telemetry::CanRecord()) { + if (!Telemetry::CanRecordExtended()) { mCachedTelemetryData = true; aCallback->Complete(); return NS_OK; @@ -1622,7 +1767,10 @@ TelemetryImpl::AsyncFetchTelemetryData(nsIFetchTelemetryDataCallback *aCallback) TelemetryImpl::TelemetryImpl(): mHistogramMap(Telemetry::HistogramCount), -mCanRecord(XRE_GetProcessType() == GoannaProcessType_Default), +mCanRecordBase(XRE_GetProcessType() == GoannaProcessType_Default || + XRE_GetProcessType() == GoannaProcessType_Content), +mCanRecordExtended(XRE_GetProcessType() == GoannaProcessType_Default || + XRE_GetProcessType() == GoannaProcessType_Content), mHashMutex("Telemetry::mHashMutex"), mHangReportsMutex("Telemetry::mHangReportsMutex"), mCachedTelemetryData(false), @@ -1658,6 +1806,7 @@ mFailedLockCount(0) mTrackedDBs.MarkImmutable(); #endif + // Create registered keyed histograms for (size_t i = 0; i < ArrayLength(gHistograms); ++i) { const TelemetryHistogram& h = gHistograms[i]; if (!h.keyed) { @@ -1834,25 +1983,12 @@ TelemetryImpl::HistogramFrom(const nsACString &name, const nsACString &existing_ if (NS_FAILED(rv)) { return rv; } - const TelemetryHistogram &p = gHistograms[id]; - Histogram *existing; - rv = GetHistogramByEnumId(id, &existing); - if (NS_FAILED(rv)) { - return rv; + Histogram* clone = CloneHistogram(name, id); + if (!clone) { + return NS_ERROR_FAILURE; } - Histogram *clone; - rv = HistogramGet(PromiseFlatCString(name).get(), p.expiration(), - p.histogramType, existing->declared_min(), - existing->declared_max(), existing->bucket_count(), - true, &clone); - if (NS_FAILED(rv)) - return rv; - - Histogram::SampleSet ss; - existing->SnapshotSample(&ss); - clone->AddSampleSet(ss); return WrapAndReturnHistogram(clone, cx, ret); } @@ -2035,8 +2171,11 @@ TelemetryImpl::UnregisterAddonHistograms(const nsACString &id) return NS_OK; } -NS_IMETHODIMP -TelemetryImpl::GetHistogramSnapshots(JSContext *cx, JS::MutableHandle ret) +nsresult +TelemetryImpl::CreateHistogramSnapshots(JSContext *cx, + JS::MutableHandle ret, + bool subsession, + bool clearSubsession) { JS::Rooted root_obj(cx, JS_NewPlainObject(cx)); if (!root_obj) @@ -2077,6 +2216,14 @@ TelemetryImpl::GetHistogramSnapshots(JSContext *cx, JS::MutableHandle continue; } + Histogram* original = h; + if (subsession) { + h = GetSubsessionHistogram(*h); + if (!h) { + continue; + } + } + hobj = JS_NewPlainObject(cx); if (!hobj) { return NS_ERROR_FAILURE; @@ -2090,15 +2237,33 @@ TelemetryImpl::GetHistogramSnapshots(JSContext *cx, JS::MutableHandle case REFLECT_FAILURE: return NS_ERROR_FAILURE; case REFLECT_OK: - if (!JS_DefineProperty(cx, root_obj, h->histogram_name().c_str(), hobj, - JSPROP_ENUMERATE)) { + if (!JS_DefineProperty(cx, root_obj, original->histogram_name().c_str(), + hobj, JSPROP_ENUMERATE)) { return NS_ERROR_FAILURE; } } + + if (subsession && clearSubsession) { + h->Clear(); + } } return NS_OK; } +NS_IMETHODIMP +TelemetryImpl::GetHistogramSnapshots(JSContext *cx, JS::MutableHandle ret) +{ + return CreateHistogramSnapshots(cx, ret, false, false); +} + +NS_IMETHODIMP +TelemetryImpl::SnapshotSubsessionHistograms(bool clearSubsession, + JSContext *cx, + JS::MutableHandle ret) +{ + return CreateHistogramSnapshots(cx, ret, true, clearSubsession); +} + bool TelemetryImpl::CreateHistogramForAddon(const nsACString &name, AddonHistogramInfo &info) @@ -2204,7 +2369,7 @@ TelemetryImpl::KeyedHistogramsReflector(const nsACString& key, nsAutoPtrGetJSSnapshot(cx, snapshot))) { + if (!NS_SUCCEEDED(entry->GetJSSnapshot(cx, snapshot, false, false))) { return PL_DHASH_STOP; } @@ -2357,9 +2522,9 @@ TelemetryImpl::GetChromeHangs(JSContext *cx, JS::MutableHandle ret) if (!jsAnnotation) { return NS_ERROR_FAILURE; } - nsAutoPtr annotationsEnum; - if (!annotationInfo[iterIndex].mAnnotations->GetEnumerator( - annotationsEnum.StartAssignment())) { + UniquePtr annotationsEnum = + annotationInfo[iterIndex].mAnnotations->GetEnumerator(); + if (!annotationsEnum) { return NS_ERROR_FAILURE; } nsAutoString key; @@ -2722,24 +2887,56 @@ TelemetryImpl::GetKeyedHistogramById(const nsACString &name) } NS_IMETHODIMP -TelemetryImpl::GetCanRecord(bool *ret) { - *ret = mCanRecord; +TelemetryImpl::GetCanRecordBase(bool *ret) { + *ret = mCanRecordBase; return NS_OK; } NS_IMETHODIMP -TelemetryImpl::SetCanRecord(bool canRecord) { - mCanRecord = !!canRecord; +TelemetryImpl::SetCanRecordBase(bool canRecord) { + mCanRecordBase = canRecord; return NS_OK; } +/** + * Indicates if Telemetry can record base data (FHR data). This is true if the + * FHR data reporting service or self-support are enabled. + * + * In the unlikely event that adding a new base probe is needed, please check the data + * collection wiki at https://wiki.mozilla.org/Firefox/Data_Collection and talk to the + * Telemetry team. + */ bool -TelemetryImpl::CanRecord() { - return !sTelemetry || sTelemetry->mCanRecord; +TelemetryImpl::CanRecordBase() { + return !sTelemetry || sTelemetry->mCanRecordBase; } NS_IMETHODIMP -TelemetryImpl::GetCanSend(bool *ret) { +TelemetryImpl::GetCanRecordExtended(bool *ret) { + *ret = mCanRecordExtended; + return NS_OK; +} + +NS_IMETHODIMP +TelemetryImpl::SetCanRecordExtended(bool canRecord) { + mCanRecordExtended = canRecord; + return NS_OK; +} + +/** + * Indicates if Telemetry is allowed to record extended data. Returns false if the user + * hasn't opted into "extended Telemetry" on the Release channel, when the user has + * explicitly opted out of Telemetry on Nightly/Aurora/Beta or if manually set to false + * during tests. + * If the returned value is false, gathering of extended telemetry statistics is disabled. + */ +bool +TelemetryImpl::CanRecordExtended() { + return !sTelemetry || sTelemetry->mCanRecordExtended; +} + +NS_IMETHODIMP +TelemetryImpl::GetIsOfficialTelemetry(bool *ret) { #if defined(MOZILLA_OFFICIAL) && defined(MOZ_TELEMETRY_REPORTING) *ret = true; #else @@ -2934,7 +3131,7 @@ TelemetryImpl::RecordSlowStatement(const nsACString &sql, const nsACString &dbName, uint32_t delay) { - if (!sTelemetry || !sTelemetry->mCanRecord) + if (!sTelemetry || !sTelemetry->mCanRecordExtended) return; bool isFirefoxDB = sTelemetry->mTrackedDBs.Contains(dbName); @@ -3069,7 +3266,7 @@ RecordShutdownStartTimeStamp() { recorded = true; #endif - if (!Telemetry::CanRecord()) + if (!Telemetry::CanRecordExtended()) return; gRecordedShutdownStartTime = TimeStamp::Now(); @@ -3120,13 +3317,13 @@ Accumulate(ID aHistogram, uint32_t aSample) { return; /* - if (!TelemetryImpl::CanRecord()) { + if (!TelemetryImpl::CanRecordExtended()) { return; } Histogram *h; nsresult rv = GetHistogramByEnumId(aHistogram, &h); if (NS_SUCCEEDED(rv)) - h->Add(aSample); + HistogramAdd(*h, aSample); */ } @@ -3135,7 +3332,7 @@ Accumulate(ID aID, const nsCString& aKey, uint32_t aSample) { return; /* - if (!TelemetryImpl::CanRecord()) { + if (!TelemetryImpl::CanRecordExtended()) { return; } @@ -3143,10 +3340,7 @@ return; KeyedHistogram* keyed = TelemetryImpl::GetKeyedHistogramById(nsDependentCString(th.id())); MOZ_ASSERT(keyed); - Histogram* histogram = keyed->GetHistogram(aKey); - if (histogram) { - histogram->Add(aSample); - } + keyed->Add(aKey, aSample); */ } @@ -3155,7 +3349,7 @@ Accumulate(const char* name, uint32_t sample) { return; /* - if (!TelemetryImpl::CanRecord()) { + if (!TelemetryImpl::CanRecordExtended()) { return; } ID id; @@ -3167,7 +3361,7 @@ return; Histogram *h; rv = GetHistogramByEnumId(id, &h); if (NS_SUCCEEDED(rv)) { - h->Add(sample); + HistogramAdd(*h, sample); } */ } @@ -3183,9 +3377,15 @@ return; } bool -CanRecord() +CanRecordBase() { - return false; // TelemetryImpl::CanRecord(); + return false; // TelemetryImpl::CanRecordBase(); +} + +bool +CanRecordExtended() +{ + return false; // TelemetryImpl::CanRecordExtended(); } base::Histogram* @@ -3388,6 +3588,7 @@ KeyedHistogram::KeyedHistogram(const nsACString &name, const nsACString &expirat uint32_t histogramType, uint32_t min, uint32_t max, uint32_t bucketCount) : mHistogramMap() + , mSubsessionMap() , mName(name) , mExpiration(expiration) , mHistogramType(histogramType) @@ -3398,15 +3599,21 @@ KeyedHistogram::KeyedHistogram(const nsACString &name, const nsACString &expirat } nsresult -KeyedHistogram::GetHistogram(const nsCString& key, Histogram** histogram) +KeyedHistogram::GetHistogram(const nsCString& key, Histogram** histogram, + bool subsession) { - KeyedHistogramEntry* entry = mHistogramMap.GetEntry(key); + KeyedHistogramMapType& map = subsession ? mSubsessionMap : mHistogramMap; + KeyedHistogramEntry* entry = map.GetEntry(key); if (entry) { *histogram = entry->mData; return NS_OK; } - nsCString histogramName = mName; + nsCString histogramName; + if (subsession) { + histogramName.Append(SUBSESSION_HISTOGRAM_PREFIX); + } + histogramName.Append(mName); histogramName.Append(KEYED_HISTOGRAM_NAME_SEPARATOR); histogramName.Append(key); @@ -3422,7 +3629,7 @@ KeyedHistogram::GetHistogram(const nsCString& key, Histogram** histogram) h->SetFlags(Histogram::kExtendedStatisticsFlag); *histogram = h; - entry = mHistogramMap.PutEntry(key); + entry = map.PutEntry(key); if (MOZ_UNLIKELY(!entry)) { return NS_ERROR_OUT_OF_MEMORY; } @@ -3432,10 +3639,10 @@ KeyedHistogram::GetHistogram(const nsCString& key, Histogram** histogram) } Histogram* -KeyedHistogram::GetHistogram(const nsCString& key) +KeyedHistogram::GetHistogram(const nsCString& key, bool subsession) { Histogram* h = nullptr; - if (NS_FAILED(GetHistogram(key, &h))) { + if (NS_FAILED(GetHistogram(key, &h, subsession))) { return nullptr; } return h; @@ -3464,9 +3671,34 @@ KeyedHistogram::ClearHistogramEnumerator(KeyedHistogramEntry* entry, void*) return PL_DHASH_NEXT; } -void -KeyedHistogram::Clear() +nsresult +KeyedHistogram::Add(const nsCString& key, uint32_t sample) { + if (!TelemetryImpl::CanRecordExtended()) { + return NS_OK; + } + + Histogram* histogram = GetHistogram(key, false); + Histogram* subsession = GetHistogram(key, true); + MOZ_ASSERT(histogram && subsession); + if (!histogram || !subsession) { + return NS_ERROR_FAILURE; + } + + histogram->Add(sample); + subsession->Add(sample); + return NS_OK; +} + +void +KeyedHistogram::Clear(bool onlySubsession) +{ + mSubsessionMap.EnumerateEntries(&KeyedHistogram::ClearHistogramEnumerator, nullptr); + mSubsessionMap.Clear(); + if (onlySubsession) { + return; + } + mHistogramMap.EnumerateEntries(&KeyedHistogram::ClearHistogramEnumerator, nullptr); mHistogramMap.Clear(); } @@ -3532,11 +3764,17 @@ KeyedHistogram::ReflectKeyedHistogram(KeyedHistogramEntry* entry, JSContext* cx, } nsresult -KeyedHistogram::GetJSSnapshot(JSContext* cx, JS::Handle obj) +KeyedHistogram::GetJSSnapshot(JSContext* cx, JS::Handle obj, + bool subsession, bool clearSubsession) { - if (!mHistogramMap.ReflectIntoJS(&KeyedHistogram::ReflectKeyedHistogram, cx, obj)) { + KeyedHistogramMapType& map = subsession ? mSubsessionMap : mHistogramMap; + if (!map.ReflectIntoJS(&KeyedHistogram::ReflectKeyedHistogram, cx, obj)) { return NS_ERROR_FAILURE; } + if (subsession && clearSubsession) { + Clear(true); + } + return NS_OK; } diff --git a/toolkit/components/telemetry/Telemetry.h b/toolkit/components/telemetry/Telemetry.h index fb45de3a25..1671561a14 100644 --- a/toolkit/components/telemetry/Telemetry.h +++ b/toolkit/components/telemetry/Telemetry.h @@ -174,11 +174,15 @@ private: }; /** - * Indicates whether Telemetry recording is turned on. This is intended - * to guard calls to Accumulate when the statistic being recorded is - * expensive to compute. + * Indicates whether Telemetry base data recording is turned on. Added for future uses. */ -bool CanRecord(); +bool CanRecordBase(); + +/** + * Indicates whether Telemetry extended data recording is turned on. This is intended + * to guard calls to Accumulate when the statistic being recorded is expensive to compute. + */ +bool CanRecordExtended(); /** * Records slow SQL statements for Telemetry reporting. diff --git a/toolkit/components/telemetry/TelemetryEnvironment.jsm b/toolkit/components/telemetry/TelemetryEnvironment.jsm new file mode 100644 index 0000000000..3fdd919fba --- /dev/null +++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm @@ -0,0 +1,143 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +this.EXPORTED_SYMBOLS = [ + "TelemetryEnvironment", +]; + +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); +Cu.import("resource://gre/modules/Task.jsm"); +Cu.import("resource://gre/modules/Log.jsm"); + +const LOGGER_NAME = "Toolkit.Telemetry"; + +this.TelemetryEnvironment = { + _shutdown: true, + + // A map of (sync) listeners that will be called on environment changes. + _changeListeners: new Map(), + // Async task for collecting the environment data. + _collectTask: null, + _doNotify: false, + + /** + * Initialize TelemetryEnvironment. + */ + init: function () { + if (!this._shutdown) { + this._log.error("init - Already initialized"); + return; + } + + this._configureLog(); + this._log.trace("init"); + this._shutdown = false; + }, + + /** + * Shutdown TelemetryEnvironment. + * @return Promise<> that is resolved when shutdown is finished. + */ + shutdown: Task.async(function* () { + if (this._shutdown) { + if (this._log) { + this._log.error("shutdown - Already shut down"); + } else { + Cu.reportError("TelemetryEnvironment.shutdown - Already shut down"); + } + return; + } + + this._log.trace("shutdown"); + this._shutdown = true; + this._changeListeners.clear(); + yield this._collectTask; + }), + + _configureLog: function () { + if (this._log) { + return; + } + this._log = Log.repository.getLoggerWithMessagePrefix( + LOGGER_NAME, "TelemetryEnvironment::"); + }, + + /** + * Register a listener for environment changes. + * It's fine to call this on an unitialized TelemetryEnvironment. + * @param name The name of the listener - good for debugging purposes. + * @param listener A JS callback function. + */ + registerChangeListener: function (name, listener) { + this._configureLog(); + this._log.trace("registerChangeListener for " + name); + if (this._shutdown) { + this._log.warn("registerChangeListener - already shutdown") + return; + } + this._changeListeners.set(name, listener); + }, + + /** + * Unregister from listening to environment changes. + * It's fine to call this on an unitialized TelemetryEnvironment. + * @param name The name of the listener to remove. + */ + unregisterChangeListener: function (name) { + this._configureLog(); + this._log.trace("unregisterChangeListener for " + name); + if (this._shutdown) { + this._log.warn("registerChangeListener - already shutdown") + return; + } + this._changeListeners.delete(name); + }, + + /** + * Get the environment data in object form. + * @return Promise Resolved with the data on success, otherwise rejected. + */ + getEnvironmentData: function() { + if (this._shutdown) { + this._log.error("getEnvironmentData - Already shut down"); + return Promise.reject("Already shutdown"); + } + + this._log.trace("getEnvironmentData"); + if (this._collectTask) { + return this._collectTask; + } + + this._collectTask = this._doGetEnvironmentData(); + let clear = () => this._collectTask = null; + this._collectTask.then(clear, clear); + return this._collectTask; + }, + + _doGetEnvironmentData: Task.async(function* () { + this._log.trace("getEnvironmentData"); + return {}; + }), + + _onEnvironmentChange: function (what) { + this._log.trace("_onEnvironmentChange for " + what); + if (this._shutdown) { + this._log.trace("_onEnvironmentChange - Already shut down."); + return; + } + + for (let [name, listener] of this._changeListeners) { + try { + this._log.debug("_onEnvironmentChange - calling " + name); + listener(); + } catch (e) { + this._log.warning("_onEnvironmentChange - listener " + name + " caught error", e); + } + } + }, +}; diff --git a/toolkit/components/telemetry/TelemetryFile.jsm b/toolkit/components/telemetry/TelemetryFile.jsm index 2c17000fc3..15d51c869c 100644 --- a/toolkit/components/telemetry/TelemetryFile.jsm +++ b/toolkit/components/telemetry/TelemetryFile.jsm @@ -244,13 +244,9 @@ this.TelemetryFile = { * * @return {iterator} */ - popPendingPings: function*(reason) { + popPendingPings: function*() { while (pendingPings.length > 0) { let data = pendingPings.pop(); - // Send persisted pings to the test URL too. - if (reason == "test-ping") { - data.reason = reason; - } yield data; } }, @@ -263,7 +259,7 @@ this.TelemetryFile = { ///// Utility functions function pingFilePath(ping) { - return OS.Path.join(TelemetryFile.pingDirectoryPath, ping.slug); + return OS.Path.join(TelemetryFile.pingDirectoryPath, ping.id); } function getPingDirectory() { diff --git a/toolkit/components/telemetry/TelemetryPing.jsm b/toolkit/components/telemetry/TelemetryPing.jsm index 85eb55d8a6..77b9668aab 100644 --- a/toolkit/components/telemetry/TelemetryPing.jsm +++ b/toolkit/components/telemetry/TelemetryPing.jsm @@ -30,10 +30,14 @@ const PREF_LOG_DUMP = PREF_BRANCH_LOG + "dump"; const PREF_CACHED_CLIENTID = PREF_BRANCH + "cachedClientID" const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled"; +const PING_FORMAT_VERSION = 2; + // Delay before intializing telemetry (ms) const TELEMETRY_DELAY = 60000; // Delay before initializing telemetry if we're testing (ms) const TELEMETRY_TEST_DELAY = 100; +// The number of days to keep pings serialised on the disk in case of failures. +const DEFAULT_RETENTION_DAYS = 14; XPCOMUtils.defineLazyServiceGetter(this, "Telemetry", "@mozilla.org/base/telemetry;1", @@ -46,6 +50,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "TelemetryLog", "resource://gre/modules/TelemetryLog.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "ThirdPartyCookieProbe", "resource://gre/modules/ThirdPartyCookieProbe.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment", + "resource://gre/modules/TelemetryEnvironment.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel", + "resource://gre/modules/UpdateChannel.jsm"); /** * Setup Telemetry logging. This function also gets called when loggin related @@ -108,6 +116,14 @@ this.TelemetryPing = Object.freeze({ Impl._clientID = null; return this.setup(); }, + + + /** + * Used only for testing purposes. + */ + shutdown: function() { + return Impl.shutdown(true); + }, /** * Used only for testing purposes. */ @@ -131,9 +147,105 @@ this.TelemetryPing = Object.freeze({ /** * Send payloads to the server. + * + * @param {String} aType The type of the ping. + * @param {Object} aPayload The actual data payload for the ping. + * @param {Object} [aOptions] Options object. + * @param {Number} [aOptions.retentionDays=14] The number of days to keep the ping on disk + * if sending fails. + * @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client + * id, false otherwise. + * @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the + * environment data. + * @returns {Promise} A promise that resolves when the ping is sent. */ - send: function(aReason, aPingPayload) { - return Impl.send(aReason, aPingPayload); + send: function(aType, aPayload, aOptions = {}) { + let options = aOptions; + options.retentionDays = aOptions.retentionDays || DEFAULT_RETENTION_DAYS; + options.addClientId = aOptions.addClientId || false; + options.addEnvironment = aOptions.addEnvironment || false; + + return Impl.send(aType, aPayload, options); + }, + + /** + * Add the ping to the pending ping list and save all pending pings. + * + * @param {String} aType The type of the ping. + * @param {Object} aPayload The actual data payload for the ping. + * @param {Object} [aOptions] Options object. + * @param {Number} [aOptions.retentionDays=14] The number of days to keep the ping on disk + * if sending fails. + * @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client + * id, false otherwise. + * @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the + * environment data. + * @returns {Promise} A promise that resolves when the pings are saved. + */ + savePendingPings: function(aType, aPayload, aOptions = {}) { + let options = aOptions; + options.retentionDays = aOptions.retentionDays || DEFAULT_RETENTION_DAYS; + options.addClientId = aOptions.addClientId || false; + options.addEnvironment = aOptions.addEnvironment || false; + + return Impl.savePendingPings(aType, aPayload, options); + }, + + /** + * Save a ping to disk. + * + * @param {String} aType The type of the ping. + * @param {Object} aPayload The actual data payload for the ping. + * @param {Object} [aOptions] Options object. + * @param {Number} [aOptions.retentionDays=14] The number of days to keep the ping on disk + * if sending fails. + * @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client + * id, false otherwise. + * @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the + * environment data. + * @param {Boolean} [aOptions.overwrite=false] true overwrites a ping with the same name, + * if found. + * + * @returns {Promise} A promise that resolves when the ping is saved to disk. + */ + savePing: function(aType, aPayload, aOptions = {}) { + let options = aOptions; + options.retentionDays = aOptions.retentionDays || DEFAULT_RETENTION_DAYS; + options.addClientId = aOptions.addClientId || false; + options.addEnvironment = aOptions.addEnvironment || false; + options.overwrite = aOptions.overwrite || false; + + return Impl.savePing(aType, aPayload, options); + }, + + /** + * Only used for testing. Saves a ping to disk and return the ping id once done. + * + * @param {String} aType The type of the ping. + * @param {Object} aPayload The actual data payload for the ping. + * @param {Object} [aOptions] Options object. + * @param {Number} [aOptions.retentionDays=14] The number of days to keep the ping on disk + * if sending fails. + * @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client + * id, false otherwise. + * @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the + * environment data. + * @param {Boolean} [aOptions.overwrite=false] true overwrites a ping with the same name, + * if found. + * @param {String} [aOptions.filePath] The path to save the ping to. Will save to default + * ping location if not provided. + * + * @returns {Promise} A promise that resolves with the ping id when the ping is + * saved to disk. + */ + testSavePingToFile: function(aType, aPayload, aOptions = {}) { + let options = aOptions; + options.retentionDays = aOptions.retentionDays || DEFAULT_RETENTION_DAYS; + options.addClientId = aOptions.addClientId || false; + options.addEnvironment = aOptions.addEnvironment || false; + options.overwrite = aOptions.overwrite || false; + + return Impl.testSavePingToFile(aType, aPayload, options); }, /** @@ -144,23 +256,109 @@ this.TelemetryPing = Object.freeze({ get clientID() { return Impl.clientID; }, + + /** + * The AsyncShutdown.Barrier to synchronize with TelemetryPing shutdown. + */ + get shutdown() { + return Impl._shutdownBarrier.client; + }, }); let Impl = { _initialized: false, + _initStarted: false, // Whether we started setting up TelemetryPing. _log: null, _prevValues: {}, // The previous build ID, if this is the first run with a new build. // Undefined if this is not the first run, or the previous build ID is unknown. _previousBuildID: undefined, _clientID: null, + // A task performing delayed initialization + _delayedInitTask: null, - popPayloads: function popPayloads(reason, externalPayload) { + _shutdownBarrier: new AsyncShutdown.Barrier("TelemetryPing: Waiting for clients."), + + /** + * Get the data for the "application" section of the ping. + */ + _getApplicationSection: function() { + // Querying architecture and update channel can throw. Make sure to recover and null + // those fields. + let arch = null; + try { + arch = Services.sysinfo.get("arch"); + } catch (e) { + this._log.trace("assemblePing - Unable to get system architecture.", e); + } + + let updateChannel = null; + try { + updateChannel = UpdateChannel.get(); + } catch (e) { + this._log.trace("assemblePing - Unable to get update channel.", e); + } + + return { + architecture: arch, + buildId: Services.appinfo.appBuildID, + name: Services.appinfo.name, + version: Services.appinfo.version, + vendor: Services.appinfo.vendor, + platformVersion: Services.appinfo.platformVersion, + xpcomAbi: Services.appinfo.XPCOMABI, + channel: updateChannel, + }; + }, + + /** + * Assemble a complete ping following the common ping format specification. + * + * @param {String} aType The type of the ping. + * @param {Object} aPayload The actual data payload for the ping. + * @param {Object} aOptions Options object. + * @param {Boolean} aOptions.addClientId true if the ping should contain the client + * id, false otherwise. + * @param {Boolean} aOptions.addEnvironment true if the ping should contain the + * environment data. + * + * @returns Promise A promise that resolves when the ping is completely assembled. + */ + assemblePing: function assemblePing(aType, aPayload, aOptions = {}) { + this._log.trace("assemblePing - Type " + aType + ", Server " + this._server + + ", aOptions " + JSON.stringify(aOptions)); + + // Fill the common ping fields. + let pingData = { + type: aType, + id: generateUUID(), + creationDate: (new Date()).toISOString(), + version: PING_FORMAT_VERSION, + application: this._getApplicationSection(), + payload: aPayload, + }; + + if (aOptions.addClientId) { + pingData.clientId = this._clientID; + } + + if (aOptions.addEnvironment) { + return TelemetryEnvironment.getEnvironmentData().then(environment => { + pingData.environment = environment; + return pingData; + }, + error => { + this._log.error("assemblePing - Rejection", error); + }); + } + + return Promise.resolve(pingData); + }, + + popPayloads: function popPayloads() { + this._log.trace("popPayloads"); function payloadIter() { - if (externalPayload && reason != "overdue-flush") { - yield externalPayload; - } - let iterator = TelemetryFile.popPendingPings(reason); + let iterator = TelemetryFile.popPendingPings(); for (let data of iterator) { yield data; } @@ -178,29 +376,142 @@ let Impl = { }, /** - * Send data to the server. Record success/send-time in histograms + * Build a complete ping and send data to the server. Record success/send-time in + * histograms. + * + * @param {String} aType The type of the ping. + * @param {Object} aPayload The actual data payload for the ping. + * @param {Object} aOptions Options object. + * @param {Number} aOptions.retentionDays The number of days to keep the ping on disk + * if sending fails. + * @param {Boolean} aOptions.addClientId true if the ping should contain the client id, + * false otherwise. + * @param {Boolean} aOptions.addEnvironment true if the ping should contain the + * environment data. + * + * @returns {Promise} A promise that resolves when the ping is sent. */ - send: function send(reason, aPayload) { - this._log.trace("send - Reason " + reason + ", Server " + this._server); - return this.sendPingsFromIterator(this._server, reason, - Iterator(this.popPayloads(reason, aPayload))); + send: function send(aType, aPayload, aOptions) { + this._log.trace("send - Type " + aType + ", Server " + this._server + + ", aOptions " + JSON.stringify(aOptions)); + + return this.assemblePing(aType, aPayload, aOptions) + .then(pingData => { + // Once ping is assembled, send it along with the persisted ping in the backlog. + let p = [ + // Persist the ping if sending it fails. + this.doPing(pingData, false) + .catch(() => TelemetryFile.savePing(pingData, true)), + this.sendPersistedPings(), + ]; + return Promise.all(p); + }, + error => this._log.error("send - Rejection", error)); }, - sendPingsFromIterator: function sendPingsFromIterator(server, reason, i) { - let p = [data for (data in i)].map((data) => - this.doPing(server, data).then(null, () => TelemetryFile.savePing(data, true))); - + /** + * Send the persisted pings to the server. + */ + sendPersistedPings: function sendPersistedPings() { + this._log.trace("sendPersistedPings"); + let pingsIterator = Iterator(this.popPayloads()); + let p = [data for (data in pingsIterator)].map(data => this.doPing(data, true)); return Promise.all(p); }, - finishPingRequest: function finishPingRequest(success, startTime, ping) { + /** + * Saves all the pending pings, plus the passed one, to disk. + * + * @param {String} aType The type of the ping. + * @param {Object} aPayload The actual data payload for the ping. + * @param {Object} aOptions Options object. + * @param {Number} aOptions.retentionDays The number of days to keep the ping on disk + * if sending fails. + * @param {Boolean} aOptions.addClientId true if the ping should contain the client id, + * false otherwise. + * @param {Boolean} aOptions.addEnvironment true if the ping should contain the + * environment data. + * + * @returns {Promise} A promise that resolves when all the pings are saved to disk. + */ + savePendingPings: function savePendingPings(aType, aPayload, aOptions) { + this._log.trace("savePendingPings - Type " + aType + ", Server " + this._server + + ", aOptions " + JSON.stringify(aOptions)); + + return this.assemblePing(aType, aPayload, aOptions) + .then(pingData => TelemetryFile.savePendingPings(pingData), + error => this._log.error("savePendingPings - Rejection", error)); + }, + + /** + * Save a ping to disk. + * + * @param {String} aType The type of the ping. + * @param {Object} aPayload The actual data payload for the ping. + * @param {Object} aOptions Options object. + * @param {Number} aOptions.retentionDays The number of days to keep the ping on disk + * if sending fails. + * @param {Boolean} aOptions.addClientId true if the ping should contain the client id, + * false otherwise. + * @param {Boolean} aOptions.addEnvironment true if the ping should contain the + * environment data. + * @param {Boolean} aOptions.overwrite true overwrites a ping with the same name, if found. + * + * @returns {Promise} A promise that resolves when the ping is saved to disk. + */ + savePing: function savePing(aType, aPayload, aOptions) { + this._log.trace("savePing - Type " + aType + ", Server " + this._server + + ", aOptions " + JSON.stringify(aOptions)); + + return this.assemblePing(aType, aPayload, aOptions) + .then(pingData => TelemetryFile.savePing(pingData, aOptions.overwrite), + error => this._log.error("savePing - Rejection", error)); + }, + + /** + * Save a ping to disk and return the ping id when done. + * + * @param {String} aType The type of the ping. + * @param {Object} aPayload The actual data payload for the ping. + * @param {Object} aOptions Options object. + * @param {Number} aOptions.retentionDays The number of days to keep the ping on disk + * if sending fails. + * @param {Boolean} aOptions.addClientId true if the ping should contain the client id, + * false otherwise. + * @param {Boolean} aOptions.addEnvironment true if the ping should contain the + * environment data. + * @param {Boolean} aOptions.overwrite true overwrites a ping with the same name, if found. + * @param {String} [aOptions.filePath] The path to save the ping to. Will save to default + * ping location if not provided. + * + * @returns {Promise} A promise that resolves with the ping id when the ping is saved to + * disk. + */ + testSavePingToFile: function testSavePingToFile(aType, aPayload, aOptions) { + this._log.trace("testSavePingToFile - Type " + aType + ", Server " + this._server + + ", aOptions " + JSON.stringify(aOptions)); + return this.assemblePing(aType, aPayload, aOptions) + .then(pingData => { + if (aOptions.filePath) { + return TelemetryFile.savePingToFile(pingData, aOptions.filePath, aOptions.overwrite) + .then(() => { return pingData.id; }); + } else { + return TelemetryFile.savePing(pingData, aOptions.overwrite) + .then(() => { return pingData.id; }); + } + }, error => this._log.error("testSavePing - Rejection", error)); + }, + + finishPingRequest: function finishPingRequest(success, startTime, ping, isPersisted) { + this._log.trace("finishPingRequest - Success " + success + ", Persisted " + isPersisted); + let hping = Telemetry.getHistogramById("TELEMETRY_PING"); let hsuccess = Telemetry.getHistogramById("TELEMETRY_SUCCESS"); hsuccess.add(success); hping.add(new Date() - startTime); - if (success) { + if (success && isPersisted) { return TelemetryFile.cleanupPingFile(ping); } else { return Promise.resolve(); @@ -208,23 +519,20 @@ let Impl = { }, submissionPath: function submissionPath(ping) { - let slug; - if (!ping) { - slug = this._uuid; - } else { - let info = ping.payload.info; - let pathComponents = [ping.slug, info.reason, info.appName, - info.appVersion, info.appUpdateChannel, - info.appBuildID]; - slug = pathComponents.join("/"); - } + let app = ping.application; + // We insert the Ping id in the URL to simplify server handling of duplicated + // pings. + let pathComponents = [ping.id, ping.type, app.name, app.version, + app.channel, app.buildId]; + let slug = pathComponents.join("/"); + return "/submit/telemetry/" + slug; }, - doPing: function doPing(server, ping) { - this._log.trace("doPing - Server " + server); + doPing: function doPing(ping, isPersisted) { + this._log.trace("doPing - Server " + this._server + ", Persisted " + isPersisted); let deferred = Promise.defer(); - let url = server + this.submissionPath(ping); + let url = this._server + this.submissionPath(ping); let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] .createInstance(Ci.nsIXMLHttpRequest); request.mozBackgroundRequest = true; @@ -235,14 +543,22 @@ let Impl = { let startTime = new Date(); function handler(success) { + let handleCompletion = event => { + if (success) { + deferred.resolve(); + } else { + deferred.reject(event); + } + }; + return function(event) { - this.finishPingRequest(success, startTime, ping).then(() => { - if (success) { - deferred.resolve(); - } else { - deferred.reject(event); - } - }); + this.finishPingRequest(success, startTime, ping, isPersisted) + .then(() => handleCompletion(event), + error => { + this._log.error("doPing - Request Success " + success + ", Error " + + error); + handleCompletion(event); + }); }; } request.addEventListener("error", handler(false).bind(this), false); @@ -252,7 +568,7 @@ let Impl = { let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] .createInstance(Ci.nsIScriptableUnicodeConverter); converter.charset = "UTF-8"; - let utf8Payload = converter.ConvertFromUnicode(JSON.stringify(ping.payload)); + let utf8Payload = converter.ConvertFromUnicode(JSON.stringify(ping)); utf8Payload += converter.Finish(); let payloadStream = Cc["@mozilla.org/io/string-input-stream;1"] .createInstance(Ci.nsIStringInputStream); @@ -289,13 +605,22 @@ let Impl = { * Perform telemetry initialization for either chrome or content process. */ enableTelemetryRecording: function enableTelemetryRecording(testing) { + // Enable base Telemetry recording, if needed. +#if !defined(MOZ_WIDGET_ANDROID) + Telemetry.canRecordBase = + Preferences.get("datareporting.healthreport.service.enabled", false) || + Preferences.get("browser.selfsupport.enabled", false); +#else + // FHR recording is always "enabled" on Android (data upload is not). + Telemetry.canRecordBase = true; +#endif #ifdef MOZILLA_OFFICIAL - if (!Telemetry.canSend && !testing) { + if (!Telemetry.isOfficialTelemetry && !testing) { // We can't send data; no point in initializing observers etc. // Only do this for official builds so that e.g. developer builds // still enable Telemetry based on prefs. - Telemetry.canRecord = false; + Telemetry.canRecordExtended = false; this._log.config("enableTelemetryRecording - Can't send data, disabling Telemetry recording."); return false; } @@ -303,10 +628,10 @@ let Impl = { let enabled = Preferences.get(PREF_ENABLED, false); this._server = Preferences.get(PREF_SERVER, undefined); - if (!enabled) { - // Turn off local telemetry if telemetry is disabled. - // This may change once about:telemetry is added. - Telemetry.canRecord = false; + if (!enabled || !Telemetry.canRecordBase) { + // Turn off extended telemetry recording if disabled by preferences or if base/telemetry + // telemetry recording is off. + Telemetry.canRecordExtended = false; this._log.config("enableTelemetryRecording - Telemetry is disabled, turning off Telemetry recording."); return false; } @@ -316,14 +641,32 @@ let Impl = { /** * Initializes telemetry within a timer. If there is no PREF_SERVER set, don't turn on telemetry. + * + * This delayed initialization means TelemetryPing init can be in the following states: + * 1) setupTelemetry was never called + * or it was called and + * 2) _delayedInitTask was scheduled, but didn't run yet. + * 3) _delayedInitTask is currently running. + * 4) _delayedInitTask finished running and is nulled out. */ setupTelemetry: function setupTelemetry(testing) { + this._initStarted = true; if (testing && !this._log) { this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX); } this._log.trace("setupTelemetry"); + if (this._delayedInitTask) { + this._log.error("setupTelemetry - init task already running"); + return this._delayedInitTask; + } + + if (this._initialized && !testing) { + this._log.error("setupTelemetry - already initialized"); + return Promise.resolve(); + } + // Initialize some probes that are kept in their own modules this._thirdPartyCookies = new ThirdPartyCookieProbe(); this._thirdPartyCookies.init(); @@ -343,42 +686,91 @@ let Impl = { // run various late initializers. Otherwise our gathered memory // footprint and other numbers would be too optimistic. let deferred = Promise.defer(); - let delayedTask = new DeferredTask(function* () { - this._initialized = true; + this._delayedInitTask = new DeferredTask(function* () { + try { + this._initialized = true; - yield TelemetryFile.loadSavedPings(); - // If we have any TelemetryPings lying around, we'll be aggressive - // and try to send them all off ASAP. - if (TelemetryFile.pingsOverdue > 0) { - this._log.trace("setupChromeProcess - Sending " + TelemetryFile.pingsOverdue + - " overdue pings now."); - // It doesn't really matter what we pass to this.send as a reason, - // since it's never sent to the server. All that this.send does with - // the reason is check to make sure it's not a test-ping. - yield this.send("overdue-flush"); + yield TelemetryEnvironment.init(); + + yield TelemetryFile.loadSavedPings(); + // If we have any TelemetryPings lying around, we'll be aggressive + // and try to send them all off ASAP. + if (TelemetryFile.pingsOverdue > 0) { + this._log.trace("setupChromeProcess - Sending " + TelemetryFile.pingsOverdue + + " overdue pings now."); + // It doesn't really matter what we pass to this.send as a reason, + // since it's never sent to the server. All that this.send does with + // the reason is check to make sure it's not a test-ping. + yield this.sendPersistedPings(); + } + + if ("@mozilla.org/datareporting/service;1" in Cc) { + let drs = Cc["@mozilla.org/datareporting/service;1"] + .getService(Ci.nsISupports) + .wrappedJSObject; + this._clientID = yield drs.getClientID(); + // Update cached client id. + Preferences.set(PREF_CACHED_CLIENTID, this._clientID); + } else { + // Nuke potentially cached client id. + Preferences.reset(PREF_CACHED_CLIENTID); + } + + Telemetry.asyncFetchTelemetryData(function () {}); + deferred.resolve(); + } catch (e) { + deferred.reject(e); + } finally { + this._delayedInitTask = null; } - - if ("@mozilla.org/datareporting/service;1" in Cc) { - let drs = Cc["@mozilla.org/datareporting/service;1"] - .getService(Ci.nsISupports) - .wrappedJSObject; - this._clientID = yield drs.getClientID(); - // Update cached client id. - Preferences.set(PREF_CACHED_CLIENTID, this._clientID); - } else { - // Nuke potentially cached client id. - Preferences.reset(PREF_CACHED_CLIENTID); - } - - Telemetry.asyncFetchTelemetryData(function () {}); - deferred.resolve(); - }.bind(this), testing ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY); - delayedTask.arm(); + AsyncShutdown.sendTelemetry.addBlocker("TelemetryPing: shutting down", + () => this.shutdown(), + () => this._getState()); + + this._delayedInitTask.arm(); return deferred.promise; }, + shutdown: function() { + this._log.trace("shutdown"); + + let cleanup = () => { + if (!this._initialized) { + return; + } + let reset = () => { + this._initialized = false; + this._initStarted = false; + }; + return this._shutdownBarrier.wait().then( + () => TelemetryEnvironment.shutdown().then(reset, reset)); + }; + + // We can be in one the following states here: + // 1) setupTelemetry was never called + // or it was called and + // 2) _delayedInitTask was scheduled, but didn't run yet. + // 3) _delayedInitTask is running now. + // 4) _delayedInitTask finished running already. + + // This handles 1). + if (!this._initStarted) { + return Promise.resolve(); + } + + // This handles 4). + if (!this._delayedInitTask) { + // We already ran the delayed initialization. + return cleanup(); + } + + // This handles 2) and 3). + this._delayedInitTask.disarm(); + return this._delayedInitTask.finalize().then(cleanup); + }, + /** * This observer drives telemetry. */ @@ -412,4 +804,27 @@ let Impl = { get clientID() { return this._clientID; }, + + /** + * Get an object describing the current state of this module for AsyncShutdown diagnostics. + */ + _getState: function() { + return { + initialized: this._initialized, + initStarted: this._initStarted, + haveDelayedInitTask: !!this._delayedInitTask, + }; + }, + + /** + * This tells TelemetryPing to uninitialize and save any pending pings. + * @param testing Optional. If true, always saves the ping whether Telemetry + * can send pings or not, which is used for testing. + */ + shutdown: function(testing = false) { + this.uninstall(); + if (Telemetry.canSend || testing) { + return this.savePendingPings(); + } + }, }; diff --git a/toolkit/components/telemetry/TelemetrySession.jsm b/toolkit/components/telemetry/TelemetrySession.jsm index db585d1ece..767179126d 100644 --- a/toolkit/components/telemetry/TelemetrySession.jsm +++ b/toolkit/components/telemetry/TelemetrySession.jsm @@ -12,11 +12,14 @@ const Cu = Components.utils; Cu.import("resource://gre/modules/debug.js", this); Cu.import("resource://gre/modules/Log.jsm"); +Cu.import("resource://gre/modules/osfile.jsm", this); Cu.import("resource://gre/modules/Services.jsm", this); Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); Cu.import("resource://gre/modules/Promise.jsm", this); Cu.import("resource://gre/modules/DeferredTask.jsm", this); Cu.import("resource://gre/modules/Preferences.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); +Cu.import("resource://gre/modules/Timer.jsm"); const IS_CONTENT_PROCESS = (function() { // We cannot use Services.appinfo here because in telemetry xpcshell tests, @@ -26,7 +29,23 @@ const IS_CONTENT_PROCESS = (function() { })(); // When modifying the payload in incompatible ways, please bump this version number -const PAYLOAD_VERSION = 1; +const PAYLOAD_VERSION = 4; +const PING_TYPE_MAIN = "main"; +const RETENTION_DAYS = 14; + +const REASON_DAILY = "daily"; +const REASON_SAVED_SESSION = "saved-session"; +const REASON_IDLE_DAILY = "idle-daily"; +const REASON_GATHER_PAYLOAD = "gather-payload"; +const REASON_TEST_PING = "test-ping"; +const REASON_ENVIRONMENT_CHANGE = "environment-change"; + +const ENVIRONMENT_CHANGE_LISTENER = "TelemetrySession::onEnvironmentChange"; + +const SEC_IN_ONE_DAY = 24 * 60 * 60; +const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000; + +const MIN_SUBSESSION_LENGTH_MS = 10 * 60 * 1000; // This is the HG changeset of the Histogram.json file, used to associate // submitted ping data with its histogram definition (bug 832007) @@ -44,6 +63,12 @@ const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled"; const PREF_ASYNC_PLUGIN_INIT = "dom.ipc.plugins.asyncInit"; const MESSAGE_TELEMETRY_PAYLOAD = "Telemetry:Payload"; +const MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD = "Telemetry:GetChildPayload"; + +const SESSION_STATE_FILE_NAME = "session-state.json"; + +// Maximum number of content payloads that we are willing to store. +const MAX_NUM_CONTENT_PAYLOADS = 10; // Do not gather data more than once a minute const TELEMETRY_INTERVAL = 60000; @@ -76,7 +101,13 @@ XPCOMUtils.defineLazyServiceGetter(this, "idleService", XPCOMUtils.defineLazyServiceGetter(this, "cpmm", "@mozilla.org/childprocessmessagemanager;1", "nsIMessageSender"); +XPCOMUtils.defineLazyServiceGetter(this, "cpml", + "@mozilla.org/childprocessmessagemanager;1", + "nsIMessageListenerManager"); XPCOMUtils.defineLazyServiceGetter(this, "ppmm", + "@mozilla.org/parentprocessmessagemanager;1", + "nsIMessageBroadcaster"); +XPCOMUtils.defineLazyServiceGetter(this, "ppml", "@mozilla.org/parentprocessmessagemanager;1", "nsIMessageListenerManager"); @@ -84,10 +115,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate", "resource://gre/modules/AddonManager.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown", "resource://gre/modules/AsyncShutdown.jsm"); -#ifndef MOZ_WIDGET_GONK -XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager", - "resource://gre/modules/LightweightThemeManager.jsm"); -#endif XPCOMUtils.defineLazyModuleGetter(this, "TelemetryPing", "resource://gre/modules/TelemetryPing.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "TelemetryFile", @@ -100,6 +127,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry", "resource://gre/modules/UITelemetry.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel", "resource://gre/modules/UpdateChannel.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment", + "resource://gre/modules/TelemetryEnvironment.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", + "resource://services-common/utils.js"); function generateUUID() { let str = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString(); @@ -107,6 +138,53 @@ function generateUUID() { return str.substring(1, str.length - 1); } +/** + * This is a policy object used to override behavior for testing. + */ +let Policy = { + now: () => new Date(), + setDailyTimeout: (callback, delayMs) => setTimeout(callback, delayMs), + clearDailyTimeout: (id) => clearTimeout(id), +}; + +/** + * Takes a date and returns it trunctated to a date with daily precision. + */ +function truncateToDays(date) { + return new Date(date.getFullYear(), + date.getMonth(), + date.getDate(), + 0, 0, 0, 0); +} + +/** + * Date.toISOString() gives us UTC times, this gives us local times in the ISO date format. + * http://www.w3.org/TR/NOTE-datetime + */ +function toLocalTimeISOString(date) { + function padNumber(number, places) { + number = number.toString(); + while (number.length < places) { + number = "0" + number; + } + return number; + } + + let sign = (n) => n >= 0 ? "+" : "-"; + let tzOffset = date.getTimezoneOffset(); + + // YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00) + return padNumber(date.getFullYear(), 4) + + "-" + padNumber(date.getMonth() + 1, 2) + + "-" + padNumber(date.getDate(), 2) + + "T" + padNumber(date.getHours(), 2) + + ":" + padNumber(date.getMinutes(), 2) + + ":" + padNumber(date.getSeconds(), 2); + + "." + date.getMilliseconds() + + sign(tzOffset) + Math.abs(Math.floor(tzOffset / 60)) + + ":" + Math.abs(tzOffset % 60); +} + /** * Read current process I/O counters. */ @@ -154,6 +232,95 @@ let processInfo = { } }; +/** + * This object allows the serialisation of asynchronous tasks. This is particularly + * useful to serialise write access to the disk in order to prevent race conditions + * to corrupt the data being written. + * We are using this to synchronize saving to the file that TelemetrySession persists + * its state in. + */ +let gStateSaveSerializer = { + _queuedOperations: [], + _queuedInProgress: false, + _log: Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX), + + /** + * Enqueues an operation to a list to serialise their execution in order to prevent race + * conditions. Useful to serialise access to disk. + * + * @param {Function} aFunction The task function to enqueue. It must return a promise. + * @return {Promise} A promise resolved when the enqueued task completes. + */ + enqueueTask: function (aFunction) { + let promise = new Promise((resolve, reject) => + this._queuedOperations.push([aFunction, resolve, reject])); + + if (this._queuedOperations.length == 1) { + this._popAndPerformQueuedOperation(); + } + return promise; + }, + + /** + * Make sure to flush all the pending operations. + * @return {Promise} A promise resolved when all the pending operations have completed. + */ + flushTasks: function () { + let dummyTask = () => new Promise(resolve => resolve()); + return this.enqueueTask(dummyTask); + }, + + /** + * Pop a task from the queue, executes it and continue to the next one. + * This function recursively pops all the tasks. + */ + _popAndPerformQueuedOperation: function () { + if (!this._queuedOperations.length || this._queuedInProgress) { + return; + } + + this._log.trace("_popAndPerformQueuedOperation - Performing queued operation."); + let [func, resolve, reject] = this._queuedOperations.shift(); + let promise; + + try { + this._queuedInProgress = true; + promise = func(); + } catch (ex) { + this._log.warn("_popAndPerformQueuedOperation - Queued operation threw during execution. ", + ex); + this._queuedInProgress = false; + reject(ex); + this._popAndPerformQueuedOperation(); + return; + } + + if (!promise || typeof(promise.then) != "function") { + let msg = "Queued operation did not return a promise: " + func; + this._log.warn("_popAndPerformQueuedOperation - " + msg); + + this._queuedInProgress = false; + reject(new Error(msg)); + this._popAndPerformQueuedOperation(); + return; + } + + promise.then(result => { + this._log.trace("_popAndPerformQueuedOperation - Queued operation completed."); + this._queuedInProgress = false; + resolve(result); + this._popAndPerformQueuedOperation(); + }, + error => { + this._log.warn("_popAndPerformQueuedOperation - Failure when performing queued operation.", + error); + this._queuedInProgress = false; + reject(error); + this._popAndPerformQueuedOperation(); + }); + }, +}; + this.EXPORTED_SYMBOLS = ["TelemetrySession"]; this.TelemetrySession = Object.freeze({ @@ -170,10 +337,19 @@ this.TelemetrySession = Object.freeze({ }, /** * Returns the current telemetry payload. + * @param reason Optional, the reason to trigger the payload. + * @param clearSubsession Optional, whether to clear subsession specific data. * @returns Object */ - getPayload: function() { - return Impl.getPayload(); + getPayload: function(reason, clearSubsession = false) { + return Impl.getPayload(reason, clearSubsession); + }, + /** + * Asks the content processes to send their payloads. + * @returns Object + */ + requestChildPayloads: function() { + return Impl.requestChildPayloads(); }, /** * Save histograms to a file. @@ -198,15 +374,6 @@ this.TelemetrySession = Object.freeze({ setAddOns: function(aAddOns) { return Impl.setAddOns(aAddOns); }, - /** - * Load histograms from a file. - * Used only for testing purposes. - * - * @param aFile - File to load from. - */ - testLoadHistograms: function(aFile) { - return Impl.testLoadHistograms(aFile); - }, /** * Descriptive metadata * @@ -222,14 +389,18 @@ this.TelemetrySession = Object.freeze({ * Used only for testing purposes. */ reset: function() { + Impl._subsessionCounter = 0; + Impl._profileSubsessionCounter = 0; this.uninstall(); return this.setup(); }, /** * Used only for testing purposes. + * @param {Boolean} [aForceSavePending=true] If true, always saves the ping whether Telemetry + * can send pings or not, which is used for testing. */ - shutdown: function() { - return Impl.shutdown(true); + shutdown: function(aForceSavePending = true) { + return Impl.shutdownChromeProcess(aForceSavePending); }, /** * Used only for testing purposes. @@ -268,25 +439,39 @@ let Impl = { _initialized: false, _log: null, _prevValues: {}, - // Generate a unique id once per session so the server can cope with - // duplicate submissions. - _uuid: generateUUID(), // Regex that matches histograms we care about during startup. // Keep this in sync with gen-histogram-bucket-ranges.py. _startupHistogramRegex: /SQLITE|HTTP|SPDY|CACHE|DNS/, _slowSQLStartup: {}, - _prevSession: null, _hasWindowRestoredObserver: false, _hasXulWindowVisibleObserver: false, _startupIO : {}, // The previous build ID, if this is the first run with a new build. - // Undefined if this is not the first run, or the previous build ID is unknown. - _previousBuildID: undefined, + // Null if this is the first run, or the previous build ID is unknown. + _previousBuildId: null, // Telemetry payloads sent by child processes. // Each element is in the format {source: , payload: }, // where source is a weak reference to the child process, // and payload is the telemetry payload from that child process. _childTelemetry: [], + // Generate a unique id once per session so the server can cope with duplicate + // submissions, orphaning and other oddities. The id is shared across subsessions. + _sessionId: generateUUID(), + // Random subsession id. + _subsessionId: null, + // Subsession id of the previous subsession (even if it was in a different session), + // null on first run. + _previousSubsessionId: null, + // The running no. of subsessions since the start of the browser session + _subsessionCounter: 0, + // The running no. of all subsessions for the whole profile life time + _profileSubsessionCounter: 0, + // Date of the last session split + _subsessionStartDate: null, + // The timer used for daily collections. + _dailyTimerId: null, + // A task performing delayed initialization of the chrome process + _delayedInitTask: null, /** * Gets a series of simple measurements (counters). At the moment, this @@ -458,11 +643,13 @@ let Impl = { return retgram; }, - getHistograms: function getHistograms(hls) { - this._log.trace("getHistograms"); + getHistograms: function getHistograms(subsession, clearSubsession) { + this._log.trace("getHistograms - subsession: " + subsession + ", clearSubsession: " + clearSubsession); let registered = Telemetry.registeredHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, []); + let hls = subsession ? Telemetry.snapshotSubsessionHistograms(clearSubsession) + : Telemetry.histogramSnapshots; let ret = {}; for (let name of registered) { @@ -495,8 +682,8 @@ let Impl = { return ret; }, - getKeyedHistograms: function() { - this._log.trace("getKeyedHistograms"); + getKeyedHistograms: function(subsession, clearSubsession) { + this._log.trace("getKeyedHistograms - subsession: " + subsession + ", clearSubsession: " + clearSubsession); let registered = Telemetry.registeredKeyedHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, []); @@ -505,7 +692,13 @@ let Impl = { for (let id of registered) { ret[id] = {}; let keyed = Telemetry.getKeyedHistogramById(id); - let snapshot = keyed.snapshot(); + let snapshot = null; + if (subsession) { + snapshot = clearSubsession ? keyed.snapshotSubsessionAndClear() + : keyed.subsessionSnapshot(); + } else { + snapshot = keyed.snapshot(); + } for (let key of Object.keys(snapshot)) { ret[id][key] = this.packHistogram(snapshot[key]); } @@ -525,111 +718,43 @@ let Impl = { getMetadata: function getMetadata(reason) { this._log.trace("getMetadata - Reason " + reason); - let ai = Services.appinfo; + let sessionStartDate = toLocalTimeISOString(truncateToDays(this._sessionStartDate)); + let subsessionStartDate = toLocalTimeISOString(truncateToDays(this._subsessionStartDate)); + // Compute the subsession length in milliseconds, then convert to seconds. + let subsessionLength = + Math.floor((Policy.now() - this._subsessionStartDate.getTime()) / 1000); + let ret = { reason: reason, - OS: ai.OS, - appID: ai.ID, - appVersion: ai.version, - appName: ai.name, - appBuildID: ai.appBuildID, - appUpdateChannel: UpdateChannel.get(), - platformBuildID: ai.platformBuildID, revision: HISTOGRAMS_FILE_VERSION, - locale: getLocale(), - asyncPluginInit: Preferences.get(PREF_ASYNC_PLUGIN_INIT, false) + asyncPluginInit: Preferences.get(PREF_ASYNC_PLUGIN_INIT, false), + + // Date.getTimezoneOffset() unintuitively returns negative values if we are ahead of + // UTC and vice versa (e.g. -60 for UTC+1). We invert the sign here. + timezoneOffset: -this._subsessionStartDate.getTimezoneOffset(), + previousBuildId: this._previousBuildId, + + sessionId: this._sessionId, + subsessionId: this._subsessionId, + previousSubsessionId: this._previousSubsessionId, + + subsessionCounter: this._subsessionCounter, + profileSubsessionCounter: this._profileSubsessionCounter, + + sessionStartDate: sessionStartDate, + subsessionStartDate: subsessionStartDate, + subsessionLength: subsessionLength, }; - // In order to share profile data, the appName used for Metro Firefox is "Firefox", - // (the same as desktop Firefox). We set it to "MetroFirefox" here in order to - // differentiate telemetry pings sent by desktop vs. metro Firefox. - if(Services.metro && Services.metro.immersive) { - ret.appName = "MetroFirefox"; - } - - if (this._previousBuildID) { - ret.previousBuildID = this._previousBuildID; - } - - // sysinfo fields are not always available, get what we can. - let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2); - let fields = ["cpucount", "memsize", "arch", "version", "kernel_version", - "device", "manufacturer", "hardware", "tablet", - "hasMMX", "hasSSE", "hasSSE2", "hasSSE3", - "hasSSSE3", "hasSSE4A", "hasSSE4_1", "hasSSE4_2", - "hasEDSP", "hasARMv6", "hasARMv7", "hasNEON", "isWow64", - "profileHDDModel", "profileHDDRevision", "binHDDModel", - "binHDDRevision", "winHDDModel", "winHDDRevision"]; - for each (let field in fields) { - let value; - try { - value = sysInfo.getProperty(field); - } catch (e) { - continue; - } - if (field == "memsize") { - // Send RAM size in megabytes. Rounding because sysinfo doesn't - // always provide RAM in multiples of 1024. - value = Math.round(value / 1024 / 1024); - } - ret[field] = value; - } - - // gfxInfo fields are not always available, get what we can. - let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); - let gfxfields = ["adapterDescription", "adapterVendorID", "adapterDeviceID", - "adapterSubsysID", "adapterRAM", "adapterDriver", - "adapterDriverVersion", "adapterDriverDate", - "adapterDescription2", "adapterVendorID2", - "adapterDeviceID2", "adapterSubsysID2", "adapterRAM2", - "adapterDriver2", "adapterDriverVersion2", - "adapterDriverDate2", "isGPU2Active", "D2DEnabled", - "DWriteEnabled", "DWriteVersion" - ]; - - if (gfxInfo) { - for each (let field in gfxfields) { - try { - let value = gfxInfo[field]; - // bug 940806: We need to do a strict equality comparison here, - // otherwise a type conversion will occur and boolean false values - // will get filtered out - if (value !== "") { - ret[field] = value; - } - } catch (e) { - continue - } - } - } - -#ifndef MOZ_WIDGET_GONK - let theme = LightweightThemeManager.currentTheme; - if (theme) { - ret.persona = theme.id; - } -#endif - + // TODO: Remove this when bug 1124128 lands. if (this._addons) ret.addons = this._addons; + // TODO: Remove this when bug 1124128 lands. let flashVersion = this.getFlashVersion(); if (flashVersion) ret.flashVersion = flashVersion; - try { - let scope = {}; - Cu.import("resource:///modules/experiments/Experiments.jsm", scope); - let experiments = scope.Experiments.instance() - let activeExperiment = experiments.getActiveExperimentID(); - if (activeExperiment) { - ret.activeExperiment = activeExperiment; - ret.activeExperimentBranch = experiments.getActiveExperimentBranch(); - } - } catch(e) { - // If this is not Firefox, the import will fail. - } - return ret; }, @@ -780,15 +905,17 @@ let Impl = { * to |this.getSimpleMeasurements| and |this.getMetadata|, * respectively. */ - assemblePayloadWithMeasurements: function assemblePayloadWithMeasurements(simpleMeasurements, info) { - this._log.trace("assemblePayloadWithMeasurements"); + assemblePayloadWithMeasurements: function(simpleMeasurements, info, reason, clearSubsession) { + const isSubsession = !this._isClassicReason(reason); + this._log.trace("assemblePayloadWithMeasurements - reason: " + reason + + ", submitting subsession data: " + isSubsession); // Payload common to chrome and content processes. let payloadObj = { ver: PAYLOAD_VERSION, simpleMeasurements: simpleMeasurements, - histograms: this.getHistograms(Telemetry.histogramSnapshots), - keyedHistograms: this.getKeyedHistograms(), + histograms: this.getHistograms(isSubsession, clearSubsession), + keyedHistograms: this.getKeyedHistograms(isSubsession, clearSubsession), chromeHangs: Telemetry.chromeHangs, log: TelemetryLog.entries(), }; @@ -812,32 +939,40 @@ let Impl = { payloadObj.slowSQLStartup = this._slowSQLStartup; } - let clientID = TelemetryPing.clientID; - if (clientID && Preferences.get(PREF_FHR_UPLOAD_ENABLED, false)) { - payloadObj.clientID = clientID; - } - if (this._childTelemetry.length) { payloadObj.childPayloads = this.getChildPayloads(); } + return payloadObj; }, - getSessionPayload: function getSessionPayload(reason) { - this._log.trace("getSessionPayload - Reason " + reason); - let measurements = this.getSimpleMeasurements(reason == "saved-session"); + /** + * Start a new subsession. + */ + startNewSubsession: function () { + this._subsessionStartDate = Policy.now(); + this._previousSubsessionId = this._subsessionId; + this._subsessionId = generateUUID(); + this._subsessionCounter++; + this._profileSubsessionCounter++; + }, + + getSessionPayload: function getSessionPayload(reason, clearSubsession) { + this._log.trace("getSessionPayload - reason: " + reason + ", clearSubsession: " + clearSubsession); + + let measurements = this.getSimpleMeasurements(reason == REASON_SAVED_SESSION); let info = !IS_CONTENT_PROCESS ? this.getMetadata(reason) : null; - return this.assemblePayloadWithMeasurements(measurements, info); - }, + let payload = this.assemblePayloadWithMeasurements(measurements, info, reason, clearSubsession); - assemblePing: function assemblePing(payloadObj, reason) { - let slug = this._uuid; - return { slug: slug, reason: reason, payload: payloadObj }; - }, + if (!IS_CONTENT_PROCESS && clearSubsession) { + this.startNewSubsession(); + // Persist session data to disk (don't wait until it completes). + let sessionData = this._getSessionDataObject(); + gStateSaveSerializer.enqueueTask(() => this._saveSessionData(sessionData)); + this._rescheduleDailyTimer(); + } - getSessionPayloadAndSlug: function getSessionPayloadAndSlug(reason) { - this._log.trace("getSessionPayloadAndSlug - Reason " + reason); - return this.assemblePing(this.getSessionPayload(reason), reason); + return payload; }, /** @@ -847,21 +982,15 @@ let Impl = { this._log.trace("send - Reason " + reason); // populate histograms one last time this.gatherMemory(); - return TelemetryPing.send(reason, this.getSessionPayloadAndSlug(reason)); - }, - submissionPath: function submissionPath(ping) { - let slug; - if (!ping) { - slug = this._uuid; - } else { - let info = ping.payload.info; - let pathComponents = [ping.slug, info.reason, info.appName, - info.appVersion, info.appUpdateChannel, - info.appBuildID]; - slug = pathComponents.join("/"); - } - return "/submit/telemetry/" + slug; + const isSubsession = !this._isClassicReason(reason); + let payload = this.getSessionPayload(reason, isSubsession); + let options = { + retentionDays: RETENTION_DAYS, + addClientId: true, + addEnvironment: true, + }; + return TelemetryPing.send(PING_TYPE_MAIN, payload, options); }, attachObservers: function attachObservers() { @@ -888,22 +1017,14 @@ let Impl = { enableTelemetryRecording: function enableTelemetryRecording(testing) { #ifdef MOZILLA_OFFICIAL - if (!Telemetry.canSend && !testing) { - // We can't send data; no point in initializing observers etc. - // Only do this for official builds so that e.g. developer builds - // still enable Telemetry based on prefs. - Telemetry.canRecord = false; + if (!Telemetry.isOfficialTelemetry && !testing) { this._log.config("enableTelemetryRecording - Can't send data, disabling Telemetry recording."); return false; } #endif let enabled = Preferences.get(PREF_ENABLED, false); - this._server = Preferences.get(PREF_SERVER, undefined); if (!enabled) { - // Turn off local telemetry if telemetry is disabled. - // This may change once about:telemetry is added. - Telemetry.canRecord = false; this._log.config("enableTelemetryRecording - Telemetry is disabled, turning off Telemetry recording."); return false; } @@ -915,24 +1036,39 @@ let Impl = { * Initializes telemetry within a timer. If there is no PREF_SERVER set, don't turn on telemetry. */ setupChromeProcess: function setupChromeProcess(testing) { + this._initStarted = true; if (testing && !this._log) { this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX); } this._log.trace("setupChromeProcess"); + if (this._delayedInitTask) { + this._log.error("setupTelemetry - init task already running"); + return this._delayedInitTask; + } + + if (this._initialized && !testing) { + this._log.error("setupTelemetry - already initialized"); + return Promise.resolve(); + } + + this.startNewSubsession(); + // startNewSubsession sets |_subsessionStartDate| to the current date/time. Use + // the very same value for |_sessionStartDate|. + this._sessionStartDate = this._subsessionStartDate; + // Initialize some probes that are kept in their own modules this._thirdPartyCookies = new ThirdPartyCookieProbe(); this._thirdPartyCookies.init(); // Record old value and update build ID preference if this is the first // run with a new build ID. - let previousBuildID = Preferences.get(PREF_PREVIOUS_BUILDID, undefined); + let previousBuildId = Preferences.get(PREF_PREVIOUS_BUILDID, null); let thisBuildID = Services.appinfo.appBuildID; - // If there is no previousBuildID preference, this._previousBuildID remains - // undefined so no value is sent in the telemetry metadata. - if (previousBuildID != thisBuildID) { - this._previousBuildID = previousBuildID; + // If there is no previousBuildId preference, we send null to the server. + if (previousBuildId != thisBuildID) { + this._previousBuildId = previousBuildId; Preferences.set(PREF_PREVIOUS_BUILDID, thisBuildID); } @@ -941,14 +1077,9 @@ let Impl = { return Promise.resolve(); } - AsyncShutdown.sendTelemetry.addBlocker( - "Telemetry: shutting down", - function condition(){ - this.uninstall(); - if (Telemetry.canSend) { - return this.savePendingPings(); - } - }.bind(this)); + TelemetryPing.shutdown.addBlocker("TelemetrySession: shutting down", + () => this.shutdownChromeProcess(), + () => this._getState()); Services.obs.addObserver(this, "sessionstore-windows-restored", false); #ifdef MOZ_WIDGET_ANDROID @@ -958,24 +1089,40 @@ let Impl = { this._hasWindowRestoredObserver = true; this._hasXulWindowVisibleObserver = true; - ppmm.addMessageListener(MESSAGE_TELEMETRY_PAYLOAD, this); + ppml.addMessageListener(MESSAGE_TELEMETRY_PAYLOAD, this); // Delay full telemetry initialization to give the browser time to // run various late initializers. Otherwise our gathered memory // footprint and other numbers would be too optimistic. let deferred = Promise.defer(); - let delayedTask = new DeferredTask(function* () { - this._initialized = true; + this._delayedInitTask = new DeferredTask(function* () { + try { + this._initialized = true; - this.attachObservers(); - this.gatherMemory(); + let hasLoaded = yield this._loadSessionData(); + if (!hasLoaded) { + // We could not load a valid session data file. Create one. + yield this._saveSessionData(this._getSessionDataObject()).catch(() => + this._log.error("setupChromeProcess - Could not write session data to disk.")); + } + this.attachObservers(); + this.gatherMemory(); - Telemetry.asyncFetchTelemetryData(function () {}); - deferred.resolve(); + Telemetry.asyncFetchTelemetryData(function () {}); + this._rescheduleDailyTimer(); + TelemetryEnvironment.registerChangeListener(ENVIRONMENT_CHANGE_LISTENER, + () => this._onEnvironmentChange()); + + deferred.resolve(); + } catch (e) { + deferred.reject(); + } finally { + this._delayedInitTask = null; + } }.bind(this), testing ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY); - delayedTask.arm(); + this._delayedInitTask.arm(); return deferred.promise; }, @@ -990,6 +1137,7 @@ let Impl = { } Services.obs.addObserver(this, "content-child-shutdown", false); + cpml.addMessageListener(MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD, this); this.gatherStartupHistograms(); @@ -1003,10 +1151,6 @@ let Impl = { delayedTask.arm(); }, - testLoadHistograms: function testLoadHistograms(file) { - return TelemetryFile.testLoadHistograms(file); - }, - getFlashVersion: function getFlashVersion() { this._log.trace("getFlashVersion"); let host = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); @@ -1025,9 +1169,11 @@ let Impl = { switch (message.name) { case MESSAGE_TELEMETRY_PAYLOAD: { - let target = message.target; + let source = message.data.childUUID; + delete message.data.childUUID; + for (let child of this._childTelemetry) { - if (child.source.get() === target) { + if (child.source === source) { // Update existing telemetry data. child.payload = message.data; return; @@ -1035,9 +1181,20 @@ let Impl = { } // Did not find existing child in this._childTelemetry. this._childTelemetry.push({ - source: Cu.getWeakReference(target), + source: source, payload: message.data, }); + + if (this._childTelemetry.length == MAX_NUM_CONTENT_PAYLOADS + 1) { + this._childTelemetry.shift(); + Telemetry.getHistogramById("TELEMETRY_DISCARDED_CONTENT_PINGS_COUNT").add(); + } + + break; + } + case MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD: + { + this.sendContentProcessPing("saved-session"); break; } default: @@ -1045,21 +1202,38 @@ let Impl = { } }, + _processUUID: generateUUID(), + sendContentProcessPing: function sendContentProcessPing(reason) { this._log.trace("sendContentProcessPing - Reason " + reason); - let payload = this.getSessionPayload(reason); + const isSubsession = !this._isClassicReason(reason); + let payload = this.getSessionPayload(reason, isSubsession); + payload.childUUID = this._processUUID; cpmm.sendAsyncMessage(MESSAGE_TELEMETRY_PAYLOAD, payload); }, savePendingPings: function savePendingPings() { this._log.trace("savePendingPings"); - let sessionPing = this.getSessionPayloadAndSlug("saved-session"); - return TelemetryFile.savePendingPings(sessionPing); + let payload = this.getSessionPayload(REASON_SAVED_SESSION, false); + let options = { + retentionDays: RETENTION_DAYS, + addClientId: true, + addEnvironment: true, + }; + return TelemetryPing.savePendingPings(PING_TYPE_MAIN, payload, options); }, testSaveHistograms: function testSaveHistograms(file) { - return TelemetryFile.savePingToFile(this.getSessionPayloadAndSlug("saved-session"), - file.path, true); + this._log.trace("testSaveHistograms - Path: " + file.path); + let payload = this.getSessionPayload(REASON_SAVED_SESSION, false); + let options = { + retentionDays: RETENTION_DAYS, + addClientId: true, + addEnvironment: true, + overwrite: true, + filePath: file.path, + }; + return TelemetryPing.testSavePingToFile(PING_TYPE_MAIN, payload, options); }, /** @@ -1080,8 +1254,9 @@ let Impl = { #endif }, - getPayload: function getPayload() { - this._log.trace("getPayload"); + getPayload: function getPayload(reason, clearSubsession) { + this._log.trace("getPayload - clearSubsession: " + clearSubsession); + reason = reason || REASON_GATHER_PAYLOAD; // This function returns the current Telemetry payload to the caller. // We only gather startup info once. if (Object.keys(this._slowSQLStartup).length == 0) { @@ -1089,7 +1264,12 @@ let Impl = { this._slowSQLStartup = Telemetry.slowSQL; } this.gatherMemory(); - return this.getSessionPayload("gather-payload"); + return this.getSessionPayload(reason, clearSubsession); + }, + + requestChildPayloads: function() { + this._log.trace("requestChildPayloads"); + ppmm.broadcastAsyncMessage(MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD, {}); }, gatherStartup: function gatherStartup() { @@ -1114,9 +1294,9 @@ let Impl = { this._isIdleObserver = false; } if (aTest) { - return this.send("test-ping"); - } else if (Telemetry.canSend) { - return this.send("idle-daily"); + return this.send(REASON_TEST_PING); + } else if (Telemetry.isOfficialTelemetry) { + return this.send(REASON_IDLE_DAILY); } }, @@ -1146,8 +1326,8 @@ let Impl = { Services.obs.removeObserver(this, "content-child-shutdown"); this.uninstall(); - if (Telemetry.canSend) { - this.sendContentProcessPing("saved-session"); + if (Telemetry.isOfficialTelemetry) { + this.sendContentProcessPing(REASON_SAVED_SESSION); } break; case "cycle-collector-begin": @@ -1175,7 +1355,7 @@ let Impl = { gWasDebuggerAttached = debugService.isDebuggerAttached; this.gatherStartup(); break; - case "idle-daily": + case REASON_IDLE_DAILY: // Enqueue to main-thread, otherwise components may be inited by the // idle-daily category and miss the gather-telemetry notification. Services.tm.mainThread.dispatch((function() { @@ -1207,9 +1387,15 @@ let Impl = { // backgrounding), or not (in which case we will delete it on submit, or overwrite // it on the next backgrounding). Not deleting it is faster, so that's what we do. case "application-background": - if (Telemetry.canSend) { - let ping = this.getSessionPayloadAndSlug("saved-session"); - TelemetryFile.savePing(ping, true); + if (Telemetry.isOfficialTelemetry) { + let payload = this.getSessionPayload(REASON_SAVED_SESSION, false); + let options = { + retentionDays: RETENTION_DAYS, + addClientId: true, + addEnvironment: true, + overwrite: true, + }; + TelemetryPing.savePing(PING_TYPE_MAIN, payload, options); } break; #endif @@ -1217,14 +1403,189 @@ let Impl = { }, /** - * This tells TelemetryPing to uninitialize and save any pending pings. + * This tells TelemetrySession to uninitialize and save any pending pings. * @param testing Optional. If true, always saves the ping whether Telemetry * can send pings or not, which is used for testing. */ - shutdown: function(testing = false) { - this.uninstall(); - if (Telemetry.canSend || testing) { - return this.savePendingPings(); + shutdownChromeProcess: function(testing = false) { + this._log.trace("shutdownChromeProcess - testing: " + testing); + TelemetryEnvironment.unregisterChangeListener(ENVIRONMENT_CHANGE_LISTENER); + + let cleanup = () => { + TelemetryEnvironment.unregisterChangeListener(ENVIRONMENT_CHANGE_LISTENER); + if (this._dailyTimerId) { + Policy.clearDailyTimeout(this._dailyTimerId); + this._dailyTimerId = null; + } + this.uninstall(); + + let reset = () => { + this._initStarted = false; + this._initialized = false; + }; + + if (Telemetry.isOfficialTelemetry || testing) { + return this.savePendingPings() + .then(() => gStateSaveSerializer.flushTasks()) + .then(reset); + } + + reset(); + return Promise.resolve(); + }; + + // We can be in one the following states here: + // 1) setupChromeProcess was never called + // or it was called and + // 2) _delayedInitTask was scheduled, but didn't run yet. + // 3) _delayedInitTask is running now. + // 4) _delayedInitTask finished running already. + + // This handles 1). + if (!this._initStarted) { + return Promise.resolve(); + } + + // This handles 4). + if (!this._delayedInitTask) { + // We already ran the delayed initialization. + return cleanup(); + } + + // This handles 2) and 3). + this._delayedInitTask.disarm(); + return this._delayedInitTask.finalize().then(cleanup); + }, + + _rescheduleDailyTimer: function() { + if (this._dailyTimerId) { + this._log.trace("_rescheduleDailyTimer - clearing existing timeout"); + Policy.clearDailyTimeout(this._dailyTimerId); } + + let now = Policy.now(); + let midnight = truncateToDays(now).getTime() + MS_IN_ONE_DAY; + let msUntilCollection = midnight - now.getTime(); + if (msUntilCollection < MIN_SUBSESSION_LENGTH_MS) { + msUntilCollection += MS_IN_ONE_DAY; + } + + this._log.trace("_rescheduleDailyTimer - now: " + now + + ", scheduled: " + new Date(now.getTime() + msUntilCollection)); + this._dailyTimerId = Policy.setDailyTimeout(() => this._onDailyTimer(), msUntilCollection); + }, + + _onDailyTimer: function() { + if (!this._initStarted) { + if (this._log) { + this._log.warn("_onDailyTimer - not initialized"); + } else { + Cu.reportError("TelemetrySession._onDailyTimer - not initialized"); + } + return; + } + + this._log.trace("_onDailyTimer"); + let payload = this.getSessionPayload(REASON_DAILY, true); + + let options = { + retentionDays: RETENTION_DAYS, + addClientId: true, + addEnvironment: true, + }; + let promise = TelemetryPing.send(PING_TYPE_MAIN, payload, options); + + this._rescheduleDailyTimer(); + // Return the promise so tests can wait on the ping submission. + return promise; + }, + + /** + * Loads session data from the session data file. + * @return {Promise} A promise which is resolved with a true argument when + * loading has completed, with false otherwise. + */ + _loadSessionData: Task.async(function* () { + let dataFile = OS.Path.join(OS.Constants.Path.profileDir, "datareporting", + SESSION_STATE_FILE_NAME); + + // Try to load the "profileSubsessionCounter" from the state file. + try { + let data = yield CommonUtils.readJSON(dataFile); + if (data && + "profileSubsessionCounter" in data && + typeof(data.profileSubsessionCounter) == "number" && + "previousSubsessionId" in data) { + this._previousSubsessionId = data.previousSubsessionId; + // Add |_subsessionCounter| to the |_profileSubsessionCounter| to account for + // new subsession while loading still takes place. This will always be exactly + // 1 - the current subsessions. + this._profileSubsessionCounter = data.profileSubsessionCounter + + this._subsessionCounter; + return true; + } + } catch (e) { + this._log.info("_loadSessionData - Cannot load session data file " + dataFile, e); + } + return false; + }), + + /** + * Get the session data object to serialise to disk. + */ + _getSessionDataObject: function() { + return { + previousSubsessionId: this._previousSubsessionId, + profileSubsessionCounter: this._profileSubsessionCounter, + }; + }, + + /** + * Saves session data to disk. + */ + _saveSessionData: Task.async(function* (sessionData) { + let dataDir = OS.Path.join(OS.Constants.Path.profileDir, "datareporting"); + yield OS.File.makeDir(dataDir); + + let filePath = OS.Path.join(dataDir, SESSION_STATE_FILE_NAME); + try { + yield CommonUtils.writeJSON(sessionData, filePath); + } catch(e) { + this._log.error("_saveSessionData - Failed to write session data to " + filePath, e); + } + }), + + _onEnvironmentChange: function() { + this._log.trace("_onEnvironmentChange"); + let payload = this.getSessionPayload(REASON_ENVIRONMENT_CHANGE, true); + + let options = { + retentionDays: RETENTION_DAYS, + addClientId: true, + addEnvironment: true, + }; + let promise = TelemetryPing.send(PING_TYPE_MAIN, payload, options); + }, + + _isClassicReason: function(reason) { + const classicReasons = [ + REASON_SAVED_SESSION, + REASON_IDLE_DAILY, + REASON_GATHER_PAYLOAD, + REASON_TEST_PING, + ]; + return classicReasons.indexOf(reason) != -1; + }, + + /** + * Get an object describing the current state of this module for AsyncShutdown diagnostics. + */ + _getState: function() { + return { + initialized: this._initialized, + initStarted: this._initStarted, + haveDelayedInitTask: !!this._delayedInitTask, + dailyTimerScheduled: !!this._dailyTimerId, + }; }, }; diff --git a/toolkit/components/telemetry/ThreadHangStats.h b/toolkit/components/telemetry/ThreadHangStats.h new file mode 100644 index 0000000000..859e52eafc --- /dev/null +++ b/toolkit/components/telemetry/ThreadHangStats.h @@ -0,0 +1,197 @@ +/* -*- 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_BackgroundHangTelemetry_h +#define mozilla_BackgroundHangTelemetry_h + +#include "mozilla/Array.h" +#include "mozilla/Assertions.h" +#include "mozilla/HangAnnotations.h" +#include "mozilla/Move.h" +#include "mozilla/Mutex.h" +#include "mozilla/PodOperations.h" +#include "mozilla/Vector.h" + +#include "nsString.h" +#include "prinrval.h" + +namespace mozilla { +namespace Telemetry { + +static const size_t kTimeHistogramBuckets = 8 * sizeof(PRIntervalTime); + +/* TimeHistogram is an efficient histogram that puts time durations into + exponential (base 2) buckets; times are accepted in PRIntervalTime and + stored in milliseconds. */ +class TimeHistogram : public mozilla::Array +{ +public: + TimeHistogram() + { + mozilla::PodArrayZero(*this); + } + // Get minimum (inclusive) range of bucket in milliseconds + uint32_t GetBucketMin(size_t aBucket) const { + MOZ_ASSERT(aBucket < ArrayLength(*this)); + return (1u << aBucket) & ~1u; // Bucket 0 starts at 0, not 1 + } + // Get maximum (inclusive) range of bucket in milliseconds + uint32_t GetBucketMax(size_t aBucket) const { + MOZ_ASSERT(aBucket < ArrayLength(*this)); + return (1u << (aBucket + 1u)) - 1u; + } + void Add(PRIntervalTime aTime); +}; + +/* HangStack stores an array of const char pointers, + with optional internal storage for strings. */ +class HangStack : public mozilla::Vector +{ +private: + typedef mozilla::Vector Base; + + // Stack entries can either be a static const char* + // or a pointer to within this buffer. + mozilla::Vector mBuffer; + +public: + HangStack() { } + + HangStack(HangStack&& aOther) + : Base(mozilla::Move(aOther)) + , mBuffer(mozilla::Move(aOther.mBuffer)) + { + } + + bool operator==(const HangStack& aOther) const { + for (size_t i = 0; i < length(); i++) { + if (!IsSameAsEntry(operator[](i), aOther[i])) { + return false; + } + } + return true; + } + + bool operator!=(const HangStack& aOther) const { + return !operator==(aOther); + } + + void clear() { + Base::clear(); + mBuffer.clear(); + } + + bool IsInBuffer(const char* aEntry) const { + return aEntry >= mBuffer.begin() && aEntry < mBuffer.end(); + } + + bool IsSameAsEntry(const char* aEntry, const char* aOther) const { + // If the entry came from the buffer, we need to compare its content; + // otherwise we only need to compare its pointer. + return IsInBuffer(aEntry) ? !strcmp(aEntry, aOther) : (aEntry == aOther); + } + + size_t AvailableBufferSize() const { + return mBuffer.capacity() - mBuffer.length(); + } + + bool EnsureBufferCapacity(size_t aCapacity) { + // aCapacity is the minimal capacity and Vector may make the actual + // capacity larger, in which case we want to use up all the space. + return mBuffer.reserve(aCapacity) && + mBuffer.reserve(mBuffer.capacity()); + } + + const char* InfallibleAppendViaBuffer(const char* aText, size_t aLength); + const char* AppendViaBuffer(const char* aText, size_t aLength); +}; + +/* A hang histogram consists of a stack associated with the + hang, along with a time histogram of the hang times. */ +class HangHistogram : public TimeHistogram +{ +private: + static uint32_t GetHash(const HangStack& aStack); + + HangStack mStack; + // Native stack that corresponds to the pseudostack in mStack + HangStack mNativeStack; + // Use a hash to speed comparisons + const uint32_t mHash; + // Annotations attributed to this stack + HangMonitor::HangAnnotationsVector mAnnotations; + +public: + explicit HangHistogram(HangStack&& aStack) + : mStack(mozilla::Move(aStack)) + , mHash(GetHash(mStack)) + { + } + HangHistogram(HangHistogram&& aOther) + : TimeHistogram(mozilla::Move(aOther)) + , mStack(mozilla::Move(aOther.mStack)) + , mNativeStack(mozilla::Move(aOther.mNativeStack)) + , mHash(mozilla::Move(aOther.mHash)) + , mAnnotations(mozilla::Move(aOther.mAnnotations)) + { + } + bool operator==(const HangHistogram& aOther) const; + bool operator!=(const HangHistogram& aOther) const + { + return !operator==(aOther); + } + const HangStack& GetStack() const { + return mStack; + } + HangStack& GetNativeStack() { + return mNativeStack; + } + const HangStack& GetNativeStack() const { + return mNativeStack; + } + const HangMonitor::HangAnnotationsVector& GetAnnotations() const { + return mAnnotations; + } + void Add(PRIntervalTime aTime, HangMonitor::HangAnnotationsPtr aAnnotations) { + TimeHistogram::Add(aTime); + if (aAnnotations) { + mAnnotations.append(Move(aAnnotations)); + } + } +}; + +/* Thread hang stats consist of + - thread name + - time histogram of all task run times + - hang histograms of individual hangs + - annotations for each hang +*/ +class ThreadHangStats +{ +private: + nsAutoCString mName; + +public: + TimeHistogram mActivity; + mozilla::Vector mHangs; + + explicit ThreadHangStats(const char* aName) + : mName(aName) + { + } + ThreadHangStats(ThreadHangStats&& aOther) + : mName(mozilla::Move(aOther.mName)) + , mActivity(mozilla::Move(aOther.mActivity)) + , mHangs(mozilla::Move(aOther.mHangs)) + { + } + const char* GetName() const { + return mName.get(); + } +}; + +} // namespace Telemetry +} // namespace mozilla +#endif // mozilla_BackgroundHangTelemetry_h diff --git a/toolkit/components/telemetry/docs/main-ping.rst b/toolkit/components/telemetry/docs/main-ping.rst index 54c2e68682..14023d809e 100644 --- a/toolkit/components/telemetry/docs/main-ping.rst +++ b/toolkit/components/telemetry/docs/main-ping.rst @@ -23,7 +23,7 @@ Structure:: reason: , // what triggered this ping: "saved-session", "environment-change", "shutdown", ... revision: , // the Histograms.json revision timezoneOffset: , // time-zone offset from UTC, in minutes, for the current locale - previousBuildId: , + previousBuildId: , // null if this is the first run, or the previous build ID is unknown sessionId: , // random session id, shared by subsessions subsessionId: , // random subsession id @@ -34,7 +34,7 @@ Structure:: profileSubsessionCounter: , // the running no. of all subsessions for the whole profile life time sessionStartDate: , // daily precision - subsessionStartDate: , // daily precision + subsessionStartDate: , // daily precision, ISO date in local time subsessionLength: , // the subsession length in seconds }, diff --git a/toolkit/components/telemetry/moz.build b/toolkit/components/telemetry/moz.build index 6923561708..dcebf5946c 100644 --- a/toolkit/components/telemetry/moz.build +++ b/toolkit/components/telemetry/moz.build @@ -27,6 +27,7 @@ EXTRA_COMPONENTS += [ ] EXTRA_JS_MODULES += [ + 'TelemetryEnvironment.jsm', 'TelemetryFile.jsm', 'TelemetryLog.jsm', 'TelemetryStopwatch.jsm', diff --git a/toolkit/components/telemetry/nsITelemetry.idl b/toolkit/components/telemetry/nsITelemetry.idl index d8c622f63b..23900cfbf3 100644 --- a/toolkit/components/telemetry/nsITelemetry.idl +++ b/toolkit/components/telemetry/nsITelemetry.idl @@ -12,7 +12,7 @@ interface nsIFetchTelemetryDataCallback : nsISupports void complete(); }; -[scriptable, uuid(c782cf96-7f44-45ac-8d76-e0d1b174e562)] +[scriptable, uuid(68e82b42-cad2-411f-857b-b64ea9377929)] interface nsITelemetry : nsISupports { /** @@ -39,7 +39,7 @@ interface nsITelemetry : nsISupports const unsigned long DATASET_RELEASE_CHANNEL_OPTIN = 1; - /* + /** * An object containing a snapshot from all of the currently registered histograms. * { name1: {data1}, name2:{data2}...} * where data is consists of the following properties: @@ -55,6 +55,14 @@ interface nsITelemetry : nsISupports [implicit_jscontext] readonly attribute jsval histogramSnapshots; + /** + * Get a snapshot of the internally duplicated subsession histograms. + * @param clear Whether to clear out the subsession histograms after snapshotting. + * @return An object as histogramSnapshots, except this contains the internally duplicated histograms for subsession telemetry. + */ + [implicit_jscontext] + jsval snapshotSubsessionHistograms([optional] in boolean clear); + /** * The amount of time, in milliseconds, that the last session took * to shutdown. Reads as 0 to indicate failure. @@ -226,14 +234,31 @@ interface nsITelemetry : nsISupports jsval getKeyedHistogramById(in ACString id); /** - * Set this to false to disable gathering of telemetry statistics. + * A flag indicating if Telemetry can record base data (FHR data). This is true if the + * FHR data reporting service or self-support are enabled. + * + * In the unlikely event that adding a new base probe is needed, please check the data + * collection wiki at https://wiki.mozilla.org/Firefox/Data_Collection and talk to the + * Telemetry team. */ - attribute boolean canRecord; + attribute boolean canRecordBase; /** - * A flag indicating whether Telemetry can submit official results. + * A flag indicating if Telemetry is allowed to record extended data. Returns false if + * the user hasn't opted into "extended Telemetry" on the Release channel, when the + * user has explicitly opted out of Telemetry on Nightly/Aurora/Beta or if manually + * set to false during tests. + * + * Set this to false in tests to disable gathering of extended telemetry statistics. */ - readonly attribute boolean canSend; + attribute boolean canRecordExtended; + + /** + * A flag indicating whether Telemetry can submit official results (for base or extended + * data). This is true on official builds with built in support for Mozilla Telemetry + * reporting. + */ + readonly attribute boolean isOfficialTelemetry; /** Addon telemetry hooks */ diff --git a/toolkit/components/telemetry/tests/unit/head.js b/toolkit/components/telemetry/tests/unit/head.js index 41b635eaec..e894e6f945 100644 --- a/toolkit/components/telemetry/tests/unit/head.js +++ b/toolkit/components/telemetry/tests/unit/head.js @@ -58,7 +58,17 @@ function createAppInfo(id, name, version, platformVersion) { XULAPPINFO_CONTRACTID, XULAppInfoFactory); } +// Fake setTimeout and clearTimeout for the daily timer in tests for controllable behavior. +function fakeDailyTimers(set, clear) { + let session = Components.utils.import("resource://gre/modules/TelemetrySession.jsm"); + session.Policy.setDailyTimeout = set; + session.Policy.clearDailyTimeout = clear; +} + // Set logging preferences for all the tests. Services.prefs.setCharPref("toolkit.telemetry.log.level", "Trace"); Services.prefs.setBoolPref("toolkit.telemetry.log.dump", true); TelemetryPing.initLogging(); + +// Avoid timers interrupting test behavior. +fakeDailyTimers(() => {}, () => {}); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js new file mode 100644 index 0000000000..a5c955d35b --- /dev/null +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/TelemetryEnvironment.jsm", this); + +function run_test() { + do_test_pending(); + do_get_profile(); + run_next_test(); +} + +function isRejected(promise) { + return new Promise((resolve, reject) => { + promise.then(() => resolve(false), () => resolve(true)); + }); +} + +add_task(function* test_initAndShutdown() { + // Check that init and shutdown work properly. + TelemetryEnvironment.init(); + yield TelemetryEnvironment.shutdown(); + TelemetryEnvironment.init(); + yield TelemetryEnvironment.shutdown(); + + // A double init should be silently handled. + TelemetryEnvironment.init(); + TelemetryEnvironment.init(); + + // getEnvironmentData should return a sane result. + let data = yield TelemetryEnvironment.getEnvironmentData(); + Assert.ok(!!data); + + // The change listener registration should silently fail after shutdown. + yield TelemetryEnvironment.shutdown(); + TelemetryEnvironment.registerChangeListener("foo", () => {}); + TelemetryEnvironment.unregisterChangeListener("foo"); + + // Shutting down again should be ignored. + yield TelemetryEnvironment.shutdown(); + + // Getting the environment data should reject after shutdown. + Assert.ok(yield isRejected(TelemetryEnvironment.getEnvironmentData())); +}); + +add_task(function* test_changeNotify() { + TelemetryEnvironment.init(); + + // Register some listeners + let results = new Array(4).fill(false); + for (let i=0; i results[k] = true); + } + // Trigger environment change notifications. + // TODO: test with proper environment changes, not directly. + TelemetryEnvironment._onEnvironmentChange("foo"); + Assert.ok(results.every(val => val), "All change listeners should have been notified."); + results.fill(false); + TelemetryEnvironment._onEnvironmentChange("bar"); + Assert.ok(results.every(val => val), "All change listeners should have been notified."); + + // Unregister listeners + for (let i=0; i<4; ++i) { + TelemetryEnvironment.unregisterChangeListener("test"+i); + } +}); + +add_task(function*() { + do_test_finished(); +}); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js b/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js index 6e30b9c8f0..e164924e1b 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js @@ -1,3 +1,4 @@ + /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ @@ -168,7 +169,6 @@ function checkPayloadInfo(payload, reason) { // get rid of the non-deterministic field const expected_info = { OS: "XPCShell", - appID: "xpcshell@tests.mozilla.org", appVersion: "1", appName: "XPCShell", appBuildID: "2007010101", @@ -182,7 +182,6 @@ function checkPayloadInfo(payload, reason) { do_check_eq(payload.info.reason, reason); do_check_true("appUpdateChannel" in payload.info); - do_check_true("locale" in payload.info); do_check_true("revision" in payload.info); if (Services.appinfo.isOfficial) { do_check_true(payload.info.revision.startsWith("http")); @@ -194,24 +193,6 @@ function checkPayloadInfo(payload, reason) { do_check_neq(payload.clientID, null); do_check_eq(payload.clientID, gDataReportingClientID); } - - try { - // If we've not got nsIGfxInfoDebug, then this will throw and stop us doing - // this test. - let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfoDebug); - let isWindows = ("@mozilla.org/windows-registry-key;1" in Components.classes); - let isOSX = ("nsILocalFileMac" in Components.interfaces); - - if (isWindows || isOSX) { - do_check_true("adapterVendorID" in payload.info); - do_check_true("adapterDeviceID" in payload.info); - if (isWindows) { - do_check_true("adapterSubsysID" in payload.info); - } - } - } - catch (x) { - } } function checkPayload(request, payload, reason, successfulPings) { @@ -362,17 +343,6 @@ function checkPayload(request, payload, reason, successfulPings) { Assert.deepEqual(expected_keyed_count, keyedHistograms[TELEMETRY_TEST_KEYED_COUNT]); } -function dummyTheme(id) { - return { - id: id, - name: Math.random().toString(), - headerURL: "http://lwttest.invalid/a.png", - footerURL: "http://lwttest.invalid/b.png", - textcolor: Math.random().toString(), - accentcolor: Math.random().toString() - }; -} - // A fake plugin host for testing flash version telemetry let PluginHost = { getPluginTags: function(countRef) { @@ -435,13 +405,6 @@ function write_fake_failedprofilelocks_file() { function run_test() { do_test_pending(); - try { - let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfoDebug); - gfxInfo.spoofVendorID("0xabcd"); - gfxInfo.spoofDeviceID("0x1234"); - } catch (x) { - // If we can't test gfxInfo, that's fine, we'll note it later. - } // Addon manager needs a profile directory do_get_profile(); @@ -495,7 +458,6 @@ function actualTest() { .QueryInterface(Ci.nsITimerCallback); gInternalManager.observe(null, "addons-startup", null); - LightweightThemeManager.currentTheme = dummyTheme("1234"); // fake plugin host for consistent flash version data registerFakePluginHost(); @@ -606,6 +568,173 @@ add_task(function* test_saveLoadPing() { } }); +add_task(function* test_checkSubsession() { + const COUNT_ID = "TELEMETRY_TEST_COUNT"; + const KEYED_ID = "TELEMETRY_TEST_KEYED_COUNT"; + const count = Telemetry.getHistogramById(COUNT_ID); + const keyed = Telemetry.getKeyedHistogramById(KEYED_ID); + const registeredIds = + new Set(Telemetry.registeredHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, [])); + + const stableHistograms = new Set([ + "TELEMETRY_TEST_FLAG", + "TELEMETRY_TEST_COUNT", + "TELEMETRY_TEST_RELEASE_OPTOUT", + "TELEMETRY_TEST_RELEASE_OPTIN", + "STARTUP_CRASH_DETECTED", + ]); + + const stableKeyedHistograms = new Set([ + "TELEMETRY_TEST_KEYED_FLAG", + "TELEMETRY_TEST_KEYED_COUNT", + "TELEMETRY_TEST_KEYED_RELEASE_OPTIN", + "TELEMETRY_TEST_KEYED_RELEASE_OPTOUT", + ]); + + // Compare the two sets of histograms. + // The "subsession" histograms should match the registered + // "classic" histograms. However, histograms can change + // between us collecting the different payloads, so we only + // check for deep equality on known stable histograms. + checkHistograms = (classic, subsession) => { + for (let id of Object.keys(classic)) { + if (!registeredIds.has(id)) { + continue; + } + + Assert.ok(id in subsession); + if (stableHistograms.has(id)) { + Assert.deepEqual(classic[id], + subsession[id]); + } else { + Assert.equal(classic[id].histogram_type, + subsession[id].histogram_type); + } + } + }; + + // Same as above, except for keyed histograms. + checkKeyedHistograms = (classic, subsession) => { + for (let id of Object.keys(classic)) { + if (!registeredIds.has(id)) { + continue; + } + + Assert.ok(id in subsession); + if (stableKeyedHistograms.has(id)) { + Assert.deepEqual(classic[id], + subsession[id]); + } + } + }; + + // Both classic and subsession payload histograms should start the same. + // The payloads should be identical for now except for the reason. + count.clear(); + keyed.clear(); + let classic = TelemetrySession.getPayload(); + let subsession = TelemetrySession.getPayload("environment-change"); + + Assert.equal(classic.info.reason, "gather-payload"); + Assert.equal(subsession.info.reason, "environment-change"); + Assert.ok(!(COUNT_ID in classic.histograms)); + Assert.ok(!(COUNT_ID in subsession.histograms)); + Assert.ok(KEYED_ID in classic.keyedHistograms); + Assert.ok(KEYED_ID in subsession.keyedHistograms); + Assert.deepEqual(classic.keyedHistograms[KEYED_ID], {}); + Assert.deepEqual(subsession.keyedHistograms[KEYED_ID], {}); + + checkHistograms(classic.histograms, subsession.histograms); + checkKeyedHistograms(classic.keyedHistograms, subsession.keyedHistograms); + + // Adding values should get picked up in both. + count.add(1); + keyed.add("a", 1); + keyed.add("b", 1); + classic = TelemetrySession.getPayload(); + subsession = TelemetrySession.getPayload("environment-change"); + + Assert.ok(COUNT_ID in classic.histograms); + Assert.ok(COUNT_ID in subsession.histograms); + Assert.ok(KEYED_ID in classic.keyedHistograms); + Assert.ok(KEYED_ID in subsession.keyedHistograms); + Assert.equal(classic.histograms[COUNT_ID].sum, 1); + Assert.equal(classic.keyedHistograms[KEYED_ID]["a"].sum, 1); + Assert.equal(classic.keyedHistograms[KEYED_ID]["b"].sum, 1); + + checkHistograms(classic.histograms, subsession.histograms); + checkKeyedHistograms(classic.keyedHistograms, subsession.keyedHistograms); + + // Values should still reset properly. + count.clear(); + keyed.clear(); + classic = TelemetrySession.getPayload(); + subsession = TelemetrySession.getPayload("environment-change"); + + Assert.ok(!(COUNT_ID in classic.histograms)); + Assert.ok(!(COUNT_ID in subsession.histograms)); + Assert.ok(KEYED_ID in classic.keyedHistograms); + Assert.ok(KEYED_ID in subsession.keyedHistograms); + Assert.deepEqual(classic.keyedHistograms[KEYED_ID], {}); + + checkHistograms(classic.histograms, subsession.histograms); + checkKeyedHistograms(classic.keyedHistograms, subsession.keyedHistograms); + + // Adding values should get picked up in both. + count.add(1); + keyed.add("a", 1); + keyed.add("b", 1); + classic = TelemetrySession.getPayload(); + subsession = TelemetrySession.getPayload("environment-change"); + + Assert.ok(COUNT_ID in classic.histograms); + Assert.ok(COUNT_ID in subsession.histograms); + Assert.ok(KEYED_ID in classic.keyedHistograms); + Assert.ok(KEYED_ID in subsession.keyedHistograms); + Assert.equal(classic.histograms[COUNT_ID].sum, 1); + Assert.equal(classic.keyedHistograms[KEYED_ID]["a"].sum, 1); + Assert.equal(classic.keyedHistograms[KEYED_ID]["b"].sum, 1); + + checkHistograms(classic.histograms, subsession.histograms); + checkKeyedHistograms(classic.keyedHistograms, subsession.keyedHistograms); + + // We should be able to reset only the subsession histograms. + count.clear(true); + keyed.clear(true); + classic = TelemetrySession.getPayload(); + subsession = TelemetrySession.getPayload("environment-change"); + + Assert.ok(COUNT_ID in classic.histograms); + Assert.ok(COUNT_ID in subsession.histograms); + Assert.equal(classic.histograms[COUNT_ID].sum, 1); + Assert.equal(subsession.histograms[COUNT_ID].sum, 0); + + Assert.ok(KEYED_ID in classic.keyedHistograms); + Assert.ok(KEYED_ID in subsession.keyedHistograms); + Assert.equal(classic.keyedHistograms[KEYED_ID]["a"].sum, 1); + Assert.equal(classic.keyedHistograms[KEYED_ID]["b"].sum, 1); + Assert.deepEqual(subsession.keyedHistograms[KEYED_ID], {}); + + // Adding values should get picked up in both again. + count.add(1); + keyed.add("a", 1); + keyed.add("b", 1); + classic = TelemetrySession.getPayload(); + subsession = TelemetrySession.getPayload("environment-change"); + + Assert.ok(COUNT_ID in classic.histograms); + Assert.ok(COUNT_ID in subsession.histograms); + Assert.equal(classic.histograms[COUNT_ID].sum, 2); + Assert.equal(subsession.histograms[COUNT_ID].sum, 1); + + Assert.ok(KEYED_ID in classic.keyedHistograms); + Assert.ok(KEYED_ID in subsession.keyedHistograms); + Assert.equal(classic.keyedHistograms[KEYED_ID]["a"].sum, 2); + Assert.equal(classic.keyedHistograms[KEYED_ID]["b"].sum, 2); + Assert.equal(subsession.keyedHistograms[KEYED_ID]["a"].sum, 1); + Assert.equal(subsession.keyedHistograms[KEYED_ID]["b"].sum, 1); +}); + // Checks that an expired histogram file is deleted when loaded. add_task(function* test_runOldPingFile() { let histogramsFile = getSavedHistogramsFile("old-histograms.dat"); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js b/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js index 329b7a8bb1..283a80f5ff 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js @@ -17,12 +17,12 @@ const Ci = Components.interfaces; const Cr = Components.results; const Cu = Components.utils; +Cu.import("resource://gre/modules/osfile.jsm", this); Cu.import("resource://gre/modules/Services.jsm", this); Cu.import("resource://testing-common/httpd.js", this); Cu.import("resource://gre/modules/Promise.jsm", this); Cu.import("resource://gre/modules/TelemetryFile.jsm", this); Cu.import("resource://gre/modules/TelemetryPing.jsm", this); -Cu.import("resource://gre/modules/TelemetrySession.jsm", this); Cu.import("resource://gre/modules/Task.jsm", this); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); let {OS: {File, Path, Constants}} = Cu.import("resource://gre/modules/osfile.jsm", {}); @@ -53,70 +53,66 @@ let gCreatedPings = 0; let gSeenPings = 0; /** - * Creates some TelemetrySession pings for the current session and - * saves them to disk. Each ping gets a unique ID slug based on - * an incrementor. + * Creates some Telemetry pings for the and saves them to disk. Each ping gets a + * unique ID based on an incrementor. * - * @param aNum the number of pings to create. - * @param aAge the age in milliseconds to offset from now. A value - * of 10 would make the ping 10ms older than now, for - * example. + * @param {Array} aPingInfos An array of ping type objects. Each entry must be an + * object containing a "num" field for the number of pings to create and + * an "age" field. The latter representing the age in milliseconds to offset + * from now. A value of 10 would make the ping 10ms older than now, for + * example. * @returns Promise - * @resolve an Array with the created pings. + * @resolve an Array with the created pings ids. */ -function createSavedPings(aNum, aAge) { - return Task.spawn(function*(){ - let pings = []; - let age = Date.now() - aAge; +let createSavedPings = Task.async(function* (aPingInfos) { + let pingIds = []; + let now = Date.now(); - for (let i = 0; i < aNum; ++i) { - let payload = TelemetrySession.getPayload(); - let ping = { slug: "test-ping-" + gCreatedPings, reason: "test", payload: payload }; - - yield TelemetryFile.savePing(ping); - - if (aAge) { + for (let type in aPingInfos) { + let num = aPingInfos[type].num; + let age = now - aPingInfos[type].age; + for (let i = 0; i < num; ++i) { + let pingId = yield TelemetryPing.testSavePingToFile("test-ping", {}, { overwrite: true }); + if (aPingInfos[type].age) { // savePing writes to the file synchronously, so we're good to // modify the lastModifedTime now. - let file = getSavePathForPing(ping); - yield File.setDates(file, null, age); + let filePath = getSavePathForPingId(pingId); + yield File.setDates(filePath, null, age); } gCreatedPings++; - pings.push(ping); + pingIds.push(pingId); } - return pings; - }); -} + } + + return pingIds; +}); /** - * Deletes locally saved pings in aPings if they - * exist. + * Deletes locally saved pings if they exist. * - * @param aPings an Array of pings to delete. + * @param aPingIds an Array of ping ids to delete. * @returns Promise */ -function clearPings(aPings) { - return Task.spawn(function*() { - for (let ping of aPings) { - let path = getSavePathForPing(ping); - yield File.remove(path); - } - }); -} +let clearPings = Task.async(function* (aPingIds) { + for (let pingId of aPingIds) { + let filePath = getSavePathForPingId(pingId); + yield File.remove(filePath); + } +}); /** - * Returns a handle for the file that aPing should be + * Returns a handle for the file that a ping should be * stored in locally. * * @returns path */ -function getSavePathForPing(aPing) { - return Path.join(Constants.Path.profileDir, PING_SAVE_FOLDER, aPing.slug); +function getSavePathForPingId(aPingId) { + return Path.join(Constants.Path.profileDir, PING_SAVE_FOLDER, aPingId); } /** - * Check if the number of TelemetrySession pings received by the - * HttpServer is not equal to aExpectedNum. + * Check if the number of Telemetry pings received by the HttpServer is not equal + * to aExpectedNum. * * @param aExpectedNum the number of pings we expect to receive. */ @@ -125,30 +121,28 @@ function assertReceivedPings(aExpectedNum) { } /** - * Throws if any pings in aPings is saved locally. + * Throws if any pings with the id in aPingIds is saved locally. * - * @param aPings an Array of pings to check. + * @param aPingIds an Array of pings ids to check. * @returns Promise */ -function assertNotSaved(aPings) { - return Task.spawn(function*() { - let saved = 0; - for (let ping of aPings) { - let file = getSavePathForPing(ping); - if (yield File.exists()) { - saved++; - } +let assertNotSaved = Task.async(function* (aPingIds) { + let saved = 0; + for (let id of aPingIds) { + let filePath = getSavePathForPingId(id); + if (yield File.exists(filePath)) { + saved++; } - if (saved > 0) { - do_throw("Found " + saved + " unexpected saved pings."); - } - }); -} + } + if (saved > 0) { + do_throw("Found " + saved + " unexpected saved pings."); + } +}); /** * Our handler function for the HttpServer that simply * increments the gSeenPings global when it successfully - * receives and decodes a TelemetrySession payload. + * receives and decodes a Telemetry payload. * * @param aRequest the HTTP request sent from HttpServer. */ @@ -174,7 +168,6 @@ function stopHttpServer() { * Reset Telemetry state. */ function resetTelemetry() { - TelemetrySession.uninstall(); // Quick and dirty way to clear TelemetryFile's pendingPings // collection, and put it back in its initial state. let gen = TelemetryFile.popPendingPings(); @@ -189,10 +182,6 @@ function startTelemetry() { return TelemetryPing.setup(); } -function startTelemetrySession() { - return TelemetrySession.setup(); -} - function run_test() { gHttpServer.registerPrefixHandler("/submit/telemetry/", pingHandler); gHttpServer.start(-1); @@ -209,13 +198,26 @@ function run_test() { run_next_test(); } +/** + * Setup the tests by making sure the ping storage directory is available, otherwise + * |TelemetryPing.testSaveDirectoryToFile| could fail. + */ +add_task(function* setupEnvironment() { + yield TelemetryPing.setup(); + + let directory = TelemetryFile.pingDirectoryPath; + yield File.makeDir(directory, { ignoreExisting: true, unixMode: OS.Constants.S_IRWXU }); + + yield resetTelemetry(); +}); + /** * Test that pings that are considered too old are just chucked out * immediately and never sent. */ add_task(function* test_expired_pings_are_deleted() { - yield startTelemetrySession(); - let expiredPings = yield createSavedPings(EXPIRED_PINGS, EXPIRED_PING_FILE_AGE); + let pingTypes = [{ num: EXPIRED_PINGS, age: EXPIRED_PING_FILE_AGE }]; + let expiredPings = yield createSavedPings(pingTypes); yield startTelemetry(); assertReceivedPings(0); yield assertNotSaved(expiredPings); @@ -226,8 +228,8 @@ add_task(function* test_expired_pings_are_deleted() { * Test that really recent pings are not sent on Telemetry initialization. */ add_task(function* test_recent_pings_not_sent() { - yield startTelemetrySession(); - let recentPings = yield createSavedPings(RECENT_PINGS); + let pingTypes = [{ num: RECENT_PINGS }]; + let recentPings = yield createSavedPings(pingTypes); yield startTelemetry(); assertReceivedPings(0); yield resetTelemetry(); @@ -238,17 +240,20 @@ add_task(function* test_recent_pings_not_sent() { * Test that only the most recent LRU_PINGS pings are kept at startup. */ add_task(function* test_most_recent_pings_kept() { - yield startTelemetrySession(); - let head = yield createSavedPings(LRU_PINGS); - let tail = yield createSavedPings(3, ONE_MINUTE_MS); - let pings = head.concat(tail); + let pingTypes = [ + { num: LRU_PINGS }, + { num: 3, age: ONE_MINUTE_MS }, + ]; + let pings = yield createSavedPings(pingTypes); + let head = pings.slice(0, LRU_PINGS); + let tail = pings.slice(-3); yield startTelemetry(); let gen = TelemetryFile.popPendingPings(); for (let item of gen) { - for (let p of tail) { - do_check_neq(p.slug, item.slug); + for (let id of tail) { + do_check_neq(id, item.id); } } @@ -263,10 +268,15 @@ add_task(function* test_most_recent_pings_kept() { * should just be deleted. */ add_task(function* test_overdue_pings_trigger_send() { - yield startTelemetrySession(); - let recentPings = yield createSavedPings(RECENT_PINGS); - let expiredPings = yield createSavedPings(EXPIRED_PINGS, EXPIRED_PING_FILE_AGE); - let overduePings = yield createSavedPings(OVERDUE_PINGS, OVERDUE_PING_FILE_AGE); + let pingTypes = [ + { num: RECENT_PINGS }, + { num: EXPIRED_PINGS, age: EXPIRED_PING_FILE_AGE }, + { num: OVERDUE_PINGS, age: OVERDUE_PING_FILE_AGE }, + ]; + let pings = yield createSavedPings(pingTypes); + let recentPings = pings.slice(0, RECENT_PINGS); + let expiredPings = pings.slice(RECENT_PINGS, RECENT_PINGS + EXPIRED_PINGS); + let overduePings = pings.slice(-OVERDUE_PINGS); yield startTelemetry(); assertReceivedPings(TOTAL_EXPECTED_PINGS); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js new file mode 100644 index 0000000000..e82ecfd9aa --- /dev/null +++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js @@ -0,0 +1,953 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +*/ +/* This testcase triggers two telemetry pings. + * + * Telemetry code keeps histograms of past telemetry pings. The first + * ping populates these histograms. One of those histograms is then + * checked in the second request. + */ + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; +const Cr = Components.results; + +Cu.import("resource://testing-common/httpd.js", this); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/LightweightThemeManager.jsm", this); +Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); +Cu.import("resource://gre/modules/TelemetryPing.jsm", this); +Cu.import("resource://gre/modules/TelemetrySession.jsm", this); +Cu.import("resource://gre/modules/TelemetryFile.jsm", this); +Cu.import("resource://gre/modules/TelemetryEnvironment.jsm", this); +Cu.import("resource://gre/modules/Task.jsm", this); +Cu.import("resource://gre/modules/Promise.jsm", this); +Cu.import("resource://gre/modules/Preferences.jsm"); +Cu.import("resource://gre/modules/osfile.jsm", this); + +const PING_FORMAT_VERSION = 2; +const PING_TYPE_MAIN = "main"; + +const REASON_SAVED_SESSION = "saved-session"; +const REASON_TEST_PING = "test-ping"; +const REASON_DAILY = "daily"; +const REASON_ENVIRONMENT_CHANGE = "environment-change"; + +const PLATFORM_VERSION = "1.9.2"; +const APP_VERSION = "1"; +const APP_ID = "xpcshell@tests.mozilla.org"; +const APP_NAME = "XPCShell"; + +const IGNORE_HISTOGRAM = "test::ignore_me"; +const IGNORE_HISTOGRAM_TO_CLONE = "MEMORY_HEAP_ALLOCATED"; +const IGNORE_CLONED_HISTOGRAM = "test::ignore_me_also"; +const ADDON_NAME = "Telemetry test addon"; +const ADDON_HISTOGRAM = "addon-histogram"; +// Add some unicode characters here to ensure that sending them works correctly. +const SHUTDOWN_TIME = 10000; +const FAILED_PROFILE_LOCK_ATTEMPTS = 2; + +// Constants from prio.h for nsIFileOutputStream.init +const PR_WRONLY = 0x2; +const PR_CREATE_FILE = 0x8; +const PR_TRUNCATE = 0x20; +const RW_OWNER = parseInt("0600", 8); + +const NUMBER_OF_THREADS_TO_LAUNCH = 30; +let gNumberOfThreadsLaunched = 0; + +const SEC_IN_ONE_DAY = 24 * 60 * 60; +const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000; + +const PREF_BRANCH = "toolkit.telemetry."; +const PREF_ENABLED = PREF_BRANCH + "enabled"; +const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled"; +const PREF_FHR_SERVICE_ENABLED = "datareporting.healthreport.service.enabled"; + +const HAS_DATAREPORTINGSERVICE = "@mozilla.org/datareporting/service;1" in Cc; +const SESSION_RECORDER_EXPECTED = HAS_DATAREPORTINGSERVICE && + Preferences.get(PREF_FHR_SERVICE_ENABLED, true); + +const Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); + +let gHttpServer = new HttpServer(); +let gServerStarted = false; +let gRequestIterator = null; +let gDataReportingClientID = null; + +XPCOMUtils.defineLazyGetter(this, "gDatareportingService", + () => Cc["@mozilla.org/datareporting/service;1"] + .getService(Ci.nsISupports) + .wrappedJSObject); + +function sendPing() { + TelemetrySession.gatherStartup(); + if (gServerStarted) { + TelemetryPing.setServer("http://localhost:" + gHttpServer.identity.primaryPort); + return TelemetrySession.testPing(); + } else { + TelemetryPing.setServer("http://doesnotexist"); + return TelemetrySession.testPing(); + } +} + +function wrapWithExceptionHandler(f) { + function wrapper(...args) { + try { + f(...args); + } catch (ex if typeof(ex) == 'object') { + dump("Caught exception: " + ex.message + "\n"); + dump(ex.stack); + do_test_finished(); + } + } + return wrapper; +} + +function fakeNow(date) { + let session = Cu.import("resource://gre/modules/TelemetrySession.jsm"); + session.Policy.now = () => new Date(date.getTime()); +} + +function futureDate(date, offset) { + return new Date(date.getTime() + offset); +} + +function fakeNow(date) { + let session = Cu.import("resource://gre/modules/TelemetrySession.jsm"); + session.Policy.now = () => date; +} + +function registerPingHandler(handler) { + gHttpServer.registerPrefixHandler("/submit/telemetry/", + wrapWithExceptionHandler(handler)); +} + +function setupTestData() { + Telemetry.newHistogram(IGNORE_HISTOGRAM, "never", Telemetry.HISTOGRAM_BOOLEAN); + Telemetry.histogramFrom(IGNORE_CLONED_HISTOGRAM, IGNORE_HISTOGRAM_TO_CLONE); + Services.startup.interrupted = true; + Telemetry.registerAddonHistogram(ADDON_NAME, ADDON_HISTOGRAM, + Telemetry.HISTOGRAM_LINEAR, + 1, 5, 6); + let h1 = Telemetry.getAddonHistogram(ADDON_NAME, ADDON_HISTOGRAM); + h1.add(1); + let h2 = Telemetry.getHistogramById("TELEMETRY_TEST_COUNT"); + h2.add(); + + let k1 = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_COUNT"); + k1.add("a"); + k1.add("a"); + k1.add("b"); +} + +function getSavedPingFile(basename) { + let tmpDir = Services.dirsvc.get("ProfD", Ci.nsIFile); + let pingFile = tmpDir.clone(); + pingFile.append(basename); + if (pingFile.exists()) { + pingFile.remove(true); + } + do_register_cleanup(function () { + try { + pingFile.remove(true); + } catch (e) { + } + }); + return pingFile; +} + +function decodeRequestPayload(request) { + let s = request.bodyInputStream; + let payload = null; + let decoder = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON) + + if (request.getHeader("content-encoding") == "gzip") { + let observer = { + buffer: "", + onStreamComplete: function(loader, context, status, length, result) { + this.buffer = String.fromCharCode.apply(this, result); + } + }; + + let scs = Cc["@mozilla.org/streamConverters;1"] + .getService(Ci.nsIStreamConverterService); + let listener = Cc["@mozilla.org/network/stream-loader;1"] + .createInstance(Ci.nsIStreamLoader); + listener.init(observer); + let converter = scs.asyncConvertData("gzip", "uncompressed", + listener, null); + converter.onStartRequest(null, null); + converter.onDataAvailable(null, null, s, 0, s.available()); + converter.onStopRequest(null, null, null); + let unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] + .createInstance(Ci.nsIScriptableUnicodeConverter); + unicodeConverter.charset = "UTF-8"; + let utf8string = unicodeConverter.ConvertToUnicode(observer.buffer); + utf8string += unicodeConverter.Finish(); + payload = decoder.decode(utf8string); + } else { + payload = decoder.decodeFromStream(s, s.available()); + } + + return payload; +} + +function checkPingFormat(aPing, aType, aHasClientId, aHasEnvironment) { + const MANDATORY_PING_FIELDS = [ + "type", "id", "creationDate", "version", "application", "payload" + ]; + + const APPLICATION_TEST_DATA = { + buildId: "2007010101", + name: APP_NAME, + version: APP_VERSION, + vendor: "Mozilla", + platformVersion: PLATFORM_VERSION, + xpcomAbi: "noarch-spidermonkey", + }; + + // Check that the ping contains all the mandatory fields. + for (let f of MANDATORY_PING_FIELDS) { + Assert.ok(f in aPing, f + "must be available."); + } + + Assert.equal(aPing.type, aType, "The ping must have the correct type."); + Assert.equal(aPing.version, PING_FORMAT_VERSION, "The ping must have the correct version."); + + // Test the application section. + for (let f in APPLICATION_TEST_DATA) { + Assert.equal(aPing.application[f], APPLICATION_TEST_DATA[f], + f + " must have the correct value."); + } + + // We can't check the values for channel and architecture. Just make + // sure they are in. + Assert.ok("architecture" in aPing.application, + "The application section must have an architecture field."); + Assert.ok("channel" in aPing.application, + "The application section must have a channel field."); + + // Check the clientId and environment fields, as needed. + Assert.equal("clientId" in aPing, aHasClientId); + Assert.equal("environment" in aPing, aHasEnvironment); +} + +function checkPayload(payload, reason, successfulPings) { + Assert.ok(payload.simpleMeasurements.uptime >= 0); + Assert.equal(payload.simpleMeasurements.startupInterrupted, 1); + Assert.equal(payload.simpleMeasurements.shutdownDuration, SHUTDOWN_TIME); + Assert.equal(payload.simpleMeasurements.savedPings, 1); + Assert.ok("maximalNumberOfConcurrentThreads" in payload.simpleMeasurements); + Assert.ok(payload.simpleMeasurements.maximalNumberOfConcurrentThreads >= gNumberOfThreadsLaunched); + + let activeTicks = payload.simpleMeasurements.activeTicks; + Assert.ok(SESSION_RECORDER_EXPECTED ? activeTicks >= 0 : activeTicks == -1); + + Assert.equal(payload.simpleMeasurements.failedProfileLockCount, + FAILED_PROFILE_LOCK_ATTEMPTS); + let profileDirectory = Services.dirsvc.get("ProfD", Ci.nsIFile); + let failedProfileLocksFile = profileDirectory.clone(); + failedProfileLocksFile.append("Telemetry.FailedProfileLocks.txt"); + Assert.ok(!failedProfileLocksFile.exists()); + + + let isWindows = ("@mozilla.org/windows-registry-key;1" in Components.classes); + if (isWindows) { + Assert.ok(payload.simpleMeasurements.startupSessionRestoreReadBytes > 0); + Assert.ok(payload.simpleMeasurements.startupSessionRestoreWriteBytes > 0); + } + + const TELEMETRY_PING = "TELEMETRY_PING"; + const TELEMETRY_SUCCESS = "TELEMETRY_SUCCESS"; + const TELEMETRY_TEST_FLAG = "TELEMETRY_TEST_FLAG"; + const TELEMETRY_TEST_COUNT = "TELEMETRY_TEST_COUNT"; + const TELEMETRY_TEST_KEYED_FLAG = "TELEMETRY_TEST_KEYED_FLAG"; + const TELEMETRY_TEST_KEYED_COUNT = "TELEMETRY_TEST_KEYED_COUNT"; + const READ_SAVED_PING_SUCCESS = "READ_SAVED_PING_SUCCESS"; + + Assert.ok(TELEMETRY_PING in payload.histograms); + Assert.ok(READ_SAVED_PING_SUCCESS in payload.histograms); + Assert.ok(TELEMETRY_TEST_FLAG in payload.histograms); + Assert.ok(TELEMETRY_TEST_COUNT in payload.histograms); + + let rh = Telemetry.registeredHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, []); + for (let name of rh) { + if (/SQLITE/.test(name) && name in payload.histograms) { + let histogramName = ("STARTUP_" + name); + Assert.ok(histogramName in payload.histograms, histogramName + " must be available."); + } + } + Assert.ok(!(IGNORE_HISTOGRAM in payload.histograms)); + Assert.ok(!(IGNORE_CLONED_HISTOGRAM in payload.histograms)); + + // Flag histograms should automagically spring to life. + const expected_flag = { + range: [1, 2], + bucket_count: 3, + histogram_type: 3, + values: {0:1, 1:0}, + sum: 0, + sum_squares_lo: 0, + sum_squares_hi: 0 + }; + let flag = payload.histograms[TELEMETRY_TEST_FLAG]; + Assert.equal(uneval(flag), uneval(expected_flag)); + + // We should have a test count. + const expected_count = { + range: [1, 2], + bucket_count: 3, + histogram_type: 4, + values: {0:1, 1:0}, + sum: 1, + sum_squares_lo: 1, + sum_squares_hi: 0, + }; + let count = payload.histograms[TELEMETRY_TEST_COUNT]; + Assert.equal(uneval(count), uneval(expected_count)); + + // There should be one successful report from the previous telemetry ping. + const expected_tc = { + range: [1, 2], + bucket_count: 3, + histogram_type: 2, + values: {0:2, 1:successfulPings, 2:0}, + sum: successfulPings, + sum_squares_lo: successfulPings, + sum_squares_hi: 0 + }; + let tc = payload.histograms[TELEMETRY_SUCCESS]; + Assert.equal(uneval(tc), uneval(expected_tc)); + + let h = payload.histograms[READ_SAVED_PING_SUCCESS]; + Assert.equal(h.values[0], 1); + + // The ping should include data from memory reporters. We can't check that + // this data is correct, because we can't control the values returned by the + // memory reporters. But we can at least check that the data is there. + // + // It's important to check for the presence of reporters with a mix of units, + // because TelemetryPing has separate logic for each one. But we can't + // currently check UNITS_COUNT_CUMULATIVE or UNITS_PERCENTAGE because + // Telemetry doesn't touch a memory reporter with these units that's + // available on all platforms. + + Assert.ok('MEMORY_JS_GC_HEAP' in payload.histograms); // UNITS_BYTES + Assert.ok('MEMORY_JS_COMPARTMENTS_SYSTEM' in payload.histograms); // UNITS_COUNT + + // We should have included addon histograms. + Assert.ok("addonHistograms" in payload); + Assert.ok(ADDON_NAME in payload.addonHistograms); + Assert.ok(ADDON_HISTOGRAM in payload.addonHistograms[ADDON_NAME]); + + Assert.ok(("mainThread" in payload.slowSQL) && + ("otherThreads" in payload.slowSQL)); + + // Check keyed histogram payload. + + Assert.ok("keyedHistograms" in payload); + let keyedHistograms = payload.keyedHistograms; + Assert.ok(TELEMETRY_TEST_KEYED_FLAG in keyedHistograms); + Assert.ok(TELEMETRY_TEST_KEYED_COUNT in keyedHistograms); + + Assert.deepEqual({}, keyedHistograms[TELEMETRY_TEST_KEYED_FLAG]); + + const expected_keyed_count = { + "a": { + range: [1, 2], + bucket_count: 3, + histogram_type: 4, + values: {0:2, 1:0}, + sum: 2, + sum_squares_lo: 2, + sum_squares_hi: 0, + }, + "b": { + range: [1, 2], + bucket_count: 3, + histogram_type: 4, + values: {0:1, 1:0}, + sum: 1, + sum_squares_lo: 1, + sum_squares_hi: 0, + }, + }; + Assert.deepEqual(expected_keyed_count, keyedHistograms[TELEMETRY_TEST_KEYED_COUNT]); +} + +function writeStringToFile(file, contents) { + let ostream = Cc["@mozilla.org/network/safe-file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + ostream.init(file, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, + RW_OWNER, ostream.DEFER_OPEN); + ostream.write(contents, contents.length); + ostream.QueryInterface(Ci.nsISafeOutputStream).finish(); + ostream.close(); +} + +function write_fake_shutdown_file() { + let profileDirectory = Services.dirsvc.get("ProfD", Ci.nsIFile); + let file = profileDirectory.clone(); + file.append("Telemetry.ShutdownTime.txt"); + let contents = "" + SHUTDOWN_TIME; + writeStringToFile(file, contents); +} + +function write_fake_failedprofilelocks_file() { + let profileDirectory = Services.dirsvc.get("ProfD", Ci.nsIFile); + let file = profileDirectory.clone(); + file.append("Telemetry.FailedProfileLocks.txt"); + let contents = "" + FAILED_PROFILE_LOCK_ATTEMPTS; + writeStringToFile(file, contents); +} + +function run_test() { + do_test_pending(); + + // Addon manager needs a profile directory + do_get_profile(); + loadAddonManager(APP_ID, APP_NAME, APP_VERSION, PLATFORM_VERSION); + + Services.prefs.setBoolPref(PREF_ENABLED, true); + Services.prefs.setBoolPref(PREF_FHR_UPLOAD_ENABLED, true); + + // Send the needed startup notifications to the datareporting service + // to ensure that it has been initialized. + if (HAS_DATAREPORTINGSERVICE) { + gDatareportingService.observe(null, "app-startup", null); + gDatareportingService.observe(null, "profile-after-change", null); + } + + // Make it look like we've previously failed to lock a profile a couple times. + write_fake_failedprofilelocks_file(); + + // Make it look like we've shutdown before. + write_fake_shutdown_file(); + + let currentMaxNumberOfThreads = Telemetry.maximalNumberOfConcurrentThreads; + do_check_true(currentMaxNumberOfThreads > 0); + + // Try to augment the maximal number of threads currently launched + let threads = []; + try { + for (let i = 0; i < currentMaxNumberOfThreads + 10; ++i) { + threads.push(Services.tm.newThread(0)); + } + } catch (ex) { + // If memory is too low, it is possible that not all threads will be launched. + } + gNumberOfThreadsLaunched = threads.length; + + do_check_true(Telemetry.maximalNumberOfConcurrentThreads >= gNumberOfThreadsLaunched); + + do_register_cleanup(function() { + threads.forEach(function(thread) { + thread.shutdown(); + }); + }); + + Telemetry.asyncFetchTelemetryData(wrapWithExceptionHandler(run_next_test)); +} + +add_task(function* asyncSetup() { + yield TelemetrySession.setup(); + yield TelemetryPing.setup(); + + if (HAS_DATAREPORTINGSERVICE) { + // force getSessionRecorder()==undefined to check the payload's activeTicks + gDatareportingService.simulateNoSessionRecorder(); + } + + // When no DRS or no DRS.getSessionRecorder(), activeTicks should be -1. + do_check_eq(TelemetrySession.getPayload().simpleMeasurements.activeTicks, -1); + + if (HAS_DATAREPORTINGSERVICE) { + // Restore normal behavior for getSessionRecorder() + gDatareportingService.simulateRestoreSessionRecorder(); + + gDataReportingClientID = yield gDatareportingService.getClientID(); + + // We should have cached the client id now. Lets confirm that by + // checking the client id before the async ping setup is finished. + let promisePingSetup = TelemetryPing.reset(); + do_check_eq(TelemetryPing.clientID, gDataReportingClientID); + yield promisePingSetup; + } +}); + +// Ensures that expired histograms are not part of the payload. +add_task(function* test_expiredHistogram() { + let histogram_id = "FOOBAR"; + let dummy = Telemetry.newHistogram(histogram_id, "30", Telemetry.HISTOGRAM_EXPONENTIAL, 1, 2, 3); + + dummy.add(1); + + do_check_eq(TelemetrySession.getPayload()["histograms"][histogram_id], undefined); + do_check_eq(TelemetrySession.getPayload()["histograms"]["TELEMETRY_TEST_EXPIRED"], undefined); +}); + +// Checks that an invalid histogram file is deleted if TelemetryFile fails to parse it. +add_task(function* test_runInvalidJSON() { + let pingFile = getSavedPingFile("invalid-histograms.dat"); + + writeStringToFile(pingFile, "this.is.invalid.JSON"); + do_check_true(pingFile.exists()); + + yield TelemetryFile.testLoadHistograms(pingFile); + do_check_false(pingFile.exists()); +}); + +// Sends a ping to a non existing server. If we remove this test, we won't get +// all the histograms we need in the main ping. +add_task(function* test_noServerPing() { + yield sendPing(); + // We need two pings in order to make sure STARTUP_MEMORY_STORAGE_SQLIE histograms + // are initialised. See bug 1131585. + yield sendPing(); +}); + +// Checks that a sent ping is correctly received by a dummy http server. +add_task(function* test_simplePing() { + gHttpServer.start(-1); + gServerStarted = true; + gRequestIterator = Iterator(new Request()); + + yield sendPing(); + let request = yield gRequestIterator.next(); + let ping = decodeRequestPayload(request); + + checkPingFormat(ping, PING_TYPE_MAIN, true, true); +}); + +// Saves the current session histograms, reloads them, performs a ping +// and checks that the dummy http server received both the previously +// saved histograms and the new ones. +add_task(function* test_saveLoadPing() { + let histogramsFile = getSavedPingFile("saved-histograms.dat"); + + setupTestData(); + yield TelemetrySession.testSaveHistograms(histogramsFile); + yield TelemetryFile.testLoadHistograms(histogramsFile); + yield sendPing(); + + // Get requests received by dummy server. + let request1 = yield gRequestIterator.next(); + let request2 = yield gRequestIterator.next(); + + Assert.equal(request1.getHeader("content-type"), "application/json; charset=UTF-8", + "The request must have the correct content-type."); + Assert.equal(request2.getHeader("content-type"), "application/json; charset=UTF-8", + "The request must have the correct content-type."); + + // We decode both requests to check for the |reason|. + let ping1 = decodeRequestPayload(request1); + let ping2 = decodeRequestPayload(request2); + + checkPingFormat(ping1, PING_TYPE_MAIN, true, true); + checkPingFormat(ping2, PING_TYPE_MAIN, true, true); + + // Check we have the correct two requests. Ordering is not guaranteed. + if (ping1.payload.info.reason === REASON_TEST_PING) { + // Until we change MainPing according to bug 1120982, common ping payload + // will contain another nested payload. + checkPayload(ping1.payload, REASON_TEST_PING, 1); + checkPayload(ping2.payload, REASON_SAVED_SESSION, 1); + } else { + checkPayload(ping1.payload, REASON_SAVED_SESSION, 1); + checkPayload(ping2.payload, REASON_TEST_PING, 1); + } +}); + +add_task(function* test_checkSubsession() { + let now = new Date(2020, 1, 1, 12, 0, 0); + let expectedDate = new Date(2020, 1, 1, 0, 0, 0); + fakeNow(now); + TelemetrySession.setup(); + + const COUNT_ID = "TELEMETRY_TEST_COUNT"; + const KEYED_ID = "TELEMETRY_TEST_KEYED_COUNT"; + const count = Telemetry.getHistogramById(COUNT_ID); + const keyed = Telemetry.getKeyedHistogramById(KEYED_ID); + const registeredIds = + new Set(Telemetry.registeredHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, [])); + + const stableHistograms = new Set([ + "TELEMETRY_TEST_FLAG", + "TELEMETRY_TEST_COUNT", + "TELEMETRY_TEST_RELEASE_OPTOUT", + "TELEMETRY_TEST_RELEASE_OPTIN", + "STARTUP_CRASH_DETECTED", + ]); + + const stableKeyedHistograms = new Set([ + "TELEMETRY_TEST_KEYED_FLAG", + "TELEMETRY_TEST_KEYED_COUNT", + "TELEMETRY_TEST_KEYED_RELEASE_OPTIN", + "TELEMETRY_TEST_KEYED_RELEASE_OPTOUT", + ]); + + // Compare the two sets of histograms. + // The "subsession" histograms should match the registered + // "classic" histograms. However, histograms can change + // between us collecting the different payloads, so we only + // check for deep equality on known stable histograms. + checkHistograms = (classic, subsession) => { + for (let id of Object.keys(classic)) { + if (!registeredIds.has(id)) { + continue; + } + + Assert.ok(id in subsession); + if (stableHistograms.has(id)) { + Assert.deepEqual(classic[id], + subsession[id]); + } else { + Assert.equal(classic[id].histogram_type, + subsession[id].histogram_type); + } + } + }; + + // Same as above, except for keyed histograms. + checkKeyedHistograms = (classic, subsession) => { + for (let id of Object.keys(classic)) { + if (!registeredIds.has(id)) { + continue; + } + + Assert.ok(id in subsession); + if (stableKeyedHistograms.has(id)) { + Assert.deepEqual(classic[id], + subsession[id]); + } + } + }; + + // Both classic and subsession payload histograms should start the same. + // The payloads should be identical for now except for the reason. + count.clear(); + keyed.clear(); + let classic = TelemetrySession.getPayload(); + let subsession = TelemetrySession.getPayload("environment-change"); + + Assert.equal(classic.info.reason, "gather-payload"); + Assert.equal(subsession.info.reason, "environment-change"); + Assert.ok(!(COUNT_ID in classic.histograms)); + Assert.ok(!(COUNT_ID in subsession.histograms)); + Assert.ok(KEYED_ID in classic.keyedHistograms); + Assert.ok(KEYED_ID in subsession.keyedHistograms); + Assert.deepEqual(classic.keyedHistograms[KEYED_ID], {}); + Assert.deepEqual(subsession.keyedHistograms[KEYED_ID], {}); + + checkHistograms(classic.histograms, subsession.histograms); + checkKeyedHistograms(classic.keyedHistograms, subsession.keyedHistograms); + + // Adding values should get picked up in both. + count.add(1); + keyed.add("a", 1); + keyed.add("b", 1); + classic = TelemetrySession.getPayload(); + subsession = TelemetrySession.getPayload("environment-change"); + + Assert.ok(COUNT_ID in classic.histograms); + Assert.ok(COUNT_ID in subsession.histograms); + Assert.ok(KEYED_ID in classic.keyedHistograms); + Assert.ok(KEYED_ID in subsession.keyedHistograms); + Assert.equal(classic.histograms[COUNT_ID].sum, 1); + Assert.equal(classic.keyedHistograms[KEYED_ID]["a"].sum, 1); + Assert.equal(classic.keyedHistograms[KEYED_ID]["b"].sum, 1); + + checkHistograms(classic.histograms, subsession.histograms); + checkKeyedHistograms(classic.keyedHistograms, subsession.keyedHistograms); + + // Values should still reset properly. + count.clear(); + keyed.clear(); + classic = TelemetrySession.getPayload(); + subsession = TelemetrySession.getPayload("environment-change"); + + Assert.ok(!(COUNT_ID in classic.histograms)); + Assert.ok(!(COUNT_ID in subsession.histograms)); + Assert.ok(KEYED_ID in classic.keyedHistograms); + Assert.ok(KEYED_ID in subsession.keyedHistograms); + Assert.deepEqual(classic.keyedHistograms[KEYED_ID], {}); + + checkHistograms(classic.histograms, subsession.histograms); + checkKeyedHistograms(classic.keyedHistograms, subsession.keyedHistograms); + + // Adding values should get picked up in both. + count.add(1); + keyed.add("a", 1); + keyed.add("b", 1); + classic = TelemetrySession.getPayload(); + subsession = TelemetrySession.getPayload("environment-change"); + + Assert.ok(COUNT_ID in classic.histograms); + Assert.ok(COUNT_ID in subsession.histograms); + Assert.ok(KEYED_ID in classic.keyedHistograms); + Assert.ok(KEYED_ID in subsession.keyedHistograms); + Assert.equal(classic.histograms[COUNT_ID].sum, 1); + Assert.equal(classic.keyedHistograms[KEYED_ID]["a"].sum, 1); + Assert.equal(classic.keyedHistograms[KEYED_ID]["b"].sum, 1); + + checkHistograms(classic.histograms, subsession.histograms); + checkKeyedHistograms(classic.keyedHistograms, subsession.keyedHistograms); + + // We should be able to reset only the subsession histograms. + // First check that "snapshot and clear" still returns the old state... + classic = TelemetrySession.getPayload(); + subsession = TelemetrySession.getPayload("environment-change", true); + + let subsessionStartDate = new Date(classic.info.subsessionStartDate); + Assert.equal(subsessionStartDate.toISOString(), expectedDate.toISOString()); + subsessionStartDate = new Date(subsession.info.subsessionStartDate); + Assert.equal(subsessionStartDate.toISOString(), expectedDate.toISOString()); + checkHistograms(classic.histograms, subsession.histograms); + checkKeyedHistograms(classic.keyedHistograms, subsession.keyedHistograms); + + // ... then check that the next snapshot shows the subsession + // histograms got reset. + classic = TelemetrySession.getPayload(); + subsession = TelemetrySession.getPayload("environment-change"); + + Assert.ok(COUNT_ID in classic.histograms); + Assert.ok(COUNT_ID in subsession.histograms); + Assert.equal(classic.histograms[COUNT_ID].sum, 1); + Assert.equal(subsession.histograms[COUNT_ID].sum, 0); + + Assert.ok(KEYED_ID in classic.keyedHistograms); + Assert.ok(KEYED_ID in subsession.keyedHistograms); + Assert.equal(classic.keyedHistograms[KEYED_ID]["a"].sum, 1); + Assert.equal(classic.keyedHistograms[KEYED_ID]["b"].sum, 1); + Assert.deepEqual(subsession.keyedHistograms[KEYED_ID], {}); + + // Adding values should get picked up in both again. + count.add(1); + keyed.add("a", 1); + keyed.add("b", 1); + classic = TelemetrySession.getPayload(); + subsession = TelemetrySession.getPayload("environment-change"); + + Assert.ok(COUNT_ID in classic.histograms); + Assert.ok(COUNT_ID in subsession.histograms); + Assert.equal(classic.histograms[COUNT_ID].sum, 2); + Assert.equal(subsession.histograms[COUNT_ID].sum, 1); + + Assert.ok(KEYED_ID in classic.keyedHistograms); + Assert.ok(KEYED_ID in subsession.keyedHistograms); + Assert.equal(classic.keyedHistograms[KEYED_ID]["a"].sum, 2); + Assert.equal(classic.keyedHistograms[KEYED_ID]["b"].sum, 2); + Assert.equal(subsession.keyedHistograms[KEYED_ID]["a"].sum, 1); + Assert.equal(subsession.keyedHistograms[KEYED_ID]["b"].sum, 1); +}); + +add_task(function* test_dailyCollection() { + let now = new Date(2030, 1, 1, 12, 0, 0); + let nowDay = new Date(2030, 1, 1, 0, 0, 0); + let timerCallback = null; + let timerDelay = null; + + gRequestIterator = Iterator(new Request()); + + fakeNow(now); + fakeDailyTimers((callback, timeout) => { + dump("fake setDailyTimeout(" + callback + ", " + timeout + ")\n"); + timerCallback = callback; + timerDelay = timeout; + return 1; + }, () => {}); + + // Init and check timer. + yield TelemetrySession.setup(); + TelemetryPing.setServer("http://localhost:" + gHttpServer.identity.primaryPort); + + Assert.ok(!!timerCallback); + Assert.ok(Number.isFinite(timerDelay)); + let timerDate = futureDate(now, timerDelay); + let expectedDate = futureDate(nowDay, MS_IN_ONE_DAY); + Assert.equal(timerDate.toISOString(), expectedDate.toISOString()); + + // Set histograms to expected state. + const COUNT_ID = "TELEMETRY_TEST_COUNT"; + const KEYED_ID = "TELEMETRY_TEST_KEYED_COUNT"; + const count = Telemetry.getHistogramById(COUNT_ID); + const keyed = Telemetry.getKeyedHistogramById(KEYED_ID); + + count.clear(); + keyed.clear(); + count.add(1); + keyed.add("a", 1); + keyed.add("b", 1); + keyed.add("b", 1); + + // Trigger and collect daily ping. + yield timerCallback(); + let request = yield gRequestIterator.next(); + Assert.ok(!!request); + let ping = decodeRequestPayload(request); + + Assert.equal(ping.type, PING_TYPE_MAIN); + Assert.equal(ping.payload.info.reason, REASON_DAILY); + let subsessionStartDate = new Date(ping.payload.info.subsessionStartDate); + Assert.equal(subsessionStartDate.toISOString(), nowDay.toISOString()); + + Assert.equal(ping.payload.histograms[COUNT_ID].sum, 1); + Assert.equal(ping.payload.keyedHistograms[KEYED_ID]["a"].sum, 1); + Assert.equal(ping.payload.keyedHistograms[KEYED_ID]["b"].sum, 2); + + // Trigger and collect another ping. The histograms should be reset. + yield timerCallback(); + request = yield gRequestIterator.next(); + Assert.ok(!!request); + ping = decodeRequestPayload(request); + + Assert.equal(ping.type, PING_TYPE_MAIN); + Assert.equal(ping.payload.info.reason, REASON_DAILY); + subsessionStartDate = new Date(ping.payload.info.subsessionStartDate); + Assert.equal(subsessionStartDate.toISOString(), nowDay.toISOString()); + + Assert.equal(ping.payload.histograms[COUNT_ID].sum, 0); + Assert.deepEqual(ping.payload.keyedHistograms[KEYED_ID], {}); + + // Trigger and collect another daily ping, with the histograms being set again. + count.add(1); + keyed.add("a", 1); + keyed.add("b", 1); + + yield timerCallback(); + request = yield gRequestIterator.next(); + Assert.ok(!!request); + ping = decodeRequestPayload(request); + + Assert.equal(ping.type, PING_TYPE_MAIN); + Assert.equal(ping.payload.info.reason, REASON_DAILY); + subsessionStartDate = new Date(ping.payload.info.subsessionStartDate); + Assert.equal(subsessionStartDate.toISOString(), nowDay.toISOString()); + + Assert.equal(ping.payload.histograms[COUNT_ID].sum, 1); + Assert.equal(ping.payload.keyedHistograms[KEYED_ID]["a"].sum, 1); + Assert.equal(ping.payload.keyedHistograms[KEYED_ID]["b"].sum, 1); +}); + +add_task(function* test_environmentChange() { + let now = new Date(2040, 1, 1, 12, 0, 0); + let nowDay = new Date(2040, 1, 1, 0, 0, 0); + let timerCallback = null; + let timerDelay = null; + + gRequestIterator = Iterator(new Request()); + + fakeNow(now); + fakeDailyTimers(() => {}, () => {}); + + const PREF_TEST = "toolkit.telemetry.test.pref1"; + Preferences.reset(PREF_TEST); + let prefsToWatch = {}; + prefsToWatch[PREF_TEST] = TelemetryEnvironment.RECORD_PREF_VALUE; + + // Setup. + yield TelemetrySession.setup(); + TelemetryPing.setServer("http://localhost:" + gHttpServer.identity.primaryPort); + TelemetryEnvironment._watchPreferences(prefsToWatch); + + // Set histograms to expected state. + const COUNT_ID = "TELEMETRY_TEST_COUNT"; + const KEYED_ID = "TELEMETRY_TEST_KEYED_COUNT"; + const count = Telemetry.getHistogramById(COUNT_ID); + const keyed = Telemetry.getKeyedHistogramById(KEYED_ID); + + count.clear(); + keyed.clear(); + count.add(1); + keyed.add("a", 1); + keyed.add("b", 1); + + // Trigger and collect environment-change ping. + Preferences.set(PREF_TEST, 1); + let request = yield gRequestIterator.next(); + Assert.ok(!!request); + let ping = decodeRequestPayload(request); + + Assert.equal(ping.type, PING_TYPE_MAIN); + Assert.equal(ping.environment.settings.userPrefs[PREF_TEST], 1); + Assert.equal(ping.payload.info.reason, REASON_ENVIRONMENT_CHANGE); + let subsessionStartDate = new Date(ping.payload.info.subsessionStartDate); + Assert.equal(subsessionStartDate.toISOString(), nowDay.toISOString()); + + Assert.equal(ping.payload.histograms[COUNT_ID].sum, 1); + Assert.equal(ping.payload.keyedHistograms[KEYED_ID]["a"].sum, 1); + + // Trigger and collect another ping. The histograms should be reset. + Preferences.set(PREF_TEST, 2); + request = yield gRequestIterator.next(); + Assert.ok(!!request); + ping = decodeRequestPayload(request); + + Assert.equal(ping.type, PING_TYPE_MAIN); + Assert.equal(ping.environment.settings.userPrefs[PREF_TEST], 2); + Assert.equal(ping.payload.info.reason, REASON_ENVIRONMENT_CHANGE); + subsessionStartDate = new Date(ping.payload.info.subsessionStartDate); + Assert.equal(subsessionStartDate.toISOString(), nowDay.toISOString()); + + Assert.equal(ping.payload.histograms[COUNT_ID].sum, 0); + Assert.deepEqual(ping.payload.keyedHistograms[KEYED_ID], {}); +}); + +// Checks that an expired histogram file is deleted when loaded. +add_task(function* test_runOldPingFile() { + let histogramsFile = getSavedPingFile("old-histograms.dat"); + + yield TelemetrySession.testSaveHistograms(histogramsFile); + do_check_true(histogramsFile.exists()); + let mtime = histogramsFile.lastModifiedTime; + histogramsFile.lastModifiedTime = mtime - (14 * 24 * 60 * 60 * 1000 + 60000); // 14 days, 1m + + yield TelemetryFile.testLoadHistograms(histogramsFile); + do_check_false(histogramsFile.exists()); +}); + +add_task(function* test_savedSessionClientID() { + // Assure that we store the ping properly when saving sessions on shutdown. + // We make the TelemetrySession shutdown to trigger a session save. + const dir = TelemetryFile.pingDirectoryPath; + yield OS.File.removeDir(dir, {ignoreAbsent: true}); + yield OS.File.makeDir(dir); + yield TelemetrySession.shutdown(); + + yield TelemetryFile.loadSavedPings(); + Assert.equal(TelemetryFile.pingsLoaded, 1); + let ping = TelemetryFile.popPendingPings().next(); + Assert.equal(ping.value.clientId, gDataReportingClientID); +}); + +add_task(function* stopServer(){ + gHttpServer.stop(do_test_finished); +}); + +// An iterable sequence of http requests +function Request() { + let defers = []; + let current = 0; + + function RequestIterator() {} + + // Returns a promise that resolves to the next http request + RequestIterator.prototype.next = function() { + let deferred = defers[current++]; + return deferred.promise; + } + + this.__iterator__ = function(){ + return new RequestIterator(); + } + + registerPingHandler((request, response) => { + let deferred = defers[defers.length - 1]; + defers.push(Promise.defer()); + deferred.resolve(request); + }); + + defers.push(Promise.defer()); +} diff --git a/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js b/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js index d0d1ecf733..9719ba87b9 100644 --- a/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js +++ b/toolkit/components/telemetry/tests/unit/test_nsITelemetry.js @@ -355,10 +355,10 @@ function test_addons() { function test_privateMode() { var h = Telemetry.newHistogram("test::private_mode_boolean", "never", Telemetry.HISTOGRAM_BOOLEAN); var orig = h.snapshot(); - Telemetry.canRecord = false; + Telemetry.canRecordExtended = false; h.add(1); do_check_eq(uneval(orig), uneval(h.snapshot())); - Telemetry.canRecord = true; + Telemetry.canRecordExtended = true; h.add(1); do_check_neq(uneval(orig), uneval(h.snapshot())); } @@ -391,7 +391,7 @@ function numberRange(lower, upper) function test_keyed_boolean_histogram() { const KEYED_ID = "test::keyed::boolean"; - KEYS = ["key"+(i+1) for (i of numberRange(0, 2))]; + let KEYS = ["key"+(i+1) for (i of numberRange(0, 2))]; KEYS.push("漢語"); let histogramBase = { "min": 1, @@ -605,6 +605,134 @@ function test_datasets() Assert.ok(registered.has("TELEMETRY_TEST_KEYED_RELEASE_OPTOUT")); } +function test_subsession() { + const ID = "TELEMETRY_TEST_COUNT"; + const FLAG = "TELEMETRY_TEST_FLAG"; + let h = Telemetry.getHistogramById(ID); + let flag = Telemetry.getHistogramById(FLAG); + + // Both original and duplicate should start out the same. + h.clear(); + let snapshot = Telemetry.histogramSnapshots; + let subsession = Telemetry.snapshotSubsessionHistograms(); + Assert.ok(!(ID in snapshot)); + Assert.ok(!(ID in subsession)); + + // They should instantiate and pick-up the count. + h.add(1); + snapshot = Telemetry.histogramSnapshots; + subsession = Telemetry.snapshotSubsessionHistograms(); + Assert.ok(ID in snapshot); + Assert.ok(ID in subsession); + Assert.equal(snapshot[ID].sum, 1); + Assert.equal(subsession[ID].sum, 1); + + // They should still reset properly. + h.clear(); + snapshot = Telemetry.histogramSnapshots; + subsession = Telemetry.snapshotSubsessionHistograms(); + Assert.ok(!(ID in snapshot)); + Assert.ok(!(ID in subsession)); + + // Both should instantiate and pick-up the count. + h.add(1); + snapshot = Telemetry.histogramSnapshots; + subsession = Telemetry.snapshotSubsessionHistograms(); + Assert.equal(snapshot[ID].sum, 1); + Assert.equal(subsession[ID].sum, 1); + + // Check that we are able to only reset the duplicate histogram. + h.clear(true); + snapshot = Telemetry.histogramSnapshots; + subsession = Telemetry.snapshotSubsessionHistograms(); + Assert.ok(ID in snapshot); + Assert.ok(ID in subsession); + Assert.equal(snapshot[ID].sum, 1); + Assert.equal(subsession[ID].sum, 0); + + // Both should register the next count. + h.add(1); + snapshot = Telemetry.histogramSnapshots; + subsession = Telemetry.snapshotSubsessionHistograms(); + Assert.equal(snapshot[ID].sum, 2); + Assert.equal(subsession[ID].sum, 1); + + // Retrieve a subsession snapshot and pass the flag to + // clear subsession histograms too. + h.clear(); + flag.clear(); + h.add(1); + flag.add(1); + snapshot = Telemetry.histogramSnapshots; + subsession = Telemetry.snapshotSubsessionHistograms(true); + Assert.ok(ID in snapshot); + Assert.ok(ID in subsession); + Assert.ok(FLAG in snapshot); + Assert.ok(FLAG in subsession); + Assert.equal(snapshot[ID].sum, 1); + Assert.equal(subsession[ID].sum, 1); + Assert.equal(snapshot[FLAG].sum, 1); + Assert.equal(subsession[FLAG].sum, 1); + + // The next subsesssion snapshot should show the histograms + // got reset. + snapshot = Telemetry.histogramSnapshots; + subsession = Telemetry.snapshotSubsessionHistograms(); + Assert.ok(ID in snapshot); + Assert.ok(ID in subsession); + Assert.ok(FLAG in snapshot); + Assert.ok(FLAG in subsession); + Assert.equal(snapshot[ID].sum, 1); + Assert.equal(subsession[ID].sum, 0); + Assert.equal(snapshot[FLAG].sum, 1); + Assert.equal(subsession[FLAG].sum, 0); +} + +function test_keyed_subsession() { + let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_FLAG"); + const KEY = "foo"; + + // Both original and subsession should start out the same. + h.clear(); + Assert.ok(!(KEY in h.snapshot())); + Assert.ok(!(KEY in h.subsessionSnapshot())); + Assert.equal(h.snapshot(KEY).sum, 0); + Assert.equal(h.subsessionSnapshot(KEY).sum, 0); + + // Both should register the flag. + h.add(KEY, 1); + Assert.ok(KEY in h.snapshot()); + Assert.ok(KEY in h.subsessionSnapshot()); + Assert.equal(h.snapshot(KEY).sum, 1); + Assert.equal(h.subsessionSnapshot(KEY).sum, 1); + + // Check that we are able to only reset the subsession histogram. + h.clear(true); + Assert.ok(KEY in h.snapshot()); + Assert.ok(!(KEY in h.subsessionSnapshot())); + Assert.equal(h.snapshot(KEY).sum, 1); + Assert.equal(h.subsessionSnapshot(KEY).sum, 0); + + // Setting the flag again should make both match again. + h.add(KEY, 1); + Assert.ok(KEY in h.snapshot()); + Assert.ok(KEY in h.subsessionSnapshot()); + Assert.equal(h.snapshot(KEY).sum, 1); + Assert.equal(h.subsessionSnapshot(KEY).sum, 1); + + // Check that "snapshot and clear" works properly. + let snapshot = h.snapshot(); + let subsession = h.snapshotSubsessionAndClear(); + Assert.ok(KEY in snapshot); + Assert.ok(KEY in subsession); + Assert.equal(snapshot[KEY].sum, 1); + Assert.equal(subsession[KEY].sum, 1); + + subsession = h.subsessionSnapshot(); + Assert.ok(!(KEY in subsession)); + Assert.equal(h.subsessionSnapshot(KEY).sum, 0); +} + function generateUUID() { let str = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString(); // strip {} @@ -640,4 +768,6 @@ function run_test() test_expired_histogram(); test_keyed_histogram(); test_datasets(); + test_subsession(); + test_keyed_subsession(); } diff --git a/toolkit/components/telemetry/tests/unit/xpcshell.ini b/toolkit/components/telemetry/tests/unit/xpcshell.ini index 9c8dece6eb..e71cdda797 100644 --- a/toolkit/components/telemetry/tests/unit/xpcshell.ini +++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini @@ -4,6 +4,7 @@ tail = skip-if = toolkit == 'gonk' [test_nsITelemetry.js] +[test_TelemetryEnvironment.js] [test_TelemetryFlagClear.js] [test_TelemetryLateWrites.js] [test_TelemetryLockCount.js] @@ -17,5 +18,6 @@ skip-if = toolkit == 'gonk' [test_ThirdPartyCookieProbe.js] [test_TelemetrySendOldPings.js] skip-if = debug == true || os == "android" # Disabled due to intermittent orange on Android +[test_TelemetrySession.js] [test_ThreadHangStats.js] run-sequentially = Bug 1046307, test can fail intermittently when CPU load is high diff --git a/toolkit/components/terminator/nsTerminator.cpp b/toolkit/components/terminator/nsTerminator.cpp index 552a3573e4..99d80973e6 100644 --- a/toolkit/components/terminator/nsTerminator.cpp +++ b/toolkit/components/terminator/nsTerminator.cpp @@ -389,7 +389,7 @@ void nsTerminator::StartWriter() { - if (!Telemetry::CanRecord()) { + if (!Telemetry::CanRecordExtended()) { return; } nsCOMPtr profLD; @@ -473,7 +473,7 @@ nsTerminator::UpdateHeartbeat(const char* aTopic) void nsTerminator::UpdateTelemetry() { - if (!Telemetry::CanRecord() || !gWriteReady) { + if (!Telemetry::CanRecordExtended() || !gWriteReady) { return; } diff --git a/toolkit/modules/tests/xpcshell/test_TelemetryTimestamps.js b/toolkit/modules/tests/xpcshell/test_TelemetryTimestamps.js index 1916477990..54fd2b63f5 100644 --- a/toolkit/modules/tests/xpcshell/test_TelemetryTimestamps.js +++ b/toolkit/modules/tests/xpcshell/test_TelemetryTimestamps.js @@ -65,7 +65,7 @@ add_task(function* actualTest() { do_check_true(simpleMeasurements.bar > 1); // bar was included do_check_eq(undefined, simpleMeasurements.baz); // baz wasn't included since it wasn't added - yield TelemetrySession.shutdown(); + yield TelemetrySession.shutdown(false); do_test_finished(); }); diff --git a/toolkit/mozapps/extensions/test/browser/browser_bug557956.js b/toolkit/mozapps/extensions/test/browser/browser_bug557956.js index 919564b45c..99e0b9f40a 100644 --- a/toolkit/mozapps/extensions/test/browser/browser_bug557956.js +++ b/toolkit/mozapps/extensions/test/browser/browser_bug557956.js @@ -194,6 +194,11 @@ function check_telemetry({disabled, metaenabled, metadisabled, upgraded, failed, } add_test(function test_setup() { + let oldCanRecord = Services.telemetry.canRecordExtended; + Services.telemetry.canRecordExtended = true; + registerCleanupFunction(function () { + Services.telemetry.canRecordExtended = oldCanRecord; + }); TelemetrySession.setup().then(run_next_test); }); @@ -514,5 +519,5 @@ add_test(function overrides_retrieved() { }); add_test(function test_shutdown() { - TelemetrySession.shutdown().then(run_next_test); + TelemetrySession.shutdown(false).then(run_next_test); }); diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index e868c2a716..078452f6f5 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -56,6 +56,7 @@ #include "nsICommandLineRunner.h" #include "nsIComponentManager.h" #include "nsIComponentRegistrar.h" +#include "nsIConsoleService.h" #include "nsIContentHandler.h" #include "nsIDialogParamBlock.h" #include "nsIDOMWindow.h" @@ -1475,7 +1476,15 @@ RemoteCommandLine(const char* aDesktopStartupID) gArgc, gArgv, aDesktopStartupID, getter_Copies(response), &success); // did the command fail? - if (NS_FAILED(rv) || !success) + if (!success) + return REMOTE_NOT_FOUND; + + // The "command not parseable" error is returned when the + // nsICommandLineHandler throws a NS_ERROR_ABORT. + if (response.EqualsLiteral("500 command not parseable")) + return REMOTE_ARG_BAD; + + if (NS_FAILED(rv)) return REMOTE_NOT_FOUND; return REMOTE_FOUND; diff --git a/tools/profiler/GoannaProfiler.h b/tools/profiler/GoannaProfiler.h index fbcfe9dacf..86159fa064 100644 --- a/tools/profiler/GoannaProfiler.h +++ b/tools/profiler/GoannaProfiler.h @@ -50,6 +50,8 @@ #define SAMPLER_H #include "js/TypeDecls.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/GuardObjects.h" namespace mozilla { class TimeStamp; @@ -63,7 +65,7 @@ enum TracingMetadata { TRACING_EVENT_BACKTRACE }; -#ifndef MOZ_ENABLE_PROFILER_SPS +#if !defined(MOZ_ENABLE_PROFILER_SPS) || defined(MOZILLA_XPCOMRT_API) #include #include @@ -230,4 +232,6 @@ public: } }; + + #endif // ifndef SAMPLER_H diff --git a/tools/profiler/GoannaProfilerImpl.h b/tools/profiler/GoannaProfilerImpl.h index 0b4114c798..1266605e5a 100644 --- a/tools/profiler/GoannaProfilerImpl.h +++ b/tools/profiler/GoannaProfilerImpl.h @@ -9,6 +9,8 @@ #include #include "mozilla/ThreadLocal.h" #include "mozilla/Assertions.h" +#include "mozilla/GuardObjects.h" +#include "mozilla/UniquePtr.h" #include "nscore.h" #include "GoannaProfilerFunc.h" #include "PseudoStack.h" @@ -359,6 +361,30 @@ static inline void profiler_tracing(const char* aCategory, const char* aInfo, namespace mozilla { +class ProfilerBacktrace; + +class MOZ_STACK_CLASS GoannaProfilerTracingRAII { +public: + GoannaProfilerTracingRAII(const char* aCategory, const char* aInfo, + mozilla::UniquePtr aBacktrace + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mCategory(aCategory) + , mInfo(aInfo) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + profiler_tracing(mCategory, mInfo, aBacktrace.release(), TRACING_INTERVAL_START); + } + + ~GoannaProfilerTracingRAII() { + profiler_tracing(mCategory, mInfo, TRACING_INTERVAL_END); + } + +protected: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + const char* mCategory; + const char* mInfo; +}; + class MOZ_STACK_CLASS SamplerStackFrameRAII { public: // we only copy the strings at save time, so to take multiple parameters we'd need to copy them then. diff --git a/tools/profiler/moz.build b/tools/profiler/moz.build index 746f7505bf..367d8658cc 100644 --- a/tools/profiler/moz.build +++ b/tools/profiler/moz.build @@ -4,6 +4,109 @@ # 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/. +if CONFIG['MOZ_ENABLE_PROFILER_SPS']: + FAIL_ON_WARNINGS = True + + XPIDL_MODULE = 'profiler' + XPIDL_SOURCES += [ + 'nsIProfiler.idl', + 'nsIProfileSaveEvent.idl', + ] + EXPORTS += [ + 'GeckoProfilerFunc.h', + 'GeckoProfilerImpl.h', + 'JSStreamWriter.h', + 'ProfilerBacktrace.h', + 'ProfilerMarkers.h', + 'PseudoStack.h', + 'shared-libraries.h', + ] + EXTRA_JS_MODULES += [ + 'Profiler.jsm', + ] + UNIFIED_SOURCES += [ + 'BreakpadSampler.cpp', + 'JSStreamWriter.cpp', + 'nsProfiler.cpp', + 'nsProfilerFactory.cpp', + 'nsProfilerStartParams.cpp', + 'platform.cpp', + 'ProfileEntry.cpp', + 'ProfilerBacktrace.cpp', + 'ProfilerIOInterposeObserver.cpp', + 'ProfilerMarkers.cpp', + 'SaveProfileTask.cpp', + 'SyncProfile.cpp', + 'TableTicker.cpp', + 'ThreadResponsiveness.cpp', + 'UnwinderThread2.cpp', + ] + + # This file cannot be built in unified mode because of name clashes with mozglue headers on Android. + SOURCES += [ + 'local_debug_info_symbolizer.cc', + ] + + if CONFIG['OS_TARGET'] in ('Android', 'Linux'): + UNIFIED_SOURCES += [ + 'AutoObjectMapper.cpp', + 'LulCommon.cpp', + 'LulDwarf.cpp', + 'LulDwarfSummariser.cpp', + 'LulElf.cpp', + 'LulMain.cpp', + 'LulRWLock.cpp', + ] + # These files cannot be built in unified mode because of name clashes with mozglue headers on Android. + SOURCES += [ + 'platform-linux.cc', + 'shared-libraries-linux.cc', + ] + if CONFIG['CPU_ARCH'] == 'arm': + UNIFIED_SOURCES += [ + 'LulExidx.cpp', + ] + SOURCES += [ + 'EHABIStackWalk.cpp', + ] + elif CONFIG['OS_TARGET'] == 'Darwin': + UNIFIED_SOURCES += [ + 'platform-macos.cc', + 'shared-libraries-macos.cc', + 'shim_mac_dump_syms.mm', + ] + elif CONFIG['OS_TARGET'] == 'WINNT': + SOURCES += [ + 'IntelPowerGadget.cpp', + 'platform-win32.cc', + 'shared-libraries-win32.cc', + ] + + LOCAL_INCLUDES += [ + '/docshell/base', + '/ipc/chromium/src', + '/mozglue/linker', + '/toolkit/crashreporter/google-breakpad/src', + '/xpcom/base', + ] + + # We need access to Breakpad's getcontext(3) which is suitable for Android + if CONFIG['OS_TARGET'] == 'Android': + LOCAL_INCLUDES += [ + '/toolkit/crashreporter/google-breakpad/src/common/android/include', + ] + + if CONFIG['ANDROID_CPU_ARCH'] == 'armeabi': + DEFINES['ARCH_ARMV6'] = True + + if CONFIG['ENABLE_TESTS']: + DIRS += ['tests/gtest'] + + if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and (CONFIG['ANDROID_VERSION'] <= '17' or CONFIG['ANDROID_VERSION'] >= '21'): + DEFINES['ELFSIZE'] = 32 + + FINAL_LIBRARY = 'xul' + EXPORTS += [ 'GoannaProfiler.h', ] diff --git a/tools/profiler/platform.cpp b/tools/profiler/platform.cpp index e4793db0d5..b7bd8a49a6 100644 --- a/tools/profiler/platform.cpp +++ b/tools/profiler/platform.cpp @@ -46,6 +46,7 @@ static bool sIsProfiling = false; // is raced on static bool sIsGPUProfiling = false; // is raced on static bool sIsLayersDump = false; // is raced on static bool sIsDisplayListDump = false; // is raced on +static bool sIsRestyleProfiling = false; // is raced on // env variables to control the profiler const char* PROFILER_MODE = "MOZ_PROFILER_MODE"; @@ -748,6 +749,7 @@ void mozilla_sampler_start(int aProfileEntries, double aInterval, sIsGPUProfiling = t->ProfileGPU(); sIsLayersDump = t->LayersDump(); sIsDisplayListDump = t->DisplayListDump(); + sIsRestyleProfiling = t->ProfileRestyle(); if (Sampler::CanNotifyObservers()) { nsCOMPtr os = mozilla::services::GetObserverService(); @@ -815,6 +817,7 @@ void mozilla_sampler_stop() sIsGPUProfiling = false; sIsLayersDump = false; sIsDisplayListDump = false; + sIsRestyleProfiling = false; if (Sampler::CanNotifyObservers()) { nsCOMPtr os = mozilla::services::GetObserverService(); @@ -862,6 +865,10 @@ bool mozilla_sampler_feature_active(const char* aName) return sIsDisplayListDump; } + if (strcmp(aName, "restyle") == 0) { + return sIsRestyleProfiling; + } + return false; } diff --git a/xpcom/base/nsDebugImpl.cpp b/xpcom/base/nsDebugImpl.cpp index c9494c9a6b..6cc2184654 100644 --- a/xpcom/base/nsDebugImpl.cpp +++ b/xpcom/base/nsDebugImpl.cpp @@ -347,7 +347,9 @@ NS_DebugBreak(uint32_t aSeverity, const char* aStr, const char* aExpr, if (sMultiprocessDescription) { PrintToBuffer("%s ", sMultiprocessDescription); } +#if !defined(MOZILLA_XPCOMRT_API) PrintToBuffer("%d] ", base::GetCurrentProcId()); +#endif // !defined(MOZILLA_XPCOMRT_API) PrintToBuffer("%s: ", sevString); @@ -400,7 +402,7 @@ NS_DebugBreak(uint32_t aSeverity, const char* aStr, const char* aExpr, #if defined(DEBUG) && defined(_WIN32) RealBreak(); #endif -#ifdef DEBUG +#if defined(DEBUG) && !defined(MOZILLA_XPCOMRT_API) nsTraceRefcnt::WalkTheStack(stderr); #endif Abort(buf.buffer); @@ -425,11 +427,15 @@ NS_DebugBreak(uint32_t aSeverity, const char* aStr, const char* aExpr, return; case NS_ASSERT_STACK: +#if !defined(MOZILLA_XPCOMRT_API) nsTraceRefcnt::WalkTheStack(stderr); +#endif // !defined(MOZILLA_XPCOMRT_API) return; case NS_ASSERT_STACK_AND_ABORT: +#if !defined(MOZILLA_XPCOMRT_API) nsTraceRefcnt::WalkTheStack(stderr); +#endif // !defined(MOZILLA_XPCOMRT_API) // Fall through to abort case NS_ASSERT_ABORT: diff --git a/xpcom/base/nsIMemoryReporter.idl b/xpcom/base/nsIMemoryReporter.idl index 85fee885a5..b29bad4c04 100644 --- a/xpcom/base/nsIMemoryReporter.idl +++ b/xpcom/base/nsIMemoryReporter.idl @@ -513,7 +513,7 @@ nsresult RegisterNonJSSizeOfTab(NonJSSizeOfTabFn aSizeOfTabFn); } -#if defined(MOZ_DMD) +#if defined(MOZ_DMD) && !defined(MOZILLA_XPCOMRT_API) namespace mozilla { namespace dmd { // This runs all the memory reporters in the current process but does nothing diff --git a/xpcom/build/FileLocation.cpp b/xpcom/build/FileLocation.cpp index abe6266ec5..aa63006e30 100644 --- a/xpcom/build/FileLocation.cpp +++ b/xpcom/build/FileLocation.cpp @@ -3,8 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "FileLocation.h" +#if !defined(MOZILLA_XPCOMRT_API) #include "nsZipArchive.h" #include "nsURLHelper.h" +#endif // !defined(MOZILLA_XPCOMRT_API) namespace mozilla { @@ -31,9 +33,12 @@ FileLocation::FileLocation(const FileLocation& aFile, const char* aPath) if (aFile.IsZip()) { if (aFile.mBaseFile) { Init(aFile.mBaseFile, aFile.mPath.get()); - } else { + } +#if !defined(MOZILLA_XPCOMRT_API) + else { Init(aFile.mBaseZip, aFile.mPath.get()); } +#endif if (aPath) { int32_t i = mPath.RFindChar('/'); if (kNotFound == i) { @@ -71,7 +76,9 @@ FileLocation::FileLocation(const FileLocation& aFile, const char* aPath) void FileLocation::Init(nsIFile* aFile) { +#if !defined(MOZILLA_XPCOMRT_API) mBaseZip = nullptr; +#endif //!defined(MOZILLA_XPCOMRT_API) mBaseFile = aFile; mPath.Truncate(); } @@ -79,7 +86,9 @@ FileLocation::Init(nsIFile* aFile) void FileLocation::Init(nsIFile* aFile, const char* aPath) { +#if !defined(MOZILLA_XPCOMRT_API) mBaseZip = nullptr; +#endif // !defined(MOZILLA_XPCOMRT_API) mBaseFile = aFile; mPath = aPath; } @@ -87,7 +96,9 @@ FileLocation::Init(nsIFile* aFile, const char* aPath) void FileLocation::Init(nsZipArchive* aZip, const char* aPath) { +#if !defined(MOZILLA_XPCOMRT_API) mBaseZip = aZip; +#endif // !defined(MOZILLA_XPCOMRT_API) mBaseFile = nullptr; mPath = aPath; } @@ -95,6 +106,7 @@ FileLocation::Init(nsZipArchive* aZip, const char* aPath) void FileLocation::GetURIString(nsACString& aResult) const { +#if !defined(MOZILLA_XPCOMRT_API) if (mBaseFile) { net_GetURLSpecFromActualFile(mBaseFile, aResult); } else if (mBaseZip) { @@ -106,11 +118,13 @@ FileLocation::GetURIString(nsACString& aResult) const aResult += "!/"; aResult += mPath; } +#endif // !defined(MOZILLA_XPCOMRT_API) } already_AddRefed FileLocation::GetBaseFile() { +#if !defined(MOZILLA_XPCOMRT_API) if (IsZip() && mBaseZip) { nsRefPtr handler = mBaseZip->GetFD(); if (handler) { @@ -118,6 +132,7 @@ FileLocation::GetBaseFile() } return nullptr; } +#endif // !defined(MOZILLA_XPCOMRT_API) nsCOMPtr file = mBaseFile; return file.forget(); @@ -137,6 +152,7 @@ FileLocation::Equals(const FileLocation& aFile) const const FileLocation* a = this; const FileLocation* b = &aFile; +#if !defined(MOZILLA_XPCOMRT_API) if (a->mBaseZip) { nsRefPtr handler = a->mBaseZip->GetFD(); a = &handler->mFile; @@ -145,12 +161,15 @@ FileLocation::Equals(const FileLocation& aFile) const nsRefPtr handler = b->mBaseZip->GetFD(); b = &handler->mFile; } +#endif // !defined(MOZILLA_XPCOMRT_API) + return a->Equals(*b); } nsresult FileLocation::GetData(Data& aData) { +#if !defined(MOZILLA_XPCOMRT_API) if (!IsZip()) { return mBaseFile->OpenNSPRFileDesc(PR_RDONLY, 0444, &aData.mFd.rwget()); } @@ -163,6 +182,7 @@ FileLocation::GetData(Data& aData) if (aData.mItem) { return NS_OK; } +#endif // !defined(MOZILLA_XPCOMRT_API) return NS_ERROR_FILE_UNRECOGNIZED_PATH; } @@ -181,10 +201,13 @@ FileLocation::Data::GetSize(uint32_t* aResult) *aResult = fileInfo.size; return NS_OK; - } else if (mItem) { + } +#if !defined(MOZILLA_XPCOMRT_API) + else if (mItem) { *aResult = mItem->RealSize(); return NS_OK; } +#endif // !defined(MOZILLA_XPCOMRT_API) return NS_ERROR_NOT_INITIALIZED; } @@ -201,13 +224,16 @@ FileLocation::Data::Copy(char* aBuf, uint32_t aLen) totalRead += read; } return NS_OK; - } else if (mItem) { + } +#if !defined(MOZILLA_XPCOMRT_API) + else if (mItem) { nsZipCursor cursor(mItem, mZip, reinterpret_cast(aBuf), aLen, true); uint32_t readLen; cursor.Copy(&readLen); return (readLen == aLen) ? NS_OK : NS_ERROR_FILE_CORRUPTED; } +#endif // !defined(MOZILLA_XPCOMRT_API) return NS_ERROR_NOT_INITIALIZED; } diff --git a/xpcom/build/FileLocation.h b/xpcom/build/FileLocation.h index b23028b0fe..ab66ba6a6c 100644 --- a/xpcom/build/FileLocation.h +++ b/xpcom/build/FileLocation.h @@ -87,7 +87,11 @@ public: * Boolean value corresponding to whether the file location is initialized * or not. */ +#if defined(MOZILLA_XPCOMRT_API) + operator bool() const { return mBaseFile; } +#else operator bool() const { return mBaseFile || mBaseZip; } +#endif // defined(MOZILLA_XPCOMRT_API) /** * Returns whether another FileLocation points to the same resource @@ -111,7 +115,9 @@ public: nsresult Copy(char* aBuf, uint32_t aLen); protected: friend class FileLocation; +#if !defined(MOZILLA_XPCOMRT_API) nsZipItem* mItem; +#endif // !defined(MOZILLA_XPCOMRT_API) nsRefPtr mZip; mozilla::AutoFDClose mFd; }; @@ -123,7 +129,9 @@ public: nsresult GetData(Data& aData); private: nsCOMPtr mBaseFile; +#if !defined(MOZILLA_XPCOMRT_API) nsRefPtr mBaseZip; +#endif // !defined(MOZILLA_XPCOMRT_API) nsCString mPath; }; /* class FileLocation */ diff --git a/xpcom/build/IOInterposer.h b/xpcom/build/IOInterposer.h index 9d7ad8818f..20b3bb993c 100644 --- a/xpcom/build/IOInterposer.h +++ b/xpcom/build/IOInterposer.h @@ -7,7 +7,6 @@ #include "mozilla/Attributes.h" #include "mozilla/TimeStamp.h" -#include "mozilla/XPCOM.h" namespace mozilla { diff --git a/xpcom/build/LateWriteChecks.cpp b/xpcom/build/LateWriteChecks.cpp index 6b31317105..ba5a82f03c 100644 --- a/xpcom/build/LateWriteChecks.cpp +++ b/xpcom/build/LateWriteChecks.cpp @@ -17,6 +17,7 @@ #include "nsPrintfCString.h" #include "nsStackWalk.h" #include "plstr.h" +#include "prio.h" #ifdef XP_WIN #define NS_T(str) L ## str diff --git a/xpcom/build/MainThreadIOLogger.cpp b/xpcom/build/MainThreadIOLogger.cpp index 109ad7d029..c698443c98 100644 --- a/xpcom/build/MainThreadIOLogger.cpp +++ b/xpcom/build/MainThreadIOLogger.cpp @@ -12,6 +12,7 @@ #include "mozilla/StaticPtr.h" #include "mozilla/TimeStamp.h" #include "nsAutoPtr.h" +#include "nsNativeCharsetUtils.h" /** * This code uses NSPR stuff and STL containers because it must be detached diff --git a/xpcom/build/ServiceList.h b/xpcom/build/ServiceList.h index 8a5e331475..8e4efe1a98 100644 --- a/xpcom/build/ServiceList.h +++ b/xpcom/build/ServiceList.h @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // IWYU pragma: private, include "mozilla/Services.h" +#if !defined(MOZILLA_XPCOMRT_API) #ifdef ACCESSIBILITY MOZ_SERVICE(AccessibilityService, nsIAccessibilityService, "@mozilla.org/accessibilityService;1") @@ -17,8 +18,10 @@ MOZ_SERVICE(XULOverlayProviderService, nsIXULOverlayProvider, "@mozilla.org/chrome/chrome-registry;1") MOZ_SERVICE(IOService, nsIIOService, "@mozilla.org/network/io-service;1") +#endif // !defined(MOZILLA_XPCOMRT_API) MOZ_SERVICE(ObserverService, nsIObserverService, "@mozilla.org/observer-service;1") +#if !defined(MOZILLA_XPCOMRT_API) MOZ_SERVICE(StringBundleService, nsIStringBundleService, "@mozilla.org/intl/stringbundle;1") MOZ_SERVICE(XPConnect, nsIXPConnect, @@ -42,3 +45,4 @@ MOZ_SERVICE(HistoryService, IHistory, #ifdef MOZ_USE_NAMESPACE } #endif +#endif // !defined(MOZILLA_XPCOMRT_API) diff --git a/xpcom/build/Services.cpp b/xpcom/build/Services.cpp index cde20c2029..3378a1ed6b 100644 --- a/xpcom/build/Services.cpp +++ b/xpcom/build/Services.cpp @@ -6,16 +6,17 @@ #include "mozilla/Likely.h" #include "mozilla/Services.h" #include "nsComponentManager.h" +#include "nsIObserverService.h" +#include "nsNetCID.h" +#include "nsObserverService.h" +#include "nsXPCOMPrivate.h" +#if !defined(MOZILLA_XPCOMRT_API) #include "nsIIOService.h" #include "nsIDirectoryService.h" #ifdef ACCESSIBILITY #include "nsIAccessibilityService.h" #endif #include "nsIChromeRegistry.h" -#include "nsIObserverService.h" -#include "nsNetCID.h" -#include "nsObserverService.h" -#include "nsXPCOMPrivate.h" #include "nsIStringBundle.h" #include "nsIToolkitChromeRegistry.h" #include "nsIXULOverlayProvider.h" @@ -25,6 +26,8 @@ #include "nsIPermissionManager.h" #include "nsIServiceWorkerManager.h" #include "nsIAsyncShutdown.h" +#include "nsIUUIDGenerator.h" +#endif // !defined(MOZILLA_XPCOMRT_API) using namespace mozilla; using namespace mozilla::services; diff --git a/xpcom/components/ManifestParser.h b/xpcom/components/ManifestParser.h index 3f05bc3ebf..cb4f310c1b 100644 --- a/xpcom/components/ManifestParser.h +++ b/xpcom/components/ManifestParser.h @@ -7,7 +7,9 @@ #define ManifestParser_h #include "nsComponentManager.h" +#if !defined(MOZILLA_XPCOMRT_API) #include "nsChromeRegistry.h" +#endif // !defined(MOZILLA_XPCOMRT_API) #include "mozilla/FileLocation.h" class nsIFile; diff --git a/xpcom/components/nsCategoryManager.cpp b/xpcom/components/nsCategoryManager.cpp index 0f641a236b..09f14f7a8a 100644 --- a/xpcom/components/nsCategoryManager.cpp +++ b/xpcom/components/nsCategoryManager.cpp @@ -469,7 +469,9 @@ nsCategoryManager::nsCategoryManager() void nsCategoryManager::InitMemoryReporter() { +#if !defined(MOZILLA_XPCOMRT_API) RegisterStrongMemoryReporter(this); +#endif // !defined(MOZILLA_XPCOMRT_API) } nsCategoryManager::~nsCategoryManager() @@ -862,8 +864,10 @@ NS_CreateServicesFromCategory(const char* aCategory, nsCOMPtr instance = do_GetService(contractID); if (!instance) { +#if !defined(MOZILLA_XPCOMRT_API) LogMessage("While creating services from category '%s', could not create service for entry '%s', contract ID '%s'", aCategory, entryString.get(), contractID.get()); +#endif // !defined(MOZILLA_XPCOMRT_API) continue; } @@ -873,8 +877,10 @@ NS_CreateServicesFromCategory(const char* aCategory, if (observer) { observer->Observe(aOrigin, aObserverTopic, EmptyString().get()); } else { +#if !defined(MOZILLA_XPCOMRT_API) LogMessage("While creating services from category '%s', service for entry '%s', contract ID '%s' does not implement nsIObserver.", aCategory, entryString.get(), contractID.get()); +#endif // !defined(MOZILLA_XPCOMRT_API) } } } diff --git a/xpcom/components/nsComponentManager.cpp b/xpcom/components/nsComponentManager.cpp index b2e588ffa1..190745631e 100644 --- a/xpcom/components/nsComponentManager.cpp +++ b/xpcom/components/nsComponentManager.cpp @@ -58,7 +58,10 @@ #include "private/pprthred.h" #include "nsTArray.h" #include "prio.h" +#if !defined(MOZILLA_XPCOMRT_API) #include "ManifestParser.h" +#include "nsNetUtil.h" +#endif // !defined(MOZILLA_XPCOMRT_API) #include "mozilla/Services.h" #include "mozilla/GenericFactory.h" @@ -68,7 +71,6 @@ #include "nsArrayEnumerator.h" #include "nsStringEnumerator.h" #include "mozilla/FileUtils.h" -#include "nsNetUtil.h" #include "nsDataHashtable.h" #include // for placement new @@ -250,6 +252,7 @@ private: } // anonymous namespace +#if !defined(MOZILLA_XPCOMRT_API) // this is safe to call during InitXPCOM static already_AddRefed GetLocationFromDirectoryService(const char* aProp) @@ -286,6 +289,7 @@ CloneAndAppend(nsIFile* aBase, const nsACString& aAppend) f->AppendNative(aAppend); return f.forget(); } +#endif // !defined(MOZILLA_XPCOMRT_API) //////////////////////////////////////////////////////////////////////////////// // nsComponentManagerImpl @@ -318,8 +322,10 @@ nsComponentManagerImpl::nsComponentManagerImpl() nsTArray* nsComponentManagerImpl::sStaticModules; +#if !defined(MOZILLA_XPCOMRT_API) NSMODULE_DEFN(start_kPStaticModules); NSMODULE_DEFN(end_kPStaticModules); +#endif // !defined(MOZILLA_XPCOMRT_API) /* The content between start_kPStaticModules and end_kPStaticModules is gathered * by the linker from various objects containing symbols in a specific section. @@ -335,12 +341,14 @@ nsComponentManagerImpl::InitializeStaticModules() } sStaticModules = new nsTArray; +#if !defined(MOZILLA_XPCOMRT_API) for (const mozilla::Module * const* staticModules = &NSMODULE_NAME(start_kPStaticModules) + 1; staticModules < &NSMODULE_NAME(end_kPStaticModules); ++staticModules) if (*staticModules) { // ASAN adds padding sStaticModules->AppendElement(*staticModules); } +#endif // !defined(MOZILLA_XPCOMRT_API) } nsTArray* @@ -368,26 +376,35 @@ nsComponentManagerImpl::Init() // Initialize our arena PL_INIT_ARENA_POOL(&mArena, "ComponentManagerArena", NS_CM_BLOCK_SIZE); +#if !defined(MOZILLA_XPCOMRT_API) nsCOMPtr greDir = GetLocationFromDirectoryService(NS_GRE_DIR); nsCOMPtr appDir = GetLocationFromDirectoryService(NS_XPCOM_CURRENT_PROCESS_DIR); +#endif InitializeStaticModules(); +#if !defined(MOZILLA_XPCOMRT_API) nsresult rv = mNativeModuleLoader.Init(); if (NS_FAILED(rv)) { return rv; } nsCategoryManager::GetSingleton()->SuppressNotifications(true); +#endif +#if defined(MOZILLA_XPCOMRT_API) + RegisterModule(&kXPCOMRTModule, nullptr); +#else RegisterModule(&kXPCOMModule, nullptr); +#endif // defined(MOZILLA_XPCOMRT_API) for (uint32_t i = 0; i < sStaticModules->Length(); ++i) { RegisterModule((*sStaticModules)[i], nullptr); } +#if !defined(MOZILLA_XPCOMRT_API) // The overall order in which chrome.manifests are expected to be treated // is the following: // - greDir @@ -432,6 +449,7 @@ nsComponentManagerImpl::Init() nsCategoryManager::GetSingleton()->SuppressNotifications(false); RegisterWeakMemoryReporter(this); +#endif // Unfortunately, we can't register the nsCategoryManager memory reporter // in its constructor (which is triggered by the GetSingleton() call @@ -540,10 +558,12 @@ nsComponentManagerImpl::RegisterCIDEntryLocked( existing = ""; } SafeMutexAutoUnlock unlock(mLock); +#if !defined(MOZILLA_XPCOMRT_API) LogMessage("While registering XPCOM module %s, trying to re-register CID '%s' already registered by %s.", aModule->Description().get(), idstr, existing.get()); +#endif // !defined(MOZILLA_XPCOMRT_API) return; } @@ -569,9 +589,11 @@ nsComponentManagerImpl::RegisterContractIDLocked( aEntry->cid->ToProvidedString(idstr); SafeMutexAutoUnlock unlock(mLock); +#if !defined(MOZILLA_XPCOMRT_API) LogMessage("Could not map contract ID '%s' to CID %s because no implementation of the CID is registered.", aEntry->contractid, idstr); +#endif // !defined(MOZILLA_XPCOMRT_API) return; } @@ -579,6 +601,7 @@ nsComponentManagerImpl::RegisterContractIDLocked( mContractIDs.Put(nsDependentCString(aEntry->contractid), f); } +#if !defined(MOZILLA_XPCOMRT_API) static void CutExtension(nsCString& aPath) { @@ -817,10 +840,12 @@ nsComponentManagerImpl::RereadChromeManifests(bool aChromeOnly) RegisterManifest(l.type, l.location, aChromeOnly); } } +#endif // !defined(MOZILLA_XPCOMRT_API) bool nsComponentManagerImpl::KnownModule::EnsureLoader() { +#if !defined(MOZILLA_XPCOMRT_API) if (!mLoader) { nsCString extension; mFile.GetURIString(extension); @@ -828,6 +853,7 @@ nsComponentManagerImpl::KnownModule::EnsureLoader() mLoader = nsComponentManagerImpl::gComponentManager->LoaderForExtension(extension); } +#endif // !defined(MOZILLA_XPCOMRT_API) return !!mLoader; } @@ -884,7 +910,9 @@ nsresult nsComponentManagerImpl::Shutdown(void) PR_LOG(nsComponentManagerLog, PR_LOG_DEBUG, ("nsComponentManager: Beginning Shutdown.")); +#if !defined(MOZILLA_XPCOMRT_API) UnregisterWeakMemoryReporter(this); +#endif // Release all cached factories mContractIDs.Clear(); @@ -900,8 +928,10 @@ nsresult nsComponentManagerImpl::Shutdown(void) sXPTIInfosBook = nullptr; #endif +#if !defined(MOZILLA_XPCOMRT_API) // Unload libraries mNativeModuleLoader.UnloadLibraries(); +#endif // !defined(MOZILLA_XPCOMRT_API) // delete arena for strings and small objects PL_FinishArenaPool(&mArena); @@ -1585,6 +1615,7 @@ nsComponentManagerImpl::GetServiceByContractID(const char* aContractID, return NS_OK; } +#if !defined(MOZILLA_XPCOMRT_API) already_AddRefed nsComponentManagerImpl::LoaderForExtension(const nsACString& aExt) { @@ -1601,6 +1632,7 @@ nsComponentManagerImpl::LoaderForExtension(const nsACString& aExt) return loader.forget(); } +#endif NS_IMETHODIMP nsComponentManagerImpl::RegisterFactory(const nsCID& aClass, @@ -1673,8 +1705,12 @@ nsComponentManagerImpl::UnregisterFactory(const nsCID& aClass, NS_IMETHODIMP nsComponentManagerImpl::AutoRegister(nsIFile* aLocation) { +#if !defined(MOZILLA_XPCOMRT_API) XRE_AddManifestLocation(NS_COMPONENT_LOCATION, aLocation); return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif // !defined(MOZILLA_XPCOMRT_API) } NS_IMETHODIMP @@ -2010,6 +2046,7 @@ XRE_AddStaticComponent(const mozilla::Module* aComponent) NS_IMETHODIMP nsComponentManagerImpl::AddBootstrappedManifestLocation(nsIFile* aLocation) { +#if !defined(MOZILLA_XPCOMRT_API) nsString path; nsresult rv = aLocation->GetPath(path); if (NS_FAILED(rv)) { @@ -2023,11 +2060,15 @@ nsComponentManagerImpl::AddBootstrappedManifestLocation(nsIFile* aLocation) nsCOMPtr manifest = CloneAndAppend(aLocation, NS_LITERAL_CSTRING("chrome.manifest")); return XRE_AddManifestLocation(NS_BOOTSTRAPPED_LOCATION, manifest); +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif // !defined(MOZILLA_XPCOMRT_API) } NS_IMETHODIMP nsComponentManagerImpl::RemoveBootstrappedManifestLocation(nsIFile* aLocation) { +#if !defined(MOZILLA_XPCOMRT_API) nsCOMPtr cr = mozilla::services::GetChromeRegistryService(); if (!cr) { return NS_ERROR_FAILURE; @@ -2057,11 +2098,15 @@ nsComponentManagerImpl::RemoveBootstrappedManifestLocation(nsIFile* aLocation) rv = cr->CheckForNewChrome(); return rv; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif // !defined(MOZILLA_XPCOMRT_API) } NS_IMETHODIMP nsComponentManagerImpl::GetManifestLocations(nsIArray** aLocations) { +#if !defined(MOZILLA_XPCOMRT_API) NS_ENSURE_ARG_POINTER(aLocations); *aLocations = nullptr; @@ -2085,6 +2130,9 @@ nsComponentManagerImpl::GetManifestLocations(nsIArray** aLocations) locations.forget(aLocations); return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif // !defined(MOZILLA_XPCOMRT_API) } #ifdef MOZ_B2G_LOADER @@ -2133,6 +2181,7 @@ PreloadXPT(nsIFile* aOmnijarFile) #endif /* MOZ_B2G_LOADER */ +#if !defined(MOZILLA_XPCOMRT_API) EXPORT_XPCOM_API(nsresult) XRE_AddManifestLocation(NSLocationType aType, nsIFile* aLocation) { @@ -2173,4 +2222,5 @@ XRE_AddJarManifestLocation(NSLocationType aType, nsIFile* aLocation) return NS_OK; } +#endif // !defined(MOZILLA_XPCOMRT_API) diff --git a/xpcom/components/nsComponentManager.h b/xpcom/components/nsComponentManager.h index b79947bbff..35d3d26b04 100644 --- a/xpcom/components/nsComponentManager.h +++ b/xpcom/components/nsComponentManager.h @@ -67,7 +67,11 @@ extern const char staticComponentType[]; #endif //////////////////////////////////////////////////////////////////////////////// +#if defined(MOZILLA_XPCOMRT_API) +extern const mozilla::Module kXPCOMRTModule; +#else extern const mozilla::Module kXPCOMModule; +#endif /** * This is a wrapper around mozilla::Mutex which provides runtime diff --git a/xpcom/ds/nsObserverList.cpp b/xpcom/ds/nsObserverList.cpp index 90ab8dce78..120ce18c9d 100644 --- a/xpcom/ds/nsObserverList.cpp +++ b/xpcom/ds/nsObserverList.cpp @@ -104,11 +104,13 @@ nsObserverList::NotifyObservers(nsISupports* aSubject, void nsObserverList::UnmarkGrayStrongObservers() { +#if !defined(MOZILLA_XPCOMRT_API) for (uint32_t i = 0; i < mObservers.Length(); ++i) { if (!mObservers[i].isWeakRef) { xpc_TryUnmarkWrappedGrayObject(mObservers[i].asObserver()); } } +#endif // !defined(MOZILLA_XPCOMRT_API) } NS_IMPL_ISUPPORTS(nsObserverEnumerator, nsISimpleEnumerator) diff --git a/xpcom/ds/nsObserverService.cpp b/xpcom/ds/nsObserverService.cpp index 644ce91918..40c403df24 100644 --- a/xpcom/ds/nsObserverService.cpp +++ b/xpcom/ds/nsObserverService.cpp @@ -205,13 +205,17 @@ nsObserverService::~nsObserverService(void) void nsObserverService::RegisterReporter() { +#if !defined(MOZILLA_XPCOMRT_API) RegisterWeakMemoryReporter(this); +#endif // !defined(MOZILLA_XPCOMRT_API) } void nsObserverService::Shutdown() { +#if !defined(MOZILLA_XPCOMRT_API) UnregisterWeakMemoryReporter(this); +#endif // !defined(MOZILLA_XPCOMRT_API) mShuttingDown = true; diff --git a/xpcom/glue/BlockingResourceBase.cpp b/xpcom/glue/BlockingResourceBase.cpp index 822de364d1..f9bd0bc427 100644 --- a/xpcom/glue/BlockingResourceBase.cpp +++ b/xpcom/glue/BlockingResourceBase.cpp @@ -23,7 +23,7 @@ #include "mozilla/ReentrantMonitor.h" #include "mozilla/Mutex.h" -#ifdef MOZILLA_INTERNAL_API +#if defined(MOZILLA_INTERNAL_API) && !defined(MOZILLA_XPCOMRT_API) #include "GoannaProfiler.h" #endif //MOZILLA_INTERNAL_API @@ -461,7 +461,7 @@ ReentrantMonitor::Wait(PRIntervalTime aInterval) mChainPrev = 0; nsresult rv; -#ifdef MOZILLA_INTERNAL_API +#if defined(MOZILLA_INTERNAL_API) && !defined(MOZILLA_XPCOMRT_API) { GoannaProfilerSleepRAII profiler_sleep; #endif //MOZILLA_INTERNAL_API @@ -470,7 +470,7 @@ ReentrantMonitor::Wait(PRIntervalTime aInterval) rv = PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE; -#ifdef MOZILLA_INTERNAL_API +#if defined(MOZILLA_INTERNAL_API) && !defined(MOZILLA_XPCOMRT_API) } #endif //MOZILLA_INTERNAL_API diff --git a/xpcom/glue/nsISupportsImpl.h b/xpcom/glue/nsISupportsImpl.h index c58a6c3337..8e842c31ab 100644 --- a/xpcom/glue/nsISupportsImpl.h +++ b/xpcom/glue/nsISupportsImpl.h @@ -143,7 +143,7 @@ private: // Macros for reference-count and constructor logging -#ifdef NS_BUILD_REFCNT_LOGGING +#if defined(NS_BUILD_REFCNT_LOGGING) && !defined(MOZILLA_XPCOMRT_API) #define NS_LOG_ADDREF(_p, _rc, _type, _size) \ NS_LogAddRef((_p), (_rc), (_type), (uint32_t) (_size)) @@ -172,6 +172,11 @@ do { \ NS_LogCtor((void*)this, #_type, sizeof(*this) - sizeof(_base)); \ } while (0) +#define MOZ_LOG_CTOR(_ptr, _name, _size) \ +do { \ + NS_LogCtor((void*)_ptr, _name, _size); \ +} while (0) + #define MOZ_COUNT_DTOR(_type) \ do { \ MOZ_ASSERT_CLASSNAME(_type); \ @@ -185,6 +190,11 @@ do { \ NS_LogDtor((void*)this, #_type, sizeof(*this) - sizeof(_base)); \ } while (0) +#define MOZ_LOG_DTOR(_ptr, _name, _size) \ +do { \ + NS_LogDtor((void*)_ptr, _name, _size); \ +} while (0) + /* nsCOMPtr.h allows these macros to be defined by clients * These logging functions require dynamic_cast, so they don't * do anything useful if we don't have dynamic_cast. */ @@ -202,8 +212,10 @@ do { \ #define NS_LOG_RELEASE(_p, _rc, _type) #define MOZ_COUNT_CTOR(_type) #define MOZ_COUNT_CTOR_INHERITED(_type, _base) +#define MOZ_LOG_CTOR(_ptr, _name, _size) #define MOZ_COUNT_DTOR(_type) #define MOZ_COUNT_DTOR_INHERITED(_type, _base) +#define MOZ_LOG_DTOR(_ptr, _name, _size) #endif /* NS_BUILD_REFCNT_LOGGING */ diff --git a/xpcom/libxpcomrt/XPCOMRTInit.cpp b/xpcom/libxpcomrt/XPCOMRTInit.cpp new file mode 100644 index 0000000000..5bdb8bb805 --- /dev/null +++ b/xpcom/libxpcomrt/XPCOMRTInit.cpp @@ -0,0 +1,190 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 ci 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/Module.h" +#include "mozilla/ModuleUtils.h" +#include "mozilla/NullPtr.h" +#include "mozilla/TimeStamp.h" +#include "nsCategoryManager.h" +#include "nsComponentManager.h" +#include "nsDebugImpl.h" +#include "nsIErrorService.h" +#include "nsMemoryImpl.h" +#include "nsObserverService.h" +#include "nsThreadManager.h" +#include "nsThreadPool.h" +#include "nsUUIDGenerator.h" +#include "nsXPCOMCIDInternal.h" +#include "nsXPCOMPrivate.h" +#include "TimerThread.h" +#include "XPCOMRTInit.h" + +static NS_DEFINE_CID(kComponentManagerCID, NS_COMPONENTMANAGER_CID); + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsTimerImpl) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsUUIDGenerator, Init) + +static nsresult +nsThreadManagerGetSingleton(nsISupports* aOuter, + const nsIID& aIID, + void** aInstancePtr) +{ + NS_ASSERTION(aInstancePtr, "null outptr"); + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + return nsThreadManager::get()->QueryInterface(aIID, aInstancePtr); +} + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsThreadPool) + +nsComponentManagerImpl* nsComponentManagerImpl::gComponentManager = nullptr; +bool gXPCOMShuttingDown = false; +bool gXPCOMThreadsShutDown = false; + +#define COMPONENT(NAME, Ctor) static NS_DEFINE_CID(kNS_##NAME##_CID, NS_##NAME##_CID); +#include "XPCOMRTModule.inc" +#undef COMPONENT + +#define COMPONENT(NAME, Ctor) { &kNS_##NAME##_CID, false, nullptr, Ctor }, +const mozilla::Module::CIDEntry kXPCOMCIDEntries[] = { + { &kComponentManagerCID, true, nullptr, nsComponentManagerImpl::Create }, +#include "XPCOMRTModule.inc" + { nullptr } +}; +#undef COMPONENT + +#define COMPONENT(NAME, Ctor) { NS_##NAME##_CONTRACTID, &kNS_##NAME##_CID }, +const mozilla::Module::ContractIDEntry kXPCOMContracts[] = { +#include "XPCOMRTModule.inc" + { nullptr } +}; +#undef COMPONENT + +const mozilla::Module kXPCOMRTModule = { + mozilla::Module::kVersion, kXPCOMCIDEntries, kXPCOMContracts +}; + +nsresult +NS_InitXPCOMRT() +{ + nsresult rv = NS_OK; + + NS_SetMainThread(); + + mozilla::TimeStamp::Startup(); + + rv = nsThreadManager::get()->Init(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Set up the timer globals/timer thread + rv = nsTimerImpl::Startup(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsComponentManagerImpl::gComponentManager = new nsComponentManagerImpl(); + NS_ADDREF(nsComponentManagerImpl::gComponentManager); + + rv = nsComponentManagerImpl::gComponentManager->Init(); + if (NS_FAILED(rv)) { + NS_RELEASE(nsComponentManagerImpl::gComponentManager); + return rv; + } + + return NS_OK; +} + +nsresult +NS_ShutdownXPCOMRT() +{ + nsresult rv = NS_OK; + + // Notify observers of xpcom shutting down + { + // Block it so that the COMPtr will get deleted before we hit + // servicemanager shutdown + + nsCOMPtr thread = do_GetCurrentThread(); + + if (NS_WARN_IF(!thread)) { + return NS_ERROR_UNEXPECTED; + } + + nsRefPtr observerService; + CallGetService("@mozilla.org/observer-service;1", + (nsObserverService**)getter_AddRefs(observerService)); + + if (observerService) { + observerService->NotifyObservers(nullptr, + NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, + nullptr); + + nsCOMPtr mgr; + rv = NS_GetServiceManager(getter_AddRefs(mgr)); + if (NS_SUCCEEDED(rv)) { + observerService->NotifyObservers(mgr, NS_XPCOM_SHUTDOWN_OBSERVER_ID, + nullptr); + } + } + + // This must happen after the shutdown of media and widgets, which + // are triggered by the NS_XPCOM_SHUTDOWN_OBSERVER_ID notification. + NS_ProcessPendingEvents(thread); + + if (observerService) + observerService->NotifyObservers(nullptr, + NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, + nullptr); + + gXPCOMThreadsShutDown = true; + NS_ProcessPendingEvents(thread); + + // Shutdown the timer thread and all timers that might still be alive before + // shutting down the component manager + nsTimerImpl::Shutdown(); + + NS_ProcessPendingEvents(thread); + + // Shutdown all remaining threads. This method does not return until + // all threads created using the thread manager (with the exception of + // the main thread) have exited. + nsThreadManager::get()->Shutdown(); + + NS_ProcessPendingEvents(thread); + } + + mozilla::services::Shutdown(); + + // Shutdown global servicemanager + if (nsComponentManagerImpl::gComponentManager) { + nsComponentManagerImpl::gComponentManager->FreeServices(); + } + + // Shutdown xpcom. This will release all loaders and cause others holding + // a refcount to the component manager to release it. + if (nsComponentManagerImpl::gComponentManager) { + rv = (nsComponentManagerImpl::gComponentManager)->Shutdown(); + NS_ASSERTION(NS_SUCCEEDED(rv), "Component Manager shutdown failed."); + } else { + NS_WARNING("Component Manager was never created ..."); + } + + // Finally, release the component manager last because it unloads the + // libraries: + if (nsComponentManagerImpl::gComponentManager) { + nsrefcnt cnt; + NS_RELEASE2(nsComponentManagerImpl::gComponentManager, cnt); + NS_ASSERTION(cnt == 0, "Component Manager being held past XPCOM shutdown."); + } + nsComponentManagerImpl::gComponentManager = nullptr; + nsCategoryManager::Destroy(); + + return NS_OK; +} diff --git a/xpcom/libxpcomrt/XPCOMRTInit.h b/xpcom/libxpcomrt/XPCOMRTInit.h new file mode 100644 index 0000000000..206298c8cb --- /dev/null +++ b/xpcom/libxpcomrt/XPCOMRTInit.h @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 ci 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 XPCOMRT_INIT_H__ +#define XPCOMRT_INIT_H__ + +#include "nsError.h" + +nsresult NS_InitXPCOMRT(); +nsresult NS_ShutdownXPCOMRT(); + +#endif // define XPCOMRT_INIT_H__ diff --git a/xpcom/libxpcomrt/XPCOMRTModule.inc b/xpcom/libxpcomrt/XPCOMRTModule.inc new file mode 100644 index 0000000000..4a3f119c67 --- /dev/null +++ b/xpcom/libxpcomrt/XPCOMRTModule.inc @@ -0,0 +1,8 @@ + COMPONENT(MEMORY, nsMemoryImpl::Create) + COMPONENT(DEBUG, nsDebugImpl::Create) + COMPONENT(CATEGORYMANAGER, nsCategoryManager::Create) + COMPONENT(OBSERVERSERVICE, nsObserverService::Create) + COMPONENT(TIMER, nsTimerImplConstructor) + COMPONENT(THREADMANAGER, nsThreadManagerGetSingleton) + COMPONENT(THREADPOOL, nsThreadPoolConstructor) + COMPONENT(UUID_GENERATOR, nsUUIDGeneratorConstructor) diff --git a/xpcom/libxpcomrt/XPCOMRTStubs.cpp b/xpcom/libxpcomrt/XPCOMRTStubs.cpp new file mode 100644 index 0000000000..c25b5ba12c --- /dev/null +++ b/xpcom/libxpcomrt/XPCOMRTStubs.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 ci 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 "nsXULAppAPI.h" +#include "mozilla/TimeStamp.h" + + +GoannaProcessType +XRE_GetProcessType() +{ + return GoannaProcessType_Default; +} + +#define PRINT_CALLED fprintf(stderr, "!!! ERROR: function %s defined in file %s should not be called, needs to be correctly implemented.\n", __FUNCTION__, __FILE__) + +class nsAString; +class nsCString; + +namespace base { + class Histogram; +} + +namespace mozilla { +namespace Telemetry { + +#include "mozilla/TelemetryHistogramEnums.h" + +void Accumulate(ID id, uint32_t sample) {} +void Accumulate(ID id, const nsCString& key, uint32_t sample) {} +void Accumulate(const char* name, uint32_t sample) {} +void AccumulateTimeDelta(ID id, TimeStamp start, TimeStamp end) {} + +base::Histogram* GetHistogramById(ID id) +{ + return nullptr; +} + +base::Histogram* GetKeyedHistogramById(ID id, const nsAString&) +{ + return nullptr; +} + +} // Telemetry +} // mozilla diff --git a/xpcom/libxpcomrt/docs/index.rst b/xpcom/libxpcomrt/docs/index.rst new file mode 100644 index 0000000000..9de1126ceb --- /dev/null +++ b/xpcom/libxpcomrt/docs/index.rst @@ -0,0 +1,40 @@ +========================== + XPCOM Standalone Library +========================== + +What it is for +-------------- +The XPCOM standalone library, libxpcomrt, was created to support building the WebRTC +standalone library. The libxpcomrt library contains only the parts of XPCOM that are required +to run WebRTC; parts such as the cycle collector and the startup cache required only by Gecko +are not included. A library containing a small subset of Necko was also +created to support the WebRTC standalone library. + +The libxcomrt library was created specifically to support the WebRTC standalone library. +It is not intended to be used as a general purpose library to add XPCOM functionality to +an application. It is likely that some of the code contained in the libxpcomrt library +has unresolved symbols that may be exposed if used for purposes other than being linked +into the WebRTC standalone library. + +How to use it +------------- +When compiling code utilizing libxpcomrt, both ``MOZILLA_INTERNAL_API`` and ``MOZILLA_XPCOMRT_API`` +must be defined in addition to whatever standard flags are used to compile Gecko. +The library is initialized with ``NS_InitXPCOMRT()`` and shutdown with ``NS_ShutdownXPCOMRT()``. +Both functions are declared in xpcom/libxpcomrt/XPCOMRTInit.h. +Only a small number of services which are required for the WebRTC +standalone library to function are included with libxpcomrt. The dynamic loading of services is not +supported. Including a service through ``NSMODULE_DEFN`` and static linking is also not supported. +The only way to add a service to libxpcomrt is to explicitly start the service during +``nsComponentManagerImpl::Init`` in xpcom/components/nsComponentManager.cpp. +The best method to determine what parts of XPCOM are included in libxpcomrt is to examine the +xpcom/libxpcomrt/moz.build file. It contains all of the XPCOM source files used to build libxpcomrt. +A few of the services that are included are: + +* UUID Generator +* DNS Service +* Socket Transport Service +* IDN Service + +All dependencies on ipc/chromium have been removed. +IO and preference services are not included making this library of limited utility. diff --git a/xpcom/libxpcomrt/moz.build b/xpcom/libxpcomrt/moz.build new file mode 100644 index 0000000000..2ad8dfea60 --- /dev/null +++ b/xpcom/libxpcomrt/moz.build @@ -0,0 +1,153 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +if CONFIG['OS_TARGET'] != 'WINNT' and CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk': + Library('xpcomrt') + +src_list = [ + 'XPCOMRTInit.cpp', + 'XPCOMRTStubs.cpp', +] + +xpcom_base_src = [ + 'nsDebugImpl.cpp', + 'nsMemoryImpl.cpp', + 'nsUUIDGenerator.cpp', +] +src_list += [ + '%s/xpcom/base/%s' % (TOPSRCDIR, s) for s in xpcom_base_src +] + +xpcom_build_src = [ + 'FileLocation.cpp', + 'Services.cpp', +] +src_list += [ + '%s/xpcom/build/%s' % (TOPSRCDIR, s) for s in xpcom_build_src +] + +xpcom_components_src = [ + 'nsCategoryManager.cpp', + 'nsComponentManager.cpp', +] +src_list += [ + '%s/xpcom/components/%s' % (TOPSRCDIR, s) for s in xpcom_components_src +] + +xpcom_ds_src = [ + 'nsObserverList.cpp', + 'nsObserverService.cpp', + 'nsStringEnumerator.cpp', + 'nsSupportsPrimitives.cpp', +] +if CONFIG['OS_ARCH'] == 'WINNT': + xpcom_ds_src += [ + 'TimeStamp_windows.cpp', + ] +elif CONFIG['HAVE_CLOCK_MONOTONIC']: + xpcom_ds_src += [ + 'TimeStamp_posix.cpp', + ] +elif CONFIG['OS_ARCH'] == 'Darwin': + xpcom_ds_src += [ + 'TimeStamp_darwin.cpp', + ] +elif CONFIG['COMPILE_ENVIRONMENT']: + error('No TimeStamp implementation on this platform. Build will not succeed') +src_list += [ + '%s/xpcom/ds/%s' % (TOPSRCDIR, s) for s in xpcom_ds_src +] + +xpcom_glue_src = [ + 'BlockingResourceBase.cpp', + 'nsArrayEnumerator.cpp', + 'nsClassInfoImpl.cpp', + 'nsCOMArray.cpp', + 'nsCOMPtr.cpp', + 'nsCRTGlue.cpp', + 'nsComponentManagerUtils.cpp', + 'nsEnumeratorUtils.cpp', + 'GenericFactory.cpp', + 'nsID.cpp', + 'nsISupportsImpl.cpp', + 'nsMemory.cpp', + 'nsProxyRelease.cpp', + 'nsQuickSort.cpp', + 'nsTArray.cpp', + 'nsTHashtable.cpp', + 'nsTObserverArray.cpp', + 'nsThreadUtils.cpp', + 'nsWeakReference.cpp', + 'pldhash.cpp', +] +src_list += [ + '%s/xpcom/glue/%s' % (TOPSRCDIR, s) for s in xpcom_glue_src +] + +xpcom_io_src = [ + 'nsNativeCharsetUtils.cpp', +] +src_list += [ + '%s/xpcom/io/%s' % (TOPSRCDIR, s) for s in xpcom_io_src +] + +xpcom_string_src = [ + 'nsDependentSubstring.cpp', + 'nsPromiseFlatString.cpp', + 'nsReadableUtils.cpp', + 'nsString.cpp', + 'nsStringComparator.cpp', + 'nsStringObsolete.cpp', + 'nsSubstring.cpp', + 'nsSubstringTuple.cpp', +] +if CONFIG['INTEL_ARCHITECTURE']: + xpcom_string_src += ['nsUTF8UtilsSSE2.cpp'] +src_list += [ + '%s/xpcom/string/%s' % (TOPSRCDIR, s) for s in xpcom_string_src +] + +xpcom_threads_src = [ + 'LazyIdleThread.cpp', + 'nsEnvironment.cpp', + 'nsEventQueue.cpp', + 'nsMemoryPressure.cpp', + 'nsProcessCommon.cpp', + 'nsThread.cpp', + 'nsThreadManager.cpp', + 'nsThreadPool.cpp', + 'nsTimerImpl.cpp', + 'TimerThread.cpp', +] +src_list += [ + '%s/xpcom/threads/%s' % (TOPSRCDIR, s) for s in xpcom_threads_src +] + + +SOURCES += sorted(src_list) + +if CONFIG['INTEL_ARCHITECTURE']: + sse_string_path = '%s/xpcom/string/nsUTF8UtilsSSE2.cpp' % TOPSRCDIR + SOURCES[sse_string_path].flags += CONFIG['SSE2_FLAGS'] + +GENERATED_INCLUDES += ['..'] +LOCAL_INCLUDES = [ + '../base', + '../build', + '../components', + '../ds', + '../glue', + '../threads', + '/xpcom/reflect/xptinfo/', +] + +DEFINES['MOZILLA_INTERNAL_API'] = True +DEFINES['MOZILLA_XPCOMRT_API'] = True +DEFINES['MOZILLA_EXTERNAL_LINKAGE'] = True + +include('/ipc/chromium/chromium-config.mozbuild') + +SPHINX_TREES['libxpcomrt'] = 'docs' diff --git a/xpcom/moz.build b/xpcom/moz.build index 565315b23b..3e1a89990c 100644 --- a/xpcom/moz.build +++ b/xpcom/moz.build @@ -24,6 +24,7 @@ DIRS += [ 'system', '../chrome', 'build', + 'libxpcomrt', ] if CONFIG['OS_ARCH'] == 'WINNT' and CONFIG['MOZ_DEBUG']: diff --git a/xpcom/string/nsSubstring.cpp b/xpcom/string/nsSubstring.cpp index 5c0d75973e..6c2024e1d4 100644 --- a/xpcom/string/nsSubstring.cpp +++ b/xpcom/string/nsSubstring.cpp @@ -114,11 +114,9 @@ ReleaseData(void* aData, uint32_t aFlags) } else if (aFlags & nsSubstring::F_OWNED) { free(aData); STRING_STAT_INCREMENT(AdoptFree); -#ifdef NS_BUILD_REFCNT_LOGGING // Treat this as destruction of a "StringAdopt" object for leak // tracking purposes. - NS_LogDtor(aData, "StringAdopt", 1); -#endif // NS_BUILD_REFCNT_LOGGING + MOZ_LOG_DTOR(aData, "StringAdopt", 1); } // otherwise, nothing to do. } diff --git a/xpcom/string/nsTSubstring.cpp b/xpcom/string/nsTSubstring.cpp index 12116b3759..e871583b79 100644 --- a/xpcom/string/nsTSubstring.cpp +++ b/xpcom/string/nsTSubstring.cpp @@ -18,9 +18,7 @@ nsTSubstring_CharT::nsTSubstring_CharT(char_type* aData, size_type aLength, { if (aFlags & F_OWNED) { STRING_STAT_INCREMENT(Adopt); -#ifdef NS_BUILD_REFCNT_LOGGING - NS_LogCtor(mData, "StringAdopt", 1); -#endif + MOZ_LOG_CTOR(mData, "StringAdopt", 1); } } #endif /* XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE */ @@ -460,11 +458,9 @@ nsTSubstring_CharT::Adopt(char_type* aData, size_type aLength) SetDataFlags(F_TERMINATED | F_OWNED); STRING_STAT_INCREMENT(Adopt); -#ifdef NS_BUILD_REFCNT_LOGGING // Treat this as construction of a "StringAdopt" object for leak // tracking purposes. - NS_LogCtor(mData, "StringAdopt", 1); -#endif // NS_BUILD_REFCNT_LOGGING + MOZ_LOG_CTOR(mData, "StringAdopt", 1); } else { SetIsVoid(true); } diff --git a/xpcom/threads/BackgroundHangMonitor.cpp b/xpcom/threads/BackgroundHangMonitor.cpp index a0ed088138..a8474084d1 100644 --- a/xpcom/threads/BackgroundHangMonitor.cpp +++ b/xpcom/threads/BackgroundHangMonitor.cpp @@ -156,6 +156,10 @@ public: bool mHanging; // Is the thread in a waiting state bool mWaiting; + // Annotations for the current hang + UniquePtr mAnnotations; + // Annotators registered for this thread + HangMonitor::Observer::Annotators mAnnotators; BackgroundHangThread(const char* aName, uint32_t aTimeoutMs, @@ -290,6 +294,8 @@ BackgroundHangManager::RunMonitorThread() // A hang started currentThread->mHangStart = interval; currentThread->mHanging = true; + currentThread->mAnnotations = + currentThread->mAnnotators.GatherAnnotations(); } } else { if (MOZ_LIKELY(interval != currentThread->mHangStart)) { @@ -323,6 +329,33 @@ BackgroundHangManager::RunMonitorThread() } } +bool +BackgroundHangMonitor::RegisterAnnotator(HangMonitor::Annotator& aAnnotator) +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + BackgroundHangThread* thisThread = BackgroundHangThread::FindThread(); + if (!thisThread) { + return false; + } + return thisThread->mAnnotators.Register(aAnnotator); +#else + return false; +#endif +} + +bool +BackgroundHangMonitor::UnregisterAnnotator(HangMonitor::Annotator& aAnnotator) +{ +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + BackgroundHangThread* thisThread = BackgroundHangThread::FindThread(); + if (!thisThread) { + return false; + } + return thisThread->mAnnotators.Unregister(aAnnotator); +#else + return false; +#endif +} BackgroundHangThread::BackgroundHangThread(const char* aName, uint32_t aTimeoutMs, diff --git a/xpcom/threads/BackgroundHangMonitor.h b/xpcom/threads/BackgroundHangMonitor.h index e523fe0eed..c332339015 100644 --- a/xpcom/threads/BackgroundHangMonitor.h +++ b/xpcom/threads/BackgroundHangMonitor.h @@ -7,6 +7,7 @@ #ifndef mozilla_BackgroundHangMonitor_h #define mozilla_BackgroundHangMonitor_h +#include "mozilla/HangAnnotations.h" #include "mozilla/RefPtr.h" #include "nsString.h" @@ -190,6 +191,21 @@ public: * \see Prohibit() */ static void Allow(); + + /** + * Register an annotator with BHR for the current thread. + * @param aAnnotator annotator to register + * @return true if the annotator was registered, otherwise false. + */ + static bool RegisterAnnotator(HangMonitor::Annotator& aAnnotator); + + /** + * Unregister an annotator that was previously registered via + * RegisterAnnotator. + * @param aAnnotator annotator to unregister + * @return true if there are still remaining annotators registered + */ + static bool UnregisterAnnotator(HangMonitor::Annotator& aAnnotator); }; } // namespace mozilla diff --git a/xpcom/threads/HangAnnotations.cpp b/xpcom/threads/HangAnnotations.cpp new file mode 100644 index 0000000000..775e0dcc58 --- /dev/null +++ b/xpcom/threads/HangAnnotations.cpp @@ -0,0 +1,266 @@ +/* -*- 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/HangAnnotations.h" + +#include + +#include "MainThreadUtils.h" +#include "mozilla/DebugOnly.h" +#include "nsXULAppAPI.h" + +namespace mozilla { +namespace HangMonitor { + +// Chrome hang annotators. This can go away once BHR has completely replaced +// ChromeHangs. +static StaticAutoPtr gChromehangAnnotators; + +class BrowserHangAnnotations : public HangAnnotations +{ +public: + BrowserHangAnnotations(); + ~BrowserHangAnnotations(); + + void AddAnnotation(const nsAString& aName, const int32_t aData) override; + void AddAnnotation(const nsAString& aName, const double aData) override; + void AddAnnotation(const nsAString& aName, const nsAString& aData) override; + void AddAnnotation(const nsAString& aName, const nsACString& aData) override; + void AddAnnotation(const nsAString& aName, const bool aData) override; + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override; + bool IsEmpty() const override; + UniquePtr GetEnumerator() override; + + typedef std::pair AnnotationType; + typedef std::vector VectorType; + typedef VectorType::const_iterator IteratorType; + +private: + VectorType mAnnotations; +}; + +BrowserHangAnnotations::BrowserHangAnnotations() +{ + MOZ_COUNT_CTOR(BrowserHangAnnotations); +} + +BrowserHangAnnotations::~BrowserHangAnnotations() +{ + MOZ_COUNT_DTOR(BrowserHangAnnotations); +} + +void +BrowserHangAnnotations::AddAnnotation(const nsAString& aName, const int32_t aData) +{ + nsString dataString; + dataString.AppendInt(aData); + AnnotationType annotation = std::make_pair(nsString(aName), dataString); + mAnnotations.push_back(annotation); +} + +void +BrowserHangAnnotations::AddAnnotation(const nsAString& aName, const double aData) +{ + nsString dataString; + dataString.AppendFloat(aData); + AnnotationType annotation = std::make_pair(nsString(aName), dataString); + mAnnotations.push_back(annotation); +} + +void +BrowserHangAnnotations::AddAnnotation(const nsAString& aName, const nsAString& aData) +{ + AnnotationType annotation = std::make_pair(nsString(aName), nsString(aData)); + mAnnotations.push_back(annotation); +} + +void +BrowserHangAnnotations::AddAnnotation(const nsAString& aName, const nsACString& aData) +{ + nsString dataString; + AppendUTF8toUTF16(aData, dataString); + AnnotationType annotation = std::make_pair(nsString(aName), dataString); + mAnnotations.push_back(annotation); +} + +void +BrowserHangAnnotations::AddAnnotation(const nsAString& aName, const bool aData) +{ + nsString dataString; + dataString += aData ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"); + AnnotationType annotation = std::make_pair(nsString(aName), dataString); + mAnnotations.push_back(annotation); +} + +/** + * This class itself does not use synchronization but it (and its parent object) + * should be protected by mutual exclusion in some way. In Telemetry the chrome + * hang data is protected via TelemetryImpl::mHangReportsMutex. + */ +class ChromeHangAnnotationEnumerator : public HangAnnotations::Enumerator +{ +public: + explicit ChromeHangAnnotationEnumerator(const BrowserHangAnnotations::VectorType& aAnnotations); + ~ChromeHangAnnotationEnumerator(); + + virtual bool Next(nsAString& aOutName, nsAString& aOutValue); + +private: + BrowserHangAnnotations::IteratorType mIterator; + BrowserHangAnnotations::IteratorType mEnd; +}; + +ChromeHangAnnotationEnumerator::ChromeHangAnnotationEnumerator( + const BrowserHangAnnotations::VectorType& aAnnotations) + : mIterator(aAnnotations.begin()) + , mEnd(aAnnotations.end()) +{ + MOZ_COUNT_CTOR(ChromeHangAnnotationEnumerator); +} + +ChromeHangAnnotationEnumerator::~ChromeHangAnnotationEnumerator() +{ + MOZ_COUNT_DTOR(ChromeHangAnnotationEnumerator); +} + +bool +ChromeHangAnnotationEnumerator::Next(nsAString& aOutName, nsAString& aOutValue) +{ + aOutName.Truncate(); + aOutValue.Truncate(); + if (mIterator == mEnd) { + return false; + } + aOutName = mIterator->first; + aOutValue = mIterator->second; + ++mIterator; + return true; +} + +bool +BrowserHangAnnotations::IsEmpty() const +{ + return mAnnotations.empty(); +} + +size_t +BrowserHangAnnotations::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + size_t result = sizeof(mAnnotations) + + mAnnotations.capacity() * sizeof(AnnotationType); + for (IteratorType i = mAnnotations.begin(), e = mAnnotations.end(); i != e; + ++i) { + result += i->first.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + result += i->second.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + return result; +} + +UniquePtr +BrowserHangAnnotations::GetEnumerator() +{ + if (mAnnotations.empty()) { + return nullptr; + } + return MakeUnique(mAnnotations); +} + +namespace Observer { + +Annotators::Annotators() + : mMutex("HangMonitor::Annotators::mMutex") +{ + MOZ_COUNT_CTOR(Annotators); +} + +Annotators::~Annotators() +{ + MOZ_ASSERT(mAnnotators.empty()); + MOZ_COUNT_DTOR(Annotators); +} + +bool +Annotators::Register(Annotator& aAnnotator) +{ + MutexAutoLock lock(mMutex); + auto result = mAnnotators.insert(&aAnnotator); + return result.second; +} + +bool +Annotators::Unregister(Annotator& aAnnotator) +{ + MutexAutoLock lock(mMutex); + DebugOnly::size_type> numErased; + numErased = mAnnotators.erase(&aAnnotator); + MOZ_ASSERT(numErased == 1); + return mAnnotators.empty(); +} + +UniquePtr +Annotators::GatherAnnotations() +{ + auto annotations = MakeUnique(); + { // Scope for lock + MutexAutoLock lock(mMutex); + for (std::set::iterator i = mAnnotators.begin(), + e = mAnnotators.end(); + i != e; ++i) { + (*i)->AnnotateHang(*annotations); + } + } + if (annotations->IsEmpty()) { + return nullptr; + } + return Move(annotations); +} + +} // namespace Observer + +void +RegisterAnnotator(Annotator& aAnnotator) +{ +#if 0 + BackgroundHangMonitor::RegisterAnnotator(aAnnotator); + // We still register annotators for ChromeHangs + if (NS_IsMainThread() && + GoannaProcessType_Default == XRE_GetProcessType()) { + if (!gChromehangAnnotators) { + gChromehangAnnotators = new Observer::Annotators(); + } + gChromehangAnnotators->Register(aAnnotator); + } +#endif +} + +void +UnregisterAnnotator(Annotator& aAnnotator) +{ +#if 0 + BackgroundHangMonitor::UnregisterAnnotator(aAnnotator); + // We still register annotators for ChromeHangs + if (NS_IsMainThread() && + GoannaProcessType_Default == XRE_GetProcessType()) { + if (gChromehangAnnotators->Unregister(aAnnotator)) { + gChromehangAnnotators = nullptr; + } + } +#endif +} + +UniquePtr +ChromeHangAnnotatorCallout() +{ + if (!gChromehangAnnotators) { + return nullptr; + } + return gChromehangAnnotators->GatherAnnotations(); +} + +} // namespace HangMonitor +} // namespace mozilla diff --git a/xpcom/threads/HangAnnotations.h b/xpcom/threads/HangAnnotations.h new file mode 100644 index 0000000000..6dddbf4bb4 --- /dev/null +++ b/xpcom/threads/HangAnnotations.h @@ -0,0 +1,104 @@ +/* -*- 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_HangAnnotations_h +#define mozilla_HangAnnotations_h + +#include + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.h" +#include "nsString.h" + +namespace mozilla { +namespace HangMonitor { + +/** + * This class declares an abstraction for a data type that encapsulates all + * of the annotations being reported by a registered hang Annotator. + */ +class HangAnnotations +{ +public: + virtual ~HangAnnotations() {} + + virtual void AddAnnotation(const nsAString& aName, const int32_t aData) = 0; + virtual void AddAnnotation(const nsAString& aName, const double aData) = 0; + virtual void AddAnnotation(const nsAString& aName, const nsAString& aData) = 0; + virtual void AddAnnotation(const nsAString& aName, const nsACString& aData) = 0; + virtual void AddAnnotation(const nsAString& aName, const bool aData) = 0; + + class Enumerator + { + public: + virtual ~Enumerator() {} + virtual bool Next(nsAString& aOutName, nsAString& aOutValue) = 0; + }; + + virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0; + virtual bool IsEmpty() const = 0; + virtual UniquePtr GetEnumerator() = 0; +}; + +typedef UniquePtr HangAnnotationsPtr; +typedef Vector HangAnnotationsVector; + +class Annotator +{ +public: + /** + * NB: This function is always called by the HangMonitor thread. + * Plan accordingly. + */ + virtual void AnnotateHang(HangAnnotations& aAnnotations) = 0; +}; + +/** + * Registers an Annotator to be called when a hang is detected. + * @param aAnnotator Reference to an object that implements the + * HangMonitor::Annotator interface. + */ +void RegisterAnnotator(Annotator& aAnnotator); + +/** + * Registers an Annotator that was previously registered via RegisterAnnotator. + * @param aAnnotator Reference to an object that implements the + * HangMonitor::Annotator interface. + */ +void UnregisterAnnotator(Annotator& aAnnotator); + +/** + * Gathers annotations. This function should be called by ChromeHangs. + * @return UniquePtr to HangAnnotations object or nullptr if none. + */ +HangAnnotationsPtr ChromeHangAnnotatorCallout(); + +namespace Observer { + +class Annotators +{ +public: + Annotators(); + ~Annotators(); + + bool Register(Annotator& aAnnotator); + bool Unregister(Annotator& aAnnotator); + + HangAnnotationsPtr GatherAnnotations(); + +private: + Mutex mMutex; + std::set mAnnotators; +}; + +} // namespace Observer + +} // namespace HangMonitor +} // namespace mozilla + +#endif // mozilla_HangAnnotations_h diff --git a/xpcom/threads/HangMonitor.cpp b/xpcom/threads/HangMonitor.cpp index c5a72f1422..d9efd33dcc 100644 --- a/xpcom/threads/HangMonitor.cpp +++ b/xpcom/threads/HangMonitor.cpp @@ -6,8 +6,6 @@ #include "mozilla/HangMonitor.h" -#include - #include "mozilla/Atomics.h" #include "mozilla/BackgroundHangMonitor.h" #include "mozilla/Monitor.h" @@ -275,15 +273,5 @@ Suspend() } } -void -RegisterAnnotator(Annotator& aAnnotator) -{ -} - -void -UnregisterAnnotator(Annotator& aAnnotator) -{ -} - } // namespace HangMonitor } // namespace mozilla diff --git a/xpcom/threads/HangMonitor.h b/xpcom/threads/HangMonitor.h index b8b2291e1d..fd0e6ff839 100644 --- a/xpcom/threads/HangMonitor.h +++ b/xpcom/threads/HangMonitor.h @@ -7,9 +7,6 @@ #ifndef mozilla_HangMonitor_h #define mozilla_HangMonitor_h -#include "mozilla/MemoryReporting.h" -#include "nsString.h" - namespace mozilla { namespace HangMonitor { @@ -41,57 +38,6 @@ void Startup(); */ void Shutdown(); -/** - * This class declares an abstraction for a data type that encapsulates all - * of the annotations being reported by a registered hang Annotator. - */ -class HangAnnotations -{ -public: - virtual ~HangAnnotations() {} - - virtual void AddAnnotation(const nsAString& aName, const int32_t aData) = 0; - virtual void AddAnnotation(const nsAString& aName, const double aData) = 0; - virtual void AddAnnotation(const nsAString& aName, const nsAString& aData) = 0; - virtual void AddAnnotation(const nsAString& aName, const nsACString& aData) = 0; - virtual void AddAnnotation(const nsAString& aName, const bool aData) = 0; - - class Enumerator - { - public: - virtual ~Enumerator() {} - virtual bool Next(nsAString& aOutName, nsAString& aOutValue) = 0; - }; - - virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const = 0; - virtual bool IsEmpty() const = 0; - virtual bool GetEnumerator(Enumerator **aOutEnum) = 0; -}; - -class Annotator -{ -public: - /** - * NB: This function is always called by the HangMonitor thread. - * Plan accordingly. - */ - virtual void AnnotateHang(HangAnnotations& aAnnotations) = 0; -}; - -/** - * Registers an Annotator to be called when a hang is detected. - * @param aAnnotator Reference to an object that implements the - * HangMonitor::Annotator interface. - */ -void RegisterAnnotator(Annotator& aAnnotator); - -/** - * Registers an Annotator that was previously registered via RegisterAnnotator. - * @param aAnnotator Reference to an object that implements the - * HangMonitor::Annotator interface. - */ -void UnregisterAnnotator(Annotator& aAnnotator); - /** * Notify the hang monitor of activity which will reset its internal timer. * diff --git a/xpcom/threads/LazyIdleThread.cpp b/xpcom/threads/LazyIdleThread.cpp index 647af36964..8342960398 100644 --- a/xpcom/threads/LazyIdleThread.cpp +++ b/xpcom/threads/LazyIdleThread.cpp @@ -179,8 +179,10 @@ LazyIdleThread::EnsureThread() void LazyIdleThread::InitThread() { +#if !defined(MOZILLA_XPCOMRT_API) char aLocal; profiler_register_thread(mName.get(), &aLocal); +#endif // !defined(MOZILLA_XPCOMRT_API) PR_SetCurrentThreadName(mName.get()); @@ -211,7 +213,9 @@ LazyIdleThread::CleanupThread() mThreadIsShuttingDown = true; } +#if !defined(MOZILLA_XPCOMRT_API) profiler_unregister_thread(); +#endif // !defined(MOZILLA_XPCOMRT_API) } void diff --git a/xpcom/threads/moz.build b/xpcom/threads/moz.build index f07923c807..35c452b60b 100644 --- a/xpcom/threads/moz.build +++ b/xpcom/threads/moz.build @@ -29,6 +29,7 @@ EXPORTS += [ EXPORTS.mozilla += [ 'BackgroundHangMonitor.h', + 'HangAnnotations.h', 'HangMonitor.h', 'LazyIdleThread.h', 'SyncRunnable.h', @@ -36,6 +37,7 @@ EXPORTS.mozilla += [ UNIFIED_SOURCES += [ 'BackgroundHangMonitor.cpp', + 'HangAnnotations.cpp', 'HangMonitor.cpp', 'LazyIdleThread.cpp', 'nsEnvironment.cpp', diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp index 6b910aa83d..a50aaee894 100644 --- a/xpcom/threads/nsThread.cpp +++ b/xpcom/threads/nsThread.cpp @@ -6,7 +6,9 @@ #include "nsThread.h" +#if !defined(MOZILLA_XPCOMRT_API) #include "base/message_loop.h" +#endif // !defined(MOZILLA_XPCOMRT_API) // Chromium's logging can sometimes leak through... #ifdef LOG @@ -22,13 +24,16 @@ #include "pratom.h" #include "prlog.h" #include "nsIObserverService.h" +#if !defined(MOZILLA_XPCOMRT_API) #include "mozilla/HangMonitor.h" #include "mozilla/IOInterposer.h" #include "mozilla/ipc/MessageChannel.h" +#include "mozilla/ipc/BackgroundChild.h" +#endif // defined(MOZILLA_XPCOMRT_API) #include "mozilla/Services.h" #include "nsXPCOMPrivate.h" #include "mozilla/ChaosMode.h" -#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/TimeStamp.h" #ifdef XP_LINUX #include @@ -267,7 +272,9 @@ public: NS_IMETHOD Run() { mThread->mShutdownContext = mShutdownContext; +#if !defined(MOZILLA_XPCOMRT_API) MessageLoop::current()->Quit(); +#endif // !defined(MOZILLA_XPCOMRT_API) return NS_OK; } private: @@ -318,7 +325,9 @@ SetupCurrentThreadForChaosMode() /*static*/ void nsThread::ThreadFunc(void* aArg) { +#if !defined(MOZILLA_XPCOMRT_API) using mozilla::ipc::BackgroundChild; +#endif // !defined(MOZILLA_XPCOMRT_API) nsThread* self = static_cast(aArg); // strong reference self->mThread = PR_GetCurrentThread(); @@ -331,7 +340,9 @@ nsThread::ThreadFunc(void* aArg) static_cast(nsThreadManager::get()->GetCurrentThreadStatusInfo()); #endif +#if !defined(MOZILLA_XPCOMRT_API) mozilla::IOInterposer::RegisterCurrentThread(); +#endif // !defined(MOZILLA_XPCOMRT_API) // Wait for and process startup event nsCOMPtr event; @@ -343,6 +354,11 @@ nsThread::ThreadFunc(void* aArg) event = nullptr; { +#if defined(MOZILLA_XPCOMRT_API) + while(!self->mShutdownContext) { + NS_ProcessNextEvent(); + } +#else // Scope for MessageLoop. nsAutoPtr loop( new MessageLoop(MessageLoop::TYPE_MOZILLA_NONMAINTHREAD)); @@ -351,6 +367,7 @@ nsThread::ThreadFunc(void* aArg) loop->Run(); BackgroundChild::CloseForCurrentThread(); +#endif // defined(MOZILLA_XPCOMRT_API) // Do NS_ProcessPendingEvents but with special handling to set // mEventsAreDoomed atomically with the removal of the last event. The key @@ -373,7 +390,9 @@ nsThread::ThreadFunc(void* aArg) } } +#if !defined(MOZILLA_XPCOMRT_API) mozilla::IOInterposer::UnregisterCurrentThread(); +#endif // !defined(MOZILLA_XPCOMRT_API) // Inform the threadmanager that this thread is going away nsThreadManager::get()->UnregisterCurrentThread(self); @@ -717,9 +736,11 @@ nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) { LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, aMayWait, mRunningEvent)); +#if !defined(MOZILLA_XPCOMRT_API) // If we're on the main thread, we shouldn't be dispatching CPOWs. MOZ_RELEASE_ASSERT(mIsMainThread != MAIN_THREAD || !ipc::ParentProcessIsBlocked()); +#endif // !defined(MOZILLA_XPCOMRT_API) if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { return NS_ERROR_NOT_SAME_THREAD; @@ -735,9 +756,11 @@ nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) // and repeat the nested event loop since its state change hasn't happened yet. bool reallyWait = aMayWait && (mRunningEvent > 0 || !ShuttingDown()); +#if !defined(MOZILLA_XPCOMRT_API) if (MAIN_THREAD == mIsMainThread && reallyWait) { HangMonitor::Suspend(); } +#endif // !defined(MOZILLA_XPCOMRT_API) // Fire a memory pressure notification, if we're the main thread and one is // pending. @@ -795,9 +818,11 @@ nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) if (event) { LOG(("THRD(%p) running [%p]\n", this, event.get())); +#if !defined(MOZILLA_XPCOMRT_API) if (MAIN_THREAD == mIsMainThread) { HangMonitor::NotifyActivity(); } +#endif // !defined(MOZILLA_XPCOMRT_API) event->Run(); } else if (aMayWait) { MOZ_ASSERT(ShuttingDown(), diff --git a/xpcom/threads/nsTimerImpl.cpp b/xpcom/threads/nsTimerImpl.cpp index 9fa8987e01..0d73757d87 100644 --- a/xpcom/threads/nsTimerImpl.cpp +++ b/xpcom/threads/nsTimerImpl.cpp @@ -566,8 +566,10 @@ nsTimerImpl::Fire() } #endif +#if !defined(MOZILLA_XPCOMRT_API) PROFILER_LABEL("Timer", "Fire", js::ProfileEntry::Category::OTHER); +#endif #ifdef MOZ_TASK_TRACER // mTracedTask is an instance of FakeTracedTask created by