author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Tue, 20 Sep 2016 12:01:29 +0200 | |
changeset 314510 | 62f79d676e0e11b3ad59a5425b3ebb3ec5bbefb5 |
parent 314509 | e81f8c1e38fbcd3763bf0bb87f17133ddf36d8b4 (current diff) |
parent 314437 | 8192ae07b2ba5c67d170d9d5a4257aabd52dae2d (diff) |
child 314511 | 150109898e5fe28f6f1e3b2587fc357329f5195f |
child 314642 | 76bd16c9b4059c1788358725e86e277bca0eea98 |
child 314692 | 14705f779a46da3dbbc41af098209911479cff01 |
push id | 81903 |
push user | cbook@mozilla.com |
push date | Tue, 20 Sep 2016 10:04:26 +0000 |
treeherder | mozilla-inbound@150109898e5f [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 52.0a1 |
first release with | nightly linux32
62f79d676e0e
/
52.0a1
/
20160920030429
/
files
nightly linux64
62f79d676e0e
/
52.0a1
/
20160920030429
/
files
nightly mac
62f79d676e0e
/
52.0a1
/
20160920030429
/
files
nightly win32
62f79d676e0e
/
52.0a1
/
20160920030429
/
files
nightly win64
62f79d676e0e
/
52.0a1
/
20160920030429
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
52.0a1
/
20160920030429
/
pushlog to previous
nightly linux64
52.0a1
/
20160920030429
/
pushlog to previous
nightly mac
52.0a1
/
20160920030429
/
pushlog to previous
nightly win32
52.0a1
/
20160920030429
/
pushlog to previous
nightly win64
52.0a1
/
20160920030429
/
pushlog to previous
|
--- a/accessible/ipc/DocAccessibleChildBase.cpp +++ b/accessible/ipc/DocAccessibleChildBase.cpp @@ -77,23 +77,61 @@ DocAccessibleChildBase::SerializeTree(Ac aTree.AppendElement(AccessibleData(id, role, childCount, interfaces)); #endif for (uint32_t i = 0; i < childCount; i++) { SerializeTree(aRoot->GetChildAt(i), aTree); } } +#if defined(XP_WIN) +/* static */ void +DocAccessibleChildBase::SetMsaaIds(Accessible* aRoot, + uint32_t& aMsaaIdIndex, + const nsTArray<MsaaMapping>& aNewMsaaIds) +{ + const MsaaMapping& mapping = aNewMsaaIds[aMsaaIdIndex]; +#if defined(DEBUG) + uint64_t id = reinterpret_cast<uint64_t>(aRoot->UniqueID()); + MOZ_ASSERT(mapping.ID() == id); +#endif // defined(DEBUG) + static_cast<AccessibleWrap*>(aRoot)->SetID(mapping.MsaaID()); + ++aMsaaIdIndex; + if (aRoot->IsOuterDoc()) { + // This needs to match the tree traversal in SerializeTree + return; + } + for (uint32_t i = 0, n = aRoot->ChildCount(); i < n; ++i) { + SetMsaaIds(aRoot->GetChildAt(i), aMsaaIdIndex, aNewMsaaIds); + } +} +#endif // defined(XP_WIN) + void DocAccessibleChildBase::ShowEvent(AccShowEvent* aShowEvent) { Accessible* parent = aShowEvent->Parent(); uint64_t parentID = parent->IsDoc() ? 0 : reinterpret_cast<uint64_t>(parent->UniqueID()); uint32_t idxInParent = aShowEvent->InsertionIndex(); nsTArray<AccessibleData> shownTree; ShowEventData data(parentID, idxInParent, shownTree); SerializeTree(aShowEvent->GetAccessible(), data.NewTree()); +#if defined(XP_WIN) + nsTArray<MsaaMapping> newMsaaIds; + SendShowEventInfo(data, &newMsaaIds); + // newMsaaIds could be empty if something went wrong in SendShowEvent() + if (!newMsaaIds.IsEmpty()) { + uint32_t index = 0; + SetMsaaIds(aShowEvent->GetAccessible(), index, newMsaaIds); + } + // NB: On Windows, SendShowEvent attaches the subtree and generates new IDs, + // but does *NOT* fire the native event. We need to do that after + // we've called SetMsaaIds. + SendEvent(reinterpret_cast<uint64_t>(aShowEvent->GetAccessible()->UniqueID()), + nsIAccessibleEvent::EVENT_SHOW); +#else SendShowEvent(data, aShowEvent->IsFromUserInput()); +#endif // defined(XP_WIN) } } // namespace a11y } // namespace mozilla
--- a/accessible/ipc/DocAccessibleChildBase.h +++ b/accessible/ipc/DocAccessibleChildBase.h @@ -55,16 +55,20 @@ public: mDoc->SetIPCDoc(nullptr); mDoc = nullptr; } protected: static uint32_t InterfacesFor(Accessible* aAcc); static void SerializeTree(Accessible* aRoot, nsTArray<AccessibleData>& aTree); +#if defined(XP_WIN) + static void SetMsaaIds(Accessible* aRoot, uint32_t& aMsaaIdIndex, + const nsTArray<MsaaMapping>& aNewMsaaIds); +#endif DocAccessible* mDoc; }; } // namespace a11y } // namespace mozilla #endif // mozilla_a11y_DocAccessibleChildBase_h
--- a/accessible/ipc/DocAccessibleParent.cpp +++ b/accessible/ipc/DocAccessibleParent.cpp @@ -11,18 +11,23 @@ #include "xpcAccEvents.h" #include "nsAccUtils.h" #include "nsCoreUtils.h" namespace mozilla { namespace a11y { bool +#if defined(XP_WIN) +DocAccessibleParent::RecvShowEventInfo(const ShowEventData& aData, + nsTArray<MsaaMapping>* aNewMsaaIds) +#else DocAccessibleParent::RecvShowEvent(const ShowEventData& aData, const bool& aFromUser) +#endif // defined(XP_WIN) { if (mShutdown) return true; MOZ_DIAGNOSTIC_ASSERT(CheckDocTree()); if (aData.NewTree().IsEmpty()) { NS_ERROR("no children being added"); @@ -39,17 +44,23 @@ DocAccessibleParent::RecvShowEvent(const } uint32_t newChildIdx = aData.Idx(); if (newChildIdx > parent->ChildrenCount()) { NS_ERROR("invalid index to add child at"); return true; } +#if defined(XP_WIN) + aNewMsaaIds->SetCapacity(aData.NewTree().Length()); + uint32_t consumed = AddSubtree(parent, aData.NewTree(), 0, newChildIdx, + aNewMsaaIds); +#else uint32_t consumed = AddSubtree(parent, aData.NewTree(), 0, newChildIdx); +#endif MOZ_ASSERT(consumed == aData.NewTree().Length()); // XXX This shouldn't happen, but if we failed to add children then the below // is pointless and can crash. if (!consumed) { return true; } @@ -57,38 +68,46 @@ DocAccessibleParent::RecvShowEvent(const for (uint32_t i = 0; i < consumed; i++) { uint64_t id = aData.NewTree()[i].ID(); MOZ_ASSERT(mAccessibles.GetEntry(id)); } #endif MOZ_DIAGNOSTIC_ASSERT(CheckDocTree()); + // NB: On Windows we dispatch the native event via a subsequent call to + // RecvEvent(). +#if !defined(XP_WIN) ProxyAccessible* target = parent->ChildAt(newChildIdx); ProxyShowHideEvent(target, parent, true, aFromUser); if (!nsCoreUtils::AccEventObserversExist()) { return true; } uint32_t type = nsIAccessibleEvent::EVENT_SHOW; xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target); xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this); nsIDOMNode* node = nullptr; RefPtr<xpcAccEvent> event = new xpcAccEvent(type, xpcAcc, doc, node, aFromUser); nsCoreUtils::DispatchAccEvent(Move(event)); +#endif return true; } uint32_t DocAccessibleParent::AddSubtree(ProxyAccessible* aParent, const nsTArray<a11y::AccessibleData>& aNewTree, - uint32_t aIdx, uint32_t aIdxInParent) + uint32_t aIdx, uint32_t aIdxInParent +#if defined(XP_WIN) + , nsTArray<MsaaMapping>* aNewMsaaIds +#endif + ) { if (aNewTree.Length() <= aIdx) { NS_ERROR("bad index in serialized tree!"); return 0; } const AccessibleData& newChild = aNewTree[aIdx]; if (newChild.Role() > roles::LAST_ROLE) { @@ -119,20 +138,32 @@ DocAccessibleParent::AddSubtree(ProxyAcc new ProxyAccessible(newChild.ID(), aParent, this, role, newChild.Interfaces()); #endif aParent->AddChildAt(aIdxInParent, newProxy); mAccessibles.PutEntry(newChild.ID())->mProxy = newProxy; ProxyCreated(newProxy, newChild.Interfaces()); +#if defined(XP_WIN) + Accessible* idForAcc = WrapperFor(newProxy); + MOZ_ASSERT(idForAcc); + uint32_t newMsaaId = AccessibleWrap::GetChildIDFor(idForAcc); + MOZ_ASSERT(newMsaaId); + aNewMsaaIds->AppendElement(MsaaMapping(newChild.ID(), newMsaaId)); +#endif // defined(XP_WIN) + uint32_t accessibles = 1; uint32_t kids = newChild.ChildrenCount(); for (uint32_t i = 0; i < kids; i++) { - uint32_t consumed = AddSubtree(newProxy, aNewTree, aIdx + accessibles, i); + uint32_t consumed = AddSubtree(newProxy, aNewTree, aIdx + accessibles, i +#if defined(XP_WIN) + , aNewMsaaIds +#endif + ); if (!consumed) return 0; accessibles += consumed; } MOZ_ASSERT(newProxy->ChildrenCount() == kids); @@ -471,26 +502,29 @@ DocAccessibleParent::GetXPCAccessible(Pr /** * @param aCOMProxy COM Proxy to the document in the content process. * @param aParentCOMProxy COM Proxy to the OuterDocAccessible that is * the parent of the document. The content process will use this * proxy when traversing up across the content/chrome boundary. */ bool DocAccessibleParent::RecvCOMProxy(const IAccessibleHolder& aCOMProxy, - IAccessibleHolder* aParentCOMProxy) + IAccessibleHolder* aParentCOMProxy, + uint32_t* aMsaaID) { RefPtr<IAccessible> ptr(aCOMProxy.Get()); SetCOMInterface(ptr); Accessible* outerDoc = OuterDocOfRemoteBrowser(); IAccessible* rawNative = nullptr; if (outerDoc) { outerDoc->GetNativeInterface((void**) &rawNative); } aParentCOMProxy->Set(IAccessibleHolder::COMPtrType(rawNative)); + Accessible* wrapper = WrapperFor(this); + *aMsaaID = AccessibleWrap::GetChildIDFor(wrapper); return true; } #endif // defined(XP_WIN) } // a11y } // mozilla
--- a/accessible/ipc/DocAccessibleParent.h +++ b/accessible/ipc/DocAccessibleParent.h @@ -45,18 +45,23 @@ public: /* * Called when a message from a document in a child process notifies the main * process it is firing an event. */ virtual bool RecvEvent(const uint64_t& aID, const uint32_t& aType) override; +#if defined(XP_WIN) + virtual bool RecvShowEventInfo(const ShowEventData& aData, + nsTArray<MsaaMapping>* aNewMsaaIds) override; +#else virtual bool RecvShowEvent(const ShowEventData& aData, const bool& aFromUser) override; +#endif // defined(XP_WIN) virtual bool RecvHideEvent(const uint64_t& aRootID, const bool& aFromUser) override; virtual bool RecvStateChangeEvent(const uint64_t& aID, const uint64_t& aState, const bool& aEnabled) override final; virtual bool RecvCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset) override final; @@ -139,17 +144,18 @@ public: { return const_cast<DocAccessibleParent*>(this)->GetAccessible(aID); } size_t ChildDocCount() const { return mChildDocs.Length(); } const DocAccessibleParent* ChildDocAt(size_t aIdx) const { return mChildDocs[aIdx]; } #if defined(XP_WIN) virtual bool RecvCOMProxy(const IAccessibleHolder& aCOMProxy, - IAccessibleHolder* aParentCOMProxy) override; + IAccessibleHolder* aParentCOMProxy, + uint32_t* aMsaaID) override; #endif private: class ProxyEntry : public PLDHashEntryHdr { public: explicit ProxyEntry(const void*) : mProxy(nullptr) {} @@ -169,17 +175,21 @@ private: enum { ALLOW_MEMMOVE = true }; ProxyAccessible* mProxy; }; uint32_t AddSubtree(ProxyAccessible* aParent, const nsTArray<AccessibleData>& aNewTree, uint32_t aIdx, - uint32_t aIdxInParent); + uint32_t aIdxInParent +#if defined(XP_WIN) + , nsTArray<MsaaMapping>* aNewMsaaIds +#endif // defined(XP_WIN) + ); MOZ_MUST_USE bool CheckDocTree() const; xpcAccessibleGeneric* GetXPCAccessible(ProxyAccessible* aProxy); nsTArray<DocAccessibleParent*> mChildDocs; DocAccessibleParent* mParentDoc; /* * Conceptually this is a map from IDs to proxies, but we store the ID in the
--- a/accessible/ipc/win/DocAccessibleChild.cpp +++ b/accessible/ipc/win/DocAccessibleChild.cpp @@ -29,15 +29,17 @@ DocAccessibleChild::~DocAccessibleChild( { MOZ_COUNT_DTOR_INHERITED(DocAccessibleChild, DocAccessibleChildBase); } void DocAccessibleChild::SendCOMProxy(const IAccessibleHolder& aProxy) { IAccessibleHolder parentProxy; - PDocAccessibleChild::SendCOMProxy(aProxy, &parentProxy); + uint32_t msaaID = AccessibleWrap::kNoID; + PDocAccessibleChild::SendCOMProxy(aProxy, &parentProxy, &msaaID); mParentProxy.reset(parentProxy.Release()); + mDoc->SetID(msaaID); } } // namespace a11y } // namespace mozilla
--- a/accessible/ipc/win/PDocAccessible.ipdl +++ b/accessible/ipc/win/PDocAccessible.ipdl @@ -22,16 +22,22 @@ struct AccessibleData struct ShowEventData { uint64_t ID; uint32_t Idx; AccessibleData[] NewTree; }; +struct MsaaMapping +{ + uint64_t ID; + uint32_t MsaaID; +}; + struct Attribute { nsCString Name; nsString Value; }; sync protocol PDocAccessible { @@ -40,17 +46,17 @@ sync protocol PDocAccessible parent: async Shutdown(); /* * Notify the parent process the document in the child process is firing an * event. */ async Event(uint64_t aID, uint32_t type); - async ShowEvent(ShowEventData data, bool aFromuser); + sync ShowEventInfo(ShowEventData data) returns (MsaaMapping[] aNewMsaaIds); async HideEvent(uint64_t aRootID, bool aFromUser); async StateChangeEvent(uint64_t aID, uint64_t aState, bool aEnabled); async CaretMoveEvent(uint64_t aID, int32_t aOffset); async TextChangeEvent(uint64_t aID, nsString aStr, int32_t aStart, uint32_t aLen, bool aIsInsert, bool aFromUser); async SelectionEvent(uint64_t aID, uint64_t aWidgetID, uint32_t aType); async RoleChangedEvent(uint32_t aRole); @@ -58,16 +64,16 @@ parent: * Tell the parent document to bind the existing document as a new child * document. */ async BindChildDoc(PDocAccessible aChildDoc, uint64_t aID); // For now we'll add the command to send the proxy here. This might move to // PDocAccessible constructor in PBrowser. sync COMProxy(IAccessibleHolder aDocCOMProxy) - returns(IAccessibleHolder aParentCOMProxy); + returns(IAccessibleHolder aParentCOMProxy, uint32_t aMsaaID); child: async __delete__(); }; } }
--- a/accessible/windows/msaa/AccessibleWrap.cpp +++ b/accessible/windows/msaa/AccessibleWrap.cpp @@ -4,16 +4,18 @@ * 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 "AccessibleWrap.h" #include "Accessible-inl.h" #include "Compatibility.h" #include "DocAccessible-inl.h" +#include "mozilla/dom/TabChild.h" +#include "mozilla/a11y/DocAccessibleChild.h" #include "mozilla/a11y/DocAccessibleParent.h" #include "EnumVariant.h" #include "nsAccUtils.h" #include "nsCoreUtils.h" #include "nsIAccessibleEvent.h" #include "nsWinUtils.h" #include "mozilla/a11y/ProxyAccessible.h" #include "ProxyWrappers.h" @@ -64,46 +66,42 @@ IDSet AccessibleWrap::sIDGen; static const int32_t kIEnumVariantDisconnected = -1; //////////////////////////////////////////////////////////////////////////////// // AccessibleWrap //////////////////////////////////////////////////////////////////////////////// AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) : Accessible(aContent, aDoc) -#ifdef _WIN64 , mID(kNoID) -#endif { } AccessibleWrap::~AccessibleWrap() { #ifdef _WIN64 - if (mID != kNoID) + if (mID != kNoID && XRE_IsParentProcess()) sIDGen.ReleaseID(mID); #endif } ITypeInfo* AccessibleWrap::gTypeInfo = nullptr; NS_IMPL_ISUPPORTS_INHERITED0(AccessibleWrap, Accessible) void AccessibleWrap::Shutdown() { -#ifdef _WIN64 if (mID != kNoID) { auto doc = static_cast<DocAccessibleWrap*>(mDoc); MOZ_ASSERT(doc); if (doc) { doc->RemoveID(mID); } } -#endif Accessible::Shutdown(); } //----------------------------------------------------- // IUnknown interface methods - see iunknown.h for documentation //----------------------------------------------------- @@ -1143,18 +1141,31 @@ AccessibleWrap::Invoke(DISPID dispIdMemb void AccessibleWrap::GetNativeInterface(void** aOutAccessible) { *aOutAccessible = static_cast<IAccessible*>(this); NS_ADDREF_THIS(); } void +AccessibleWrap::SetID(uint32_t aID) +{ + MOZ_ASSERT(XRE_IsContentProcess()); + mID = aID; + DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(Document()); + DebugOnly<AccessibleWrap*> checkAcc = nullptr; + MOZ_ASSERT(!(checkAcc = doc->GetAccessibleByID(aID)) || + checkAcc->GetExistingID() == aID); + doc->AddID(aID, this); +} + +void AccessibleWrap::FireWinEvent(Accessible* aTarget, uint32_t aEventType) { + MOZ_ASSERT(XRE_IsParentProcess()); static_assert(sizeof(gWinEventMap)/sizeof(gWinEventMap[0]) == nsIAccessibleEvent::EVENT_LAST_ENTRY, "MSAA event map skewed"); NS_ASSERTION(aEventType > 0 && aEventType < ArrayLength(gWinEventMap), "invalid event type"); uint32_t winEvent = gWinEventMap[aEventType]; if (!winEvent) return; @@ -1241,16 +1252,23 @@ AccessibleWrap::GetChildIDFor(Accessible // A child ID of the window is required, when we use NotifyWinEvent, // so that the 3rd party application can call back and get the IAccessible // the event occurred on. if (!aAccessible) { return 0; } + // Content should use mID which has been generated by the chrome process. + if (XRE_IsContentProcess() && !aAccessible->IsApplication()) { + const uint32_t id = static_cast<AccessibleWrap*>(aAccessible)->mID; + MOZ_ASSERT(id != kNoID); + return id; + } + #ifdef _WIN64 if (!aAccessible->Document() && !aAccessible->IsProxy()) return 0; uint32_t* id = & static_cast<AccessibleWrap*>(aAccessible)->mID; if (*id != kNoID) return *id; @@ -1281,16 +1299,32 @@ AccessibleWrap::GetChildIDFor(Accessible HWND AccessibleWrap::GetHWNDFor(Accessible* aAccessible) { if (!aAccessible) { return nullptr; } + if (XRE_IsContentProcess()) { + DocAccessible* doc = aAccessible->Document(); + if (!doc) { + return nullptr; + } + + DocAccessibleChild* ipcDoc = doc->IPCDoc(); + if (!ipcDoc) { + return nullptr; + } + + auto tab = static_cast<dom::TabChild*>(ipcDoc->Manager()); + MOZ_ASSERT(tab); + return reinterpret_cast<HWND>(tab->GetNativeWindowHandle()); + } + // Accessibles in child processes are said to have the HWND of the window // their tab is within. Popups are always in the parent process, and so // never proxied, which means this is basically correct. if (aAccessible->IsProxy()) { ProxyAccessible* proxy = aAccessible->Proxy(); if (!proxy) { return nullptr; } @@ -1340,34 +1374,32 @@ AccessibleWrap::NativeAccessible(Accessi return nullptr; } IAccessible* msaaAccessible = nullptr; aAccessible->GetNativeInterface(reinterpret_cast<void**>(&msaaAccessible)); return static_cast<IDispatch*>(msaaAccessible); } -#ifdef _WIN64 static Accessible* GetAccessibleInSubtree(DocAccessible* aDoc, uint32_t aID) { Accessible* child = static_cast<DocAccessibleWrap*>(aDoc)->GetAccessibleByID(aID); if (child) return child; uint32_t childDocCount = aDoc->ChildDocumentCount(); for (uint32_t i = 0; i < childDocCount; i++) { child = GetAccessibleInSubtree(aDoc->GetChildDocumentAt(i), aID); if (child) return child; } return nullptr; } -#endif static AccessibleWrap* GetProxiedAccessibleInSubtree(const DocAccessibleParent* aDoc, uint32_t aID) { auto wrapper = static_cast<DocProxyAccessibleWrap*>(WrapperFor(aDoc)); AccessibleWrap* child = wrapper->GetAccessibleByID(aID); if (child) { return child; @@ -1421,17 +1453,19 @@ AccessibleWrap::GetXPAccessibleFor(const if (!IsProxy()) { void* uniqueID = reinterpret_cast<void*>(intptr_t(-aVarChild.lVal)); DocAccessible* document = Document(); Accessible* child = #ifdef _WIN64 GetAccessibleInSubtree(document, static_cast<uint32_t>(aVarChild.lVal)); #else - document->GetAccessibleByUniqueIDInSubtree(uniqueID); + XRE_IsContentProcess() ? + GetAccessibleInSubtree(document, static_cast<uint32_t>(aVarChild.lVal)) : + document->GetAccessibleByUniqueIDInSubtree(uniqueID); #endif // If it is a document then just return an accessible. if (child && IsDoc()) return child; // Otherwise check whether the accessible is a child (this path works for // ARIA documents and popups).
--- a/accessible/windows/msaa/AccessibleWrap.h +++ b/accessible/windows/msaa/AccessibleWrap.h @@ -174,27 +174,25 @@ public: // construction, destruction * Find an accessible by the given child ID in cached documents. */ Accessible* GetXPAccessibleFor(const VARIANT& aVarChild); virtual void GetNativeInterface(void **aOutAccessible) override; static IDispatch* NativeAccessible(Accessible* aAccessible); -#ifdef _WIN64 uint32_t GetExistingID() const { return mID; } static const uint32_t kNoID = 0; -#endif + // This is only valid to call in content + void SetID(uint32_t aID); protected: virtual ~AccessibleWrap(); -#ifdef _WIN64 uint32_t mID; -#endif /** * Return the wrapper for the document's proxy. */ DocProxyAccessibleWrap* DocProxyWrapper() const; /** * Creates ITypeInfo for LIBID_Accessibility if it's needed and returns it.
--- a/accessible/windows/msaa/DocAccessibleWrap.cpp +++ b/accessible/windows/msaa/DocAccessibleWrap.cpp @@ -43,25 +43,20 @@ IMPL_IUNKNOWN_QUERY_HEAD(DocAccessibleWr return S_OK; } IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(HyperTextAccessibleWrap) STDMETHODIMP DocAccessibleWrap::get_accParent( /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispParent) { - HRESULT hr = DocAccessible::get_accParent(ppdispParent); - if (*ppdispParent) { - return hr; - } - // We might be a top-level document in a content process. DocAccessibleChild* ipcDoc = IPCDoc(); if (!ipcDoc) { - return S_FALSE; + return DocAccessible::get_accParent(ppdispParent); } IAccessible* dispParent = ipcDoc->GetParentIAccessible(); if (!dispParent) { return S_FALSE; } dispParent->AddRef(); *ppdispParent = static_cast<IDispatch*>(dispParent);
--- a/accessible/windows/msaa/DocAccessibleWrap.h +++ b/accessible/windows/msaa/DocAccessibleWrap.h @@ -35,35 +35,31 @@ public: virtual void Shutdown(); // DocAccessible virtual void* GetNativeWindow() const; /** * Manage the mapping from id to Accessible. */ -#ifdef _WIN64 void AddID(uint32_t aID, AccessibleWrap* aAcc) { mIDToAccessibleMap.Put(aID, aAcc); } void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); } AccessibleWrap* GetAccessibleByID(uint32_t aID) const { return mIDToAccessibleMap.Get(aID); } -#endif protected: // DocAccessible virtual void DoInitialUpdate(); protected: void* mHWND; /* * This provides a mapping from 32 bit id to accessible objects. */ -#ifdef _WIN64 nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap; -#endif }; } // namespace a11y } // namespace mozilla #endif
--- a/dom/base/test/browser_use_counters.js +++ b/dom/base/test/browser_use_counters.js @@ -1,21 +1,27 @@ /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */ requestLongerTimeout(2); var {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); +Cu.import("resource://gre/modules/Services.jsm"); const gHttpTestRoot = "http://example.com/browser/dom/base/test/"; /** * Enable local telemetry recording for the duration of the tests. */ var gOldContentCanRecord = false; +var gOldParentCanRecord = false; add_task(function* test_initialize() { + let Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); + gOldParentCanRecord = Telemetry.canRecordExtended + Telemetry.canRecordExtended = true; + // Because canRecordExtended is a per-process variable, we need to make sure // that all of the pages load in the same content process. Limit the number // of content processes to at most 1 (or 0 if e10s is off entirely). yield SpecialPowers.pushPrefEnv({ set: [[ "dom.ipc.processCount", 1 ]] }); gOldContentCanRecord = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function () { let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); let old = telemetry.canRecordExtended; @@ -69,16 +75,19 @@ add_task(function* () { // document counters, because we won't be re-parsing the SVG documents. yield check_use_counter_iframe("file_use_counter_svg_background.html", "PROPERTY_FILL", false); yield check_use_counter_iframe("file_use_counter_svg_list_style_image.html", "PROPERTY_FILL", false); }); add_task(function* () { + let Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); + Telemetry.canRecordExtended = gOldParentCanRecord; + yield ContentTask.spawn(gBrowser.selectedBrowser, { oldCanRecord: gOldContentCanRecord }, function (arg) { Cu.import("resource://gre/modules/PromiseUtils.jsm"); yield new Promise(resolve => { let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); telemetry.canRecordExtended = arg.oldCanRecord; resolve(); }); }); @@ -99,46 +108,42 @@ function waitForPageLoad(browser) { removeEventListener("load", listener, true); resolve(); } addEventListener("load", listener, true); }); }); } -function grabHistogramsFromContent(browser, use_counter_middlefix) { - return ContentTask.spawn(browser, { middlefix: use_counter_middlefix }, function* (arg) { - let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); - function snapshot_histogram(name) { - return telemetry.getHistogramById(name).snapshot(); - } - - let histogram_page_name = "USE_COUNTER2_" + arg.middlefix + "_PAGE"; - let histogram_document_name = "USE_COUNTER2_" + arg.middlefix + "_DOCUMENT"; - let histogram_page = snapshot_histogram(histogram_page_name); - let histogram_document = snapshot_histogram(histogram_document_name); - let histogram_docs = snapshot_histogram("CONTENT_DOCUMENTS_DESTROYED"); - let histogram_toplevel_docs = snapshot_histogram("TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED"); - return [histogram_page.sum, histogram_document.sum, - histogram_docs.sum, histogram_toplevel_docs.sum]; - }); +function grabHistogramsFromContent(use_counter_middlefix, page_before = null) { + let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); + let suffix = Services.appinfo.browserTabsRemoteAutostart ? "#content" : ""; + let gather = () => [ + telemetry.getHistogramById("USE_COUNTER2_" + use_counter_middlefix + "_PAGE" + suffix).snapshot().sum, + telemetry.getHistogramById("USE_COUNTER2_" + use_counter_middlefix + "_DOCUMENT" + suffix).snapshot().sum, + telemetry.getHistogramById("CONTENT_DOCUMENTS_DESTROYED" + suffix).snapshot().sum, + telemetry.getHistogramById("TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED" + suffix).snapshot().sum, + ]; + return BrowserTestUtils.waitForCondition(() => { + return page_before != telemetry.getHistogramById("USE_COUNTER2_" + use_counter_middlefix + "_PAGE" + suffix).snapshot().sum; + }).then(gather, gather); } var check_use_counter_iframe = Task.async(function* (file, use_counter_middlefix, check_documents=true) { info("checking " + file + " with histogram " + use_counter_middlefix); let newTab = gBrowser.addTab( "about:blank"); gBrowser.selectedTab = newTab; newTab.linkedBrowser.stop(); // Hold on to the current values of the telemetry histograms we're // interested in. let [histogram_page_before, histogram_document_before, histogram_docs_before, histogram_toplevel_docs_before] = - yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix); + yield grabHistogramsFromContent(use_counter_middlefix); gBrowser.selectedBrowser.loadURI(gHttpTestRoot + "file_use_counter_outer.html"); yield waitForPageLoad(gBrowser.selectedBrowser); // Inject our desired file into the iframe of the newly-loaded page. yield ContentTask.spawn(gBrowser.selectedBrowser, { file: file }, function(opts) { Cu.import("resource://gre/modules/PromiseUtils.jsm"); let deferred = PromiseUtils.defer(); @@ -146,17 +151,17 @@ var check_use_counter_iframe = Task.asyn let wu = content.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); let iframe = content.document.getElementById('content'); iframe.src = opts.file; let listener = (event) => { event.target.removeEventListener("load", listener, true); // We flush the main document first, then the iframe's document to - // ensure any propagation that might happen from child->parent should + // ensure any propagation that might happen from content->parent should // have already happened when counters are reported to telemetry. wu.forceUseCounterFlush(content.document); wu.forceUseCounterFlush(iframe.contentDocument); deferred.resolve(); }; iframe.addEventListener("load", listener, true); @@ -169,17 +174,17 @@ var check_use_counter_iframe = Task.asyn // The histograms only get recorded when the document actually gets // destroyed, which might not have happened yet due to GC/CC effects, etc. // Try to force document destruction. yield waitForDestroyedDocuments(); // Grab histograms again and compare. let [histogram_page_after, histogram_document_after, histogram_docs_after, histogram_toplevel_docs_after] = - yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix); + yield grabHistogramsFromContent(use_counter_middlefix, histogram_page_before); is(histogram_page_after, histogram_page_before + 1, "page counts for " + use_counter_middlefix + " after are correct"); ok(histogram_toplevel_docs_after >= histogram_toplevel_docs_before + 1, "top level document counts are correct"); if (check_documents) { is(histogram_document_after, histogram_document_before + 1, "document counts for " + use_counter_middlefix + " after are correct"); @@ -192,17 +197,17 @@ var check_use_counter_img = Task.async(f let newTab = gBrowser.addTab("about:blank"); gBrowser.selectedTab = newTab; newTab.linkedBrowser.stop(); // Hold on to the current values of the telemetry histograms we're // interested in. let [histogram_page_before, histogram_document_before, histogram_docs_before, histogram_toplevel_docs_before] = - yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix); + yield grabHistogramsFromContent(use_counter_middlefix); gBrowser.selectedBrowser.loadURI(gHttpTestRoot + "file_use_counter_outer.html"); yield waitForPageLoad(gBrowser.selectedBrowser); // Inject our desired file into the img of the newly-loaded page. yield ContentTask.spawn(gBrowser.selectedBrowser, { file: file }, function(opts) { Cu.import("resource://gre/modules/PromiseUtils.jsm"); let deferred = PromiseUtils.defer(); @@ -234,17 +239,17 @@ var check_use_counter_img = Task.async(f // The histograms only get recorded when the document actually gets // destroyed, which might not have happened yet due to GC/CC effects, etc. // Try to force document destruction. yield waitForDestroyedDocuments(); // Grab histograms again and compare. let [histogram_page_after, histogram_document_after, histogram_docs_after, histogram_toplevel_docs_after] = - yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix); + yield grabHistogramsFromContent(use_counter_middlefix, histogram_page_before); is(histogram_page_after, histogram_page_before + 1, "page counts for " + use_counter_middlefix + " after are correct"); is(histogram_document_after, histogram_document_before + 1, "document counts for " + use_counter_middlefix + " after are correct"); ok(histogram_toplevel_docs_after >= histogram_toplevel_docs_before + 1, "top level document counts are correct"); // 2 documents: one for the outer html page containing the <img> element, and // one for the SVG image itself. @@ -258,17 +263,17 @@ var check_use_counter_direct = Task.asyn let newTab = gBrowser.addTab( "about:blank"); gBrowser.selectedTab = newTab; newTab.linkedBrowser.stop(); // Hold on to the current values of the telemetry histograms we're // interested in. let [histogram_page_before, histogram_document_before, histogram_docs_before, histogram_toplevel_docs_before] = - yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix); + yield grabHistogramsFromContent(use_counter_middlefix); gBrowser.selectedBrowser.loadURI(gHttpTestRoot + file); yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { Cu.import("resource://gre/modules/PromiseUtils.jsm"); yield new Promise(resolve => { let listener = () => { removeEventListener("load", listener, true); @@ -287,17 +292,17 @@ var check_use_counter_direct = Task.asyn // The histograms only get recorded when the document actually gets // destroyed, which might not have happened yet due to GC/CC effects, etc. // Try to force document destruction. yield waitForDestroyedDocuments(); // Grab histograms again and compare. let [histogram_page_after, histogram_document_after, histogram_docs_after, histogram_toplevel_docs_after] = - yield grabHistogramsFromContent(gBrowser.selectedBrowser, use_counter_middlefix); + yield grabHistogramsFromContent(use_counter_middlefix, histogram_page_before); (xfail ? todo_is : is)(histogram_page_after, histogram_page_before + 1, "page counts for " + use_counter_middlefix + " after are correct"); (xfail ? todo_is : is)(histogram_document_after, histogram_document_before + 1, "document counts for " + use_counter_middlefix + " after are correct"); ok(histogram_toplevel_docs_after >= histogram_toplevel_docs_before + 1, "top level document counts are correct"); ok(histogram_docs_after >= histogram_docs_before + 1, "document counts are correct");
--- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -5408,8 +5408,24 @@ ContentParent::SendGetFilesResponseAndFo void ContentParent::ForceTabPaint(TabParent* aTabParent, uint64_t aLayerObserverEpoch) { if (!mHangMonitorActor) { return; } ProcessHangMonitor::ForcePaint(mHangMonitorActor, aTabParent, aLayerObserverEpoch); } + +bool +ContentParent::RecvAccumulateChildHistogram( + InfallibleTArray<Accumulation>&& aAccumulations) +{ + Telemetry::AccumulateChild(aAccumulations); + return true; +} + +bool +ContentParent::RecvAccumulateChildKeyedHistogram( + InfallibleTArray<KeyedAccumulation>&& aAccumulations) +{ + Telemetry::AccumulateChildKeyed(aAccumulations); + return true; +}
--- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -1132,16 +1132,20 @@ private: virtual bool RecvNotifyLowMemory() override; virtual bool RecvGetFilesRequest(const nsID& aID, const nsString& aDirectoryPath, const bool& aRecursiveFlag) override; virtual bool RecvDeleteGetFilesRequest(const nsID& aID) override; + virtual bool RecvAccumulateChildHistogram( + InfallibleTArray<Accumulation>&& aAccumulations) override; + virtual bool RecvAccumulateChildKeyedHistogram( + InfallibleTArray<KeyedAccumulation>&& aAccumulations) override; public: void SendGetFilesResponseAndForget(const nsID& aID, const GetFilesResponseResult& aResult); private: // If you add strong pointers to cycle collected objects here, be sure to // release these objects in ShutDownProcess. See the comment there for more
--- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -832,16 +832,24 @@ child: /** * Tell the child to print the current page with the given settings. * * @param aOuterWindowID the ID of the outer window to print * @param aPrintData the serialized settings to print with */ async Print(uint64_t aOuterWindowID, PrintData aPrintData); + /** + * Update the child with the tab's current top-level native window handle. + * This is used by a11y objects who must expose their native window. + * + * @param aNewHandle The native window handle of the tab's top-level window. + */ + async UpdateNativeWindowHandle(uintptr_t aNewHandle); + /* * FIXME: write protocol! state LIVE: send LoadURL goto LIVE; //etc. send Destroy goto DYING;
--- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -95,16 +95,18 @@ using mozilla::dom::ContentParentId from using mozilla::LayoutDeviceIntPoint from "Units.h"; using struct LookAndFeelInt from "mozilla/widget/WidgetMessageUtils.h"; using class mozilla::dom::MessagePort from "mozilla/dom/MessagePort.h"; using class mozilla::dom::ipc::StructuredCloneData from "mozilla/dom/ipc/StructuredCloneData.h"; using mozilla::DataStorageType from "ipc/DataStorageIPCUtils.h"; using mozilla::DocShellOriginAttributes from "mozilla/ipc/BackgroundUtils.h"; using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h"; using struct mozilla::dom::FlyWebPublishOptions from "mozilla/dom/FlyWebPublishOptionsIPCSerializer.h"; +using mozilla::Telemetry::Accumulation from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::KeyedAccumulation from "mozilla/TelemetryComms.h"; union ChromeRegistryItem { ChromePackage; OverrideMapping; SubstitutionMapping; }; @@ -1211,16 +1213,22 @@ parent: async GetFilesRequest(nsID aID, nsString aDirectory, bool aRecursiveFlag); async DeleteGetFilesRequest(nsID aID); async StoreAndBroadcastBlobURLRegistration(nsCString url, PBlob blob, Principal principal); async UnstoreAndBroadcastBlobURLUnregistration(nsCString url); + /** + * Messages for communicating child Telemetry to the parent process + */ + async AccumulateChildHistogram(Accumulation[] accumulations); + async AccumulateChildKeyedHistogram(KeyedAccumulation[] accumulations); + both: async AsyncMessage(nsString aMessage, CpowEntry[] aCpows, Principal aPrincipal, ClonedMessageData aData); /** * Notify `push-subscription-modified` observers in the parent and child. */ async NotifyPushSubscriptionModifiedObservers(nsCString scope,
--- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -544,16 +544,19 @@ TabChild::TabChild(nsIContentChild* aMan , mDefaultScale(0) , mIsTransparent(false) , mIPCOpen(true) , mParentIsActive(false) , mDidSetRealShowInfo(false) , mDidLoadURLInit(false) , mAPZChild(nullptr) , mLayerObserverEpoch(0) +#if defined(XP_WIN) && defined(ACCESSIBILITY) + , mNativeWindowHandle(0) +#endif { // In the general case having the TabParent tell us if APZ is enabled or not // doesn't really work because the TabParent itself may not have a reference // to the owning widget during initialization. Instead we assume that this // TabChild corresponds to a widget type that would have APZ enabled, and just // check the other conditions necessary for enabling APZ. mAsyncPanZoomEnabled = gfxPlatform::AsyncPanZoomEnabled(); @@ -2530,16 +2533,27 @@ TabChild::RecvPrint(const uint64_t& aOut return true; } #endif return true; } bool +TabChild::RecvUpdateNativeWindowHandle(const uintptr_t& aNewHandle) +{ +#if defined(XP_WIN) && defined(ACCESSIBILITY) + mNativeWindowHandle = aNewHandle; + return true; +#else + return false; +#endif +} + +bool TabChild::RecvDestroy() { MOZ_ASSERT(mDestroyed == false); mDestroyed = true; nsTArray<PContentPermissionRequestChild*> childArray = nsContentPermissionUtils::GetContentPermissionRequestChildById(GetTabId());
--- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -585,16 +585,18 @@ public: virtual bool RecvHandledWindowedPluginKeyEvent( const mozilla::NativeEventData& aKeyEventData, const bool& aIsConsumed) override; virtual bool RecvPrint(const uint64_t& aOuterWindowID, const PrintData& aPrintData) override; + virtual bool RecvUpdateNativeWindowHandle(const uintptr_t& aNewHandle) override; + /** * Native widget remoting protocol for use with windowed plugins with e10s. */ PPluginWidgetChild* AllocPPluginWidgetChild() override; bool DeallocPPluginWidgetChild(PPluginWidgetChild* aActor) override; nsresult CreatePluginWidget(nsIWidget* aParent, nsIWidget** aOut); @@ -646,16 +648,20 @@ public: void SetAPZChild(layers::APZChild* aAPZChild) { mAPZChild = aAPZChild; } // Request that the docshell be marked as active. void ForcePaint(uint64_t aLayerObserverEpoch); +#if defined(XP_WIN) && defined(ACCESSIBILITY) + uintptr_t GetNativeWindowHandle() const { return mNativeWindowHandle; } +#endif + protected: virtual ~TabChild(); virtual PRenderFrameChild* AllocPRenderFrameChild() override; virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) override; virtual bool RecvDestroy() override; @@ -784,15 +790,20 @@ private: RefPtr<layers::IAPZCTreeManager> mApzcTreeManager; // APZChild clears this pointer from its destructor, so it shouldn't be a // dangling pointer. layers::APZChild* mAPZChild; // The most recently seen layer observer epoch in RecvSetDocShellIsActive. uint64_t mLayerObserverEpoch; +#if defined(XP_WIN) && defined(ACCESSIBILITY) + // The handle associated with the native window that contains this tab + uintptr_t mNativeWindowHandle; +#endif // defined(XP_WIN) + DISALLOW_EVIL_CONSTRUCTORS(TabChild); }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_TabChild_h
--- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -395,16 +395,27 @@ TabParent::SetOwnerElement(Element* aEle } if (mFrameElement) { bool useGlobalHistory = !mFrameElement->HasAttr(kNameSpaceID_None, nsGkAtoms::disableglobalhistory); Unused << SendSetUseGlobalHistory(useGlobalHistory); } +#if defined(XP_WIN) && defined(ACCESSIBILITY) + if (!mIsDestroyed) { + uintptr_t newWindowHandle = 0; + if (nsCOMPtr<nsIWidget> widget = GetWidget()) { + newWindowHandle = + reinterpret_cast<uintptr_t>(widget->GetNativeData(NS_NATIVE_WINDOW)); + } + Unused << SendUpdateNativeWindowHandle(newWindowHandle); + } +#endif + AddWindowListeners(); TryCacheDPIAndScale(); } void TabParent::AddWindowListeners() { if (mFrameElement && mFrameElement->OwnerDoc()) {
--- a/gfx/tests/reftest/reftest.list +++ b/gfx/tests/reftest/reftest.list @@ -1,9 +1,9 @@ # 468496-1 will also detect bugs in video drivers. == 468496-1.html 468496-1-ref.html -fuzzy-if(winWidget,175,443) == 611498-1.html 611498-ref.html +fuzzy(175,443) == 611498-1.html 611498-ref.html skip-if(B2G) fuzzy-if(Android,8,1000) == 709477-1.html 709477-1-ref.html # bug 773482 skip-if(!asyncPan) == 1086723.html 1086723-ref.html == 853889-1.html 853889-1-ref.html skip-if(Android) fuzzy-if(skiaContent,1,587) == 1143303-1.svg pass.svg fuzzy(100,30) == 1149923.html 1149923-ref.html # use fuzzy due to few distorted pixels caused by border-radius == 1131264-1.svg pass.svg
--- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -533,16 +533,20 @@ private: // These affect how line scrolls from wheel events will be accelerated. DECL_GFX_PREF(Live, "mousewheel.acceleration.factor", MouseWheelAccelerationFactor, int32_t, -1); DECL_GFX_PREF(Live, "mousewheel.acceleration.start", MouseWheelAccelerationStart, int32_t, -1); // This affects whether events will be routed through APZ or not. DECL_GFX_PREF(Live, "mousewheel.system_scroll_override_on_root_content.enabled", MouseWheelHasRootScrollDeltaOverride, bool, false); + DECL_GFX_PREF(Live, "mousewheel.system_scroll_override_on_root_content.horizontal.factor", + MouseWheelRootScrollHorizontalFactor, int32_t, 0); + DECL_GFX_PREF(Live, "mousewheel.system_scroll_override_on_root_content.vertical.factor", + MouseWheelRootScrollVerticalFactor, int32_t, 0); DECL_GFX_PREF(Live, "mousewheel.transaction.ignoremovedelay",MouseWheelIgnoreMoveDelayMs, int32_t, (int32_t)100); DECL_GFX_PREF(Live, "mousewheel.transaction.timeout", MouseWheelTransactionTimeoutMs, int32_t, (int32_t)1500); DECL_GFX_PREF(Live, "nglayout.debug.widget_update_flashing", WidgetUpdateFlashing, bool, false); DECL_GFX_PREF(Live, "test.events.async.enabled", TestEventsAsyncEnabled, bool, false); DECL_GFX_PREF(Live, "test.mousescroll", MouseScrollTestingEnabled, bool, false);
--- a/ipc/glue/MessageChannel.cpp +++ b/ipc/glue/MessageChannel.cpp @@ -483,16 +483,17 @@ MessageChannel::MessageChannel(MessageLi mDispatchingAsyncMessagePriority(0), mTransactionStack(nullptr), mTimedOutMessageSeqno(0), mTimedOutMessagePriority(0), mRemoteStackDepthGuess(false), mSawInterruptOutMsg(false), mIsWaitingForIncoming(false), mAbortOnError(false), + mNotifiedChannelDone(false), mFlags(REQUIRE_DEFAULT), mPeerPidSet(false), mPeerPid(-1) #if defined(MOZ_CRASHREPORTER) && defined(OS_WIN) , mPending(AnnotateAllocator<Message>(*this)) #endif { MOZ_COUNT_CTOR(ipc::MessageChannel); @@ -2076,16 +2077,23 @@ MessageChannel::NotifyMaybeChannelError( return; } Clear(); // Oops, error! Let the listener know about it. mChannelState = ChannelError; + // IPDL assumes these notifications do not fire twice, so we do not let + // that happen. + if (mNotifiedChannelDone) { + return; + } + mNotifiedChannelDone = true; + // After this, the channel may be deleted. Based on the premise that // mListener owns this channel, any calls back to this class that may // work with mListener should still work on living objects. mListener->OnChannelError(); } void MessageChannel::OnNotifyMaybeChannelError() @@ -2233,16 +2241,23 @@ MessageChannel::NotifyChannelClosed() { mMonitor->AssertNotCurrentThreadOwns(); if (ChannelClosed != mChannelState) NS_RUNTIMEABORT("channel should have been closed!"); Clear(); + // IPDL assumes these notifications do not fire twice, so we do not let + // that happen. + if (mNotifiedChannelDone) { + return; + } + mNotifiedChannelDone = true; + // OK, the IO thread just closed the channel normally. Let the // listener know about it. After this point the channel may be // deleted. mListener->OnChannelClose(); } void MessageChannel::DebugAbort(const char* file, int line, const char* cond,
--- a/ipc/glue/MessageChannel.h +++ b/ipc/glue/MessageChannel.h @@ -778,16 +778,20 @@ class MessageChannel : HasResultCodes #ifdef OS_WIN HANDLE mEvent; #endif // Should the channel abort the process from the I/O thread when // a channel error occurs? bool mAbortOnError; + // True if the listener has already been notified of a channel close or + // error. + bool mNotifiedChannelDone; + // See SetChannelFlags ChannelFlags mFlags; // Task and state used to asynchronously notify channel has been connected // safely. This is necessary to be able to cancel notification if we are // closed at the same time. RefPtr<RefCountedTask> mOnChannelConnectedTask; bool mPeerPidSet;
--- a/ipc/glue/ProtocolUtils.h +++ b/ipc/glue/ProtocolUtils.h @@ -504,16 +504,19 @@ public: mMode == Transport::MODE_SERVER ? ParentSide : ChildSide)) { return false; } mValid = false; aActor->SetTransport(Move(t)); return true; } + bool IsValid() const { + return mValid; + } private: friend struct IPC::ParamTraits<Endpoint<PFooSide>>; Endpoint(const Endpoint&) = delete; Endpoint& operator=(const Endpoint&) = delete; bool mValid; @@ -644,17 +647,20 @@ struct ParamTraits<mozilla::ipc::ActorHa template<class PFooSide> struct ParamTraits<mozilla::ipc::Endpoint<PFooSide>> { typedef mozilla::ipc::Endpoint<PFooSide> paramType; static void Write(Message* aMsg, const paramType& aParam) { - MOZ_RELEASE_ASSERT(aParam.mValid); + IPC::WriteParam(aMsg, aParam.mValid); + if (!aParam.mValid) { + return; + } IPC::WriteParam(aMsg, static_cast<uint32_t>(aParam.mMode)); // We duplicate the descriptor so that our own file descriptor remains // valid after the write. An alternative would be to set // aParam.mTransport.mValid to false, but that won't work because aParam // is const. mozilla::ipc::TransportDescriptor desc = mozilla::ipc::DuplicateDescriptor(aParam.mTransport); @@ -663,17 +669,25 @@ struct ParamTraits<mozilla::ipc::Endpoin IPC::WriteParam(aMsg, aParam.mMyPid); IPC::WriteParam(aMsg, aParam.mOtherPid); IPC::WriteParam(aMsg, static_cast<uint32_t>(aParam.mProtocolId)); } static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) { MOZ_RELEASE_ASSERT(!aResult->mValid); - aResult->mValid = true; + + if (!IPC::ReadParam(aMsg, aIter, &aResult->mValid)) { + return false; + } + if (!aResult->mValid) { + // Object is empty, but read succeeded. + return true; + } + uint32_t mode, protocolId; if (!IPC::ReadParam(aMsg, aIter, &mode) || !IPC::ReadParam(aMsg, aIter, &aResult->mTransport) || !IPC::ReadParam(aMsg, aIter, &aResult->mMyPid) || !IPC::ReadParam(aMsg, aIter, &aResult->mOtherPid) || !IPC::ReadParam(aMsg, aIter, &protocolId)) { return false; }
--- a/ipc/ipdl/ipdl/lower.py +++ b/ipc/ipdl/ipdl/lower.py @@ -2379,17 +2379,20 @@ def _generateCxxUnion(ud): StmtExpr(callAssertSanity(uvar=othervar)), copyswitch, StmtExpr(ExprAssn(mtypevar, othertype)) ]) cls.addstmts([ copyctor, Whitespace.NL ]) # ~Union() dtor = DestructorDefn(DestructorDecl(ud.name)) - dtor.addstmt(StmtExpr(callMaybeDestroy(tnonevar))) + # The void cast prevents Coverity from complaining about missing return + # value checks. + dtor.addstmt(StmtExpr(ExprCast(callMaybeDestroy(tnonevar), Type.VOID, + static=1))) cls.addstmts([ dtor, Whitespace.NL ]) # type() typemeth = MethodDefn(MethodDecl('type', ret=typetype, const=1, force_inline=1)) typemeth.addstmt(StmtReturn(mtypevar)) cls.addstmts([ typemeth, Whitespace.NL ]) @@ -2420,19 +2423,24 @@ def _generateCxxUnion(ud): case = StmtBlock() case.addstmts([ maybeReconstruct(c, rhstypevar), StmtExpr(c.callOperatorEq( ExprCall(ExprSelect(rhsvar, '.', c.getConstTypeName())))), StmtBreak() ]) opeqswitch.addcase(CaseLabel(c.enum()), case) - opeqswitch.addcase(CaseLabel(tnonevar.name), - StmtBlock([ StmtExpr(callMaybeDestroy(rhstypevar)), - StmtBreak() ])) + opeqswitch.addcase( + CaseLabel(tnonevar.name), + # The void cast prevents Coverity from complaining about missing return + # value checks. + StmtBlock([ StmtExpr(ExprCast(callMaybeDestroy(rhstypevar), Type.VOID, + static=1)), + StmtBreak() ]) + ) opeqswitch.addcase( DefaultLabel(), StmtBlock([ _logicError('unreached'), StmtBreak() ])) opeq.addstmts([ StmtExpr(callAssertSanity(uvar=rhsvar)), StmtDecl(Decl(typetype, rhstypevar.name), init=ud.callType(rhsvar)), opeqswitch, StmtExpr(ExprAssn(mtypevar, rhstypevar)),
--- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -6069,38 +6069,44 @@ CodeGenerator::visitCreateArgumentsObjec Register callObj = ToRegister(lir->getCallObject()); Register temp = ToRegister(lir->temp0()); Label done; if (ArgumentsObject* templateObj = lir->mir()->templateObject()) { Register objTemp = ToRegister(lir->temp1()); Register cxTemp = ToRegister(lir->temp2()); + masm.Push(callObj); + // Try to allocate an arguments object. This will leave the reserved // slots uninitialized, so it's important we don't GC until we // initialize these slots in ArgumentsObject::finishForIon. Label failure; masm.createGCObject(objTemp, temp, templateObj, gc::DefaultHeap, &failure, /* initContents = */ false); masm.moveStackPtrTo(temp); - masm.addPtr(Imm32(frameSize()), temp); + masm.addPtr(Imm32(masm.framePushed()), temp); masm.setupUnalignedABICall(cxTemp); masm.loadJSContext(cxTemp); masm.passABIArg(cxTemp); masm.passABIArg(temp); masm.passABIArg(callObj); masm.passABIArg(objTemp); masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ArgumentsObject::finishForIon)); - masm.branchTestPtr(Assembler::Zero, ReturnReg, ReturnReg, masm.exceptionLabel()); + masm.branchTestPtr(Assembler::Zero, ReturnReg, ReturnReg, &failure); + + // Discard saved callObj on the stack. + masm.addToStackPtr(Imm32(sizeof(uintptr_t))); masm.jump(&done); masm.bind(&failure); + masm.Pop(callObj); } masm.moveStackPtrTo(temp); masm.addPtr(Imm32(frameSize()), temp); pushArg(callObj); pushArg(temp); callVM(NewIonArgumentsObjectInfo, lir);
--- a/js/src/vm/ArgumentsObject.cpp +++ b/js/src/vm/ArgumentsObject.cpp @@ -378,17 +378,19 @@ ArgumentsObject::finishForIon(JSContext* unsigned numActuals = frame->numActualArgs(); unsigned numFormals = callee->nargs(); unsigned numArgs = Max(numActuals, numFormals); unsigned numBytes = ArgumentsData::bytesRequired(numArgs); ArgumentsData* data = reinterpret_cast<ArgumentsData*>(AllocateObjectBuffer<uint8_t>(cx, obj, numBytes)); if (!data) { - // Make the object safe for GC. + // Make the object safe for GC. Don't report OOM, the slow path will + // retry the allocation. + cx->recoverFromOutOfMemory(); obj->initFixedSlot(DATA_SLOT, PrivateValue(nullptr)); return nullptr; } data->numArgs = numArgs; data->rareData = nullptr; obj->initFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(numActuals << PACKED_BITS_COUNT));
--- a/taskcluster/ci/spidermonkey/kind.yml +++ b/taskcluster/ci/spidermonkey/kind.yml @@ -44,27 +44,30 @@ jobs: using: spidermonkey-package spidermonkey-variant: plain run-on-projects: - integration - release when: files-changed: - build/** + - config/** - configure.py - dom/bindings/** - intl/icu/** - js/moz.configure - layout/tools/reftest/reftest/** + - Makefile.in - media/webrtc/trunk/tools/gyp/** - memory/** - mfbt/** - modules/fdlibm/** - modules/zlib/src/** - mozglue/** + - moz.build - moz.configure - nsprpub/** - python/** - taskcluster/moz.build - testing/mozbase/** - test.mozbuild - toolkit/mozapps/installer/package-name.mk - toolkit/mozapps/installer/upload-files.mk
--- a/testing/web-platform/meta/html/semantics/embedded-content/the-iframe-element/iframe-allowfullscreen.html.ini +++ b/testing/web-platform/meta/html/semantics/embedded-content/the-iframe-element/iframe-allowfullscreen.html.ini @@ -1,3 +1,4 @@ [iframe-allow-fullscreen.html.ini] type: testharness + disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1282024 prefs: [full-screen-api.unprefix.enabled:true]
--- a/testing/xpcshell/selftest.py +++ b/testing/xpcshell/selftest.py @@ -9,30 +9,30 @@ import mozunit import os import pprint import re import shutil import sys import tempfile import unittest +from buildconfig import substs from StringIO import StringIO from mozlog import structured from mozbuild.base import MozbuildObject os.environ.pop('MOZ_OBJDIR', None) build_obj = MozbuildObject.from_environment() from runxpcshelltests import XPCShellTests mozinfo.find_and_update_from_json() objdir = build_obj.topobjdir.encode("utf-8") if mozinfo.isMac: - from buildconfig import substs xpcshellBin = os.path.join(objdir, "dist", substs['MOZ_MACBUNDLE_NAME'], "Contents", "MacOS", "xpcshell") else: xpcshellBin = os.path.join(objdir, "dist", "bin", "xpcshell") if sys.platform == "win32": xpcshellBin += ".exe" TEST_PASS_STRING = "TEST-PASS" TEST_FAIL_STRING = "TEST-UNEXPECTED-FAIL" @@ -853,17 +853,19 @@ add_test({ """ Ensure a simple test with an uncaught rejection is reported. """ self.writeFile("test_simple_uncaught_rejection.js", SIMPLE_UNCAUGHT_REJECTION_TEST) self.writeManifest(["test_simple_uncaught_rejection.js"]) self.assertTestResult(False) self.assertInLog(TEST_FAIL_STRING) - self.assertInLog("test_simple_uncaught_rejection.js:3:3") + if not substs.get('RELEASE_BUILD'): + # async stacks are currently not enabled in release builds. + self.assertInLog("test_simple_uncaught_rejection.js:3:3") self.assertInLog("Test rejection.") self.assertEquals(1, self.x.testCount) self.assertEquals(0, self.x.passCount) self.assertEquals(1, self.x.failCount) def testUncaughtRejectionJSM(self): """ Ensure a simple test with an uncaught rejection from Promise.jsm is reported.
--- a/toolkit/components/telemetry/Telemetry.cpp +++ b/toolkit/components/telemetry/Telemetry.cpp @@ -2343,16 +2343,23 @@ TelemetryImpl::SnapshotScalars(unsigned NS_IMETHODIMP TelemetryImpl::ClearScalars() { TelemetryScalar::ClearScalars(); return NS_OK; } +NS_IMETHODIMP +TelemetryImpl::FlushBatchedChildTelemetry() +{ + TelemetryHistogram::IPCTimerFired(nullptr, nullptr); + return NS_OK; +} + size_t TelemetryImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { size_t n = aMallocSizeOf(this); // Ignore the hashtables in mAddonMap; they are not significant. n += TelemetryHistogram::GetMapShallowSizesOfExcludingThis(aMallocSizeOf); n += TelemetryScalar::GetMapShallowSizesOfExcludingThis(aMallocSizeOf); @@ -2789,16 +2796,28 @@ AccumulateCategorical(ID id, const nsCSt void AccumulateTimeDelta(ID aHistogram, TimeStamp start, TimeStamp end) { Accumulate(aHistogram, static_cast<uint32_t>((end - start).ToMilliseconds())); } void +AccumulateChild(const nsTArray<Accumulation>& aAccumulations) +{ + TelemetryHistogram::AccumulateChild(aAccumulations); +} + +void +AccumulateChildKeyed(const nsTArray<KeyedAccumulation>& aAccumulations) +{ + TelemetryHistogram::AccumulateChildKeyed(aAccumulations); +} + +void ClearHistogram(ID aId) { TelemetryHistogram::ClearHistogram(aId); } const char* GetHistogramName(ID id) {
--- a/toolkit/components/telemetry/Telemetry.h +++ b/toolkit/components/telemetry/Telemetry.h @@ -28,16 +28,19 @@ *****************************************************************************/ namespace mozilla { namespace HangMonitor { class HangAnnotations; } // namespace HangMonitor namespace Telemetry { +struct Accumulation; +struct KeyedAccumulation; + enum TimerResolution { Millisecond, Microsecond }; /** * Create and destroy the underlying base::StatisticsRecorder singleton. * Creation has to be done very early in the startup sequence. @@ -121,16 +124,30 @@ void AccumulateCategorical(ID id, const * * @param id - histogram id * @param start - start time * @param end - end time */ void AccumulateTimeDelta(ID id, TimeStamp start, TimeStamp end = TimeStamp::Now()); /** + * Accumulate child data into child histograms + * + * @param aAccumulations - accumulation actions to perform + */ +void AccumulateChild(const nsTArray<Accumulation>& aAccumulations); + +/** + * Accumulate child data into child keyed histograms + * + * @param aAccumulations - accumulation actions to perform + */ +void AccumulateChildKeyed(const nsTArray<KeyedAccumulation>& aAccumulations); + +/** * This clears the data for a histogram in TelemetryHistogramEnums.h. * * @param id - histogram id */ void ClearHistogram(ID id); /** * Enable/disable recording for this histogram at runtime.
new file mode 100644 --- /dev/null +++ b/toolkit/components/telemetry/TelemetryComms.h @@ -0,0 +1,84 @@ +/* -*- 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 + */ + +#ifndef Telemetry_Comms_h__ +#define Telemetry_Comms_h__ + +#include "ipc/IPCMessageUtils.h" + +namespace mozilla { +namespace Telemetry { + +enum ID : uint32_t; + +struct Accumulation +{ + mozilla::Telemetry::ID mId; + uint32_t mSample; +}; + +struct KeyedAccumulation +{ + mozilla::Telemetry::ID mId; + uint32_t mSample; + nsCString mKey; +}; + +} // namespace Telemetry +} // namespace mozilla + +namespace IPC { + +template<> +struct +ParamTraits<mozilla::Telemetry::Accumulation> +{ + typedef mozilla::Telemetry::Accumulation paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + aMsg->WriteUInt32(aParam.mId); + WriteParam(aMsg, aParam.mSample); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + if (!aMsg->ReadUInt32(aIter, reinterpret_cast<uint32_t*>(&(aResult->mId))) || + !ReadParam(aMsg, aIter, &(aResult->mSample))) { + return false; + } + + return true; + } +}; + +template<> +struct +ParamTraits<mozilla::Telemetry::KeyedAccumulation> +{ + typedef mozilla::Telemetry::KeyedAccumulation paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + aMsg->WriteUInt32(aParam.mId); + WriteParam(aMsg, aParam.mSample); + WriteParam(aMsg, aParam.mKey); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + if (!aMsg->ReadUInt32(aIter, reinterpret_cast<uint32_t*>(&(aResult->mId))) || + !ReadParam(aMsg, aIter, &(aResult->mSample)) || + !ReadParam(aMsg, aIter, &(aResult->mKey))) { + return false; + } + + return true; + } +}; + +} // namespace IPC + +#endif // Telemetry_Comms_h__
--- a/toolkit/components/telemetry/TelemetryHistogram.cpp +++ b/toolkit/components/telemetry/TelemetryHistogram.cpp @@ -9,33 +9,40 @@ #include "js/GCAPI.h" #include "nsString.h" #include "nsTHashtable.h" #include "nsHashKeys.h" #include "nsBaseHashtable.h" #include "nsClassHashtable.h" #include "nsITelemetry.h" +#include "mozilla/dom/ContentChild.h" #include "mozilla/dom/ToJSValue.h" +#include "mozilla/Atomics.h" #include "mozilla/StartupTimeline.h" #include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Unused.h" #include "TelemetryCommon.h" #include "TelemetryHistogram.h" #include "base/histogram.h" using base::Histogram; using base::StatisticsRecorder; using base::BooleanHistogram; using base::CountHistogram; using base::FlagHistogram; using base::LinearHistogram; using mozilla::StaticMutex; using mozilla::StaticMutexAutoLock; +using mozilla::StaticAutoPtr; +using mozilla::Telemetry::Accumulation; +using mozilla::Telemetry::KeyedAccumulation; //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // Naming: there are two kinds of functions in this file: // // * Functions named internal_*: these can only be reached via an @@ -87,16 +94,17 @@ using mozilla::StaticMutexAutoLock; //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // PRIVATE TYPES #define EXPIRED_ID "__expired__" #define SUBSESSION_HISTOGRAM_PREFIX "sub#" #define KEYED_HISTOGRAM_NAME_SEPARATOR "#" +#define CHILD_HISTOGRAM_SUFFIX "#content" namespace { using mozilla::Telemetry::Common::AutoHashtable; using mozilla::Telemetry::Common::IsExpiredVersion; using mozilla::Telemetry::Common::CanRecordDataset; using mozilla::Telemetry::Common::IsInDataset; @@ -180,16 +188,23 @@ bool gCorruptHistograms[mozilla::Telemet // This is for gHistograms, gHistogramStringTable #include "TelemetryHistogramData.inc" AddonMapType gAddonMap; // The singleton StatisticsRecorder object for this process. base::StatisticsRecorder* gStatisticsRecorder = nullptr; +// For batching and sending child process accumulations to the parent +nsITimer* gIPCTimer = nullptr; +mozilla::Atomic<bool, mozilla::Relaxed> gIPCTimerArmed(false); +mozilla::Atomic<bool, mozilla::Relaxed> gIPCTimerArming(false); +StaticAutoPtr<nsTArray<Accumulation>> gAccumulations; +StaticAutoPtr<nsTArray<KeyedAccumulation>> gKeyedAccumulations; + } // namespace //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // PRIVATE CONSTANTS @@ -199,16 +214,27 @@ namespace { const mozilla::Telemetry::ID kRecordingInitiallyDisabledIDs[] = { mozilla::Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, // The array must not be empty. Leave these item here. mozilla::Telemetry::TELEMETRY_TEST_COUNT_INIT_NO_RECORD, mozilla::Telemetry::TELEMETRY_TEST_KEYED_COUNT_INIT_NO_RECORD }; +// Sending each remote accumulation immediately places undue strain on the +// IPC subsystem. Batch the remote accumulations for a period of time before +// sending them all at once. This value was chosen as a balance between data +// timeliness and performance (see bug 1218576) +const uint32_t kBatchTimeoutMs = 2000; + +// To stop growing unbounded in memory while waiting for kBatchTimeoutMs to +// drain the g*Accumulations arrays, request an immediate flush if the arrays +// manage to reach this high water mark of elements. +const size_t kAccumulationsArrayHighWaterMark = 5 * 1024; + } // namespace //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // PRIVATE: Misc small helpers @@ -306,16 +332,26 @@ HistogramInfo::label_id(const char* labe *labelId = i; return NS_OK; } } return NS_ERROR_FAILURE; } +bool +StringEndsWith(const std::string& name, const std::string& suffix) +{ + if (name.size() < suffix.size()) { + return false; + } + + return name.compare(name.size() - suffix.size(), suffix.size(), suffix) == 0; +} + } // namespace //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // PRIVATE: Histogram Get, Add, Clone, Clear functions @@ -390,49 +426,70 @@ internal_HistogramGet(const char *name, break; default: NS_ASSERTION(false, "Invalid histogram type"); return NS_ERROR_INVALID_ARG; } return NS_OK; } +CharPtrEntryType* +internal_GetHistogramMapEntry(const char* name) +{ + nsDependentCString histogramName(name); + NS_NAMED_LITERAL_CSTRING(suffix, CHILD_HISTOGRAM_SUFFIX); + if (!StringEndsWith(histogramName, suffix)) { + return gHistogramMap.GetEntry(name); + } + auto root = Substring(histogramName, 0, histogramName.Length() - suffix.Length()); + return gHistogramMap.GetEntry(PromiseFlatCString(root).get()); +} + nsresult internal_GetHistogramEnumId(const char *name, mozilla::Telemetry::ID *id) { if (!gInitDone) { return NS_ERROR_FAILURE; } - CharPtrEntryType *entry = gHistogramMap.GetEntry(name); + CharPtrEntryType *entry = internal_GetHistogramMapEntry(name); if (!entry) { return NS_ERROR_INVALID_ARG; } *id = entry->mData; return NS_OK; } // O(1) histogram lookup by numeric id nsresult -internal_GetHistogramByEnumId(mozilla::Telemetry::ID id, Histogram **ret) +internal_GetHistogramByEnumId(mozilla::Telemetry::ID id, Histogram **ret, + bool child = false) { static Histogram* knownHistograms[mozilla::Telemetry::HistogramCount] = {0}; - Histogram *h = knownHistograms[id]; + static Histogram* knownChildHistograms[mozilla::Telemetry::HistogramCount] = {0}; + Histogram *h = child ? knownChildHistograms[id] : knownHistograms[id]; if (h) { *ret = h; return NS_OK; } const HistogramInfo &p = gHistograms[id]; if (p.keyed) { return NS_ERROR_FAILURE; } - nsresult rv = internal_HistogramGet(p.id(), p.expiration(), p.histogramType, - p.min, p.max, p.bucketCount, true, &h); + nsCString histogramName; + histogramName.Append(p.id()); + if (child) { + histogramName.AppendLiteral(CHILD_HISTOGRAM_SUFFIX); + } + + nsresult rv = internal_HistogramGet(histogramName.get(), p.expiration(), + p.histogramType, p.min, p.max, + p.bucketCount, true, &h); if (NS_FAILED(rv)) return rv; #ifdef DEBUG // Check that the C++ Histogram code computes the same ranges as the // Python histogram code. if (!IsExpiredVersion(p.expiration())) { const struct bounds &b = gBucketLowerBoundIndex[id]; @@ -442,31 +499,37 @@ internal_GetHistogramByEnumId(mozilla::T for (int i = 0; i < b.length; ++i) { MOZ_ASSERT(gBucketLowerBounds[b.offset + i] == h->ranges(i), "C++/Python bucket mismatch"); } } } #endif - *ret = knownHistograms[id] = h; + if (child) { + *ret = knownChildHistograms[id] = h; + } else { + *ret = knownHistograms[id] = h; + } return NS_OK; } nsresult internal_GetHistogramByName(const nsACString &name, Histogram **ret) { mozilla::Telemetry::ID id; nsresult rv = internal_GetHistogramEnumId(PromiseFlatCString(name).get(), &id); if (NS_FAILED(rv)) { return rv; } - rv = internal_GetHistogramByEnumId(id, ret); + bool isChild = StringEndsWith(name, + NS_LITERAL_CSTRING(CHILD_HISTOGRAM_SUFFIX)); + rv = internal_GetHistogramByEnumId(id, ret, isChild); if (NS_FAILED(rv)) return rv; return NS_OK; } /** * This clones a histogram |existing| with the id |existingId| to a @@ -512,42 +575,53 @@ internal_CloneHistogram(const nsACString if (NS_FAILED(rv)) { return nullptr; } return internal_CloneHistogram(newName, existingId, *existing); } #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) + Histogram* internal_GetSubsessionHistogram(Histogram& existing) { mozilla::Telemetry::ID id; nsresult rv = internal_GetHistogramEnumId(existing.histogram_name().c_str(), &id); if (NS_FAILED(rv) || gHistograms[id].keyed) { return nullptr; } + bool isChild = StringEndsWith(existing.histogram_name(), + CHILD_HISTOGRAM_SUFFIX); + static Histogram* subsession[mozilla::Telemetry::HistogramCount] = {}; - if (subsession[id]) { - return subsession[id]; + static Histogram* subsessionChild[mozilla::Telemetry::HistogramCount] = {}; + Histogram* cached = isChild ? subsessionChild[id] : subsession[id]; + if (cached) { + return cached; } 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); + subsessionName.Append(existing.histogram_name().c_str()); - subsession[id] = internal_CloneHistogram(subsessionName, id, existing); - return subsession[id]; + Histogram* clone = internal_CloneHistogram(subsessionName, id, existing); + if (isChild) { + subsessionChild[id] = clone; + } else { + subsession[id] = clone; + } + return clone; } #endif nsresult internal_HistogramAdd(Histogram& histogram, int32_t value, uint32_t dataset) { // Check if we are allowed to record the data. bool canRecordDataset = CanRecordDataset(dataset, @@ -586,36 +660,23 @@ internal_HistogramAdd(Histogram& histogr return NS_OK; } dataset = gHistograms[id].dataset; } return internal_HistogramAdd(histogram, value, dataset); } -nsresult -internal_HistogramAddCategorical(mozilla::Telemetry::ID id, const nsCString& label) -{ - uint32_t labelId = 0; - if (NS_FAILED(gHistograms[id].label_id(label.get(), &labelId))) { - return NS_ERROR_ILLEGAL_VALUE; - } - - Histogram* h = nullptr; - nsresult rv = internal_GetHistogramByEnumId(id, &h); - if (NS_FAILED(rv)) { - return rv; - } - - return internal_HistogramAdd(*h, labelId); -} - void internal_HistogramClear(Histogram& aHistogram, bool onlySubsession) { + MOZ_ASSERT(XRE_IsParentProcess()); + if (!XRE_IsParentProcess()) { + return; + } if (!onlySubsession) { aHistogram.Clear(); } #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) if (Histogram* subsession = internal_GetSubsessionHistogram(aHistogram)) { subsession->Clear(); } @@ -801,16 +862,18 @@ public: bool subsession, bool clearSubsession); void SetRecordingEnabled(bool aEnabled) { mRecordingEnabled = aEnabled; }; bool IsRecordingEnabled() const { return mRecordingEnabled; }; nsresult Add(const nsCString& key, uint32_t aSample); void Clear(bool subsession); + nsresult GetEnumId(mozilla::Telemetry::ID& id); + private: typedef nsBaseHashtableET<nsCStringHashKey, Histogram*> KeyedHistogramEntry; typedef AutoHashtable<KeyedHistogramEntry> KeyedHistogramMapType; KeyedHistogramMapType mHistogramMap; #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) KeyedHistogramMapType mSubsessionMap; #endif @@ -943,16 +1006,20 @@ KeyedHistogram::Add(const nsCString& key subsession->Add(sample); #endif return NS_OK; } void KeyedHistogram::Clear(bool onlySubsession) { + MOZ_ASSERT(XRE_IsParentProcess()); + if (!XRE_IsParentProcess()) { + return; + } #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) for (auto iter = mSubsessionMap.Iter(); !iter.Done(); iter.Next()) { iter.Get()->mData->Clear(); } mSubsessionMap.Clear(); if (onlySubsession) { return; } @@ -1030,16 +1097,22 @@ KeyedHistogram::GetJSSnapshot(JSContext* if (subsession && clearSubsession) { Clear(true); } #endif return NS_OK; } +nsresult +KeyedHistogram::GetEnumId(mozilla::Telemetry::ID& id) +{ + return internal_GetHistogramEnumId(mName.get(), &id); +} + } // namespace //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // PRIVATE: KeyedHistogram helpers @@ -1058,16 +1131,329 @@ internal_GetKeyedHistogramById(const nsA } } // namespace //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // +// PRIVATE: functions related to addon histograms + +namespace { + +// Compute the name to pass into Histogram for the addon histogram +// 'name' from the addon 'id'. We can't use 'name' directly because it +// might conflict with other histograms in other addons or even with our +// own. +void +internal_AddonHistogramName(const nsACString &id, const nsACString &name, + nsACString &ret) +{ + ret.Append(id); + ret.Append(':'); + ret.Append(name); +} + +bool +internal_CreateHistogramForAddon(const nsACString &name, + AddonHistogramInfo &info) +{ + Histogram *h; + nsresult rv = internal_HistogramGet(PromiseFlatCString(name).get(), "never", + info.histogramType, info.min, info.max, + info.bucketCount, true, &h); + if (NS_FAILED(rv)) { + return false; + } + // Don't let this histogram be reported via the normal means + // (e.g. Telemetry.registeredHistograms); we'll make it available in + // other ways. + h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); + info.h = h; + return true; +} + +bool +internal_AddonHistogramReflector(AddonHistogramEntryType *entry, + JSContext *cx, JS::Handle<JSObject*> obj) +{ + AddonHistogramInfo &info = entry->mData; + + // Never even accessed the histogram. + if (!info.h) { + // Have to force creation of HISTOGRAM_FLAG histograms. + if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG) + return true; + + if (!internal_CreateHistogramForAddon(entry->GetKey(), info)) { + return false; + } + } + + if (internal_IsEmpty(info.h)) { + return true; + } + + JS::Rooted<JSObject*> snapshot(cx, JS_NewPlainObject(cx)); + if (!snapshot) { + // Just consider this to be skippable. + return true; + } + switch (internal_ReflectHistogramSnapshot(cx, snapshot, info.h)) { + case REFLECT_FAILURE: + case REFLECT_CORRUPT: + return false; + case REFLECT_OK: + const nsACString &histogramName = entry->GetKey(); + if (!JS_DefineProperty(cx, obj, PromiseFlatCString(histogramName).get(), + snapshot, JSPROP_ENUMERATE)) { + return false; + } + break; + } + return true; +} + +bool +internal_AddonReflector(AddonEntryType *entry, JSContext *cx, + JS::Handle<JSObject*> obj) +{ + const nsACString &addonId = entry->GetKey(); + JS::Rooted<JSObject*> subobj(cx, JS_NewPlainObject(cx)); + if (!subobj) { + return false; + } + + AddonHistogramMapType *map = entry->mData; + if (!(map->ReflectIntoJS(internal_AddonHistogramReflector, cx, subobj) + && JS_DefineProperty(cx, obj, PromiseFlatCString(addonId).get(), + subobj, JSPROP_ENUMERATE))) { + return false; + } + return true; +} + +} // namespace + + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// +// PRIVATE: thread-unsafe helpers for the external interface + +// This is a StaticMutex rather than a plain Mutex (1) so that +// it gets initialised in a thread-safe manner the first time +// it is used, and (2) because it is never de-initialised, and +// a normal Mutex would show up as a leak in BloatView. StaticMutex +// also has the "OffTheBooks" property, so it won't show as a leak +// in BloatView. +static StaticMutex gTelemetryHistogramMutex; + +namespace { + +void +internal_SetHistogramRecordingEnabled(mozilla::Telemetry::ID aID, bool aEnabled) +{ + if (!internal_IsHistogramEnumId(aID)) { + MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) must be used with an enum id"); + return; + } + + if (gHistograms[aID].keyed) { + const nsDependentCString id(gHistograms[aID].id()); + KeyedHistogram* keyed = internal_GetKeyedHistogramById(id); + if (keyed) { + keyed->SetRecordingEnabled(aEnabled); + return; + } + } else { + Histogram *h; + nsresult rv = internal_GetHistogramByEnumId(aID, &h); + if (NS_SUCCEEDED(rv)) { + h->SetRecordingEnabled(aEnabled); + return; + } + } + + MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) id not found"); +} + +void internal_armIPCTimerMainThread() +{ + MOZ_ASSERT(NS_IsMainThread()); + gIPCTimerArming = false; + if (gIPCTimerArmed) { + return; + } + if (!gIPCTimer) { + CallCreateInstance(NS_TIMER_CONTRACTID, &gIPCTimer); + } + if (gIPCTimer) { + gIPCTimer->InitWithFuncCallback(TelemetryHistogram::IPCTimerFired, + nullptr, kBatchTimeoutMs, + nsITimer::TYPE_ONE_SHOT); + gIPCTimerArmed = true; + } +} + +void internal_armIPCTimer() +{ + if (gIPCTimerArmed || gIPCTimerArming) { + return; + } + gIPCTimerArming = true; + if (NS_IsMainThread()) { + internal_armIPCTimerMainThread(); + } else { + NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + internal_armIPCTimerMainThread(); + })); + } +} + +bool +internal_RemoteAccumulate(mozilla::Telemetry::ID aId, uint32_t aSample) +{ + if (XRE_IsParentProcess()) { + return false; + } + if (!gAccumulations) { + gAccumulations = new nsTArray<Accumulation>(); + } + if (gAccumulations->Length() == kAccumulationsArrayHighWaterMark) { + NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void { + TelemetryHistogram::IPCTimerFired(nullptr, nullptr); + })); + } + gAccumulations->AppendElement(Accumulation{aId, aSample}); + internal_armIPCTimer(); + return true; +} + +bool +internal_RemoteAccumulate(mozilla::Telemetry::ID aId, + const nsCString& aKey, uint32_t aSample) +{ + if (XRE_IsParentProcess()) { + return false; + } + if (!gKeyedAccumulations) { + gKeyedAccumulations = new nsTArray<KeyedAccumulation>(); + } + if (gKeyedAccumulations->Length() == kAccumulationsArrayHighWaterMark) { + NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void { + TelemetryHistogram::IPCTimerFired(nullptr, nullptr); + })); + } + gKeyedAccumulations->AppendElement(KeyedAccumulation{aId, aSample, aKey}); + internal_armIPCTimer(); + return true; +} + +void internal_Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample) +{ + bool isValid = internal_IsHistogramEnumId(aHistogram); + MOZ_ASSERT(isValid, "Accumulation using invalid id"); + if (!internal_CanRecordBase() || !isValid || + internal_RemoteAccumulate(aHistogram, aSample)) { + return; + } + Histogram *h; + nsresult rv = internal_GetHistogramByEnumId(aHistogram, &h); + if (NS_SUCCEEDED(rv)) { + internal_HistogramAdd(*h, aSample, gHistograms[aHistogram].dataset); + } +} + +void +internal_Accumulate(mozilla::Telemetry::ID aID, + const nsCString& aKey, uint32_t aSample) +{ + bool isValid = internal_IsHistogramEnumId(aID); + MOZ_ASSERT(isValid, "Child keyed telemetry accumulation using invalid id"); + if (!gInitDone || !internal_CanRecordBase() || !isValid || + internal_RemoteAccumulate(aID, aKey, aSample)) { + return; + } + const HistogramInfo& th = gHistograms[aID]; + KeyedHistogram* keyed + = internal_GetKeyedHistogramById(nsDependentCString(th.id())); + MOZ_ASSERT(keyed); + keyed->Add(aKey, aSample); +} + +void +internal_Accumulate(Histogram& aHistogram, uint32_t aSample) +{ + if (XRE_IsParentProcess()) { + internal_HistogramAdd(aHistogram, aSample); + return; + } + + mozilla::Telemetry::ID id; + nsresult rv = internal_GetHistogramEnumId(aHistogram.histogram_name().c_str(), &id); + if (NS_SUCCEEDED(rv)) { + internal_RemoteAccumulate(id, aSample); + } +} + +void +internal_Accumulate(KeyedHistogram& aKeyed, + const nsCString& aKey, uint32_t aSample) +{ + if (XRE_IsParentProcess()) { + aKeyed.Add(aKey, aSample); + return; + } + + mozilla::Telemetry::ID id; + if (NS_SUCCEEDED(aKeyed.GetEnumId(id))) { + internal_RemoteAccumulate(id, aKey, aSample); + } +} + +void +internal_AccumulateChild(mozilla::Telemetry::ID aId, uint32_t aSample) +{ + if (!internal_CanRecordBase()) { + return; + } + Histogram* h; + nsresult rv = internal_GetHistogramByEnumId(aId, &h, true); + if (NS_SUCCEEDED(rv)) { + internal_HistogramAdd(*h, aSample, gHistograms[aId].dataset); + } else { + NS_WARNING("NS_FAILED GetHistogramByEnumId for CHILD"); + } +} + +void +internal_AccumulateChildKeyed(mozilla::Telemetry::ID aId, + const nsCString& aKey, uint32_t aSample) +{ + if (!gInitDone || !internal_CanRecordBase()) { + return; + } + const HistogramInfo& th = gHistograms[aId]; + nsCString id; + id.Append(th.id()); + id.AppendLiteral(CHILD_HISTOGRAM_SUFFIX); + KeyedHistogram* keyed = internal_GetKeyedHistogramById(id); + MOZ_ASSERT(keyed); + keyed->Add(aKey, aSample); +} + +} // namespace + + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// // PRIVATE: JSHistogram_* functions // NOTE: the functions in this section: // // internal_JSHistogram_Add // internal_JSHistogram_Snapshot // internal_JSHistogram_Clear // internal_JSHistogram_Dataset @@ -1097,62 +1483,57 @@ internal_JSHistogram_Add(JSContext *cx, Histogram::ClassType type = h->histogram_type(); JS::CallArgs args = CallArgsFromVp(argc, vp); if (!internal_CanRecordBase()) { return true; } - // If we don't have an argument for the count histogram, assume an increment of 1. - // Otherwise, make sure to run some sanity checks on the argument. + uint32_t value = 0; + mozilla::Telemetry::ID id; if ((type == base::CountHistogram::COUNT_HISTOGRAM) && (args.length() == 0)) { - internal_HistogramAdd(*h, 1); - return true; - } - - // For categorical histograms we allow passing a string argument that specifies the label. - mozilla::Telemetry::ID id; - if (type == base::LinearHistogram::LINEAR_HISTOGRAM && + // If we don't have an argument for the count histogram, assume an increment of 1. + // Otherwise, make sure to run some sanity checks on the argument. + value = 1; + } else if (type == base::LinearHistogram::LINEAR_HISTOGRAM && (args.length() > 0) && args[0].isString() && NS_SUCCEEDED(internal_GetHistogramEnumId(h->histogram_name().c_str(), &id)) && gHistograms[id].histogramType == nsITelemetry::HISTOGRAM_CATEGORICAL) { + // For categorical histograms we allow passing a string argument that specifies the label. nsAutoJSString label; if (!label.init(cx, args[0])) { JS_ReportError(cx, "Invalid string parameter"); return false; } - nsresult rv = internal_HistogramAddCategorical(id, NS_ConvertUTF16toUTF8(label)); + nsresult rv = gHistograms[id].label_id(NS_ConvertUTF16toUTF8(label).get(), &value); if (NS_FAILED(rv)) { JS_ReportError(cx, "Unknown label for categorical histogram"); return false; } - - return true; - } + } else { + // All other accumulations expect one numerical argument. + if (!args.length()) { + JS_ReportError(cx, "Expected one argument"); + return false; + } - // All other accumulations expect one numerical argument. - int32_t value = 0; - if (!args.length()) { - JS_ReportError(cx, "Expected one argument"); - return false; + if (!(args[0].isNumber() || args[0].isBoolean())) { + JS_ReportError(cx, "Not a number"); + return false; + } + + if (!JS::ToUint32(cx, args[0], &value)) { + JS_ReportError(cx, "Failed to convert argument"); + return false; + } } - if (!(args[0].isNumber() || args[0].isBoolean())) { - JS_ReportError(cx, "Not a number"); - return false; - } - - if (!JS::ToInt32(cx, args[0], &value)) { - JS_ReportError(cx, "Failed to convert argument"); - return false; - } - - internal_HistogramAdd(*h, value); + internal_Accumulate(*h, value); return true; } bool internal_JSHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JSObject *obj = JS_THIS_OBJECT(cx, vp); @@ -1390,17 +1771,17 @@ internal_JSKeyedHistogram_Add(JSContext return false; } if (!JS::ToInt32(cx, args[1], &value)) { return false; } } - keyed->Add(NS_ConvertUTF16toUTF8(key), value); + internal_Accumulate(*keyed, NS_ConvertUTF16toUTF8(key), value); return true; } bool internal_JSKeyedHistogram_Keys(JSContext *cx, unsigned argc, JS::Value *vp) { JSObject *obj = JS_THIS_OBJECT(cx, vp); if (!obj) { @@ -1543,191 +1924,18 @@ internal_WrapAndReturnKeyedHistogram(Key } } // namespace //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // -// PRIVATE: functions related to addon histograms - -namespace { - -// Compute the name to pass into Histogram for the addon histogram -// 'name' from the addon 'id'. We can't use 'name' directly because it -// might conflict with other histograms in other addons or even with our -// own. -void -internal_AddonHistogramName(const nsACString &id, const nsACString &name, - nsACString &ret) -{ - ret.Append(id); - ret.Append(':'); - ret.Append(name); -} - -bool -internal_CreateHistogramForAddon(const nsACString &name, - AddonHistogramInfo &info) -{ - Histogram *h; - nsresult rv = internal_HistogramGet(PromiseFlatCString(name).get(), "never", - info.histogramType, info.min, info.max, - info.bucketCount, true, &h); - if (NS_FAILED(rv)) { - return false; - } - // Don't let this histogram be reported via the normal means - // (e.g. Telemetry.registeredHistograms); we'll make it available in - // other ways. - h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); - info.h = h; - return true; -} - -bool -internal_AddonHistogramReflector(AddonHistogramEntryType *entry, - JSContext *cx, JS::Handle<JSObject*> obj) -{ - AddonHistogramInfo &info = entry->mData; - - // Never even accessed the histogram. - if (!info.h) { - // Have to force creation of HISTOGRAM_FLAG histograms. - if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG) - return true; - - if (!internal_CreateHistogramForAddon(entry->GetKey(), info)) { - return false; - } - } - - if (internal_IsEmpty(info.h)) { - return true; - } - - JS::Rooted<JSObject*> snapshot(cx, JS_NewPlainObject(cx)); - if (!snapshot) { - // Just consider this to be skippable. - return true; - } - switch (internal_ReflectHistogramSnapshot(cx, snapshot, info.h)) { - case REFLECT_FAILURE: - case REFLECT_CORRUPT: - return false; - case REFLECT_OK: - const nsACString &histogramName = entry->GetKey(); - if (!JS_DefineProperty(cx, obj, PromiseFlatCString(histogramName).get(), - snapshot, JSPROP_ENUMERATE)) { - return false; - } - break; - } - return true; -} - -bool -internal_AddonReflector(AddonEntryType *entry, JSContext *cx, - JS::Handle<JSObject*> obj) -{ - const nsACString &addonId = entry->GetKey(); - JS::Rooted<JSObject*> subobj(cx, JS_NewPlainObject(cx)); - if (!subobj) { - return false; - } - - AddonHistogramMapType *map = entry->mData; - if (!(map->ReflectIntoJS(internal_AddonHistogramReflector, cx, subobj) - && JS_DefineProperty(cx, obj, PromiseFlatCString(addonId).get(), - subobj, JSPROP_ENUMERATE))) { - return false; - } - return true; -} - -} // namespace - - -//////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////// -// -// PRIVATE: thread-unsafe helpers for the external interface - -namespace { - -void -internal_SetHistogramRecordingEnabled(mozilla::Telemetry::ID aID, bool aEnabled) -{ - if (!internal_IsHistogramEnumId(aID)) { - MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) must be used with an enum id"); - return; - } - - if (gHistograms[aID].keyed) { - const nsDependentCString id(gHistograms[aID].id()); - KeyedHistogram* keyed = internal_GetKeyedHistogramById(id); - if (keyed) { - keyed->SetRecordingEnabled(aEnabled); - return; - } - } else { - Histogram *h; - nsresult rv = internal_GetHistogramByEnumId(aID, &h); - if (NS_SUCCEEDED(rv)) { - h->SetRecordingEnabled(aEnabled); - return; - } - } - - MOZ_ASSERT(false, "Telemetry::SetHistogramRecordingEnabled(...) id not found"); -} - -void internal_Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample) -{ - if (!internal_CanRecordBase()) { - return; - } - Histogram *h; - nsresult rv = internal_GetHistogramByEnumId(aHistogram, &h); - if (NS_SUCCEEDED(rv)) { - internal_HistogramAdd(*h, aSample, gHistograms[aHistogram].dataset); - } -} - -void -internal_Accumulate(mozilla::Telemetry::ID aID, - const nsCString& aKey, uint32_t aSample) -{ - if (!gInitDone || !internal_CanRecordBase()) { - return; - } - const HistogramInfo& th = gHistograms[aID]; - KeyedHistogram* keyed - = internal_GetKeyedHistogramById(nsDependentCString(th.id())); - MOZ_ASSERT(keyed); - keyed->Add(aKey, aSample); -} - -} // namespace - - -//////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////// -// // EXTERNALLY VISIBLE FUNCTIONS in namespace TelemetryHistogram:: -// This is a StaticMutex rather than a plain Mutex (1) so that -// it gets initialised in a thread-safe manner the first time -// it is used, and (2) because it is never de-initialised, and -// a normal Mutex would show up as a leak in BloatView. StaticMutex -// also has the "OffTheBooks" property, so it won't show as a leak -// in BloatView. -static StaticMutex gTelemetryHistogramMutex; - // All of these functions are actually in namespace TelemetryHistogram::, // but the ::TelemetryHistogram prefix is given explicitly. This is // because it is critical to see which calls from these functions are // to another function in this interface. Mis-identifying "inwards // calls" from "calls to another function in this interface" will lead // to deadlocking and/or races. See comments at the top of the file // for further (important!) details. @@ -1781,16 +1989,26 @@ void TelemetryHistogram::InitializeGloba if (!h.keyed) { continue; } const nsDependentCString id(h.id()); const nsDependentCString expiration(h.expiration()); gKeyedHistograms.Put(id, new KeyedHistogram(id, expiration, h.histogramType, h.min, h.max, h.bucketCount, h.dataset)); + if (XRE_IsParentProcess()) { + // We must create registered child keyed histograms as well or else the + // same code in TelemetrySession.jsm that fails without parent keyed + // histograms will fail without child keyed histograms. + nsCString childId(id); + childId.AppendLiteral(CHILD_HISTOGRAM_SUFFIX); + gKeyedHistograms.Put(childId, + new KeyedHistogram(id, expiration, h.histogramType, + h.min, h.max, h.bucketCount, h.dataset)); + } } // Some Telemetry histograms depend on the value of C++ constants and hardcode // their values in Histograms.json. // We add static asserts here for those values to match so that future changes // don't go unnoticed. // TODO: Compare explicitly with gHistograms[<histogram id>].bucketCount here // once we can make gHistograms constexpr (requires VS2015). @@ -1811,16 +2029,21 @@ void TelemetryHistogram::InitializeGloba void TelemetryHistogram::DeInitializeGlobalState() { StaticMutexAutoLock locker(gTelemetryHistogramMutex); gCanRecordBase = false; gCanRecordExtended = false; gHistogramMap.Clear(); gKeyedHistograms.Clear(); gAddonMap.Clear(); + gAccumulations = nullptr; + gKeyedAccumulations = nullptr; + if (gIPCTimer) { + NS_RELEASE(gIPCTimer); + } gInitDone = false; } #ifdef DEBUG bool TelemetryHistogram::GlobalStateHasBeenInitialized() { StaticMutexAutoLock locker(gTelemetryHistogramMutex); return gInitDone; } @@ -1916,22 +2139,17 @@ TelemetryHistogram::Accumulate(const cha if (!internal_CanRecordBase()) { return; } mozilla::Telemetry::ID id; nsresult rv = internal_GetHistogramEnumId(name, &id); if (NS_FAILED(rv)) { return; } - - Histogram *h; - rv = internal_GetHistogramByEnumId(id, &h); - if (NS_SUCCEEDED(rv)) { - internal_HistogramAdd(*h, sample, gHistograms[id].dataset); - } + internal_Accumulate(id, sample); } void TelemetryHistogram::Accumulate(const char* name, const nsCString& key, uint32_t sample) { StaticMutexAutoLock locker(gTelemetryHistogramMutex); if (!internal_CanRecordBase()) { @@ -1944,17 +2162,62 @@ TelemetryHistogram::Accumulate(const cha } } void TelemetryHistogram::AccumulateCategorical(mozilla::Telemetry::ID aId, const nsCString& label) { StaticMutexAutoLock locker(gTelemetryHistogramMutex); - internal_HistogramAddCategorical(aId, label); + if (!internal_CanRecordBase()) { + return; + } + uint32_t labelId = 0; + if (NS_FAILED(gHistograms[aId].label_id(label.get(), &labelId))) { + return; + } + internal_Accumulate(aId, labelId); +} + +void +TelemetryHistogram::AccumulateChild(const nsTArray<Accumulation>& aAccumulations) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + if (!internal_CanRecordBase()) { + return; + } + for (uint32_t i = 0; i < aAccumulations.Length(); ++i) { + bool isValid = internal_IsHistogramEnumId(aAccumulations[i].mId); + MOZ_ASSERT(isValid); + if (!isValid) { + continue; + } + internal_AccumulateChild(aAccumulations[i].mId, aAccumulations[i].mSample); + } +} + +void +TelemetryHistogram::AccumulateChildKeyed(const nsTArray<KeyedAccumulation>& aAccumulations) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + if (!internal_CanRecordBase()) { + return; + } + for (uint32_t i = 0; i < aAccumulations.Length(); ++i) { + bool isValid = internal_IsHistogramEnumId(aAccumulations[i].mId); + MOZ_ASSERT(isValid); + if (!isValid) { + continue; + } + internal_AccumulateChildKeyed(aAccumulations[i].mId, + aAccumulations[i].mKey, + aAccumulations[i].mSample); + } } void TelemetryHistogram::ClearHistogram(mozilla::Telemetry::ID aId) { StaticMutexAutoLock locker(gTelemetryHistogramMutex); if (!internal_CanRecordBase()) { return; @@ -2053,16 +2316,18 @@ TelemetryHistogram::CreateHistogramSnaps } const uint32_t type = gHistograms[i].histogramType; if (type == nsITelemetry::HISTOGRAM_FLAG || type == nsITelemetry::HISTOGRAM_COUNT) { Histogram *h; mozilla::DebugOnly<nsresult> rv = internal_GetHistogramByEnumId(mozilla::Telemetry::ID(i), &h); MOZ_ASSERT(NS_SUCCEEDED(rv)); + rv = internal_GetHistogramByEnumId(mozilla::Telemetry::ID(i), &h, true); + MOZ_ASSERT(NS_SUCCEEDED(rv)); } } StatisticsRecorder::Histograms hs; StatisticsRecorder::GetHistograms(&hs); // We identify corrupt histograms first, rather than interspersing it // in the loop below, to ensure that our corruption statistics don't @@ -2316,8 +2581,48 @@ TelemetryHistogram::GetHistogramSizesofI StatisticsRecorder::GetHistograms(&hs); size_t n = 0; for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) { Histogram *h = *it; n += h->SizeOfIncludingThis(aMallocSizeOf); } return n; } + +// This method takes the lock only to double-buffer the batched telemetry. +// It releases the lock before calling out to IPC code which can (and does) +// Accumulate (which would deadlock) +// +// To ensure we don't loop IPCTimerFired->AccumulateChild->arm timer, we don't +// unset gIPCTimerArmed until the IPC completes +// +// This function must be called on the main thread, otherwise IPC will fail. +void +TelemetryHistogram::IPCTimerFired(nsITimer* aTimer, void* aClosure) +{ + MOZ_ASSERT(NS_IsMainThread()); + nsTArray<Accumulation> accumulationsToSend; + nsTArray<KeyedAccumulation> keyedAccumulationsToSend; + { + StaticMutexAutoLock locker(gTelemetryHistogramMutex); + if (gAccumulations) { + accumulationsToSend.SwapElements(*gAccumulations); + } + if (gKeyedAccumulations) { + keyedAccumulationsToSend.SwapElements(*gKeyedAccumulations); + } + } + + mozilla::dom::ContentChild* contentChild = mozilla::dom::ContentChild::GetSingleton(); + mozilla::Unused << NS_WARN_IF(!contentChild); + if (contentChild) { + if (accumulationsToSend.Length()) { + mozilla::Unused << + NS_WARN_IF(!contentChild->SendAccumulateChildHistogram(accumulationsToSend)); + } + if (keyedAccumulationsToSend.Length()) { + mozilla::Unused << + NS_WARN_IF(!contentChild->SendAccumulateChildKeyedHistogram(keyedAccumulationsToSend)); + } + } + + gIPCTimerArmed = false; +}
--- a/toolkit/components/telemetry/TelemetryHistogram.h +++ b/toolkit/components/telemetry/TelemetryHistogram.h @@ -3,16 +3,18 @@ * 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 TelemetryHistogram_h__ #define TelemetryHistogram_h__ #include "mozilla/TelemetryHistogramEnums.h" +#include "mozilla/TelemetryComms.h" + // This module is internal to Telemetry. It encapsulates Telemetry's // histogram accumulation and storage logic. It should only be used by // Telemetry.cpp. These functions should not be used anywhere else. // For the public interface to Telemetry functionality, see Telemetry.h. namespace TelemetryHistogram { void CreateStatisticsRecorder(); @@ -37,16 +39,19 @@ nsresult SetHistogramRecordingEnabled(co void Accumulate(mozilla::Telemetry::ID aHistogram, uint32_t aSample); void Accumulate(mozilla::Telemetry::ID aID, const nsCString& aKey, uint32_t aSample); void Accumulate(const char* name, uint32_t sample); void Accumulate(const char* name, const nsCString& key, uint32_t sample); void AccumulateCategorical(mozilla::Telemetry::ID aId, const nsCString& aLabel); +void AccumulateChild(const nsTArray<mozilla::Telemetry::Accumulation>& aAccumulations); +void AccumulateChildKeyed(const nsTArray<mozilla::Telemetry::KeyedAccumulation>& aAccumulations); + void ClearHistogram(mozilla::Telemetry::ID aId); nsresult GetHistogramById(const nsACString &name, JSContext *cx, JS::MutableHandle<JS::Value> ret); nsresult @@ -91,11 +96,13 @@ nsresult GetAddonHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret); size_t GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf); size_t GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); +void +IPCTimerFired(nsITimer* aTimer, void* aClosure); } // namespace TelemetryHistogram #endif // TelemetryHistogram_h__
--- a/toolkit/components/telemetry/TelemetrySession.jsm +++ b/toolkit/components/telemetry/TelemetrySession.jsm @@ -36,33 +36,37 @@ const REASON_ABORTED_SESSION = "aborted- const REASON_DAILY = "daily"; const REASON_SAVED_SESSION = "saved-session"; const REASON_GATHER_PAYLOAD = "gather-payload"; const REASON_GATHER_SUBSESSION_PAYLOAD = "gather-subsession-payload"; const REASON_TEST_PING = "test-ping"; const REASON_ENVIRONMENT_CHANGE = "environment-change"; const REASON_SHUTDOWN = "shutdown"; +const HISTOGRAM_SUFFIXES = { + PARENT: "", + CONTENT: "#content", +} + const ENVIRONMENT_CHANGE_LISTENER = "TelemetrySession::onEnvironmentChange"; const MS_IN_ONE_HOUR = 60 * 60 * 1000; const MIN_SUBSESSION_LENGTH_MS = Preferences.get("toolkit.telemetry.minSubsessionLength", 10 * 60) * 1000; const LOGGER_NAME = "Toolkit.Telemetry"; const LOGGER_PREFIX = "TelemetrySession" + (Utils.isContentProcess ? "#content::" : "::"); const PREF_BRANCH = "toolkit.telemetry."; const PREF_PREVIOUS_BUILDID = PREF_BRANCH + "previousBuildID"; const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled"; const PREF_ASYNC_PLUGIN_INIT = "dom.ipc.plugins.asyncInit.enabled"; const PREF_UNIFIED = PREF_BRANCH + "unified"; const MESSAGE_TELEMETRY_PAYLOAD = "Telemetry:Payload"; -const MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD = "Telemetry:GetChildPayload"; const MESSAGE_TELEMETRY_THREAD_HANGS = "Telemetry:ChildThreadHangs"; const MESSAGE_TELEMETRY_GET_CHILD_THREAD_HANGS = "Telemetry:GetChildThreadHangs"; const MESSAGE_TELEMETRY_USS = "Telemetry:USS"; const MESSAGE_TELEMETRY_GET_CHILD_USS = "Telemetry:GetChildUSS"; const DATAREPORTING_DIRECTORY = "datareporting"; const ABORTED_SESSION_FILE_NAME = "aborted-session-ping"; @@ -536,23 +540,16 @@ this.TelemetrySession = Object.freeze({ * @param reason Optional, the reason to trigger the payload. * @param clearSubsession Optional, whether to clear subsession specific data. * @returns Object */ 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(); - }, - /** * Returns a promise that resolves to an array of thread hang stats from content processes, one entry per process. * The structure of each entry is identical to that of "threadHangStats" in nsITelemetry. * While thread hang stats are also part of the child payloads, this function is useful for cheaply getting this information, * which is useful for realtime hang monitoring. * Child processes that do not respond, or spawn/die during execution of this function are excluded from the result. * @returns Promise */ getChildThreadHangs: function() { @@ -879,33 +876,38 @@ var Impl = { * @return {Integer} A value from nsITelemetry.DATASET_*. */ getDatasetType: function() { return Telemetry.canRecordExtended ? Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN : Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT; }, getHistograms: function getHistograms(subsession, clearSubsession) { - this._log.trace("getHistograms - subsession: " + subsession + ", clearSubsession: " + clearSubsession); + this._log.trace("getHistograms - subsession: " + subsession + + ", clearSubsession: " + clearSubsession); let registered = Telemetry.registeredHistograms(this.getDatasetType(), []); + if (this._testing == false) { + // Omit telemetry test histograms outside of tests. + registered = registered.filter(n => !n.startsWith("TELEMETRY_TEST_")); + } + registered = registered.concat(registered.map(n => "STARTUP_" + n)); + let hls = subsession ? Telemetry.snapshotSubsessionHistograms(clearSubsession) : Telemetry.histogramSnapshots; let ret = {}; for (let name of registered) { - for (let n of [name, "STARTUP_" + name]) { - if (n in hls) { - // Omit telemetry test histograms outside of tests. - if (n.startsWith('TELEMETRY_TEST_') && this._testing == false) { - this._log.trace("getHistograms - Skipping test histogram: " + n); - } else { - ret[n] = this.packHistogram(hls[n]); + for (let suffix of Object.values(HISTOGRAM_SUFFIXES)) { + if (name + suffix in hls) { + if (!(suffix in ret)) { + ret[suffix] = {}; } + ret[suffix][name] = this.packHistogram(hls[name + suffix]); } } } return ret; }, getAddonHistograms: function getAddonHistograms() { @@ -923,46 +925,51 @@ var Impl = { if (Object.keys(packedHistograms).length != 0) ret[addonName] = packedHistograms; } return ret; }, getKeyedHistograms: function(subsession, clearSubsession) { - this._log.trace("getKeyedHistograms - subsession: " + subsession + ", clearSubsession: " + clearSubsession); + this._log.trace("getKeyedHistograms - subsession: " + subsession + + ", clearSubsession: " + clearSubsession); let registered = Telemetry.registeredKeyedHistograms(this.getDatasetType(), []); + if (this._testing == false) { + // Omit telemetry test histograms outside of tests. + registered = registered.filter(id => !id.startsWith("TELEMETRY_TEST_")); + } let ret = {}; for (let id of registered) { - // Omit telemetry test histograms outside of tests. - if (id.startsWith('TELEMETRY_TEST_') && this._testing == false) { - this._log.trace("getKeyedHistograms - Skipping test histogram: " + id); - continue; - } - let keyed = Telemetry.getKeyedHistogramById(id); - let snapshot = null; - if (subsession) { - snapshot = clearSubsession ? keyed.snapshotSubsessionAndClear() - : keyed.subsessionSnapshot(); - } else { - snapshot = keyed.snapshot(); - } + for (let suffix of Object.values(HISTOGRAM_SUFFIXES)) { + let keyed = Telemetry.getKeyedHistogramById(id + suffix); + let snapshot = null; + if (subsession) { + snapshot = clearSubsession ? keyed.snapshotSubsessionAndClear() + : keyed.subsessionSnapshot(); + } else { + snapshot = keyed.snapshot(); + } - let keys = Object.keys(snapshot); - if (keys.length == 0) { - // Skip empty keyed histogram. - continue; - } + let keys = Object.keys(snapshot); + if (keys.length == 0) { + // Skip empty keyed histogram. + continue; + } - ret[id] = {}; - for (let key of keys) { - ret[id][key] = this.packHistogram(snapshot[key]); + if (!(suffix in ret)) { + ret[suffix] = {}; + } + ret[suffix][id] = {}; + for (let key of keys) { + ret[suffix][id][key] = this.packHistogram(snapshot[key]); + } } } return ret; }, getScalars: function (subsession, clearSubsession) { this._log.trace("getScalars - subsession: " + subsession + ", clearSubsession: " + clearSubsession); @@ -1238,53 +1245,58 @@ var Impl = { assemblePayloadWithMeasurements: function(simpleMeasurements, info, reason, clearSubsession) { const isSubsession = IS_UNIFIED_TELEMETRY && !this._isClassicReason(reason); clearSubsession = IS_UNIFIED_TELEMETRY && clearSubsession; this._log.trace("assemblePayloadWithMeasurements - reason: " + reason + ", submitting subsession data: " + isSubsession); // This allows wrapping data retrieval calls in a try-catch block so that // failures don't break the rest of the ping assembly. - const protect = (fn) => { + const protect = (fn, defaultReturn = null) => { try { return fn(); } catch (ex) { this._log.error("assemblePayloadWithMeasurements - caught exception", ex); - return null; + return defaultReturn; } }; // Payload common to chrome and content processes. let payloadObj = { ver: PAYLOAD_VERSION, simpleMeasurements: simpleMeasurements, - histograms: protect(() => this.getHistograms(isSubsession, clearSubsession)), - keyedHistograms: protect(() => this.getKeyedHistograms(isSubsession, clearSubsession)), }; // Add extended set measurements common to chrome & content processes if (Telemetry.canRecordExtended) { payloadObj.chromeHangs = protect(() => Telemetry.chromeHangs); payloadObj.threadHangStats = protect(() => this.getThreadHangStats(Telemetry.threadHangStats)); payloadObj.log = protect(() => TelemetryLog.entries()); payloadObj.webrtc = protect(() => Telemetry.webrtcStats); } if (Utils.isContentProcess) { return payloadObj; } - // Set the scalars for the parent process. + // Additional payload for chrome process. + let histograms = protect(() => this.getHistograms(isSubsession, clearSubsession), {}); + let keyedHistograms = protect(() => this.getKeyedHistograms(isSubsession, clearSubsession), {}); + payloadObj.histograms = histograms[HISTOGRAM_SUFFIXES.PARENT] || {}; + payloadObj.keyedHistograms = keyedHistograms[HISTOGRAM_SUFFIXES.PARENT] || {}; payloadObj.processes = { parent: { scalars: protect(() => this.getScalars(isSubsession, clearSubsession)), - } + }, + content: { + histograms: histograms[HISTOGRAM_SUFFIXES.CONTENT], + keyedHistograms: keyedHistograms[HISTOGRAM_SUFFIXES.CONTENT], + }, }; - // Additional payload for chrome process. payloadObj.info = info; // Add extended set measurements for chrome process. if (Telemetry.canRecordExtended) { payloadObj.slowSQL = protect(() => Telemetry.slowSQL); payloadObj.fileIOReports = protect(() => Telemetry.fileIOReports); payloadObj.lateWrites = protect(() => Telemetry.lateWrites); @@ -1516,17 +1528,16 @@ var Impl = { this._testing = testing; if (!Telemetry.canRecordBase) { this._log.trace("setupContentProcess - base recording is disabled, not initializing"); return; } Services.obs.addObserver(this, "content-child-shutdown", false); - cpml.addMessageListener(MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD, this); cpml.addMessageListener(MESSAGE_TELEMETRY_GET_CHILD_THREAD_HANGS, this); cpml.addMessageListener(MESSAGE_TELEMETRY_GET_CHILD_USS, this); this.gatherStartupHistograms(); let delayedTask = new DeferredTask(function* () { this._initialized = true; @@ -1554,42 +1565,28 @@ var Impl = { this._log.trace("receiveMessage - Message name " + message.name); switch (message.name) { case MESSAGE_TELEMETRY_PAYLOAD: { // In parent process, receive Telemetry payload from child let source = message.data.childUUID; delete message.data.childUUID; - for (let child of this._childTelemetry) { - if (child.source === source) { - // Update existing telemetry data. - child.payload = message.data; - return; - } - } - // Did not find existing child in this._childTelemetry. this._childTelemetry.push({ 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: - { - // In child process, send the requested Telemetry payload - this.sendContentProcessPing("saved-session"); - break; - } case MESSAGE_TELEMETRY_THREAD_HANGS: { // Accumulate child thread hang stats from this child this._childThreadHangs.push(message.data); // Check if we've got data from all the children, accounting for child processes dying // if it happens before the last response is received and no new child processes are spawned at the exact same time // If that happens, we can resolve the promise earlier rather than having to wait for the timeout to expire @@ -1759,21 +1756,16 @@ var Impl = { if (Object.keys(this._slowSQLStartup).length == 0) { this.gatherStartupHistograms(); this._slowSQLStartup = Telemetry.slowSQL; } this.gatherMemory(); return this.getSessionPayload(reason, clearSubsession); }, - requestChildPayloads: function() { - this._log.trace("requestChildPayloads"); - ppmm.broadcastAsyncMessage(MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD, {}); - }, - getChildThreadHangs: function getChildThreadHangs() { return new Promise((resolve) => { // Return immediately if there are no child processes to get stats from if (ppmm.childCount === 0) { resolve([]); return; } @@ -1841,17 +1833,17 @@ var Impl = { this._log.trace("observe - " + aTopic + " notified."); } switch (aTopic) { case "content-child-shutdown": // content-child-shutdown is only registered for content processes. Services.obs.removeObserver(this, "content-child-shutdown"); this.uninstall(); - + Telemetry.flushBatchedChildTelemetry(); this.sendContentProcessPing(REASON_SAVED_SESSION); break; case TOPIC_CYCLE_COLLECTOR_BEGIN: let now = new Date(); if (!gLastMemoryPoll || (TELEMETRY_INTERVAL <= now - gLastMemoryPoll)) { gLastMemoryPoll = now; this.gatherMemory();
--- a/toolkit/components/telemetry/moz.build +++ b/toolkit/components/telemetry/moz.build @@ -14,16 +14,17 @@ XPIDL_SOURCES += [ XPIDL_MODULE = 'telemetry' EXPORTS.mozilla += [ '!TelemetryHistogramEnums.h', '!TelemetryScalarEnums.h', 'ProcessedStack.h', 'Telemetry.h', + 'TelemetryComms.h', 'ThreadHangStats.h', ] SOURCES += [ 'Telemetry.cpp', 'TelemetryCommon.cpp', 'TelemetryHistogram.cpp', 'TelemetryScalar.cpp',
--- a/toolkit/components/telemetry/nsITelemetry.idl +++ b/toolkit/components/telemetry/nsITelemetry.idl @@ -391,9 +391,15 @@ interface nsITelemetry : nsISupports */ [implicit_jscontext, optional_argc] jsval snapshotScalars(in uint32_t aDataset, [optional] in boolean aClear); /** * Resets all the stored scalars. This is intended to be only used in tests. */ void clearScalars(); + + /** + * Immediately sends any Telemetry batched on this process to the parent + * process. This is intended only to be used on process shutdown. + */ + void flushBatchedChildTelemetry(); };
--- a/toolkit/components/telemetry/tests/unit/test_ChildHistograms.js +++ b/toolkit/components/telemetry/tests/unit/test_ChildHistograms.js @@ -1,12 +1,13 @@ Cu.import("resource://gre/modules/TelemetryController.jsm", this); Cu.import("resource://gre/modules/TelemetrySession.jsm", this); Cu.import("resource://gre/modules/PromiseUtils.jsm", this); +Cu.import("resource://testing-common/ContentTaskUtils.jsm", this); const MESSAGE_TELEMETRY_PAYLOAD = "Telemetry:Payload"; const MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD = "Telemetry:GetChildPayload"; const MESSAGE_CHILD_TEST_DONE = "ChildTest:Done"; const PLATFORM_VERSION = "1.9.2"; const APP_VERSION = "1"; const APP_ID = "xpcshell@tests.mozilla.org"; @@ -14,38 +15,40 @@ const APP_NAME = "XPCShell"; function run_child_test() { // Setup histograms with some fixed values. let flagHist = Telemetry.getHistogramById("TELEMETRY_TEST_FLAG"); flagHist.add(1); let countHist = Telemetry.getHistogramById("TELEMETRY_TEST_COUNT"); countHist.add(); countHist.add(); + let categHist = Telemetry.getHistogramById("TELEMETRY_TEST_CATEGORICAL"); + categHist.add("Label2"); + categHist.add("Label3"); let flagKeyed = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_FLAG"); flagKeyed.add("a", 1); flagKeyed.add("b", 1); let countKeyed = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_COUNT"); countKeyed.add("a"); countKeyed.add("b"); countKeyed.add("b"); - - // Check payload values. - const payload = TelemetrySession.getPayload("test-ping"); - check_histogram_values(payload); } function check_histogram_values(payload) { const hs = payload.histograms; Assert.ok("TELEMETRY_TEST_COUNT" in hs, "Should have count test histogram."); Assert.ok("TELEMETRY_TEST_FLAG" in hs, "Should have flag test histogram."); + Assert.ok("TELEMETRY_TEST_CATEGORICAL" in hs, "Should have categorical test histogram."); Assert.equal(hs["TELEMETRY_TEST_COUNT"].sum, 2, "Count test histogram should have the right value."); Assert.equal(hs["TELEMETRY_TEST_FLAG"].sum, 1, "Flag test histogram should have the right value."); + Assert.equal(hs["TELEMETRY_TEST_CATEGORICAL"].sum, 3, + "Categorical test histogram should have the right sum."); const kh = payload.keyedHistograms; Assert.ok("TELEMETRY_TEST_KEYED_COUNT" in kh, "Should have keyed count test histogram."); Assert.ok("TELEMETRY_TEST_KEYED_FLAG" in kh, "Should have keyed flag test histogram."); Assert.equal(kh["TELEMETRY_TEST_KEYED_COUNT"]["a"].sum, 1, "Keyed count test histogram should have the right value."); Assert.equal(kh["TELEMETRY_TEST_KEYED_COUNT"]["b"].sum, 2, "Keyed count test histogram should have the right value."); @@ -56,18 +59,16 @@ function check_histogram_values(payload) } add_task(function*() { if (!runningInParent) { TelemetryController.testSetupContent(); run_child_test(); dump("... done with child test\n"); do_send_remote_message(MESSAGE_CHILD_TEST_DONE); - dump("... waiting for child payload collection\n"); - yield do_await_remote_message(MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD); return; } // Setup. do_get_profile(true); loadAddonManager(APP_ID, APP_NAME, APP_VERSION, PLATFORM_VERSION); Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true); yield TelemetryController.testSetup(); @@ -75,25 +76,25 @@ add_task(function*() { // Make sure we don't generate unexpected pings due to pref changes. yield setEmptyPrefWatchlist(); } // Run test in child, don't wait for it to finish. let childPromise = run_test_in_child("test_ChildHistograms.js"); yield do_await_remote_message(MESSAGE_CHILD_TEST_DONE); - // Gather payload from child. - dump("... requesting child payloads\n"); - let promiseMessage = do_await_remote_message(MESSAGE_TELEMETRY_PAYLOAD); - TelemetrySession.requestChildPayloads(); - yield promiseMessage; - dump("... received child payload\n"); - - // Check child payload. + yield ContentTaskUtils.waitForCondition(() => { + let payload = TelemetrySession.getPayload("test-ping"); + return payload && + "processes" in payload && + "content" in payload.processes && + "histograms" in payload.processes.content && + "TELEMETRY_TEST_COUNT" in payload.processes.content.histograms; + }); const payload = TelemetrySession.getPayload("test-ping"); - Assert.ok("childPayloads" in payload, "Should have child payloads."); - Assert.equal(payload.childPayloads.length, 1, "Should have received one child payload so far."); - Assert.ok("histograms" in payload.childPayloads[0], "Child payload should have histograms."); - Assert.ok("keyedHistograms" in payload.childPayloads[0], "Child payload should have keyed histograms."); - check_histogram_values(payload.childPayloads[0]); + Assert.ok("processes" in payload, "Should have processes section"); + Assert.ok("content" in payload.processes, "Should have child process section"); + Assert.ok("histograms" in payload.processes.content, "Child process section should have histograms."); + Assert.ok("keyedHistograms" in payload.processes.content, "Child process section should have keyed histograms."); + check_histogram_values(payload.processes.content); do_test_finished(); });
--- a/toolkit/content/aboutTelemetry.css +++ b/toolkit/content/aboutTelemetry.css @@ -224,16 +224,24 @@ body[dir="rtl"] .copy-node { padding-inline-start: 10em; display: none; } .has-data.expanded .filter-ui { display: inline; } +.processes-ui { + display: none; +} + +.has-data.expanded .processes-ui { + display: initial; +} + .filter-blocked { display: none; } #raw-ping-data-section { width: 100%; height: 100%; background-color:-moz-Dialog; @@ -252,8 +260,12 @@ body[dir="rtl"] .copy-node { padding: 5px 10px; } /* addon subsection style */ .addon-caption { font-size: larger; margin: 5px 0; } + +.process-picker { + margin: 0 0.5em; +}
--- a/toolkit/content/aboutTelemetry.js +++ b/toolkit/content/aboutTelemetry.js @@ -301,16 +301,20 @@ var PingPicker = { }, false); document.getElementById("newer-ping") .addEventListener("click", () => this._movePingIndex(-1), false); document.getElementById("older-ping") .addEventListener("click", () => this._movePingIndex(1), false); document.getElementById("choose-payload") .addEventListener("change", () => displayPingData(gPingData), false); + document.getElementById("histograms-processes") + .addEventListener("change", () => displayPingData(gPingData), false); + document.getElementById("keyed-histograms-processes") + .addEventListener("change", () => displayPingData(gPingData), false); }, onPingSourceChanged: function() { this.update(); }, onPingDisplayChanged: function() { this.update(); @@ -1777,16 +1781,43 @@ function sortStartupMilestones(aSimpleMe let result = {}; for (let key of sortedKeys) { result[key] = aSimpleMeasurements[key]; } return result; } +function renderProcessList(ping, selectEl) { + removeAllChildNodes(selectEl); + let option = document.createElement("option"); + option.appendChild(document.createTextNode("parent")); + option.setAttribute("value", ""); + option.selected = true; + selectEl.appendChild(option); + + if (!("processes" in ping.payload)) { + selectEl.disabled = true; + return; + } + selectEl.disabled = false; + + for (let process of Object.keys(ping.payload.processes)) { + // TODO: parent hgrams are on root payload, not in payload.processes.parent + // When/If that gets moved, you'll need to remove this: + if (process === "parent") { + continue; + } + option = document.createElement("option"); + option.appendChild(document.createTextNode(process)); + option.setAttribute("value", process); + selectEl.appendChild(option); + } +} + function renderPayloadList(ping) { // Rebuild the payload select with options: // Parent Payload (selected) // Child Payload 1..ping.payload.childPayloads.length let listEl = document.getElementById("choose-payload"); removeAllChildNodes(listEl); let option = document.createElement("option"); @@ -1845,19 +1876,21 @@ function displayPingData(ping, updatePay // Render raw ping data. let pre = document.getElementById("raw-ping-data"); pre.textContent = JSON.stringify(gPingData, null, 2); // Update the structured data rendering. const keysHeader = bundle.GetStringFromName("keysHeader"); const valuesHeader = bundle.GetStringFromName("valuesHeader"); - // Update the payload list + // Update the payload list and process lists if (updatePayloadList) { renderPayloadList(ping); + renderProcessList(ping, document.getElementById("histograms-processes")); + renderProcessList(ping, document.getElementById("keyed-histograms-processes")); } // Show general data. GeneralData.render(ping); // Show environment data. EnvironmentData.render(ping); @@ -1924,18 +1957,28 @@ function displayPingData(ping, updatePay // Show scalar data. Scalars.render(payload); // Show histogram data let hgramDiv = document.getElementById("histograms"); removeAllChildNodes(hgramDiv); let histograms = payload.histograms; + + let hgramsSelect = document.getElementById("histograms-processes"); + let hgramsOption = hgramsSelect.selectedOptions.item(0); + let hgramsProcess = hgramsOption.getAttribute("value"); + if (hgramsProcess && + "processes" in ping.payload && + hgramsProcess in ping.payload.processes) { + histograms = ping.payload.processes[hgramsProcess].histograms; + } + hasData = Object.keys(histograms).length > 0; - setHasData("histograms-section", hasData); + setHasData("histograms-section", hasData || hgramsSelect.options.length); if (hasData) { for (let [name, hgram] of Object.entries(histograms)) { Histogram.render(hgramDiv, name, hgram, {unpacked: true}); } let filterBox = document.getElementById("histograms-filter"); filterBox.addEventListener("input", Histogram.histogramFilterChanged, false); @@ -1945,27 +1988,37 @@ function displayPingData(ping, updatePay setHasData("histograms-section", true); } // Show keyed histogram data let keyedDiv = document.getElementById("keyed-histograms"); removeAllChildNodes(keyedDiv); - setHasData("keyed-histograms-section", false); let keyedHistograms = payload.keyedHistograms; + + let keyedHgramsSelect = document.getElementById("keyed-histograms-processes"); + let keyedHgramsOption = keyedHgramsSelect.selectedOptions.item(0); + let keyedHgramsProcess = keyedHgramsOption.getAttribute("value"); + if (keyedHgramsProcess && + "processes" in ping.payload && + keyedHgramsProcess in ping.payload.processes) { + keyedHistograms = ping.payload.processes[keyedHgramsProcess].keyedHistograms; + } + + setHasData("keyed-histograms-section", keyedHgramsSelect.options.length); if (keyedHistograms) { let hasData = false; for (let [id, keyed] of Object.entries(keyedHistograms)) { if (Object.keys(keyed).length > 0) { hasData = true; KeyedHistogram.render(keyedDiv, id, keyed, {unpacked: true}); } } - setHasData("keyed-histograms-section", hasData); + setHasData("keyed-histograms-section", hasData || keyedHgramsSelect.options.length); } // Show addon histogram data let addonDiv = document.getElementById("addon-histograms"); removeAllChildNodes(addonDiv); let addonHistogramsRendered = false; let addonData = payload.addonHistograms;
--- a/toolkit/content/aboutTelemetry.xhtml +++ b/toolkit/content/aboutTelemetry.xhtml @@ -147,25 +147,31 @@ <section id="histograms-section" class="data-section"> <input type="checkbox" class="statebox"/> <h1 class="section-name">&aboutTelemetry.histogramsSection;</h1> <span class="toggle-caption">&aboutTelemetry.toggle;</span> <span class="empty-caption">&aboutTelemetry.emptySection;</span> <span class="filter-ui"> &aboutTelemetry.filterText; <input type="text" class="filter" id="histograms-filter" target_id="histograms"/> </span> + <div class="processes-ui"> + <select id="histograms-processes" class="process-picker"></select> + </div> <div id="histograms" class="data"> </div> </section> <section id="keyed-histograms-section" class="data-section"> <input type="checkbox" class="statebox"/> <h1 class="section-name">&aboutTelemetry.keyedHistogramsSection;</h1> <span class="toggle-caption">&aboutTelemetry.toggle;</span> <span class="empty-caption">&aboutTelemetry.emptySection;</span> + <div class="processes-ui"> + <select id="keyed-histograms-processes" class="process-picker"></select> + </div> <div id="keyed-histograms" class="data"> </div> </section> <section id="simple-measurements-section" class="data-section"> <input type="checkbox" class="statebox"/> <h1 class="section-name">&aboutTelemetry.simpleMeasurementsSection;</h1> <span class="toggle-caption">&aboutTelemetry.toggle;</span>
--- a/widget/WidgetEventImpl.cpp +++ b/widget/WidgetEventImpl.cpp @@ -1,13 +1,14 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "gfxPrefs.h" #include "mozilla/BasicEvents.h" #include "mozilla/ContentEvents.h" #include "mozilla/InternalMutationEvent.h" #include "mozilla/MiscEvents.h" #include "mozilla/MouseEvents.h" #include "mozilla/Preferences.h" #include "mozilla/TextEvents.h" #include "mozilla/TouchEvents.h" @@ -407,45 +408,25 @@ WidgetInputEvent::AccelModifier() } return sAccelModifier; } /****************************************************************************** * mozilla::WidgetWheelEvent (MouseEvents.h) ******************************************************************************/ -bool WidgetWheelEvent::sInitialized = false; -bool WidgetWheelEvent::sIsSystemScrollSpeedOverrideEnabled = false; -int32_t WidgetWheelEvent::sOverrideFactorX = 0; -int32_t WidgetWheelEvent::sOverrideFactorY = 0; - -/* static */ void -WidgetWheelEvent::Initialize() -{ - if (sInitialized) { - return; - } - - Preferences::AddBoolVarCache(&sIsSystemScrollSpeedOverrideEnabled, - "mousewheel.system_scroll_override_on_root_content.enabled", false); - Preferences::AddIntVarCache(&sOverrideFactorX, - "mousewheel.system_scroll_override_on_root_content.horizontal.factor", 0); - Preferences::AddIntVarCache(&sOverrideFactorY, - "mousewheel.system_scroll_override_on_root_content.vertical.factor", 0); - sInitialized = true; -} - /* static */ double WidgetWheelEvent::ComputeOverriddenDelta(double aDelta, bool aIsForVertical) { - Initialize(); - if (!sIsSystemScrollSpeedOverrideEnabled) { + if (!gfxPrefs::MouseWheelHasRootScrollDeltaOverride()) { return aDelta; } - int32_t intFactor = aIsForVertical ? sOverrideFactorY : sOverrideFactorX; + int32_t intFactor = aIsForVertical + ? gfxPrefs::MouseWheelRootScrollVerticalFactor() + : gfxPrefs::MouseWheelRootScrollHorizontalFactor(); // Making the scroll speed slower doesn't make sense. So, ignore odd factor // which is less than 1.0. if (intFactor <= 100) { return aDelta; } double factor = static_cast<double>(intFactor) / 100; return aDelta * factor; }