From e36586f9011137e9719421965e1bf79f5101b0ba Mon Sep 17 00:00:00 2001 From: Fernando Jimenez Date: Fri, 22 May 2015 09:32:25 +0200 Subject: [PATCH 01/89] Bug 1161684 - Allow JAR channels to be intercepted by service workers. r=jdm --- dom/workers/ServiceWorkerManager.cpp | 97 +++++++++-------- modules/libjar/InterceptedJARChannel.cpp | 126 ++++++++++++++++++++++ modules/libjar/InterceptedJARChannel.h | 62 +++++++++++ modules/libjar/moz.build | 2 + modules/libjar/nsJARChannel.cpp | 127 +++++++++++++++++++++-- modules/libjar/nsJARChannel.h | 25 +++++ netwerk/base/moz.build | 1 + 7 files changed, 387 insertions(+), 53 deletions(-) create mode 100644 modules/libjar/InterceptedJARChannel.cpp create mode 100644 modules/libjar/InterceptedJARChannel.h diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index 4e5ad8ce478..77a117b9387 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -14,6 +14,7 @@ #include "nsIHttpChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIHttpHeaderVisitor.h" +#include "nsIJARChannel.h" #include "nsINetworkInterceptController.h" #include "nsIMutableArray.h" #include "nsIUploadChannel2.h" @@ -2594,60 +2595,70 @@ public: rv = uri->GetSpec(mSpec); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr httpChannel = do_QueryInterface(channel); - NS_ENSURE_TRUE(httpChannel, NS_ERROR_NOT_AVAILABLE); - - rv = httpChannel->GetRequestMethod(mMethod); - NS_ENSURE_SUCCESS(rv, rv); - uint32_t loadFlags; rv = channel->GetLoadFlags(&loadFlags); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr internalChannel = do_QueryInterface(httpChannel); - NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE); - - uint32_t mode; - internalChannel->GetCorsMode(&mode); - switch (mode) { - case nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN: - mRequestMode = RequestMode::Same_origin; - break; - case nsIHttpChannelInternal::CORS_MODE_NO_CORS: - mRequestMode = RequestMode::No_cors; - break; - case nsIHttpChannelInternal::CORS_MODE_CORS: - case nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT: - mRequestMode = RequestMode::Cors; - break; - default: - MOZ_CRASH("Unexpected CORS mode"); - } - - if (loadFlags & nsIRequest::LOAD_ANONYMOUS) { - mRequestCredentials = RequestCredentials::Omit; - } else { - bool includeCrossOrigin; - internalChannel->GetCorsIncludeCredentials(&includeCrossOrigin); - if (includeCrossOrigin) { - mRequestCredentials = RequestCredentials::Include; - } - } - - rv = httpChannel->VisitRequestHeaders(this); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr loadInfo; rv = channel->GetLoadInfo(getter_AddRefs(loadInfo)); NS_ENSURE_SUCCESS(rv, rv); mContentPolicyType = loadInfo->GetContentPolicyType(); - nsCOMPtr uploadChannel = do_QueryInterface(httpChannel); - if (uploadChannel) { - MOZ_ASSERT(!mUploadStream); - rv = uploadChannel->CloneUploadStream(getter_AddRefs(mUploadStream)); + nsCOMPtr httpChannel = do_QueryInterface(channel); + if (httpChannel) { + rv = httpChannel->GetRequestMethod(mMethod); NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr internalChannel = do_QueryInterface(httpChannel); + NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE); + + uint32_t mode; + internalChannel->GetCorsMode(&mode); + switch (mode) { + case nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN: + mRequestMode = RequestMode::Same_origin; + break; + case nsIHttpChannelInternal::CORS_MODE_NO_CORS: + mRequestMode = RequestMode::No_cors; + break; + case nsIHttpChannelInternal::CORS_MODE_CORS: + case nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT: + mRequestMode = RequestMode::Cors; + break; + default: + MOZ_CRASH("Unexpected CORS mode"); + } + + if (loadFlags & nsIRequest::LOAD_ANONYMOUS) { + mRequestCredentials = RequestCredentials::Omit; + } else { + bool includeCrossOrigin; + internalChannel->GetCorsIncludeCredentials(&includeCrossOrigin); + if (includeCrossOrigin) { + mRequestCredentials = RequestCredentials::Include; + } + } + + rv = httpChannel->VisitRequestHeaders(this); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr uploadChannel = do_QueryInterface(httpChannel); + if (uploadChannel) { + MOZ_ASSERT(!mUploadStream); + rv = uploadChannel->CloneUploadStream(getter_AddRefs(mUploadStream)); + NS_ENSURE_SUCCESS(rv, rv); + } + } else { + nsCOMPtr jarChannel = do_QueryInterface(channel); + // If it is not an HTTP channel it must be a JAR one. + NS_ENSURE_TRUE(jarChannel, NS_ERROR_NOT_AVAILABLE); + + mMethod = "GET"; + + if (loadFlags & nsIRequest::LOAD_ANONYMOUS) { + mRequestCredentials = RequestCredentials::Omit; + } } return NS_OK; diff --git a/modules/libjar/InterceptedJARChannel.cpp b/modules/libjar/InterceptedJARChannel.cpp new file mode 100644 index 00000000000..d706df4bffe --- /dev/null +++ b/modules/libjar/InterceptedJARChannel.cpp @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set expandtab ts=2 sw=2 sts=2 cin: */ +/* 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 "InterceptedJARChannel.h" +#include "nsIPipe.h" + +using namespace mozilla::net; + +NS_IMPL_ISUPPORTS(InterceptedJARChannel, nsIInterceptedChannel) + +InterceptedJARChannel::InterceptedJARChannel(nsJARChannel* aChannel, + nsINetworkInterceptController* aController, + bool aIsNavigation) +: mController(aController) +, mChannel(aChannel) +, mIsNavigation(aIsNavigation) +{ +} + +NS_IMETHODIMP +InterceptedJARChannel::GetResponseBody(nsIOutputStream** aStream) +{ + NS_IF_ADDREF(*aStream = mResponseBody); + return NS_OK; +} + +NS_IMETHODIMP +InterceptedJARChannel::GetIsNavigation(bool* aIsNavigation) +{ + *aIsNavigation = mIsNavigation; + return NS_OK; +} + +NS_IMETHODIMP +InterceptedJARChannel::GetChannel(nsIChannel** aChannel) +{ + NS_IF_ADDREF(*aChannel = mChannel); + return NS_OK; +} + +NS_IMETHODIMP +InterceptedJARChannel::ResetInterception() +{ + if (!mChannel) { + return NS_ERROR_NOT_AVAILABLE; + } + + mResponseBody = nullptr; + mSynthesizedInput = nullptr; + + mChannel->ResetInterception(); + mChannel = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +InterceptedJARChannel::SynthesizeStatus(uint16_t aStatus, + const nsACString& aReason) +{ + return NS_OK; +} + +NS_IMETHODIMP +InterceptedJARChannel::SynthesizeHeader(const nsACString& aName, + const nsACString& aValue) +{ + return NS_OK; +} + +NS_IMETHODIMP +InterceptedJARChannel::FinishSynthesizedResponse() +{ + if (NS_WARN_IF(!mChannel)) { + return NS_ERROR_NOT_AVAILABLE; + } + + mChannel->OverrideWithSynthesizedResponse(mSynthesizedInput); + + mResponseBody = nullptr; + mChannel = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +InterceptedJARChannel::Cancel() +{ + if (!mChannel) { + return NS_ERROR_FAILURE; + } + + nsresult rv = mChannel->Cancel(NS_BINDING_ABORTED); + NS_ENSURE_SUCCESS(rv, rv); + mResponseBody = nullptr; + mChannel = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +InterceptedJARChannel::SetSecurityInfo(nsISupports* aSecurityInfo) +{ + if (!mChannel) { + return NS_ERROR_FAILURE; + } + + return mChannel->OverrideSecurityInfo(aSecurityInfo); +} + +void +InterceptedJARChannel::NotifyController() +{ + nsresult rv = NS_NewPipe(getter_AddRefs(mSynthesizedInput), + getter_AddRefs(mResponseBody), + 0, UINT32_MAX, true, true); + NS_ENSURE_SUCCESS_VOID(rv); + + rv = mController->ChannelIntercepted(this); + if (NS_WARN_IF(NS_FAILED(rv))) { + rv = ResetInterception(); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), + "Failed to resume intercepted network request"); + } + mController = nullptr; +} diff --git a/modules/libjar/InterceptedJARChannel.h b/modules/libjar/InterceptedJARChannel.h new file mode 100644 index 00000000000..1f5fc130a13 --- /dev/null +++ b/modules/libjar/InterceptedJARChannel.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set expandtab ts=2 sw=2 sts=2 cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef InterceptedJARChannel_h +#define InterceptedJARChannel_h + +#include "nsJAR.h" +#include "nsJARChannel.h" +#include "nsIInputStream.h" +#include "nsIInputStreamPump.h" +#include "nsINetworkInterceptController.h" +#include "nsIOutputStream.h" +#include "nsRefPtr.h" + +#include "mozilla/Maybe.h" + +class nsIStreamListener; +class nsJARChannel; + +namespace mozilla { +namespace net { + +// An object representing a channel that has been intercepted. This avoids +// complicating the actual channel implementation with the details of +// synthesizing responses. +class InterceptedJARChannel : public nsIInterceptedChannel +{ + // The interception controller to notify about the successful channel + // interception. + nsCOMPtr mController; + + // The actual channel being intercepted. + nsRefPtr mChannel; + + // Reader-side of the synthesized response body. + nsCOMPtr mSynthesizedInput; + + // The stream to write the body of the synthesized response. + nsCOMPtr mResponseBody; + + // Wether this intercepted channel was performing a navigation. + bool mIsNavigation; + + virtual ~InterceptedJARChannel() {}; +public: + InterceptedJARChannel(nsJARChannel* aChannel, + nsINetworkInterceptController* aController, + bool aIsNavigation); + + NS_DECL_ISUPPORTS + NS_DECL_NSIINTERCEPTEDCHANNEL + + void NotifyController(); +}; + +} // namespace net +} // namespace mozilla + +#endif // InterceptedJARChannel_h diff --git a/modules/libjar/moz.build b/modules/libjar/moz.build index d02c680829c..858bbe30a7a 100644 --- a/modules/libjar/moz.build +++ b/modules/libjar/moz.build @@ -23,12 +23,14 @@ XPIDL_SOURCES += [ XPIDL_MODULE = 'jar' EXPORTS += [ + 'InterceptedJARChannel.h', 'nsJARURI.h', 'nsZipArchive.h', 'zipstruct.h', ] UNIFIED_SOURCES += [ + 'InterceptedJARChannel.cpp', 'nsJARProtocolHandler.cpp', 'nsJARURI.cpp', ] diff --git a/modules/libjar/nsJARChannel.cpp b/modules/libjar/nsJARChannel.cpp index 93cc1431535..4160ea7182c 100644 --- a/modules/libjar/nsJARChannel.cpp +++ b/modules/libjar/nsJARChannel.cpp @@ -24,6 +24,9 @@ #include "mozilla/net/RemoteOpenFileChild.h" #include "nsITabChild.h" #include "private/pprio.h" +#include "nsINetworkInterceptController.h" +#include "InterceptedJARChannel.h" +#include "nsInputStreamPump.h" using namespace mozilla; using namespace mozilla::net; @@ -199,6 +202,7 @@ nsJARChannel::nsJARChannel() , mIsUnsafe(true) , mOpeningRemote(false) , mEnsureChildFd(false) + , mSynthesizedStreamLength(0) { if (!gJarProtocolLog) gJarProtocolLog = PR_NewLogModule("nsJarProtocol"); @@ -234,7 +238,7 @@ NS_IMPL_ISUPPORTS_INHERITED(nsJARChannel, nsIThreadRetargetableStreamListener, nsIJARChannel) -nsresult +nsresult nsJARChannel::Init(nsIURI *uri) { nsresult rv; @@ -530,6 +534,8 @@ nsJARChannel::GetStatus(nsresult *status) { if (mPump && NS_SUCCEEDED(mStatus)) mPump->GetStatus(status); + else if (mSynthesizedResponsePump && NS_SUCCEEDED(mStatus)) + mSynthesizedResponsePump->GetStatus(status); else *status = mStatus; return NS_OK; @@ -541,6 +547,8 @@ nsJARChannel::Cancel(nsresult status) mStatus = status; if (mPump) return mPump->Cancel(status); + if (mSynthesizedResponsePump) + return mSynthesizedResponsePump->Cancel(status); NS_ASSERTION(!mIsPending, "need to implement cancel when downloading"); return NS_OK; @@ -551,6 +559,8 @@ nsJARChannel::Suspend() { if (mPump) return mPump->Suspend(); + if (mSynthesizedResponsePump) + return mSynthesizedResponsePump->Suspend(); NS_ASSERTION(!mIsPending, "need to implement suspend when downloading"); return NS_OK; @@ -561,6 +571,8 @@ nsJARChannel::Resume() { if (mPump) return mPump->Resume(); + if (mSynthesizedResponsePump) + return mSynthesizedResponsePump->Resume(); NS_ASSERTION(!mIsPending, "need to implement resume when downloading"); return NS_OK; @@ -670,7 +682,20 @@ nsJARChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) return NS_OK; } -NS_IMETHODIMP +nsresult +nsJARChannel::OverrideSecurityInfo(nsISupports* aSecurityInfo) +{ + MOZ_RELEASE_ASSERT(!mSecurityInfo, + "This can only be called when we don't have a security info object already"); + MOZ_RELEASE_ASSERT(aSecurityInfo, + "This can only be called with a valid security info object"); + MOZ_RELEASE_ASSERT(ShouldIntercept(), + "This can only be called on channels that can be intercepted"); + mSecurityInfo = aSecurityInfo; + return NS_OK; +} + +NS_IMETHODIMP nsJARChannel::GetSecurityInfo(nsISupports **aSecurityInfo) { NS_PRECONDITION(aSecurityInfo, "Null out param"); @@ -836,6 +861,66 @@ nsJARChannel::Open(nsIInputStream **stream) return NS_OK; } +bool +nsJARChannel::ShouldIntercept() +{ + LOG(("nsJARChannel::ShouldIntercept [this=%x]\n", this)); + // We only intercept app:// requests + if (!mAppURI) { + return false; + } + + nsCOMPtr controller; + NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, + NS_GET_IID(nsINetworkInterceptController), + getter_AddRefs(controller)); + bool shouldIntercept = false; + if (controller) { + bool isNavigation = mLoadFlags & LOAD_DOCUMENT_URI; + nsresult rv = controller->ShouldPrepareForIntercept(mAppURI, + isNavigation, + &shouldIntercept); + NS_ENSURE_SUCCESS(rv, false); + } + + return shouldIntercept; +} + +void nsJARChannel::ResetInterception() +{ + LOG(("nsJARChannel::ResetInterception [this=%x]\n", this)); + + // Continue with the origin request. + nsresult rv = ContinueAsyncOpen(); + NS_ENSURE_SUCCESS_VOID(rv); +} + +void +nsJARChannel::OverrideWithSynthesizedResponse(nsIInputStream* aSynthesizedInput) +{ + // In our current implementation, the FetchEvent handler will copy the + // response stream completely into the pipe backing the input stream so we + // can treat the available as the length of the stream. + uint64_t available; + nsresult rv = aSynthesizedInput->Available(&available); + if (NS_WARN_IF(NS_FAILED(rv))) { + mSynthesizedStreamLength = -1; + } else { + mSynthesizedStreamLength = int64_t(available); + } + + rv = nsInputStreamPump::Create(getter_AddRefs(mSynthesizedResponsePump), + aSynthesizedInput, + int64_t(-1), int64_t(-1), 0, 0, true); + if (NS_WARN_IF(NS_FAILED(rv))) { + aSynthesizedInput->Close(); + return; + } + + rv = mSynthesizedResponsePump->AsyncRead(this, nullptr); + NS_ENSURE_SUCCESS_VOID(rv); +} + NS_IMETHODIMP nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx) { @@ -851,18 +936,40 @@ nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx) // Initialize mProgressSink NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, mProgressSink); - nsresult rv = LookupFile(true); - if (NS_FAILED(rv)) - return rv; - - // These variables must only be set if we're going to trigger an - // OnStartRequest, either from AsyncRead or OnDownloadComplete. - // - // That means: Do not add early return statements beyond this point! mListener = listener; mListenerContext = ctx; mIsPending = true; + // Check if this channel should intercept the network request and prepare + // for a possible synthesized response instead. + if (ShouldIntercept()) { + nsCOMPtr controller; + NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, + NS_GET_IID(nsINetworkInterceptController), + getter_AddRefs(controller)); + + bool isNavigation = mLoadFlags & LOAD_DOCUMENT_URI; + nsRefPtr intercepted = + new InterceptedJARChannel(this, controller, isNavigation); + intercepted->NotifyController(); + return NS_OK; + } + + return ContinueAsyncOpen(); +} + +nsresult +nsJARChannel::ContinueAsyncOpen() +{ + LOG(("nsJARChannel::ContinueAsyncOpen [this=%x]\n", this)); + nsresult rv = LookupFile(true); + if (NS_FAILED(rv)) { + mIsPending = false; + mListenerContext = nullptr; + mListener = nullptr; + return rv; + } + nsCOMPtr channel; if (!mJarFile) { diff --git a/modules/libjar/nsJARChannel.h b/modules/libjar/nsJARChannel.h index 108e0339311..d7f5cbefd1e 100644 --- a/modules/libjar/nsJARChannel.h +++ b/modules/libjar/nsJARChannel.h @@ -10,6 +10,7 @@ #include "nsIJARChannel.h" #include "nsIJARURI.h" #include "nsIInputStreamPump.h" +#include "InterceptedJARChannel.h" #include "nsIInterfaceRequestor.h" #include "nsIProgressEventSink.h" #include "nsIStreamListener.h" @@ -27,6 +28,13 @@ #include "mozilla/Logging.h" class nsJARInputThunk; +class nsInputStreamPump; + +namespace mozilla { +namespace net { + class InterceptedJARChannel; +} // namespace net +} // namespace mozilla //----------------------------------------------------------------------------- @@ -69,6 +77,18 @@ private: mozilla::net::MemoryDownloader::Data aData) override; + // Returns true if this channel should intercept the network request and + // prepare for a possible synthesized response instead. + bool ShouldIntercept(); + nsresult ContinueAsyncOpen(); + // Discard the prior interception and continue with the original network + // request. + void ResetInterception(); + // Override this channel's pending response with a synthesized one. The + // content will be asynchronously read from the pump. + void OverrideWithSynthesizedResponse(nsIInputStream* aSynthesizedInput); + nsresult OverrideSecurityInfo(nsISupports* aSecurityInfo); + nsCString mSpec; bool mOpened; @@ -107,6 +127,11 @@ private: nsCOMPtr mJarBaseURI; nsCString mJarEntry; nsCString mInnerJarEntry; + + nsRefPtr mSynthesizedResponsePump; + int64_t mSynthesizedStreamLength; + + friend class mozilla::net::InterceptedJARChannel; }; #endif // nsJARChannel_h__ diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build index 0960556dba8..542af4b43bf 100644 --- a/netwerk/base/moz.build +++ b/netwerk/base/moz.build @@ -144,6 +144,7 @@ EXPORTS += [ 'nsASocketHandler.h', 'nsAsyncRedirectVerifyHelper.h', 'nsFileStreams.h', + 'nsInputStreamPump.h', 'nsMIMEInputStream.h', 'nsNetUtil.h', 'nsReadLine.h', From 72fb1c17c3c9dbf9fb1037e89c680d9d977a2cbe Mon Sep 17 00:00:00 2001 From: Fernando Jimenez Date: Fri, 22 May 2015 09:32:25 +0200 Subject: [PATCH 02/89] Bug 1161684 - Allow JAR channels to be intercepted by service workers. Tests. r=jdm --- .../serviceworkers/app-protocol/README.txt | 2 + .../app-protocol/application.zip | Bin 0 -> 2331 bytes .../app-protocol/controlled.html | 18 +++ .../test/serviceworkers/app-protocol/foo.txt | 1 + .../serviceworkers/app-protocol/index.html | 31 ++++ .../app-protocol/manifest.webapp | 5 + .../test/serviceworkers/app-protocol/sw.js | 8 + .../test/serviceworkers/app-protocol/test.js | 29 ++++ .../serviceworkers/app-protocol/update.webapp | 6 + .../app-protocol/update.webapp^headers^ | 1 + dom/workers/test/serviceworkers/mochitest.ini | 2 + .../serviceworkers/test_app_protocol.html | 146 ++++++++++++++++++ 12 files changed, 249 insertions(+) create mode 100644 dom/workers/test/serviceworkers/app-protocol/README.txt create mode 100644 dom/workers/test/serviceworkers/app-protocol/application.zip create mode 100644 dom/workers/test/serviceworkers/app-protocol/controlled.html create mode 100644 dom/workers/test/serviceworkers/app-protocol/foo.txt create mode 100644 dom/workers/test/serviceworkers/app-protocol/index.html create mode 100644 dom/workers/test/serviceworkers/app-protocol/manifest.webapp create mode 100644 dom/workers/test/serviceworkers/app-protocol/sw.js create mode 100644 dom/workers/test/serviceworkers/app-protocol/test.js create mode 100644 dom/workers/test/serviceworkers/app-protocol/update.webapp create mode 100644 dom/workers/test/serviceworkers/app-protocol/update.webapp^headers^ create mode 100644 dom/workers/test/serviceworkers/test_app_protocol.html diff --git a/dom/workers/test/serviceworkers/app-protocol/README.txt b/dom/workers/test/serviceworkers/app-protocol/README.txt new file mode 100644 index 00000000000..fcf383b3003 --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/README.txt @@ -0,0 +1,2 @@ +application.zip contains foo.txt, index.html, sw.js and manifest.webapp. +Any change to one of these three files should be added to application.zip as well. diff --git a/dom/workers/test/serviceworkers/app-protocol/application.zip b/dom/workers/test/serviceworkers/app-protocol/application.zip new file mode 100644 index 0000000000000000000000000000000000000000..0e914a94cf6639d4f26e47a5a26e21c0422eb82e GIT binary patch literal 2331 zcmaJ?c~nzp7EcH<0TOnB0hKrn${G*`WNl$0yFeNZn;Mo7*}`IoEV2s3DmWRILRnKW zpdge}Fl1y?1S|=J9T@~smJykPG8L?F2r@6Im{3pOcfRw^`{Uj7yZ5(ToGV0983YGT zD3Rj;5_zOS@*upu-3d2)3@x5U@J4_JPWcg31VqB`U}ey`frrRWBZR|j;_Y=r_Hse;-_rtEJ)xlk2W#90GH)%H2v& zj2zY&bnfeard|FD?APG6$V!U zI|j!9Aj^P@+l7wJ6^AYdKqo~-(x_1s3ONW9LW`gX@k@C65vEW3?cgWmx`lTcA+D@! z?4RWEv<@4!*p=Bn>A6T&{)JJ`GJB1m;<`I%Z>`)7?rq7>Tw8ps`XDhSaWW;6=>Nal z4Sn&(pd{O2$D{X%8w#xkmU5lG6B_B$w6qrD@kn&rcm-dw)u!}*bY9X?b!kHQ$v92kRdolrLma_H!7}^?X#+m&DNLYM?V{*o$#7z;Flk+@U*Fmu=EsD*!afiBXlC zBTki2WucKlD>hVscEj zF(2=;y}4}OKhjW6aUJyV@W_f{K4+@^S*Qp}KQD_*%hw#uv zL4V2gD-eQLG1n#{?TA$ z9;YHm@mT|neWxlKjykJ4iAqM9(mOottj?Kdl-M3m= zGmXA{fSsC5_-EyKZa{L={FSgN>7}CCA^bb0|G=eV%)v=rv$pW*sh0!Tnk=EZH8tf6!lG#83MCZh)@$%>1B6f0X@8s_%lrijTR9Q8G4=^L&L<8 z0|TO?_Xb7n0v?C!b>+Je8u2))zBXb+FNSIrZzP~La>nKq>D}oPbhhBH^vV$$6pJ;%n#d?=)%#+0B=*?RMbR6OSAdwr zO7}<%3V@7>!-U0rf_Cepx=a~%GU>MR^p*EnaT=;&OJ^6gTA+546$^r*AJ< zshJ3!DSg*o%)MA6LmJSGr8ay!alxr3D1pGX>oxJ>QV~-AeFcjhD}}5@v$09buIZ*S z{go!#-=!0-wEHU0oRYL||8MT)x#Q84w(OHVV#lvwbpJ*r3+HR~ShcFx;zKJQRLiQ#pgdB3KRkn5TBH#I4Ey8m!m1G$iq?Z_hAw2h;f!xq0W^w!-$VNkD81&BP-cdm}RlJ>A)zxmy$ ziT`}GGe3L#D$l}sj7_&c?2&}HdZ#O;HIc+j?9X?Y&*SJ3e}CYU$zbO6hD~u`v|D$z zs0}M;^rfmCeOc$~LUJu@?U1yA?~m!c?e^3mR5#nho?t2ShW4B;)1Phkc@@{^KIWtMRUqCU82v8OhrO+Q|4G%z`s{4} zhh)qsDF`$IToAwG0$md-tx{9Ph2!NKI$F}ma@GZJI;k%m?=lZ#ZaA}Dqubfzo zR-ncoRv3JDg^3mt2DNC|!o&*61JHM4`^!j#nJnse@ovro1w<1X4*HbPdqIfWQM?

Q@ literal 0 HcmV?d00001 diff --git a/dom/workers/test/serviceworkers/app-protocol/controlled.html b/dom/workers/test/serviceworkers/app-protocol/controlled.html new file mode 100644 index 00000000000..cf11816aeae --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/controlled.html @@ -0,0 +1,18 @@ + + + + Test app for bug 1161684 + + + + + + diff --git a/dom/workers/test/serviceworkers/app-protocol/foo.txt b/dom/workers/test/serviceworkers/app-protocol/foo.txt new file mode 100644 index 00000000000..8d1556472f3 --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/foo.txt @@ -0,0 +1 @@ +networkresponse \ No newline at end of file diff --git a/dom/workers/test/serviceworkers/app-protocol/index.html b/dom/workers/test/serviceworkers/app-protocol/index.html new file mode 100644 index 00000000000..38726531c5b --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/index.html @@ -0,0 +1,31 @@ + + + + Test app for bug 1161684 + + + + + + diff --git a/dom/workers/test/serviceworkers/app-protocol/manifest.webapp b/dom/workers/test/serviceworkers/app-protocol/manifest.webapp new file mode 100644 index 00000000000..e47d156f44a --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/manifest.webapp @@ -0,0 +1,5 @@ +{ + "name": "App", + "launch_path": "/index.html", + "description": "Test app for bug 1161684" +} diff --git a/dom/workers/test/serviceworkers/app-protocol/sw.js b/dom/workers/test/serviceworkers/app-protocol/sw.js new file mode 100644 index 00000000000..38975595b07 --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/sw.js @@ -0,0 +1,8 @@ +self.addEventListener('fetch', (event) => { + if (event.request.url.indexOf('foo.txt') >= 0) { + var body = 'swresponse'; + event.respondWith(new Response(body, { + headers: {'Content-Type': 'text/plain'} + })); + } +}); diff --git a/dom/workers/test/serviceworkers/app-protocol/test.js b/dom/workers/test/serviceworkers/app-protocol/test.js new file mode 100644 index 00000000000..e08d6156040 --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/test.js @@ -0,0 +1,29 @@ +function ok(aCondition, aMessage) { + if (aCondition) { + alert('OK: ' + aMessage); + } else { + alert('KO: ' + aMessage); + } +} + +function ready() { + alert('READY'); +} + +function done() { + alert('DONE'); +} + +function testFetchAppResource(aExpectedResponse) { + return fetch('foo.txt').then(res => { + ok(true, 'fetch should resolve'); + if (res.type == 'error') { + ok(false, 'fetch failed'); + } + ok(res.status == 200, 'status should be 200'); + ok(res.statusText == 'OK', 'statusText should be OK'); + return res.text().then(body => { + ok(body == aExpectedResponse, 'body should match'); + }); + }); +} diff --git a/dom/workers/test/serviceworkers/app-protocol/update.webapp b/dom/workers/test/serviceworkers/app-protocol/update.webapp new file mode 100644 index 00000000000..e4649d81c85 --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/update.webapp @@ -0,0 +1,6 @@ +{ + "name": "App", + "launch_path": "/index.html", + "description": "Test app for bug 1161684", + "package_path": "application.zip" +} diff --git a/dom/workers/test/serviceworkers/app-protocol/update.webapp^headers^ b/dom/workers/test/serviceworkers/app-protocol/update.webapp^headers^ new file mode 100644 index 00000000000..90818c6398e --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/update.webapp^headers^ @@ -0,0 +1 @@ +Content-Type: application/manifest+json diff --git a/dom/workers/test/serviceworkers/mochitest.ini b/dom/workers/test/serviceworkers/mochitest.ini index 283f294cef3..10a81a3de0b 100644 --- a/dom/workers/test/serviceworkers/mochitest.ini +++ b/dom/workers/test/serviceworkers/mochitest.ini @@ -97,6 +97,7 @@ support-files = claim_worker_2.js claim_clients/client.html claim_fetch_worker.js + app-protocol/* [test_unregister.html] [test_installation_simple.html] @@ -134,4 +135,5 @@ support-files = [test_sanitize.html] [test_sanitize_domain.html] [test_service_worker_allowed.html] +[test_app_protocol.html] [test_claim_fetch.html] diff --git a/dom/workers/test/serviceworkers/test_app_protocol.html b/dom/workers/test/serviceworkers/test_app_protocol.html new file mode 100644 index 00000000000..daa3af05e8c --- /dev/null +++ b/dom/workers/test/serviceworkers/test_app_protocol.html @@ -0,0 +1,146 @@ + + + + + Bug 1161684 - Allow JAR channels to be intercepted by service workers + + + + +

+ +

+
+
+ + From cfea3153937641a167bc6f1dbe23285a60bb2ec0 Mon Sep 17 00:00:00 2001 From: Fernando Jimenez Date: Sun, 24 May 2015 21:28:15 +0200 Subject: [PATCH 03/89] Bug 1162281 - Invalid system message handler in an App Manifest can break the entire system. r=fabrice --- dom/apps/Webapps.jsm | 46 ++-- dom/messages/SystemMessageInternal.js | 71 +++--- .../interfaces/nsISystemMessagesInternal.idl | 6 +- dom/messages/test/chrome.ini | 12 +- dom/messages/test/invalid_manifest.webapp | 8 + .../test/invalid_manifest.webapp^headers^ | 1 + dom/messages/test/manifest.webapp | 9 + dom/messages/test/manifest.webapp^headers^ | 1 + .../test/test_sysmsg_registration.html | 235 ++++++++++++++++++ 9 files changed, 337 insertions(+), 52 deletions(-) create mode 100644 dom/messages/test/invalid_manifest.webapp create mode 100644 dom/messages/test/invalid_manifest.webapp^headers^ create mode 100644 dom/messages/test/manifest.webapp create mode 100644 dom/messages/test/manifest.webapp^headers^ create mode 100644 dom/messages/test/test_sysmsg_registration.html diff --git a/dom/apps/Webapps.jsm b/dom/apps/Webapps.jsm index d94fc9b6dab..05d3a9e2fe5 100644 --- a/dom/apps/Webapps.jsm +++ b/dom/apps/Webapps.jsm @@ -860,6 +860,10 @@ this.DOMApplicationRegistry = { if (!root.messages || !Array.isArray(root.messages) || root.messages.length == 0) { + dump("Could not register invalid system message entry\n"); + try { + dump(JSON.stringify(root.messages) + "\n"); + } catch(e) {} return; } @@ -869,28 +873,32 @@ this.DOMApplicationRegistry = { root.messages.forEach(function registerPages(aMessage) { let handlerPageURI = launchPathURI; let messageName; - if (typeof(aMessage) === "object" && Object.keys(aMessage).length === 1) { - messageName = Object.keys(aMessage)[0]; - let handlerPath = aMessage[messageName]; - // Resolve the handler path from origin. If |handler_path| is absent, - // simply skip. - let fullHandlerPath; + if (typeof(aMessage) !== "object" || Object.keys(aMessage).length !== 1) { + dump("Could not register invalid system message entry\n"); try { - if (handlerPath && handlerPath.trim()) { - fullHandlerPath = manifest.resolveURL(handlerPath); - } else { - throw new Error("Empty or blank handler path."); - } - } catch(e) { - debug("system message handler path (" + handlerPath + ") is " + - "invalid, skipping. Error is: " + e); - return; - } - handlerPageURI = Services.io.newURI(fullHandlerPath, null, null); - } else { - messageName = aMessage; + dump(JSON.stringify(aMessage) + "\n"); + } catch(e) {} + return; } + messageName = Object.keys(aMessage)[0]; + let handlerPath = aMessage[messageName]; + // Resolve the handler path from origin. If |handler_path| is absent, + // simply skip. + let fullHandlerPath; + try { + if (handlerPath && handlerPath.trim()) { + fullHandlerPath = manifest.resolveURL(handlerPath); + } else { + throw new Error("Empty or blank handler path."); + } + } catch(e) { + debug("system message handler path (" + handlerPath + ") is " + + "invalid, skipping. Error is: " + e); + return; + } + handlerPageURI = Services.io.newURI(fullHandlerPath, null, null); + if (SystemMessagePermissionsChecker .isSystemMessagePermittedToRegister(messageName, aApp.manifestURL, diff --git a/dom/messages/SystemMessageInternal.js b/dom/messages/SystemMessageInternal.js index 3e030070f73..072bcb17dbf 100644 --- a/dom/messages/SystemMessageInternal.js +++ b/dom/messages/SystemMessageInternal.js @@ -273,7 +273,7 @@ SystemMessageInternal.prototype = { type: aType, msg: aMessage, extra: aExtra }); - return; + return Promise.resolve(); } // Give this message an ID so that we can identify the message and @@ -285,37 +285,50 @@ SystemMessageInternal.prototype = { let shouldDispatchFunc = this._getMessageConfigurator(aType).shouldDispatch; - // Find pages that registered an handler for this type. - this._pages.forEach(function(aPage) { - if (aPage.type !== aType) { - return; - } + if (!this._pages.length) { + return Promise.resolve(); + } - let doDispatch = () => { - let result = this._sendMessageCommon(aType, - aMessage, - messageID, - aPage.pageURL, - aPage.manifestURL, - aExtra); - debug("Returned status of sending message: " + result); - }; - - if ('function' !== typeof shouldDispatchFunc) { - // If the configurator has no 'shouldDispatch' defined, - // always dispatch this message. - doDispatch(); - return; - } - - shouldDispatchFunc(aPage.manifestURL, aPage.pageURL, aType, aMessage, aExtra) - .then(aShouldDispatch => { - if (aShouldDispatch) { - doDispatch(); + // Find pages that registered a handler for this type. + let promises = []; + for (let i = 0; i < this._pages.length; i++) { + let promise = ((page) => { + return new Promise((resolve, reject) => { + if (page.type !== aType) { + resolve(); + return; } - }); - }, this); + let doDispatch = () => { + let result = this._sendMessageCommon(aType, + aMessage, + messageID, + page.pageURL, + page.manifestURL, + aExtra); + debug("Returned status of sending message: " + result); + resolve(); + }; + + if ('function' !== typeof shouldDispatchFunc) { + // If the configurator has no 'shouldDispatch' defined, + // always dispatch this message. + doDispatch(); + return; + } + + shouldDispatchFunc(page.manifestURL, page.pageURL, aType, aMessage, aExtra) + .then(aShouldDispatch => { + if (aShouldDispatch) { + doDispatch(); + } + }); + }); + })(this._pages[i]); + promises.push(promise); + } + + return Promise.all(promises); }, registerPage: function(aType, aPageURI, aManifestURI) { diff --git a/dom/messages/interfaces/nsISystemMessagesInternal.idl b/dom/messages/interfaces/nsISystemMessagesInternal.idl index ad6f95121d1..92e562cebe9 100644 --- a/dom/messages/interfaces/nsISystemMessagesInternal.idl +++ b/dom/messages/interfaces/nsISystemMessagesInternal.idl @@ -10,7 +10,7 @@ interface nsIMessageSender; // Implemented by the contract id @mozilla.org/system-message-internal;1 -[scriptable, uuid(54c8e274-decb-4258-9a24-4ebfcbf3d00a)] +[scriptable, uuid(59b6beda-f911-4d47-a296-8c81e6abcfb9)] interface nsISystemMessagesInternal : nsISupports { /* @@ -36,8 +36,10 @@ interface nsISystemMessagesInternal : nsISupports * @param message The message payload. * @param extra Extra opaque information that will be passed around in the observer * notification to open the page. + * returns a Promise */ - void broadcastMessage(in DOMString type, in jsval message, [optional] in jsval extra); + nsISupports broadcastMessage(in DOMString type, in jsval message, + [optional] in jsval extra); /* * Registration of a page that wants to be notified of a message type. diff --git a/dom/messages/test/chrome.ini b/dom/messages/test/chrome.ini index 77ad7c46861..cf8cbd4ad9b 100644 --- a/dom/messages/test/chrome.ini +++ b/dom/messages/test/chrome.ini @@ -1,3 +1,11 @@ -[test_hasPendingMessage.html] +[DEFAULT] skip-if = (buildapp != "browser") || e10s -support-files = file_hasPendingMessage.html +support-files = + file_hasPendingMessage.html + invalid_manifest.webapp + invalid_manifest.webapp^headers^ + manifest.webapp + manifest.webapp^headers^ + +[test_hasPendingMessage.html] +[test_sysmsg_registration.html] diff --git a/dom/messages/test/invalid_manifest.webapp b/dom/messages/test/invalid_manifest.webapp new file mode 100644 index 00000000000..ead4f9f64b0 --- /dev/null +++ b/dom/messages/test/invalid_manifest.webapp @@ -0,0 +1,8 @@ +{ + "name": "Random app", + "launch_path": "/index.html", + "messages": [{ + "dummy-system-message": "/index.html", + "dummy-system-message2": "/index.html" + }] +} diff --git a/dom/messages/test/invalid_manifest.webapp^headers^ b/dom/messages/test/invalid_manifest.webapp^headers^ new file mode 100644 index 00000000000..90818c6398e --- /dev/null +++ b/dom/messages/test/invalid_manifest.webapp^headers^ @@ -0,0 +1 @@ +Content-Type: application/manifest+json diff --git a/dom/messages/test/manifest.webapp b/dom/messages/test/manifest.webapp new file mode 100644 index 00000000000..45b15930891 --- /dev/null +++ b/dom/messages/test/manifest.webapp @@ -0,0 +1,9 @@ +{ + "name": "Random app", + "launch_path": "/index.html", + "messages": [{ + "dummy-system-message": "/index.html" + }, { + "dummy-system-message2": "/index.html" + }] +} diff --git a/dom/messages/test/manifest.webapp^headers^ b/dom/messages/test/manifest.webapp^headers^ new file mode 100644 index 00000000000..90818c6398e --- /dev/null +++ b/dom/messages/test/manifest.webapp^headers^ @@ -0,0 +1 @@ +Content-Type: application/manifest+json diff --git a/dom/messages/test/test_sysmsg_registration.html b/dom/messages/test/test_sysmsg_registration.html new file mode 100644 index 00000000000..adacbfa8ce4 --- /dev/null +++ b/dom/messages/test/test_sysmsg_registration.html @@ -0,0 +1,235 @@ + + + + System messages registration tests + + + + + +Mozilla Bug {1162281} +

+ +
+
+
+ + From 5171b1d412729907e7d620eed668cbcbb7eaa458 Mon Sep 17 00:00:00 2001 From: Phil Ringnalda Date: Sun, 24 May 2015 13:59:43 -0700 Subject: [PATCH 04/89] Back out ffb94ee54752 (bug 1162281) for build and test bustage CLOSED TREE --- dom/apps/Webapps.jsm | 44 ++-- dom/messages/SystemMessageInternal.js | 63 ++--- .../interfaces/nsISystemMessagesInternal.idl | 6 +- dom/messages/test/chrome.ini | 12 +- dom/messages/test/invalid_manifest.webapp | 8 - .../test/invalid_manifest.webapp^headers^ | 1 - dom/messages/test/manifest.webapp | 9 - dom/messages/test/manifest.webapp^headers^ | 1 - .../test/test_sysmsg_registration.html | 235 ------------------ 9 files changed, 47 insertions(+), 332 deletions(-) delete mode 100644 dom/messages/test/invalid_manifest.webapp delete mode 100644 dom/messages/test/invalid_manifest.webapp^headers^ delete mode 100644 dom/messages/test/manifest.webapp delete mode 100644 dom/messages/test/manifest.webapp^headers^ delete mode 100644 dom/messages/test/test_sysmsg_registration.html diff --git a/dom/apps/Webapps.jsm b/dom/apps/Webapps.jsm index 05d3a9e2fe5..d94fc9b6dab 100644 --- a/dom/apps/Webapps.jsm +++ b/dom/apps/Webapps.jsm @@ -860,10 +860,6 @@ this.DOMApplicationRegistry = { if (!root.messages || !Array.isArray(root.messages) || root.messages.length == 0) { - dump("Could not register invalid system message entry\n"); - try { - dump(JSON.stringify(root.messages) + "\n"); - } catch(e) {} return; } @@ -873,31 +869,27 @@ this.DOMApplicationRegistry = { root.messages.forEach(function registerPages(aMessage) { let handlerPageURI = launchPathURI; let messageName; - if (typeof(aMessage) !== "object" || Object.keys(aMessage).length !== 1) { - dump("Could not register invalid system message entry\n"); + if (typeof(aMessage) === "object" && Object.keys(aMessage).length === 1) { + messageName = Object.keys(aMessage)[0]; + let handlerPath = aMessage[messageName]; + // Resolve the handler path from origin. If |handler_path| is absent, + // simply skip. + let fullHandlerPath; try { - dump(JSON.stringify(aMessage) + "\n"); - } catch(e) {} - return; - } - - messageName = Object.keys(aMessage)[0]; - let handlerPath = aMessage[messageName]; - // Resolve the handler path from origin. If |handler_path| is absent, - // simply skip. - let fullHandlerPath; - try { - if (handlerPath && handlerPath.trim()) { - fullHandlerPath = manifest.resolveURL(handlerPath); - } else { - throw new Error("Empty or blank handler path."); + if (handlerPath && handlerPath.trim()) { + fullHandlerPath = manifest.resolveURL(handlerPath); + } else { + throw new Error("Empty or blank handler path."); + } + } catch(e) { + debug("system message handler path (" + handlerPath + ") is " + + "invalid, skipping. Error is: " + e); + return; } - } catch(e) { - debug("system message handler path (" + handlerPath + ") is " + - "invalid, skipping. Error is: " + e); - return; + handlerPageURI = Services.io.newURI(fullHandlerPath, null, null); + } else { + messageName = aMessage; } - handlerPageURI = Services.io.newURI(fullHandlerPath, null, null); if (SystemMessagePermissionsChecker .isSystemMessagePermittedToRegister(messageName, diff --git a/dom/messages/SystemMessageInternal.js b/dom/messages/SystemMessageInternal.js index 072bcb17dbf..3e030070f73 100644 --- a/dom/messages/SystemMessageInternal.js +++ b/dom/messages/SystemMessageInternal.js @@ -273,7 +273,7 @@ SystemMessageInternal.prototype = { type: aType, msg: aMessage, extra: aExtra }); - return Promise.resolve(); + return; } // Give this message an ID so that we can identify the message and @@ -285,50 +285,37 @@ SystemMessageInternal.prototype = { let shouldDispatchFunc = this._getMessageConfigurator(aType).shouldDispatch; - if (!this._pages.length) { - return Promise.resolve(); - } + // Find pages that registered an handler for this type. + this._pages.forEach(function(aPage) { + if (aPage.type !== aType) { + return; + } - // Find pages that registered a handler for this type. - let promises = []; - for (let i = 0; i < this._pages.length; i++) { - let promise = ((page) => { - return new Promise((resolve, reject) => { - if (page.type !== aType) { - resolve(); - return; - } + let doDispatch = () => { + let result = this._sendMessageCommon(aType, + aMessage, + messageID, + aPage.pageURL, + aPage.manifestURL, + aExtra); + debug("Returned status of sending message: " + result); + }; - let doDispatch = () => { - let result = this._sendMessageCommon(aType, - aMessage, - messageID, - page.pageURL, - page.manifestURL, - aExtra); - debug("Returned status of sending message: " + result); - resolve(); - }; + if ('function' !== typeof shouldDispatchFunc) { + // If the configurator has no 'shouldDispatch' defined, + // always dispatch this message. + doDispatch(); + return; + } - if ('function' !== typeof shouldDispatchFunc) { - // If the configurator has no 'shouldDispatch' defined, - // always dispatch this message. + shouldDispatchFunc(aPage.manifestURL, aPage.pageURL, aType, aMessage, aExtra) + .then(aShouldDispatch => { + if (aShouldDispatch) { doDispatch(); - return; } - - shouldDispatchFunc(page.manifestURL, page.pageURL, aType, aMessage, aExtra) - .then(aShouldDispatch => { - if (aShouldDispatch) { - doDispatch(); - } - }); }); - })(this._pages[i]); - promises.push(promise); - } - return Promise.all(promises); + }, this); }, registerPage: function(aType, aPageURI, aManifestURI) { diff --git a/dom/messages/interfaces/nsISystemMessagesInternal.idl b/dom/messages/interfaces/nsISystemMessagesInternal.idl index 92e562cebe9..ad6f95121d1 100644 --- a/dom/messages/interfaces/nsISystemMessagesInternal.idl +++ b/dom/messages/interfaces/nsISystemMessagesInternal.idl @@ -10,7 +10,7 @@ interface nsIMessageSender; // Implemented by the contract id @mozilla.org/system-message-internal;1 -[scriptable, uuid(59b6beda-f911-4d47-a296-8c81e6abcfb9)] +[scriptable, uuid(54c8e274-decb-4258-9a24-4ebfcbf3d00a)] interface nsISystemMessagesInternal : nsISupports { /* @@ -36,10 +36,8 @@ interface nsISystemMessagesInternal : nsISupports * @param message The message payload. * @param extra Extra opaque information that will be passed around in the observer * notification to open the page. - * returns a Promise */ - nsISupports broadcastMessage(in DOMString type, in jsval message, - [optional] in jsval extra); + void broadcastMessage(in DOMString type, in jsval message, [optional] in jsval extra); /* * Registration of a page that wants to be notified of a message type. diff --git a/dom/messages/test/chrome.ini b/dom/messages/test/chrome.ini index cf8cbd4ad9b..77ad7c46861 100644 --- a/dom/messages/test/chrome.ini +++ b/dom/messages/test/chrome.ini @@ -1,11 +1,3 @@ -[DEFAULT] -skip-if = (buildapp != "browser") || e10s -support-files = - file_hasPendingMessage.html - invalid_manifest.webapp - invalid_manifest.webapp^headers^ - manifest.webapp - manifest.webapp^headers^ - [test_hasPendingMessage.html] -[test_sysmsg_registration.html] +skip-if = (buildapp != "browser") || e10s +support-files = file_hasPendingMessage.html diff --git a/dom/messages/test/invalid_manifest.webapp b/dom/messages/test/invalid_manifest.webapp deleted file mode 100644 index ead4f9f64b0..00000000000 --- a/dom/messages/test/invalid_manifest.webapp +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "Random app", - "launch_path": "/index.html", - "messages": [{ - "dummy-system-message": "/index.html", - "dummy-system-message2": "/index.html" - }] -} diff --git a/dom/messages/test/invalid_manifest.webapp^headers^ b/dom/messages/test/invalid_manifest.webapp^headers^ deleted file mode 100644 index 90818c6398e..00000000000 --- a/dom/messages/test/invalid_manifest.webapp^headers^ +++ /dev/null @@ -1 +0,0 @@ -Content-Type: application/manifest+json diff --git a/dom/messages/test/manifest.webapp b/dom/messages/test/manifest.webapp deleted file mode 100644 index 45b15930891..00000000000 --- a/dom/messages/test/manifest.webapp +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "Random app", - "launch_path": "/index.html", - "messages": [{ - "dummy-system-message": "/index.html" - }, { - "dummy-system-message2": "/index.html" - }] -} diff --git a/dom/messages/test/manifest.webapp^headers^ b/dom/messages/test/manifest.webapp^headers^ deleted file mode 100644 index 90818c6398e..00000000000 --- a/dom/messages/test/manifest.webapp^headers^ +++ /dev/null @@ -1 +0,0 @@ -Content-Type: application/manifest+json diff --git a/dom/messages/test/test_sysmsg_registration.html b/dom/messages/test/test_sysmsg_registration.html deleted file mode 100644 index adacbfa8ce4..00000000000 --- a/dom/messages/test/test_sysmsg_registration.html +++ /dev/null @@ -1,235 +0,0 @@ - - - - System messages registration tests - - - - - -Mozilla Bug {1162281} -

- -
-
-
- - From 38e011321b08390e724eb94f8df47aac3f10d3e6 Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Tue, 19 May 2015 17:32:57 +1200 Subject: [PATCH 05/89] bug 1167045 don't reuse output sample when !mMFTProvidesOutputSamples r=mattwoodrow --- dom/media/platforms/wmf/MFTDecoder.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/dom/media/platforms/wmf/MFTDecoder.cpp b/dom/media/platforms/wmf/MFTDecoder.cpp index 5fac9fdba1f..7a77c980b38 100644 --- a/dom/media/platforms/wmf/MFTDecoder.cpp +++ b/dom/media/platforms/wmf/MFTDecoder.cpp @@ -201,15 +201,15 @@ MFTDecoder::Output(RefPtr* aOutput) MFT_OUTPUT_DATA_BUFFER output = {0}; - bool providedSample = false; RefPtr sample; - if (*aOutput) { - output.pSample = *aOutput; - providedSample = true; - } else if (!mMFTProvidesOutputSamples) { - hr = CreateOutputSample(&sample); - NS_ENSURE_TRUE(SUCCEEDED(hr), hr); - output.pSample = sample; + if (!mMFTProvidesOutputSamples) { + if (*aOutput) { + output.pSample = *aOutput; + } else { + hr = CreateOutputSample(&sample); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + output.pSample = sample; + } } DWORD status = 0; @@ -248,7 +248,7 @@ MFTDecoder::Output(RefPtr* aOutput) } *aOutput = output.pSample; // AddRefs - if (mMFTProvidesOutputSamples && !providedSample) { + if (mMFTProvidesOutputSamples) { // If the MFT is providing samples, we must release the sample here. // Typically only the H.264 MFT provides samples when using DXVA, // and it always re-uses the same sample, so if we don't release it From 3b1dcc246db604ca6d281ce998d59c84c5b5fc44 Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Fri, 22 May 2015 15:23:00 +1200 Subject: [PATCH 06/89] bug 1162364 detect and abort MF_E_TRANSFORM_STREAM_CHANGE infinite loops r=cpearce --- dom/media/platforms/wmf/WMFAudioMFTManager.cpp | 6 ++++++ dom/media/platforms/wmf/WMFVideoMFTManager.cpp | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/dom/media/platforms/wmf/WMFAudioMFTManager.cpp b/dom/media/platforms/wmf/WMFAudioMFTManager.cpp index 64bfab608cf..9968a68fe69 100644 --- a/dom/media/platforms/wmf/WMFAudioMFTManager.cpp +++ b/dom/media/platforms/wmf/WMFAudioMFTManager.cpp @@ -205,6 +205,7 @@ WMFAudioMFTManager::Output(int64_t aStreamOffset, aOutData = nullptr; RefPtr sample; HRESULT hr; + int typeChangeCount = 0; while (true) { hr = mDecoder->Output(&sample); if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { @@ -213,6 +214,11 @@ WMFAudioMFTManager::Output(int64_t aStreamOffset, if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { hr = UpdateOutputType(); NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + // Catch infinite loops, but some decoders perform at least 2 stream + // changes on consecutive calls, so be permissive. + // 100 is arbitrarily > 2. + NS_ENSURE_TRUE(typeChangeCount < 100, MF_E_TRANSFORM_STREAM_CHANGE); + ++typeChangeCount; continue; } break; diff --git a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp index c345fa8bb4d..d50e0d7a749 100644 --- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp +++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp @@ -482,6 +482,7 @@ WMFVideoMFTManager::Output(int64_t aStreamOffset, RefPtr sample; HRESULT hr; aOutData = nullptr; + int typeChangeCount = 0; // Loop until we decode a sample, or an unexpected error that we can't // handle occurs. @@ -497,7 +498,12 @@ WMFVideoMFTManager::Output(int64_t aStreamOffset, MOZ_ASSERT(!sample); hr = ConfigureVideoFrameGeometry(); NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + // Catch infinite loops, but some decoders perform at least 2 stream + // changes on consecutive calls, so be permissive. + // 100 is arbitrarily > 2. + NS_ENSURE_TRUE(typeChangeCount < 100, MF_E_TRANSFORM_STREAM_CHANGE); // Loop back and try decoding again... + ++typeChangeCount; continue; } if (SUCCEEDED(hr)) { From 2a2deee1a1634e8e7866e7a744136b4d7d038bf2 Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Mon, 25 May 2015 08:52:30 +1200 Subject: [PATCH 07/89] bug 1166107 release internal drain monitor before calling Flush() r=gerald The DrainComplete() caught with mWaitForInternalDrain still won't necessarily be from the internal Drain(), but all we need is that one DrainComplete() is caught for the internal Drain() because one more will be generated if there is a Drain() in progress. What protecting mWaitForInternalDrain access with the monitor provides here is that the compiler won't use its address for storage of other data meaningless in the context of mWaitForInternalDrain and so, for example, two DrainComplete() calls won't unintentionally think that they are both for one internal drain. And TSan warnings. --- dom/media/platforms/SharedDecoderManager.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/dom/media/platforms/SharedDecoderManager.cpp b/dom/media/platforms/SharedDecoderManager.cpp index 6203973a699..55c50680533 100644 --- a/dom/media/platforms/SharedDecoderManager.cpp +++ b/dom/media/platforms/SharedDecoderManager.cpp @@ -163,16 +163,15 @@ void SharedDecoderManager::SetIdle(MediaDataDecoder* aProxy) { if (aProxy && mActiveProxy == aProxy) { - MonitorAutoLock mon(mMonitor); - mWaitForInternalDrain = true; - nsresult rv; { - // We don't want to hold the lock while calling Drain() has some + MonitorAutoLock mon(mMonitor); + mWaitForInternalDrain = true; + // We don't want to hold the lock while calling Drain() as some // platform implementations call DrainComplete() immediately. - MonitorAutoUnlock mon(mMonitor); - rv = mActiveProxy->Drain(); } + nsresult rv = mActiveProxy->Drain(); if (NS_SUCCEEDED(rv)) { + MonitorAutoLock mon(mMonitor); while (mWaitForInternalDrain) { mon.Wait(); } From e40d0dfd2836a9fd2d6ee494915773bda4dfc8bc Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Fri, 22 May 2015 11:10:00 +1200 Subject: [PATCH 08/89] bug 1166107 documentation of mWaitForInternalDrain thread access r=gerald --- dom/media/platforms/SharedDecoderManager.cpp | 2 +- dom/media/platforms/SharedDecoderManager.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dom/media/platforms/SharedDecoderManager.cpp b/dom/media/platforms/SharedDecoderManager.cpp index 55c50680533..05c896a14b7 100644 --- a/dom/media/platforms/SharedDecoderManager.cpp +++ b/dom/media/platforms/SharedDecoderManager.cpp @@ -74,7 +74,7 @@ SharedDecoderManager::SharedDecoderManager() , mActiveProxy(nullptr) , mActiveCallback(nullptr) , mWaitForInternalDrain(false) - , mMonitor("SharedDecoderProxy") + , mMonitor("SharedDecoderManager") , mDecoderReleasedResources(false) { MOZ_ASSERT(NS_IsMainThread()); // taskqueue must be created on main thread. diff --git a/dom/media/platforms/SharedDecoderManager.h b/dom/media/platforms/SharedDecoderManager.h index c6b33d8fec5..7c283085662 100644 --- a/dom/media/platforms/SharedDecoderManager.h +++ b/dom/media/platforms/SharedDecoderManager.h @@ -56,6 +56,7 @@ private: SharedDecoderProxy* mActiveProxy; MediaDataDecoderCallback* mActiveCallback; nsAutoPtr mCallback; + // access protected by mMonitor bool mWaitForInternalDrain; Monitor mMonitor; bool mDecoderReleasedResources; From 71402a0b354e9a99e823dbb18531ca8cfe8d058a Mon Sep 17 00:00:00 2001 From: Timothy Nikkel Date: Sun, 24 May 2015 16:48:26 -0400 Subject: [PATCH 09/89] Bug 1160642. Add reftest-async-zoom to apply an async zoom before taking snapshot. r=dbaron --- dom/base/nsDOMWindowUtils.cpp | 27 ++++++++++++++ dom/interfaces/base/nsIDOMWindowUtils.idl | 10 ++++- gfx/layers/apz/src/AsyncPanZoomController.cpp | 6 ++- gfx/layers/apz/src/AsyncPanZoomController.h | 9 +++++ gfx/layers/ipc/LayerTransactionParent.cpp | 16 ++++++++ gfx/layers/ipc/LayerTransactionParent.h | 2 + gfx/layers/ipc/PLayerTransaction.ipdl | 5 +++ .../reftest-sanity/async-zoom-1-ref.html | 6 +++ .../reftests/reftest-sanity/async-zoom-1.html | 6 +++ .../reftest-sanity/async-zoom-2-ref.html | 6 +++ .../reftests/reftest-sanity/async-zoom-2.html | 6 +++ layout/reftests/reftest-sanity/reftest.list | 3 ++ layout/tools/reftest/README.txt | 24 ++++++++---- layout/tools/reftest/reftest-content.js | 37 +++++++++++++++++-- 14 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 layout/reftests/reftest-sanity/async-zoom-1-ref.html create mode 100644 layout/reftests/reftest-sanity/async-zoom-1.html create mode 100644 layout/reftests/reftest-sanity/async-zoom-2-ref.html create mode 100644 layout/reftests/reftest-sanity/async-zoom-2.html diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index 73d86550e14..782de64457f 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -2455,6 +2455,33 @@ nsDOMWindowUtils::SetAsyncScrollOffset(nsIDOMNode* aNode, return NS_OK; } +NS_IMETHODIMP +nsDOMWindowUtils::SetAsyncZoom(nsIDOMNode* aRootElement, float aValue) +{ + nsCOMPtr element = do_QueryInterface(aRootElement); + if (!element) { + return NS_ERROR_INVALID_ARG; + } + FrameMetrics::ViewID viewId; + if (!nsLayoutUtils::FindIDFor(element, &viewId)) { + return NS_ERROR_UNEXPECTED; + } + nsIWidget* widget = GetWidget(); + if (!widget) { + return NS_ERROR_FAILURE; + } + LayerManager* manager = widget->GetLayerManager(); + if (!manager) { + return NS_ERROR_FAILURE; + } + ShadowLayerForwarder* forwarder = manager->AsShadowForwarder(); + if (!forwarder || !forwarder->HasShadowManager()) { + return NS_ERROR_UNEXPECTED; + } + forwarder->GetShadowManager()->SendSetAsyncZoom(viewId, aValue); + return NS_OK; +} + NS_IMETHODIMP nsDOMWindowUtils::ComputeAnimationDistance(nsIDOMElement* aElement, const nsAString& aProperty, diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl index 72602da9f86..20e765585c3 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -49,7 +49,7 @@ interface nsIJSRAIIHelper; interface nsIContentPermissionRequest; interface nsIObserver; -[scriptable, uuid(0ce789cc-3fb6-48b8-a58e-32deefc337b4)] +[scriptable, uuid(7719798a-62fa-4dff-a6ed-782256649232)] interface nsIDOMWindowUtils : nsISupports { /** @@ -1428,6 +1428,14 @@ interface nsIDOMWindowUtils : nsISupports { */ void setAsyncScrollOffset(in nsIDOMNode aNode, in int32_t aX, in int32_t aY); + /** + * Set async zoom value. aRootElement should be the document element of our + * document. The next composite will render with that zoom added to any + * existing zoom if async scrolling is enabled, and then the zoom will be + * removed. Only call this while test-controlled refreshes is enabled. + */ + void setAsyncZoom(in nsIDOMNode aRootElement, in float aValue); + /** * Method for testing StyleAnimationValue::ComputeDistance. * diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index 9c1dc5601e7..34f17f63439 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -2750,9 +2750,11 @@ ViewTransform AsyncPanZoomController::GetCurrentAsyncTransform() const { } ParentLayerPoint translation = (currentScrollOffset - lastPaintScrollOffset) - * mFrameMetrics.GetZoom(); + * mFrameMetrics.GetZoom() * mTestAsyncZoom.scale; - return ViewTransform(mFrameMetrics.GetAsyncZoom(), -translation); + return ViewTransform( + LayerToParentLayerScale(mFrameMetrics.GetAsyncZoom().scale * mTestAsyncZoom.scale), + -translation); } Matrix4x4 AsyncPanZoomController::GetCurrentAsyncTransformWithOverscroll() const { diff --git a/gfx/layers/apz/src/AsyncPanZoomController.h b/gfx/layers/apz/src/AsyncPanZoomController.h index ba756ef40a8..19679b43b8e 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.h +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -1069,6 +1069,13 @@ public: { mTestAsyncScrollOffset = aPoint; } + /** + * Set an extra offset for testing async scrolling. + */ + void SetTestAsyncZoom(const LayerToParentLayerScale& aZoom) + { + mTestAsyncZoom = aZoom; + } void MarkAsyncTransformAppliedToContent() { @@ -1083,6 +1090,8 @@ public: private: // Extra offset to add in SampleContentTransformForFrame for testing CSSPoint mTestAsyncScrollOffset; + // Extra zoom to include in SampleContentTransformForFrame for testing + LayerToParentLayerScale mTestAsyncZoom; // Flag to track whether or not the APZ transform is not used. This // flag is recomputed for every composition frame. bool mAsyncTransformAppliedToContent; diff --git a/gfx/layers/ipc/LayerTransactionParent.cpp b/gfx/layers/ipc/LayerTransactionParent.cpp index 8fde530862f..f1cb1143a10 100644 --- a/gfx/layers/ipc/LayerTransactionParent.cpp +++ b/gfx/layers/ipc/LayerTransactionParent.cpp @@ -783,6 +783,22 @@ LayerTransactionParent::RecvSetAsyncScrollOffset(const FrameMetrics::ViewID& aSc return true; } +bool +LayerTransactionParent::RecvSetAsyncZoom(const FrameMetrics::ViewID& aScrollID, + const float& aValue) +{ + if (mDestroyed || !layer_manager() || layer_manager()->IsDestroyed()) { + return false; + } + + AsyncPanZoomController* controller = GetAPZCForViewID(mRoot, aScrollID); + if (!controller) { + return false; + } + controller->SetTestAsyncZoom(LayerToParentLayerScale(aValue)); + return true; +} + bool LayerTransactionParent::RecvGetAPZTestData(APZTestData* aOutData) { diff --git a/gfx/layers/ipc/LayerTransactionParent.h b/gfx/layers/ipc/LayerTransactionParent.h index d63a3117d6b..e59b956deb9 100644 --- a/gfx/layers/ipc/LayerTransactionParent.h +++ b/gfx/layers/ipc/LayerTransactionParent.h @@ -133,6 +133,8 @@ protected: override; virtual bool RecvSetAsyncScrollOffset(const FrameMetrics::ViewID& aId, const int32_t& aX, const int32_t& aY) override; + virtual bool RecvSetAsyncZoom(const FrameMetrics::ViewID& aId, + const float& aValue) override; virtual bool RecvGetAPZTestData(APZTestData* aOutData) override; virtual bool RecvRequestProperty(const nsString& aProperty, float* aValue) override; virtual bool RecvSetConfirmedTargetAPZC(const uint64_t& aBlockId, diff --git a/gfx/layers/ipc/PLayerTransaction.ipdl b/gfx/layers/ipc/PLayerTransaction.ipdl index 11a8ac65cf1..249d79097cd 100644 --- a/gfx/layers/ipc/PLayerTransaction.ipdl +++ b/gfx/layers/ipc/PLayerTransaction.ipdl @@ -89,6 +89,11 @@ parent: // Useful for testing rendering of async scrolling. sync SetAsyncScrollOffset(ViewID id, int32_t x, int32_t y); + // The next time the layer tree is composited, include this async zoom in + // for the given ViewID. + // Useful for testing rendering of async zooming. + sync SetAsyncZoom(ViewID id, float zoom); + // Drop any front buffers that might be retained on the compositor // side. async ClearCachedResources(); diff --git a/layout/reftests/reftest-sanity/async-zoom-1-ref.html b/layout/reftests/reftest-sanity/async-zoom-1-ref.html new file mode 100644 index 00000000000..734bce16da8 --- /dev/null +++ b/layout/reftests/reftest-sanity/async-zoom-1-ref.html @@ -0,0 +1,6 @@ + + + +This is some content. + + diff --git a/layout/reftests/reftest-sanity/async-zoom-1.html b/layout/reftests/reftest-sanity/async-zoom-1.html new file mode 100644 index 00000000000..7f4d2244d1b --- /dev/null +++ b/layout/reftests/reftest-sanity/async-zoom-1.html @@ -0,0 +1,6 @@ + + + +This is some content. + + diff --git a/layout/reftests/reftest-sanity/async-zoom-2-ref.html b/layout/reftests/reftest-sanity/async-zoom-2-ref.html new file mode 100644 index 00000000000..939f62579f3 --- /dev/null +++ b/layout/reftests/reftest-sanity/async-zoom-2-ref.html @@ -0,0 +1,6 @@ + + + +
+ + diff --git a/layout/reftests/reftest-sanity/async-zoom-2.html b/layout/reftests/reftest-sanity/async-zoom-2.html new file mode 100644 index 00000000000..6ca88367885 --- /dev/null +++ b/layout/reftests/reftest-sanity/async-zoom-2.html @@ -0,0 +1,6 @@ + + + +
+ + diff --git a/layout/reftests/reftest-sanity/reftest.list b/layout/reftests/reftest-sanity/reftest.list index ba8c80cd570..2f3c3e60d82 100644 --- a/layout/reftests/reftest-sanity/reftest.list +++ b/layout/reftests/reftest-sanity/reftest.list @@ -166,6 +166,9 @@ default-preferences pref(layers.low-precision-buffer,false) skip-if(!asyncPanZoom||!browserIsRemote) != async-scroll-1b.html async-scroll-1-ref.html default-preferences +skip-if(!asyncPanZoom) != async-zoom-1.html async-zoom-1-ref.html +fuzzy(112,1197) skip-if(!asyncPanZoom) == async-zoom-2.html async-zoom-2-ref.html + # reftest-opaque-layer == reftest-opaque-layer-pass.html reftest-opaque-layer-pass.html != reftest-opaque-layer-pass.html about:blank diff --git a/layout/tools/reftest/README.txt b/layout/tools/reftest/README.txt index f68ace1dae3..aeab2f4f5d3 100644 --- a/layout/tools/reftest/README.txt +++ b/layout/tools/reftest/README.txt @@ -495,12 +495,12 @@ Zoom Tests: reftest-zoom="" ================================== When the root element of a test has a "reftest-zoom" attribute, that zoom -factor is applied when rendering the test. The reftest document will be -800 device pixels wide by 1000 device pixels high. The reftest harness assumes -that the CSS pixel dimensions are 800/zoom and 1000/zoom. For best results -therefore, choose zoom factors that do not require rounding when we calculate -the number of appunits per device pixel; i.e. the zoom factor should divide 60, -so 60/zoom is an integer. +factor is applied when rendering the test. The corresponds to the desktop "full +zoom" style zoom. The reftest document will be 800 device pixels wide by 1000 +device pixels high. The reftest harness assumes that the CSS pixel dimensions +are 800/zoom and 1000/zoom. For best results therefore, choose zoom factors +that do not require rounding when we calculate the number of appunits per +device pixel; i.e. the zoom factor should divide 60, so 60/zoom is an integer. Setting Viewport Size: reftest-viewport-w/h="" =================================================== @@ -521,7 +521,7 @@ Setting Async Scroll Mode: reftest-async-scroll attribute ========================================================= If the "reftest-async-scroll" attribute is set on the root element, we try to -enable async scrolling for the document. This is unsupported in many +enable async scrolling and zooming for the document. This is unsupported in many configurations. Setting Displayport Dimensions: reftest-displayport-x/y/w/h="" @@ -544,6 +544,16 @@ element where either the "reftest-async-scroll-x" or "reftest-async-scroll-y attributes are nonzero, at the end of the test take the snapshot with the given offset (in CSS pixels) added to the async scroll offset. +Testing Async Zooming: reftest-async-zoom="" +========================================================= + +When the "reftest-async-zoom" attribute is present on the root element then at +the end of the test take the snapshot with the given async zoom on top of any +existing zoom. Content is not re-rendered at the new zoom level. This +corresponds to the mobile style "pinch zoom" style of zoom. This is unsupported +in many configurations, and any tests using this will probably want to have +skip-if(!asyncPanZoom) on them. + Printing Tests: class="reftest-print" ===================================== diff --git a/layout/tools/reftest/reftest-content.js b/layout/tools/reftest/reftest-content.js index acf7cf3fff7..2388b012ecb 100644 --- a/layout/tools/reftest/reftest-content.js +++ b/layout/tools/reftest/reftest-content.js @@ -140,7 +140,7 @@ function StartTestURI(type, uri, timeout) LoadURI(gCurrentURL); } -function setupZoom(contentRootElement) { +function setupFullZoom(contentRootElement) { if (!contentRootElement || !contentRootElement.hasAttribute('reftest-zoom')) return; markupDocumentViewer().fullZoom = @@ -300,6 +300,28 @@ function setupAsyncScrollOffsets(options) { return false; } +function setupAsyncZoom(options) { + var currentDoc = content.document; + var contentRootElement = currentDoc ? currentDoc.documentElement : null; + + if (!contentRootElement || !contentRootElement.hasAttribute('reftest-async-zoom')) + return false; + + var zoom = attrOrDefault(contentRootElement, "reftest-async-zoom", 1); + if (zoom != 1) { + try { + windowUtils().setAsyncZoom(contentRootElement, zoom); + return true; + } catch (e) { + if (!options.allowFailure) { + throw e; + } + } + } + return false; +} + + function resetDisplayportAndViewport() { // XXX currently the displayport configuration lives on the // presshell and so is "reset" on nav when we get a new presshell. @@ -637,7 +659,7 @@ function OnDocumentLoad(event) var contentRootElement = currentDoc ? currentDoc.documentElement : null; currentDoc = null; - setupZoom(contentRootElement); + setupFullZoom(contentRootElement); setupViewport(contentRootElement); setupDisplayport(contentRootElement); var inPrintMode = false; @@ -812,8 +834,14 @@ function RecordResult() // Setup async scroll offsets now in case SynchronizeForSnapshot is not // called (due to reftest-no-sync-layers being supplied, or in the single // process case). - var changedAsyncScrollOffsets = setupAsyncScrollOffsets({allowFailure:true}) ; - if (changedAsyncScrollOffsets && !gBrowserIsRemote) { + var changedAsyncScrollZoom = false; + if (setupAsyncScrollOffsets({allowFailure:true})) { + changedAsyncScrollZoom = true; + } + if (setupAsyncZoom({allowFailure:true})) { + changedAsyncScrollZoom = true; + } + if (changedAsyncScrollZoom && !gBrowserIsRemote) { sendAsyncMessage("reftest:UpdateWholeCanvasForInvalidation"); } @@ -895,6 +923,7 @@ function SynchronizeForSnapshot(flags) // Setup async scroll offsets now, because any scrollable layers should // have had their AsyncPanZoomControllers created. setupAsyncScrollOffsets({allowFailure:false}); + setupAsyncZoom({allowFailure:false}); } function RegisterMessageListeners() From b04762c394b6e8d72d065534bcec251adf37bf21 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Sun, 24 May 2015 16:48:26 -0400 Subject: [PATCH 10/89] Bug 1151617 - Add async-zooming tests for async scrollbar positioning. r=tn,botond --- .../apz/reftests/async-scrollbar-zoom-1-ref.html | 9 +++++++++ .../apz/reftests/async-scrollbar-zoom-1.html | 14 ++++++++++++++ .../apz/reftests/async-scrollbar-zoom-2-ref.html | 9 +++++++++ .../apz/reftests/async-scrollbar-zoom-2.html | 14 ++++++++++++++ gfx/layers/apz/reftests/reftest.list | 8 ++++++++ 5 files changed, 54 insertions(+) create mode 100644 gfx/layers/apz/reftests/async-scrollbar-zoom-1-ref.html create mode 100644 gfx/layers/apz/reftests/async-scrollbar-zoom-1.html create mode 100644 gfx/layers/apz/reftests/async-scrollbar-zoom-2-ref.html create mode 100644 gfx/layers/apz/reftests/async-scrollbar-zoom-2.html diff --git a/gfx/layers/apz/reftests/async-scrollbar-zoom-1-ref.html b/gfx/layers/apz/reftests/async-scrollbar-zoom-1-ref.html new file mode 100644 index 00000000000..5ed970f7644 --- /dev/null +++ b/gfx/layers/apz/reftests/async-scrollbar-zoom-1-ref.html @@ -0,0 +1,9 @@ + + + + + +
+ + + diff --git a/gfx/layers/apz/reftests/async-scrollbar-zoom-1.html b/gfx/layers/apz/reftests/async-scrollbar-zoom-1.html new file mode 100644 index 00000000000..09be51a79af --- /dev/null +++ b/gfx/layers/apz/reftests/async-scrollbar-zoom-1.html @@ -0,0 +1,14 @@ + + + + + + +
+ + + diff --git a/gfx/layers/apz/reftests/async-scrollbar-zoom-2-ref.html b/gfx/layers/apz/reftests/async-scrollbar-zoom-2-ref.html new file mode 100644 index 00000000000..5ed970f7644 --- /dev/null +++ b/gfx/layers/apz/reftests/async-scrollbar-zoom-2-ref.html @@ -0,0 +1,9 @@ + + + + + +
+ + + diff --git a/gfx/layers/apz/reftests/async-scrollbar-zoom-2.html b/gfx/layers/apz/reftests/async-scrollbar-zoom-2.html new file mode 100644 index 00000000000..abe822c21b8 --- /dev/null +++ b/gfx/layers/apz/reftests/async-scrollbar-zoom-2.html @@ -0,0 +1,14 @@ + + + + + + +
+ + + diff --git a/gfx/layers/apz/reftests/reftest.list b/gfx/layers/apz/reftests/reftest.list index 33c54a36748..bda25be5ce5 100644 --- a/gfx/layers/apz/reftests/reftest.list +++ b/gfx/layers/apz/reftests/reftest.list @@ -6,3 +6,11 @@ skip-if(!asyncPanZoom) == async-scrollbar-1-vh.html async-scrollbar-1-vh-ref.htm skip-if(!asyncPanZoom) == async-scrollbar-1-v-rtl.html async-scrollbar-1-v-rtl-ref.html skip-if(!asyncPanZoom) == async-scrollbar-1-h-rtl.html async-scrollbar-1-h-rtl-ref.html skip-if(!asyncPanZoom) == async-scrollbar-1-vh-rtl.html async-scrollbar-1-vh-rtl-ref.html + +# Different zoom levels. Since B2G is the only APZ-enabled platform where we +# currently allow async zooming, that's the only platform on which these tests +# are run. And because the scrollthumb gets async-scaled in the compositor, the +# border-radius ends of the scrollthumb are going to be a little off, hence the +# fuzzy-if clauses. +skip-if(!asyncPanZoom||!B2G) fuzzy-if(B2G,77,82) == async-scrollbar-zoom-1.html async-scrollbar-zoom-1-ref.html +skip-if(!asyncPanZoom||!B2G) fuzzy-if(B2G,94,146) == async-scrollbar-zoom-2.html async-scrollbar-zoom-2-ref.html From 92cf4ee2ff575bec8800fbca3dbfb356f1d008dd Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 5 May 2015 16:11:43 -0700 Subject: [PATCH 11/89] Bug 1166598 (part 1) - Use PLDHashTable2 in nsScriptNameSpaceManager. r=froydnj. --- dom/base/nsScriptNameSpaceManager.cpp | 44 ++++++++++----------------- dom/base/nsScriptNameSpaceManager.h | 6 ++-- 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/dom/base/nsScriptNameSpaceManager.cpp b/dom/base/nsScriptNameSpaceManager.cpp index d068115be12..3c28890ad73 100644 --- a/dom/base/nsScriptNameSpaceManager.cpp +++ b/dom/base/nsScriptNameSpaceManager.cpp @@ -119,20 +119,29 @@ NS_IMPL_ISUPPORTS( nsISupportsWeakReference, nsIMemoryReporter) +static const PLDHashTableOps hash_table_ops = +{ + GlobalNameHashHashKey, + GlobalNameHashMatchEntry, + PL_DHashMoveEntryStub, + GlobalNameHashClearEntry, + GlobalNameHashInitEntry +}; + +#define GLOBALNAME_HASHTABLE_INITIAL_LENGTH 512 + nsScriptNameSpaceManager::nsScriptNameSpaceManager() - : mIsInitialized(false) + : mGlobalNames(&hash_table_ops, sizeof(GlobalNameMapEntry), + GLOBALNAME_HASHTABLE_INITIAL_LENGTH) + , mNavigatorNames(&hash_table_ops, sizeof(GlobalNameMapEntry), + GLOBALNAME_HASHTABLE_INITIAL_LENGTH) { MOZ_COUNT_CTOR(nsScriptNameSpaceManager); } nsScriptNameSpaceManager::~nsScriptNameSpaceManager() { - if (mIsInitialized) { - UnregisterWeakMemoryReporter(this); - // Destroy the hash - PL_DHashTableFinish(&mGlobalNames); - PL_DHashTableFinish(&mNavigatorNames); - } + UnregisterWeakMemoryReporter(this); MOZ_COUNT_DTOR(nsScriptNameSpaceManager); } @@ -309,30 +318,9 @@ nsScriptNameSpaceManager::RegisterInterface(const char* aIfName, return NS_OK; } -#define GLOBALNAME_HASHTABLE_INITIAL_LENGTH 512 - nsresult nsScriptNameSpaceManager::Init() { - static const PLDHashTableOps hash_table_ops = - { - GlobalNameHashHashKey, - GlobalNameHashMatchEntry, - PL_DHashMoveEntryStub, - GlobalNameHashClearEntry, - GlobalNameHashInitEntry - }; - - PL_DHashTableInit(&mGlobalNames, &hash_table_ops, - sizeof(GlobalNameMapEntry), - GLOBALNAME_HASHTABLE_INITIAL_LENGTH); - - PL_DHashTableInit(&mNavigatorNames, &hash_table_ops, - sizeof(GlobalNameMapEntry), - GLOBALNAME_HASHTABLE_INITIAL_LENGTH); - - mIsInitialized = true; - RegisterWeakMemoryReporter(this); nsresult rv = NS_OK; diff --git a/dom/base/nsScriptNameSpaceManager.h b/dom/base/nsScriptNameSpaceManager.h index a1a93178f07..fe909a2930b 100644 --- a/dom/base/nsScriptNameSpaceManager.h +++ b/dom/base/nsScriptNameSpaceManager.h @@ -234,10 +234,8 @@ private: nsGlobalNameStruct* LookupNameInternal(const nsAString& aName, const char16_t **aClassName = nullptr); - PLDHashTable mGlobalNames; - PLDHashTable mNavigatorNames; - - bool mIsInitialized; + PLDHashTable2 mGlobalNames; + PLDHashTable2 mNavigatorNames; }; #endif /* nsScriptNameSpaceManager_h__ */ From 42ce5ac7f438f6a64f228fe34ef499cf55c587a0 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 5 May 2015 16:11:43 -0700 Subject: [PATCH 12/89] Bug 1166598 (part 2) - Use PLDHashTable2 in SpanningCellSorter. r=froydnj,dbaron. --- layout/tables/SpanningCellSorter.cpp | 19 +++---------------- layout/tables/SpanningCellSorter.h | 2 +- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/layout/tables/SpanningCellSorter.cpp b/layout/tables/SpanningCellSorter.cpp index 63bc700bc4c..a2334e35c82 100644 --- a/layout/tables/SpanningCellSorter.cpp +++ b/layout/tables/SpanningCellSorter.cpp @@ -16,6 +16,7 @@ SpanningCellSorter::SpanningCellSorter() : mState(ADDING) + , mHashTable(&HashTableOps, sizeof(HashTableEntry)) , mSortedHashTable(nullptr) { memset(mArray, 0, sizeof(mArray)); @@ -23,9 +24,6 @@ SpanningCellSorter::SpanningCellSorter() SpanningCellSorter::~SpanningCellSorter() { - if (mHashTable.IsInitialized()) { - PL_DHashTableFinish(&mHashTable); - } delete [] mSortedHashTable; } @@ -70,10 +68,6 @@ SpanningCellSorter::AddCell(int32_t aColSpan, int32_t aRow, int32_t aCol) i->next = mArray[index]; mArray[index] = i; } else { - if (!mHashTable.IsInitialized()) { - PL_DHashTableInit(&mHashTable, &HashTableOps, - sizeof(HashTableEntry)); - } HashTableEntry *entry = static_cast (PL_DHashTableAdd(&mHashTable, NS_INT32_TO_PTR(aColSpan), fallible)); @@ -146,14 +140,9 @@ SpanningCellSorter::GetNext(int32_t *aColSpan) /* prepare to enumerate the hash */ mState = ENUMERATING_HASH; mEnumerationIndex = 0; - if (mHashTable.IsInitialized()) { + if (mHashTable.EntryCount() > 0) { HashTableEntry **sh = new HashTableEntry*[mHashTable.EntryCount()]; - if (!sh) { - // give up - mState = DONE; - return nullptr; - } PL_DHashTableEnumerate(&mHashTable, FillSortedArray, sh); NS_QuickSort(sh, mHashTable.EntryCount(), sizeof(sh[0]), SortArray, nullptr); @@ -161,9 +150,7 @@ SpanningCellSorter::GetNext(int32_t *aColSpan) } /* fall through */ case ENUMERATING_HASH: - if (mHashTable.IsInitialized() && - mEnumerationIndex < mHashTable.EntryCount()) - { + if (mEnumerationIndex < mHashTable.EntryCount()) { Item *result = mSortedHashTable[mEnumerationIndex]->mItems; *aColSpan = mSortedHashTable[mEnumerationIndex]->mColSpan; NS_ASSERTION(result, "holes in hash table"); diff --git a/layout/tables/SpanningCellSorter.h b/layout/tables/SpanningCellSorter.h index de8c2143452..6e46cbc62e2 100644 --- a/layout/tables/SpanningCellSorter.h +++ b/layout/tables/SpanningCellSorter.h @@ -62,7 +62,7 @@ private: return SpanToIndex(aSpan) < ARRAY_SIZE; } - PLDHashTable mHashTable; + PLDHashTable2 mHashTable; struct HashTableEntry : public PLDHashEntryHdr { int32_t mColSpan; Item *mItems; From ff4aa2cbfdc8ae58682ad4ea175fc40dfe5e081f Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 5 May 2015 18:23:39 -0700 Subject: [PATCH 13/89] Bug 1166598 (part 3) - Use PLDHashTable2 in nsCommandParams. r=froydnj. --- embedding/components/build/nsEmbeddingModule.cpp | 2 +- .../components/commandhandler/nsCommandParams.cpp | 10 +--------- embedding/components/commandhandler/nsCommandParams.h | 4 +--- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/embedding/components/build/nsEmbeddingModule.cpp b/embedding/components/build/nsEmbeddingModule.cpp index 9ab26fbd75f..48351ec3adc 100644 --- a/embedding/components/build/nsEmbeddingModule.cpp +++ b/embedding/components/build/nsEmbeddingModule.cpp @@ -31,7 +31,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebBrowserFind) NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebBrowserPersist) NS_GENERIC_FACTORY_CONSTRUCTOR(nsControllerCommandTable) NS_GENERIC_FACTORY_CONSTRUCTOR(nsCommandManager) -NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsCommandParams, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsCommandParams) NS_GENERIC_FACTORY_CONSTRUCTOR(nsControllerCommandGroup) NS_GENERIC_FACTORY_CONSTRUCTOR(nsBaseCommandController) diff --git a/embedding/components/commandhandler/nsCommandParams.cpp b/embedding/components/commandhandler/nsCommandParams.cpp index addb9711466..9aaf94744ff 100644 --- a/embedding/components/commandhandler/nsCommandParams.cpp +++ b/embedding/components/commandhandler/nsCommandParams.cpp @@ -25,20 +25,12 @@ const PLDHashTableOps nsCommandParams::sHashOps = NS_IMPL_ISUPPORTS(nsCommandParams, nsICommandParams) nsCommandParams::nsCommandParams() + : mValuesHash(&sHashOps, sizeof(HashEntry), 2) { - // init the hash table later } nsCommandParams::~nsCommandParams() { - PL_DHashTableFinish(&mValuesHash); -} - -nsresult -nsCommandParams::Init() -{ - PL_DHashTableInit(&mValuesHash, &sHashOps, sizeof(HashEntry), 2); - return NS_OK; } NS_IMETHODIMP diff --git a/embedding/components/commandhandler/nsCommandParams.h b/embedding/components/commandhandler/nsCommandParams.h index 32a590f0a08..ee85a5f1817 100644 --- a/embedding/components/commandhandler/nsCommandParams.h +++ b/embedding/components/commandhandler/nsCommandParams.h @@ -20,8 +20,6 @@ public: NS_DECL_ISUPPORTS NS_DECL_NSICOMMANDPARAMS - nsresult Init(); - protected: virtual ~nsCommandParams(); @@ -126,7 +124,7 @@ protected: static void HashClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry); - PLDHashTable mValuesHash; + PLDHashTable2 mValuesHash; static const PLDHashTableOps sHashOps; }; From 0c7ca68cbede9323065d61ad617a8fa7f2642998 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 5 May 2015 18:30:27 -0700 Subject: [PATCH 14/89] Bug 1166598 (part 4) - Use PLDHashTable2 in RDFServiceImpl. r=froydnj. --- rdf/base/nsRDFService.cpp | 31 ++++++------------------------- rdf/base/nsRDFService.h | 10 +++++----- 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/rdf/base/nsRDFService.cpp b/rdf/base/nsRDFService.cpp index a4ee099f222..1540d888efd 100644 --- a/rdf/base/nsRDFService.cpp +++ b/rdf/base/nsRDFService.cpp @@ -719,7 +719,12 @@ RDFServiceImpl* RDFServiceImpl::gRDFService; RDFServiceImpl::RDFServiceImpl() - : mNamedDataSources(nullptr) + : mNamedDataSources(nullptr) + , mResources(&gResourceTableOps, sizeof(ResourceHashEntry)) + , mLiterals(&gLiteralTableOps, sizeof(LiteralHashEntry)) + , mInts(&gIntTableOps, sizeof(IntHashEntry)) + , mDates(&gDateTableOps, sizeof(DateHashEntry)) + , mBlobs(&gBlobTableOps, sizeof(BlobHashEntry)) { gRDFService = this; } @@ -738,17 +743,6 @@ RDFServiceImpl::Init() if (! mNamedDataSources) return NS_ERROR_OUT_OF_MEMORY; - PL_DHashTableInit(&mResources, &gResourceTableOps, - sizeof(ResourceHashEntry)); - - PL_DHashTableInit(&mLiterals, &gLiteralTableOps, sizeof(LiteralHashEntry)); - - PL_DHashTableInit(&mInts, &gIntTableOps, sizeof(IntHashEntry)); - - PL_DHashTableInit(&mDates, &gDateTableOps, sizeof(DateHashEntry)); - - PL_DHashTableInit(&mBlobs, &gBlobTableOps, sizeof(BlobHashEntry)); - mDefaultResourceFactory = do_GetClassObject(kRDFDefaultResourceCID, &rv); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get default resource factory"); if (NS_FAILED(rv)) return rv; @@ -766,16 +760,6 @@ RDFServiceImpl::~RDFServiceImpl() PL_HashTableDestroy(mNamedDataSources); mNamedDataSources = nullptr; } - if (mResources.IsInitialized()) - PL_DHashTableFinish(&mResources); - if (mLiterals.IsInitialized()) - PL_DHashTableFinish(&mLiterals); - if (mInts.IsInitialized()) - PL_DHashTableFinish(&mInts); - if (mDates.IsInitialized()) - PL_DHashTableFinish(&mDates); - if (mBlobs.IsInitialized()) - PL_DHashTableFinish(&mBlobs); gRDFService = nullptr; } @@ -793,9 +777,6 @@ RDFServiceImpl::CreateSingleton(nsISupports* aOuter, } nsRefPtr serv = new RDFServiceImpl(); - if (!serv) - return NS_ERROR_OUT_OF_MEMORY; - nsresult rv = serv->Init(); if (NS_FAILED(rv)) return rv; diff --git a/rdf/base/nsRDFService.h b/rdf/base/nsRDFService.h index 8dad600975a..0c193351641 100644 --- a/rdf/base/nsRDFService.h +++ b/rdf/base/nsRDFService.h @@ -38,11 +38,11 @@ class RDFServiceImpl final : public nsIRDFService, { protected: PLHashTable* mNamedDataSources; - PLDHashTable mResources; - PLDHashTable mLiterals; - PLDHashTable mInts; - PLDHashTable mDates; - PLDHashTable mBlobs; + PLDHashTable2 mResources; + PLDHashTable2 mLiterals; + PLDHashTable2 mInts; + PLDHashTable2 mDates; + PLDHashTable2 mBlobs; nsAutoCString mLastURIPrefix; nsCOMPtr mLastFactory; From 6f0071f22914d56ae68b487bbee4955993a90093 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 5 May 2015 18:39:20 -0700 Subject: [PATCH 15/89] Bug 1166598 (part 5) - Use PLDHashTable2 in InMemoryDataSource. r=froydnj. --- rdf/base/nsInMemoryDataSource.cpp | 36 +++++++++---------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/rdf/base/nsInMemoryDataSource.cpp b/rdf/base/nsInMemoryDataSource.cpp index 9b1b6fced17..7414408e37d 100644 --- a/rdf/base/nsInMemoryDataSource.cpp +++ b/rdf/base/nsInMemoryDataSource.cpp @@ -250,8 +250,8 @@ protected: // nsIRDFResource object per unique URI). The value of an entry is // an Assertion struct, which is a linked list of (subject // predicate object) triples. - PLDHashTable mForwardArcs; - PLDHashTable mReverseArcs; + PLDHashTable2 mForwardArcs; + PLDHashTable2 mReverseArcs; nsCOMArray mObservers; uint32_t mNumObservers; @@ -286,7 +286,6 @@ protected: explicit InMemoryDataSource(nsISupports* aOuter); virtual ~InMemoryDataSource(); - nsresult Init(); friend nsresult NS_NewRDFInMemoryDataSource(nsISupports* aOuter, const nsIID& aIID, void** aResult); @@ -752,16 +751,11 @@ NS_NewRDFInMemoryDataSource(nsISupports* aOuter, const nsIID& aIID, void** aResu } InMemoryDataSource* datasource = new InMemoryDataSource(aOuter); - if (! datasource) - return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(datasource); - nsresult rv = datasource->Init(); - if (NS_SUCCEEDED(rv)) { - datasource->fAggregated.AddRef(); - rv = datasource->AggregatedQueryInterface(aIID, aResult); // This'll AddRef() - datasource->fAggregated.Release(); - } + datasource->fAggregated.AddRef(); + nsresult rv = datasource->AggregatedQueryInterface(aIID, aResult); // This'll AddRef() + datasource->fAggregated.Release(); NS_RELEASE(datasource); return rv; @@ -769,25 +763,18 @@ NS_NewRDFInMemoryDataSource(nsISupports* aOuter, const nsIID& aIID, void** aResu InMemoryDataSource::InMemoryDataSource(nsISupports* aOuter) - : mNumObservers(0), mReadCount(0) + : mForwardArcs(PL_DHashGetStubOps(), sizeof(Entry)) + , mReverseArcs(PL_DHashGetStubOps(), sizeof(Entry)) + , mNumObservers(0) + , mReadCount(0) { NS_INIT_AGGREGATED(aOuter); mPropagateChanges = true; MOZ_COUNT_CTOR(InMemoryDataSource); -} - - -nsresult -InMemoryDataSource::Init() -{ - PL_DHashTableInit(&mForwardArcs, PL_DHashGetStubOps(), sizeof(Entry)); - PL_DHashTableInit(&mReverseArcs, PL_DHashGetStubOps(), sizeof(Entry)); if (! gLog) gLog = PR_NewLogModule("InMemoryDataSource"); - - return NS_OK; } @@ -798,16 +785,13 @@ InMemoryDataSource::~InMemoryDataSource() fprintf(stdout, "%d - RDF: InMemoryDataSource\n", gInstanceCount); #endif - if (mForwardArcs.IsInitialized()) { + if (mForwardArcs.EntryCount() > 0) { // This'll release all of the Assertion objects that are // associated with this data source. We only need to do this // for the forward arcs, because the reverse arcs table // indexes the exact same set of resources. PL_DHashTableEnumerate(&mForwardArcs, DeleteForwardArcsEntry, nullptr); - PL_DHashTableFinish(&mForwardArcs); } - if (mReverseArcs.IsInitialized()) - PL_DHashTableFinish(&mReverseArcs); MOZ_LOG(gLog, PR_LOG_NOTICE, ("InMemoryDataSource(%p): destroyed.", this)); From 16479c956046e22ce3af6e40ca4f445792dd015b Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 5 May 2015 21:13:53 -0700 Subject: [PATCH 16/89] Bug 1166598 (part 6) - Clean up nsStaticCaseInsensitiveNameTable. r=froydnj. This patch simplifies nsStaticCaseInsensitiveNameTable greatly by taking advantage of the following observations. - |new| is infallible, so |new nsStaticCaseInsensitiveNameTable()| calls don't need their return value checked. - nsStaticCaseInsensitiveNameTable::Init() checks if any of the added entries differ only in case, so the callers of that function don't need to do the same check. - nsStaticCaseInsensitiveNameTable::Init() never returns false because moz_xmalloc() is infallible. (Its callers never check the return value anyway.) So it can be merged into the constructor. And ~nsStaticCaseInsensitiveNameTable() need not null-check |mNameArray|. - PLDHashTable now has an initializing constructor and destructor, so these can be used in nsStaticCaseInsensitiveNameTable, thus avoiding manual PLD_HashTable{Init,Finish}() calls. --- gfx/src/nsColor.cpp | 17 ++--------- layout/style/nsCSSKeywords.cpp | 25 ++++++--------- layout/style/nsCSSProps.cpp | 21 +++++-------- xpcom/ds/nsStaticNameTable.cpp | 56 ++++++++++++---------------------- xpcom/ds/nsStaticNameTable.h | 5 ++- 5 files changed, 40 insertions(+), 84 deletions(-) diff --git a/gfx/src/nsColor.cpp b/gfx/src/nsColor.cpp index ea59ba64da2..4b8613e5057 100644 --- a/gfx/src/nsColor.cpp +++ b/gfx/src/nsColor.cpp @@ -39,21 +39,8 @@ void nsColorNames::AddRefTable(void) { NS_ASSERTION(!gColorTable, "pre existing array!"); if (!gColorTable) { - gColorTable = new nsStaticCaseInsensitiveNameTable(); - if (gColorTable) { -#ifdef DEBUG - { - // let's verify the table... - for (uint32_t index = 0; index < eColorName_COUNT; ++index) { - nsAutoCString temp1(kColorNames[index]); - nsAutoCString temp2(kColorNames[index]); - ToLowerCase(temp1); - NS_ASSERTION(temp1.Equals(temp2), "upper case char in table"); - } - } -#endif - gColorTable->Init(kColorNames, eColorName_COUNT); - } + gColorTable = + new nsStaticCaseInsensitiveNameTable(kColorNames, eColorName_COUNT); } } diff --git a/layout/style/nsCSSKeywords.cpp b/layout/style/nsCSSKeywords.cpp index c61b0f65715..8dd362e7c2b 100644 --- a/layout/style/nsCSSKeywords.cpp +++ b/layout/style/nsCSSKeywords.cpp @@ -27,24 +27,17 @@ nsCSSKeywords::AddRefTable(void) { if (0 == gKeywordTableRefCount++) { NS_ASSERTION(!gKeywordTable, "pre existing array!"); - gKeywordTable = new nsStaticCaseInsensitiveNameTable(); - if (gKeywordTable) { + gKeywordTable = + new nsStaticCaseInsensitiveNameTable(kCSSRawKeywords, eCSSKeyword_COUNT); #ifdef DEBUG - { - // let's verify the table... - int32_t index = 0; - for (; index < eCSSKeyword_COUNT && kCSSRawKeywords[index]; ++index) { - nsAutoCString temp1(kCSSRawKeywords[index]); - nsAutoCString temp2(kCSSRawKeywords[index]); - ToLowerCase(temp1); - NS_ASSERTION(temp1.Equals(temp2), "upper case char in table"); - NS_ASSERTION(-1 == temp1.FindChar('_'), "underscore char in table"); - } - NS_ASSERTION(index == eCSSKeyword_COUNT, "kCSSRawKeywords and eCSSKeyword_COUNT are out of sync"); - } -#endif - gKeywordTable->Init(kCSSRawKeywords, eCSSKeyword_COUNT); + // Partially verify the entries. + int32_t index = 0; + for (; index < eCSSKeyword_COUNT && kCSSRawKeywords[index]; ++index) { + nsAutoCString temp(kCSSRawKeywords[index]); + NS_ASSERTION(-1 == temp.FindChar('_'), "underscore char in table"); } + NS_ASSERTION(index == eCSSKeyword_COUNT, "kCSSRawKeywords and eCSSKeyword_COUNT are out of sync"); +#endif } } diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index 7a6ccd3aa3d..8527020fc36 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -134,22 +134,15 @@ static nsCSSProperty gAliases[eCSSAliasCount != 0 ? eCSSAliasCount : 1] = { nsStaticCaseInsensitiveNameTable* CreateStaticTable(const char* const aRawTable[], int32_t aLength) { - auto table = new nsStaticCaseInsensitiveNameTable(); - if (table) { + auto table = new nsStaticCaseInsensitiveNameTable(aRawTable, aLength); #ifdef DEBUG - // let's verify the table... - for (int32_t index = 0; index < aLength; ++index) { - nsAutoCString temp1(aRawTable[index]); - nsAutoCString temp2(aRawTable[index]); - ToLowerCase(temp1); - MOZ_ASSERT(temp1.Equals(temp2), - "upper case char in case insensitive name table"); - MOZ_ASSERT(-1 == temp1.FindChar('_'), - "underscore char in case insensitive name table"); - } -#endif - table->Init(aRawTable, aLength); + // Partially verify the entries. + for (int32_t index = 0; index < aLength; ++index) { + nsAutoCString temp(aRawTable[index]); + MOZ_ASSERT(-1 == temp.FindChar('_'), + "underscore char in case insensitive name table"); } +#endif return table; } diff --git a/xpcom/ds/nsStaticNameTable.cpp b/xpcom/ds/nsStaticNameTable.cpp index 1c655baba05..e872da54e7b 100644 --- a/xpcom/ds/nsStaticNameTable.cpp +++ b/xpcom/ds/nsStaticNameTable.cpp @@ -101,45 +101,20 @@ static const struct PLDHashTableOps nametable_CaseInsensitiveHashTableOps = { nullptr, }; -nsStaticCaseInsensitiveNameTable::nsStaticCaseInsensitiveNameTable() +nsStaticCaseInsensitiveNameTable::nsStaticCaseInsensitiveNameTable( + const char* const aNames[], int32_t aLength) : mNameArray(nullptr) + , mNameTable(&nametable_CaseInsensitiveHashTableOps, + sizeof(NameTableEntry), aLength) , mNullStr("") { MOZ_COUNT_CTOR(nsStaticCaseInsensitiveNameTable); -} -nsStaticCaseInsensitiveNameTable::~nsStaticCaseInsensitiveNameTable() -{ - if (mNameArray) { - // manually call the destructor on placement-new'ed objects - for (uint32_t index = 0; index < mNameTable.EntryCount(); index++) { - mNameArray[index].~nsDependentCString(); - } - free((void*)mNameArray); - } - if (mNameTable.IsInitialized()) { - PL_DHashTableFinish(&mNameTable); - } - MOZ_COUNT_DTOR(nsStaticCaseInsensitiveNameTable); -} - -bool -nsStaticCaseInsensitiveNameTable::Init(const char* const aNames[], - int32_t aLength) -{ - NS_ASSERTION(!mNameArray, "double Init"); - NS_ASSERTION(!mNameTable.IsInitialized(), "double Init"); - NS_ASSERTION(aNames, "null name table"); - NS_ASSERTION(aLength, "0 length"); + MOZ_ASSERT(aNames, "null name table"); + MOZ_ASSERT(aLength, "0 length"); mNameArray = (nsDependentCString*) moz_xmalloc(aLength * sizeof(nsDependentCString)); - if (!mNameArray) { - return false; - } - - PL_DHashTableInit(&mNameTable, &nametable_CaseInsensitiveHashTableOps, - sizeof(NameTableEntry), aLength); for (int32_t index = 0; index < aLength; ++index) { const char* raw = aNames[index]; @@ -149,10 +124,10 @@ nsStaticCaseInsensitiveNameTable::Init(const char* const aNames[], nsAutoCString temp1(raw); nsDependentCString temp2(raw); ToLowerCase(temp1); - NS_ASSERTION(temp1.Equals(temp2), "upper case char in table"); - NS_ASSERTION(nsCRT::IsAscii(raw), - "non-ascii string in table -- " - "case-insensitive matching won't work right"); + MOZ_ASSERT(temp1.Equals(temp2), "upper case char in table"); + MOZ_ASSERT(nsCRT::IsAscii(raw), + "non-ascii string in table -- " + "case-insensitive matching won't work right"); } #endif // use placement-new to initialize the string object @@ -175,7 +150,16 @@ nsStaticCaseInsensitiveNameTable::Init(const char* const aNames[], #ifdef DEBUG PL_DHashMarkTableImmutable(&mNameTable); #endif - return true; +} + +nsStaticCaseInsensitiveNameTable::~nsStaticCaseInsensitiveNameTable() +{ + // manually call the destructor on placement-new'ed objects + for (uint32_t index = 0; index < mNameTable.EntryCount(); index++) { + mNameArray[index].~nsDependentCString(); + } + free((void*)mNameArray); + MOZ_COUNT_DTOR(nsStaticCaseInsensitiveNameTable); } int32_t diff --git a/xpcom/ds/nsStaticNameTable.h b/xpcom/ds/nsStaticNameTable.h index 5bcc7039074..f447ab7018d 100644 --- a/xpcom/ds/nsStaticNameTable.h +++ b/xpcom/ds/nsStaticNameTable.h @@ -33,17 +33,16 @@ class nsStaticCaseInsensitiveNameTable public: enum { NOT_FOUND = -1 }; - bool Init(const char* const aNames[], int32_t aLength); int32_t Lookup(const nsACString& aName); int32_t Lookup(const nsAString& aName); const nsAFlatCString& GetStringValue(int32_t aIndex); - nsStaticCaseInsensitiveNameTable(); + nsStaticCaseInsensitiveNameTable(const char* const aNames[], int32_t aLength); ~nsStaticCaseInsensitiveNameTable(); private: nsDependentCString* mNameArray; - PLDHashTable mNameTable; + PLDHashTable2 mNameTable; nsDependentCString mNullStr; }; From 4cdc3dfb3c36fe1854495adf57edf4a9be0b62b3 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 12 May 2015 17:33:44 -0700 Subject: [PATCH 17/89] Bug 1166598 (part 7) - Use PLDHashTable2 in nsLoadGroup. r=froydnj. Things to note: - nsLoadGroupConnectionInfo and its methods were just moved higher up in the file so it could be referenced in nsLoadGroup's constructor; none of that code has been changed; - ~nsLoadGroup() is made public because NS_GENERIC_AGGREGATED_CONSTRUCTOR requires it to be. --- netwerk/base/nsLoadGroup.cpp | 159 ++++++++++++++++------------------ netwerk/base/nsLoadGroup.h | 7 +- netwerk/build/nsNetModule.cpp | 2 +- 3 files changed, 76 insertions(+), 92 deletions(-) diff --git a/netwerk/base/nsLoadGroup.cpp b/netwerk/base/nsLoadGroup.cpp index c0b71507623..cf9e4aa07d8 100644 --- a/netwerk/base/nsLoadGroup.cpp +++ b/netwerk/base/nsLoadGroup.cpp @@ -43,6 +43,69 @@ static PRLogModuleInfo* gLoadGroupLog = nullptr; #undef LOG #define LOG(args) MOZ_LOG(gLoadGroupLog, PR_LOG_DEBUG, args) +//////////////////////////////////////////////////////////////////////////////// +// nsLoadGroupConnectionInfo + +class nsLoadGroupConnectionInfo final : public nsILoadGroupConnectionInfo +{ + ~nsLoadGroupConnectionInfo() {} + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSILOADGROUPCONNECTIONINFO + + nsLoadGroupConnectionInfo(); +private: + Atomic mBlockingTransactionCount; + nsAutoPtr mSpdyCache; +}; + +NS_IMPL_ISUPPORTS(nsLoadGroupConnectionInfo, nsILoadGroupConnectionInfo) + +nsLoadGroupConnectionInfo::nsLoadGroupConnectionInfo() + : mBlockingTransactionCount(0) +{ +} + +NS_IMETHODIMP +nsLoadGroupConnectionInfo::GetBlockingTransactionCount(uint32_t *aBlockingTransactionCount) +{ + NS_ENSURE_ARG_POINTER(aBlockingTransactionCount); + *aBlockingTransactionCount = mBlockingTransactionCount; + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroupConnectionInfo::AddBlockingTransaction() +{ + mBlockingTransactionCount++; + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroupConnectionInfo::RemoveBlockingTransaction(uint32_t *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + mBlockingTransactionCount--; + *_retval = mBlockingTransactionCount; + return NS_OK; +} + +/* [noscript] attribute SpdyPushCachePtr spdyPushCache; */ +NS_IMETHODIMP +nsLoadGroupConnectionInfo::GetSpdyPushCache(mozilla::net::SpdyPushCache **aSpdyPushCache) +{ + *aSpdyPushCache = mSpdyCache.get(); + return NS_OK; +} + +NS_IMETHODIMP +nsLoadGroupConnectionInfo::SetSpdyPushCache(mozilla::net::SpdyPushCache *aSpdyPushCache) +{ + mSpdyCache = aSpdyPushCache; + return NS_OK; +} + //////////////////////////////////////////////////////////////////////////////// class RequestMapEntry : public PLDHashEntryHdr @@ -86,6 +149,14 @@ RequestHashInitEntry(PLDHashEntryHdr *entry, const void *key) new (entry) RequestMapEntry(request); } +static const PLDHashTableOps sRequestHashOps = +{ + PL_DHashVoidPtrKeyStub, + RequestHashMatchEntry, + PL_DHashMoveEntryStub, + RequestHashClearEntry, + RequestHashInitEntry +}; static void RescheduleRequest(nsIRequest *aRequest, int32_t delta) @@ -106,11 +177,12 @@ RescheduleRequests(PLDHashTable *table, PLDHashEntryHdr *hdr, return PL_DHASH_NEXT; } - nsLoadGroup::nsLoadGroup(nsISupports* outer) : mForegroundCount(0) , mLoadFlags(LOAD_NORMAL) , mDefaultLoadFlags(0) + , mConnectionInfo(new nsLoadGroupConnectionInfo()) + , mRequests(&sRequestHashOps, sizeof(RequestMapEntry)) , mStatus(NS_OK) , mPriority(PRIORITY_NORMAL) , mIsCanceling(false) @@ -133,10 +205,6 @@ nsLoadGroup::~nsLoadGroup() DebugOnly rv = Cancel(NS_BINDING_ABORTED); NS_ASSERTION(NS_SUCCEEDED(rv), "Cancel failed"); - if (mRequests.IsInitialized()) { - PL_DHashTableFinish(&mRequests); - } - mDefaultLoadRequest = 0; LOG(("LOADGROUP [%x]: Destroyed.\n", this)); @@ -1055,85 +1123,4 @@ nsresult nsLoadGroup::MergeLoadFlags(nsIRequest *aRequest, nsLoadFlags& outFlags return rv; } -// nsLoadGroupConnectionInfo - -class nsLoadGroupConnectionInfo final : public nsILoadGroupConnectionInfo -{ - ~nsLoadGroupConnectionInfo() {} - -public: - NS_DECL_THREADSAFE_ISUPPORTS - NS_DECL_NSILOADGROUPCONNECTIONINFO - - nsLoadGroupConnectionInfo(); -private: - Atomic mBlockingTransactionCount; - nsAutoPtr mSpdyCache; -}; - -NS_IMPL_ISUPPORTS(nsLoadGroupConnectionInfo, nsILoadGroupConnectionInfo) - -nsLoadGroupConnectionInfo::nsLoadGroupConnectionInfo() - : mBlockingTransactionCount(0) -{ -} - -NS_IMETHODIMP -nsLoadGroupConnectionInfo::GetBlockingTransactionCount(uint32_t *aBlockingTransactionCount) -{ - NS_ENSURE_ARG_POINTER(aBlockingTransactionCount); - *aBlockingTransactionCount = mBlockingTransactionCount; - return NS_OK; -} - -NS_IMETHODIMP -nsLoadGroupConnectionInfo::AddBlockingTransaction() -{ - mBlockingTransactionCount++; - return NS_OK; -} - -NS_IMETHODIMP -nsLoadGroupConnectionInfo::RemoveBlockingTransaction(uint32_t *_retval) -{ - NS_ENSURE_ARG_POINTER(_retval); - mBlockingTransactionCount--; - *_retval = mBlockingTransactionCount; - return NS_OK; -} - -/* [noscript] attribute SpdyPushCachePtr spdyPushCache; */ -NS_IMETHODIMP -nsLoadGroupConnectionInfo::GetSpdyPushCache(mozilla::net::SpdyPushCache **aSpdyPushCache) -{ - *aSpdyPushCache = mSpdyCache.get(); - return NS_OK; -} - -NS_IMETHODIMP -nsLoadGroupConnectionInfo::SetSpdyPushCache(mozilla::net::SpdyPushCache *aSpdyPushCache) -{ - mSpdyCache = aSpdyPushCache; - return NS_OK; -} - -nsresult nsLoadGroup::Init() -{ - static const PLDHashTableOps hash_table_ops = - { - PL_DHashVoidPtrKeyStub, - RequestHashMatchEntry, - PL_DHashMoveEntryStub, - RequestHashClearEntry, - RequestHashInitEntry - }; - - PL_DHashTableInit(&mRequests, &hash_table_ops, - sizeof(RequestMapEntry)); - - mConnectionInfo = new nsLoadGroupConnectionInfo(); - - return NS_OK; -} - #undef LOG diff --git a/netwerk/base/nsLoadGroup.h b/netwerk/base/nsLoadGroup.h index 28d4c29a9c5..b89380bee21 100644 --- a/netwerk/base/nsLoadGroup.h +++ b/netwerk/base/nsLoadGroup.h @@ -50,12 +50,9 @@ public: // nsLoadGroup methods: explicit nsLoadGroup(nsISupports* outer); - - nsresult Init(); - -protected: virtual ~nsLoadGroup(); +protected: nsresult MergeLoadFlags(nsIRequest *aRequest, nsLoadFlags& flags); private: @@ -73,7 +70,7 @@ protected: nsCOMPtr mConnectionInfo; nsCOMPtr mDefaultLoadRequest; - PLDHashTable mRequests; + PLDHashTable2 mRequests; nsWeakPtr mObserver; nsWeakPtr mParentLoadGroup; diff --git a/netwerk/build/nsNetModule.cpp b/netwerk/build/nsNetModule.cpp index af6a3b827fa..63ee99de43d 100644 --- a/netwerk/build/nsNetModule.cpp +++ b/netwerk/build/nsNetModule.cpp @@ -113,7 +113,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsSafeFileOutputStream) NS_GENERIC_FACTORY_CONSTRUCTOR(nsFileStream) -NS_GENERIC_AGGREGATED_CONSTRUCTOR_INIT(nsLoadGroup, Init) +NS_GENERIC_AGGREGATED_CONSTRUCTOR(nsLoadGroup) #include "ArrayBufferInputStream.h" NS_GENERIC_FACTORY_CONSTRUCTOR(ArrayBufferInputStream) From adcd365f5e225eb0f017433394fbc30069644a1c Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 12 May 2015 17:33:45 -0700 Subject: [PATCH 18/89] Bug 1166598 (part 8) - Use PLDHashTable2 in nsHostResolver. r=froydnj. --- netwerk/dns/nsHostResolver.cpp | 6 +----- netwerk/dns/nsHostResolver.h | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/netwerk/dns/nsHostResolver.cpp b/netwerk/dns/nsHostResolver.cpp index f173e9b2c3a..9b7cfde4882 100644 --- a/netwerk/dns/nsHostResolver.cpp +++ b/netwerk/dns/nsHostResolver.cpp @@ -552,6 +552,7 @@ nsHostResolver::nsHostResolver(uint32_t maxCacheEntries, , mNumIdleThreads(0) , mThreadCount(0) , mActiveAnyThreadCount(0) + , mDB(&gHostDB_ops, sizeof(nsHostDBEnt), 0) , mEvictionQSize(0) , mPendingCount(0) , mShutdown(true) @@ -568,7 +569,6 @@ nsHostResolver::nsHostResolver(uint32_t maxCacheEntries, nsHostResolver::~nsHostResolver() { - PL_DHashTableFinish(&mDB); } nsresult @@ -578,8 +578,6 @@ nsHostResolver::Init() return NS_ERROR_FAILURE; } - PL_DHashTableInit(&mDB, &gHostDB_ops, sizeof(nsHostDBEnt), 0); - mShutdown = false; #if TTL_AVAILABLE @@ -1496,8 +1494,6 @@ nsHostResolver::Create(uint32_t maxCacheEntries, nsHostResolver *res = new nsHostResolver(maxCacheEntries, defaultCacheEntryLifetime, defaultGracePeriod); - if (!res) - return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(res); nsresult rv = res->Init(); diff --git a/netwerk/dns/nsHostResolver.h b/netwerk/dns/nsHostResolver.h index 88b4d618fd6..6c7587d4e55 100644 --- a/netwerk/dns/nsHostResolver.h +++ b/netwerk/dns/nsHostResolver.h @@ -345,7 +345,7 @@ private: uint32_t mNumIdleThreads; uint32_t mThreadCount; uint32_t mActiveAnyThreadCount; - PLDHashTable mDB; + PLDHashTable2 mDB; PRCList mHighQ; PRCList mMediumQ; PRCList mLowQ; From 23f2b638f415a3a6b074f81af7b5eeedc823703c Mon Sep 17 00:00:00 2001 From: JW Wang Date: Sun, 10 May 2015 12:07:14 +0800 Subject: [PATCH 19/89] Bug 1163489 - Remove dependency on MediaDecoder from DecodedStream. r=roc. --- dom/media/DecodedStream.cpp | 62 +++++++++++++++++++++---------------- dom/media/DecodedStream.h | 15 ++++----- dom/media/MediaDecoder.cpp | 5 +-- 3 files changed, 47 insertions(+), 35 deletions(-) diff --git a/dom/media/DecodedStream.cpp b/dom/media/DecodedStream.cpp index 2a976c7730e..891361345f9 100644 --- a/dom/media/DecodedStream.cpp +++ b/dom/media/DecodedStream.cpp @@ -6,16 +6,15 @@ #include "DecodedStream.h" #include "MediaStreamGraph.h" -#include "MediaDecoder.h" +#include "mozilla/ReentrantMonitor.h" namespace mozilla { class DecodedStreamGraphListener : public MediaStreamListener { typedef MediaStreamListener::MediaStreamGraphEvent MediaStreamGraphEvent; public: - DecodedStreamGraphListener(MediaStream* aStream, DecodedStreamData* aData) - : mData(aData) - , mMutex("DecodedStreamGraphListener::mMutex") + explicit DecodedStreamGraphListener(MediaStream* aStream) + : mMutex("DecodedStreamGraphListener::mMutex") , mStream(aStream) , mLastOutputTime(aStream->StreamTimeToMicroseconds(aStream->GetCurrentTime())) , mStreamFinishedOnMainThread(false) {} @@ -52,7 +51,6 @@ public: void Forget() { MOZ_ASSERT(NS_IsMainThread()); - mData = nullptr; MutexAutoLock lock(mMutex); mStream = nullptr; } @@ -64,9 +62,6 @@ public: } private: - // Main thread only - DecodedStreamData* mData; - Mutex mMutex; // Members below are protected by mMutex. nsRefPtr mStream; @@ -74,14 +69,12 @@ private: bool mStreamFinishedOnMainThread; }; -DecodedStreamData::DecodedStreamData(MediaDecoder* aDecoder, - int64_t aInitialTime, +DecodedStreamData::DecodedStreamData(int64_t aInitialTime, SourceMediaStream* aStream) : mAudioFramesWritten(0) , mInitialTime(aInitialTime) , mNextVideoTime(-1) , mNextAudioTime(-1) - , mDecoder(aDecoder) , mStreamInitialized(false) , mHaveSentFinish(false) , mHaveSentFinishAudio(false) @@ -91,7 +84,7 @@ DecodedStreamData::DecodedStreamData(MediaDecoder* aDecoder, , mHaveBlockedForStateMachineNotPlaying(false) , mEOSVideoCompensation(false) { - mListener = new DecodedStreamGraphListener(mStream, this); + mListener = new DecodedStreamGraphListener(mStream); mStream->AddListener(mListener); } @@ -116,8 +109,8 @@ DecodedStreamData::GetClock() const class OutputStreamListener : public MediaStreamListener { typedef MediaStreamListener::MediaStreamGraphEvent MediaStreamGraphEvent; public: - OutputStreamListener(MediaDecoder* aDecoder, MediaStream* aStream) - : mDecoder(aDecoder), mStream(aStream) {} + OutputStreamListener(DecodedStream* aDecodedStream, MediaStream* aStream) + : mDecodedStream(aDecodedStream), mStream(aStream) {} void NotifyEvent(MediaStreamGraph* aGraph, MediaStreamGraphEvent event) override { @@ -131,22 +124,22 @@ public: void Forget() { MOZ_ASSERT(NS_IsMainThread()); - mDecoder = nullptr; + mDecodedStream = nullptr; } private: void DoNotifyFinished() { MOZ_ASSERT(NS_IsMainThread()); - if (!mDecoder) { + if (!mDecodedStream) { return; } // Remove the finished stream so it won't block the decoded stream. - ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); - auto& streams = mDecoder->OutputStreams(); - // Don't read |mDecoder| in the loop since removing the element will lead - // to ~OutputStreamData() which will call Forget() to reset |mDecoder|. + ReentrantMonitorAutoEnter mon(mDecodedStream->GetReentrantMonitor()); + auto& streams = mDecodedStream->OutputStreams(); + // Don't read |mDecodedStream| in the loop since removing the element will lead + // to ~OutputStreamData() which will call Forget() to reset |mDecodedStream|. for (int32_t i = streams.Length() - 1; i >= 0; --i) { auto& os = streams[i]; MediaStream* p = os.mStream.get(); @@ -162,7 +155,7 @@ private: } // Main thread only - MediaDecoder* mDecoder; + DecodedStream* mDecodedStream; nsRefPtr mStream; }; @@ -177,37 +170,54 @@ OutputStreamData::~OutputStreamData() } void -OutputStreamData::Init(MediaDecoder* aDecoder, ProcessedMediaStream* aStream) +OutputStreamData::Init(DecodedStream* aDecodedStream, ProcessedMediaStream* aStream) { mStream = aStream; - mListener = new OutputStreamListener(aDecoder, aStream); + mListener = new OutputStreamListener(aDecodedStream, aStream); aStream->AddListener(mListener); } +DecodedStream::DecodedStream(ReentrantMonitor& aMonitor) + : mMonitor(aMonitor) +{ + // +} + DecodedStreamData* DecodedStream::GetData() { + GetReentrantMonitor().AssertCurrentThreadIn(); return mData.get(); } void DecodedStream::DestroyData() { + MOZ_ASSERT(NS_IsMainThread()); + GetReentrantMonitor().AssertCurrentThreadIn(); mData = nullptr; } void -DecodedStream::RecreateData(MediaDecoder* aDecoder, int64_t aInitialTime, - SourceMediaStream* aStream) +DecodedStream::RecreateData(int64_t aInitialTime, SourceMediaStream* aStream) { + MOZ_ASSERT(NS_IsMainThread()); + GetReentrantMonitor().AssertCurrentThreadIn(); MOZ_ASSERT(!mData); - mData.reset(new DecodedStreamData(aDecoder, aInitialTime, aStream)); + mData.reset(new DecodedStreamData(aInitialTime, aStream)); } nsTArray& DecodedStream::OutputStreams() { + GetReentrantMonitor().AssertCurrentThreadIn(); return mOutputStreams; } +ReentrantMonitor& +DecodedStream::GetReentrantMonitor() +{ + return mMonitor; +} + } // namespace mozilla diff --git a/dom/media/DecodedStream.h b/dom/media/DecodedStream.h index 74dd93c89f7..f6ce4503ba8 100644 --- a/dom/media/DecodedStream.h +++ b/dom/media/DecodedStream.h @@ -14,13 +14,14 @@ namespace mozilla { -class MediaDecoder; class MediaInputPort; class SourceMediaStream; class ProcessedMediaStream; +class DecodedStream; class DecodedStreamGraphListener; class OutputStreamData; class OutputStreamListener; +class ReentrantMonitor; namespace layers { class Image; @@ -36,8 +37,7 @@ class Image; */ class DecodedStreamData { public: - DecodedStreamData(MediaDecoder* aDecoder, int64_t aInitialTime, - SourceMediaStream* aStream); + DecodedStreamData(int64_t aInitialTime, SourceMediaStream* aStream); ~DecodedStreamData(); bool IsFinished() const; int64_t GetClock() const; @@ -55,7 +55,6 @@ public: // to the output stream. int64_t mNextVideoTime; // microseconds int64_t mNextAudioTime; // microseconds - MediaDecoder* mDecoder; // The last video image sent to the stream. Useful if we need to replicate // the image. nsRefPtr mLastVideoImage; @@ -89,7 +88,7 @@ public: // to work. OutputStreamData(); ~OutputStreamData(); - void Init(MediaDecoder* aDecoder, ProcessedMediaStream* aStream); + void Init(DecodedStream* aDecodedStream, ProcessedMediaStream* aStream); nsRefPtr mStream; // mPort connects DecodedStreamData::mStream to our mStream. nsRefPtr mPort; @@ -98,16 +97,18 @@ public: class DecodedStream { public: + explicit DecodedStream(ReentrantMonitor& aMonitor); DecodedStreamData* GetData(); void DestroyData(); - void RecreateData(MediaDecoder* aDecoder, int64_t aInitialTime, - SourceMediaStream* aStream); + void RecreateData(int64_t aInitialTime, SourceMediaStream* aStream); nsTArray& OutputStreams(); + ReentrantMonitor& GetReentrantMonitor(); private: UniquePtr mData; // Data about MediaStreams that are being fed by the decoder. nsTArray mOutputStreams; + ReentrantMonitor& mMonitor; }; } // namespace mozilla diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index 041417ba2c6..3d05d8ab528 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -384,7 +384,7 @@ void MediaDecoder::RecreateDecodedStream(int64_t aStartTimeUSecs, } DestroyDecodedStream(); - mDecodedStream.RecreateData(this, aStartTimeUSecs, aGraph->CreateSourceStream(nullptr)); + mDecodedStream.RecreateData(aStartTimeUSecs, aGraph->CreateSourceStream(nullptr)); // Note that the delay between removing ports in DestroyDecodedStream // and adding new ones won't cause a glitch since all graph operations @@ -419,7 +419,7 @@ void MediaDecoder::AddOutputStream(ProcessedMediaStream* aStream, RecreateDecodedStream(mLogicalPosition, aStream->Graph()); } OutputStreamData* os = OutputStreams().AppendElement(); - os->Init(this, aStream); + os->Init(&mDecodedStream, aStream); ConnectDecodedStreamToOutputStream(os); if (aFinishWhenEnded) { // Ensure that aStream finishes the moment mDecodedStream does. @@ -483,6 +483,7 @@ MediaDecoder::MediaDecoder() : mMediaSeekable(true), mSameOriginMedia(false), mReentrantMonitor("media.decoder"), + mDecodedStream(mReentrantMonitor), mPlayState(AbstractThread::MainThread(), PLAY_STATE_LOADING, "MediaDecoder::mPlayState (Canonical)"), mNextState(AbstractThread::MainThread(), PLAY_STATE_PAUSED, From 2be891d110b78cd3b7683c5d4f4f2d1d73638b64 Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Mon, 25 May 2015 14:31:02 +1200 Subject: [PATCH 20/89] back out 780f3d9c7cc3..afb28a3157b3 for test failures (bug 1167045, bug 1162364, bug 1166107) CLOSED TREE --- dom/media/platforms/SharedDecoderManager.cpp | 13 +++++++------ dom/media/platforms/SharedDecoderManager.h | 1 - dom/media/platforms/wmf/MFTDecoder.cpp | 18 +++++++++--------- dom/media/platforms/wmf/WMFAudioMFTManager.cpp | 6 ------ dom/media/platforms/wmf/WMFVideoMFTManager.cpp | 6 ------ 5 files changed, 16 insertions(+), 28 deletions(-) diff --git a/dom/media/platforms/SharedDecoderManager.cpp b/dom/media/platforms/SharedDecoderManager.cpp index 05c896a14b7..6203973a699 100644 --- a/dom/media/platforms/SharedDecoderManager.cpp +++ b/dom/media/platforms/SharedDecoderManager.cpp @@ -74,7 +74,7 @@ SharedDecoderManager::SharedDecoderManager() , mActiveProxy(nullptr) , mActiveCallback(nullptr) , mWaitForInternalDrain(false) - , mMonitor("SharedDecoderManager") + , mMonitor("SharedDecoderProxy") , mDecoderReleasedResources(false) { MOZ_ASSERT(NS_IsMainThread()); // taskqueue must be created on main thread. @@ -163,15 +163,16 @@ void SharedDecoderManager::SetIdle(MediaDataDecoder* aProxy) { if (aProxy && mActiveProxy == aProxy) { + MonitorAutoLock mon(mMonitor); + mWaitForInternalDrain = true; + nsresult rv; { - MonitorAutoLock mon(mMonitor); - mWaitForInternalDrain = true; - // We don't want to hold the lock while calling Drain() as some + // We don't want to hold the lock while calling Drain() has some // platform implementations call DrainComplete() immediately. + MonitorAutoUnlock mon(mMonitor); + rv = mActiveProxy->Drain(); } - nsresult rv = mActiveProxy->Drain(); if (NS_SUCCEEDED(rv)) { - MonitorAutoLock mon(mMonitor); while (mWaitForInternalDrain) { mon.Wait(); } diff --git a/dom/media/platforms/SharedDecoderManager.h b/dom/media/platforms/SharedDecoderManager.h index 7c283085662..c6b33d8fec5 100644 --- a/dom/media/platforms/SharedDecoderManager.h +++ b/dom/media/platforms/SharedDecoderManager.h @@ -56,7 +56,6 @@ private: SharedDecoderProxy* mActiveProxy; MediaDataDecoderCallback* mActiveCallback; nsAutoPtr mCallback; - // access protected by mMonitor bool mWaitForInternalDrain; Monitor mMonitor; bool mDecoderReleasedResources; diff --git a/dom/media/platforms/wmf/MFTDecoder.cpp b/dom/media/platforms/wmf/MFTDecoder.cpp index 7a77c980b38..5fac9fdba1f 100644 --- a/dom/media/platforms/wmf/MFTDecoder.cpp +++ b/dom/media/platforms/wmf/MFTDecoder.cpp @@ -201,15 +201,15 @@ MFTDecoder::Output(RefPtr* aOutput) MFT_OUTPUT_DATA_BUFFER output = {0}; + bool providedSample = false; RefPtr sample; - if (!mMFTProvidesOutputSamples) { - if (*aOutput) { - output.pSample = *aOutput; - } else { - hr = CreateOutputSample(&sample); - NS_ENSURE_TRUE(SUCCEEDED(hr), hr); - output.pSample = sample; - } + if (*aOutput) { + output.pSample = *aOutput; + providedSample = true; + } else if (!mMFTProvidesOutputSamples) { + hr = CreateOutputSample(&sample); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + output.pSample = sample; } DWORD status = 0; @@ -248,7 +248,7 @@ MFTDecoder::Output(RefPtr* aOutput) } *aOutput = output.pSample; // AddRefs - if (mMFTProvidesOutputSamples) { + if (mMFTProvidesOutputSamples && !providedSample) { // If the MFT is providing samples, we must release the sample here. // Typically only the H.264 MFT provides samples when using DXVA, // and it always re-uses the same sample, so if we don't release it diff --git a/dom/media/platforms/wmf/WMFAudioMFTManager.cpp b/dom/media/platforms/wmf/WMFAudioMFTManager.cpp index 9968a68fe69..64bfab608cf 100644 --- a/dom/media/platforms/wmf/WMFAudioMFTManager.cpp +++ b/dom/media/platforms/wmf/WMFAudioMFTManager.cpp @@ -205,7 +205,6 @@ WMFAudioMFTManager::Output(int64_t aStreamOffset, aOutData = nullptr; RefPtr sample; HRESULT hr; - int typeChangeCount = 0; while (true) { hr = mDecoder->Output(&sample); if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { @@ -214,11 +213,6 @@ WMFAudioMFTManager::Output(int64_t aStreamOffset, if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { hr = UpdateOutputType(); NS_ENSURE_TRUE(SUCCEEDED(hr), hr); - // Catch infinite loops, but some decoders perform at least 2 stream - // changes on consecutive calls, so be permissive. - // 100 is arbitrarily > 2. - NS_ENSURE_TRUE(typeChangeCount < 100, MF_E_TRANSFORM_STREAM_CHANGE); - ++typeChangeCount; continue; } break; diff --git a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp index d50e0d7a749..c345fa8bb4d 100644 --- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp +++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp @@ -482,7 +482,6 @@ WMFVideoMFTManager::Output(int64_t aStreamOffset, RefPtr sample; HRESULT hr; aOutData = nullptr; - int typeChangeCount = 0; // Loop until we decode a sample, or an unexpected error that we can't // handle occurs. @@ -498,12 +497,7 @@ WMFVideoMFTManager::Output(int64_t aStreamOffset, MOZ_ASSERT(!sample); hr = ConfigureVideoFrameGeometry(); NS_ENSURE_TRUE(SUCCEEDED(hr), hr); - // Catch infinite loops, but some decoders perform at least 2 stream - // changes on consecutive calls, so be permissive. - // 100 is arbitrarily > 2. - NS_ENSURE_TRUE(typeChangeCount < 100, MF_E_TRANSFORM_STREAM_CHANGE); // Loop back and try decoding again... - ++typeChangeCount; continue; } if (SUCCEEDED(hr)) { From cd5471d5c91f5344f50c369ed09b609919dac122 Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 15:09:15 +1000 Subject: [PATCH 21/89] Bug 1159171: Part2. Fix pts calculations in older LibAV version. r=edwin LibAV v0.8 always set the pts to 0 while the dts is set to the pts. --- .../platforms/ffmpeg/FFmpegH264Decoder.cpp | 30 ++++++++++++++++++- .../platforms/ffmpeg/FFmpegH264Decoder.h | 1 + 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/dom/media/platforms/ffmpeg/FFmpegH264Decoder.cpp b/dom/media/platforms/ffmpeg/FFmpegH264Decoder.cpp index ff31439af29..94735a86976 100644 --- a/dom/media/platforms/ffmpeg/FFmpegH264Decoder.cpp +++ b/dom/media/platforms/ffmpeg/FFmpegH264Decoder.cpp @@ -12,6 +12,7 @@ #include "MediaInfo.h" #include "FFmpegH264Decoder.h" +#include "FFmpegLog.h" #define GECKO_FRAME_TYPE 0x00093CC0 @@ -49,6 +50,20 @@ FFmpegH264Decoder::Init() return NS_OK; } +int64_t +FFmpegH264Decoder::GetPts(const AVPacket& packet) +{ +#if LIBAVCODEC_VERSION_MAJOR == 53 + if (mFrame->pkt_pts == 0) { + return mFrame->pkt_dts; + } else { + return mFrame->pkt_pts; + } +#else + return mFrame->pkt_pts; +#endif +} + FFmpegH264Decoder::DecodeResult FFmpegH264Decoder::DoDecodeFrame(MediaRawData* aSample) { @@ -68,10 +83,19 @@ FFmpegH264Decoder::DoDecodeFrame(MediaRawData* aSample) return DecodeResult::DECODE_ERROR; } + // Required with old version of FFmpeg/LibAV + mFrame->reordered_opaque = AV_NOPTS_VALUE; + int decoded; int bytesConsumed = avcodec_decode_video2(mCodecContext, mFrame, &decoded, &packet); + FFMPEG_LOG("DoDecodeFrame:decode_video: rv=%d decoded=%d " + "(Input: pts(%lld) dts(%lld) Output: pts(%lld) " + "opaque(%lld) pkt_pts(%lld) pkt_dts(%lld))", + bytesConsumed, decoded, packet.pts, packet.dts, mFrame->pts, + mFrame->reordered_opaque, mFrame->pkt_pts, mFrame->pkt_dts); + if (bytesConsumed < 0) { NS_WARNING("FFmpeg video decoder error."); mCallback->Error(); @@ -80,6 +104,10 @@ FFmpegH264Decoder::DoDecodeFrame(MediaRawData* aSample) // If we've decoded a frame then we need to output it if (decoded) { + int64_t pts = GetPts(packet); + FFMPEG_LOG("Got one frame output with pts=%lld opaque=%lld", + pts, mCodecContext->reordered_opaque); + VideoInfo info; info.mDisplay = nsIntSize(mDisplayWidth, mDisplayHeight); @@ -105,7 +133,7 @@ FFmpegH264Decoder::DoDecodeFrame(MediaRawData* aSample) nsRefPtr v = VideoData::Create(info, mImageContainer, aSample->mOffset, - mFrame->pkt_pts, + pts, aSample->mDuration, b, aSample->mKeyframe, diff --git a/dom/media/platforms/ffmpeg/FFmpegH264Decoder.h b/dom/media/platforms/ffmpeg/FFmpegH264Decoder.h index 82d5329b9a8..0ff1f5bed42 100644 --- a/dom/media/platforms/ffmpeg/FFmpegH264Decoder.h +++ b/dom/media/platforms/ffmpeg/FFmpegH264Decoder.h @@ -59,6 +59,7 @@ private: static int AllocateBufferCb(AVCodecContext* aCodecContext, AVFrame* aFrame); static void ReleaseBufferCb(AVCodecContext* aCodecContext, AVFrame* aFrame); + int64_t GetPts(const AVPacket& packet); MediaDataDecoderCallback* mCallback; nsRefPtr mImageContainer; From 50d43ee5fbec637b1912633a8e38bde215a4472f Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 15:09:15 +1000 Subject: [PATCH 22/89] Bug 1165938: Disable test on XP/Linux for intermittent failures. r=karlt --- .../meta/media-source/mediasource-redundant-seek.html.ini | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/testing/web-platform/meta/media-source/mediasource-redundant-seek.html.ini b/testing/web-platform/meta/media-source/mediasource-redundant-seek.html.ini index 0e6464a8947..17e9746b948 100644 --- a/testing/web-platform/meta/media-source/mediasource-redundant-seek.html.ini +++ b/testing/web-platform/meta/media-source/mediasource-redundant-seek.html.ini @@ -1,8 +1,9 @@ [mediasource-redundant-seek.html] type: testharness prefs: [media.mediasource.enabled:true, media.mediasource.whitelist:false] - expected: - if not debug and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT + disabled: + if (os == "win") and (version == "5.1.2600"): https://bugzilla.mozilla.org/show_bug.cgi?id=1165938 + if os == "linux": https://bugzilla.mozilla.org/show_bug.cgi?id=1165938 [Test redundant fully prebuffered seek] disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1154881 From 88bb1a8eb1bcfc40e20c6ca23e260c41ab62be39 Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 15:09:16 +1000 Subject: [PATCH 23/89] Bug 1166282: Have TimeIntervals and IntervalSet nsTArray_CopyChooser specialization. r=nfroyd --- dom/media/Intervals.h | 14 ++++++++++++++ dom/media/TimeUnits.h | 12 ++++++++++++ dom/media/gtest/TestIntervalSet.cpp | 9 +++++++++ 3 files changed, 35 insertions(+) diff --git a/dom/media/Intervals.h b/dom/media/Intervals.h index f5d747be1b0..8655b824578 100644 --- a/dom/media/Intervals.h +++ b/dom/media/Intervals.h @@ -11,6 +11,20 @@ #include "mozilla/TypeTraits.h" #include "nsTArray.h" +// Specialization for nsTArray CopyChooser. +namespace mozilla { +namespace media { +template +class IntervalSet; +} +} + +template +struct nsTArray_CopyChooser> +{ + typedef nsTArray_CopyWithConstructors> Type; +}; + namespace mozilla { namespace media { diff --git a/dom/media/TimeUnits.h b/dom/media/TimeUnits.h index f4784814157..6828221a1f7 100644 --- a/dom/media/TimeUnits.h +++ b/dom/media/TimeUnits.h @@ -12,6 +12,18 @@ #include "mozilla/FloatingPoint.h" #include "mozilla/dom/TimeRanges.h" +namespace mozilla { +namespace media { +class TimeIntervals; +} +} +// CopyChooser specalization for nsTArray +template<> +struct nsTArray_CopyChooser +{ + typedef nsTArray_CopyWithConstructors Type; +}; + namespace mozilla { namespace media { diff --git a/dom/media/gtest/TestIntervalSet.cpp b/dom/media/gtest/TestIntervalSet.cpp index 266ecb2fc02..08aac2041ee 100644 --- a/dom/media/gtest/TestIntervalSet.cpp +++ b/dom/media/gtest/TestIntervalSet.cpp @@ -709,3 +709,12 @@ TEST(IntervalSet, FooIntervalSet) EXPECT_EQ(Foo(), is[0].mStart); EXPECT_EQ(Foo(4,5,6), is[0].mEnd); } + +TEST(IntervalSet, StaticAssert) +{ + typedef media::IntervalSet IntIntervals; + media::Interval i; + + static_assert(mozilla::IsSame::Type, nsTArray_CopyWithConstructors>::value, "Must use copy constructor"); + static_assert(mozilla::IsSame::Type, nsTArray_CopyWithConstructors>::value, "Must use copy constructor"); +} From 7801d659451b8b01b29cba1bda8ce6331cf6bbea Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 15:09:16 +1000 Subject: [PATCH 24/89] Bug 1163227: Part1. Notify parent decoder that data got removed. r=mattwoodrow --- dom/media/mediasource/TrackBuffer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dom/media/mediasource/TrackBuffer.cpp b/dom/media/mediasource/TrackBuffer.cpp index 1138a1782ce..2b49e57253c 100644 --- a/dom/media/mediasource/TrackBuffer.cpp +++ b/dom/media/mediasource/TrackBuffer.cpp @@ -429,6 +429,10 @@ TrackBuffer::EvictData(double aPlaybackTime, } } + if (evicted) { + mParentDecoder->NotifyTimeRangesChanged(); + } + return evicted; } @@ -493,6 +497,7 @@ TrackBuffer::EvictBefore(double aTime) mInitializedDecoders[i]->GetResource()->EvictBefore(endOffset); } } + mParentDecoder->NotifyTimeRangesChanged(); } media::TimeIntervals @@ -1104,6 +1109,7 @@ TrackBuffer::RangeRemoval(media::TimeUnit aStart, RemoveEmptyDecoders(decoders); + mParentDecoder->NotifyTimeRangesChanged(); return true; } From da404110e2a7de7143117c4cf1b9b833422ec888 Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 15:09:16 +1000 Subject: [PATCH 25/89] Bug 1163227: Part2. Add MediaDecoderReader::NotifyDataRemoved method. r=cpearce --- dom/media/MediaDataDemuxer.h | 6 ++++++ dom/media/MediaDecoderReader.h | 2 ++ dom/media/MediaFormatReader.cpp | 27 ++++++++++++++++++++++++++- dom/media/MediaFormatReader.h | 1 + dom/media/fmp4/MP4Demuxer.cpp | 8 ++++++++ dom/media/fmp4/MP4Demuxer.h | 2 ++ 6 files changed, 45 insertions(+), 1 deletion(-) diff --git a/dom/media/MediaDataDemuxer.h b/dom/media/MediaDataDemuxer.h index e91d12b4116..9a966802e84 100644 --- a/dom/media/MediaDataDemuxer.h +++ b/dom/media/MediaDataDemuxer.h @@ -85,6 +85,12 @@ public: // data is available. virtual void NotifyDataArrived(uint32_t aLength, int64_t aOffset) { } + // Notifies the demuxer that the underlying resource has had data removed. + // The demuxer can use this mechanism to inform all track demuxers to update + // its TimeIntervals. + // This will be called should the demuxer be used with MediaSource. + virtual void NotifyDataRemoved() { } + protected: virtual ~MediaDataDemuxer() { diff --git a/dom/media/MediaDecoderReader.h b/dom/media/MediaDecoderReader.h index 510bd0ec64b..cfce796c871 100644 --- a/dom/media/MediaDecoderReader.h +++ b/dom/media/MediaDecoderReader.h @@ -247,6 +247,8 @@ public: // Only used by WebMReader and MediaOmxReader for now, so stub here rather // than in every reader than inherits from MediaDecoderReader. virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) {} + // Notify the reader that data from the resource was evicted (MediaSource only) + virtual void NotifyDataRemoved() {} virtual int64_t GetEvictionOffset(double aTime) { return -1; } virtual MediaQueue& AudioQueue() { return mAudioQueue; } diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp index d48d18f0088..a1f9c3d37be 100644 --- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -1334,7 +1334,11 @@ MediaFormatReader::NotifyDemuxer(uint32_t aLength, int64_t aOffset) return; } - mDemuxer->NotifyDataArrived(aLength, aOffset); + if (aLength || aOffset) { + mDemuxer->NotifyDataArrived(aLength, aOffset); + } else { + mDemuxer->NotifyDataRemoved(); + } if (HasVideo()) { mVideo.mReceivedNewData = true; ScheduleUpdate(TrackType::kVideoTrack); @@ -1355,6 +1359,7 @@ MediaFormatReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int6 } MOZ_ASSERT(mMainThreadDemuxer); + MOZ_ASSERT(aBuffer || aLength); mMainThreadDemuxer->NotifyDataArrived(aLength, aOffset); // Queue a task to notify our main demuxer. @@ -1365,4 +1370,24 @@ MediaFormatReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int6 GetTaskQueue()->Dispatch(task.forget()); } +void +MediaFormatReader::NotifyDataRemoved() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mInitDone) { + return; + } + + MOZ_ASSERT(mMainThreadDemuxer); + mMainThreadDemuxer->NotifyDataRemoved(); + + // Queue a task to notify our main demuxer. + RefPtr task = + NS_NewRunnableMethodWithArgs( + this, &MediaFormatReader::NotifyDemuxer, + 0, 0); + GetTaskQueue()->Dispatch(task.forget()); +} + } // namespace mozilla diff --git a/dom/media/MediaFormatReader.h b/dom/media/MediaFormatReader.h index 9895c7be788..6a6680dcc06 100644 --- a/dom/media/MediaFormatReader.h +++ b/dom/media/MediaFormatReader.h @@ -76,6 +76,7 @@ public: virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) override; + virtual void NotifyDataRemoved() override; virtual media::TimeIntervals GetBuffered() override; diff --git a/dom/media/fmp4/MP4Demuxer.cpp b/dom/media/fmp4/MP4Demuxer.cpp index 601f746c63c..ce0c1016094 100644 --- a/dom/media/fmp4/MP4Demuxer.cpp +++ b/dom/media/fmp4/MP4Demuxer.cpp @@ -114,6 +114,14 @@ MP4Demuxer::NotifyDataArrived(uint32_t aLength, int64_t aOffset) } } +void +MP4Demuxer::NotifyDataRemoved() +{ + for (uint32_t i = 0; i < mDemuxers.Length(); i++) { + mDemuxers[i]->NotifyDataArrived(); + } +} + UniquePtr MP4Demuxer::GetCrypto() { diff --git a/dom/media/fmp4/MP4Demuxer.h b/dom/media/fmp4/MP4Demuxer.h index 48b2a6bb24a..6e98b7f50d8 100644 --- a/dom/media/fmp4/MP4Demuxer.h +++ b/dom/media/fmp4/MP4Demuxer.h @@ -45,6 +45,8 @@ public: virtual void NotifyDataArrived(uint32_t aLength, int64_t aOffset) override; + virtual void NotifyDataRemoved() override; + private: friend class MP4TrackDemuxer; nsRefPtr mResource; From 7186ab86c18afd7f7c5718e92b229dd5382818eb Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 15:09:16 +1000 Subject: [PATCH 26/89] Bug 1163227: Part3. Notify mediasource sub-readers that data was evicted. r=mattwoodrow --- dom/media/mediasource/TrackBuffer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dom/media/mediasource/TrackBuffer.cpp b/dom/media/mediasource/TrackBuffer.cpp index 2b49e57253c..a49528850b0 100644 --- a/dom/media/mediasource/TrackBuffer.cpp +++ b/dom/media/mediasource/TrackBuffer.cpp @@ -347,6 +347,7 @@ TrackBuffer::EvictData(double aPlaybackTime, playbackOffset); } } + decoders[i]->GetReader()->NotifyDataRemoved(); } } @@ -368,6 +369,7 @@ TrackBuffer::EvictData(double aPlaybackTime, buffered.GetStart().ToSeconds(), buffered.GetEnd().ToSeconds(), aPlaybackTime, decoders[i]->GetResource()->GetSize()); toEvict -= decoders[i]->GetResource()->EvictAll(); + decoders[i]->GetReader()->NotifyDataRemoved(); } // Evict all data from future decoders, starting furthest away from @@ -413,6 +415,7 @@ TrackBuffer::EvictData(double aPlaybackTime, buffered.GetStart().ToSeconds(), buffered.GetEnd().ToSeconds(), aPlaybackTime, decoders[i]->GetResource()->GetSize()); toEvict -= decoders[i]->GetResource()->EvictAll(); + decoders[i]->GetReader()->NotifyDataRemoved(); } RemoveEmptyDecoders(decoders); @@ -495,6 +498,7 @@ TrackBuffer::EvictBefore(double aTime) MSE_DEBUG("decoder=%u offset=%lld", i, endOffset); mInitializedDecoders[i]->GetResource()->EvictBefore(endOffset); + mInitializedDecoders[i]->GetReader()->NotifyDataRemoved(); } } mParentDecoder->NotifyTimeRangesChanged(); @@ -1094,6 +1098,7 @@ TrackBuffer::RangeRemoval(media::TimeUnit aStart, decoders[i]->GetResource()->EvictData(offset, offset); } } + decoders[i]->GetReader()->NotifyDataRemoved(); } } else { // Only trimming existing buffers. @@ -1104,6 +1109,7 @@ TrackBuffer::RangeRemoval(media::TimeUnit aStart, } else { decoders[i]->Trim(aStart.ToMicroseconds()); } + decoders[i]->GetReader()->NotifyDataRemoved(); } } From f7e131b6e07125636629bfa757820cc2b17f7ef2 Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 15:09:16 +1000 Subject: [PATCH 27/89] Bug 1163227: Part4. Fix MP4TrackDemuxer eviction offset calculations. r=mattwoodrow --- dom/media/fmp4/MP4Demuxer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dom/media/fmp4/MP4Demuxer.cpp b/dom/media/fmp4/MP4Demuxer.cpp index ce0c1016094..001c5ba4809 100644 --- a/dom/media/fmp4/MP4Demuxer.cpp +++ b/dom/media/fmp4/MP4Demuxer.cpp @@ -298,7 +298,8 @@ int64_t MP4TrackDemuxer::GetEvictionOffset(media::TimeUnit aTime) { MonitorAutoLock mon(mMonitor); - return int64_t(mIndex->GetEvictionOffset(aTime.ToMicroseconds())); + uint64_t offset = mIndex->GetEvictionOffset(aTime.ToMicroseconds()); + return int64_t(offset == std::numeric_limits::max() ? 0 : offset); } media::TimeIntervals From c2737df50a39bbe0215a1835d0e97d4a825dac8b Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 15:09:16 +1000 Subject: [PATCH 28/89] Bug 1166836: Part1. Cache main thread buffered time range. r=cpearce GetBuffered() can be particularly slow under some circumstances. --- dom/media/MediaFormatReader.cpp | 45 ++++++++++++++++++++++++++++++--- dom/media/MediaFormatReader.h | 10 +++++++- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp index a1f9c3d37be..9519cefadc0 100644 --- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -69,6 +69,7 @@ MediaFormatReader::MediaFormatReader(AbstractMediaDecoder* aDecoder, , mInitDone(false) , mSeekable(false) , mIsEncrypted(false) + , mCachedTimeRangesStale(true) #if defined(READER_DORMANT_HEURISTIC) , mDormantEnabled(Preferences::GetBool("media.decoder.heuristic.dormant.enabled", false)) #endif @@ -1251,14 +1252,43 @@ MediaFormatReader::GetBuffered() media::TimeIntervals videoti; media::TimeIntervals audioti; + if (!mInitDone) { + return media::TimeIntervals(); + } if (NS_IsMainThread()) { + if (!mCachedTimeRangesStale) { + return mCachedTimeRanges; + } + MOZ_ASSERT(mMainThreadDemuxer); + if (!mDataRange.IsEmpty()) { + mMainThreadDemuxer->NotifyDataArrived(mDataRange.Length(), mDataRange.mStart); + } if (mVideoTrackDemuxer) { videoti = mVideoTrackDemuxer->GetBuffered(); } if (mAudioTrackDemuxer) { audioti = mAudioTrackDemuxer->GetBuffered(); } + if (HasAudio() && HasVideo()) { + mCachedTimeRanges = Intersection(Move(videoti), Move(audioti)); + } else if (HasAudio()) { + mCachedTimeRanges = Move(audioti); + } else if (HasVideo()) { + mCachedTimeRanges = Move(videoti); + } + mDataRange = ByteInterval(); + mCachedTimeRangesStale = false; + return mCachedTimeRanges; } else { + if (OnTaskQueue()) { + // Ensure we have up to date buffered time range. + if (HasVideo()) { + UpdateReceivedNewData(TrackType::kVideoTrack); + } + if (HasAudio()) { + UpdateReceivedNewData(TrackType::kAudioTrack); + } + } if (HasVideo()) { MonitorAutoLock lock(mVideo.mMonitor); videoti = mVideo.mTimeRanges; @@ -1354,14 +1384,18 @@ MediaFormatReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int6 { MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aBuffer || aLength); + if (mDataRange.IsEmpty()) { + mDataRange = ByteInterval(aOffset, aOffset + aLength); + } else { + mDataRange = mDataRange.Span(ByteInterval(aOffset, aOffset + aLength)); + } + mCachedTimeRangesStale = true; + if (!mInitDone) { return; } - MOZ_ASSERT(mMainThreadDemuxer); - MOZ_ASSERT(aBuffer || aLength); - mMainThreadDemuxer->NotifyDataArrived(aLength, aOffset); - // Queue a task to notify our main demuxer. RefPtr task = NS_NewRunnableMethodWithArgs( @@ -1375,6 +1409,9 @@ MediaFormatReader::NotifyDataRemoved() { MOZ_ASSERT(NS_IsMainThread()); + mDataRange = ByteInterval(); + mCachedTimeRangesStale = true; + if (!mInitDone) { return; } diff --git a/dom/media/MediaFormatReader.h b/dom/media/MediaFormatReader.h index 6a6680dcc06..66ec550eefc 100644 --- a/dom/media/MediaFormatReader.h +++ b/dom/media/MediaFormatReader.h @@ -26,6 +26,7 @@ namespace mozilla { class MediaFormatReader final : public MediaDecoderReader { typedef TrackInfo::TrackType TrackType; + typedef media::Interval ByteInterval; public: explicit MediaFormatReader(AbstractMediaDecoder* aDecoder, @@ -104,6 +105,9 @@ public: private: bool InitDemuxer(); + // Notify the demuxer that new data has been received. + // The next queued task calling GetBuffered() is guaranteed to have up to date + // buffered ranges. void NotifyDemuxer(uint32_t aLength, int64_t aOffset); void ReturnOutput(MediaData* aData, TrackType aTrack); @@ -305,7 +309,6 @@ private: DecoderDataWithPromise mVideo; // Returns true when the decoder for this track needs input. - // aDecoder.mMonitor must be locked. bool NeedInput(DecoderData& aDecoder); DecoderData& GetDecoderData(TrackType aTrack); @@ -390,9 +393,14 @@ private: nsRefPtr mSharedDecoderManager; // Main thread objects + // Those are only used to calculate our buffered range on the main thread. + // The cached buffered range is calculated one when required. nsRefPtr mMainThreadDemuxer; nsRefPtr mAudioTrackDemuxer; nsRefPtr mVideoTrackDemuxer; + ByteInterval mDataRange; + media::TimeIntervals mCachedTimeRanges; + bool mCachedTimeRangesStale; #if defined(READER_DORMANT_HEURISTIC) const bool mDormantEnabled; From 70c9952e4ac1190012d1185d7a2b74b9743b9b01 Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 15:09:16 +1000 Subject: [PATCH 29/89] Bug 1166836: Part2. Only rescans MP4's moof when necessary. r=cpearce --- dom/media/fmp4/MP4Demuxer.cpp | 37 ++++++++++++++++++++--------------- dom/media/fmp4/MP4Demuxer.h | 2 ++ 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/dom/media/fmp4/MP4Demuxer.cpp b/dom/media/fmp4/MP4Demuxer.cpp index 001c5ba4809..ada2c4c3d32 100644 --- a/dom/media/fmp4/MP4Demuxer.cpp +++ b/dom/media/fmp4/MP4Demuxer.cpp @@ -151,6 +151,7 @@ MP4TrackDemuxer::MP4TrackDemuxer(MP4Demuxer* aParent, uint32_t aTrackNumber) : mParent(aParent) , mStream(new mp4_demuxer::ResourceStream(mParent->mResource)) + , mNeedReIndex(true) , mMonitor("MP4TrackDemuxer") { mInfo = mParent->mMetadata->GetTrackInfo(aType, aTrackNumber); @@ -176,6 +177,23 @@ MP4TrackDemuxer::GetInfo() const return mInfo->Clone(); } +void +MP4TrackDemuxer::EnsureUpToDateIndex() +{ + if (!mNeedReIndex) { + return; + } + AutoPinned resource(mParent->mResource); + nsTArray byteRanges; + nsresult rv = resource->GetCachedRanges(byteRanges); + if (NS_FAILED(rv)) { + return; + } + MonitorAutoLock mon(mMonitor); + mIndex->UpdateMoofIndex(byteRanges); + mNeedReIndex = false; +} + nsRefPtr MP4TrackDemuxer::Seek(media::TimeUnit aTime) { @@ -305,6 +323,7 @@ MP4TrackDemuxer::GetEvictionOffset(media::TimeUnit aTime) media::TimeIntervals MP4TrackDemuxer::GetBuffered() { + EnsureUpToDateIndex(); AutoPinned resource(mParent->mResource); nsTArray byteRanges; nsresult rv = resource->GetCachedRanges(byteRanges); @@ -315,16 +334,9 @@ MP4TrackDemuxer::GetBuffered() nsTArray> timeRanges; MonitorAutoLock mon(mMonitor); - int64_t endComposition = - mIndex->GetEndCompositionIfBuffered(byteRanges); - mIndex->ConvertByteRangesToTimeRanges(byteRanges, &timeRanges); - if (endComposition) { - mp4_demuxer::Interval::SemiNormalAppend( - timeRanges, mp4_demuxer::Interval(endComposition, endComposition)); - } // convert timeRanges. - media::TimeIntervals ranges; + media::TimeIntervals ranges = media::TimeIntervals(); for (size_t i = 0; i < timeRanges.Length(); i++) { ranges += media::TimeInterval(media::TimeUnit::FromMicroseconds(timeRanges[i].start), @@ -336,14 +348,7 @@ MP4TrackDemuxer::GetBuffered() void MP4TrackDemuxer::NotifyDataArrived() { - AutoPinned resource(mParent->mResource); - nsTArray byteRanges; - nsresult rv = resource->GetCachedRanges(byteRanges); - if (NS_FAILED(rv)) { - return; - } - MonitorAutoLock mon(mMonitor); - mIndex->UpdateMoofIndex(byteRanges); + mNeedReIndex = true; } void diff --git a/dom/media/fmp4/MP4Demuxer.h b/dom/media/fmp4/MP4Demuxer.h index 6e98b7f50d8..dd8618f9d4e 100644 --- a/dom/media/fmp4/MP4Demuxer.h +++ b/dom/media/fmp4/MP4Demuxer.h @@ -85,6 +85,7 @@ private: friend class MP4Demuxer; void NotifyDataArrived(); void UpdateSamples(nsTArray>& aSamples); + void EnsureUpToDateIndex(); nsRefPtr mParent; nsRefPtr mIndex; UniquePtr mIterator; @@ -93,6 +94,7 @@ private: Maybe mNextKeyframeTime; // Queued samples extracted by the demuxer, but not yet returned. nsRefPtr mQueuedSample; + bool mNeedReIndex; // We do not actually need a monitor, however MoofParser will assert // if a monitor isn't held. From b37bb7571f24f10f90b963515940c1049f6dfd53 Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 15:09:16 +1000 Subject: [PATCH 30/89] Bug 1166836: Part3. Optimise most common addition to IntervalSet. r=mattwoodrow --- dom/media/Intervals.h | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/dom/media/Intervals.h b/dom/media/Intervals.h index 8655b824578..65fb80da032 100644 --- a/dom/media/Intervals.h +++ b/dom/media/Intervals.h @@ -212,6 +212,15 @@ public: mFuzz = aFuzz; } + // Returns true if the two intervals intersect with this being on the right + // of aOther + bool TouchesOnRight(const SelfType& aOther) const + { + return aOther.mStart <= mStart && + (mStart - mFuzz <= aOther.mEnd + aOther.mFuzz) && + (aOther.mStart - aOther.mFuzz <= mEnd + mFuzz); + } + T mStart; T mEnd; T mFuzz; @@ -305,10 +314,14 @@ public: mIntervals.AppendElement(aInterval); return *this; } + ElemType& last = mIntervals.LastElement(); + if (aInterval.TouchesOnRight(last)) { + last = last.Span(aInterval); + return *this; + } // Most of our actual usage is adding an interval that will be outside the // range. We can speed up normalization here. - if (aInterval.RightOf(mIntervals.LastElement()) && - !aInterval.Touches(mIntervals.LastElement())) { + if (aInterval.RightOf(last)) { mIntervals.AppendElement(aInterval); return *this; } From 2b44f4a3e52c59cb5e34a8bf9605acdaacd0b9b6 Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 15:09:16 +1000 Subject: [PATCH 31/89] Bug 1163227: Part5: Increase verbosity content for debugging purposes. r=cpearce Small reorganisation of Update(). With bug 1153295 fixed, remove need for StorensRefPtrPassByPtr. --- dom/media/MediaFormatReader.cpp | 65 +++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp index 9519cefadc0..21c061e31d7 100644 --- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -657,6 +657,8 @@ void MediaFormatReader::NotifyNewOutput(TrackType aTrack, MediaData* aSample) { MOZ_ASSERT(OnTaskQueue()); + LOGV("Received new sample time:%lld duration:%lld", + aSample->mTime, aSample->mDuration); auto& decoder = GetDecoderData(aTrack); if (!decoder.mOutputRequested) { LOG("MediaFormatReader produced output while flushing, discarding."); @@ -671,6 +673,7 @@ void MediaFormatReader::NotifyInputExhausted(TrackType aTrack) { MOZ_ASSERT(OnTaskQueue()); + LOGV("Decoder has requested more %s data", TrackTypeToStr(aTrack)); auto& decoder = GetDecoderData(aTrack); decoder.mInputExhausted = true; ScheduleUpdate(aTrack); @@ -693,6 +696,7 @@ void MediaFormatReader::NotifyError(TrackType aTrack) { MOZ_ASSERT(OnTaskQueue()); + LOGV("%s Decoding error", TrackTypeToStr(aTrack)); auto& decoder = GetDecoderData(aTrack); decoder.mError = true; ScheduleUpdate(aTrack); @@ -719,6 +723,7 @@ MediaFormatReader::NeedInput(DecoderData& aDecoder) return !aDecoder.mError && aDecoder.HasPromise() && + !aDecoder.mDemuxRequest.Exists() && aDecoder.mOutput.IsEmpty() && (aDecoder.mInputExhausted || !aDecoder.mQueuedSamples.IsEmpty() || aDecoder.mNumSamplesInput - aDecoder.mNumSamplesOutput < aDecoder.mDecodeAhead); @@ -767,13 +772,16 @@ MediaFormatReader::UpdateReceivedNewData(TrackType aTrack) decoder.mDemuxEOSServiced = false; } - if (!decoder.mError && decoder.HasWaitingPromise()) { + if (decoder.mError) { + return false; + } + if (decoder.HasWaitingPromise()) { MOZ_ASSERT(!decoder.HasPromise()); LOG("We have new data. Resolving WaitingPromise"); decoder.mWaitingPromise.Resolve(decoder.mType, __func__); return true; } - if (!decoder.mError && !mSeekPromise.IsEmpty()) { + if (!mSeekPromise.IsEmpty()) { MOZ_ASSERT(!decoder.HasPromise()); if (mVideoSeekRequest.Exists() || mAudioSeekRequest.Exists()) { // Already waiting for a seek to complete. Nothing more to do. @@ -791,20 +799,19 @@ MediaFormatReader::RequestDemuxSamples(TrackType aTrack) { MOZ_ASSERT(OnTaskQueue()); auto& decoder = GetDecoderData(aTrack); + MOZ_ASSERT(!decoder.mDemuxRequest.Exists()); if (!decoder.mQueuedSamples.IsEmpty()) { // No need to demux new samples. return; } - if (decoder.mDemuxRequest.Exists()) { - // We are already in the process of demuxing. - return; - } if (decoder.mDemuxEOS) { // Nothing left to demux. return; } + + LOGV("Requesting extra demux %s", TrackTypeToStr(aTrack)); if (aTrack == TrackInfo::kVideoTrack) { DoDemuxVideo(); } else { @@ -822,7 +829,7 @@ MediaFormatReader::DecodeDemuxedSamples(TrackType aTrack, if (decoder.mQueuedSamples.IsEmpty()) { return; } - + LOGV("Giving %s input to decoder", TrackTypeToStr(aTrack)); decoder.mOutputRequested = true; // Decode all our demuxed frames. @@ -846,9 +853,7 @@ MediaFormatReader::Update(TrackType aTrack) return; } - // Record number of frames decoded and parsed. Automatically update the - // stats counters using the AutoNotifyDecoded stack-based class. - AbstractMediaDecoder::AutoNotifyDecoded a(mDecoder); + LOGV("Processing update for %s", TrackTypeToStr(aTrack)); bool needInput = false; bool needOutput = false; @@ -856,6 +861,7 @@ MediaFormatReader::Update(TrackType aTrack) decoder.mUpdateScheduled = false; if (UpdateReceivedNewData(aTrack)) { + LOGV("Nothing more to do"); return; } @@ -872,13 +878,14 @@ MediaFormatReader::Update(TrackType aTrack) } } else if (decoder.mWaitingForData) { // Nothing more we can do at present. + LOGV("Still waiting for data."); return; } - if (NeedInput(decoder)) { - needInput = true; - decoder.mInputExhausted = false; - } + // Record number of frames decoded and parsed. Automatically update the + // stats counters using the AutoNotifyDecoded stack-based class. + AbstractMediaDecoder::AutoNotifyDecoded a(mDecoder); + if (aTrack == TrackInfo::kVideoTrack) { uint64_t delta = decoder.mNumSamplesOutput - mLastReportedNumDecodedFrames; @@ -899,18 +906,27 @@ MediaFormatReader::Update(TrackType aTrack) } } - LOGV("Update(%s) ni=%d no=%d", TrackTypeToStr(aTrack), needInput, needOutput); - if (decoder.mDemuxEOS && !decoder.mDemuxEOSServiced) { decoder.mOutputRequested = true; decoder.mDecoder->Drain(); decoder.mDemuxEOSServiced = true; - } - - if (!needInput) { + LOGV("Requesting decoder to drain"); return; } + if (!NeedInput(decoder)) { + LOGV("No need for additional input"); + return; + } + + needInput = true; + decoder.mInputExhausted = false; + + LOGV("Update(%s) ni=%d no=%d ie=%d, in:%d out:%d qs=%d", + TrackTypeToStr(aTrack), needInput, needOutput, decoder.mInputExhausted, + decoder.mNumSamplesInput, decoder.mNumSamplesOutput, + size_t(decoder.mSizeOfQueue)); + // Demux samples if we don't have some. RequestDemuxSamples(aTrack); // Decode all pending demuxed samples. @@ -923,6 +939,7 @@ MediaFormatReader::ReturnOutput(MediaData* aData, TrackType aTrack) auto& decoder = GetDecoderData(aTrack); MOZ_ASSERT(decoder.HasPromise()); if (decoder.mDiscontinuity) { + LOGV("Setting discontinuity flag"); decoder.mDiscontinuity = false; aData->mDiscontinuity = true; } @@ -942,6 +959,7 @@ MediaFormatReader::ReturnOutput(MediaData* aData, TrackType aTrack) } else if (aTrack == TrackInfo::kVideoTrack) { mVideo.mPromise.Resolve(static_cast(aData), __func__); } + LOG("Resolved data promise for %s", TrackTypeToStr(aTrack)); } size_t @@ -980,6 +998,7 @@ MediaFormatReader::ResetDecode() { MOZ_ASSERT(OnTaskQueue()); + LOGV(""); ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); mAudioSeekRequest.DisconnectIfExists(); @@ -1016,7 +1035,7 @@ MediaFormatReader::Output(TrackType aTrack, MediaData* aSample) } RefPtr task = - NS_NewRunnableMethodWithArgs>( + NS_NewRunnableMethodWithArgs( this, &MediaFormatReader::NotifyNewOutput, aTrack, aSample); GetTaskQueue()->Dispatch(task.forget()); } @@ -1168,6 +1187,7 @@ void MediaFormatReader::OnSeekFailed(TrackType aTrack, DemuxerFailureReason aResult) { MOZ_ASSERT(OnTaskQueue()); + LOGV("%s failure = %d", TrackTypeToStr(aTrack), aResult); if (aTrack == TrackType::kVideoTrack) { mVideoSeekRequest.Complete(); } else { @@ -1187,6 +1207,7 @@ void MediaFormatReader::DoVideoSeek() { MOZ_ASSERT(mPendingSeekTime.isSome()); + LOGV("Seeking video to %lld", mPendingSeekTime.ref().ToMicroseconds()); media::TimeUnit seekTime = mPendingSeekTime.ref(); mVideoSeekRequest.Begin(mVideo.mTrackDemuxer->Seek(seekTime) ->RefableThen(GetTaskQueue(), __func__, this, @@ -1198,6 +1219,7 @@ void MediaFormatReader::OnVideoSeekCompleted(media::TimeUnit aTime) { MOZ_ASSERT(OnTaskQueue()); + LOGV("Video seeked to %lld", aTime.ToMicroseconds()); mVideoSeekRequest.Complete(); if (HasAudio()) { @@ -1214,6 +1236,7 @@ void MediaFormatReader::DoAudioSeek() { MOZ_ASSERT(mPendingSeekTime.isSome()); + LOGV("Seeking audio to %lld", mPendingSeekTime.ref().ToMicroseconds()); media::TimeUnit seekTime = mPendingSeekTime.ref(); mAudioSeekRequest.Begin(mAudio.mTrackDemuxer->Seek(seekTime) ->RefableThen(GetTaskQueue(), __func__, this, @@ -1225,6 +1248,7 @@ void MediaFormatReader::OnAudioSeekCompleted(media::TimeUnit aTime) { MOZ_ASSERT(OnTaskQueue()); + LOGV("Audio seeked to %lld", aTime.ToMicroseconds()); mAudioSeekRequest.Complete(); mPendingSeekTime.reset(); mSeekPromise.Resolve(aTime.ToMicroseconds(), __func__); @@ -1360,6 +1384,7 @@ MediaFormatReader::NotifyDemuxer(uint32_t aLength, int64_t aOffset) { MOZ_ASSERT(OnTaskQueue()); + LOGV("aLength=%u, aOffset=%lld", aLength, aOffset); if (mShutdown) { return; } From 04ba19f4883908f17f511d977deeea965f97b974 Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 15:09:16 +1000 Subject: [PATCH 32/89] Bug 1163227: Part6. Always notify MediaSourceReader of new data on its own task queue. r=mattwoodrow --- dom/media/mediasource/TrackBuffer.cpp | 23 ++++++++++++++++------- dom/media/mediasource/TrackBuffer.h | 2 ++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/dom/media/mediasource/TrackBuffer.cpp b/dom/media/mediasource/TrackBuffer.cpp index a49528850b0..384c70d6d2a 100644 --- a/dom/media/mediasource/TrackBuffer.cpp +++ b/dom/media/mediasource/TrackBuffer.cpp @@ -241,7 +241,7 @@ TrackBuffer::AppendData(MediaLargeByteBuffer* aData, int64_t aTimestampOffset) // Tell our reader that we have more data to ensure that playback starts if // required when data is appended. - mParentDecoder->GetReader()->MaybeNotifyHaveData(); + NotifyTimeRangesChanged(); mInitializationPromise.Resolve(gotMedia, __func__); return p; @@ -263,11 +263,20 @@ TrackBuffer::AppendDataToCurrentResource(MediaLargeByteBuffer* aData, uint32_t a mCurrentDecoder->NotifyDataArrived(reinterpret_cast(aData->Elements()), aData->Length(), appendOffset); mParentDecoder->NotifyBytesDownloaded(); - mParentDecoder->NotifyTimeRangesChanged(); + NotifyTimeRangesChanged(); return true; } +void +TrackBuffer::NotifyTimeRangesChanged() +{ + RefPtr task = + NS_NewRunnableMethod(mParentDecoder->GetReader(), + &MediaSourceReader::NotifyTimeRangesChanged); + mParentDecoder->GetReader()->GetTaskQueue()->Dispatch(task.forget()); +} + class DecoderSorter { public: @@ -433,7 +442,7 @@ TrackBuffer::EvictData(double aPlaybackTime, } if (evicted) { - mParentDecoder->NotifyTimeRangesChanged(); + NotifyTimeRangesChanged(); } return evicted; @@ -501,7 +510,7 @@ TrackBuffer::EvictBefore(double aTime) mInitializedDecoders[i]->GetReader()->NotifyDataRemoved(); } } - mParentDecoder->NotifyTimeRangesChanged(); + NotifyTimeRangesChanged(); } media::TimeIntervals @@ -797,7 +806,7 @@ TrackBuffer::CompleteInitializeDecoder(SourceBufferDecoder* aDecoder) // Tell our reader that we have more data to ensure that playback starts if // required when data is appended. - mParentDecoder->GetReader()->MaybeNotifyHaveData(); + NotifyTimeRangesChanged(); MSE_DEBUG("Reader %p activated", aDecoder->GetReader()); @@ -839,7 +848,7 @@ TrackBuffer::RegisterDecoder(SourceBufferDecoder* aDecoder) return false; } mInitializedDecoders.AppendElement(aDecoder); - mParentDecoder->NotifyTimeRangesChanged(); + NotifyTimeRangesChanged(); return true; } @@ -1115,7 +1124,7 @@ TrackBuffer::RangeRemoval(media::TimeUnit aStart, RemoveEmptyDecoders(decoders); - mParentDecoder->NotifyTimeRangesChanged(); + NotifyTimeRangesChanged(); return true; } diff --git a/dom/media/mediasource/TrackBuffer.h b/dom/media/mediasource/TrackBuffer.h index 14358a84a19..2a0c6a46d8c 100644 --- a/dom/media/mediasource/TrackBuffer.h +++ b/dom/media/mediasource/TrackBuffer.h @@ -129,6 +129,8 @@ private: // data is appended to the current decoder's SourceBufferResource. bool AppendDataToCurrentResource(MediaLargeByteBuffer* aData, uint32_t aDuration /* microseconds */); + // Queue on the parent's decoder task queue a call to NotifyTimeRangesChanged. + void NotifyTimeRangesChanged(); // Queue execution of InitializeDecoder on mTaskQueue. bool QueueInitializeDecoder(SourceBufferDecoder* aDecoder); From 4dc728204b1ff23db624ba3140e03bc1c6876f62 Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 15:09:16 +1000 Subject: [PATCH 33/89] Bug 1163227: Part7. Never do blocking read if we don't have data. r=kentuckyfriedtakahe --- media/libstagefright/binding/Index.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/media/libstagefright/binding/Index.cpp b/media/libstagefright/binding/Index.cpp index ec14b6d027a..cb66baec61c 100644 --- a/media/libstagefright/binding/Index.cpp +++ b/media/libstagefright/binding/Index.cpp @@ -91,6 +91,13 @@ already_AddRefed SampleIterator::GetNext() return nullptr; } + int64_t length = std::numeric_limits::max(); + mIndex->mSource->Length(&length); + if (s->mByteRange.mEnd > length) { + // We don't have this complete sample. + return nullptr; + } + nsRefPtr sample = new MediaRawData(); sample->mTimecode= s->mDecodeTime; sample->mTime = s->mCompositionRange.start; From f7a6aa17d038181d1a0692e1668a08c111c07fcb Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 15:09:16 +1000 Subject: [PATCH 34/89] Bug 1163227: Part8. Fix incorrect seconds / microseconds conversion. r=mattwoodrow This led to incorrect data eviction in sourcebuffer --- dom/media/mediasource/TrackBuffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dom/media/mediasource/TrackBuffer.cpp b/dom/media/mediasource/TrackBuffer.cpp index 384c70d6d2a..659618626d0 100644 --- a/dom/media/mediasource/TrackBuffer.cpp +++ b/dom/media/mediasource/TrackBuffer.cpp @@ -346,7 +346,7 @@ TrackBuffer::EvictData(double aPlaybackTime, toEvict -= decoders[i]->GetResource()->EvictAll(); } else { int64_t playbackOffset = - decoders[i]->ConvertToByteOffset(time.ToMicroseconds()); + decoders[i]->ConvertToByteOffset(time.ToSeconds()); MSE_DEBUG("evicting some bufferedEnd=%f " "aPlaybackTime=%f time=%f, playbackOffset=%lld size=%lld", buffered.GetEnd().ToSeconds(), aPlaybackTime, time, From 076004e0b2191e92adb159c77184aefbbf2d9193 Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 15:09:16 +1000 Subject: [PATCH 35/89] Bug 1163227: Part9. Don't reset demuxer when skipping to next keyframe. r=cpearce --- dom/media/MediaFormatReader.cpp | 8 +++++--- dom/media/MediaFormatReader.h | 7 +++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp index 21c061e31d7..b9363c568a8 100644 --- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -108,6 +108,7 @@ MediaFormatReader::Shutdown() mAudio.mDecoder = nullptr; } if (mAudio.mTrackDemuxer) { + mAudio.ResetDemuxer(); mAudio.mTrackDemuxer->BreakCycles(); mAudio.mTrackDemuxer = nullptr; } @@ -124,6 +125,7 @@ MediaFormatReader::Shutdown() mVideo.mDecoder = nullptr; } if (mVideo.mTrackDemuxer) { + mVideo.ResetDemuxer(); mVideo.mTrackDemuxer->BreakCycles(); mVideo.mTrackDemuxer = nullptr; } @@ -510,6 +512,7 @@ MediaFormatReader::RequestVideoData(bool aSkipToNextKeyframe, MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise(), "No duplicate sample requests"); MOZ_DIAGNOSTIC_ASSERT(!mVideoSeekRequest.Exists()); MOZ_DIAGNOSTIC_ASSERT(!mAudioSeekRequest.Exists()); + MOZ_DIAGNOSTIC_ASSERT(!mSkipRequest.Exists(), "called mid-skipping"); MOZ_DIAGNOSTIC_ASSERT(!IsSeeking(), "called mid-seek"); LOGV("RequestVideoData(%d, %lld)", aSkipToNextKeyframe, aTimeThreshold); @@ -1014,9 +1017,11 @@ MediaFormatReader::ResetDecode() mPendingSeekTime.reset(); if (HasVideo()) { + mVideo.ResetDemuxer(); Flush(TrackInfo::kVideoTrack); } if (HasAudio()) { + mAudio.ResetDemuxer(); Flush(TrackInfo::kAudioTrack); } return MediaDecoderReader::ResetDecode(); @@ -1078,9 +1083,6 @@ MediaFormatReader::Flush(TrackType aTrack) return; } - // Clear demuxer related data. - decoder.mDemuxRequest.DisconnectIfExists(); - decoder.mTrackDemuxer->Reset(); decoder.mDecoder->Flush(); // Purge the current decoder's state. // ResetState clears mOutputRequested flag so that we ignore all output until diff --git a/dom/media/MediaFormatReader.h b/dom/media/MediaFormatReader.h index 66ec550eefc..49d3faf8c9e 100644 --- a/dom/media/MediaFormatReader.h +++ b/dom/media/MediaFormatReader.h @@ -256,6 +256,13 @@ private: virtual void RejectPromise(MediaDecoderReader::NotDecodedReason aReason, const char* aMethodName) = 0; + void ResetDemuxer() + { + // Clear demuxer related data. + mDemuxRequest.DisconnectIfExists(); + mTrackDemuxer->Reset(); + } + void ResetState() { MOZ_ASSERT(mOwner->OnTaskQueue()); From de9065833d1301c931cc6d86dd7d777b97a3cc63 Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 15:09:16 +1000 Subject: [PATCH 36/89] Bug 1163227: Part10. Properly recalculate next keyframe time after seeking. r=cpearce --- dom/media/fmp4/MP4Demuxer.cpp | 21 +++++++++++++++------ dom/media/fmp4/MP4Demuxer.h | 1 + 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/dom/media/fmp4/MP4Demuxer.cpp b/dom/media/fmp4/MP4Demuxer.cpp index ada2c4c3d32..db719d999b3 100644 --- a/dom/media/fmp4/MP4Demuxer.cpp +++ b/dom/media/fmp4/MP4Demuxer.cpp @@ -208,6 +208,7 @@ MP4TrackDemuxer::Seek(media::TimeUnit aTime) if (mQueuedSample) { seekTime = mQueuedSample->mTime; } + SetNextKeyFrameTime(); return SeekPromise::CreateAndResolve(media::TimeUnit::FromMicroseconds(seekTime), __func__); } @@ -240,6 +241,17 @@ MP4TrackDemuxer::GetSamples(int32_t aNumSamples) } } +void +MP4TrackDemuxer::SetNextKeyFrameTime() +{ + mNextKeyframeTime.reset(); + mp4_demuxer::Microseconds frameTime = mIterator->GetNextKeyframeTime(); + if (frameTime != -1) { + mNextKeyframeTime.emplace( + media::TimeUnit::FromMicroseconds(frameTime)); + } +} + void MP4TrackDemuxer::Reset() { @@ -247,6 +259,7 @@ MP4TrackDemuxer::Reset() // TODO, Seek to first frame available, which isn't always 0. MonitorAutoLock mon(mMonitor); mIterator->Seek(0); + SetNextKeyFrameTime(); } void @@ -266,12 +279,7 @@ MP4TrackDemuxer::UpdateSamples(nsTArray>& aSamples) } if (mNextKeyframeTime.isNothing() || aSamples.LastElement()->mTime >= mNextKeyframeTime.value().ToMicroseconds()) { - mNextKeyframeTime.reset(); - mp4_demuxer::Microseconds frameTime = mIterator->GetNextKeyframeTime(); - if (frameTime != -1) { - mNextKeyframeTime.emplace( - media::TimeUnit::FromMicroseconds(frameTime)); - } + SetNextKeyFrameTime(); } } @@ -304,6 +312,7 @@ MP4TrackDemuxer::SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) mQueuedSample = sample; } } + SetNextKeyFrameTime(); if (found) { return SkipAccessPointPromise::CreateAndResolve(parsed, __func__); } else { diff --git a/dom/media/fmp4/MP4Demuxer.h b/dom/media/fmp4/MP4Demuxer.h index dd8618f9d4e..57bbd744444 100644 --- a/dom/media/fmp4/MP4Demuxer.h +++ b/dom/media/fmp4/MP4Demuxer.h @@ -86,6 +86,7 @@ private: void NotifyDataArrived(); void UpdateSamples(nsTArray>& aSamples); void EnsureUpToDateIndex(); + void SetNextKeyFrameTime(); nsRefPtr mParent; nsRefPtr mIndex; UniquePtr mIterator; From 0f158b319fdde1f2c4c2d79ce17753205e764d51 Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Fri, 22 May 2015 15:23:00 +1200 Subject: [PATCH 37/89] bug 1162364 detect and abort MF_E_TRANSFORM_STREAM_CHANGE infinite loops r=cpearce --- dom/media/platforms/wmf/WMFAudioMFTManager.cpp | 6 ++++++ dom/media/platforms/wmf/WMFVideoMFTManager.cpp | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/dom/media/platforms/wmf/WMFAudioMFTManager.cpp b/dom/media/platforms/wmf/WMFAudioMFTManager.cpp index 64bfab608cf..9968a68fe69 100644 --- a/dom/media/platforms/wmf/WMFAudioMFTManager.cpp +++ b/dom/media/platforms/wmf/WMFAudioMFTManager.cpp @@ -205,6 +205,7 @@ WMFAudioMFTManager::Output(int64_t aStreamOffset, aOutData = nullptr; RefPtr sample; HRESULT hr; + int typeChangeCount = 0; while (true) { hr = mDecoder->Output(&sample); if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { @@ -213,6 +214,11 @@ WMFAudioMFTManager::Output(int64_t aStreamOffset, if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { hr = UpdateOutputType(); NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + // Catch infinite loops, but some decoders perform at least 2 stream + // changes on consecutive calls, so be permissive. + // 100 is arbitrarily > 2. + NS_ENSURE_TRUE(typeChangeCount < 100, MF_E_TRANSFORM_STREAM_CHANGE); + ++typeChangeCount; continue; } break; diff --git a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp index c345fa8bb4d..d50e0d7a749 100644 --- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp +++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp @@ -482,6 +482,7 @@ WMFVideoMFTManager::Output(int64_t aStreamOffset, RefPtr sample; HRESULT hr; aOutData = nullptr; + int typeChangeCount = 0; // Loop until we decode a sample, or an unexpected error that we can't // handle occurs. @@ -497,7 +498,12 @@ WMFVideoMFTManager::Output(int64_t aStreamOffset, MOZ_ASSERT(!sample); hr = ConfigureVideoFrameGeometry(); NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + // Catch infinite loops, but some decoders perform at least 2 stream + // changes on consecutive calls, so be permissive. + // 100 is arbitrarily > 2. + NS_ENSURE_TRUE(typeChangeCount < 100, MF_E_TRANSFORM_STREAM_CHANGE); // Loop back and try decoding again... + ++typeChangeCount; continue; } if (SUCCEEDED(hr)) { From 29634532f020db64c23cad7a77f01247122266c1 Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Mon, 25 May 2015 08:52:30 +1200 Subject: [PATCH 38/89] bug 1166107 release internal drain monitor before calling Flush() r=gerald The DrainComplete() caught with mWaitForInternalDrain still won't necessarily be from the internal Drain(), but all we need is that one DrainComplete() is caught for the internal Drain() because one more will be generated if there is a Drain() in progress. What protecting mWaitForInternalDrain access with the monitor provides here is that the compiler won't use its address for storage of other data meaningless in the context of mWaitForInternalDrain and so, for example, two DrainComplete() calls won't unintentionally think that they are both for one internal drain. And TSan warnings. --- dom/media/platforms/SharedDecoderManager.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/dom/media/platforms/SharedDecoderManager.cpp b/dom/media/platforms/SharedDecoderManager.cpp index 6203973a699..55c50680533 100644 --- a/dom/media/platforms/SharedDecoderManager.cpp +++ b/dom/media/platforms/SharedDecoderManager.cpp @@ -163,16 +163,15 @@ void SharedDecoderManager::SetIdle(MediaDataDecoder* aProxy) { if (aProxy && mActiveProxy == aProxy) { - MonitorAutoLock mon(mMonitor); - mWaitForInternalDrain = true; - nsresult rv; { - // We don't want to hold the lock while calling Drain() has some + MonitorAutoLock mon(mMonitor); + mWaitForInternalDrain = true; + // We don't want to hold the lock while calling Drain() as some // platform implementations call DrainComplete() immediately. - MonitorAutoUnlock mon(mMonitor); - rv = mActiveProxy->Drain(); } + nsresult rv = mActiveProxy->Drain(); if (NS_SUCCEEDED(rv)) { + MonitorAutoLock mon(mMonitor); while (mWaitForInternalDrain) { mon.Wait(); } From 49b1bd6b85774dfa2107d4e1128126ef7308c409 Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Fri, 22 May 2015 11:10:00 +1200 Subject: [PATCH 39/89] bug 1166107 documentation of mWaitForInternalDrain thread access r=gerald --- dom/media/platforms/SharedDecoderManager.cpp | 2 +- dom/media/platforms/SharedDecoderManager.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dom/media/platforms/SharedDecoderManager.cpp b/dom/media/platforms/SharedDecoderManager.cpp index 55c50680533..05c896a14b7 100644 --- a/dom/media/platforms/SharedDecoderManager.cpp +++ b/dom/media/platforms/SharedDecoderManager.cpp @@ -74,7 +74,7 @@ SharedDecoderManager::SharedDecoderManager() , mActiveProxy(nullptr) , mActiveCallback(nullptr) , mWaitForInternalDrain(false) - , mMonitor("SharedDecoderProxy") + , mMonitor("SharedDecoderManager") , mDecoderReleasedResources(false) { MOZ_ASSERT(NS_IsMainThread()); // taskqueue must be created on main thread. diff --git a/dom/media/platforms/SharedDecoderManager.h b/dom/media/platforms/SharedDecoderManager.h index c6b33d8fec5..7c283085662 100644 --- a/dom/media/platforms/SharedDecoderManager.h +++ b/dom/media/platforms/SharedDecoderManager.h @@ -56,6 +56,7 @@ private: SharedDecoderProxy* mActiveProxy; MediaDataDecoderCallback* mActiveCallback; nsAutoPtr mCallback; + // access protected by mMonitor bool mWaitForInternalDrain; Monitor mMonitor; bool mDecoderReleasedResources; From e847e83a18f754d3e29ca3337113fb8a9b5e3b54 Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 19:27:21 +1000 Subject: [PATCH 40/89] Bug 1168004: Part1. Ensure buffered range referential starts at 0. r=bholley --- dom/media/Intervals.h | 3 +- dom/media/MediaFormatReader.cpp | 69 ++++++++++++++++++--------------- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/dom/media/Intervals.h b/dom/media/Intervals.h index 65fb80da032..a4d70b06b0c 100644 --- a/dom/media/Intervals.h +++ b/dom/media/Intervals.h @@ -537,12 +537,13 @@ public: } // Shift all values by aOffset. - void Shift(const T& aOffset) + SelfType& Shift(const T& aOffset) { for (auto& interval : mIntervals) { interval.mStart = interval.mStart + aOffset; interval.mEnd = interval.mEnd + aOffset; } + return *this; } void SetFuzz(const T& aFuzz) { diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp index b9363c568a8..776c114c50d 100644 --- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -1277,34 +1277,40 @@ MediaFormatReader::GetBuffered() { media::TimeIntervals videoti; media::TimeIntervals audioti; + media::TimeIntervals intervals; if (!mInitDone) { - return media::TimeIntervals(); + return intervals; + } + int64_t startTime; + { + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + MOZ_ASSERT(mStartTime != -1, "Need to finish metadata decode first"); + startTime = mStartTime; } if (NS_IsMainThread()) { - if (!mCachedTimeRangesStale) { - return mCachedTimeRanges; + if (mCachedTimeRangesStale) { + MOZ_ASSERT(mMainThreadDemuxer); + if (!mDataRange.IsEmpty()) { + mMainThreadDemuxer->NotifyDataArrived(mDataRange.Length(), mDataRange.mStart); + } + if (mVideoTrackDemuxer) { + videoti = mVideoTrackDemuxer->GetBuffered(); + } + if (mAudioTrackDemuxer) { + audioti = mAudioTrackDemuxer->GetBuffered(); + } + if (HasAudio() && HasVideo()) { + mCachedTimeRanges = media::Intersection(Move(videoti), Move(audioti)); + } else if (HasAudio()) { + mCachedTimeRanges = Move(audioti); + } else if (HasVideo()) { + mCachedTimeRanges = Move(videoti); + } + mDataRange = ByteInterval(); + mCachedTimeRangesStale = false; } - MOZ_ASSERT(mMainThreadDemuxer); - if (!mDataRange.IsEmpty()) { - mMainThreadDemuxer->NotifyDataArrived(mDataRange.Length(), mDataRange.mStart); - } - if (mVideoTrackDemuxer) { - videoti = mVideoTrackDemuxer->GetBuffered(); - } - if (mAudioTrackDemuxer) { - audioti = mAudioTrackDemuxer->GetBuffered(); - } - if (HasAudio() && HasVideo()) { - mCachedTimeRanges = Intersection(Move(videoti), Move(audioti)); - } else if (HasAudio()) { - mCachedTimeRanges = Move(audioti); - } else if (HasVideo()) { - mCachedTimeRanges = Move(videoti); - } - mDataRange = ByteInterval(); - mCachedTimeRangesStale = false; - return mCachedTimeRanges; + intervals = mCachedTimeRanges; } else { if (OnTaskQueue()) { // Ensure we have up to date buffered time range. @@ -1323,17 +1329,16 @@ MediaFormatReader::GetBuffered() MonitorAutoLock lock(mAudio.mMonitor); audioti = mAudio.mTimeRanges; } - } - if (HasAudio() && HasVideo()) { - videoti.Intersection(audioti); - return videoti; - } else if (HasAudio()) { - return audioti; - } else if (HasVideo()) { - return videoti; + if (HasAudio() && HasVideo()) { + intervals = media::Intersection(Move(videoti), Move(audioti)); + } else if (HasAudio()) { + intervals = Move(audioti); + } else if (HasVideo()) { + intervals = Move(videoti); + } } - return media::TimeIntervals(); + return intervals.Shift(media::TimeUnit::FromMicroseconds(-startTime)); } bool MediaFormatReader::IsDormantNeeded() From 885ef719d63f3479f18402b1434b0d9bb3b7e685 Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 25 May 2015 19:28:02 +1000 Subject: [PATCH 41/89] Bug 1168004: Part2. Add sample file. r=bholley Add MP4 sample file where video starts at 0.8s and audio at 1.95s. --- dom/media/test/bipbop-lateaudio.mp4 | Bin 0 -> 70404 bytes dom/media/test/bipbop-lateaudio.mp4^headers^ | 1 + dom/media/test/manifest.js | 3 +++ dom/media/test/mochitest.ini | 2 ++ 4 files changed, 6 insertions(+) create mode 100644 dom/media/test/bipbop-lateaudio.mp4 create mode 100644 dom/media/test/bipbop-lateaudio.mp4^headers^ diff --git a/dom/media/test/bipbop-lateaudio.mp4 b/dom/media/test/bipbop-lateaudio.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..5b4cc57095617301994aa3e5a1a1440693598976 GIT binary patch literal 70404 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�jy;>YYgdp04mjEOYHPVk7rK2QK*^;+aea!; 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 Imy|gEA6m}Z@c;k- literal 0 HcmV?d00001 diff --git a/dom/media/test/bipbop-lateaudio.mp4^headers^ b/dom/media/test/bipbop-lateaudio.mp4^headers^ new file mode 100644 index 00000000000..4030ea1d3dd --- /dev/null +++ b/dom/media/test/bipbop-lateaudio.mp4^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/test/manifest.js b/dom/media/test/manifest.js index 695aa1d7ed7..d22311d4af9 100644 --- a/dom/media/test/manifest.js +++ b/dom/media/test/manifest.js @@ -227,6 +227,9 @@ var gPlayTests = [ { name:"test-8-7.1.opus", type:"audio/ogg; codecs=opus", duration:13.478 }, { name:"gizmo.mp4", type:"video/mp4", duration:5.56 }, + // Test playback of a MP4 file with a non-zero start time (and audio starting + // a second later). + { name:"bipbop-lateaudio.mp4", type:"video/mp4", duration:2.401 }, { name:"small-shot.m4a", type:"audio/mp4", duration:0.29 }, { name:"small-shot.mp3", type:"audio/mpeg", duration:0.27 }, diff --git a/dom/media/test/mochitest.ini b/dom/media/test/mochitest.ini index ceb57b0e35f..e90301d846b 100644 --- a/dom/media/test/mochitest.ini +++ b/dom/media/test/mochitest.ini @@ -66,6 +66,8 @@ support-files = bipbop-cenc1-video1.m4s bipbop-cenc1-video2.m4s bipbop-cenc1-videoinit.mp4 + bipbop-lateaudio.mp4 + bipbop-lateaudio.mp4^headers^ bogus.duh bogus.ogv bogus.ogv^headers^ From 34161b93d21938fa001142f73e068a223b46bdea Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Mon, 25 May 2015 12:50:15 +0100 Subject: [PATCH 42/89] Bug 1167423 - patch 1 - Handle return values of FallibleTArray functions in Console API, r=smaug --- dom/base/Console.cpp | 117 ++++++++++++++++++++++++++++--------------- dom/base/Console.h | 4 +- 2 files changed, 79 insertions(+), 42 deletions(-) diff --git a/dom/base/Console.cpp b/dom/base/Console.cpp index 7d186af3aaf..186245700d2 100644 --- a/dom/base/Console.cpp +++ b/dom/base/Console.cpp @@ -174,7 +174,9 @@ public: mMethodString = aString; for (uint32_t i = 0; i < aArguments.Length(); ++i) { - mArguments.AppendElement(aArguments[i]); + if (!mArguments.AppendElement(aArguments[i])) { + return; + } } } @@ -666,7 +668,9 @@ private: return; } - arguments.AppendElement(value); + if (!arguments.AppendElement(value)) { + return; + } } mConsole->ProfileMethod(aCx, mAction, arguments); @@ -826,8 +830,8 @@ Console::Time(JSContext* aCx, const JS::Handle aTime) Sequence data; SequenceRooter rooter(aCx, &data); - if (!aTime.isUndefined()) { - data.AppendElement(aTime); + if (!aTime.isUndefined() && !data.AppendElement(aTime)) { + return; } Method(aCx, MethodTime, NS_LITERAL_STRING("time"), data); @@ -839,8 +843,8 @@ Console::TimeEnd(JSContext* aCx, const JS::Handle aTime) Sequence data; SequenceRooter rooter(aCx, &data); - if (!aTime.isUndefined()) { - data.AppendElement(aTime); + if (!aTime.isUndefined() && !data.AppendElement(aTime)) { + return; } Method(aCx, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"), data); @@ -852,8 +856,8 @@ Console::TimeStamp(JSContext* aCx, const JS::Handle aData) Sequence data; SequenceRooter rooter(aCx, &data); - if (aData.isString()) { - data.AppendElement(aData); + if (aData.isString() && !data.AppendElement(aData)) { + return; } Method(aCx, MethodTimeStamp, NS_LITERAL_STRING("timeStamp"), data); @@ -892,7 +896,9 @@ Console::ProfileMethod(JSContext* aCx, const nsAString& aAction, Sequence& sequence = event.mArguments.Value(); for (uint32_t i = 0; i < aData.Length(); ++i) { - sequence.AppendElement(aData[i]); + if (!sequence.AppendElement(aData[i])) { + return; + } } JS::Rooted eventValue(aCx); @@ -1293,13 +1299,18 @@ Console::ProcessCallData(ConsoleCallData* aData) case MethodAssert: event.mArguments.Construct(); event.mStyles.Construct(); - ProcessArguments(cx, aData->mArguments, event.mArguments.Value(), - event.mStyles.Value()); + if (!ProcessArguments(cx, aData->mArguments, event.mArguments.Value(), + event.mStyles.Value())) { + return; + } + break; default: event.mArguments.Construct(); - ArgumentsToValueList(aData->mArguments, event.mArguments.Value()); + if (!ArgumentsToValueList(aData->mArguments, event.mArguments.Value())) { + return; + } } if (aData->mMethodName == MethodGroup || @@ -1418,48 +1429,52 @@ Console::ProcessCallData(ConsoleCallData* aData) namespace { // Helper method for ProcessArguments. Flushes output, if non-empty, to aSequence. -void -FlushOutput(JSContext* aCx, Sequence& aSequence, nsString &output) +bool +FlushOutput(JSContext* aCx, Sequence& aSequence, nsString &aOutput) { - if (!output.IsEmpty()) { + if (!aOutput.IsEmpty()) { JS::Rooted str(aCx, JS_NewUCStringCopyN(aCx, - output.get(), - output.Length())); + aOutput.get(), + aOutput.Length())); if (!str) { - return; + return false; } - aSequence.AppendElement(JS::StringValue(str)); - output.Truncate(); + if (!aSequence.AppendElement(JS::StringValue(str))) { + return false; + } + + aOutput.Truncate(); } + + return true; } } // anonymous namespace -void +bool Console::ProcessArguments(JSContext* aCx, const nsTArray>& aData, Sequence& aSequence, Sequence& aStyles) { if (aData.IsEmpty()) { - return; + return true; } if (aData.Length() == 1 || !aData[0].isString()) { - ArgumentsToValueList(aData, aSequence); - return; + return ArgumentsToValueList(aData, aSequence); } JS::Rooted format(aCx, aData[0]); JS::Rooted jsString(aCx, JS::ToString(aCx, format)); if (!jsString) { - return; + return false; } nsAutoJSString string; if (!string.init(aCx, jsString)) { - return; + return false; } nsString::const_iterator start, end; @@ -1547,35 +1562,47 @@ Console::ProcessArguments(JSContext* aCx, case 'o': case 'O': { - FlushOutput(aCx, aSequence, output); + if (!FlushOutput(aCx, aSequence, output)) { + return false; + } JS::Rooted v(aCx); if (index < aData.Length()) { v = aData[index++]; } - aSequence.AppendElement(v); + if (!aSequence.AppendElement(v)) { + return false; + } + break; } case 'c': { - FlushOutput(aCx, aSequence, output); + if (!FlushOutput(aCx, aSequence, output)) { + return false; + } if (index < aData.Length()) { JS::Rooted v(aCx, aData[index++]); JS::Rooted jsString(aCx, JS::ToString(aCx, v)); if (!jsString) { - return; + return false; } int32_t diff = aSequence.Length() - aStyles.Length(); if (diff > 0) { for (int32_t i = 0; i < diff; i++) { - aStyles.AppendElement(JS::NullValue()); + if (!aStyles.AppendElement(JS::NullValue())) { + return false; + } } } - aStyles.AppendElement(JS::StringValue(jsString)); + + if (!aStyles.AppendElement(JS::StringValue(jsString))) { + return false; + } } break; } @@ -1585,12 +1612,12 @@ Console::ProcessArguments(JSContext* aCx, JS::Rooted value(aCx, aData[index++]); JS::Rooted jsString(aCx, JS::ToString(aCx, value)); if (!jsString) { - return; + return false; } nsAutoJSString v; if (!v.init(aCx, jsString)) { - return; + return false; } output.Append(v); @@ -1604,7 +1631,7 @@ Console::ProcessArguments(JSContext* aCx, int32_t v; if (!JS::ToInt32(aCx, value, &v)) { - return; + return false; } nsCString format; @@ -1619,7 +1646,7 @@ Console::ProcessArguments(JSContext* aCx, double v; if (!JS::ToNumber(aCx, value, &v)) { - return; + return false; } nsCString format; @@ -1634,7 +1661,9 @@ Console::ProcessArguments(JSContext* aCx, } } - FlushOutput(aCx, aSequence, output); + if (!FlushOutput(aCx, aSequence, output)) { + return false; + } // Discard trailing style element if there is no output to apply it to. if (aStyles.Length() > aSequence.Length()) { @@ -1643,8 +1672,12 @@ Console::ProcessArguments(JSContext* aCx, // The rest of the array, if unused by the format string. for (; index < aData.Length(); ++index) { - aSequence.AppendElement(aData[index]); + if (!aSequence.AppendElement(aData[index])) { + return false; + } } + + return true; } void @@ -1770,13 +1803,17 @@ Console::StopTimer(JSContext* aCx, const JS::Value& aName, return value; } -void +bool Console::ArgumentsToValueList(const nsTArray>& aData, Sequence& aSequence) { for (uint32_t i = 0; i < aData.Length(); ++i) { - aSequence.AppendElement(aData[i]); + if (!aSequence.AppendElement(aData[i])) { + return false; + } } + + return true; } JS::Value diff --git a/dom/base/Console.h b/dom/base/Console.h index 4fff166d3d2..8c958486396 100644 --- a/dom/base/Console.h +++ b/dom/base/Console.h @@ -158,7 +158,7 @@ private: // finds based the format string. The index of the styles matches the indexes // of elements that need the custom styling from aSequence. For elements with // no custom styling the array is padded with null elements. - void + bool ProcessArguments(JSContext* aCx, const nsTArray>& aData, Sequence& aSequence, Sequence& aStyles); @@ -182,7 +182,7 @@ private: DOMHighResTimeStamp aTimestamp); // The method populates a Sequence from an array of JS::Value. - void + bool ArgumentsToValueList(const nsTArray>& aData, Sequence& aSequence); From 98514d828a4eaed1bd72bd3460dad4affd64e77f Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Mon, 25 May 2015 12:50:15 +0100 Subject: [PATCH 43/89] Bug 1167423 - patch 2 - Handle return values of FallibleTArray functions in WebSocket, r=smaug --- dom/base/WebSocket.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dom/base/WebSocket.cpp b/dom/base/WebSocket.cpp index b41aa05322a..81278786e52 100644 --- a/dom/base/WebSocket.cpp +++ b/dom/base/WebSocket.cpp @@ -960,7 +960,11 @@ WebSocket::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) { Sequence protocols; - protocols.AppendElement(aProtocol); + if (!protocols.AppendElement(aProtocol)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv); } From 4cb28b400a2004cdcab45fb38a40975e342b38f3 Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Mon, 25 May 2015 12:50:15 +0100 Subject: [PATCH 44/89] Bug 1167423 - patch 3 - Handle return values of FallibleTArray functions in MutationObserver, r=smaug --- dom/base/nsDOMMutationObserver.cpp | 9 +++++++-- dom/base/nsDOMMutationObserver.h | 3 ++- dom/webidl/MutationObserver.webidl | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/dom/base/nsDOMMutationObserver.cpp b/dom/base/nsDOMMutationObserver.cpp index 96d1666cdd1..8202b1b056f 100644 --- a/dom/base/nsDOMMutationObserver.cpp +++ b/dom/base/nsDOMMutationObserver.cpp @@ -693,7 +693,9 @@ nsDOMMutationObserver::TakeRecords( } void -nsDOMMutationObserver::GetObservingInfo(nsTArray >& aResult) +nsDOMMutationObserver::GetObservingInfo( + nsTArray>& aResult, + mozilla::ErrorResult& aRv) { aResult.SetCapacity(mReceivers.Count()); for (int32_t i = 0; i < mReceivers.Count(); ++i) { @@ -712,7 +714,10 @@ nsDOMMutationObserver::GetObservingInfo(nsTArray mozilla::dom::Sequence& filtersAsStrings = info.mAttributeFilter.Value(); for (int32_t j = 0; j < filters.Count(); ++j) { - filtersAsStrings.AppendElement(nsDependentAtomString(filters[j])); + if (!filtersAsStrings.AppendElement(nsDependentAtomString(filters[j]))) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } } } info.mObservedNode = mr->Target(); diff --git a/dom/base/nsDOMMutationObserver.h b/dom/base/nsDOMMutationObserver.h index ee6a511e44e..8a53b6bd805 100644 --- a/dom/base/nsDOMMutationObserver.h +++ b/dom/base/nsDOMMutationObserver.h @@ -493,7 +493,8 @@ public: void HandleMutation(); - void GetObservingInfo(nsTArray >& aResult); + void GetObservingInfo(nsTArray>& aResult, + mozilla::ErrorResult& aRv); mozilla::dom::MutationCallback* MutationCallback() { return mCallback; } diff --git a/dom/webidl/MutationObserver.webidl b/dom/webidl/MutationObserver.webidl index e1bbccd4253..f222a8fe996 100644 --- a/dom/webidl/MutationObserver.webidl +++ b/dom/webidl/MutationObserver.webidl @@ -43,7 +43,7 @@ interface MutationObserver { void disconnect(); sequence takeRecords(); - [ChromeOnly] + [ChromeOnly, Throws] sequence getObservingInfo(); [ChromeOnly] readonly attribute MutationCallback mutationCallback; From c89a834b25d818acf236bab5b5e0da25ef621e64 Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Mon, 25 May 2015 12:50:15 +0100 Subject: [PATCH 45/89] Bug 1167423 - patch 4 - Handle return values of FallibleTArray functions in CanvasRenderingContext2D, r=smaug --- dom/canvas/CanvasRenderingContext2D.cpp | 25 ++++++++++++++++------ dom/canvas/CanvasRenderingContext2D.h | 5 +++-- dom/webidl/CanvasRenderingContext2D.webidl | 4 ++-- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 39643bd5e3d..7447d7c6218 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -2642,7 +2642,8 @@ CanvasRenderingContext2D::Stroke(const CanvasPath& path) Redraw(); } -void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement) +void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement, + ErrorResult& aRv) { EnsureUserSpacePath(); @@ -2674,8 +2675,12 @@ void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement // set dashing for foreground FallibleTArray& dash = CurrentState().dash; - dash.AppendElement(1); - dash.AppendElement(1); + for (uint32_t i = 0; i < 2; ++i) { + if (!dash.AppendElement(1)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + } // set the foreground focus color CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(0,0,0, 255)); @@ -3947,7 +3952,8 @@ CanvasRenderingContext2D::SetMozDashOffset(double mozDashOffset) } void -CanvasRenderingContext2D::SetLineDash(const Sequence& aSegments) +CanvasRenderingContext2D::SetLineDash(const Sequence& aSegments, + ErrorResult& aRv) { FallibleTArray dash; @@ -3957,11 +3963,18 @@ CanvasRenderingContext2D::SetLineDash(const Sequence& aSegments) // taken care of by WebIDL return; } - dash.AppendElement(aSegments[x]); + + if (!dash.AppendElement(aSegments[x])) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } } if (aSegments.Length() % 2) { // If the number of elements is odd, concatenate again for (uint32_t x = 0; x < aSegments.Length(); x++) { - dash.AppendElement(aSegments[x]); + if (!dash.AppendElement(aSegments[x])) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } } } diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h index 0488bbd4088..efa97b565f8 100644 --- a/dom/canvas/CanvasRenderingContext2D.h +++ b/dom/canvas/CanvasRenderingContext2D.h @@ -187,7 +187,7 @@ public: void Fill(const CanvasPath& path, const CanvasWindingRule& winding); void Stroke(); void Stroke(const CanvasPath& path); - void DrawFocusIfNeeded(mozilla::dom::Element& element); + void DrawFocusIfNeeded(mozilla::dom::Element& element, ErrorResult& aRv); bool DrawCustomFocusRing(mozilla::dom::Element& element); void Clip(const CanvasWindingRule& winding); void Clip(const CanvasPath& path, const CanvasWindingRule& winding); @@ -363,7 +363,8 @@ public: void SetMozDash(JSContext* cx, const JS::Value& mozDash, mozilla::ErrorResult& error); - void SetLineDash(const Sequence& mSegments); + void SetLineDash(const Sequence& mSegments, + mozilla::ErrorResult& aRv); void GetLineDash(nsTArray& mSegments) const; void SetLineDashOffset(double mOffset); diff --git a/dom/webidl/CanvasRenderingContext2D.webidl b/dom/webidl/CanvasRenderingContext2D.webidl index b471b0e1504..c1157c0284f 100644 --- a/dom/webidl/CanvasRenderingContext2D.webidl +++ b/dom/webidl/CanvasRenderingContext2D.webidl @@ -92,7 +92,7 @@ interface CanvasRenderingContext2D { void fill(Path2D path, optional CanvasWindingRule winding = "nonzero"); void stroke(); void stroke(Path2D path); - [Pref="canvas.focusring.enabled"] void drawFocusIfNeeded(Element element); + [Pref="canvas.focusring.enabled", Throws] void drawFocusIfNeeded(Element element); // NOT IMPLEMENTED void drawSystemFocusRing(Path path, HTMLElement element); [Pref="canvas.customfocusring.enabled"] boolean drawCustomFocusRing(Element element); // NOT IMPLEMENTED boolean drawCustomFocusRing(Path path, HTMLElement element); @@ -246,7 +246,7 @@ interface CanvasDrawingStyles { attribute double miterLimit; // (default 10) // dashed lines - [LenientFloat] void setLineDash(sequence segments); // default empty + [LenientFloat, Throws] void setLineDash(sequence segments); // default empty sequence getLineDash(); [LenientFloat] attribute double lineDashOffset; From f831429bbc5f1c4be52bb7d35b15ed014dd3b5c2 Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Mon, 25 May 2015 12:50:15 +0100 Subject: [PATCH 46/89] Bug 1167423 - patch 5 - Handle return values of FallibleTArray functions in WebGL2Context, r=smaug --- dom/canvas/WebGL2Context.h | 6 ++-- dom/canvas/WebGL2ContextFramebuffers.cpp | 37 +++++++++++++++++++----- dom/webidl/WebGL2RenderingContext.webidl | 5 ++++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/dom/canvas/WebGL2Context.h b/dom/canvas/WebGL2Context.h index 9ea73007f4e..ce8b15ce3e7 100644 --- a/dom/canvas/WebGL2Context.h +++ b/dom/canvas/WebGL2Context.h @@ -10,6 +10,7 @@ namespace mozilla { +class ErrorResult; class WebGLSampler; class WebGLSync; class WebGLTransformFeedback; @@ -56,9 +57,10 @@ public: GLbitfield mask, GLenum filter); void FramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); void GetInternalformatParameter(JSContext*, GLenum target, GLenum internalformat, GLenum pname, JS::MutableHandleValue retval); - void InvalidateFramebuffer(GLenum target, const dom::Sequence& attachments); + void InvalidateFramebuffer(GLenum target, const dom::Sequence& attachments, + ErrorResult& aRv); void InvalidateSubFramebuffer (GLenum target, const dom::Sequence& attachments, GLint x, GLint y, - GLsizei width, GLsizei height); + GLsizei width, GLsizei height, ErrorResult& aRv); void ReadBuffer(GLenum mode); void RenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); diff --git a/dom/canvas/WebGL2ContextFramebuffers.cpp b/dom/canvas/WebGL2ContextFramebuffers.cpp index f6083363c36..1d074f14607 100644 --- a/dom/canvas/WebGL2ContextFramebuffers.cpp +++ b/dom/canvas/WebGL2ContextFramebuffers.cpp @@ -343,26 +343,38 @@ WebGL2Context::GetInternalformatParameter(JSContext*, GLenum target, GLenum inte // Map attachments intended for the default buffer, to attachments for a non- // default buffer. -static void +static bool TranslateDefaultAttachments(const dom::Sequence& in, dom::Sequence* out) { for (size_t i = 0; i < in.Length(); i++) { switch (in[i]) { case LOCAL_GL_COLOR: - out->AppendElement(LOCAL_GL_COLOR_ATTACHMENT0); + if (!out->AppendElement(LOCAL_GL_COLOR_ATTACHMENT0)) { + return false; + } break; + case LOCAL_GL_DEPTH: - out->AppendElement(LOCAL_GL_DEPTH_ATTACHMENT); + if (!out->AppendElement(LOCAL_GL_DEPTH_ATTACHMENT)) { + return false; + } break; + case LOCAL_GL_STENCIL: - out->AppendElement(LOCAL_GL_STENCIL_ATTACHMENT); + if (!out->AppendElement(LOCAL_GL_STENCIL_ATTACHMENT)) { + return false; + } break; } } + + return true; } void -WebGL2Context::InvalidateFramebuffer(GLenum target, const dom::Sequence& attachments) +WebGL2Context::InvalidateFramebuffer(GLenum target, + const dom::Sequence& attachments, + ErrorResult& aRv) { if (IsContextLost()) return; @@ -407,7 +419,11 @@ WebGL2Context::InvalidateFramebuffer(GLenum target, const dom::Sequence& if (!fb && !isDefaultFB) { dom::Sequence tmpAttachments; - TranslateDefaultAttachments(attachments, &tmpAttachments); + if (!TranslateDefaultAttachments(attachments, &tmpAttachments)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + gl->fInvalidateFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements()); } else { gl->fInvalidateFramebuffer(target, attachments.Length(), attachments.Elements()); @@ -416,7 +432,8 @@ WebGL2Context::InvalidateFramebuffer(GLenum target, const dom::Sequence& void WebGL2Context::InvalidateSubFramebuffer(GLenum target, const dom::Sequence& attachments, - GLint x, GLint y, GLsizei width, GLsizei height) + GLint x, GLint y, GLsizei width, GLsizei height, + ErrorResult& aRv) { if (IsContextLost()) return; @@ -461,7 +478,11 @@ WebGL2Context::InvalidateSubFramebuffer(GLenum target, const dom::Sequence tmpAttachments; - TranslateDefaultAttachments(attachments, &tmpAttachments); + if (!TranslateDefaultAttachments(attachments, &tmpAttachments)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + gl->fInvalidateSubFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements(), x, y, width, height); } else { diff --git a/dom/webidl/WebGL2RenderingContext.webidl b/dom/webidl/WebGL2RenderingContext.webidl index 382aa21e7ec..a98a6d127bc 100644 --- a/dom/webidl/WebGL2RenderingContext.webidl +++ b/dom/webidl/WebGL2RenderingContext.webidl @@ -329,9 +329,14 @@ interface WebGL2RenderingContext : WebGLRenderingContext GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); void framebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); any getInternalformatParameter(GLenum target, GLenum internalformat, GLenum pname); + + [Throws] void invalidateFramebuffer(GLenum target, sequence attachments); + + [Throws] void invalidateSubFramebuffer (GLenum target, sequence attachments, GLint x, GLint y, GLsizei width, GLsizei height); + void readBuffer(GLenum src); /* Renderbuffer objects */ From 38122c45e6447bc93425d3dde472e66a2b3c70d2 Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Mon, 25 May 2015 12:50:15 +0100 Subject: [PATCH 47/89] Bug 1167423 - patch 6 - Handle return values of FallibleTArray functions in WebCryptTask, r=smaug --- dom/crypto/WebCryptoTask.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dom/crypto/WebCryptoTask.cpp b/dom/crypto/WebCryptoTask.cpp index 1b0936f4f23..26e604b1f0a 100644 --- a/dom/crypto/WebCryptoTask.cpp +++ b/dom/crypto/WebCryptoTask.cpp @@ -1962,7 +1962,9 @@ private: if (!mKeyUsages.IsEmpty()) { mJwk.mKey_ops.Construct(); - mJwk.mKey_ops.Value().AppendElements(mKeyUsages); + if (!mJwk.mKey_ops.Value().AppendElements(mKeyUsages)) { + return NS_ERROR_OUT_OF_MEMORY; + } } return NS_OK; From 405a2e5eb650205b42d4f20cb0fccf61f81cc022 Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Mon, 25 May 2015 12:50:15 +0100 Subject: [PATCH 48/89] Bug 1167423 - patch 7 - Handle return values of FallibleTArray functions in DataStore API, r=smaug --- dom/datastore/DataStoreDB.cpp | 4 +++- dom/datastore/DataStoreService.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dom/datastore/DataStoreDB.cpp b/dom/datastore/DataStoreDB.cpp index fff2e177b38..ed800f7b053 100644 --- a/dom/datastore/DataStoreDB.cpp +++ b/dom/datastore/DataStoreDB.cpp @@ -316,7 +316,9 @@ DataStoreDB::DatabaseOpened() } StringOrStringSequence objectStores; - objectStores.RawSetAsStringSequence().AppendElements(mObjectStores); + if (!objectStores.RawSetAsStringSequence().AppendElements(mObjectStores)) { + return NS_ERROR_OUT_OF_MEMORY; + } nsRefPtr txn; error = mDatabase->Transaction(objectStores, diff --git a/dom/datastore/DataStoreService.cpp b/dom/datastore/DataStoreService.cpp index 716e820003e..b7a4344d711 100644 --- a/dom/datastore/DataStoreService.cpp +++ b/dom/datastore/DataStoreService.cpp @@ -1358,7 +1358,9 @@ DataStoreService::CreateFirstRevisionId(uint32_t aAppId, new FirstRevisionIdCallback(aAppId, aName, aManifestURL); Sequence dbs; - dbs.AppendElement(NS_LITERAL_STRING(DATASTOREDB_REVISION)); + if (!dbs.AppendElement(NS_LITERAL_STRING(DATASTOREDB_REVISION))) { + return NS_ERROR_OUT_OF_MEMORY; + } return db->Open(IDBTransactionMode::Readwrite, dbs, callback); } From 68ce6f4a2c7953e03804cef3746030a82b42dc2e Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Mon, 25 May 2015 12:50:15 +0100 Subject: [PATCH 49/89] Bug 1167423 - patch 8 - Handle return values of FallibleTArray functions in HTMLInputElement, r=smaug --- dom/html/HTMLInputElement.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index e7f2c876681..36332e35f8d 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -1860,7 +1860,11 @@ HTMLInputElement::SetValue(const nsAString& aValue, ErrorResult& aRv) return; } Sequence list; - list.AppendElement(aValue); + if (!list.AppendElement(aValue)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + MozSetFileNameArray(list, aRv); return; } @@ -2441,7 +2445,9 @@ HTMLInputElement::MozSetFileNameArray(const char16_t** aFileNames, uint32_t aLen Sequence list; for (uint32_t i = 0; i < aLength; ++i) { - list.AppendElement(nsDependentString(aFileNames[i])); + if (!list.AppendElement(nsDependentString(aFileNames[i]))) { + return NS_ERROR_OUT_OF_MEMORY; + } } ErrorResult rv; @@ -2492,7 +2498,10 @@ HTMLInputElement::SetUserInput(const nsAString& aValue) if (mType == NS_FORM_INPUT_FILE) { Sequence list; - list.AppendElement(aValue); + if (!list.AppendElement(aValue)) { + return NS_ERROR_OUT_OF_MEMORY; + } + ErrorResult rv; MozSetFileNameArray(list, rv); return rv.StealNSResult(); From 7d65c828b2c6a968c91b50d94126e818c0582b79 Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Mon, 25 May 2015 12:50:15 +0100 Subject: [PATCH 50/89] Bug 1167423 - patch 9 - Handle return values of FallibleTArray functions in MediaSource, r=jya --- dom/media/mediasource/ContainerParser.cpp | 9 +++++++- dom/media/mediasource/ResourceQueue.cpp | 15 ++++++++----- dom/media/mediasource/ResourceQueue.h | 7 ++++-- .../mediasource/SourceBufferResource.cpp | 9 ++++---- dom/media/mediasource/SourceBufferResource.h | 5 +++-- dom/media/mediasource/TrackBuffer.cpp | 22 ++++++++++++++++--- 6 files changed, 50 insertions(+), 17 deletions(-) diff --git a/dom/media/mediasource/ContainerParser.cpp b/dom/media/mediasource/ContainerParser.cpp index 310918734a4..ee17396ac2d 100644 --- a/dom/media/mediasource/ContainerParser.cpp +++ b/dom/media/mediasource/ContainerParser.cpp @@ -8,6 +8,7 @@ #include "WebMBufferedParser.h" #include "mozilla/Endian.h" +#include "mozilla/ErrorResult.h" #include "mp4_demuxer/MoofParser.h" #include "mozilla/Logging.h" #include "MediaData.h" @@ -324,7 +325,13 @@ public: mp4_demuxer::Interval compositionRange = mParser->GetCompositionRange(byteRanges); - mResource->EvictData(mParser->mOffset, mParser->mOffset); + + ErrorResult rv; + mResource->EvictData(mParser->mOffset, mParser->mOffset, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return false; + } if (compositionRange.IsNull()) { return false; diff --git a/dom/media/mediasource/ResourceQueue.cpp b/dom/media/mediasource/ResourceQueue.cpp index ef5248658f1..fb5f8a53883 100644 --- a/dom/media/mediasource/ResourceQueue.cpp +++ b/dom/media/mediasource/ResourceQueue.cpp @@ -89,14 +89,15 @@ ResourceQueue::AppendItem(MediaLargeByteBuffer* aData) } uint32_t -ResourceQueue::Evict(uint64_t aOffset, uint32_t aSizeToEvict) +ResourceQueue::Evict(uint64_t aOffset, uint32_t aSizeToEvict, + ErrorResult& aRv) { SBR_DEBUG("Evict(aOffset=%llu, aSizeToEvict=%u)", aOffset, aSizeToEvict); - return EvictBefore(std::min(aOffset, mOffset + (uint64_t)aSizeToEvict)); + return EvictBefore(std::min(aOffset, mOffset + (uint64_t)aSizeToEvict), aRv); } -uint32_t ResourceQueue::EvictBefore(uint64_t aOffset) +uint32_t ResourceQueue::EvictBefore(uint64_t aOffset, ErrorResult& aRv) { SBR_DEBUG("EvictBefore(%llu)", aOffset); uint32_t evicted = 0; @@ -111,8 +112,12 @@ uint32_t ResourceQueue::EvictBefore(uint64_t aOffset) mOffset += offset; evicted += offset; nsRefPtr data = new MediaLargeByteBuffer; - data->AppendElements(item->mData->Elements() + offset, - item->mData->Length() - offset); + if (!data->AppendElements(item->mData->Elements() + offset, + item->mData->Length() - offset)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return 0; + } + item->mData = data; break; } diff --git a/dom/media/mediasource/ResourceQueue.h b/dom/media/mediasource/ResourceQueue.h index 0f791c81c4b..1ff26d00c54 100644 --- a/dom/media/mediasource/ResourceQueue.h +++ b/dom/media/mediasource/ResourceQueue.h @@ -12,6 +12,8 @@ namespace mozilla { +class ErrorResult; + // A SourceBufferResource has a queue containing the data that is appended // to it. The queue holds instances of ResourceItem which is an array of the // bytes. Appending data to the SourceBufferResource pushes this onto the @@ -47,9 +49,10 @@ public: // Tries to evict at least aSizeToEvict from the queue up until // aOffset. Returns amount evicted. - uint32_t Evict(uint64_t aOffset, uint32_t aSizeToEvict); + uint32_t Evict(uint64_t aOffset, uint32_t aSizeToEvict, + ErrorResult& aRv); - uint32_t EvictBefore(uint64_t aOffset); + uint32_t EvictBefore(uint64_t aOffset, ErrorResult& aRv); uint32_t EvictAll(); diff --git a/dom/media/mediasource/SourceBufferResource.cpp b/dom/media/mediasource/SourceBufferResource.cpp index d0e162ce4d2..3e624031df6 100644 --- a/dom/media/mediasource/SourceBufferResource.cpp +++ b/dom/media/mediasource/SourceBufferResource.cpp @@ -174,12 +174,13 @@ SourceBufferResource::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCo } uint32_t -SourceBufferResource::EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold) +SourceBufferResource::EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold, + ErrorResult& aRv) { SBR_DEBUG("EvictData(aPlaybackOffset=%llu," "aThreshold=%u)", aPlaybackOffset, aThreshold); ReentrantMonitorAutoEnter mon(mMonitor); - uint32_t result = mInputBuffer.Evict(aPlaybackOffset, aThreshold); + uint32_t result = mInputBuffer.Evict(aPlaybackOffset, aThreshold, aRv); if (result > 0) { // Wake up any waiting threads in case a ReadInternal call // is now invalid. @@ -189,13 +190,13 @@ SourceBufferResource::EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold) } void -SourceBufferResource::EvictBefore(uint64_t aOffset) +SourceBufferResource::EvictBefore(uint64_t aOffset, ErrorResult& aRv) { SBR_DEBUG("EvictBefore(aOffset=%llu)", aOffset); ReentrantMonitorAutoEnter mon(mMonitor); // If aOffset is past the current playback offset we don't evict. if (aOffset < mOffset) { - mInputBuffer.EvictBefore(aOffset); + mInputBuffer.EvictBefore(aOffset, aRv); } // Wake up any waiting threads in case a ReadInternal call // is now invalid. diff --git a/dom/media/mediasource/SourceBufferResource.h b/dom/media/mediasource/SourceBufferResource.h index 227c59f8226..b2d922f5343 100644 --- a/dom/media/mediasource/SourceBufferResource.h +++ b/dom/media/mediasource/SourceBufferResource.h @@ -112,10 +112,11 @@ public: } // Remove data from resource if it holds more than the threshold // number of bytes. Returns amount evicted. - uint32_t EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold); + uint32_t EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold, + ErrorResult& aRv); // Remove data from resource before the given offset. - void EvictBefore(uint64_t aOffset); + void EvictBefore(uint64_t aOffset, ErrorResult& aRv); // Remove all data from the resource uint32_t EvictAll(); diff --git a/dom/media/mediasource/TrackBuffer.cpp b/dom/media/mediasource/TrackBuffer.cpp index 659618626d0..030ca935a13 100644 --- a/dom/media/mediasource/TrackBuffer.cpp +++ b/dom/media/mediasource/TrackBuffer.cpp @@ -352,8 +352,14 @@ TrackBuffer::EvictData(double aPlaybackTime, buffered.GetEnd().ToSeconds(), aPlaybackTime, time, playbackOffset, decoders[i]->GetResource()->GetSize()); if (playbackOffset > 0) { + ErrorResult rv; toEvict -= decoders[i]->GetResource()->EvictData(playbackOffset, - playbackOffset); + playbackOffset, + rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return false; + } } } decoders[i]->GetReader()->NotifyDataRemoved(); @@ -506,7 +512,12 @@ TrackBuffer::EvictBefore(double aTime) if (endOffset > 0) { MSE_DEBUG("decoder=%u offset=%lld", i, endOffset); - mInitializedDecoders[i]->GetResource()->EvictBefore(endOffset); + ErrorResult rv; + mInitializedDecoders[i]->GetResource()->EvictBefore(endOffset, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return; + } mInitializedDecoders[i]->GetReader()->NotifyDataRemoved(); } } @@ -1104,7 +1115,12 @@ TrackBuffer::RangeRemoval(media::TimeUnit aStart, buffered.GetEnd().ToSeconds(), offset, decoders[i]->GetResource()->GetSize()); if (offset > 0) { - decoders[i]->GetResource()->EvictData(offset, offset); + ErrorResult rv; + decoders[i]->GetResource()->EvictData(offset, offset, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return false; + } } } decoders[i]->GetReader()->NotifyDataRemoved(); From 961419bb29a2a5da41918420ae674ace5b67261f Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Mon, 25 May 2015 12:50:15 +0100 Subject: [PATCH 51/89] Bug 1167423 - patch 10 - Handle return values of FallibleTArray functions in MobileMessage, r=smaug --- dom/mobilemessage/MobileMessageManager.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dom/mobilemessage/MobileMessageManager.cpp b/dom/mobilemessage/MobileMessageManager.cpp index 0a918cd81e4..4c1dc2a9644 100644 --- a/dom/mobilemessage/MobileMessageManager.cpp +++ b/dom/mobilemessage/MobileMessageManager.cpp @@ -599,13 +599,17 @@ MobileMessageManager::DispatchTrustedDeletedEventToSelf(nsISupports* aDeletedInf uint32_t msgIdLength = info->GetData().deletedMessageIds().Length(); if (msgIdLength) { Sequence& deletedMsgIds = init.mDeletedMessageIds.SetValue(); - deletedMsgIds.AppendElements(info->GetData().deletedMessageIds()); + if (!deletedMsgIds.AppendElements(info->GetData().deletedMessageIds())) { + return NS_ERROR_OUT_OF_MEMORY; + } } uint32_t threadIdLength = info->GetData().deletedThreadIds().Length(); if (threadIdLength) { Sequence& deletedThreadIds = init.mDeletedThreadIds.SetValue(); - deletedThreadIds.AppendElements(info->GetData().deletedThreadIds()); + if (!deletedThreadIds.AppendElements(info->GetData().deletedThreadIds())) { + return NS_ERROR_OUT_OF_MEMORY; + } } nsRefPtr event = From ae6471ea81eb93f711ae710df4beb5647e2fa22b Mon Sep 17 00:00:00 2001 From: Jeff Muizelaar Date: Wed, 20 May 2015 16:01:23 -0400 Subject: [PATCH 52/89] Bug 1166879. Avoid uninitialized read in FlattenBezier. r=mstange We only want to read from t2 if count is equal to 2. Reordering this condition makes that true. --- gfx/2d/Path.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gfx/2d/Path.cpp b/gfx/2d/Path.cpp index d9463952383..a276609bef1 100644 --- a/gfx/2d/Path.cpp +++ b/gfx/2d/Path.cpp @@ -437,7 +437,7 @@ FlattenBezier(const BezierControlPoints &aControlPoints, FindInflectionPoints(aControlPoints, &t1, &t2, &count); // Check that at least one of the inflection points is inside [0..1] - if (count == 0 || ((t1 < 0 || t1 > 1.0) && ((t2 < 0 || t2 > 1.0) || count == 1)) ) { + if (count == 0 || ((t1 < 0 || t1 > 1.0) && (count == 1 || (t2 < 0 || t2 > 1.0))) ) { FlattenBezierCurveSegment(aControlPoints, aSink, aTolerance); return; } From 8ceb41cb432829bae0d4d6e68a09aeb950ddc49f Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Thu, 21 May 2015 13:35:29 +0800 Subject: [PATCH 53/89] Bug 1166183 - Back out the direct listener removal landed by mistake in bug 1141781. r=jesup --- .../signaling/src/mediapipeline/MediaPipeline.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp index 7e15811343c..f60a6f128d4 100644 --- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp +++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp @@ -630,12 +630,12 @@ void MediaPipelineTransmit::AttachToTrack(const std::string& track_id) { stream_->AddListener(listener_); - // // Is this a gUM mediastream? If so, also register the Listener directly with - // // the SourceMediaStream that's attached to the TrackUnion so we can get direct - // // unqueued (and not resampled) data - // if (domstream_->AddDirectListener(listener_)) { - // listener_->direct_connect_ = true; - // } + // Is this a gUM mediastream? If so, also register the Listener directly with + // the SourceMediaStream that's attached to the TrackUnion so we can get direct + // unqueued (and not resampled) data + if (domstream_->AddDirectListener(listener_)) { + listener_->direct_connect_ = true; + } #ifndef MOZILLA_INTERNAL_API // this enables the unit tests that can't fiddle with principals and the like From 016b4c15501492468b014a6eeb5a9288de286bfd Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Thu, 21 May 2015 13:35:29 +0800 Subject: [PATCH 54/89] Bug 1166183 - Work around bug 934512 in track_peerConnection_replaceTrack.html. r=pehrson --- dom/media/tests/mochitest/test_peerConnection_replaceTrack.html | 1 + 1 file changed, 1 insertion(+) diff --git a/dom/media/tests/mochitest/test_peerConnection_replaceTrack.html b/dom/media/tests/mochitest/test_peerConnection_replaceTrack.html index 8d5956d2314..e7545dbc9c2 100644 --- a/dom/media/tests/mochitest/test_peerConnection_replaceTrack.html +++ b/dom/media/tests/mochitest/test_peerConnection_replaceTrack.html @@ -32,6 +32,7 @@ var audiotrack; return navigator.mediaDevices.getUserMedia({video:true, audio:true, fake:true}) .then(newStream => { + window.grip = newStream; newTrack = newStream.getVideoTracks()[0]; audiotrack = newStream.getAudioTracks()[0]; isnot(newTrack, sender.track, "replacing with a different track"); From 028f3e78aca34798b8762e6cc30642af2a6bdf76 Mon Sep 17 00:00:00 2001 From: Andreas Pehrson Date: Thu, 21 May 2015 13:35:29 +0800 Subject: [PATCH 55/89] Bug 1166183 - Reset PipelineListener's flag after ReplaceTrack(). r=bwc --- .../signaling/src/mediapipeline/MediaPipeline.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp index f60a6f128d4..0374184a510 100644 --- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp +++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp @@ -630,12 +630,10 @@ void MediaPipelineTransmit::AttachToTrack(const std::string& track_id) { stream_->AddListener(listener_); - // Is this a gUM mediastream? If so, also register the Listener directly with - // the SourceMediaStream that's attached to the TrackUnion so we can get direct - // unqueued (and not resampled) data - if (domstream_->AddDirectListener(listener_)) { - listener_->direct_connect_ = true; - } + // Is this a gUM mediastream? If so, also register the Listener directly with + // the SourceMediaStream that's attached to the TrackUnion so we can get direct + // unqueued (and not resampled) data + listener_->direct_connect_ = domstream_->AddDirectListener(listener_); #ifndef MOZILLA_INTERNAL_API // this enables the unit tests that can't fiddle with principals and the like From 874588c43a4feebe8579a475b67de7b8e953b27e Mon Sep 17 00:00:00 2001 From: Blake Kaplan Date: Thu, 21 May 2015 14:57:00 -0400 Subject: [PATCH 56/89] Bug 1167412 - Always register testing JS modules. r=ted --- testing/mochitest/chrome-harness.js | 16 ---------------- .../components/SpecialPowersObserver.js | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/testing/mochitest/chrome-harness.js b/testing/mochitest/chrome-harness.js index 32388c038ad..83e8c5c67ec 100644 --- a/testing/mochitest/chrome-harness.js +++ b/testing/mochitest/chrome-harness.js @@ -293,23 +293,7 @@ function readConfig(filename) { return JSON.parse(str); } -function registerTests() { - var testsURI = Components.classes["@mozilla.org/file/directory_service;1"]. - getService(Components.interfaces.nsIProperties). - get("ProfD", Components.interfaces.nsILocalFile); - testsURI.append("tests.manifest"); - var ioSvc = Components.classes["@mozilla.org/network/io-service;1"]. - getService(Components.interfaces.nsIIOService); - var manifestFile = ioSvc.newFileURI(testsURI). - QueryInterface(Components.interfaces.nsIFileURL).file; - - Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar). - autoRegister(manifestFile); -} - function getTestList(params, callback) { - registerTests(); - var baseurl = 'chrome://mochitests/content'; if (window.parseQueryString) { params = parseQueryString(location.search.substring(1), true); diff --git a/testing/specialpowers/components/SpecialPowersObserver.js b/testing/specialpowers/components/SpecialPowersObserver.js index b1ae61f89e8..14d2b1ecc3d 100644 --- a/testing/specialpowers/components/SpecialPowersObserver.js +++ b/testing/specialpowers/components/SpecialPowersObserver.js @@ -116,6 +116,20 @@ SpecialPowersObserver.prototype = new SpecialPowersObserverAPI(); var obs = Services.obs; obs.addObserver(this, "xpcom-shutdown", false); obs.addObserver(this, "chrome-document-global-created", false); + + // Register special testing modules. + var testsURI = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties). + get("ProfD", Ci.nsILocalFile); + testsURI.append("tests.manifest"); + var ioSvc = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + var manifestFile = ioSvc.newFileURI(testsURI). + QueryInterface(Ci.nsIFileURL).file; + + Components.manager.QueryInterface(Ci.nsIComponentRegistrar). + autoRegister(manifestFile); + obs.addObserver(this, "http-on-modify-request", false); if (messageManager) { From e7ffccf507cab147195909a76793360ba3b740a2 Mon Sep 17 00:00:00 2001 From: chunminchang Date: Sun, 24 May 2015 18:49:00 -0400 Subject: [PATCH 57/89] Bug 1149868 - Move permissionObserver to SpecialPowersObserver to listen all perm-changed signals. r=jmaher --- .../mochitest/tests/Harness_sanity/app.html | 9 ++ .../tests/Harness_sanity/file_app.sjs | 55 +++++++++ .../Harness_sanity/file_app.template.webapp | 6 + .../tests/Harness_sanity/mochitest.ini | 5 + .../test_SpecialPowersPushAppPermissions.html | 111 ++++++++++++++++++ .../components/SpecialPowersObserver.js | 24 ++++ .../content/SpecialPowersObserverAPI.js | 6 +- .../specialpowers/content/specialpowersAPI.js | 103 ++++++++++++++-- 8 files changed, 310 insertions(+), 9 deletions(-) create mode 100644 testing/mochitest/tests/Harness_sanity/app.html create mode 100644 testing/mochitest/tests/Harness_sanity/file_app.sjs create mode 100644 testing/mochitest/tests/Harness_sanity/file_app.template.webapp create mode 100644 testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushAppPermissions.html diff --git a/testing/mochitest/tests/Harness_sanity/app.html b/testing/mochitest/tests/Harness_sanity/app.html new file mode 100644 index 00000000000..7dd21adabcd --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/app.html @@ -0,0 +1,9 @@ + + + + + empty app + + + + diff --git a/testing/mochitest/tests/Harness_sanity/file_app.sjs b/testing/mochitest/tests/Harness_sanity/file_app.sjs new file mode 100644 index 00000000000..2d04205fb0b --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/file_app.sjs @@ -0,0 +1,55 @@ +var gBasePath = "tests/testing/mochitest/tests/Harness_sanity/"; + +function handleRequest(request, response) { + var query = getQuery(request); + + var testToken = ''; + if ('testToken' in query) { + testToken = query.testToken; + } + + var template = ''; + if ('template' in query) { + template = query.template; + } + var template = gBasePath + template; + response.setHeader("Content-Type", "application/x-web-app-manifest+json", false); + response.write(readTemplate(template).replace(/TESTTOKEN/g, testToken)); +} + +// Copy-pasted incantations. There ought to be a better way to synchronously read +// a file into a string, but I guess we're trying to discourage that. +function readTemplate(path) { + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsILocalFile); + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. + createInstance(Components.interfaces.nsIFileInputStream); + var cis = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Components.interfaces.nsIConverterInputStream); + var split = path.split("/"); + for(var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + fis.init(file, -1, -1, false); + cis.init(fis, "UTF-8", 0, 0); + + var data = ""; + let str = {}; + let read = 0; + do { + read = cis.readString(0xffffffff, str); // read as much as we can and put it in str.value + data += str.value; + } while (read != 0); + cis.close(); + return data; +} + +function getQuery(request) { + var query = {}; + request.queryString.split('&').forEach(function (val) { + var [name, value] = val.split('='); + query[name] = unescape(value); + }); + return query; +} diff --git a/testing/mochitest/tests/Harness_sanity/file_app.template.webapp b/testing/mochitest/tests/Harness_sanity/file_app.template.webapp new file mode 100644 index 00000000000..d43cd06e113 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/file_app.template.webapp @@ -0,0 +1,6 @@ +{ + "name": "EMPTY-APP", + "description": "Empty app for Harness_sanity.", + "launch_path": "/tests/testing/mochitest/tests/Harness_sanity/TESTTOKEN", + "icons": { "128": "default_icon" } +} diff --git a/testing/mochitest/tests/Harness_sanity/mochitest.ini b/testing/mochitest/tests/Harness_sanity/mochitest.ini index 0eefc3feea7..44ef14efc7f 100644 --- a/testing/mochitest/tests/Harness_sanity/mochitest.ini +++ b/testing/mochitest/tests/Harness_sanity/mochitest.ini @@ -12,6 +12,11 @@ skip-if = (toolkit == 'android' && processor == 'x86') #x86 only [test_SpecialPowersExtension2.html] support-files = file_SpecialPowersFrame1.html [test_SpecialPowersPushPermissions.html] +[test_SpecialPowersPushAppPermissions.html] +support-files = + file_app.sjs + file_app.template.webapp + app.html [test_SpecialPowersPushPrefEnv.html] [test_SimpletestGetTestFileURL.html] [test_SpecialPowersLoadChromeScript.html] diff --git a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushAppPermissions.html b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushAppPermissions.html new file mode 100644 index 00000000000..1b516fbeaf3 --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushAppPermissions.html @@ -0,0 +1,111 @@ + + + + Test for SpecialPowers extension + + + + + +
+
+
+ + + diff --git a/testing/specialpowers/components/SpecialPowersObserver.js b/testing/specialpowers/components/SpecialPowersObserver.js index 14d2b1ecc3d..95f51445540 100644 --- a/testing/specialpowers/components/SpecialPowersObserver.js +++ b/testing/specialpowers/components/SpecialPowersObserver.js @@ -146,6 +146,9 @@ SpecialPowersObserver.prototype = new SpecialPowersObserverAPI(); obs.removeObserver(this, "chrome-document-global-created"); obs.removeObserver(this, "http-on-modify-request"); obs.removeObserver(this, "xpcom-shutdown"); + this._registerObservers._topics.forEach(function(element) { + obs.removeObserver(this._registerObservers, element); + }); this._removeProcessCrashObservers(); if (this._isFrameScriptLoaded) { @@ -200,6 +203,27 @@ SpecialPowersObserver.prototype = new SpecialPowersObserverAPI(); this._processCrashObserversRegistered = false; }; + SpecialPowersObserver.prototype._registerObservers = { + _self: null, + _topics: [], + _add: function(topic) { + if (this._topics.indexOf(topic) < 0) { + this._topics.push(topic); + Services.obs.addObserver(this, topic, false); + } + }, + observe: function (aSubject, aTopic, aData) { + var msg = { aData: aData }; + switch (aTopic) { + case "perm-changed": + var permission = aSubject.QueryInterface(Ci.nsIPermission); + msg.permission = { appId: permission.appId, type: permission.type }; + default: + this._self._sendAsyncMessage("specialpowers-" + aTopic, msg); + } + } + }; + /** * messageManager callback function * This will get requests from our API in the window and process them in chrome for it diff --git a/testing/specialpowers/content/SpecialPowersObserverAPI.js b/testing/specialpowers/content/SpecialPowersObserverAPI.js index a0d94039ebb..42f4d0daa4c 100644 --- a/testing/specialpowers/content/SpecialPowersObserverAPI.js +++ b/testing/specialpowers/content/SpecialPowersObserverAPI.js @@ -409,12 +409,16 @@ SpecialPowersObserverAPI.prototype = { } case "SPObserverService": { + let topic = aMessage.json.observerTopic; switch (aMessage.json.op) { case "notify": - let topic = aMessage.json.observerTopic; let data = aMessage.json.observerData Services.obs.notifyObservers(null, topic, data); break; + case "add": + this._registerObservers._self = this; + this._registerObservers._add(topic); + break; default: throw new SpecialPowersError("Invalid operation for SPObserverervice"); } diff --git a/testing/specialpowers/content/specialpowersAPI.js b/testing/specialpowers/content/specialpowersAPI.js index d16dc3dedd8..d1442c6955e 100644 --- a/testing/specialpowers/content/specialpowersAPI.js +++ b/testing/specialpowers/content/specialpowersAPI.js @@ -44,6 +44,7 @@ function SpecialPowersAPI() { this._permissionsUndoStack = []; this._pendingPermissions = []; this._applyingPermissions = false; + this._observingPermissions = false; this._fm = null; this._cb = null; this._quotaManagerCallbackInfos = null; @@ -830,6 +831,21 @@ SpecialPowersAPI.prototype = { // that the callback checks for. The second delay is because pref // observers often defer making their changes by posting an event to the // event loop. + if (!this._observingPermissions) { + this._observingPermissions = true; + // If specialpowers is in main-process, then we can add a observer + // to get all 'perm-changed' signals. Otherwise, it can't receive + // all signals, so we register a observer in specialpowersobserver(in + // main-process) and get signals from it. + if (this.isMainProcess()) { + this.permissionObserverProxy._specialPowersAPI = this; + Services.obs.addObserver(this.permissionObserverProxy, "perm-changed", false); + } else { + this.registerObservers("perm-changed"); + // bind() is used to set 'this' to SpecialPowersAPI itself. + this._addMessageListener("specialpowers-perm-changed", this.permChangedProxy.bind(this)); + } + } this._permissionsUndoStack.push(cleanupPermissions); this._pendingPermissions.push([pendingPermissions, this._delayCallbackTwice(callback)]); @@ -839,6 +855,51 @@ SpecialPowersAPI.prototype = { } }, + /* + * This function should be used when specialpowers is in content process but + * it want to get the notification from chrome space. + * + * This function will call Services.obs.addObserver in SpecialPowersObserver + * (that is in chrome process) and forward the data received to SpecialPowers + * via messageManager. + * You can use this._addMessageListener("specialpowers-YOUR_TOPIC") to fire + * the callback. + * + * To get the expected data, you should modify + * SpecialPowersObserver.prototype._registerObservers.observe. Or the message + * you received from messageManager will only contain 'aData' from Service.obs. + * + * NOTICE: there is no implementation of _addMessageListener in + * ChromePowers.js + */ + registerObservers: function(topic) { + var msg = { + 'op': 'add', + 'observerTopic': topic, + }; + this._sendSyncMessage("SPObserverService", msg); + }, + + permChangedProxy: function(aMessage) { + let permission = aMessage.json.permission; + let aData = aMessage.json.aData; + this._permissionObserver.observe(permission, aData); + }, + + permissionObserverProxy: { + // 'this' in permChangedObserverProxy is the permChangedObserverProxy + // object itself. The '_specialPowersAPI' will be set to the 'SpecialPowersAPI' + // object to call the member function in SpecialPowersAPI. + _specialPowersAPI: null, + observe: function (aSubject, aTopic, aData) + { + if (aTopic == "perm-changed") { + var permission = aSubject.QueryInterface(Ci.nsIPermission); + this._specialPowersAPI._permissionObserver.observe(permission, aData); + } + } + }, + popPermissions: function(callback) { if (this._permissionsUndoStack.length > 0) { // See pushPermissions comment regarding delay. @@ -847,7 +908,11 @@ SpecialPowersAPI.prototype = { this._pendingPermissions.push([this._permissionsUndoStack.pop(), cb]); this._applyPermissions(); } else { - this._setTimeout(callback); + if (this._observingPermissions) { + this._observingPermissions = false; + this._removeMessageListener("specialpowers-perm-changed", this.permChangedProxy.bind(this)); + } + this._setTimeout(callback); } }, @@ -870,15 +935,39 @@ SpecialPowersAPI.prototype = { _lastPermission: {}, _callBack: null, _nextCallback: null, - - observe: function (aSubject, aTopic, aData) + _obsDataMap: { + 'deleted':'remove', + 'added':'add' + }, + observe: function (permission, aData) { - if (aTopic == "perm-changed") { - var permission = aSubject.QueryInterface(Ci.nsIPermission); + if (this._self._applyingPermissions) { if (permission.type == this._lastPermission.type) { - Services.obs.removeObserver(this, "perm-changed"); this._self._setTimeout(this._callback); this._self._setTimeout(this._nextCallback); + this._callback = null; + this._nextCallback = null; + } + } else { + var found = false; + for (var i = 0; !found && i < this._self._permissionsUndoStack.length; i++) { + var undos = this._self._permissionsUndoStack[i]; + for (var j = 0; j < undos.length; j++) { + var undo = undos[j]; + if (undo.op == this._obsDataMap[aData] && + undo.appId == permission.appId && + undo.type == permission.type) { + // Remove this undo item if it has been done by others(not + // specialpowers itself.) + undos.splice(j,1); + found = true; + break; + } + } + if (!undos.length) { + // Remove the empty row in permissionsUndoStack + this._self._permissionsUndoStack.splice(i, 1); + } } } } @@ -910,8 +999,6 @@ SpecialPowersAPI.prototype = { self._applyPermissions(); } - Services.obs.addObserver(this._permissionObserver, "perm-changed", false); - for (var idx in pendingActions) { var perm = pendingActions[idx]; this._sendSyncMessage('SPPermissionManager', perm)[0]; From 2635954b4fce79ab3c8850bf8fa721ea3912c095 Mon Sep 17 00:00:00 2001 From: Michael Layzell Date: Fri, 22 May 2015 10:10:00 -0400 Subject: [PATCH 58/89] Bug 1167396 - Make ProtocolCloneContext::mContentParent a smart pointer. r=bent --- ipc/glue/ProtocolUtils.cpp | 12 ++++++++++++ ipc/glue/ProtocolUtils.h | 14 +++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/ipc/glue/ProtocolUtils.cpp b/ipc/glue/ProtocolUtils.cpp index 07daf3ba2ad..ee5995602ad 100644 --- a/ipc/glue/ProtocolUtils.cpp +++ b/ipc/glue/ProtocolUtils.cpp @@ -26,6 +26,18 @@ using base::ProcessId; namespace mozilla { namespace ipc { +ProtocolCloneContext::ProtocolCloneContext() + : mNeckoParent(nullptr) +{} + +ProtocolCloneContext::~ProtocolCloneContext() +{} + +void ProtocolCloneContext::SetContentParent(ContentParent* aContentParent) +{ + mContentParent = aContentParent; +} + static StaticMutex gProtocolMutex; IToplevelProtocol::IToplevelProtocol(ProtocolId aProtoId) diff --git a/ipc/glue/ProtocolUtils.h b/ipc/glue/ProtocolUtils.h index af93438cc11..5898d71f390 100644 --- a/ipc/glue/ProtocolUtils.h +++ b/ipc/glue/ProtocolUtils.h @@ -126,19 +126,15 @@ class ProtocolCloneContext typedef mozilla::dom::ContentParent ContentParent; typedef mozilla::net::NeckoParent NeckoParent; - ContentParent* mContentParent; + nsRefPtr mContentParent; NeckoParent* mNeckoParent; public: - ProtocolCloneContext() - : mContentParent(nullptr) - , mNeckoParent(nullptr) - {} + ProtocolCloneContext(); - void SetContentParent(ContentParent* aContentParent) - { - mContentParent = aContentParent; - } + ~ProtocolCloneContext(); + + void SetContentParent(ContentParent* aContentParent); ContentParent* GetContentParent() { return mContentParent; } From 725659916e2c95bab3cee9eb0ff5ed1f394f70de Mon Sep 17 00:00:00 2001 From: Franziskus Kiefer Date: Thu, 14 May 2015 11:07:38 -0700 Subject: [PATCH 59/89] Bug 1161221 - Split http referrer tests and enable them on all platforms except https on b2g. r=sstamm --- dom/base/test/bug704320.sjs | 5 +- dom/base/test/mochitest.ini | 12 +- dom/base/test/referrerHelper.js | 163 ++++++++++- dom/base/test/test_bug1163743.html | 2 +- dom/base/test/test_bug704320.html | 252 ------------------ dom/base/test/test_bug704320_http_http.html | 62 +++++ dom/base/test/test_bug704320_http_https.html | 62 +++++ dom/base/test/test_bug704320_https_http.html | 62 +++++ dom/base/test/test_bug704320_https_https.html | 62 +++++ dom/base/test/test_bug704320_policyset.html | 18 +- dom/base/test/test_bug704320_preload.html | 46 +--- 11 files changed, 434 insertions(+), 312 deletions(-) delete mode 100644 dom/base/test/test_bug704320.html create mode 100644 dom/base/test/test_bug704320_http_http.html create mode 100644 dom/base/test/test_bug704320_http_https.html create mode 100644 dom/base/test/test_bug704320_https_http.html create mode 100644 dom/base/test/test_bug704320_https_https.html diff --git a/dom/base/test/bug704320.sjs b/dom/base/test/bug704320.sjs index 652a442c674..b154475d05b 100644 --- a/dom/base/test/bug704320.sjs +++ b/dom/base/test/bug704320.sjs @@ -80,6 +80,7 @@ function createTest(schemeFrom, schemeTo, policy) { \n\ \n\ - - - -Mozilla Bug 704320 -

-
-
-
-
- - - - - diff --git a/dom/base/test/test_bug704320_http_http.html b/dom/base/test/test_bug704320_http_http.html new file mode 100644 index 00000000000..cd5fc27d995 --- /dev/null +++ b/dom/base/test/test_bug704320_http_http.html @@ -0,0 +1,62 @@ + + + + + + Test for Bug 704320 - HTTP to HTTP + + + + + + + + +Mozilla Bug 704320 - HTTP to HTTP +

+
+
+ + + + + diff --git a/dom/base/test/test_bug704320_http_https.html b/dom/base/test/test_bug704320_http_https.html new file mode 100644 index 00000000000..5ec91802d48 --- /dev/null +++ b/dom/base/test/test_bug704320_http_https.html @@ -0,0 +1,62 @@ + + + + + + Test for Bug 704320 - HTTP to HTTPS + + + + + + + + +Mozilla Bug 704320 - HTTP to HTTPS +

+
+
+ + + + + diff --git a/dom/base/test/test_bug704320_https_http.html b/dom/base/test/test_bug704320_https_http.html new file mode 100644 index 00000000000..aaf03a8329f --- /dev/null +++ b/dom/base/test/test_bug704320_https_http.html @@ -0,0 +1,62 @@ + + + + + + Test for Bug 704320 - HTTPS to HTTP + + + + + + + + +Mozilla Bug 704320 - HTTPS to HTTP +

+
+
+ + + + + diff --git a/dom/base/test/test_bug704320_https_https.html b/dom/base/test/test_bug704320_https_https.html new file mode 100644 index 00000000000..5148fbfab84 --- /dev/null +++ b/dom/base/test/test_bug704320_https_https.html @@ -0,0 +1,62 @@ + + + + + + Test for Bug 704320 - HTTPS to HTTPS + + + + + + + + +Mozilla Bug 704320 - HTTPS to HTTPS +

+
+
+ + + + + diff --git a/dom/base/test/test_bug704320_policyset.html b/dom/base/test/test_bug704320_policyset.html index 411ce94721f..7c608ad2b19 100644 --- a/dom/base/test/test_bug704320_policyset.html +++ b/dom/base/test/test_bug704320_policyset.html @@ -37,7 +37,7 @@ var tests = (function() { yield iframe.src = sjs + "&policy=" + escape('default'); // check the first test (two images, no referrers) - yield checkResults("default", ["full"]); + yield checkIndividualResults("default", ["full"]); // check invalid policy // According to the spec section 6.4, if there is a policy token @@ -45,7 +45,7 @@ var tests = (function() { // should be the policy used. yield resetCounter(); yield iframe.src = sjs + "&policy=" + escape('invalid-policy'); - yield checkResults("invalid", ["none"]); + yield checkIndividualResults("invalid", ["none"]); // whitespace checks. // according to the spec section 4.1, the content attribute's value @@ -53,20 +53,20 @@ var tests = (function() { // trailing whitespace. yield resetCounter(); yield iframe.src = sjs + "&policy=" + escape('default '); - yield checkResults("trailing whitespace", ["full"]); + yield checkIndividualResults("trailing whitespace", ["full"]); yield resetCounter(); yield iframe.src = sjs + "&policy=" + escape(' origin\f'); - yield checkResults("trailing form feed", ["origin"]); + yield checkIndividualResults("trailing form feed", ["origin"]); yield resetCounter(); yield iframe.src = sjs + "&policy=" + escape('\f origin'); - yield checkResults("leading form feed", ["origin"]); + yield checkIndividualResults("leading form feed", ["origin"]); // origin when cross-origin (trimming whitespace) yield resetCounter(); yield iframe.src = sjs + "&policy=" + escape(' origin-when-cross-origin'); - yield checkResults("origin-when-cross-origin", ["origin", "full"]); + yield checkIndividualResults("origin-when-cross-origin", ["origin", "full"]); // according to the spec section 4.1: // "If the meta element lacks a content attribute, or if that attribute’s @@ -77,16 +77,16 @@ var tests = (function() { // http://www.w3.org/html/wg/drafts/html/CR/infrastructure.html#space-character yield resetCounter(); yield iframe.src = sjs + "&policy=" + escape(' \t '); - yield checkResults("basic whitespace only policy", ["full"]); + yield checkIndividualResults("basic whitespace only policy", ["full"]); yield resetCounter(); yield iframe.src = sjs + "&policy=" + escape(' \f\r\n\t '); - yield checkResults("whitespace only policy", ["full"]); + yield checkIndividualResults("whitespace only policy", ["full"]); // and double-check that no-referrer works. yield resetCounter(); yield iframe.src = sjs + "&policy=" + escape('no-referrer'); - yield checkResults("no-referrer", ["none"]); + yield checkIndividualResults("no-referrer", ["none"]); // complete. Be sure to yield so we don't call this twice. yield SimpleTest.finish(); diff --git a/dom/base/test/test_bug704320_preload.html b/dom/base/test/test_bug704320_preload.html index 3adfdff1199..d7f2fc72d39 100644 --- a/dom/base/test/test_bug704320_preload.html +++ b/dom/base/test/test_bug704320_preload.html @@ -9,6 +9,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=704320 Test preloads for Bug 704320 + From da54b2402eef6a937234744af6fe7da5ab21b39f Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Mon, 25 May 2015 12:11:07 -0400 Subject: [PATCH 71/89] Backed out changeset a3d6ad2faa3a (bug 996652) for checktest bustage. --- testing/xpcshell/head.js | 76 +++++++--------------------------------- 1 file changed, 13 insertions(+), 63 deletions(-) diff --git a/testing/xpcshell/head.js b/testing/xpcshell/head.js index e593e8e9064..829bc113434 100644 --- a/testing/xpcshell/head.js +++ b/testing/xpcshell/head.js @@ -10,7 +10,6 @@ * for more information. */ -(function(exports) { var _quit = false; var _passed = true; var _tests_pending = 0; @@ -196,7 +195,7 @@ _Timer.prototype = { }; function _do_main() { - if (exports._quit) + if (_quit) return; _testLogger.info("running event loop"); @@ -204,7 +203,7 @@ function _do_main() { var thr = Components.classes["@mozilla.org/thread-manager;1"] .getService().currentThread; - while (!exports._quit) + while (!_quit) thr.processNextEvent(true); while (thr.hasPendingEvents()) @@ -214,7 +213,7 @@ function _do_main() { function _do_quit() { _testLogger.info("exiting test"); _Promise.Debugging.flushUncaughtErrors(); - exports._quit = true; + _quit = true; } /** @@ -494,6 +493,12 @@ function _execute_test() { // _TEST_FILE is dynamically defined by . _load_files(_TEST_FILE); + // Tack Assert.jsm methods to the current scope. + this.Assert = Assert; + for (let func in Assert) { + this[func] = Assert[func].bind(Assert); + } + try { do_test_pending("MAIN run_test"); // Check if run_test() is defined. If defined, run it. @@ -508,12 +513,12 @@ function _execute_test() { _do_main(); } catch (e) { _passed = false; - // do_check failures are already logged and set exports._quit to true and throw + // do_check failures are already logged and set _quit to true and throw // NS_ERROR_ABORT. If both of those are true it is likely this exception // has already been logged so there is no need to log it again. It's // possible that this will mask an NS_ERROR_ABORT that happens after a // do_check failure though. - if (!exports._quit || e != Components.results.NS_ERROR_ABORT) { + if (!_quit || e != Components.results.NS_ERROR_ABORT) { let extra = {}; if (e.fileName) { extra.source_file = e.fileName; @@ -647,12 +652,12 @@ function do_execute_soon(callback, aName) { try { callback(); } catch (e) { - // do_check failures are already logged and set exports._quit to true and throw + // do_check failures are already logged and set _quit to true and throw // NS_ERROR_ABORT. If both of those are true it is likely this exception // has already been logged so there is no need to log it again. It's // possible that this will mask an NS_ERROR_ABORT that happens after a // do_check failure though. - if (!exports._quit || e != Components.results.NS_ERROR_ABORT) { + if (!_quit || e != Components.results.NS_ERROR_ABORT) { let stack = e.stack ? _format_stack(e.stack) : null; _testLogger.testStatus(_TEST_NAME, funcName, @@ -1493,58 +1498,3 @@ try { prefs.deleteBranch("browser.devedition.theme.enabled"); } } catch (e) { } - -exports._execute_test = _execute_test; -exports._setupDebuggerServer = _setupDebuggerServer; -exports._quit = _quit; -exports._testLogger = _testLogger; -exports._wrap_with_quotes_if_necessary = _wrap_with_quotes_if_necessary; -exports.Assert = Assert; -exports.add_task = add_task; -exports.add_test = add_test; -exports.do_await_remote_message = do_await_remote_message; -exports.do_check_eq = do_check_eq; -exports.do_check_false = do_check_false; -exports.do_check_instanceof = do_check_instanceof; -exports.do_check_matches = do_check_matches; -exports.do_check_neq = do_check_neq; -exports.do_check_null = do_check_null; -exports.do_check_throws_nsIException = do_check_throws_nsIException; -exports.do_check_true = do_check_true; -exports.do_execute_soon = do_execute_soon; -exports.do_get_cwd = do_get_cwd; -exports.do_get_file = do_get_file; -exports.do_get_idle = do_get_idle; -exports.do_get_minidumpdir = do_get_minidumpdir; -exports.do_get_profile = do_get_profile; -exports.do_get_tempdir = do_get_tempdir; -exports.do_load_child_test_harness = do_load_child_test_harness; -exports.do_load_manifest = do_load_manifest; -exports.do_note_exception = do_note_exception; -exports.do_parse_document = do_parse_document; -exports.do_print = do_print; -exports.do_register_cleanup = do_register_cleanup; -exports.do_report_result = do_report_result; -exports.do_report_unexpected_exception = do_report_unexpected_exception; -exports.do_send_remote_message = do_send_remote_message; -exports.do_test_finished = do_test_finished; -exports.do_test_pending = do_test_pending; -exports.do_throw = do_throw; -exports.do_timeout = do_timeout; -exports.legible_exception = legible_exception; -exports.run_next_test = run_next_test; -exports.run_test_in_child = run_test_in_child; -exports.runningInParent = runningInParent; -exports.todo_check_eq = todo_check_eq; -exports.todo_check_false = todo_check_false; -exports.todo_check_instanceof = todo_check_instanceof; -exports.todo_check_neq = todo_check_neq; -exports.todo_check_null = todo_check_null; -exports.todo_check_true = todo_check_true; - -// Tack Assert.jsm methods to the current scope. -exports.Assert = Assert; -for (let func in Assert) { - exports[func] = Assert[func].bind(Assert); -} -} From b66ef4e88caa1e3f2d8657b047def7dc5bec084b Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Mon, 25 May 2015 17:53:07 +0100 Subject: [PATCH 72/89] Bug 1155761 - User Timing API in Workers, r=ehsan --- dom/base/PerformanceEntry.cpp | 9 +- dom/base/PerformanceEntry.h | 11 +- dom/base/PerformanceMark.cpp | 12 +- dom/base/PerformanceMark.h | 5 +- dom/base/PerformanceMeasure.cpp | 7 +- dom/base/PerformanceMeasure.h | 2 +- dom/base/nsDOMNavigationTiming.cpp | 3 +- dom/base/nsPerformance.cpp | 631 ++++++++++-------- dom/base/nsPerformance.h | 155 +++-- dom/base/test/mochitest.ini | 1 + .../test/test_performance_user_timing.html | 268 +------- dom/base/test/test_performance_user_timing.js | 272 ++++++++ dom/webidl/Performance.webidl | 24 +- dom/webidl/PerformanceEntry.webidl | 1 + dom/webidl/PerformanceMark.webidl | 1 + dom/webidl/PerformanceMeasure.webidl | 1 + dom/workers/Performance.cpp | 48 +- dom/workers/Performance.h | 32 +- dom/workers/WorkerPrivate.cpp | 7 +- dom/workers/WorkerPrivate.h | 12 + dom/workers/test/mochitest.ini | 4 + .../test_serviceworker_interfaces.js | 6 + .../sharedworker_performance_user_timing.js | 30 + .../test/test_performance_user_timing.html | 27 + ..._sharedWorker_performance_user_timing.html | 30 + dom/workers/test/test_worker_interfaces.js | 6 + .../test/worker_performance_user_timing.js | 24 + 27 files changed, 996 insertions(+), 633 deletions(-) create mode 100644 dom/base/test/test_performance_user_timing.js create mode 100644 dom/workers/test/sharedworker_performance_user_timing.js create mode 100644 dom/workers/test/test_performance_user_timing.html create mode 100644 dom/workers/test/test_sharedWorker_performance_user_timing.html create mode 100644 dom/workers/test/worker_performance_user_timing.js diff --git a/dom/base/PerformanceEntry.cpp b/dom/base/PerformanceEntry.cpp index 0f6fc9c1d0f..3b9b15cefa9 100644 --- a/dom/base/PerformanceEntry.cpp +++ b/dom/base/PerformanceEntry.cpp @@ -10,7 +10,7 @@ using namespace mozilla::dom; -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PerformanceEntry, mPerformance) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PerformanceEntry, mParent) NS_IMPL_CYCLE_COLLECTING_ADDREF(PerformanceEntry) NS_IMPL_CYCLE_COLLECTING_RELEASE(PerformanceEntry) @@ -20,14 +20,15 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceEntry) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END -PerformanceEntry::PerformanceEntry(nsPerformance* aPerformance, +PerformanceEntry::PerformanceEntry(nsISupports* aParent, const nsAString& aName, const nsAString& aEntryType) -: mPerformance(aPerformance), +: mParent(aParent), mName(aName), mEntryType(aEntryType) { - MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); + // mParent is null in workers. + MOZ_ASSERT(mParent || !NS_IsMainThread()); } PerformanceEntry::~PerformanceEntry() diff --git a/dom/base/PerformanceEntry.h b/dom/base/PerformanceEntry.h index 9efdb571334..4489fb8a0ac 100644 --- a/dom/base/PerformanceEntry.h +++ b/dom/base/PerformanceEntry.h @@ -7,9 +7,10 @@ #ifndef mozilla_dom_PerformanceEntry_h___ #define mozilla_dom_PerformanceEntry_h___ -#include "nsPerformance.h" #include "nsDOMNavigationTiming.h" +class nsISupports; + namespace mozilla { namespace dom { @@ -21,7 +22,7 @@ protected: virtual ~PerformanceEntry(); public: - PerformanceEntry(nsPerformance* aPerformance, + PerformanceEntry(nsISupports* aParent, const nsAString& aName, const nsAString& aEntryType); @@ -30,9 +31,9 @@ public: virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - nsPerformance* GetParentObject() const + nsISupports* GetParentObject() const { - return mPerformance; + return mParent; } void GetName(nsAString& aName) const @@ -76,7 +77,7 @@ public: } protected: - nsRefPtr mPerformance; + nsCOMPtr mParent; nsString mName; nsString mEntryType; }; diff --git a/dom/base/PerformanceMark.cpp b/dom/base/PerformanceMark.cpp index 451cbcce426..908793c58ef 100644 --- a/dom/base/PerformanceMark.cpp +++ b/dom/base/PerformanceMark.cpp @@ -9,12 +9,14 @@ using namespace mozilla::dom; -PerformanceMark::PerformanceMark(nsPerformance* aPerformance, - const nsAString& aName) -: PerformanceEntry(aPerformance, aName, NS_LITERAL_STRING("mark")) +PerformanceMark::PerformanceMark(nsISupports* aParent, + const nsAString& aName, + DOMHighResTimeStamp aStartTime) + : PerformanceEntry(aParent, aName, NS_LITERAL_STRING("mark")) + , mStartTime(aStartTime) { - MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); - mStartTime = aPerformance->GetDOMTiming()->TimeStampToDOMHighRes(mozilla::TimeStamp::Now()); + // mParent is null in workers. + MOZ_ASSERT(mParent || !NS_IsMainThread()); } PerformanceMark::~PerformanceMark() diff --git a/dom/base/PerformanceMark.h b/dom/base/PerformanceMark.h index 69c2e06d64c..52ad49805c0 100644 --- a/dom/base/PerformanceMark.h +++ b/dom/base/PerformanceMark.h @@ -16,8 +16,9 @@ namespace dom { class PerformanceMark final : public PerformanceEntry { public: - PerformanceMark(nsPerformance* aPerformance, - const nsAString& aName); + PerformanceMark(nsISupports* aParent, + const nsAString& aName, + DOMHighResTimeStamp aStartTime); virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; diff --git a/dom/base/PerformanceMeasure.cpp b/dom/base/PerformanceMeasure.cpp index 83555db656c..615f992d9ce 100644 --- a/dom/base/PerformanceMeasure.cpp +++ b/dom/base/PerformanceMeasure.cpp @@ -9,15 +9,16 @@ using namespace mozilla::dom; -PerformanceMeasure::PerformanceMeasure(nsPerformance* aPerformance, +PerformanceMeasure::PerformanceMeasure(nsISupports* aParent, const nsAString& aName, DOMHighResTimeStamp aStartTime, DOMHighResTimeStamp aEndTime) -: PerformanceEntry(aPerformance, aName, NS_LITERAL_STRING("measure")), +: PerformanceEntry(aParent, aName, NS_LITERAL_STRING("measure")), mStartTime(aStartTime), mDuration(aEndTime - aStartTime) { - MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); + // mParent is null in workers. + MOZ_ASSERT(mParent || !NS_IsMainThread()); } PerformanceMeasure::~PerformanceMeasure() diff --git a/dom/base/PerformanceMeasure.h b/dom/base/PerformanceMeasure.h index dc975e2e342..241ec0d5b9f 100644 --- a/dom/base/PerformanceMeasure.h +++ b/dom/base/PerformanceMeasure.h @@ -16,7 +16,7 @@ namespace dom { class PerformanceMeasure final : public PerformanceEntry { public: - PerformanceMeasure(nsPerformance* aPerformance, + PerformanceMeasure(nsISupports* aParent, const nsAString& aName, DOMHighResTimeStamp aStartTime, DOMHighResTimeStamp aEndTime); diff --git a/dom/base/nsDOMNavigationTiming.cpp b/dom/base/nsDOMNavigationTiming.cpp index 9904d89b1d7..92140cb4016 100644 --- a/dom/base/nsDOMNavigationTiming.cpp +++ b/dom/base/nsDOMNavigationTiming.cpp @@ -57,7 +57,8 @@ nsDOMNavigationTiming::TimeStampToDOM(mozilla::TimeStamp aStamp) const return GetNavigationStart() + static_cast(duration.ToMilliseconds()); } -DOMTimeMilliSec nsDOMNavigationTiming::DurationFromStart(){ +DOMTimeMilliSec nsDOMNavigationTiming::DurationFromStart() +{ return TimeStampToDOM(mozilla::TimeStamp::Now()); } diff --git a/dom/base/nsPerformance.cpp b/dom/base/nsPerformance.cpp index 104523ec0e3..f6da11eaf29 100644 --- a/dom/base/nsPerformance.cpp +++ b/dom/base/nsPerformance.cpp @@ -23,9 +23,12 @@ #include "mozilla/dom/PerformanceBinding.h" #include "mozilla/dom/PerformanceTimingBinding.h" #include "mozilla/dom/PerformanceNavigationBinding.h" +#include "mozilla/Preferences.h" #include "mozilla/IntegerPrintfMacros.h" #include "mozilla/TimeStamp.h" #include "js/HeapAPI.h" +#include "WorkerPrivate.h" +#include "WorkerRunnable.h" #ifdef MOZ_WIDGET_GONK #define PERFLOG(msg, ...) __android_log_print(ANDROID_LOG_INFO, "PerformanceTiming", msg, ##__VA_ARGS__) @@ -35,6 +38,7 @@ using namespace mozilla; using namespace mozilla::dom; +using namespace mozilla::dom::workers; NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPerformanceTiming, mPerformance) @@ -398,40 +402,36 @@ nsPerformanceNavigation::WrapObject(JSContext *cx, JS::Handle aGivenP NS_IMPL_CYCLE_COLLECTION_CLASS(nsPerformance) -NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsPerformance, DOMEventTargetHelper) -NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow, mTiming, - mNavigation, mUserEntries, - mResourceEntries, +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsPerformance, PerformanceBase) +NS_IMPL_CYCLE_COLLECTION_UNLINK(mTiming, + mNavigation, mParentPerformance) tmp->mMozMemory = nullptr; mozilla::DropJSObjects(this); NS_IMPL_CYCLE_COLLECTION_UNLINK_END -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsPerformance, DOMEventTargetHelper) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mTiming, - mNavigation, mUserEntries, - mResourceEntries, +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsPerformance, PerformanceBase) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTiming, + mNavigation, mParentPerformance) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END -NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsPerformance, DOMEventTargetHelper) +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsPerformance, PerformanceBase) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMozMemory) NS_IMPL_CYCLE_COLLECTION_TRACE_END -NS_IMPL_ADDREF_INHERITED(nsPerformance, DOMEventTargetHelper) -NS_IMPL_RELEASE_INHERITED(nsPerformance, DOMEventTargetHelper) +NS_IMPL_ADDREF_INHERITED(nsPerformance, PerformanceBase) +NS_IMPL_RELEASE_INHERITED(nsPerformance, PerformanceBase) nsPerformance::nsPerformance(nsPIDOMWindow* aWindow, nsDOMNavigationTiming* aDOMTiming, nsITimedChannel* aChannel, nsPerformance* aParentPerformance) - : DOMEventTargetHelper(aWindow), - mWindow(aWindow), + : PerformanceBase(aWindow), mDOMTiming(aDOMTiming), mChannel(aChannel), - mParentPerformance(aParentPerformance), - mResourceTimingBufferSize(kDefaultResourceTimingBufferSize) + mParentPerformance(aParentPerformance) { MOZ_ASSERT(aWindow, "Parent window object should be provided"); } @@ -499,7 +499,7 @@ nsPerformance::Navigation() } DOMHighResTimeStamp -nsPerformance::Now() +nsPerformance::Now() const { return GetDOMTiming()->TimeStampToDOMHighRes(TimeStamp::Now()); } @@ -510,90 +510,6 @@ nsPerformance::WrapObject(JSContext *cx, JS::Handle aGivenProto) return PerformanceBinding::Wrap(cx, this, aGivenProto); } -void -nsPerformance::GetEntries(nsTArray>& retval) -{ - MOZ_ASSERT(NS_IsMainThread()); - - retval = mResourceEntries; - retval.AppendElements(mUserEntries); - retval.Sort(PerformanceEntryComparator()); -} - -void -nsPerformance::GetEntriesByType(const nsAString& entryType, - nsTArray>& retval) -{ - MOZ_ASSERT(NS_IsMainThread()); - - retval.Clear(); - if (entryType.EqualsLiteral("resource")) { - retval = mResourceEntries; - } else if (entryType.EqualsLiteral("mark") || - entryType.EqualsLiteral("measure")) { - for (PerformanceEntry* entry : mUserEntries) { - if (entry->GetEntryType().Equals(entryType)) { - retval.AppendElement(entry); - } - } - } -} - -void -nsPerformance::GetEntriesByName(const nsAString& name, - const Optional& entryType, - nsTArray>& retval) -{ - MOZ_ASSERT(NS_IsMainThread()); - - retval.Clear(); - for (PerformanceEntry* entry : mResourceEntries) { - if (entry->GetName().Equals(name) && - (!entryType.WasPassed() || - entry->GetEntryType().Equals(entryType.Value()))) { - retval.AppendElement(entry); - } - } - for (PerformanceEntry* entry : mUserEntries) { - if (entry->GetName().Equals(name) && - (!entryType.WasPassed() || - entry->GetEntryType().Equals(entryType.Value()))) { - retval.AppendElement(entry); - } - } - retval.Sort(PerformanceEntryComparator()); -} - -void -nsPerformance::ClearUserEntries(const Optional& aEntryName, - const nsAString& aEntryType) -{ - for (uint32_t i = 0; i < mUserEntries.Length();) { - if ((!aEntryName.WasPassed() || - mUserEntries[i]->GetName().Equals(aEntryName.Value())) && - (aEntryType.IsEmpty() || - mUserEntries[i]->GetEntryType().Equals(aEntryType))) { - mUserEntries.RemoveElementAt(i); - } else { - ++i; - } - } -} - -void -nsPerformance::ClearResourceTimings() -{ - MOZ_ASSERT(NS_IsMainThread()); - mResourceEntries.Clear(); -} - -void -nsPerformance::SetResourceTimingBufferSize(uint64_t maxSize) -{ - MOZ_ASSERT(NS_IsMainThread()); - mResourceTimingBufferSize = maxSize; -} - /** * An entry should be added only after the resource is loaded. * This method is not thread safe and can only be called on the main thread. @@ -609,7 +525,7 @@ nsPerformance::AddEntry(nsIHttpChannel* channel, } // Don't add the entry if the buffer is full - if (mResourceEntries.Length() >= mResourceTimingBufferSize) { + if (IsResourceEntryLimitReached()) { return; } @@ -652,174 +568,6 @@ nsPerformance::AddEntry(nsIHttpChannel* channel, } } -bool -nsPerformance::PerformanceEntryComparator::Equals( - const PerformanceEntry* aElem1, - const PerformanceEntry* aElem2) const -{ - MOZ_ASSERT(aElem1 && aElem2, - "Trying to compare null performance entries"); - return aElem1->StartTime() == aElem2->StartTime(); -} - -bool -nsPerformance::PerformanceEntryComparator::LessThan( - const PerformanceEntry* aElem1, - const PerformanceEntry* aElem2) const -{ - MOZ_ASSERT(aElem1 && aElem2, - "Trying to compare null performance entries"); - return aElem1->StartTime() < aElem2->StartTime(); -} - -void -nsPerformance::InsertResourceEntry(PerformanceEntry* aEntry) -{ - MOZ_ASSERT(aEntry); - MOZ_ASSERT(mResourceEntries.Length() < mResourceTimingBufferSize); - if (mResourceEntries.Length() >= mResourceTimingBufferSize) { - return; - } - mResourceEntries.InsertElementSorted(aEntry, - PerformanceEntryComparator()); - if (mResourceEntries.Length() == mResourceTimingBufferSize) { - // call onresourcetimingbufferfull - DispatchBufferFullEvent(); - } -} - -void -nsPerformance::InsertUserEntry(PerformanceEntry* aEntry) -{ - if (nsContentUtils::IsUserTimingLoggingEnabled()) { - nsAutoCString uri; - nsresult rv = mWindow->GetDocumentURI()->GetHost(uri); - if(NS_FAILED(rv)) { - // If we have no URI, just put in "none". - uri.AssignLiteral("none"); - } - PERFLOG("Performance Entry: %s|%s|%s|%f|%f|%" PRIu64 "\n", - uri.get(), - NS_ConvertUTF16toUTF8(aEntry->GetEntryType()).get(), - NS_ConvertUTF16toUTF8(aEntry->GetName()).get(), - aEntry->StartTime(), - aEntry->Duration(), - static_cast(PR_Now() / PR_USEC_PER_MSEC)); - } - mUserEntries.InsertElementSorted(aEntry, - PerformanceEntryComparator()); -} - -void -nsPerformance::Mark(const nsAString& aName, ErrorResult& aRv) -{ - MOZ_ASSERT(NS_IsMainThread()); - // Don't add the entry if the buffer is full. XXX should be removed by bug 1159003. - if (mUserEntries.Length() >= mResourceTimingBufferSize) { - return; - } - if (IsPerformanceTimingAttribute(aName)) { - aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); - return; - } - nsRefPtr performanceMark = - new PerformanceMark(this, aName); - InsertUserEntry(performanceMark); -} - -void -nsPerformance::ClearMarks(const Optional& aName) -{ - MOZ_ASSERT(NS_IsMainThread()); - ClearUserEntries(aName, NS_LITERAL_STRING("mark")); -} - -DOMHighResTimeStamp -nsPerformance::ResolveTimestampFromName(const nsAString& aName, - ErrorResult& aRv) -{ - nsAutoTArray, 1> arr; - DOMHighResTimeStamp ts; - Optional typeParam; - nsAutoString str; - str.AssignLiteral("mark"); - typeParam = &str; - GetEntriesByName(aName, typeParam, arr); - if (!arr.IsEmpty()) { - return arr.LastElement()->StartTime(); - } - if (!IsPerformanceTimingAttribute(aName)) { - aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); - return 0; - } - ts = GetPerformanceTimingFromString(aName); - if (!ts) { - aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); - return 0; - } - return ConvertDOMMilliSecToHighRes(ts); -} - -void -nsPerformance::Measure(const nsAString& aName, - const Optional& aStartMark, - const Optional& aEndMark, - ErrorResult& aRv) -{ - MOZ_ASSERT(NS_IsMainThread()); - // Don't add the entry if the buffer is full. XXX should be removed by bug 1159003. - if (mUserEntries.Length() >= mResourceTimingBufferSize) { - return; - } - DOMHighResTimeStamp startTime; - DOMHighResTimeStamp endTime; - - if (IsPerformanceTimingAttribute(aName)) { - aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); - return; - } - - if (aStartMark.WasPassed()) { - startTime = ResolveTimestampFromName(aStartMark.Value(), aRv); - if (NS_WARN_IF(aRv.Failed())) { - return; - } - } else { - // Navigation start is used in this case, but since DOMHighResTimeStamp is - // in relation to navigation start, this will be zero if a name is not - // passed. - startTime = 0; - } - if (aEndMark.WasPassed()) { - endTime = ResolveTimestampFromName(aEndMark.Value(), aRv); - if (NS_WARN_IF(aRv.Failed())) { - return; - } - } else { - endTime = Now(); - } - nsRefPtr performanceMeasure = - new PerformanceMeasure(this, aName, startTime, endTime); - InsertUserEntry(performanceMeasure); -} - -void -nsPerformance::ClearMeasures(const Optional& aName) -{ - MOZ_ASSERT(NS_IsMainThread()); - ClearUserEntries(aName, NS_LITERAL_STRING("measure")); -} - -DOMHighResTimeStamp -nsPerformance::ConvertDOMMilliSecToHighRes(DOMTimeMilliSec aTime) { - // If the time we're trying to convert is equal to zero, it hasn't been set - // yet so just return 0. - if (aTime == 0) { - return 0; - } - return aTime - GetDOMTiming()->GetNavigationStart(); -} - // To be removed once bug 1124165 lands bool nsPerformance::IsPerformanceTimingAttribute(const nsAString& aName) @@ -841,7 +589,7 @@ nsPerformance::IsPerformanceTimingAttribute(const nsAString& aName) return false; } -DOMTimeMilliSec +DOMHighResTimeStamp nsPerformance::GetPerformanceTimingFromString(const nsAString& aProperty) { if (!IsPerformanceTimingAttribute(aProperty)) { @@ -913,3 +661,346 @@ nsPerformance::GetPerformanceTimingFromString(const nsAString& aProperty) return 0; } +namespace { + +// Helper classes +class MOZ_STACK_CLASS PerformanceEntryComparator final +{ +public: + bool Equals(const PerformanceEntry* aElem1, + const PerformanceEntry* aElem2) const + { + MOZ_ASSERT(aElem1 && aElem2, + "Trying to compare null performance entries"); + return aElem1->StartTime() == aElem2->StartTime(); + } + + bool LessThan(const PerformanceEntry* aElem1, + const PerformanceEntry* aElem2) const + { + MOZ_ASSERT(aElem1 && aElem2, + "Trying to compare null performance entries"); + return aElem1->StartTime() < aElem2->StartTime(); + } +}; + +class PrefEnabledRunnable final : public WorkerMainThreadRunnable +{ +public: + explicit PrefEnabledRunnable(WorkerPrivate* aWorkerPrivate) + : WorkerMainThreadRunnable(aWorkerPrivate) + , mEnabled(false) + { } + + bool MainThreadRun() override + { + MOZ_ASSERT(NS_IsMainThread()); + mEnabled = Preferences::GetBool("dom.enable_user_timing", false); + return true; + } + + bool IsEnabled() const + { + return mEnabled; + } + +private: + bool mEnabled; +}; + +} // anonymous namespace + +/* static */ bool +nsPerformance::IsEnabled(JSContext* aCx, JSObject* aGlobal) +{ + if (NS_IsMainThread()) { + return Preferences::GetBool("dom.enable_user_timing", false); + } + + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + workerPrivate->AssertIsOnWorkerThread(); + + nsRefPtr runnable = + new PrefEnabledRunnable(workerPrivate); + runnable->Dispatch(workerPrivate->GetJSContext()); + + return runnable->IsEnabled(); +} + +void +nsPerformance::InsertUserEntry(PerformanceEntry* aEntry) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (nsContentUtils::IsUserTimingLoggingEnabled()) { + nsAutoCString uri; + nsresult rv = GetOwner()->GetDocumentURI()->GetHost(uri); + if(NS_FAILED(rv)) { + // If we have no URI, just put in "none". + uri.AssignLiteral("none"); + } + PERFLOG("Performance Entry: %s|%s|%s|%f|%f|%" PRIu64 "\n", + uri.get(), + NS_ConvertUTF16toUTF8(aEntry->GetEntryType()).get(), + NS_ConvertUTF16toUTF8(aEntry->GetName()).get(), + aEntry->StartTime(), + aEntry->Duration(), + static_cast(PR_Now() / PR_USEC_PER_MSEC)); + } + + PerformanceBase::InsertUserEntry(aEntry); +} + +DOMHighResTimeStamp +nsPerformance::DeltaFromNavigationStart(DOMHighResTimeStamp aTime) +{ + // If the time we're trying to convert is equal to zero, it hasn't been set + // yet so just return 0. + if (aTime == 0) { + return 0; + } + return aTime - GetDOMTiming()->GetNavigationStart(); +} + +// PerformanceBase + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PerformanceBase) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_CYCLE_COLLECTION_INHERITED(PerformanceBase, + DOMEventTargetHelper, + mUserEntries, + mResourceEntries); + +NS_IMPL_ADDREF_INHERITED(PerformanceBase, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(PerformanceBase, DOMEventTargetHelper) + +PerformanceBase::PerformanceBase() + : mResourceTimingBufferSize(kDefaultResourceTimingBufferSize) +{ + MOZ_ASSERT(!NS_IsMainThread()); +} + +PerformanceBase::PerformanceBase(nsPIDOMWindow* aWindow) + : DOMEventTargetHelper(aWindow) + , mResourceTimingBufferSize(kDefaultResourceTimingBufferSize) +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +PerformanceBase::~PerformanceBase() +{} + +void +PerformanceBase::GetEntries(nsTArray>& aRetval) +{ + aRetval = mResourceEntries; + aRetval.AppendElements(mUserEntries); + aRetval.Sort(PerformanceEntryComparator()); +} + +void +PerformanceBase::GetEntriesByType(const nsAString& aEntryType, + nsTArray>& aRetval) +{ + if (aEntryType.EqualsLiteral("resource")) { + aRetval = mResourceEntries; + return; + } + + aRetval.Clear(); + + if (aEntryType.EqualsLiteral("mark") || + aEntryType.EqualsLiteral("measure")) { + for (PerformanceEntry* entry : mUserEntries) { + if (entry->GetEntryType().Equals(aEntryType)) { + aRetval.AppendElement(entry); + } + } + } +} + +void +PerformanceBase::GetEntriesByName(const nsAString& aName, + const Optional& aEntryType, + nsTArray>& aRetval) +{ + aRetval.Clear(); + + for (PerformanceEntry* entry : mResourceEntries) { + if (entry->GetName().Equals(aName) && + (!aEntryType.WasPassed() || + entry->GetEntryType().Equals(aEntryType.Value()))) { + aRetval.AppendElement(entry); + } + } + + for (PerformanceEntry* entry : mUserEntries) { + if (entry->GetName().Equals(aName) && + (!aEntryType.WasPassed() || + entry->GetEntryType().Equals(aEntryType.Value()))) { + aRetval.AppendElement(entry); + } + } + + aRetval.Sort(PerformanceEntryComparator()); +} + +void +PerformanceBase::ClearUserEntries(const Optional& aEntryName, + const nsAString& aEntryType) +{ + for (uint32_t i = 0; i < mUserEntries.Length();) { + if ((!aEntryName.WasPassed() || + mUserEntries[i]->GetName().Equals(aEntryName.Value())) && + (aEntryType.IsEmpty() || + mUserEntries[i]->GetEntryType().Equals(aEntryType))) { + mUserEntries.RemoveElementAt(i); + } else { + ++i; + } + } +} + +void +PerformanceBase::ClearResourceTimings() +{ + MOZ_ASSERT(NS_IsMainThread()); + mResourceEntries.Clear(); +} + +void +PerformanceBase::Mark(const nsAString& aName, ErrorResult& aRv) +{ + // Don't add the entry if the buffer is full. XXX should be removed by bug 1159003. + if (mUserEntries.Length() >= mResourceTimingBufferSize) { + return; + } + + if (IsPerformanceTimingAttribute(aName)) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + + nsRefPtr performanceMark = + new PerformanceMark(GetAsISupports(), aName, Now()); + InsertUserEntry(performanceMark); +} + +void +PerformanceBase::ClearMarks(const Optional& aName) +{ + ClearUserEntries(aName, NS_LITERAL_STRING("mark")); +} + +DOMHighResTimeStamp +PerformanceBase::ResolveTimestampFromName(const nsAString& aName, + ErrorResult& aRv) +{ + nsAutoTArray, 1> arr; + DOMHighResTimeStamp ts; + Optional typeParam; + nsAutoString str; + str.AssignLiteral("mark"); + typeParam = &str; + GetEntriesByName(aName, typeParam, arr); + if (!arr.IsEmpty()) { + return arr.LastElement()->StartTime(); + } + + if (!IsPerformanceTimingAttribute(aName)) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return 0; + } + + ts = GetPerformanceTimingFromString(aName); + if (!ts) { + aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); + return 0; + } + + return DeltaFromNavigationStart(ts); +} + +void +PerformanceBase::Measure(const nsAString& aName, + const Optional& aStartMark, + const Optional& aEndMark, + ErrorResult& aRv) +{ + // Don't add the entry if the buffer is full. XXX should be removed by bug + // 1159003. + if (mUserEntries.Length() >= mResourceTimingBufferSize) { + return; + } + + DOMHighResTimeStamp startTime; + DOMHighResTimeStamp endTime; + + if (IsPerformanceTimingAttribute(aName)) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + + if (aStartMark.WasPassed()) { + startTime = ResolveTimestampFromName(aStartMark.Value(), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + } else { + // Navigation start is used in this case, but since DOMHighResTimeStamp is + // in relation to navigation start, this will be zero if a name is not + // passed. + startTime = 0; + } + + if (aEndMark.WasPassed()) { + endTime = ResolveTimestampFromName(aEndMark.Value(), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + } else { + endTime = Now(); + } + + nsRefPtr performanceMeasure = + new PerformanceMeasure(GetAsISupports(), aName, startTime, endTime); + InsertUserEntry(performanceMeasure); +} + +void +PerformanceBase::ClearMeasures(const Optional& aName) +{ + ClearUserEntries(aName, NS_LITERAL_STRING("measure")); +} + +void +PerformanceBase::InsertUserEntry(PerformanceEntry* aEntry) +{ + mUserEntries.InsertElementSorted(aEntry, + PerformanceEntryComparator()); +} + +void +PerformanceBase::SetResourceTimingBufferSize(uint64_t aMaxSize) +{ + mResourceTimingBufferSize = aMaxSize; +} + +void +PerformanceBase::InsertResourceEntry(PerformanceEntry* aEntry) +{ + MOZ_ASSERT(aEntry); + MOZ_ASSERT(mResourceEntries.Length() < mResourceTimingBufferSize); + if (mResourceEntries.Length() >= mResourceTimingBufferSize) { + return; + } + + mResourceEntries.InsertElementSorted(aEntry, + PerformanceEntryComparator()); + if (mResourceEntries.Length() == mResourceTimingBufferSize) { + // call onresourcetimingbufferfull + DispatchBufferFullEvent(); + } +} diff --git a/dom/base/nsPerformance.h b/dom/base/nsPerformance.h index b727653c118..c39f8e1934f 100644 --- a/dom/base/nsPerformance.h +++ b/dom/base/nsPerformance.h @@ -288,18 +288,90 @@ private: nsRefPtr mPerformance; }; -// Script "performance" object -class nsPerformance final : public mozilla::DOMEventTargetHelper +// Base class for main-thread and worker Performance API +class PerformanceBase : public mozilla::DOMEventTargetHelper { public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PerformanceBase, + DOMEventTargetHelper) + + PerformanceBase(); + explicit PerformanceBase(nsPIDOMWindow* aWindow); + typedef mozilla::dom::PerformanceEntry PerformanceEntry; + + void GetEntries(nsTArray>& aRetval); + void GetEntriesByType(const nsAString& aEntryType, + nsTArray>& aRetval); + void GetEntriesByName(const nsAString& aName, + const mozilla::dom::Optional& aEntryType, + nsTArray>& aRetval); + void ClearResourceTimings(); + + virtual DOMHighResTimeStamp Now() const = 0; + + void Mark(const nsAString& aName, mozilla::ErrorResult& aRv); + void ClearMarks(const mozilla::dom::Optional& aName); + void Measure(const nsAString& aName, + const mozilla::dom::Optional& aStartMark, + const mozilla::dom::Optional& aEndMark, + mozilla::ErrorResult& aRv); + void ClearMeasures(const mozilla::dom::Optional& aName); + + void SetResourceTimingBufferSize(uint64_t aMaxSize); + +protected: + virtual ~PerformanceBase(); + + virtual void InsertUserEntry(PerformanceEntry* aEntry); + void InsertResourceEntry(PerformanceEntry* aEntry); + + void ClearUserEntries(const mozilla::dom::Optional& aEntryName, + const nsAString& aEntryType); + + DOMHighResTimeStamp ResolveTimestampFromName(const nsAString& aName, + mozilla::ErrorResult& aRv); + + virtual nsISupports* GetAsISupports() = 0; + + virtual void DispatchBufferFullEvent() = 0; + + virtual DOMHighResTimeStamp + DeltaFromNavigationStart(DOMHighResTimeStamp aTime) = 0; + + virtual bool IsPerformanceTimingAttribute(const nsAString& aName) = 0; + + virtual DOMHighResTimeStamp + GetPerformanceTimingFromString(const nsAString& aTimingName) = 0; + + bool IsResourceEntryLimitReached() const + { + return mResourceEntries.Length() >= mResourceTimingBufferSize; + } + +private: + nsTArray> mUserEntries; + nsTArray> mResourceEntries; + + uint64_t mResourceTimingBufferSize; + static const uint64_t kDefaultResourceTimingBufferSize = 150; +}; + +// Script "performance" object +class nsPerformance final : public PerformanceBase +{ +public: nsPerformance(nsPIDOMWindow* aWindow, nsDOMNavigationTiming* aDOMTiming, nsITimedChannel* aChannel, nsPerformance* aParentPerformance); NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsPerformance, DOMEventTargetHelper) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsPerformance, + PerformanceBase) + + static bool IsEnabled(JSContext* aCx, JSObject* aGlobal); nsDOMNavigationTiming* GetDOMTiming() const { @@ -316,35 +388,28 @@ public: return mParentPerformance; } - nsPIDOMWindow* GetParentObject() const - { - return mWindow.get(); - } - - virtual JSObject* WrapObject(JSContext *cx, JS::Handle aGivenProto) override; + JSObject* WrapObject(JSContext *cx, + JS::Handle aGivenProto) override; // Performance WebIDL methods - DOMHighResTimeStamp Now(); + DOMHighResTimeStamp Now() const override; + nsPerformanceTiming* Timing(); nsPerformanceNavigation* Navigation(); - void GetEntries(nsTArray>& retval); - void GetEntriesByType(const nsAString& entryType, - nsTArray>& retval); - void GetEntriesByName(const nsAString& name, - const mozilla::dom::Optional< nsAString >& entryType, - nsTArray>& retval); void AddEntry(nsIHttpChannel* channel, nsITimedChannel* timedChannel); - void ClearResourceTimings(); - void SetResourceTimingBufferSize(uint64_t maxSize); - void Mark(const nsAString& aName, mozilla::ErrorResult& aRv); - void ClearMarks(const mozilla::dom::Optional& aName); - void Measure(const nsAString& aName, - const mozilla::dom::Optional& aStartMark, - const mozilla::dom::Optional& aEndMark, - mozilla::ErrorResult& aRv); - void ClearMeasures(const mozilla::dom::Optional& aName); + + using PerformanceBase::GetEntries; + using PerformanceBase::GetEntriesByType; + using PerformanceBase::GetEntriesByName; + using PerformanceBase::ClearResourceTimings; + + using PerformanceBase::Mark; + using PerformanceBase::ClearMarks; + using PerformanceBase::Measure; + using PerformanceBase::ClearMeasures; + using PerformanceBase::SetResourceTimingBufferSize; void GetMozMemory(JSContext *aCx, JS::MutableHandle aObj); @@ -352,36 +417,30 @@ public: private: ~nsPerformance(); - bool IsPerformanceTimingAttribute(const nsAString& aName); - DOMHighResTimeStamp ResolveTimestampFromName(const nsAString& aName, mozilla::ErrorResult& aRv); - DOMTimeMilliSec GetPerformanceTimingFromString(const nsAString& aTimingName); - DOMHighResTimeStamp ConvertDOMMilliSecToHighRes(const DOMTimeMilliSec aTime); - void DispatchBufferFullEvent(); + + nsISupports* GetAsISupports() override + { + return this; + } + void InsertUserEntry(PerformanceEntry* aEntry); - void ClearUserEntries(const mozilla::dom::Optional& aEntryName, - const nsAString& aEntryType); - void InsertResourceEntry(PerformanceEntry* aEntry); - nsCOMPtr mWindow; + + bool IsPerformanceTimingAttribute(const nsAString& aName) override; + + DOMHighResTimeStamp + DeltaFromNavigationStart(DOMHighResTimeStamp aTime) override; + + DOMHighResTimeStamp + GetPerformanceTimingFromString(const nsAString& aTimingName) override; + + void DispatchBufferFullEvent() override; + nsRefPtr mDOMTiming; nsCOMPtr mChannel; nsRefPtr mTiming; nsRefPtr mNavigation; - nsTArray> mResourceEntries; - nsTArray> mUserEntries; nsRefPtr mParentPerformance; - uint64_t mResourceTimingBufferSize; JS::Heap mMozMemory; - - static const uint64_t kDefaultResourceTimingBufferSize = 150; - - // Helper classes - class PerformanceEntryComparator { - public: - bool Equals(const PerformanceEntry* aElem1, - const PerformanceEntry* aElem2) const; - bool LessThan(const PerformanceEntry* aElem1, - const PerformanceEntry* aElem2) const; - }; }; inline nsDOMNavigationTiming* diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini index 3d5611e6a47..ac03e628a98 100644 --- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -243,6 +243,7 @@ support-files = wholeTexty-helper.xml file_nonascii_blob_url.html referrerHelper.js + test_performance_user_timing.js [test_anonymousContent_api.html] [test_anonymousContent_append_after_reflow.html] diff --git a/dom/base/test/test_performance_user_timing.html b/dom/base/test/test_performance_user_timing.html index 1666daf8186..e302b38560e 100644 --- a/dom/base/test/test_performance_user_timing.html +++ b/dom/base/test/test_performance_user_timing.html @@ -7,6 +7,7 @@ Test for Bug 782751 + Mozilla Bug 782751 - User Timing API @@ -15,271 +16,6 @@
             
+  
+  
+    
+  
+
diff --git a/dom/workers/test/test_sharedWorker_performance_user_timing.html b/dom/workers/test/test_sharedWorker_performance_user_timing.html
new file mode 100644
index 00000000000..c9ae26b0c12
--- /dev/null
+++ b/dom/workers/test/test_sharedWorker_performance_user_timing.html
@@ -0,0 +1,30 @@
+
+
+
+  
+    Test for worker performance timing API
+    
+    
+  
+  
+    
+  
+
diff --git a/dom/workers/test/test_worker_interfaces.js b/dom/workers/test/test_worker_interfaces.js
index 5c7b4d20e30..17ad12c5bc4 100644
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -144,6 +144,12 @@ var interfaceNamesInGlobalScope =
     "MessagePort",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Performance",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "PerformanceEntry",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "PerformanceMark",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "PerformanceMeasure",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Promise",
 // IMPORTANT: Do not change this list without review from a DOM peer!
diff --git a/dom/workers/test/worker_performance_user_timing.js b/dom/workers/test/worker_performance_user_timing.js
new file mode 100644
index 00000000000..8e1656426ab
--- /dev/null
+++ b/dom/workers/test/worker_performance_user_timing.js
@@ -0,0 +1,24 @@
+function ok(a, msg) {
+  dump("OK: " + !!a + "  =>  " + a + " " + msg + "\n");
+  postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
+}
+
+function is(a, b, msg) {
+  dump("IS: " + (a===b) + "  =>  " + a + " | " + b + " " + msg + "\n");
+  postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
+}
+
+function isnot(a, b, msg) {
+  dump("ISNOT: " + (a===b) + "  =>  " + a + " | " + b + " " + msg + "\n");
+  postMessage({type: 'status', status: a != b, msg: a + " != " + b + ": " + msg });
+}
+
+importScripts(['../../../dom/base/test/test_performance_user_timing.js']);
+
+for (var i = 0; i < steps.length; ++i) {
+  performance.clearMarks();
+  performance.clearMeasures();
+  steps[i]();
+}
+
+postMessage({type: 'finish'});

From c3d731247ca83fdf373154cf68ee2250f9d41439 Mon Sep 17 00:00:00 2001
From: Nicolas Silva 
Date: Fri, 22 May 2015 13:38:13 +0200
Subject: [PATCH 73/89] Bug 1150549 - Simplify TiledContentHost. r=jrmuizel

---
 gfx/layers/Effects.h                      |   8 +-
 gfx/layers/TiledLayerBuffer.h             |  81 ++-
 gfx/layers/client/TiledContentClient.cpp  |  29 +-
 gfx/layers/client/TiledContentClient.h    |  12 +-
 gfx/layers/composite/TextureHost.h        |   1 +
 gfx/layers/composite/TiledContentHost.cpp | 600 ++++++++++------------
 gfx/layers/composite/TiledContentHost.h   |  71 ++-
 gfx/layers/ipc/LayersMessages.ipdlh       |   3 +
 8 files changed, 407 insertions(+), 398 deletions(-)

diff --git a/gfx/layers/Effects.h b/gfx/layers/Effects.h
index f32098d9a3d..128b84ada3b 100644
--- a/gfx/layers/Effects.h
+++ b/gfx/layers/Effects.h
@@ -285,8 +285,14 @@ CreateTexturedEffect(TextureSource* aSource,
 {
   MOZ_ASSERT(aSource);
   if (aSourceOnWhite) {
+    if ((aSource->GetFormat() != gfx::SurfaceFormat::R8G8B8X8 &&
+         aSource->GetFormat() != gfx::SurfaceFormat::B8G8R8X8) ||
+        aSource->GetFormat() != aSourceOnWhite->GetFormat()) {
+      printf_stderr("XXXX - source %i - on white %i\n", (int)aSource->GetFormat(), (int)aSourceOnWhite->GetFormat());
+    }
     MOZ_ASSERT(aSource->GetFormat() == gfx::SurfaceFormat::R8G8B8X8 ||
-               aSourceOnWhite->GetFormat() == gfx::SurfaceFormat::B8G8R8X8);
+               aSource->GetFormat() == gfx::SurfaceFormat::B8G8R8X8);
+    MOZ_ASSERT(aSource->GetFormat() == aSourceOnWhite->GetFormat());
     return MakeAndAddRef(aSource, aSourceOnWhite, aFilter);
   }
 
diff --git a/gfx/layers/TiledLayerBuffer.h b/gfx/layers/TiledLayerBuffer.h
index 717676b92ef..c38a4bffcd9 100644
--- a/gfx/layers/TiledLayerBuffer.h
+++ b/gfx/layers/TiledLayerBuffer.h
@@ -94,7 +94,9 @@ class TiledLayerBuffer
 {
 public:
   TiledLayerBuffer()
-    : mRetainedWidth(0)
+    : mFirstTileX(0)
+    , mFirstTileY(0)
+    , mRetainedWidth(0)
     , mRetainedHeight(0)
     , mResolution(1)
     , mTileSize(gfxPlatform::GetPlatform()->GetTileWidth(), gfxPlatform::GetPlatform()->GetTileHeight())
@@ -108,13 +110,21 @@ public:
   //       (aTileOrigin.x, aTileOrigin.y,
   //        GetScaledTileSize().width, GetScaledTileSize().height)
   //       and GetValidRegion() to get the area of the tile that is valid.
-  Tile GetTile(const nsIntPoint& aTileOrigin) const;
-
+  Tile& GetTile(const gfx::IntPoint& aTileOrigin);
   // Given a tile x, y relative to the top left of the layer, this function
   // will return the tile for
   // (x*GetScaledTileSize().width, y*GetScaledTileSize().height,
   //  GetScaledTileSize().width, GetScaledTileSize().height)
-  Tile GetTile(int x, int y) const;
+  Tile& GetTile(int x, int y);
+
+  int TileIndex(const gfx::IntPoint& aTileOrigin) const;
+  int TileIndex(int x, int y) const { return x * mRetainedHeight + y; }
+
+  bool HasTile(int index) const { return index >= 0 && index < (int)mRetainedTiles.Length(); }
+  bool HasTile(const gfx::IntPoint& aTileOrigin) const;
+  bool HasTile(int x, int y) const {
+    return x >= 0 && x < mRetainedWidth && y >= 0 && y < mRetainedHeight;
+  }
 
   const gfx::IntSize& GetTileSize() const { return mTileSize; }
 
@@ -155,14 +165,6 @@ public:
   // individual tile's rect in relation to the valid region.
   // Setting the resolution will invalidate the buffer.
   float GetResolution() const { return mResolution; }
-  void SetResolution(float aResolution) {
-    if (mResolution == aResolution) {
-      return;
-    }
-
-    Update(nsIntRegion(), nsIntRegion());
-    mResolution = aResolution;
-  }
   bool IsLowPrecision() const { return mResolution < 1; }
 
   typedef Tile* Iterator;
@@ -178,6 +180,10 @@ protected:
   // to the implementor.
   void Update(const nsIntRegion& aNewValidRegion, const nsIntRegion& aPaintRegion);
 
+  // Return a reference to this tile in GetTile when the requested tile offset
+  // does not exist.
+  Tile mPlaceHolderTile;
+
   nsIntRegion     mValidRegion;
   nsIntRegion     mPaintedRegion;
 
@@ -190,6 +196,8 @@ protected:
    * tiles is scaled by mResolution.
    */
   nsTArray  mRetainedTiles;
+  int             mFirstTileX;
+  int             mFirstTileY;
   int             mRetainedWidth;  // in tiles
   int             mRetainedHeight; // in tiles
   float           mResolution;
@@ -249,24 +257,39 @@ static inline int floor_div(int a, int b)
   }
 }
 
-template Tile
-TiledLayerBuffer::GetTile(const nsIntPoint& aTileOrigin) const
+template bool
+TiledLayerBuffer::HasTile(const gfx::IntPoint& aTileOrigin) const {
+  gfx::IntSize scaledTileSize = GetScaledTileSize();
+  return HasTile(floor_div(aTileOrigin.x, scaledTileSize.width) - mFirstTileX,
+                 floor_div(aTileOrigin.y, scaledTileSize.height) - mFirstTileY);
+}
+
+template Tile&
+TiledLayerBuffer::GetTile(const nsIntPoint& aTileOrigin)
+{
+  if (HasTile(aTileOrigin)) {
+    return mRetainedTiles[TileIndex(aTileOrigin)];
+  }
+  return mPlaceHolderTile;
+}
+
+template int
+TiledLayerBuffer::TileIndex(const gfx::IntPoint& aTileOrigin) const
 {
-  // TODO Cache firstTileOriginX/firstTileOriginY
   // Find the tile x/y of the first tile and the target tile relative to the (0, 0)
   // origin, the difference is the tile x/y relative to the start of the tile buffer.
   gfx::IntSize scaledTileSize = GetScaledTileSize();
-  int firstTileX = floor_div(mValidRegion.GetBounds().x, scaledTileSize.width);
-  int firstTileY = floor_div(mValidRegion.GetBounds().y, scaledTileSize.height);
-  return GetTile(floor_div(aTileOrigin.x, scaledTileSize.width) - firstTileX,
-                 floor_div(aTileOrigin.y, scaledTileSize.height) - firstTileY);
+  return TileIndex(floor_div(aTileOrigin.x, scaledTileSize.width) - mFirstTileX,
+                   floor_div(aTileOrigin.y, scaledTileSize.height) - mFirstTileY);
 }
 
-template Tile
-TiledLayerBuffer::GetTile(int x, int y) const
+template Tile&
+TiledLayerBuffer::GetTile(int x, int y)
 {
-  int index = x * mRetainedHeight + y;
-  return mRetainedTiles.SafeElementAt(index, AsDerived().GetPlaceholderTile());
+  if (HasTile(x, y)) {
+    return mRetainedTiles[TileIndex(x, y)];
+  }
+  return mPlaceHolderTile;
 }
 
 template void
@@ -282,15 +305,15 @@ TiledLayerBuffer::Dump(std::stringstream& aStream,
 
     for (int32_t y = visibleRect.y; y < visibleRect.y + visibleRect.height;) {
       int32_t tileStartY = GetTileStart(y, scaledTileSize.height);
-      Tile tileTexture =
-        GetTile(nsIntPoint(RoundDownToTileEdge(x, scaledTileSize.width),
-                           RoundDownToTileEdge(y, scaledTileSize.height)));
+      nsIntPoint tileOrigin = nsIntPoint(RoundDownToTileEdge(x, scaledTileSize.width),
+                                         RoundDownToTileEdge(y, scaledTileSize.height));
+      Tile& tileTexture = GetTile(tileOrigin);
       int32_t h = scaledTileSize.height - tileStartY;
 
       aStream << "\n" << aPrefix << "Tile (x=" <<
         RoundDownToTileEdge(x, scaledTileSize.width) << ", y=" <<
         RoundDownToTileEdge(y, scaledTileSize.height) << "): ";
-      if (tileTexture != AsDerived().GetPlaceholderTile()) {
+      if (!tileTexture.IsPlaceholderTile()) {
         tileTexture.DumpTexture(aStream);
       } else {
         aStream << "empty tile";
@@ -597,6 +620,10 @@ TiledLayerBuffer::Update(const nsIntRegion& newValidRegion,
 
   mRetainedTiles = newRetainedTiles;
   mValidRegion = newValidRegion;
+
+  mFirstTileX = floor_div(mValidRegion.GetBounds().x, scaledTileSize.width);
+  mFirstTileY = floor_div(mValidRegion.GetBounds().y, scaledTileSize.height);
+
   mPaintedRegion.Or(mPaintedRegion, aPaintRegion);
 }
 
diff --git a/gfx/layers/client/TiledContentClient.cpp b/gfx/layers/client/TiledContentClient.cpp
index 0c07cd3865d..26314d9663f 100644
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -374,7 +374,7 @@ int32_t
 gfxMemorySharedReadLock::ReadUnlock()
 {
   int32_t readCount = PR_ATOMIC_DECREMENT(&mReadCount);
-  NS_ASSERTION(readCount >= 0, "ReadUnlock called without ReadLock.");
+  MOZ_ASSERT(readCount >= 0);
 
   return readCount;
 }
@@ -424,7 +424,7 @@ gfxShmSharedReadLock::ReadUnlock() {
   }
   ShmReadLockInfo* info = GetShmReadLockInfoPtr();
   int32_t readCount = PR_ATOMIC_DECREMENT(&info->readCount);
-  NS_ASSERTION(readCount >= 0, "ReadUnlock called without a ReadLock.");
+  MOZ_ASSERT(readCount >= 0);
   if (readCount <= 0) {
     mAllocator->FreeShmemSection(mShmemSection);
   }
@@ -520,6 +520,7 @@ TileClient::TileClient(const TileClient& o)
   mBackLock = o.mBackLock;
   mFrontLock = o.mFrontLock;
   mCompositableClient = o.mCompositableClient;
+  mUpdateRect = o.mUpdateRect;
 #ifdef GFX_TILEDLAYER_DEBUG_OVERLAY
   mLastUpdate = o.mLastUpdate;
 #endif
@@ -539,6 +540,7 @@ TileClient::operator=(const TileClient& o)
   mBackLock = o.mBackLock;
   mFrontLock = o.mFrontLock;
   mCompositableClient = o.mCompositableClient;
+  mUpdateRect = o.mUpdateRect;
 #ifdef GFX_TILEDLAYER_DEBUG_OVERLAY
   mLastUpdate = o.mLastUpdate;
 #endif
@@ -609,6 +611,8 @@ CopyFrontToBack(TextureClient* aFront,
 
   gfx::IntPoint rectToCopyTopLeft = aRectToCopy.TopLeft();
   aFront->CopyToTextureClient(aBack, &aRectToCopy, &rectToCopyTopLeft);
+
+  aFront->Unlock();
   return true;
 }
 
@@ -705,9 +709,9 @@ TileClient::DiscardBackBuffer()
        mManager->ReportClientLost(*mBackBufferOnWhite);
      }
     } else {
-      mManager->ReturnTextureClient(*mBackBuffer);
+      mManager->ReturnTextureClientDeferred(*mBackBuffer);
       if (mBackBufferOnWhite) {
-        mManager->ReturnTextureClient(*mBackBufferOnWhite);
+        mManager->ReturnTextureClientDeferred(*mBackBufferOnWhite);
       }
     }
     mBackLock->ReadUnlock();
@@ -816,23 +820,17 @@ TileClient::GetTileDescriptor()
   if (mFrontLock->GetType() == gfxSharedReadLock::TYPE_MEMORY) {
     return TexturedTileDescriptor(nullptr, mFrontBuffer->GetIPDLActor(),
                                   mFrontBufferOnWhite ? MaybeTexture(mFrontBufferOnWhite->GetIPDLActor()) : MaybeTexture(null_t()),
+                                  mUpdateRect,
                                   TileLock(uintptr_t(mFrontLock.get())));
   } else {
     gfxShmSharedReadLock *lock = static_cast(mFrontLock.get());
     return TexturedTileDescriptor(nullptr, mFrontBuffer->GetIPDLActor(),
                                   mFrontBufferOnWhite ? MaybeTexture(mFrontBufferOnWhite->GetIPDLActor()) : MaybeTexture(null_t()),
+                                  mUpdateRect,
                                   TileLock(lock->GetShmemSection()));
   }
 }
 
-void
-ClientTiledLayerBuffer::ReadUnlock() {
-  for (size_t i = 0; i < mRetainedTiles.Length(); i++) {
-    if (mRetainedTiles[i].IsPlaceholderTile()) continue;
-    mRetainedTiles[i].ReadUnlock();
-  }
-}
-
 void
 ClientTiledLayerBuffer::ReadLock() {
   for (size_t i = 0; i < mRetainedTiles.Length(); i++) {
@@ -873,9 +871,12 @@ ClientTiledLayerBuffer::GetSurfaceDescriptorTiles()
       tileDesc = mRetainedTiles[i].GetTileDescriptor();
     }
     tiles.AppendElement(tileDesc);
+    mRetainedTiles[i].mUpdateRect = IntRect();
   }
   return SurfaceDescriptorTiles(mValidRegion, mPaintedRegion,
-                                tiles, mRetainedWidth, mRetainedHeight,
+                                tiles,
+                                mFirstTileX, mFirstTileY,
+                                mRetainedWidth, mRetainedHeight,
                                 mResolution, mFrameResolution.xScale,
                                 mFrameResolution.yScale);
 }
@@ -1136,6 +1137,8 @@ ClientTiledLayerBuffer::ValidateTile(TileClient aTile,
                         &createdTextureClient, extraPainted,
                         &backBufferOnWhite);
 
+  aTile.mUpdateRect = offsetScaledDirtyRegion.GetBounds().Union(extraPainted.GetBounds());
+
   extraPainted.MoveBy(aTileOrigin);
   extraPainted.And(extraPainted, mNewValidRegion);
   mPaintedRegion.Or(mPaintedRegion, extraPainted);
diff --git a/gfx/layers/client/TiledContentClient.h b/gfx/layers/client/TiledContentClient.h
index 80a1a4b2f57..6dfb20e1c0a 100644
--- a/gfx/layers/client/TiledContentClient.h
+++ b/gfx/layers/client/TiledContentClient.h
@@ -270,6 +270,7 @@ struct TileClient
   RefPtr mBackLock;
   RefPtr mFrontLock;
   RefPtr mManager;
+  gfx::IntRect mUpdateRect;
   CompositableClient* mCompositableClient;
 #ifdef GFX_TILEDLAYER_DEBUG_OVERLAY
   TimeStamp        mLastUpdate;
@@ -418,8 +419,6 @@ public:
                    LayerManager::DrawPaintedLayerCallback aCallback,
                    void* aCallbackData);
 
-  void ReadUnlock();
-
   void ReadLock();
 
   void Release();
@@ -445,6 +444,15 @@ public:
 
   SurfaceDescriptorTiles GetSurfaceDescriptorTiles();
 
+  void SetResolution(float aResolution) {
+    if (mResolution == aResolution) {
+      return;
+    }
+
+    Update(nsIntRegion(), nsIntRegion());
+    mResolution = aResolution;
+  }
+
 protected:
   TileClient ValidateTile(TileClient aTile,
                           const nsIntPoint& aTileRect,
diff --git a/gfx/layers/composite/TextureHost.h b/gfx/layers/composite/TextureHost.h
index 68e8093385d..d842808c23a 100644
--- a/gfx/layers/composite/TextureHost.h
+++ b/gfx/layers/composite/TextureHost.h
@@ -326,6 +326,7 @@ protected:
   virtual ~TextureHost();
 
 public:
+
   /**
    * Factory method.
    */
diff --git a/gfx/layers/composite/TiledContentHost.cpp b/gfx/layers/composite/TiledContentHost.cpp
index c21e94c7fe7..bf28b7d029e 100644
--- a/gfx/layers/composite/TiledContentHost.cpp
+++ b/gfx/layers/composite/TiledContentHost.cpp
@@ -29,198 +29,28 @@ class Layer;
 
 TiledLayerBufferComposite::TiledLayerBufferComposite()
   : mFrameResolution()
-  , mHasDoubleBufferedTiles(false)
-  , mIsValid(false)
 {}
 
+TiledLayerBufferComposite::~TiledLayerBufferComposite()
+{
+  Clear();
+}
+
 /* static */ void
 TiledLayerBufferComposite::RecycleCallback(TextureHost* textureHost, void* aClosure)
 {
   textureHost->CompositorRecycle();
 }
 
-TiledLayerBufferComposite::TiledLayerBufferComposite(ISurfaceAllocator* aAllocator,
-                                                     const SurfaceDescriptorTiles& aDescriptor,
-                                                     const nsIntRegion& aOldPaintedRegion,
-                                                     Compositor* aCompositor)
-{
-  mIsValid = true;
-  mHasDoubleBufferedTiles = false;
-  mValidRegion = aDescriptor.validRegion();
-  mPaintedRegion = aDescriptor.paintedRegion();
-  mRetainedWidth = aDescriptor.retainedWidth();
-  mRetainedHeight = aDescriptor.retainedHeight();
-  mResolution = aDescriptor.resolution();
-  mFrameResolution = CSSToParentLayerScale2D(aDescriptor.frameXResolution(),
-                                             aDescriptor.frameYResolution());
-  if (mResolution == 0 || IsNaN(mResolution)) {
-    // There are divisions by mResolution so this protects the compositor process
-    // against malicious content processes and fuzzing.
-    mIsValid = false;
-    return;
-  }
-
-  // Combine any valid content that wasn't already uploaded
-  nsIntRegion oldPaintedRegion(aOldPaintedRegion);
-  oldPaintedRegion.And(oldPaintedRegion, mValidRegion);
-  mPaintedRegion.Or(mPaintedRegion, oldPaintedRegion);
-
-  bool isSameProcess = aAllocator->IsSameProcess();
-
-  const InfallibleTArray& tiles = aDescriptor.tiles();
-  for(size_t i = 0; i < tiles.Length(); i++) {
-    CompositableTextureHostRef texture;
-    CompositableTextureHostRef textureOnWhite;
-    const TileDescriptor& tileDesc = tiles[i];
-    switch (tileDesc.type()) {
-      case TileDescriptor::TTexturedTileDescriptor : {
-        texture = TextureHost::AsTextureHost(tileDesc.get_TexturedTileDescriptor().textureParent());
-        MaybeTexture onWhite = tileDesc.get_TexturedTileDescriptor().textureOnWhite();
-        if (onWhite.type() == MaybeTexture::TPTextureParent) {
-          textureOnWhite = TextureHost::AsTextureHost(onWhite.get_PTextureParent());
-        }
-        const TileLock& ipcLock = tileDesc.get_TexturedTileDescriptor().sharedLock();
-        nsRefPtr sharedLock;
-        if (ipcLock.type() == TileLock::TShmemSection) {
-          sharedLock = gfxShmSharedReadLock::Open(aAllocator, ipcLock.get_ShmemSection());
-        } else {
-          if (!isSameProcess) {
-            // Trying to use a memory based lock instead of a shmem based one in
-            // the cross-process case is a bad security violation.
-            NS_ERROR("A client process may be trying to peek at the host's address space!");
-            // This tells the TiledContentHost that deserialization failed so that
-            // it can propagate the error.
-            mIsValid = false;
-
-            mRetainedTiles.Clear();
-            return;
-          }
-          sharedLock = reinterpret_cast(ipcLock.get_uintptr_t());
-          if (sharedLock) {
-            // The corresponding AddRef is in TiledClient::GetTileDescriptor
-            sharedLock.get()->Release();
-          }
-        }
-
-        CompositableTextureSourceRef textureSource;
-        CompositableTextureSourceRef textureSourceOnWhite;
-        if (texture) {
-          texture->SetCompositor(aCompositor);
-          texture->PrepareTextureSource(textureSource);
-        }
-        if (textureOnWhite) {
-          textureOnWhite->SetCompositor(aCompositor);
-          textureOnWhite->PrepareTextureSource(textureSourceOnWhite);
-        }
-        mRetainedTiles.AppendElement(TileHost(sharedLock,
-                                              texture.get(),
-                                              textureOnWhite.get(),
-                                              textureSource.get(),
-                                              textureSourceOnWhite.get()));
-        break;
-      }
-      default:
-        NS_WARNING("Unrecognised tile descriptor type");
-        // Fall through
-      case TileDescriptor::TPlaceholderTileDescriptor :
-        mRetainedTiles.AppendElement(GetPlaceholderTile());
-        break;
-    }
-    if (texture && !texture->HasInternalBuffer()) {
-      mHasDoubleBufferedTiles = true;
-    }
-  }
-}
-
-void
-TiledLayerBufferComposite::ReadUnlock()
-{
-  if (!IsValid()) {
-    return;
-  }
-  for (size_t i = 0; i < mRetainedTiles.Length(); i++) {
-    mRetainedTiles[i].ReadUnlock();
-  }
-}
-
-void
-TiledLayerBufferComposite::ReleaseTextureHosts()
-{
-  if (!IsValid()) {
-    return;
-  }
-  for (size_t i = 0; i < mRetainedTiles.Length(); i++) {
-    mRetainedTiles[i].mTextureHost = nullptr;
-    mRetainedTiles[i].mTextureHostOnWhite = nullptr;
-    mRetainedTiles[i].mTextureSource = nullptr;
-    mRetainedTiles[i].mTextureSourceOnWhite = nullptr;
-  }
-}
-
-void
-TiledLayerBufferComposite::Upload()
-{
-  if(!IsValid()) {
-    return;
-  }
-  // The TextureClients were created with the TextureFlags::IMMEDIATE_UPLOAD flag,
-  // so calling Update on all the texture hosts will perform the texture upload.
-  Update(mValidRegion, mPaintedRegion);
-  ClearPaintedRegion();
-}
-
-TileHost
-TiledLayerBufferComposite::ValidateTile(TileHost aTile,
-                                        const IntPoint& aTileOrigin,
-                                        const nsIntRegion& aDirtyRect)
-{
-  if (aTile.IsPlaceholderTile()) {
-    NS_WARNING("Placeholder tile encountered in painted region");
-    return aTile;
-  }
-
-#ifdef GFX_TILEDLAYER_PREF_WARNINGS
-  printf_stderr("Upload tile %i, %i\n", aTileOrigin.x, aTileOrigin.y);
-  long start = PR_IntervalNow();
-#endif
-
-  MOZ_ASSERT(aTile.mTextureHost->GetFlags() & TextureFlags::IMMEDIATE_UPLOAD);
-
-#ifdef MOZ_GFX_OPTIMIZE_MOBILE
-  MOZ_ASSERT(!aTile.mTextureHostOnWhite);
-  // We possibly upload the entire texture contents here. This is a purposeful
-  // decision, as sub-image upload can often be slow and/or unreliable, but
-  // we may want to reevaluate this in the future.
-  // For !HasInternalBuffer() textures, this is likely a no-op.
-  aTile.mTextureHost->Updated(nullptr);
-#else
-  nsIntRegion tileUpdated = aDirtyRect.MovedBy(-aTileOrigin);
-  aTile.mTextureHost->Updated(&tileUpdated);
-  if (aTile.mTextureHostOnWhite) {
-    aTile.mTextureHostOnWhite->Updated(&tileUpdated);
-  }
-#endif
-
-#ifdef GFX_TILEDLAYER_PREF_WARNINGS
-  if (PR_IntervalNow() - start > 1) {
-    printf_stderr("Tile Time to upload %i\n", PR_IntervalNow() - start);
-  }
-#endif
-  return aTile;
-}
-
 void
 TiledLayerBufferComposite::SetCompositor(Compositor* aCompositor)
 {
   MOZ_ASSERT(aCompositor);
-  if (!IsValid()) {
-    return;
-  }
-  for (size_t i = 0; i < mRetainedTiles.Length(); i++) {
-    if (mRetainedTiles[i].IsPlaceholderTile()) continue;
-    mRetainedTiles[i].mTextureHost->SetCompositor(aCompositor);
-    if (mRetainedTiles[i].mTextureHostOnWhite) {
-      mRetainedTiles[i].mTextureHostOnWhite->SetCompositor(aCompositor);
+  for (TileHost& tile : mRetainedTiles) {
+    if (tile.IsPlaceholderTile()) continue;
+    tile.mTextureHost->SetCompositor(aCompositor);
+    if (tile.mTextureHostOnWhite) {
+      tile.mTextureHostOnWhite->SetCompositor(aCompositor);
     }
   }
 }
@@ -229,10 +59,6 @@ TiledContentHost::TiledContentHost(const TextureInfo& aTextureInfo)
   : ContentHost(aTextureInfo)
   , mTiledBuffer(TiledLayerBufferComposite())
   , mLowPrecisionTiledBuffer(TiledLayerBufferComposite())
-  , mOldTiledBuffer(TiledLayerBufferComposite())
-  , mOldLowPrecisionTiledBuffer(TiledLayerBufferComposite())
-  , mPendingUpload(false)
-  , mPendingLowPrecisionUpload(false)
 {
   MOZ_COUNT_CTOR(TiledContentHost);
 }
@@ -240,28 +66,6 @@ TiledContentHost::TiledContentHost(const TextureInfo& aTextureInfo)
 TiledContentHost::~TiledContentHost()
 {
   MOZ_COUNT_DTOR(TiledContentHost);
-
-  // Unlock any buffers that may still be locked. If we have a pending upload,
-  // we will need to unlock the buffer that was about to be uploaded.
-  // If a buffer that was being composited had double-buffered tiles, we will
-  // need to unlock that buffer too.
-  if (mPendingUpload) {
-    mTiledBuffer.ReadUnlock();
-    if (mOldTiledBuffer.HasDoubleBufferedTiles()) {
-      mOldTiledBuffer.ReadUnlock();
-    }
-  } else if (mTiledBuffer.HasDoubleBufferedTiles()) {
-    mTiledBuffer.ReadUnlock();
-  }
-
-  if (mPendingLowPrecisionUpload) {
-    mLowPrecisionTiledBuffer.ReadUnlock();
-    if (mOldLowPrecisionTiledBuffer.HasDoubleBufferedTiles()) {
-      mOldLowPrecisionTiledBuffer.ReadUnlock();
-    }
-  } else if (mLowPrecisionTiledBuffer.HasDoubleBufferedTiles()) {
-    mLowPrecisionTiledBuffer.ReadUnlock();
-  }
 }
 
 void
@@ -277,33 +81,10 @@ TiledContentHost::Detach(Layer* aLayer,
                          AttachFlags aFlags /* = NO_FLAGS */)
 {
   if (!mKeepAttached || aLayer == mLayer || aFlags & FORCE_DETACH) {
-
-    // Unlock any buffers that may still be locked. If we have a pending upload,
-    // we will need to unlock the buffer that was about to be uploaded.
-    // If a buffer that was being composited had double-buffered tiles, we will
-    // need to unlock that buffer too.
-    if (mPendingUpload) {
-      mTiledBuffer.ReadUnlock();
-      if (mOldTiledBuffer.HasDoubleBufferedTiles()) {
-        mOldTiledBuffer.ReadUnlock();
-      }
-    } else if (mTiledBuffer.HasDoubleBufferedTiles()) {
-      mTiledBuffer.ReadUnlock();
-    }
-
-    if (mPendingLowPrecisionUpload) {
-      mLowPrecisionTiledBuffer.ReadUnlock();
-      if (mOldLowPrecisionTiledBuffer.HasDoubleBufferedTiles()) {
-        mOldLowPrecisionTiledBuffer.ReadUnlock();
-      }
-    } else if (mLowPrecisionTiledBuffer.HasDoubleBufferedTiles()) {
-      mLowPrecisionTiledBuffer.ReadUnlock();
-    }
-
-    mTiledBuffer = TiledLayerBufferComposite();
-    mLowPrecisionTiledBuffer = TiledLayerBufferComposite();
-    mOldTiledBuffer = TiledLayerBufferComposite();
-    mOldLowPrecisionTiledBuffer = TiledLayerBufferComposite();
+    // Clear the TiledLayerBuffers, which will take care of releasing the
+    // copy-on-write locks.
+    mTiledBuffer.Clear();
+    mLowPrecisionTiledBuffer.Clear();
   }
   CompositableHost::Detach(aLayer,aFlags);
 }
@@ -313,60 +94,280 @@ TiledContentHost::UseTiledLayerBuffer(ISurfaceAllocator* aAllocator,
                                       const SurfaceDescriptorTiles& aTiledDescriptor)
 {
   if (aTiledDescriptor.resolution() < 1) {
-    if (mPendingLowPrecisionUpload) {
-      mLowPrecisionTiledBuffer.ReadUnlock();
-    } else {
-      mPendingLowPrecisionUpload = true;
-      // If the old buffer has double-buffered tiles, hang onto it so we can
-      // unlock it after we've composited the new buffer.
-      // We only need to hang onto the locks, but not the textures.
-      // Releasing the textures here can help prevent a memory spike in the
-      // situation that the client starts rendering new content before we get
-      // to composite the new buffer.
-      if (mLowPrecisionTiledBuffer.HasDoubleBufferedTiles()) {
-        mOldLowPrecisionTiledBuffer = mLowPrecisionTiledBuffer;
-        mOldLowPrecisionTiledBuffer.ReleaseTextureHosts();
-      }
-    }
-    mLowPrecisionTiledBuffer =
-      TiledLayerBufferComposite(aAllocator,
-                                aTiledDescriptor,
-                                mLowPrecisionTiledBuffer.GetPaintedRegion(),
-                                mCompositor);
-    if (!mLowPrecisionTiledBuffer.IsValid()) {
-      // Something bad happened. Stop here, return false (kills the child process),
-      // and do as little work as possible on the received data as it appears
-      // to be corrupted.
-      mPendingLowPrecisionUpload = false;
-      mPendingUpload = false;
+    if (!mLowPrecisionTiledBuffer.UseTiles(aTiledDescriptor, mCompositor, aAllocator)) {
       return false;
     }
   } else {
-    if (mPendingUpload) {
-      mTiledBuffer.ReadUnlock();
-    } else {
-      mPendingUpload = true;
-      if (mTiledBuffer.HasDoubleBufferedTiles()) {
-        mOldTiledBuffer = mTiledBuffer;
-        mOldTiledBuffer.ReleaseTextureHosts();
-      }
-    }
-    mTiledBuffer = TiledLayerBufferComposite(aAllocator,
-                                             aTiledDescriptor,
-                                             mTiledBuffer.GetPaintedRegion(),
-                                             mCompositor);
-    if (!mTiledBuffer.IsValid()) {
-      // Something bad happened. Stop here, return false (kills the child process),
-      // and do as little work as possible on the received data as it appears
-      // to be corrupted.
-      mPendingLowPrecisionUpload = false;
-      mPendingUpload = false;
+    if (!mTiledBuffer.UseTiles(aTiledDescriptor, mCompositor, aAllocator)) {
       return false;
     }
   }
   return true;
 }
 
+void
+UseTileTexture(CompositableTextureHostRef& aTexture,
+               CompositableTextureSourceRef& aTextureSource,
+               const IntRect& aUpdateRect,
+               TextureHost* aNewTexture,
+               Compositor* aCompositor)
+{
+  if (aTexture && aTexture->GetFormat() != aNewTexture->GetFormat()) {
+    // Only reuse textures if their format match the new texture's.
+    aTextureSource = nullptr;
+    aTexture = nullptr;
+  }
+  aTexture = aNewTexture;
+  if (aTexture) {
+    if (aCompositor) {
+      aTexture->SetCompositor(aCompositor);
+    }
+
+    if (!aUpdateRect.IsEmpty()) {
+#ifdef MOZ_GFX_OPTIMIZE_MOBILE
+      aTexture->Updated(nullptr);
+#else
+      // We possibly upload the entire texture contents here. This is a purposeful
+      // decision, as sub-image upload can often be slow and/or unreliable, but
+      // we may want to reevaluate this in the future.
+      // For !HasInternalBuffer() textures, this is likely a no-op.
+      nsIntRegion region = aUpdateRect;
+      aTexture->Updated(®ion);
+#endif
+    }
+    aTexture->PrepareTextureSource(aTextureSource);
+  }
+}
+
+bool
+GetCopyOnWriteLock(const TileLock& ipcLock, TileHost& aTile, ISurfaceAllocator* aAllocator) {
+  MOZ_ASSERT(aAllocator);
+
+  nsRefPtr sharedLock;
+  if (ipcLock.type() == TileLock::TShmemSection) {
+    sharedLock = gfxShmSharedReadLock::Open(aAllocator, ipcLock.get_ShmemSection());
+  } else {
+    if (!aAllocator->IsSameProcess()) {
+      // Trying to use a memory based lock instead of a shmem based one in
+      // the cross-process case is a bad security violation.
+      NS_ERROR("A client process may be trying to peek at the host's address space!");
+      return false;
+    }
+    sharedLock = reinterpret_cast(ipcLock.get_uintptr_t());
+    if (sharedLock) {
+      // The corresponding AddRef is in TiledClient::GetTileDescriptor
+      sharedLock.get()->Release();
+    }
+  }
+  aTile.mSharedLock = sharedLock;
+  return true;
+}
+
+bool
+TiledLayerBufferComposite::UseTiles(const SurfaceDescriptorTiles& aTiles,
+                                    Compositor* aCompositor,
+                                    ISurfaceAllocator* aAllocator)
+{
+  if (mResolution != aTiles.resolution()) {
+    Clear();
+  }
+  MOZ_ASSERT(aAllocator);
+  MOZ_ASSERT(aCompositor);
+  if (!aAllocator || !aCompositor) {
+    return false;
+  }
+
+  if (aTiles.resolution() == 0 || IsNaN(aTiles.resolution())) {
+    // There are divisions by mResolution so this protects the compositor process
+    // against malicious content processes and fuzzing.
+    return false;
+  }
+
+  int newFirstTileX = aTiles.firstTileX();
+  int newFirstTileY = aTiles.firstTileY();
+  int oldFirstTileX = mFirstTileX;
+  int oldFirstTileY = mFirstTileY;
+  int newRetainedWidth = aTiles.retainedWidth();
+  int newRetainedHeight = aTiles.retainedHeight();
+  int oldRetainedWidth = mRetainedWidth;
+  int oldRetainedHeight = mRetainedHeight;
+
+  const InfallibleTArray& tileDescriptors = aTiles.tiles();
+
+  nsTArray oldTiles;
+  mRetainedTiles.SwapElements(oldTiles);
+  mRetainedTiles.SetLength(tileDescriptors.Length());
+
+  // Step 1, we need to unlock tiles that don't have an internal buffer after the
+  // next frame where they are replaced.
+  // Since we are about to replace the tiles' textures, we need to keep their locks
+  // somewhere (in mPreviousSharedLock) until we composite the layer.
+  for (size_t i = 0; i < oldTiles.Length(); ++i) {
+    TileHost& tile = oldTiles[i];
+    // It can happen that we still have a previous lock at this point,
+    // if we changed a tile's front buffer (causing mSharedLock to
+    // go into mPreviousSharedLock, and then did not composite that tile until
+    // the next transaction, either because the tile is offscreen or because the
+    // two transactions happened with no composition in between (over-production).
+    tile.ReadUnlockPrevious();
+
+    if (tile.mTextureHost && !tile.mTextureHost->HasInternalBuffer()) {
+      MOZ_ASSERT(tile.mSharedLock);
+      int tileX = i % oldRetainedWidth + oldFirstTileX;
+      int tileY = i / oldRetainedWidth + oldFirstTileY;
+
+      if (tileX >= newFirstTileX && tileY >= newFirstTileY &&
+          tileX < (newFirstTileX + newRetainedWidth) &&
+          tileY < (newFirstTileY + newRetainedHeight)) {
+        // This tile still exist in the new buffer
+        tile.mPreviousSharedLock = tile.mSharedLock;
+        tile.mSharedLock = nullptr;
+      } else {
+        // This tile does not exist anymore in the new buffer because the size
+        // changed.
+        tile.ReadUnlock();
+      }
+    }
+
+    // By now we should not have anything in mSharedLock.
+    MOZ_ASSERT(!tile.mSharedLock);
+  }
+
+  // Step 2, move the tiles in mRetainedTiles at places that correspond to where
+  // they should be with the new retained with and height rather than the
+  // old one.
+  for (size_t i = 0; i < tileDescriptors.Length(); i++) {
+    int tileX = i % newRetainedWidth + newFirstTileX;
+    int tileY = i / newRetainedWidth + newFirstTileY;
+
+    // First, get the already existing tiles to the right place in the array,
+    // and use placeholders where there was no tiles.
+    if (tileX < oldFirstTileX || tileY < oldFirstTileY ||
+        tileX >= (oldFirstTileX + oldRetainedWidth) ||
+        tileY >= (oldFirstTileY + oldRetainedHeight)) {
+      mRetainedTiles[i] = GetPlaceholderTile();
+    } else {
+      mRetainedTiles[i] = oldTiles[(tileY - oldFirstTileY) * oldRetainedWidth +
+                                   (tileX - oldFirstTileX)];
+      // If we hit this assertion it means we probably mixed something up in the
+      // logic that tries to reuse tiles on the compositor side. It is most likely
+      // benign, but we are missing some fast paths so let's try to make it not happen.
+      MOZ_ASSERT(tileX == mRetainedTiles[i].x && tileY == mRetainedTiles[i].y);
+    }
+  }
+
+  // It is important to remove the duplicated reference to tiles before calling
+  // TextureHost::PrepareTextureSource, etc. because depending on the textures
+  // ref counts we may or may not get some of the fast paths.
+  oldTiles.Clear();
+
+  // Step 3, handle the texture updates and release the copy-on-write locks.
+  for (size_t i = 0; i < mRetainedTiles.Length(); i++) {
+    const TileDescriptor& tileDesc = tileDescriptors[i];
+
+    TileHost& tile = mRetainedTiles[i];
+
+    switch (tileDesc.type()) {
+      case TileDescriptor::TTexturedTileDescriptor: {
+        const TexturedTileDescriptor& texturedDesc = tileDesc.get_TexturedTileDescriptor();
+
+        const TileLock& ipcLock = texturedDesc.sharedLock();
+        if (!GetCopyOnWriteLock(ipcLock, tile, aAllocator)) {
+          return false;
+        }
+
+        RefPtr textureHost = TextureHost::AsTextureHost(
+          texturedDesc.textureParent()
+        );
+
+        RefPtr textureOnWhite = nullptr;
+        if (texturedDesc.textureOnWhite().type() == MaybeTexture::TPTextureParent) {
+          textureOnWhite = TextureHost::AsTextureHost(
+            texturedDesc.textureOnWhite().get_PTextureParent()
+          );
+        }
+
+        UseTileTexture(tile.mTextureHost,
+                       tile.mTextureSource,
+                       texturedDesc.updateRect(),
+                       textureHost,
+                       aCompositor);
+
+        if (textureOnWhite) {
+          UseTileTexture(tile.mTextureHostOnWhite,
+                         tile.mTextureSourceOnWhite,
+                         texturedDesc.updateRect(),
+                         textureOnWhite,
+                         aCompositor);
+        } else {
+          // We could still have component alpha textures from a previous frame.
+          tile.mTextureSourceOnWhite = nullptr;
+          tile.mTextureHostOnWhite = nullptr;
+        }
+
+        if (textureHost->HasInternalBuffer()) {
+          // Now that we did the texture upload (in UseTileTexture), we can release
+          // the lock.
+          tile.ReadUnlock();
+        }
+
+        break;
+      }
+      default:
+        NS_WARNING("Unrecognised tile descriptor type");
+      case TileDescriptor::TPlaceholderTileDescriptor: {
+
+        if (tile.mTextureHost) {
+          tile.mTextureHost->UnbindTextureSource();
+          tile.mTextureSource = nullptr;
+        }
+        if (tile.mTextureHostOnWhite) {
+          tile.mTextureHostOnWhite->UnbindTextureSource();
+          tile.mTextureSourceOnWhite = nullptr;
+        }
+        // we may have a previous lock, and are about to loose our reference to it.
+        // It is okay to unlock it because we just destroyed the texture source.
+        tile.ReadUnlockPrevious();
+        tile = GetPlaceholderTile();
+
+        break;
+      }
+    }
+
+    tile.x = i % newRetainedWidth + newFirstTileX;
+    tile.y = i / newRetainedWidth + newFirstTileY;
+  }
+
+  mFirstTileX = newFirstTileX;
+  mFirstTileY = newFirstTileY;
+  mRetainedWidth = newRetainedWidth;
+  mRetainedHeight = newRetainedHeight;
+  mValidRegion = aTiles.validRegion();
+
+  mResolution = aTiles.resolution();
+  mFrameResolution = CSSToParentLayerScale2D(aTiles.frameXResolution(),
+                                             aTiles.frameYResolution());
+
+  return true;
+}
+
+void
+TiledLayerBufferComposite::Clear()
+{
+  for (TileHost& tile : mRetainedTiles) {
+    tile.ReadUnlock();
+    tile.ReadUnlockPrevious();
+  }
+  mRetainedTiles.Clear();
+  mFirstTileX = 0;
+  mFirstTileY = 0;
+  mRetainedWidth = 0;
+  mRetainedHeight = 0;
+  mValidRegion = nsIntRegion();
+  mPaintedRegion = nsIntRegion();
+  mResolution = 1.0;
+}
+
 void
 TiledContentHost::Composite(EffectChain& aEffectChain,
                             float aOpacity,
@@ -376,25 +377,6 @@ TiledContentHost::Composite(EffectChain& aEffectChain,
                             const nsIntRegion* aVisibleRegion /* = nullptr */)
 {
   MOZ_ASSERT(mCompositor);
-  if (mPendingUpload) {
-    mTiledBuffer.SetCompositor(mCompositor);
-    mTiledBuffer.Upload();
-
-    // For a single-buffered tiled buffer, Upload will upload the shared memory
-    // surface to texture memory and we no longer need to read from them.
-    if (!mTiledBuffer.HasDoubleBufferedTiles()) {
-      mTiledBuffer.ReadUnlock();
-    }
-  }
-  if (mPendingLowPrecisionUpload) {
-    mLowPrecisionTiledBuffer.SetCompositor(mCompositor);
-    mLowPrecisionTiledBuffer.Upload();
-
-    if (!mLowPrecisionTiledBuffer.HasDoubleBufferedTiles()) {
-      mLowPrecisionTiledBuffer.ReadUnlock();
-    }
-  }
-
   // Reduce the opacity of the low-precision buffer to make it a
   // little more subtle and less jarring. In particular, text
   // rendered at low-resolution and scaled tends to look pretty
@@ -440,24 +422,11 @@ TiledContentHost::Composite(EffectChain& aEffectChain,
                     aFilter, aClipRect, *renderRegion, aTransform);
   RenderLayerBuffer(mTiledBuffer, nullptr, aEffectChain, aOpacity, aFilter,
                     aClipRect, *renderRegion, aTransform);
-
-  // Now release the old buffer if it had double-buffered tiles, as we can
-  // guarantee that they're no longer on the screen (and so any locks that may
-  // have been held have been released).
-  if (mPendingUpload && mOldTiledBuffer.HasDoubleBufferedTiles()) {
-    mOldTiledBuffer.ReadUnlock();
-    mOldTiledBuffer = TiledLayerBufferComposite();
-  }
-  if (mPendingLowPrecisionUpload && mOldLowPrecisionTiledBuffer.HasDoubleBufferedTiles()) {
-    mOldLowPrecisionTiledBuffer.ReadUnlock();
-    mOldLowPrecisionTiledBuffer = TiledLayerBufferComposite();
-  }
-  mPendingUpload = mPendingLowPrecisionUpload = false;
 }
 
 
 void
-TiledContentHost::RenderTile(const TileHost& aTile,
+TiledContentHost::RenderTile(TileHost& aTile,
                              const gfxRGBA* aBackgroundColor,
                              EffectChain& aEffectChain,
                              float aOpacity,
@@ -524,6 +493,7 @@ TiledContentHost::RenderTile(const TileHost& aTile,
   }
   mCompositor->DrawDiagnostics(flags,
                                aScreenRegion, aClipRect, aTransform, mFlashCounter);
+  aTile.ReadUnlockPrevious();
 }
 
 void
@@ -591,10 +561,10 @@ TiledContentHost::RenderLayerBuffer(TiledLayerBufferComposite& aLayerBuffer,
         h = visibleRect.y + visibleRect.height - y;
       }
 
-      TileHost tileTexture = aLayerBuffer.
-        GetTile(IntPoint(aLayerBuffer.RoundDownToTileEdge(x, scaledTileSize.width),
-                         aLayerBuffer.RoundDownToTileEdge(y, scaledTileSize.height)));
-      if (tileTexture != aLayerBuffer.GetPlaceholderTile()) {
+      nsIntPoint tileOrigin = nsIntPoint(aLayerBuffer.RoundDownToTileEdge(x, scaledTileSize.width),
+                                         aLayerBuffer.RoundDownToTileEdge(y, scaledTileSize.height));
+      TileHost& tileTexture = aLayerBuffer.GetTile(tileOrigin);
+      if (!tileTexture.IsPlaceholderTile()) {
         nsIntRegion tileDrawRegion;
         tileDrawRegion.And(IntRect(x, y, w, h), aLayerBuffer.GetValidRegion());
         tileDrawRegion.And(tileDrawRegion, aVisibleRegion);
diff --git a/gfx/layers/composite/TiledContentHost.h b/gfx/layers/composite/TiledContentHost.h
index 13a89a20797..2647c6f1557 100644
--- a/gfx/layers/composite/TiledContentHost.h
+++ b/gfx/layers/composite/TiledContentHost.h
@@ -52,6 +52,8 @@ public:
   // essentially, this is a sentinel used to represent an invalid or blank
   // tile.
   TileHost()
+  : x(-1)
+  , y(-1)
   {}
 
   // Constructs a TileHost from a gfxSharedReadLock and TextureHost.
@@ -65,6 +67,8 @@ public:
     , mTextureHostOnWhite(aTextureHostOnWhite)
     , mTextureSource(aSource)
     , mTextureSourceOnWhite(aSourceOnWhite)
+    , x(-1)
+    , y(-1)
   {}
 
   TileHost(const TileHost& o) {
@@ -73,6 +77,9 @@ public:
     mTextureSource = o.mTextureSource;
     mTextureSourceOnWhite = o.mTextureSourceOnWhite;
     mSharedLock = o.mSharedLock;
+    mPreviousSharedLock = o.mPreviousSharedLock;
+    x = o.x;
+    y = o.y;
   }
   TileHost& operator=(const TileHost& o) {
     if (this == &o) {
@@ -83,6 +90,9 @@ public:
     mTextureSource = o.mTextureSource;
     mTextureSourceOnWhite = o.mTextureSourceOnWhite;
     mSharedLock = o.mSharedLock;
+    mPreviousSharedLock = o.mPreviousSharedLock;
+    x = o.x;
+    y = o.y;
     return *this;
   }
 
@@ -98,6 +108,14 @@ public:
   void ReadUnlock() {
     if (mSharedLock) {
       mSharedLock->ReadUnlock();
+      mSharedLock = nullptr;
+    }
+  }
+
+  void ReadUnlockPrevious() {
+    if (mPreviousSharedLock) {
+      mPreviousSharedLock->ReadUnlock();
+      mPreviousSharedLock = nullptr;
     }
   }
 
@@ -111,10 +129,14 @@ public:
   }
 
   RefPtr mSharedLock;
+  RefPtr mPreviousSharedLock;
   CompositableTextureHostRef mTextureHost;
   CompositableTextureHostRef mTextureHostOnWhite;
   mutable CompositableTextureSourceRef mTextureSource;
   mutable CompositableTextureSourceRef mTextureSourceOnWhite;
+  // This is not strictly necessary but makes debugging whole lot easier.
+  int x;
+  int y;
 };
 
 class TiledLayerBufferComposite
@@ -123,13 +145,14 @@ class TiledLayerBufferComposite
   friend class TiledLayerBuffer;
 
 public:
-  typedef TiledLayerBuffer::Iterator Iterator;
-
   TiledLayerBufferComposite();
-  TiledLayerBufferComposite(ISurfaceAllocator* aAllocator,
-                            const SurfaceDescriptorTiles& aDescriptor,
-                            const nsIntRegion& aOldPaintedRegion,
-                            Compositor* aCompositor);
+  ~TiledLayerBufferComposite();
+
+  bool UseTiles(const SurfaceDescriptorTiles& aTileDescriptors,
+                Compositor* aCompositor,
+                ISurfaceAllocator* aAllocator);
+
+  void Clear();
 
   TileHost GetPlaceholderTile() const { return TileHost(); }
 
@@ -137,43 +160,16 @@ public:
   // by the sum of the resolutions of all parent layers' FrameMetrics.
   const CSSToParentLayerScale2D& GetFrameResolution() { return mFrameResolution; }
 
-  void ReadUnlock();
-
-  void ReleaseTextureHosts();
-
-  /**
-   * This will synchronously upload any necessary texture contents, making the
-   * sources immediately available for compositing. For texture hosts that
-   * don't have an internal buffer, this is unlikely to actually do anything.
-   */
-  void Upload();
-
   void SetCompositor(Compositor* aCompositor);
 
-  bool HasDoubleBufferedTiles() { return mHasDoubleBufferedTiles; }
-
-  bool IsValid() const { return mIsValid; }
-
   // Recycle callback for TextureHost.
   // Used when TiledContentClient is present in client side.
   static void RecycleCallback(TextureHost* textureHost, void* aClosure);
 
 protected:
-  TileHost ValidateTile(TileHost aTile,
-                        const gfx::IntPoint& aTileRect,
-                        const nsIntRegion& dirtyRect);
-
-  // do nothing, the desctructor in the texture host takes care of releasing resources
-  void ReleaseTile(TileHost aTile) {}
-
   void SwapTiles(TileHost& aTileA, TileHost& aTileB) { std::swap(aTileA, aTileB); }
 
-  void UnlockTile(TileHost aTile) {}
-  void PostValidate(const nsIntRegion& aPaintRegion) {}
-private:
   CSSToParentLayerScale2D mFrameResolution;
-  bool mHasDoubleBufferedTiles;
-  bool mIsValid;
 };
 
 /**
@@ -233,11 +229,10 @@ public:
 
   virtual void SetCompositor(Compositor* aCompositor) override
   {
+    MOZ_ASSERT(aCompositor);
     CompositableHost::SetCompositor(aCompositor);
     mTiledBuffer.SetCompositor(aCompositor);
     mLowPrecisionTiledBuffer.SetCompositor(aCompositor);
-    mOldTiledBuffer.SetCompositor(aCompositor);
-    mOldLowPrecisionTiledBuffer.SetCompositor(aCompositor);
   }
 
   virtual bool UseTiledLayerBuffer(ISurfaceAllocator* aAllocator,
@@ -279,7 +274,7 @@ private:
                          gfx::Matrix4x4 aTransform);
 
   // Renders a single given tile.
-  void RenderTile(const TileHost& aTile,
+  void RenderTile(TileHost& aTile,
                   const gfxRGBA* aBackgroundColor,
                   EffectChain& aEffectChain,
                   float aOpacity,
@@ -294,10 +289,6 @@ private:
 
   TiledLayerBufferComposite    mTiledBuffer;
   TiledLayerBufferComposite    mLowPrecisionTiledBuffer;
-  TiledLayerBufferComposite    mOldTiledBuffer;
-  TiledLayerBufferComposite    mOldLowPrecisionTiledBuffer;
-  bool                         mPendingUpload;
-  bool                         mPendingLowPrecisionUpload;
 };
 
 }
diff --git a/gfx/layers/ipc/LayersMessages.ipdlh b/gfx/layers/ipc/LayersMessages.ipdlh
index c1a0a12e078..22fdd827bd3 100644
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -315,6 +315,7 @@ union MaybeTexture {
 struct TexturedTileDescriptor {
   PTexture texture;
   MaybeTexture textureOnWhite;
+  IntRect updateRect;
   TileLock sharedLock;
 };
 
@@ -330,6 +331,8 @@ struct SurfaceDescriptorTiles {
   nsIntRegion validRegion;
   nsIntRegion paintedRegion;
   TileDescriptor[] tiles;
+  int         firstTileX;
+  int         firstTileY;
   int         retainedWidth;
   int         retainedHeight;
   float       resolution;

From ce018c3942ca6231bbbdc0ed65b13ab81aa2617d Mon Sep 17 00:00:00 2001
From: Victor Porof 
Date: Sun, 24 May 2015 12:12:20 -0400
Subject: [PATCH 74/89] Bug 1167961 - Task is incorrectly used in
 compatibility.js, r=jsantell

---
 .../devtools/performance/modules/logic/compatibility.js   | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/browser/devtools/performance/modules/logic/compatibility.js b/browser/devtools/performance/modules/logic/compatibility.js
index 62b3203fe94..8b7f676b77a 100644
--- a/browser/devtools/performance/modules/logic/compatibility.js
+++ b/browser/devtools/performance/modules/logic/compatibility.js
@@ -3,8 +3,6 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const { Task } = require("resource://gre/modules/Task.jsm");
-
 loader.lazyRequireGetter(this, "promise");
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/toolkit/event-emitter");
@@ -86,7 +84,7 @@ function memoryActorSupported (target) {
   // but no memory actor (like addon debugging in Gecko 38+)
   return !!target.getTrait("memoryActorAllocations") && target.hasActor("memory");
 }
-exports.memoryActorSupported = Task.async(memoryActorSupported);
+exports.memoryActorSupported = memoryActorSupported;
 
 /**
  * Takes a TabTarget, and checks existence of a TimelineActor on
@@ -104,7 +102,7 @@ function timelineActorSupported(target) {
 
   return target.hasActor("timeline");
 }
-exports.timelineActorSupported = Task.async(timelineActorSupported);
+exports.timelineActorSupported = timelineActorSupported;
 
 /**
  * Returns a promise resolving to the location of the profiler actor
@@ -131,7 +129,7 @@ function getProfiler (target) {
   }
   return deferred.promise;
 }
-exports.getProfiler = Task.async(getProfiler);
+exports.getProfiler = getProfiler;
 
 /**
  * Makes a request to an actor that does not have the modern `Front`

From e410cc6092c5af93ff5cd10a0b598c87ba06e8f2 Mon Sep 17 00:00:00 2001
From: Victor Porof 
Date: Sun, 24 May 2015 12:12:20 -0400
Subject: [PATCH 75/89] Bug 1167962 - Keep exports at bottom of modules,
 r=jsantell

---
 .../performance/modules/logic/actors.js       | 17 +++++------
 .../modules/logic/compatibility.js            | 11 +++----
 .../performance/modules/logic/frame-utils.js  | 15 ++++++----
 .../performance/modules/logic/front.js        |  2 +-
 .../devtools/performance/modules/logic/io.js  |  6 ++--
 .../devtools/performance/modules/logic/jit.js |  7 +++--
 .../performance/modules/logic/marker-utils.js | 10 ++++---
 .../modules/logic/recording-model.js          |  2 +-
 .../modules/logic/recording-utils.js          | 30 +++++++++++--------
 .../performance/modules/logic/tree-model.js   |  8 ++---
 .../modules/widgets/marker-details.js         |  4 +--
 .../performance/modules/widgets/tree-view.js  |  4 +--
 .../performance/performance-controller.js     |  2 +-
 .../browser_perf-allocations-to-samples.js    |  2 +-
 .../test/browser_perf-events-calltree.js      |  3 +-
 .../test/browser_perf-jit-model-01.js         |  2 +-
 .../test/browser_perf-jit-model-02.js         |  2 +-
 .../test/browser_perf-jit-view-01.js          |  2 +-
 .../test/browser_perf-jit-view-02.js          |  2 +-
 .../test/browser_perf_recordings-io-04.js     |  2 +-
 .../test/browser_profiler_tree-model-06.js    |  2 +-
 browser/devtools/performance/test/head.js     |  2 +-
 22 files changed, 74 insertions(+), 63 deletions(-)

diff --git a/browser/devtools/performance/modules/logic/actors.js b/browser/devtools/performance/modules/logic/actors.js
index 3c12cf899ba..0cf3cdc9fb2 100644
--- a/browser/devtools/performance/modules/logic/actors.js
+++ b/browser/devtools/performance/modules/logic/actors.js
@@ -14,7 +14,7 @@ loader.lazyRequireGetter(this, "Poller",
 loader.lazyRequireGetter(this, "CompatUtils",
   "devtools/performance/compatibility");
 loader.lazyRequireGetter(this, "RecordingUtils",
-  "devtools/performance/recording-utils", true);
+  "devtools/performance/recording-utils");
 loader.lazyRequireGetter(this, "TimelineFront",
   "devtools/server/actors/timeline", true);
 loader.lazyRequireGetter(this, "MemoryFront",
@@ -207,10 +207,6 @@ ProfilerFrontFacade.prototype = {
   toString: () => "[object ProfilerFrontFacade]"
 };
 
-// Bind all the methods that directly proxy to the actor
-PROFILER_ACTOR_METHODS.forEach(method => ProfilerFrontFacade.prototype[method] = CompatUtils.actorCompatibilityBridge(method));
-exports.ProfilerFront = ProfilerFrontFacade;
-
 /**
  * Constructor for a facade around an underlying TimelineFront.
  */
@@ -258,10 +254,6 @@ TimelineFrontFacade.prototype = {
   toString: () => "[object TimelineFrontFacade]"
 };
 
-// Bind all the methods that directly proxy to the actor
-TIMELINE_ACTOR_METHODS.forEach(method => TimelineFrontFacade.prototype[method] = CompatUtils.actorCompatibilityBridge(method));
-exports.TimelineFront = TimelineFrontFacade;
-
 /**
  * Constructor for a facade around an underlying ProfilerFront.
  */
@@ -378,5 +370,10 @@ MemoryFrontFacade.prototype = {
 };
 
 // Bind all the methods that directly proxy to the actor
-MEMORY_ACTOR_METHODS.forEach(method => MemoryFrontFacade.prototype[method] = CompatUtils.actorCompatibilityBridge(method));
+PROFILER_ACTOR_METHODS.forEach(m => ProfilerFrontFacade.prototype[m] = CompatUtils.actorCompatibilityBridge(m));
+TIMELINE_ACTOR_METHODS.forEach(m => TimelineFrontFacade.prototype[m] = CompatUtils.actorCompatibilityBridge(m));
+MEMORY_ACTOR_METHODS.forEach(m => MemoryFrontFacade.prototype[m] = CompatUtils.actorCompatibilityBridge(m));
+
+exports.ProfilerFront = ProfilerFrontFacade;
+exports.TimelineFront = TimelineFrontFacade;
 exports.MemoryFront = MemoryFrontFacade;
diff --git a/browser/devtools/performance/modules/logic/compatibility.js b/browser/devtools/performance/modules/logic/compatibility.js
index 8b7f676b77a..d9a2d102c17 100644
--- a/browser/devtools/performance/modules/logic/compatibility.js
+++ b/browser/devtools/performance/modules/logic/compatibility.js
@@ -34,7 +34,6 @@ function MockMemoryFront () {
     ["getAllocations", createMockAllocations],
   ]);
 }
-exports.MockMemoryFront = MockMemoryFront;
 
 function MockTimelineFront () {
   MockFront.call(this, [
@@ -43,7 +42,6 @@ function MockTimelineFront () {
     ["stop", 0],
   ]);
 }
-exports.MockTimelineFront = MockTimelineFront;
 
 /**
  * Create a fake allocations object, to be used with the MockMemoryFront
@@ -84,7 +82,6 @@ function memoryActorSupported (target) {
   // but no memory actor (like addon debugging in Gecko 38+)
   return !!target.getTrait("memoryActorAllocations") && target.hasActor("memory");
 }
-exports.memoryActorSupported = memoryActorSupported;
 
 /**
  * Takes a TabTarget, and checks existence of a TimelineActor on
@@ -102,7 +99,6 @@ function timelineActorSupported(target) {
 
   return target.hasActor("timeline");
 }
-exports.timelineActorSupported = timelineActorSupported;
 
 /**
  * Returns a promise resolving to the location of the profiler actor
@@ -129,7 +125,6 @@ function getProfiler (target) {
   }
   return deferred.promise;
 }
-exports.getProfiler = getProfiler;
 
 /**
  * Makes a request to an actor that does not have the modern `Front`
@@ -174,4 +169,10 @@ function actorCompatibilityBridge (method) {
     }
   };
 }
+
+exports.MockMemoryFront = MockMemoryFront;
+exports.MockTimelineFront = MockTimelineFront;
+exports.memoryActorSupported = memoryActorSupported;
+exports.timelineActorSupported = timelineActorSupported;
+exports.getProfiler = getProfiler;
 exports.actorCompatibilityBridge = actorCompatibilityBridge;
diff --git a/browser/devtools/performance/modules/logic/frame-utils.js b/browser/devtools/performance/modules/logic/frame-utils.js
index bc212f1e353..faa2b26a098 100644
--- a/browser/devtools/performance/modules/logic/frame-utils.js
+++ b/browser/devtools/performance/modules/logic/frame-utils.js
@@ -42,7 +42,7 @@ const gInflatedFrameStore = new WeakMap();
  * Parses the raw location of this function call to retrieve the actual
  * function name, source url, host name, line and column.
  */
-exports.parseLocation = function parseLocation(location, fallbackLine, fallbackColumn) {
+function parseLocation(location, fallbackLine, fallbackColumn) {
   // Parse the `location` for the function name, source url, line, column etc.
 
   let line, column, url;
@@ -190,7 +190,6 @@ function isContent({ location, category }) {
   // If there was no left parenthesis, try matching from the start.
   return isContentScheme(location, 0);
 }
-exports.isContent = isContent;
 
 /**
  * Get caches to cache inflated frames and computed frame keys of a frame
@@ -199,7 +198,7 @@ exports.isContent = isContent;
  * @param object framesTable
  * @return object
  */
-exports.getInflatedFrameCache = function getInflatedFrameCache(frameTable) {
+function getInflatedFrameCache(frameTable) {
   let inflatedCache = gInflatedFrameStore.get(frameTable);
   if (inflatedCache !== undefined) {
     return inflatedCache;
@@ -220,7 +219,7 @@ exports.getInflatedFrameCache = function getInflatedFrameCache(frameTable) {
  * @param object stringTable
  * @param object allocationsTable
  */
-exports.getOrAddInflatedFrame = function getOrAddInflatedFrame(cache, index, frameTable, stringTable, allocationsTable) {
+function getOrAddInflatedFrame(cache, index, frameTable, stringTable, allocationsTable) {
   let inflatedFrame = cache[index];
   if (inflatedFrame === null) {
     inflatedFrame = cache[index] = new InflatedFrame(index, frameTable, stringTable, allocationsTable);
@@ -292,8 +291,6 @@ InflatedFrame.prototype.getFrameKey = function getFrameKey(options) {
   return "";
 };
 
-exports.InflatedFrame = InflatedFrame;
-
 /**
  * Helper for getting an nsIURL instance out of a string.
  */
@@ -420,3 +417,9 @@ function isChromeScheme(location, i) {
 function isNumeric(c) {
   return c >= CHAR_CODE_0 && c <= CHAR_CODE_9;
 }
+
+exports.parseLocation = parseLocation;
+exports.isContent = isContent;
+exports.getInflatedFrameCache = getInflatedFrameCache;
+exports.getOrAddInflatedFrame = getOrAddInflatedFrame;
+exports.InflatedFrame = InflatedFrame;
diff --git a/browser/devtools/performance/modules/logic/front.js b/browser/devtools/performance/modules/logic/front.js
index 309b2cd448c..621e1d31dc2 100644
--- a/browser/devtools/performance/modules/logic/front.js
+++ b/browser/devtools/performance/modules/logic/front.js
@@ -534,5 +534,5 @@ function getRecordingModelPrefs () {
   };
 }
 
-exports.getPerformanceActorsConnection = target => SharedPerformanceActors.forTarget(target);
+exports.getPerformanceActorsConnection = t => SharedPerformanceActors.forTarget(t);
 exports.PerformanceFront = PerformanceFront;
diff --git a/browser/devtools/performance/modules/logic/io.js b/browser/devtools/performance/modules/logic/io.js
index cd51ad568d9..d4587c001e5 100644
--- a/browser/devtools/performance/modules/logic/io.js
+++ b/browser/devtools/performance/modules/logic/io.js
@@ -8,7 +8,7 @@ const { Cc, Ci, Cu, Cr } = require("chrome");
 loader.lazyRequireGetter(this, "Services");
 loader.lazyRequireGetter(this, "promise");
 loader.lazyRequireGetter(this, "RecordingUtils",
-  "devtools/performance/recording-utils", true);
+  "devtools/performance/recording-utils");
 
 loader.lazyImporter(this, "FileUtils",
   "resource://gre/modules/FileUtils.jsm");
@@ -111,8 +111,6 @@ let PerformanceIO = {
   }
 };
 
-exports.PerformanceIO = PerformanceIO;
-
 /**
  * Returns a boolean indicating whether or not the passed in `version`
  * is supported by this serializer.
@@ -161,3 +159,5 @@ function convertLegacyData (legacyData) {
 
   return data;
 }
+
+exports.PerformanceIO = PerformanceIO;
diff --git a/browser/devtools/performance/modules/logic/jit.js b/browser/devtools/performance/modules/logic/jit.js
index b7e9729f16c..6ddc28aae02 100644
--- a/browser/devtools/performance/modules/logic/jit.js
+++ b/browser/devtools/performance/modules/logic/jit.js
@@ -114,7 +114,7 @@ const SUCCESSFUL_OUTCOMES = [
  * @type {number} id
  */
 
-const OptimizationSite = exports.OptimizationSite = function (id, opts) {
+const OptimizationSite = function (id, opts) {
   this.id = id;
   this.data = opts;
   this.samples = 1;
@@ -166,7 +166,7 @@ OptimizationSite.prototype.getIonTypes = function () {
  *                        JIT optimizations. Do not modify this!
  */
 
-const JITOptimizations = exports.JITOptimizations = function (rawSites, stringTable) {
+const JITOptimizations = function (rawSites, stringTable) {
   // Build a histogram of optimization sites.
   let sites = [];
 
@@ -238,3 +238,6 @@ function maybeTypeset(typeset, stringTable) {
     };
   });
 }
+
+exports.OptimizationSite = OptimizationSite;
+exports.JITOptimizations = JITOptimizations;
diff --git a/browser/devtools/performance/modules/logic/marker-utils.js b/browser/devtools/performance/modules/logic/marker-utils.js
index 108563bf0fb..274b5707208 100644
--- a/browser/devtools/performance/modules/logic/marker-utils.js
+++ b/browser/devtools/performance/modules/logic/marker-utils.js
@@ -28,7 +28,6 @@ function getMarkerLabel (marker) {
   // as a string.
   return typeof blueprint.label === "function" ? blueprint.label(marker) : blueprint.label;
 }
-exports.getMarkerLabel = getMarkerLabel;
 
 /**
  * Returns the correct generic name for a marker class, like "Function Call"
@@ -57,7 +56,6 @@ function getMarkerClassName (type) {
 
   return className;
 }
-exports.getMarkerClassName = getMarkerClassName;
 
 /**
  * Returns an array of objects with key/value pairs of what should be rendered
@@ -92,12 +90,11 @@ function getMarkerFields (marker) {
     return fields;
   }, []);
 }
-exports.getMarkerFields = getMarkerFields;
 
 /**
  * Utilites for creating elements for markers.
  */
-const DOM = exports.DOM = {
+const DOM = {
   /**
    * Builds all the fields possible for the given marker. Returns an
    * array of elements to be appended to a parent element.
@@ -272,3 +269,8 @@ const DOM = exports.DOM = {
     return container;
   }
 };
+
+exports.getMarkerLabel = getMarkerLabel;
+exports.getMarkerClassName = getMarkerClassName;
+exports.getMarkerFields = getMarkerFields;
+exports.DOM = DOM;
diff --git a/browser/devtools/performance/modules/logic/recording-model.js b/browser/devtools/performance/modules/logic/recording-model.js
index 3174e93ef08..67e2a719ad7 100644
--- a/browser/devtools/performance/modules/logic/recording-model.js
+++ b/browser/devtools/performance/modules/logic/recording-model.js
@@ -9,7 +9,7 @@ const { Task } = require("resource://gre/modules/Task.jsm");
 loader.lazyRequireGetter(this, "PerformanceIO",
   "devtools/performance/io", true);
 loader.lazyRequireGetter(this, "RecordingUtils",
-  "devtools/performance/recording-utils", true);
+  "devtools/performance/recording-utils");
 
 /**
  * Model for a wholistic profile, containing the duration, profiling data,
diff --git a/browser/devtools/performance/modules/logic/recording-utils.js b/browser/devtools/performance/modules/logic/recording-utils.js
index 91cbb4ce7e3..af7f7fc3d56 100644
--- a/browser/devtools/performance/modules/logic/recording-utils.js
+++ b/browser/devtools/performance/modules/logic/recording-utils.js
@@ -10,8 +10,6 @@ const { Cc, Ci, Cu, Cr } = require("chrome");
  * such as filtering profile samples or offsetting timestamps.
  */
 
-exports.RecordingUtils = {};
-
 /**
  * Filters all the samples in the provided profiler data to be more recent
  * than the specified start time.
@@ -21,7 +19,7 @@ exports.RecordingUtils = {};
  * @param number profilerStartTime
  *        The earliest acceptable sample time (in milliseconds).
  */
-exports.RecordingUtils.filterSamples = function(profile, profilerStartTime) {
+function filterSamples(profile, profilerStartTime) {
   let firstThread = profile.threads[0];
   const TIME_SLOT = firstThread.samples.schema.time;
   firstThread.samples.data = firstThread.samples.data.filter(e => {
@@ -37,7 +35,7 @@ exports.RecordingUtils.filterSamples = function(profile, profilerStartTime) {
  * @param number timeOffset
  *        The amount of time to offset by (in milliseconds).
  */
-exports.RecordingUtils.offsetSampleTimes = function(profile, timeOffset) {
+function offsetSampleTimes(profile, timeOffset) {
   let firstThread = profile.threads[0];
   const TIME_SLOT = firstThread.samples.schema.time;
   let samplesData = firstThread.samples.data;
@@ -54,7 +52,7 @@ exports.RecordingUtils.offsetSampleTimes = function(profile, timeOffset) {
  * @param number timeOffset
  *        The amount of time to offset by (in milliseconds).
  */
-exports.RecordingUtils.offsetMarkerTimes = function(markers, timeOffset) {
+function offsetMarkerTimes(markers, timeOffset) {
   for (let marker of markers) {
     marker.start -= timeOffset;
     marker.end -= timeOffset;
@@ -72,7 +70,7 @@ exports.RecordingUtils.offsetMarkerTimes = function(markers, timeOffset) {
  * @param number timeScale
  *        The factor to scale by, after offsetting.
  */
-exports.RecordingUtils.offsetAndScaleTimestamps = function(timestamps, timeOffset, timeScale) {
+function offsetAndScaleTimestamps(timestamps, timeOffset, timeScale) {
   for (let i = 0, len = timestamps.length; i < len; i++) {
     timestamps[i] -= timeOffset;
     timestamps[i] /= timeScale;
@@ -95,7 +93,7 @@ let gProfileThreadFromAllocationCache = new WeakMap();
  * @return object
  *         The "profile" describing the allocations log.
  */
-exports.RecordingUtils.getProfileThreadFromAllocations = function(allocations) {
+function getProfileThreadFromAllocations(allocations) {
   let cached = gProfileThreadFromAllocationCache.get(allocations);
   if (cached) {
     return cached;
@@ -208,7 +206,7 @@ exports.RecordingUtils.getProfileThreadFromAllocations = function(allocations) {
  * @return object
  *         The filtered timeline blueprint.
  */
-exports.RecordingUtils.getFilteredBlueprint = function({ blueprint, hiddenMarkers }) {
+function getFilteredBlueprint({ blueprint, hiddenMarkers }) {
   // Clone functions here just to prevent an error, as the blueprint
   // contains functions (even though we do not use them).
   let filteredBlueprint = Cu.cloneInto(blueprint, {}, { cloneFunctions: true });
@@ -259,7 +257,7 @@ exports.RecordingUtils.getFilteredBlueprint = function({ blueprint, hiddenMarker
  * @param object profile
  *               A profile with version 2.
  */
-exports.RecordingUtils.deflateProfile = function deflateProfile(profile) {
+function deflateProfile(profile) {
   profile.threads = profile.threads.map((thread) => {
     let uniqueStacks = new UniqueStacks();
     return deflateThread(thread, uniqueStacks);
@@ -379,7 +377,6 @@ function deflateThread(thread, uniqueStacks) {
     stringTable: uniqueStacks.getStringTable()
   };
 }
-exports.RecordingUtils.deflateThread = deflateThread;
 
 function stackTableWithSchema(data) {
   let slot = 0;
@@ -448,8 +445,6 @@ UniqueStrings.prototype.getOrAddStringIndex = function(s) {
   return index;
 };
 
-exports.RecordingUtils.UniqueStrings = UniqueStrings;
-
 /**
  * A helper class to deduplicate old-version profiles.
  *
@@ -571,4 +566,13 @@ UniqueStacks.prototype.getOrAddStringIndex = function(s) {
   return this._uniqueStrings.getOrAddStringIndex(s);
 };
 
-exports.RecordingUtils.UniqueStacks = UniqueStacks;
+exports.filterSamples = filterSamples;
+exports.offsetSampleTimes = offsetSampleTimes;
+exports.offsetMarkerTimes = offsetMarkerTimes;
+exports.offsetAndScaleTimestamps = offsetAndScaleTimestamps;
+exports.getProfileThreadFromAllocations = getProfileThreadFromAllocations;
+exports.getFilteredBlueprint = getFilteredBlueprint;
+exports.deflateProfile = deflateProfile;
+exports.deflateThread = deflateThread;
+exports.UniqueStrings = UniqueStrings;
+exports.UniqueStacks = UniqueStacks;
diff --git a/browser/devtools/performance/modules/logic/tree-model.js b/browser/devtools/performance/modules/logic/tree-model.js
index fc1c5f8653f..e8b362ed5be 100644
--- a/browser/devtools/performance/modules/logic/tree-model.js
+++ b/browser/devtools/performance/modules/logic/tree-model.js
@@ -20,10 +20,6 @@ loader.lazyRequireGetter(this, "JITOptimizations",
 loader.lazyRequireGetter(this, "FrameUtils",
   "devtools/performance/frame-utils");
 
-exports.ThreadNode = ThreadNode;
-exports.FrameNode = FrameNode;
-exports.FrameNode.isContent = FrameUtils.isContent;
-
 /**
  * A call tree for a thread. This is essentially a linkage between all frames
  * of all samples into a single tree structure, with additional information
@@ -502,3 +498,7 @@ FrameNode.prototype = {
     return new JITOptimizations(this._optimizations, this._stringTable);
   }
 };
+
+exports.ThreadNode = ThreadNode;
+exports.FrameNode = FrameNode;
+exports.FrameNode.isContent = FrameUtils.isContent;
diff --git a/browser/devtools/performance/modules/widgets/marker-details.js b/browser/devtools/performance/modules/widgets/marker-details.js
index a5c2045947b..19572c1dc46 100644
--- a/browser/devtools/performance/modules/widgets/marker-details.js
+++ b/browser/devtools/performance/modules/widgets/marker-details.js
@@ -100,8 +100,6 @@ MarkerDetails.prototype = {
   },
 };
 
-exports.MarkerDetails = MarkerDetails;
-
 /**
  * Take an element from an event `target`, and asend through
  * the DOM, looking for an element with a `data-action` attribute. Return
@@ -123,3 +121,5 @@ function findActionFromEvent (target, container) {
   }
   return null;
 }
+
+exports.MarkerDetails = MarkerDetails;
diff --git a/browser/devtools/performance/modules/widgets/tree-view.js b/browser/devtools/performance/modules/widgets/tree-view.js
index 545697b5bb8..a02eff7c51d 100644
--- a/browser/devtools/performance/modules/widgets/tree-view.js
+++ b/browser/devtools/performance/modules/widgets/tree-view.js
@@ -41,8 +41,6 @@ const DEFAULT_VISIBLE_CELLS = {
 const clamp = (val, min, max) => Math.max(min, Math.min(max, val));
 const sum = vals => vals.reduce((a, b) => a + b, 0);
 
-exports.CallView = CallView;
-
 /**
  * An item in a call tree view, which looks like this:
  *
@@ -402,3 +400,5 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
     this.root.emit("link", this);
   }
 });
+
+exports.CallView = CallView;
diff --git a/browser/devtools/performance/performance-controller.js b/browser/devtools/performance/performance-controller.js
index 077c6ad9eac..df30704cd21 100644
--- a/browser/devtools/performance/performance-controller.js
+++ b/browser/devtools/performance/performance-controller.js
@@ -20,7 +20,7 @@ loader.lazyRequireGetter(this, "L10N",
 loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
   "devtools/performance/global", true);
 loader.lazyRequireGetter(this, "RecordingUtils",
-  "devtools/performance/recording-utils", true);
+  "devtools/performance/recording-utils");
 loader.lazyRequireGetter(this, "RecordingModel",
   "devtools/performance/recording-model", true);
 loader.lazyRequireGetter(this, "GraphsController",
diff --git a/browser/devtools/performance/test/browser_perf-allocations-to-samples.js b/browser/devtools/performance/test/browser_perf-allocations-to-samples.js
index 30a680e53c8..4317951d6a0 100644
--- a/browser/devtools/performance/test/browser_perf-allocations-to-samples.js
+++ b/browser/devtools/performance/test/browser_perf-allocations-to-samples.js
@@ -8,7 +8,7 @@
  */
 
 function test() {
-  let { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
+  let RecordingUtils = devtools.require("devtools/performance/recording-utils");
 
   let output = RecordingUtils.getProfileThreadFromAllocations(TEST_DATA);
   is(output.toSource(), EXPECTED_OUTPUT.toSource(), "The output is correct.");
diff --git a/browser/devtools/performance/test/browser_perf-events-calltree.js b/browser/devtools/performance/test/browser_perf-events-calltree.js
index 61cbd0f1d0b..ed6c38ce17e 100644
--- a/browser/devtools/performance/test/browser_perf-events-calltree.js
+++ b/browser/devtools/performance/test/browser_perf-events-calltree.js
@@ -5,7 +5,8 @@
  * Tests that the call tree up/down events work for js calltree and memory calltree.
  */
 const { ThreadNode } = devtools.require("devtools/performance/tree-model");
-const { RecordingUtils } = devtools.require("devtools/performance/recording-utils")
+const RecordingUtils = devtools.require("devtools/performance/recording-utils")
+
 function spawnTest () {
   let focus = 0;
   let focusEvent = () => focus++;
diff --git a/browser/devtools/performance/test/browser_perf-jit-model-01.js b/browser/devtools/performance/test/browser_perf-jit-model-01.js
index b923aa2dd6e..b2d30324819 100644
--- a/browser/devtools/performance/test/browser_perf-jit-model-01.js
+++ b/browser/devtools/performance/test/browser_perf-jit-model-01.js
@@ -7,7 +7,7 @@
  * FrameNode, and the returning of that data is as expected.
  */
 
-const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
+const RecordingUtils = devtools.require("devtools/performance/recording-utils");
 
 function test() {
   let { JITOptimizations } = devtools.require("devtools/performance/jit");
diff --git a/browser/devtools/performance/test/browser_perf-jit-model-02.js b/browser/devtools/performance/test/browser_perf-jit-model-02.js
index 367c007bb0e..1ac6d200adc 100644
--- a/browser/devtools/performance/test/browser_perf-jit-model-02.js
+++ b/browser/devtools/performance/test/browser_perf-jit-model-02.js
@@ -6,7 +6,7 @@
  * OptimizationSites methods work as expected.
  */
 
-const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
+const RecordingUtils = devtools.require("devtools/performance/recording-utils");
 
 function test() {
   let { JITOptimizations, OptimizationSite } = devtools.require("devtools/performance/jit");
diff --git a/browser/devtools/performance/test/browser_perf-jit-view-01.js b/browser/devtools/performance/test/browser_perf-jit-view-01.js
index 2c8970ef7e1..2792839605e 100644
--- a/browser/devtools/performance/test/browser_perf-jit-view-01.js
+++ b/browser/devtools/performance/test/browser_perf-jit-view-01.js
@@ -6,7 +6,7 @@
  * if on, and displays selected frames on focus.
  */
 
-const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
+const RecordingUtils = devtools.require("devtools/performance/recording-utils");
 
 Services.prefs.setBoolPref(INVERT_PREF, false);
 
diff --git a/browser/devtools/performance/test/browser_perf-jit-view-02.js b/browser/devtools/performance/test/browser_perf-jit-view-02.js
index d6969ed7204..68fdf47cb83 100644
--- a/browser/devtools/performance/test/browser_perf-jit-view-02.js
+++ b/browser/devtools/performance/test/browser_perf-jit-view-02.js
@@ -7,7 +7,7 @@
  */
 
 const { CATEGORY_MASK } = devtools.require("devtools/performance/global");
-const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
+const RecordingUtils = devtools.require("devtools/performance/recording-utils");
 
 Services.prefs.setBoolPref(INVERT_PREF, false);
 Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false);
diff --git a/browser/devtools/performance/test/browser_perf_recordings-io-04.js b/browser/devtools/performance/test/browser_perf_recordings-io-04.js
index 83b8e2c0dff..cf194c0907f 100644
--- a/browser/devtools/performance/test/browser_perf_recordings-io-04.js
+++ b/browser/devtools/performance/test/browser_perf_recordings-io-04.js
@@ -5,7 +5,7 @@
  * Tests if the performance tool can import profiler data from the
  * original profiler tool (Performance Recording v1, and Profiler data v2) and the correct views and graphs are loaded.
  */
-let { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
+let RecordingUtils = devtools.require("devtools/performance/recording-utils");
 
 let TICKS_DATA = (function () {
   let ticks = [];
diff --git a/browser/devtools/performance/test/browser_profiler_tree-model-06.js b/browser/devtools/performance/test/browser_profiler_tree-model-06.js
index d2456b7d990..9e40b913e50 100644
--- a/browser/devtools/performance/test/browser_profiler_tree-model-06.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-model-06.js
@@ -6,7 +6,7 @@
  * the FrameNodes have the correct optimization data after iterating over samples.
  */
 
-const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
+const RecordingUtils = devtools.require("devtools/performance/recording-utils");
 
 let gUniqueStacks = new RecordingUtils.UniqueStacks();
 
diff --git a/browser/devtools/performance/test/head.js b/browser/devtools/performance/test/head.js
index 88cce8f8a15..f4197ae7451 100644
--- a/browser/devtools/performance/test/head.js
+++ b/browser/devtools/performance/test/head.js
@@ -557,7 +557,7 @@ function getFrameNodePath(root, path) {
  * Synthesize a profile for testing.
  */
 function synthesizeProfileForTest(samples) {
-  const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
+  const RecordingUtils = devtools.require("devtools/performance/recording-utils");
 
   samples.unshift({
     time: 0,

From 289a05eefc7d9ce0747d9c25068f37a3c7a71172 Mon Sep 17 00:00:00 2001
From: Victor Porof 
Date: Sun, 24 May 2015 12:12:20 -0400
Subject: [PATCH 76/89] Bug 1167963 - FrameNode should not export `isContent`,
 r=jsantell

---
 .../performance/modules/logic/tree-model.js   |  1 -
 .../test/browser_profiler_content-check.js    | 34 +++++++++----------
 .../test/browser_profiler_tree-frame-node.js  | 17 +++++-----
 3 files changed, 26 insertions(+), 26 deletions(-)

diff --git a/browser/devtools/performance/modules/logic/tree-model.js b/browser/devtools/performance/modules/logic/tree-model.js
index e8b362ed5be..14907bd70de 100644
--- a/browser/devtools/performance/modules/logic/tree-model.js
+++ b/browser/devtools/performance/modules/logic/tree-model.js
@@ -501,4 +501,3 @@ FrameNode.prototype = {
 
 exports.ThreadNode = ThreadNode;
 exports.FrameNode = FrameNode;
-exports.FrameNode.isContent = FrameUtils.isContent;
diff --git a/browser/devtools/performance/test/browser_profiler_content-check.js b/browser/devtools/performance/test/browser_profiler_content-check.js
index 7a8f46e58c4..0f2b1b304f8 100644
--- a/browser/devtools/performance/test/browser_profiler_content-check.js
+++ b/browser/devtools/performance/test/browser_profiler_content-check.js
@@ -7,44 +7,44 @@
  */
 
 function test() {
-  let { FrameNode } = devtools.require("devtools/performance/tree-model");
+  let FrameUtils = devtools.require("devtools/performance/frame-utils");
 
-  ok(FrameNode.isContent({ location: "http://foo" }),
+  ok(FrameUtils.isContent({ location: "http://foo" }),
     "Verifying content/chrome frames is working properly.");
-  ok(FrameNode.isContent({ location: "https://foo" }),
+  ok(FrameUtils.isContent({ location: "https://foo" }),
     "Verifying content/chrome frames is working properly.");
-  ok(FrameNode.isContent({ location: "file://foo" }),
+  ok(FrameUtils.isContent({ location: "file://foo" }),
     "Verifying content/chrome frames is working properly.");
 
-  ok(!FrameNode.isContent({ location: "chrome://foo" }),
+  ok(!FrameUtils.isContent({ location: "chrome://foo" }),
     "Verifying content/chrome frames is working properly.");
-  ok(!FrameNode.isContent({ location: "resource://foo" }),
+  ok(!FrameUtils.isContent({ location: "resource://foo" }),
     "Verifying content/chrome frames is working properly.");
 
-  ok(!FrameNode.isContent({ location: "chrome://foo -> http://bar" }),
+  ok(!FrameUtils.isContent({ location: "chrome://foo -> http://bar" }),
     "Verifying content/chrome frames is working properly.");
-  ok(!FrameNode.isContent({ location: "chrome://foo -> https://bar" }),
+  ok(!FrameUtils.isContent({ location: "chrome://foo -> https://bar" }),
     "Verifying content/chrome frames is working properly.");
-  ok(!FrameNode.isContent({ location: "chrome://foo -> file://bar" }),
+  ok(!FrameUtils.isContent({ location: "chrome://foo -> file://bar" }),
     "Verifying content/chrome frames is working properly.");
 
-  ok(!FrameNode.isContent({ location: "resource://foo -> http://bar" }),
+  ok(!FrameUtils.isContent({ location: "resource://foo -> http://bar" }),
     "Verifying content/chrome frames is working properly.");
-  ok(!FrameNode.isContent({ location: "resource://foo -> https://bar" }),
+  ok(!FrameUtils.isContent({ location: "resource://foo -> https://bar" }),
     "Verifying content/chrome frames is working properly.");
-  ok(!FrameNode.isContent({ location: "resource://foo -> file://bar" }),
+  ok(!FrameUtils.isContent({ location: "resource://foo -> file://bar" }),
     "Verifying content/chrome frames is working properly.");
 
-  ok(!FrameNode.isContent({ category: 1, location: "chrome://foo" }),
+  ok(!FrameUtils.isContent({ category: 1, location: "chrome://foo" }),
     "Verifying content/chrome frames is working properly.");
-  ok(!FrameNode.isContent({ category: 1, location: "resource://foo" }),
+  ok(!FrameUtils.isContent({ category: 1, location: "resource://foo" }),
     "Verifying content/chrome frames is working properly.");
 
-  ok(!FrameNode.isContent({ category: 1, location: "file://foo -> http://bar" }),
+  ok(!FrameUtils.isContent({ category: 1, location: "file://foo -> http://bar" }),
     "Verifying content/chrome frames is working properly.");
-  ok(!FrameNode.isContent({ category: 1, location: "file://foo -> https://bar" }),
+  ok(!FrameUtils.isContent({ category: 1, location: "file://foo -> https://bar" }),
     "Verifying content/chrome frames is working properly.");
-  ok(!FrameNode.isContent({ category: 1, location: "file://foo -> file://bar" }),
+  ok(!FrameUtils.isContent({ category: 1, location: "file://foo -> file://bar" }),
     "Verifying content/chrome frames is working properly.");
 
   finish();
diff --git a/browser/devtools/performance/test/browser_profiler_tree-frame-node.js b/browser/devtools/performance/test/browser_profiler_tree-frame-node.js
index 3862104a3c1..fcd7a9a23a7 100644
--- a/browser/devtools/performance/test/browser_profiler_tree-frame-node.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-frame-node.js
@@ -6,13 +6,14 @@
  */
 
 function test() {
+  let FrameUtils = devtools.require("devtools/performance/frame-utils");
   let { FrameNode } = devtools.require("devtools/performance/tree-model");
   let { CATEGORY_OTHER } = devtools.require("devtools/performance/global");
 
   let frame1 = new FrameNode("hello/<.world (http://foo/bar.js:123:987)", {
     location: "hello/<.world (http://foo/bar.js:123:987)",
     line: 456,
-    isContent: FrameNode.isContent({
+    isContent: FrameUtils.isContent({
       location: "hello/<.world (http://foo/bar.js:123:987)"
     })
   }, false);
@@ -39,7 +40,7 @@ function test() {
   let frame2 = new FrameNode("hello/<.world (http://foo/bar.js#baz:123:987)", {
     location: "hello/<.world (http://foo/bar.js#baz:123:987)",
     line: 456,
-    isContent: FrameNode.isContent({
+    isContent: FrameUtils.isContent({
       location: "hello/<.world (http://foo/bar.js#baz:123:987)"
     })
   }, false);
@@ -66,7 +67,7 @@ function test() {
   let frame3 = new FrameNode("hello/<.world (http://foo/#bar:123:987)", {
     location: "hello/<.world (http://foo/#bar:123:987)",
     line: 456,
-    isContent: FrameNode.isContent({
+    isContent: FrameUtils.isContent({
       location: "hello/<.world (http://foo/#bar:123:987)"
     })
   }, false);
@@ -93,7 +94,7 @@ function test() {
   let frame4 = new FrameNode("hello/<.world (http://foo/:123:987)", {
     location: "hello/<.world (http://foo/:123:987)",
     line: 456,
-    isContent: FrameNode.isContent({
+    isContent: FrameUtils.isContent({
       location: "hello/<.world (http://foo/:123:987)"
     })
   }, false);
@@ -120,7 +121,7 @@ function test() {
   let frame5 = new FrameNode("hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)", {
     location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)",
     line: 456,
-    isContent: FrameNode.isContent({
+    isContent: FrameUtils.isContent({
       location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)"
     })
   }, false);
@@ -148,7 +149,7 @@ function test() {
     location: "Foo::Bar::Baz",
     line: 456,
     category: CATEGORY_OTHER,
-    isContent: FrameNode.isContent({
+    isContent: FrameUtils.isContent({
       location: "Foo::Bar::Baz",
       category: CATEGORY_OTHER
     })
@@ -173,7 +174,7 @@ function test() {
 
   let frame7 = new FrameNode("EnterJIT", {
     location: "EnterJIT",
-    isContent: FrameNode.isContent({
+    isContent: FrameUtils.isContent({
       location: "EnterJIT"
     })
   }, false);
@@ -217,7 +218,7 @@ function test() {
   let frame10 = new FrameNode("main (http://localhost:8888/file.js:123:987)", {
     location: "main (http://localhost:8888/file.js:123:987)",
     line: 123,
-    isContent: FrameNode.isContent({
+    isContent: FrameUtils.isContent({
       location: "main (http://localhost:8888/file.js:123:987)"
     })
   }, false);

From c2772871a3af4a4242c58d3bea929b1de8e9120e Mon Sep 17 00:00:00 2001
From: Victor Porof 
Date: Sun, 24 May 2015 12:12:20 -0400
Subject: [PATCH 77/89] Bug 1167967 - Memory allocations tree render() has an
 extra unnecessary options argument, r=jsantell

---
 .../views/details-memory-call-tree.js         | 37 +++++++++----------
 1 file changed, 18 insertions(+), 19 deletions(-)

diff --git a/browser/devtools/performance/views/details-memory-call-tree.js b/browser/devtools/performance/views/details-memory-call-tree.js
index 08b3d0c024e..3b41f67ca78 100644
--- a/browser/devtools/performance/views/details-memory-call-tree.js
+++ b/browser/devtools/performance/views/details-memory-call-tree.js
@@ -21,6 +21,8 @@ let MemoryCallTreeView = Heritage.extend(DetailsSubview, {
     DetailsSubview.initialize.call(this);
 
     this._onLink = this._onLink.bind(this);
+
+    this.container = $("#memory-calltree-view > .call-tree-cells-container");
   },
 
   /**
@@ -35,10 +37,11 @@ let MemoryCallTreeView = Heritage.extend(DetailsSubview, {
    *
    * @param object interval [optional]
    *        The { startTime, endTime }, in milliseconds.
-   * @param object options [optional]
-   *        Additional options for new the call tree.
    */
-  render: function (interval={}, options={}) {
+  render: function (interval={}) {
+    let options = {
+      invertTree: PerformanceController.getOption("invert-call-tree")
+    };
     let recording = PerformanceController.getCurrentRecording();
     let allocations = recording.getAllocations();
     let threadNode = this._prepareCallTree(allocations, interval, options);
@@ -66,33 +69,30 @@ let MemoryCallTreeView = Heritage.extend(DetailsSubview, {
    */
   _prepareCallTree: function (allocations, { startTime, endTime }, options) {
     let thread = RecordingUtils.getProfileThreadFromAllocations(allocations);
-    let invertTree = PerformanceController.getOption("invert-call-tree");
+    let { invertTree } = options;
 
-    let threadNode = new ThreadNode(thread,
-      { startTime, endTime, invertTree });
-
-    // If we have an empty profile (no samples), then don't invert the tree, as
-    // it would hide the root node and a completely blank call tree space can be
-    // mis-interpreted as an error.
-    options.inverted = invertTree && threadNode.samples > 0;
-
-    return threadNode;
+    return new ThreadNode(thread, { startTime, endTime, invertTree });
   },
 
   /**
    * Renders the call tree.
    */
   _populateCallTree: function (frameNode, options={}) {
+    // If we have an empty profile (no samples), then don't invert the tree, as
+    // it would hide the root node and a completely blank call tree space can be
+    // mis-interpreted as an error.
+    let inverted = options.invertTree && frameNode.samples > 0;
+
     let root = new CallView({
       frame: frameNode,
-      inverted: options.inverted,
+      inverted: inverted,
       // Root nodes are hidden in inverted call trees.
-      hidden: options.inverted,
+      hidden: inverted,
       // Memory call trees should be sorted by allocations.
       sortingPredicate: (a, b) => a.frame.allocations < b.frame.allocations ? 1 : -1,
       // Call trees should only auto-expand when not inverted. Passing undefined
       // will default to the CALL_TREE_AUTO_EXPAND depth.
-      autoExpandDepth: options.inverted ? 0 : undefined,
+      autoExpandDepth: inverted ? 0 : undefined,
       // Some cells like the time duration and cost percentage don't make sense
       // for a memory allocations call tree.
       visibleCells: {
@@ -109,9 +109,8 @@ let MemoryCallTreeView = Heritage.extend(DetailsSubview, {
     root.on("focus", () => this.emit("focus"));
 
     // Clear out other call trees.
-    let container = $("#memory-calltree-view > .call-tree-cells-container");
-    container.innerHTML = "";
-    root.attachTo(container);
+    this.container.innerHTML = "";
+    root.attachTo(this.container);
 
     // Memory allocation samples don't contain cateogry labels.
     root.toggleCategories(false);

From bf154d89a3284bfe924702e98cede88003fbf4ce Mon Sep 17 00:00:00 2001
From: Victor Porof 
Date: Sun, 24 May 2015 12:12:20 -0400
Subject: [PATCH 78/89] Bug 1167975 - CallView._displaySelf sets this.document
 just because other functions use it; it should pass it as an argument
 instead, r=jsantell

---
 .../performance/modules/widgets/tree-view.js  | 56 +++++++++----------
 1 file changed, 27 insertions(+), 29 deletions(-)

diff --git a/browser/devtools/performance/modules/widgets/tree-view.js b/browser/devtools/performance/modules/widgets/tree-view.js
index a02eff7c51d..b240176040c 100644
--- a/browser/devtools/performance/modules/widgets/tree-view.js
+++ b/browser/devtools/performance/modules/widgets/tree-view.js
@@ -121,34 +121,32 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
    * @return nsIDOMNode
    */
   _displaySelf: function(document, arrowNode) {
-    this.document = document;
-
     let displayedData = this.getDisplayedData();
     let frameInfo = this.frame.getInfo();
 
     if (this.visibleCells.duration) {
-      var durationCell = this._createTimeCell(displayedData.totalDuration);
+      var durationCell = this._createTimeCell(document, displayedData.totalDuration);
     }
     if (this.visibleCells.selfDuration) {
-      var selfDurationCell = this._createTimeCell(displayedData.selfDuration, true);
+      var selfDurationCell = this._createTimeCell(document, displayedData.selfDuration, true);
     }
     if (this.visibleCells.percentage) {
-      var percentageCell = this._createExecutionCell(displayedData.totalPercentage);
+      var percentageCell = this._createExecutionCell(document, displayedData.totalPercentage);
     }
     if (this.visibleCells.selfPercentage) {
-      var selfPercentageCell = this._createExecutionCell(displayedData.selfPercentage, true);
+      var selfPercentageCell = this._createExecutionCell(document, displayedData.selfPercentage, true);
     }
     if (this.visibleCells.allocations) {
-      var allocationsCell = this._createAllocationsCell(displayedData.totalAllocations);
+      var allocationsCell = this._createAllocationsCell(document, displayedData.totalAllocations);
     }
     if (this.visibleCells.selfAllocations) {
-      var selfAllocationsCell = this._createAllocationsCell(displayedData.selfAllocations, true);
+      var selfAllocationsCell = this._createAllocationsCell(document, displayedData.selfAllocations, true);
     }
     if (this.visibleCells.samples) {
-      var samplesCell = this._createSamplesCell(displayedData.samples);
+      var samplesCell = this._createSamplesCell(document, displayedData.samples);
     }
     if (this.visibleCells.function) {
-      var functionCell = this._createFunctionCell(arrowNode, displayedData.name, frameInfo, this.level);
+      var functionCell = this._createFunctionCell(document, arrowNode, displayedData.name, frameInfo, this.level);
     }
 
     let targetNode = document.createElement("hbox");
@@ -214,40 +212,40 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
    * Functions creating each cell in this call view.
    * Invoked by `_displaySelf`.
    */
-  _createTimeCell: function(duration, isSelf = false) {
-    let cell = this.document.createElement("label");
+  _createTimeCell: function(doc, duration, isSelf = false) {
+    let cell = doc.createElement("label");
     cell.className = "plain call-tree-cell";
     cell.setAttribute("type", isSelf ? "self-duration" : "duration");
     cell.setAttribute("crop", "end");
     cell.setAttribute("value", L10N.numberWithDecimals(duration, 2) + " " + MILLISECOND_UNITS);
     return cell;
   },
-  _createExecutionCell: function(percentage, isSelf = false) {
-    let cell = this.document.createElement("label");
+  _createExecutionCell: function(doc, percentage, isSelf = false) {
+    let cell = doc.createElement("label");
     cell.className = "plain call-tree-cell";
     cell.setAttribute("type", isSelf ? "self-percentage" : "percentage");
     cell.setAttribute("crop", "end");
     cell.setAttribute("value", L10N.numberWithDecimals(percentage, 2) + PERCENTAGE_UNITS);
     return cell;
   },
-  _createAllocationsCell: function(count, isSelf = false) {
-    let cell = this.document.createElement("label");
+  _createAllocationsCell: function(doc, count, isSelf = false) {
+    let cell = doc.createElement("label");
     cell.className = "plain call-tree-cell";
     cell.setAttribute("type", isSelf ? "self-allocations" : "allocations");
     cell.setAttribute("crop", "end");
     cell.setAttribute("value", count || 0);
     return cell;
   },
-  _createSamplesCell: function(count) {
-    let cell = this.document.createElement("label");
+  _createSamplesCell: function(doc, count) {
+    let cell = doc.createElement("label");
     cell.className = "plain call-tree-cell";
     cell.setAttribute("type", "samples");
     cell.setAttribute("crop", "end");
     cell.setAttribute("value", count || "");
     return cell;
   },
-  _createFunctionCell: function(arrowNode, frameName, frameInfo, frameLevel) {
-    let cell = this.document.createElement("hbox");
+  _createFunctionCell: function(doc, arrowNode, frameName, frameInfo, frameLevel) {
+    let cell = doc.createElement("hbox");
     cell.className = "call-tree-cell";
     cell.style.MozMarginStart = (frameLevel * CALL_TREE_INDENTATION) + "px";
     cell.setAttribute("type", "function");
@@ -256,7 +254,7 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
     // Don't render a name label node if there's no function name. A different
     // location label node will be rendered instead.
     if (frameName) {
-      let nameNode = this.document.createElement("label");
+      let nameNode = doc.createElement("label");
       nameNode.className = "plain call-tree-name";
       nameNode.setAttribute("flex", "1");
       nameNode.setAttribute("crop", "end");
@@ -266,7 +264,7 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
 
     // Don't render detailed labels for meta category frames
     if (!frameInfo.isMetaCategory) {
-      this._appendFunctionDetailsCells(cell, frameInfo);
+      this._appendFunctionDetailsCells(doc, cell, frameInfo);
     }
 
     // Don't render an expando-arrow for leaf nodes.
@@ -277,9 +275,9 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
 
     return cell;
   },
-  _appendFunctionDetailsCells: function(cell, frameInfo) {
+  _appendFunctionDetailsCells: function(doc, cell, frameInfo) {
     if (frameInfo.fileName) {
-      let urlNode = this.document.createElement("label");
+      let urlNode = doc.createElement("label");
       urlNode.className = "plain call-tree-url";
       urlNode.setAttribute("flex", "1");
       urlNode.setAttribute("crop", "end");
@@ -290,32 +288,32 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
     }
 
     if (frameInfo.line) {
-      let lineNode = this.document.createElement("label");
+      let lineNode = doc.createElement("label");
       lineNode.className = "plain call-tree-line";
       lineNode.setAttribute("value", ":" + frameInfo.line);
       cell.appendChild(lineNode);
     }
 
     if (frameInfo.column) {
-      let columnNode = this.document.createElement("label");
+      let columnNode = doc.createElement("label");
       columnNode.className = "plain call-tree-column";
       columnNode.setAttribute("value", ":" + frameInfo.column);
       cell.appendChild(columnNode);
     }
 
     if (frameInfo.host) {
-      let hostNode = this.document.createElement("label");
+      let hostNode = doc.createElement("label");
       hostNode.className = "plain call-tree-host";
       hostNode.setAttribute("value", frameInfo.host);
       cell.appendChild(hostNode);
     }
 
-    let spacerNode = this.document.createElement("spacer");
+    let spacerNode = doc.createElement("spacer");
     spacerNode.setAttribute("flex", "10000");
     cell.appendChild(spacerNode);
 
     if (frameInfo.categoryData.label) {
-      let categoryNode = this.document.createElement("label");
+      let categoryNode = doc.createElement("label");
       categoryNode.className = "plain call-tree-category";
       categoryNode.style.color = frameInfo.categoryData.color;
       categoryNode.setAttribute("value", frameInfo.categoryData.label);

From 5e85bb4a0a82cb849c713bbb2bb714fb372f33f0 Mon Sep 17 00:00:00 2001
From: Victor Porof 
Date: Sun, 24 May 2015 14:12:53 -0400
Subject: [PATCH 79/89] Bug 1167962 - Fix import in synthesizeProfileForTest,
 r=orange

---
 browser/devtools/shared/test/head.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/browser/devtools/shared/test/head.js b/browser/devtools/shared/test/head.js
index fa53d136c7d..a23bf3e9f7c 100644
--- a/browser/devtools/shared/test/head.js
+++ b/browser/devtools/shared/test/head.js
@@ -250,7 +250,7 @@ function* openAndCloseToolbox(nbOfTimes, usageTime, toolId) {
  * Synthesize a profile for testing.
  */
 function synthesizeProfileForTest(samples) {
-  const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
+  const RecordingUtils = devtools.require("devtools/performance/recording-utils");
 
   samples.unshift({
     time: 0,

From 28b349c805723af41ad82f5b1ec0efafbb9fc0d0 Mon Sep 17 00:00:00 2001
From: Georg Fritzsche 
Date: Fri, 22 May 2015 22:42:29 +0700
Subject: [PATCH 80/89] Bug 1166705 - Don't send a saved-session ping when
 extended Telemetry is off. r=vladan

This also cleans up the pending pings persistance, putting the control of saving them out of TelemetrySession.
---
 .../telemetry/TelemetryController.jsm         | 43 ------------
 .../components/telemetry/TelemetrySession.jsm | 70 ++++++++++---------
 .../components/telemetry/TelemetryStorage.jsm | 24 ++-----
 3 files changed, 41 insertions(+), 96 deletions(-)

diff --git a/toolkit/components/telemetry/TelemetryController.jsm b/toolkit/components/telemetry/TelemetryController.jsm
index 6bf839ef51d..2a9097200cd 100644
--- a/toolkit/components/telemetry/TelemetryController.jsm
+++ b/toolkit/components/telemetry/TelemetryController.jsm
@@ -235,27 +235,6 @@ this.TelemetryController = Object.freeze({
     return Impl.getCurrentPingData(aSubsession);
   },
 
-  /**
-   * Add the ping to the pending ping list and save all pending pings.
-   *
-   * @param {String} aType The type of the ping.
-   * @param {Object} aPayload The actual data payload for the ping.
-   * @param {Object} [aOptions] Options object.
-   * @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client
-   *                  id, false otherwise.
-   * @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the
-   *                  environment data.
-   * @param {Object}  [aOptions.overrideEnvironment=null] set to override the environment data.
-   * @returns {Promise} A promise that resolves when the pings are saved.
-   */
-  savePendingPings: function(aType, aPayload, aOptions = {}) {
-    let options = aOptions;
-    options.addClientId = aOptions.addClientId || false;
-    options.addEnvironment = aOptions.addEnvironment || false;
-
-    return Impl.savePendingPings(aType, aPayload, options);
-  },
-
   /**
    * Save a ping to disk.
    *
@@ -659,28 +638,6 @@ let Impl = {
     return promise;
   },
 
-  /**
-   * Saves all the pending pings, plus the passed one, to disk.
-   *
-   * @param {String} aType The type of the ping.
-   * @param {Object} aPayload The actual data payload for the ping.
-   * @param {Object} aOptions Options object.
-   * @param {Boolean} aOptions.addClientId true if the ping should contain the client id,
-   *                  false otherwise.
-   * @param {Boolean} aOptions.addEnvironment true if the ping should contain the
-   *                  environment data.
-   * @param {Object}  [aOptions.overrideEnvironment=null] set to override the environment data.
-   *
-   * @returns {Promise} A promise that resolves when all the pings are saved to disk.
-   */
-  savePendingPings: function savePendingPings(aType, aPayload, aOptions) {
-    this._log.trace("savePendingPings - Type " + aType + ", Server " + this._server +
-                    ", aOptions " + JSON.stringify(aOptions));
-
-    let pingData = this.assemblePing(aType, aPayload, aOptions);
-    return TelemetryStorage.savePendingPings(pingData);
-  },
-
   /**
    * Save a ping to disk.
    *
diff --git a/toolkit/components/telemetry/TelemetrySession.jsm b/toolkit/components/telemetry/TelemetrySession.jsm
index 82f20e2702b..ebb1bdfbe45 100644
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -1620,42 +1620,44 @@ let Impl = {
     cpmm.sendAsyncMessage(MESSAGE_TELEMETRY_PAYLOAD, payload);
   },
 
-  /**
-   * Save both the "saved-session" and the "shutdown" pings to disk.
-   */
-  savePendingPings: function savePendingPings() {
-    this._log.trace("savePendingPings");
+   /**
+    * Save both the "saved-session" and the "shutdown" pings to disk.
+    */
+  saveShutdownPings: Task.async(function*() {
+    this._log.trace("saveShutdownPings");
 
-    if (!IS_UNIFIED_TELEMETRY) {
-      return this.savePendingPingsClassic();
+    if (IS_UNIFIED_TELEMETRY) {
+      try {
+        let shutdownPayload = this.getSessionPayload(REASON_SHUTDOWN, false);
+
+        let options = {
+          addClientId: true,
+          addEnvironment: true,
+          overwrite: true,
+        };
+        yield TelemetryController.addPendingPing(getPingType(shutdownPayload), shutdownPayload, options);
+      } catch (ex) {
+        this._log.error("saveShutdownPings - failed to submit shutdown ping", ex);
+      }
+     }
+
+    // As a temporary measure, we want to submit saved-session too if extended Telemetry is enabled
+    // to keep existing performance analysis working.
+    if (Telemetry.canRecordExtended) {
+      try {
+        let payload = this.getSessionPayload(REASON_SAVED_SESSION, false);
+
+        let options = {
+          addClientId: true,
+          addEnvironment: true,
+        };
+        yield TelemetryController.addPendingPing(getPingType(payload), payload, options);
+      } catch (ex) {
+        this._log.error("saveShutdownPings - failed to submit saved-session ping", ex);
+      }
     }
+  }),
 
-    let options = {
-      addClientId: true,
-      addEnvironment: true,
-      overwrite: true,
-    };
-
-    let shutdownPayload = this.getSessionPayload(REASON_SHUTDOWN, false);
-    // Make sure we try to save the pending pings, even though we failed saving the shutdown
-    // ping.
-    return TelemetryController.addPendingPing(getPingType(shutdownPayload), shutdownPayload, options)
-                        .then(() => this.savePendingPingsClassic(),
-                              () => this.savePendingPingsClassic());
-  },
-
-  /**
-   * Save the "saved-session" ping and make TelemetryController save all the pending pings to disk.
-   */
-  savePendingPingsClassic: function savePendingPingsClassic() {
-    this._log.trace("savePendingPingsClassic");
-    let payload = this.getSessionPayload(REASON_SAVED_SESSION, false);
-    let options = {
-      addClientId: true,
-      addEnvironment: true,
-    };
-    return TelemetryController.savePendingPings(getPingType(payload), payload, options);
-  },
 
   testSaveHistograms: function testSaveHistograms(file) {
     this._log.trace("testSaveHistograms - Path: " + file.path);
@@ -1844,7 +1846,7 @@ let Impl = {
 
       if (Telemetry.isOfficialTelemetry || testing) {
         return Task.spawn(function*() {
-          yield this.savePendingPings();
+          yield this.saveShutdownPings();
           yield this._stateSaveSerializer.flushTasks();
 
           if (IS_UNIFIED_TELEMETRY) {
diff --git a/toolkit/components/telemetry/TelemetryStorage.jsm b/toolkit/components/telemetry/TelemetryStorage.jsm
index 8f3f07dbac2..8763ae465df 100644
--- a/toolkit/components/telemetry/TelemetryStorage.jsm
+++ b/toolkit/components/telemetry/TelemetryStorage.jsm
@@ -217,16 +217,6 @@ this.TelemetryStorage = {
     return TelemetryStorageImpl.savePing(ping, overwrite);
   },
 
-  /**
-   * Save all pending pings.
-   *
-   * @param {object} sessionPing The additional session ping.
-   * @returns {promise}
-   */
-  savePendingPings: function(sessionPing) {
-    return TelemetryStorageImpl.savePendingPings(sessionPing);
-  },
-
   /**
    * Add a ping to the saved pings directory so that it gets saved
    * and sent along with other pings.
@@ -460,6 +450,7 @@ let TelemetryStorageImpl = {
   shutdown: Task.async(function*() {
     this._shutdown = true;
     yield this._abortedSessionSerializer.flushTasks();
+    yield this.savePendingPings();
     // If the archive cleaning task is running, block on it. It should bail out as soon
     // as possible.
     yield this._archiveCleanTask;
@@ -758,17 +749,12 @@ let TelemetryStorageImpl = {
   /**
    * Save all pending pings.
    *
-   * @param {object} sessionPing The additional session ping.
    * @returns {promise}
    */
-  savePendingPings: function(sessionPing) {
-    let p = pendingPings.reduce((p, ping) => {
-      // Restore the files with the previous pings if for some reason they have
-      // been deleted, don't overwrite them otherwise.
-      p.push(this.savePing(ping, false));
-      return p;}, [this.savePing(sessionPing, true)]);
-
-    pendingPings = [];
+  savePendingPings: function() {
+    let p = [for (ping of pendingPings) this.savePing(ping, false).catch(ex => {
+      this._log.error("savePendingPings - failed to save pending pings.");
+    })];
     return Promise.all(p);
   },
 

From 7696876034cee18b4a012351642334bd0a44a4b6 Mon Sep 17 00:00:00 2001
From: Nicolas Silva 
Date: Mon, 25 May 2015 18:59:22 +0200
Subject: [PATCH 81/89] Bug 1150549 - Remove debugging code accidentally left
 in the previous patch

---
 gfx/layers/Effects.h | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/gfx/layers/Effects.h b/gfx/layers/Effects.h
index 128b84ada3b..d5cae98a29e 100644
--- a/gfx/layers/Effects.h
+++ b/gfx/layers/Effects.h
@@ -285,11 +285,6 @@ CreateTexturedEffect(TextureSource* aSource,
 {
   MOZ_ASSERT(aSource);
   if (aSourceOnWhite) {
-    if ((aSource->GetFormat() != gfx::SurfaceFormat::R8G8B8X8 &&
-         aSource->GetFormat() != gfx::SurfaceFormat::B8G8R8X8) ||
-        aSource->GetFormat() != aSourceOnWhite->GetFormat()) {
-      printf_stderr("XXXX - source %i - on white %i\n", (int)aSource->GetFormat(), (int)aSourceOnWhite->GetFormat());
-    }
     MOZ_ASSERT(aSource->GetFormat() == gfx::SurfaceFormat::R8G8B8X8 ||
                aSource->GetFormat() == gfx::SurfaceFormat::B8G8R8X8);
     MOZ_ASSERT(aSource->GetFormat() == aSourceOnWhite->GetFormat());

From bbcbe5cc8b337aafacb8a4fe3911cb071bd6bb45 Mon Sep 17 00:00:00 2001
From: Ryan VanderMeulen 
Date: Mon, 25 May 2015 13:15:06 -0400
Subject: [PATCH 82/89] Backed out 4 changesets (bug 1155493) for Android
 test_browserElement_inproc_CopyPaste.html timeouts/crashes.

Backed out changeset 3bd7adb9f591 (bug 1155493)
Backed out changeset 0380b1684e6b (bug 1155493)
Backed out changeset 58b7c1eaf3c8 (bug 1155493)
Backed out changeset 896beb5088a7 (bug 1155493)
---
 b2g/chrome/content/shell.js                   | 28 ------
 dom/browser-element/BrowserElementChild.js    | 13 +--
 .../BrowserElementCopyPaste.js                | 90 ------------------
 dom/browser-element/BrowserElementParent.js   | 35 -------
 .../mochitest/browserElementTestHelpers.js    |  4 -
 .../mochitest/browserElement_CopyPaste.js     | 34 +++----
 dom/ipc/jar.mn                                |  1 -
 dom/ipc/preload.js                            |  1 -
 dom/webidl/CaretStateChangedEvent.webidl      | 32 -------
 dom/webidl/moz.build                          |  1 -
 layout/base/AccessibleCaretManager.cpp        | 92 +------------------
 layout/base/AccessibleCaretManager.h          |  5 -
 12 files changed, 17 insertions(+), 319 deletions(-)
 delete mode 100644 dom/browser-element/BrowserElementCopyPaste.js
 delete mode 100644 dom/webidl/CaretStateChangedEvent.webidl

diff --git a/b2g/chrome/content/shell.js b/b2g/chrome/content/shell.js
index abb0f45fd13..eb2607ddeea 100644
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -350,7 +350,6 @@ var shell = {
     this.contentBrowser.addEventListener('mozbrowserloadstart', this, true);
     this.contentBrowser.addEventListener('mozbrowserselectionstatechanged', this, true);
     this.contentBrowser.addEventListener('mozbrowserscrollviewchange', this, true);
-    this.contentBrowser.addEventListener('mozbrowsercaretstatechanged', this);
 
     CustomEventManager.init();
     WebappsHelper.init();
@@ -381,7 +380,6 @@ var shell = {
     this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true);
     this.contentBrowser.removeEventListener('mozbrowserselectionstatechanged', this, true);
     this.contentBrowser.removeEventListener('mozbrowserscrollviewchange', this, true);
-    this.contentBrowser.removeEventListener('mozbrowsercaretstatechanged', this);
     ppmm.removeMessageListener("content-handler", this);
 
     UserAgentOverrides.uninit();
@@ -492,28 +490,6 @@ var shell = {
           detail: data,
         });
         break;
-      case 'mozbrowsercaretstatechanged':
-        {
-          let elt = evt.target;
-          let win = elt.ownerDocument.defaultView;
-          let offsetX = win.mozInnerScreenX - window.mozInnerScreenX;
-          let offsetY = win.mozInnerScreenY - window.mozInnerScreenY;
-
-          let rect = elt.getBoundingClientRect();
-          offsetX += rect.left;
-          offsetY += rect.top;
-
-          let data = evt.detail;
-          data.offsetX = offsetX;
-          data.offsetY = offsetY;
-          data.sendDoCommandMsg = null;
-
-          shell.sendChromeEvent({
-            type: 'caretstatechanged',
-            detail: data,
-          });
-        }
-        break;
 
       case 'MozApplicationManifest':
         try {
@@ -742,10 +718,6 @@ var CustomEventManager = {
       case 'do-command':
         DoCommandHelper.handleEvent(detail.cmd);
         break;
-      case 'copypaste-do-command':
-        Services.obs.notifyObservers({ wrappedJSObject: shell.contentBrowser },
-                                     'ask-children-to-execute-copypaste-command', detail.cmd);
-        break;
     }
   }
 }
diff --git a/dom/browser-element/BrowserElementChild.js b/dom/browser-element/BrowserElementChild.js
index 653c5de604c..6bf1c5f80b3 100644
--- a/dom/browser-element/BrowserElementChild.js
+++ b/dom/browser-element/BrowserElementChild.js
@@ -34,15 +34,12 @@ function isTopBrowserElement(docShell) {
 }
 
 if (!('BrowserElementIsPreloaded' in this)) {
-  if (isTopBrowserElement(docShell)) {
-    if (Services.prefs.getBoolPref("dom.mozInputMethod.enabled")) {
-      try {
-        Services.scriptloader.loadSubScript("chrome://global/content/forms.js");
-      } catch (e) {
-      }
+  if (isTopBrowserElement(docShell) &&
+      Services.prefs.getBoolPref("dom.mozInputMethod.enabled")) {
+    try {
+      Services.scriptloader.loadSubScript("chrome://global/content/forms.js");
+    } catch (e) {
     }
-
-    Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementCopyPaste.js");
   }
 
   if (Services.prefs.getIntPref("dom.w3c_touch_events.enabled") == 1) {
diff --git a/dom/browser-element/BrowserElementCopyPaste.js b/dom/browser-element/BrowserElementCopyPaste.js
deleted file mode 100644
index bfe4f71bc2f..00000000000
--- a/dom/browser-element/BrowserElementCopyPaste.js
+++ /dev/null
@@ -1,90 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
-/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-dump("###################################### BrowserElementCopyPaste.js loaded\n");
-
-let CopyPasteAssistent = {
-  COMMAND_MAP: {
-    'cut': 'cmd_cut',
-    'copy': 'cmd_copyAndCollapseToEnd',
-    'paste': 'cmd_paste',
-    'selectall': 'cmd_selectAll'
-  },
-
-  init: function() {
-    addEventListener('mozcaretstatechanged',
-                     this._caretStateChangedHandler.bind(this),
-                     /* useCapture = */ true,
-                     /* wantsUntrusted = */ false);
-    addMessageListener('browser-element-api:call', this._browserAPIHandler.bind(this));
-  },
-
-  _browserAPIHandler: function(e) {
-    switch (e.data.msg_name) {
-      case 'copypaste-do-command':
-        if (this._isCommandEnabled(e.data.command)) {
-          docShell.doCommand(COMMAND_MAP[e.data.command]);
-        }
-        break;
-    }
-  },
-
-  _isCommandEnabled: function(cmd) {
-    let command = this.COMMAND_MAP[cmd];
-    if (!command) {
-      return false;
-    }
-
-    return docShell.isCommandEnabled(command);
-  },
-
-  _caretStateChangedHandler: function(e) {
-    e.stopPropagation();
-
-    let boundingClientRect = e.boundingClientRect;
-    let canPaste = this._isCommandEnabled("paste");
-    let zoomFactor = content.innerWidth == 0 ? 1 : content.screen.width / content.innerWidth;
-
-    let detail = {
-      rect: {
-        width: boundingClientRect ? boundingClientRect.width : 0,
-        height: boundingClientRect ? boundingClientRect.height : 0,
-        top: boundingClientRect ? boundingClientRect.top : 0,
-        bottom: boundingClientRect ? boundingClientRect.bottom : 0,
-        left: boundingClientRect ? boundingClientRect.left : 0,
-        right: boundingClientRect ? boundingClientRect.right : 0,
-      },
-      commands: {
-        canSelectAll: this._isCommandEnabled("selectall"),
-        canCut: this._isCommandEnabled("cut"),
-        canCopy: this._isCommandEnabled("copy"),
-        canPaste: this._isCommandEnabled("paste"),
-      },
-      zoomFactor: zoomFactor,
-      reason: e.reason,
-      collapsed: e.collapsed,
-      caretVisible: e.caretVisible,
-      selectionVisible: e.selectionVisible
-    };
-
-    // Get correct geometry information if we have nested iframe.
-    let currentWindow = e.target.defaultView;
-    while (currentWindow.realFrameElement) {
-      let currentRect = currentWindow.realFrameElement.getBoundingClientRect();
-      detail.rect.top += currentRect.top;
-      detail.rect.bottom += currentRect.top;
-      detail.rect.left += currentRect.left;
-      detail.rect.right += currentRect.left;
-      currentWindow = currentWindow.realFrameElement.ownerDocument.defaultView;
-    }
-
-    sendAsyncMsg('caretstatechanged', detail);
-  },
-};
-
-CopyPasteAssistent.init();
diff --git a/dom/browser-element/BrowserElementParent.js b/dom/browser-element/BrowserElementParent.js
index 46b775e3cc8..7d99cd141c7 100644
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.js
@@ -87,7 +87,6 @@ function BrowserElementParent() {
 
   Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
   Services.obs.addObserver(this, 'copypaste-docommand', /* ownsWeak = */ true);
-  Services.obs.addObserver(this, 'ask-children-to-execute-copypaste-command', /* ownsWeak = */ true);
 }
 
 BrowserElementParent.prototype = {
@@ -204,7 +203,6 @@ BrowserElementParent.prototype = {
       "got-set-input-method-active": this._gotDOMRequestResult,
       "selectionstatechanged": this._handleSelectionStateChanged,
       "scrollviewchange": this._handleScrollViewChange,
-      "caretstatechanged": this._handleCaretStateChanged,
     };
 
     let mmSecuritySensitiveCalls = {
@@ -440,34 +438,6 @@ BrowserElementParent.prototype = {
     this._frameElement.dispatchEvent(evt);
   },
 
-  // Called when state of accessible caret in child has changed.
-  // The fields of data is as following:
-  //  - rect: Contains bounding rectangle of selection, Include width, height,
-  //          top, bottom, left and right.
-  //  - commands: Describe what commands can be executed in child. Include canSelectAll,
-  //              canCut, canCopy and canPaste. For example: if we want to check if cut
-  //              command is available, using following code, if (data.commands.canCut) {}.
-  //  - zoomFactor: Current zoom factor in child frame.
-  //  - reason: The reason causes the state changed. Include "visibilitychange",
-  //            "updateposition", "longpressonemptycontent", "taponcaret", "presscaret",
-  //            "releasecaret".
-  //  - collapsed: Indicate current selection is collapsed or not.
-  //  - caretVisible: Indicate the caret visiibility.
-  //  - selectionVisible: Indicate current selection is visible or not.
-  _handleCaretStateChanged: function(data) {
-    let evt = this._createEvent('caretstatechanged', data.json,
-                                /* cancelable = */ false);
-
-    let self = this;
-    function sendDoCommandMsg(cmd) {
-      let data = { command: cmd };
-      self._sendAsyncMsg('copypaste-do-command', data);
-    }
-    Cu.exportFunction(sendDoCommandMsg, evt.detail, { defineAs: 'sendDoCommandMsg' });
-
-    this._frameElement.dispatchEvent(evt);
-  },
-
   _handleScrollViewChange: function(data) {
     let evt = this._createEvent("scrollviewchange", data.json,
                                 /* cancelable = */ false);
@@ -1009,11 +979,6 @@ BrowserElementParent.prototype = {
         this._sendAsyncMsg('do-command', { command: data });
       }
       break;
-    case 'ask-children-to-execute-copypaste-command':
-      if (this._isAlive() && this._frameElement == subject.wrappedJSObject) {
-        this._sendAsyncMsg('copypaste-do-command', { command: data });
-      }
-      break;
     default:
       debug('Unknown topic: ' + topic);
       break;
diff --git a/dom/browser-element/mochitest/browserElementTestHelpers.js b/dom/browser-element/mochitest/browserElementTestHelpers.js
index f557517de47..007b0300ccc 100644
--- a/dom/browser-element/mochitest/browserElementTestHelpers.js
+++ b/dom/browser-element/mochitest/browserElementTestHelpers.js
@@ -65,10 +65,6 @@ const browserElementTestHelpers = {
     this._setPref('selectioncaret.enabled', value);
   },
 
-  setAccessibleCaretEnabledPref: function(value) {
-    this._setPref('layout.accessiblecaret.enabled', value);
-  },
-
   getOOPByDefaultPref: function() {
     return this._getBoolPref("dom.ipc.browser_frames.oop_by_default");
   },
diff --git a/dom/browser-element/mochitest/browserElement_CopyPaste.js b/dom/browser-element/mochitest/browserElement_CopyPaste.js
index 99f18274755..762b792507c 100644
--- a/dom/browser-element/mochitest/browserElement_CopyPaste.js
+++ b/dom/browser-element/mochitest/browserElement_CopyPaste.js
@@ -21,7 +21,6 @@ var defaultData;
 var pasteData;
 var focusScript;
 var createEmbededFrame = false;
-var testAccessibleCaret = false;
 
 function copyToClipboard(str) {
   gTextarea.value = str;
@@ -161,24 +160,16 @@ function dispatchTest(e) {
       break;
     default:
       if (createEmbededFrame || browserElementTestHelpers.getOOPByDefaultPref()) {
-        if (testAccessibleCaret) {
-          SimpleTest.finish();
-          return;
-        } else {
-          testAccessibleCaret = true;
-          createEmbededFrame = false;
-          browserElementTestHelpers.setSelectionChangeEnabledPref(false);
-          browserElementTestHelpers.setAccessibleCaretEnabledPref(true);
-        }
+        SimpleTest.finish();
       } else {
         createEmbededFrame = true;
-      }
 
-      // clean up and run test again.
-      document.body.removeChild(iframeOuter);
-      document.body.removeChild(gTextarea);
-      state = 0;
-      runTest();
+        // clean up and run test again.
+        document.body.removeChild(iframeOuter);
+        document.body.removeChild(gTextarea);
+        state = 0;
+        runTest();
+      }
       break;
   }
 }
@@ -192,17 +183,14 @@ function isChildProcess() {
 function testSelectAll(e) {
   // Skip mozbrowser test if we're at child process.
   if (!isChildProcess()) {
-    let eventName = testAccessibleCaret ? "mozbrowsercaretstatechanged" : "mozbrowserselectionstatechanged";
-    iframeOuter.addEventListener(eventName, function selectchangeforselectall(e) {
-      if (!e.detail.states || e.detail.states.indexOf('selectall') == 0) {
-        iframeOuter.removeEventListener(eventName, selectchangeforselectall, true);
+    iframeOuter.addEventListener("mozbrowserselectionstatechanged", function selectchangeforselectall(e) {
+      if (e.detail.states.indexOf('selectall') == 0) {
+        iframeOuter.removeEventListener("mozbrowserselectionstatechanged", selectchangeforselectall, true);
         ok(true, "got mozbrowserselectionstatechanged event." + stateMeaning);
         ok(e.detail, "event.detail is not null." + stateMeaning);
         ok(e.detail.width != 0, "event.detail.width is not zero" + stateMeaning);
         ok(e.detail.height != 0, "event.detail.height is not zero" + stateMeaning);
-        if (!testAccessibleCaret) {
-          ok(e.detail.states, "event.detail.state " + e.detail.states);
-        }
+        ok(e.detail.states, "event.detail.state " + e.detail.states);
         SimpleTest.executeSoon(function() { testCopy1(e); });
       }
     }, true);
diff --git a/dom/ipc/jar.mn b/dom/ipc/jar.mn
index 57395c39c22..b3f1bc88e0e 100644
--- a/dom/ipc/jar.mn
+++ b/dom/ipc/jar.mn
@@ -7,7 +7,6 @@ toolkit.jar:
         content/global/remote-test-ipc.js (remote-test.js)
         content/global/BrowserElementChild.js (../browser-element/BrowserElementChild.js)
         content/global/BrowserElementChildPreload.js (../browser-element/BrowserElementChildPreload.js)
-        content/global/BrowserElementCopyPaste.js (../browser-element/BrowserElementCopyPaste.js)
         content/global/BrowserElementPanning.js (../browser-element/BrowserElementPanning.js)
 *       content/global/BrowserElementPanningAPZDisabled.js (../browser-element/BrowserElementPanningAPZDisabled.js)
         content/global/manifestMessages.js (manifestMessages.js)
diff --git a/dom/ipc/preload.js b/dom/ipc/preload.js
index 165933e924c..7e613cffd7f 100644
--- a/dom/ipc/preload.js
+++ b/dom/ipc/preload.js
@@ -103,7 +103,6 @@ const BrowserElementIsPreloaded = true;
     Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanning.js", global);
   }
 
-  Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementCopyPaste.js", global);
   Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementChildPreload.js", global);
 
   Services.io.getProtocolHandler("app");
diff --git a/dom/webidl/CaretStateChangedEvent.webidl b/dom/webidl/CaretStateChangedEvent.webidl
deleted file mode 100644
index 45a4e38d301..00000000000
--- a/dom/webidl/CaretStateChangedEvent.webidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-enum CaretChangedReason {
-  "visibilitychange",
-  "updateposition",
-  "longpressonemptycontent",
-  "taponcaret",
-  "presscaret",
-  "releasecaret"
-};
-
-dictionary CaretStateChangedEventInit : EventInit {
-  boolean collapsed = true;
-  DOMRectReadOnly? boundingClientRect = null;
-  CaretChangedReason reason = "visibilitychange";
-  boolean caretVisible = false;
-  boolean selectionVisible = false;
-};
-
-[Constructor(DOMString type, optional CaretStateChangedEventInit eventInit),
- ChromeOnly]
-interface CaretStateChangedEvent : Event {
-  readonly attribute boolean collapsed;
-  readonly attribute DOMRectReadOnly? boundingClientRect;
-  readonly attribute CaretChangedReason reason;
-  readonly attribute boolean caretVisible;
-  readonly attribute boolean selectionVisible;
-};
diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build
index 780e95eed27..a53e50c862c 100644
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -725,7 +725,6 @@ GENERATED_EVENTS_WEBIDL_FILES = [
     'CameraConfigurationEvent.webidl',
     'CameraFacesDetectedEvent.webidl',
     'CameraStateChangeEvent.webidl',
-    'CaretStateChangedEvent.webidl',
     'CFStateChangeEvent.webidl',
     'CloseEvent.webidl',
     'CSSFontFaceLoadEvent.webidl',
diff --git a/layout/base/AccessibleCaretManager.cpp b/layout/base/AccessibleCaretManager.cpp
index 00c3f661b40..9b818f74587 100644
--- a/layout/base/AccessibleCaretManager.cpp
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -90,7 +90,6 @@ AccessibleCaretManager::HideCarets()
     AC_LOG("%s", __FUNCTION__);
     mFirstCaret->SetAppearance(Appearance::None);
     mSecondCaret->SetAppearance(Appearance::None);
-    DispatchCaretStateChangedEvent(CaretChangedReason::Visibilitychange);
     CancelCaretTimeoutTimer();
   }
 }
@@ -155,8 +154,7 @@ AccessibleCaretManager::UpdateCaretsForCursorMode()
   // No need to consider whether the caret's position is out of scrollport.
   // According to the spec, we need to explicitly hide it after the scrolling is
   // ended.
-  bool oldSecondCaretVisible = mSecondCaret->IsLogicallyVisible();
-  PositionChangedResult caretResult = mFirstCaret->SetPosition(frame, offset);
+  mFirstCaret->SetPosition(frame, offset);
   mFirstCaret->SetSelectionBarEnabled(false);
   if (nsContentUtils::HasNonEmptyTextContent(
         editingHost, nsContentUtils::eRecurseIntoChildren)) {
@@ -166,11 +164,6 @@ AccessibleCaretManager::UpdateCaretsForCursorMode()
     mFirstCaret->SetAppearance(Appearance::NormalNotShown);
   }
   mSecondCaret->SetAppearance(Appearance::None);
-
-  if ((caretResult == PositionChangedResult::Changed ||
-      oldSecondCaretVisible) && !mActiveCaret) {
-    DispatchCaretStateChangedEvent(CaretChangedReason::Updateposition);
-  }
 }
 
 void
@@ -221,14 +214,6 @@ AccessibleCaretManager::UpdateCaretsForSelectionMode()
   }
 
   UpdateCaretsForTilt();
-
-  if ((firstCaretResult == PositionChangedResult::Changed ||
-       secondCaretResult == PositionChangedResult::Changed ||
-       firstCaretResult == PositionChangedResult::Invisible ||
-       secondCaretResult == PositionChangedResult::Invisible) &&
-      !mActiveCaret) {
-    DispatchCaretStateChangedEvent(CaretChangedReason::Updateposition);
-  }
 }
 
 void
@@ -268,7 +253,6 @@ AccessibleCaretManager::PressCaret(const nsPoint& aPoint)
     mOffsetYToCaretLogicalPosition =
       mActiveCaret->LogicalPosition().y - aPoint.y;
     SetSelectionDragState(true);
-    DispatchCaretStateChangedEvent(CaretChangedReason::Presscaret);
     CancelCaretTimeoutTimer();
     rv = NS_OK;
   }
@@ -295,7 +279,6 @@ AccessibleCaretManager::ReleaseCaret()
 
   mActiveCaret = nullptr;
   SetSelectionDragState(false);
-  DispatchCaretStateChangedEvent(CaretChangedReason::Releasecaret);
   LaunchCaretTimeoutTimer();
   return NS_OK;
 }
@@ -308,7 +291,6 @@ AccessibleCaretManager::TapCaret(const nsPoint& aPoint)
   nsresult rv = NS_ERROR_FAILURE;
 
   if (GetCaretMode() == CaretMode::Cursor) {
-    DispatchCaretStateChangedEvent(CaretChangedReason::Taponcaret);
     rv = NS_OK;
   }
 
@@ -349,7 +331,6 @@ AccessibleCaretManager::SelectWordOrShortcut(const nsPoint& aPoint)
                          editingHost, nsContentUtils::eRecurseIntoChildren))) {
     // Content is empty. No need to select word.
     AC_LOG("%s, Cannot select word bacause content is empty", __FUNCTION__);
-    DispatchCaretStateChangedEvent(CaretChangedReason::Longpressonemptycontent);
     return NS_OK;
   }
 
@@ -885,75 +866,4 @@ AccessibleCaretManager::CancelCaretTimeoutTimer()
   }
 }
 
-void
-AccessibleCaretManager::DispatchCaretStateChangedEvent(CaretChangedReason aReason) const
-{
-  MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
-  // Holding PresShell to prevent AccessibleCaretManager to be destroyed.
-  nsCOMPtr presShell = mPresShell;
-  // XXX: Do we need to flush layout?
-  presShell->FlushPendingNotifications(Flush_Layout);
-  if (presShell->IsDestroying()) {
-    return;
-  }
-
-  Selection* sel = GetSelection();
-  if (!sel) {
-    return;
-  }
-
-  nsIDocument* doc = mPresShell->GetDocument();
-  MOZ_ASSERT(doc);
-
-  CaretStateChangedEventInit init;
-  init.mBubbles = true;
-
-  const nsRange* range = sel->GetAnchorFocusRange();
-  nsINode* commonAncestorNode = nullptr;
-  if (range) {
-    commonAncestorNode = range->GetCommonAncestor();
-  }
-
-  if (!commonAncestorNode) {
-    commonAncestorNode = sel->GetFrameSelection()->GetAncestorLimiter();
-  }
-
-  nsRefPtr domRect = new DOMRect(ToSupports(doc));
-  nsRect rect = nsContentUtils::GetSelectionBoundingRect(sel);
-
-  nsIFrame* commonAncestorFrame = nullptr;
-  nsIFrame* rootFrame = mPresShell->GetRootFrame();
-
-  if (commonAncestorNode && commonAncestorNode->IsContent()) {
-    commonAncestorFrame = commonAncestorNode->AsContent()->GetPrimaryFrame();
-  }
-
-  if (commonAncestorFrame && rootFrame) {
-    nsLayoutUtils::TransformRect(rootFrame, commonAncestorFrame, rect);
-    nsRect clampedRect = nsLayoutUtils::ClampRectToScrollFrames(commonAncestorFrame,
-                                                                rect);
-    nsLayoutUtils::TransformRect(commonAncestorFrame, rootFrame, clampedRect);
-    domRect->SetLayoutRect(clampedRect);
-    init.mSelectionVisible = !clampedRect.IsEmpty();
-    init.mBoundingClientRect = domRect;
-  } else {
-    domRect->SetLayoutRect(rect);
-    init.mSelectionVisible = true;
-  }
-
-  init.mBoundingClientRect = domRect;
-  init.mReason = aReason;
-  init.mCollapsed = sel->IsCollapsed();
-  init.mCaretVisible = mFirstCaret->IsLogicallyVisible() ||
-                       mSecondCaret->IsLogicallyVisible();
-
-  nsRefPtr event =
-    CaretStateChangedEvent::Constructor(doc, NS_LITERAL_STRING("mozcaretstatechanged"), init);
-
-  event->SetTrusted(true);
-  event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
-  bool ret;
-  doc->DispatchEvent(event, &ret);
-}
-
 } // namespace mozilla
diff --git a/layout/base/AccessibleCaretManager.h b/layout/base/AccessibleCaretManager.h
index a03c6c48925..102788f3f8d 100644
--- a/layout/base/AccessibleCaretManager.h
+++ b/layout/base/AccessibleCaretManager.h
@@ -13,7 +13,6 @@
 #include "nsISelectionListener.h"
 #include "nsRefPtr.h"
 #include "nsWeakReference.h"
-#include "mozilla/dom/CaretStateChangedEvent.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
@@ -133,10 +132,6 @@ protected:
   already_AddRefed GetFrameSelection() const;
   nsIContent* GetFocusedContent() const;
 
-  // This function will call FlushPendingNotifications. So caller must ensure
-  // everything exists after calling this method.
-  void DispatchCaretStateChangedEvent(dom::CaretChangedReason aReason) const;
-
   // If we're dragging the first caret, we do not want to drag it over the
   // previous character of the second caret. Same as the second caret. So we
   // check if content offset exceeds the previous/next character of second/first

From 6c7ae855325568901c4e12a4f7408098bc69d329 Mon Sep 17 00:00:00 2001
From: Tom Schuster 
Date: Mon, 25 May 2015 19:31:46 +0200
Subject: [PATCH 83/89] Bug 1166950 - Introduce a new FunctionKind for
 class-constructors. r=efaust

---
 js/src/jsfriendapi.h |  2 +-
 js/src/jsfun.h       | 24 +++++++++++-------------
 2 files changed, 12 insertions(+), 14 deletions(-)

diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h
index a930c339d63..1e4cf89c9a6 100644
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2442,7 +2442,7 @@ FunctionObjectToShadowFunction(JSObject* fun)
 }
 
 /* Statically asserted in jsfun.h. */
-static const unsigned JS_FUNCTION_INTERPRETED_BITS = 0x401;
+static const unsigned JS_FUNCTION_INTERPRETED_BITS = 0x0201;
 
 // Return whether the given function object is native.
 static MOZ_ALWAYS_INLINE bool
diff --git a/js/src/jsfun.h b/js/src/jsfun.h
index 7f026948f44..0b228cb96d3 100644
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -32,6 +32,7 @@ class JSFunction : public js::NativeObject
         NormalFunction = 0,
         Arrow,                      /* ES6 '(args) => body' syntax */
         Method,                     /* ES6 MethodDefinition */
+        ClassConstructor,
         Getter,
         Setter,
         AsmJS,                      /* function is an asm.js module or exported function */
@@ -52,18 +53,18 @@ class JSFunction : public js::NativeObject
                                        function-statement) */
         SELF_HOSTED      = 0x0080,  /* function is self-hosted builtin and must not be
                                        decompilable nor constructible. */
-        // Free bit
-        HAS_REST         = 0x0200,  /* function has a rest (...) parameter */
-        INTERPRETED_LAZY = 0x0400,  /* function is interpreted but doesn't have a script yet */
-        RESOLVED_LENGTH  = 0x0800,  /* f.length has been resolved (see fun_resolve). */
-        RESOLVED_NAME    = 0x1000,  /* f.name has been resolved (see fun_resolve). */
+        HAS_REST         = 0x0100,  /* function has a rest (...) parameter */
+        INTERPRETED_LAZY = 0x0200,  /* function is interpreted but doesn't have a script yet */
+        RESOLVED_LENGTH  = 0x0400,  /* f.length has been resolved (see fun_resolve). */
+        RESOLVED_NAME    = 0x0800,  /* f.name has been resolved (see fun_resolve). */
 
-        FUNCTION_KIND_SHIFT = 13,
-        FUNCTION_KIND_MASK  = 0x7 << FUNCTION_KIND_SHIFT,
+        FUNCTION_KIND_SHIFT = 12,
+        FUNCTION_KIND_MASK  = 0xf << FUNCTION_KIND_SHIFT,
 
         ASMJS_KIND = AsmJS << FUNCTION_KIND_SHIFT,
         ARROW_KIND = Arrow << FUNCTION_KIND_SHIFT,
         METHOD_KIND = Method << FUNCTION_KIND_SHIFT,
+        CLASSCONSTRUCTOR_KIND = ClassConstructor << FUNCTION_KIND_SHIFT,
         GETTER_KIND = Getter << FUNCTION_KIND_SHIFT,
         SETTER_KIND = Setter << FUNCTION_KIND_SHIFT,
 
@@ -73,7 +74,7 @@ class JSFunction : public js::NativeObject
         ASMJS_CTOR = ASMJS_KIND | NATIVE_CTOR,
         ASMJS_LAMBDA_CTOR = ASMJS_KIND | NATIVE_CTOR | LAMBDA,
         INTERPRETED_METHOD = INTERPRETED | METHOD_KIND,
-        INTERPRETED_CLASS_CONSTRUCTOR = INTERPRETED | METHOD_KIND | CONSTRUCTOR,
+        INTERPRETED_CLASS_CONSTRUCTOR = INTERPRETED | CLASSCONSTRUCTOR_KIND | CONSTRUCTOR,
         INTERPRETED_GETTER = INTERPRETED | GETTER_KIND,
         INTERPRETED_SETTER = INTERPRETED | SETTER_KIND,
         INTERPRETED_LAMBDA = INTERPRETED | LAMBDA | CONSTRUCTOR,
@@ -165,15 +166,12 @@ class JSFunction : public js::NativeObject
     // Arrow functions store their lexical |this| in the first extended slot.
     bool isArrow()                  const { return kind() == Arrow; }
     // Every class-constructor is also a method.
-    bool isMethod()                 const { return kind() == Method; }
+    bool isMethod()                 const { return kind() == Method || kind() == ClassConstructor; }
+    bool isClassConstructor()       const { return kind() == ClassConstructor; }
 
     bool isGetter()                 const { return kind() == Getter; }
     bool isSetter()                 const { return kind() == Setter; }
 
-    bool isClassConstructor() const {
-        return kind() == Method && isConstructor();
-    }
-
     bool allowSuperProperty() const {
         return isMethod() || isGetter() || isSetter();
     }

From b0ceda53f3c3d4e8c215c27952e21599e1d3a268 Mon Sep 17 00:00:00 2001
From: Tom Schuster 
Date: Mon, 25 May 2015 19:31:46 +0200
Subject: [PATCH 84/89] Bug 1166950 - Make generator methods constructors.
 r=efaust

---
 js/src/frontend/Parser.cpp               | 13 +++++++++----
 js/src/frontend/Parser.h                 |  3 ++-
 js/src/jsfun.h                           |  1 +
 js/src/tests/ecma_6/Class/methDefnGen.js |  9 +++++++++
 4 files changed, 21 insertions(+), 5 deletions(-)

diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp
index 8d520af769a..1c80fe4aaf7 100644
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -1210,7 +1210,8 @@ struct BindData
 
 template 
 JSFunction*
-Parser::newFunction(HandleAtom atom, FunctionSyntaxKind kind, HandleObject proto)
+Parser::newFunction(HandleAtom atom, FunctionSyntaxKind kind,
+                                  GeneratorKind generatorKind, HandleObject proto)
 {
     MOZ_ASSERT_IF(kind == Statement, atom != nullptr);
 
@@ -1227,7 +1228,11 @@ Parser::newFunction(HandleAtom atom, FunctionSyntaxKind kind, Hand
         allocKind = gc::AllocKind::FUNCTION_EXTENDED;
         break;
       case Method:
-        flags = JSFunction::INTERPRETED_METHOD;
+        MOZ_ASSERT(generatorKind == NotGenerator || generatorKind == StarGenerator);
+        if (generatorKind == NotGenerator)
+            flags = JSFunction::INTERPRETED_METHOD;
+        else
+            flags = JSFunction::INTERPRETED_METHOD_GENERATOR;
         allocKind = gc::AllocKind::FUNCTION_EXTENDED;
         break;
       case ClassConstructor:
@@ -2193,7 +2198,7 @@ Parser::functionDef(InHandling inHandling, YieldHandling yieldHand
         if (!proto)
             return null();
     }
-    RootedFunction fun(context, newFunction(funName, kind, proto));
+    RootedFunction fun(context, newFunction(funName, kind, generatorKind, proto));
     if (!fun)
         return null();
 
@@ -7340,7 +7345,7 @@ Parser::generatorComprehensionLambda(GeneratorKind comprehensionKi
             return null();
     }
 
-    RootedFunction fun(context, newFunction(/* atom = */ nullptr, Expression, proto));
+    RootedFunction fun(context, newFunction(/* atom = */ nullptr, Expression, comprehensionKind, proto));
     if (!fun)
         return null();
 
diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h
index 85eff296624..f864be99c85 100644
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -445,7 +445,8 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
      * Create a new function object given a name (which is optional if this is
      * a function expression).
      */
-    JSFunction* newFunction(HandleAtom atom, FunctionSyntaxKind kind, HandleObject proto);
+    JSFunction* newFunction(HandleAtom atom, FunctionSyntaxKind kind, GeneratorKind generatorKind,
+                            HandleObject proto);
 
     void trace(JSTracer* trc);
 
diff --git a/js/src/jsfun.h b/js/src/jsfun.h
index 0b228cb96d3..c583267fcd9 100644
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -74,6 +74,7 @@ class JSFunction : public js::NativeObject
         ASMJS_CTOR = ASMJS_KIND | NATIVE_CTOR,
         ASMJS_LAMBDA_CTOR = ASMJS_KIND | NATIVE_CTOR | LAMBDA,
         INTERPRETED_METHOD = INTERPRETED | METHOD_KIND,
+        INTERPRETED_METHOD_GENERATOR = INTERPRETED | METHOD_KIND | CONSTRUCTOR,
         INTERPRETED_CLASS_CONSTRUCTOR = INTERPRETED | CLASSCONSTRUCTOR_KIND | CONSTRUCTOR,
         INTERPRETED_GETTER = INTERPRETED | GETTER_KIND,
         INTERPRETED_SETTER = INTERPRETED | SETTER_KIND,
diff --git a/js/src/tests/ecma_6/Class/methDefnGen.js b/js/src/tests/ecma_6/Class/methDefnGen.js
index 30f96db03f6..596d55477f5 100644
--- a/js/src/tests/ecma_6/Class/methDefnGen.js
+++ b/js/src/tests/ecma_6/Class/methDefnGen.js
@@ -72,4 +72,13 @@ assertEq(a.b(1).next().value, 1);
 a = {*["b"](c){"use strict";return c;}};
 assertEq(a.b(1).next().value, 1);
 
+// Constructing
+a = {*g() { yield 1; }}
+it = new a.g;
+next = it.next();
+assertEq(next.done, false);
+assertEq(next.value, 1);
+next = it.next();
+assertEq(next.done, true);
+
 reportCompare(0, 0, "ok");

From edf9a1b4cc1de910e93ac354aa754eef1bf26870 Mon Sep 17 00:00:00 2001
From: Tom Schuster 
Date: Mon, 25 May 2015 19:31:46 +0200
Subject: [PATCH 85/89] Bug 1166950 - Only give constructor functions a
 prototype. r=efaust

---
 js/src/jsfun.cpp                              | 13 +++---
 js/src/tests/ecma_6/Class/methDefn.js         |  4 ++
 js/src/tests/ecma_6/Class/methDefnGen.js      |  3 ++
 js/src/tests/ecma_6/Class/methodsPrototype.js | 45 +++++++++++++++++++
 4 files changed, 60 insertions(+), 5 deletions(-)
 create mode 100644 js/src/tests/ecma_6/Class/methodsPrototype.js

diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp
index 27201a70275..9cfe32c87f2 100644
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -443,16 +443,19 @@ fun_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp)
          * or (Object.prototype, Function.prototype, etc.) have that property
          * created eagerly.
          *
-         * ES5 15.3.4: the non-native function object named Function.prototype
-         * does not have a .prototype property.
-         *
          * ES5 15.3.4.5: bound functions don't have a prototype property. The
          * isBuiltin() test covers this case because bound functions are native
          * (and thus built-in) functions by definition/construction.
          *
-         * ES6 19.2.4.3: arrow functions also don't have a prototype property.
+         * In ES6 9.2.8 MakeConstructor the .prototype property is only assigned
+         * to constructors.
+         *
+         * Thus all of the following don't get a .prototype property:
+         * - Methods (that are not class-constructors or generators)
+         * - Arrow functions
+         * - Function.prototype
          */
-        if (fun->isBuiltin() || fun->isArrow() || fun->isFunctionPrototype())
+        if (fun->isBuiltin() || !fun->isConstructor())
             return true;
 
         if (!ResolveInterpretedFunctionPrototype(cx, fun))
diff --git a/js/src/tests/ecma_6/Class/methDefn.js b/js/src/tests/ecma_6/Class/methDefn.js
index 50a09280a14..bbce2f2789f 100644
--- a/js/src/tests/ecma_6/Class/methDefn.js
+++ b/js/src/tests/ecma_6/Class/methDefn.js
@@ -118,6 +118,10 @@ assertEq(b.enumerable, true);
 assertEq(b.writable, true);
 assertEq(b.value(), 4);
 
+// prototype property
+assertEq(a.b.prototype, undefined);
+assertEq(a.b.hasOwnProperty("prototype"), false);
+
 // Defining several methods using eval.
 var code = "({";
 for (i = 0; i < 1000; i++)
diff --git a/js/src/tests/ecma_6/Class/methDefnGen.js b/js/src/tests/ecma_6/Class/methDefnGen.js
index 596d55477f5..8afd94198a8 100644
--- a/js/src/tests/ecma_6/Class/methDefnGen.js
+++ b/js/src/tests/ecma_6/Class/methDefnGen.js
@@ -66,6 +66,9 @@ assertEq(next.done, true);
 assertEq(next.value.hello, 2);
 assertEq(next.value.world, 3);
 
+// prototype property
+assertEq(b.g.hasOwnProperty("prototype"), true);
+
 // Strict mode
 a = {*b(c){"use strict";yield c;}};
 assertEq(a.b(1).next().value, 1);
diff --git a/js/src/tests/ecma_6/Class/methodsPrototype.js b/js/src/tests/ecma_6/Class/methodsPrototype.js
new file mode 100644
index 00000000000..65d5f465d77
--- /dev/null
+++ b/js/src/tests/ecma_6/Class/methodsPrototype.js
@@ -0,0 +1,45 @@
+var test = `
+class TestClass {
+    constructor() { }
+    method() { }
+    get getter() { }
+    set setter(x) { }
+    *generator() { }
+    static staticMethod() { }
+    static get staticGetter() { }
+    static set staticSetter(x) { }
+    static *staticGenerator() { }
+}
+
+var test = new TestClass();
+
+var hasPrototype = [
+    test.constructor,
+    test.generator,
+    TestClass.staticGenerator
+]
+
+for (var fun of hasPrototype) {
+    assertEq(fun.hasOwnProperty('prototype'), true);
+}
+
+var hasNoPrototype = [
+    test.method,
+    Object.getOwnPropertyDescriptor(test.__proto__, 'getter').get,
+    Object.getOwnPropertyDescriptor(test.__proto__, 'setter').set,
+    TestClass.staticMethod,
+    Object.getOwnPropertyDescriptor(TestClass, 'staticGetter').get,
+    Object.getOwnPropertyDescriptor(TestClass, 'staticSetter').set,
+]
+
+for (var fun of hasNoPrototype) {
+    assertEq(fun.hasOwnProperty('prototype'), false);
+}
+
+`;
+
+if (classesEnabled())
+    eval(test);
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0, "OK");

From 5d21775961c90567029d0087f6bc8beb37ea65c7 Mon Sep 17 00:00:00 2001
From: Ryan VanderMeulen 
Date: Mon, 25 May 2015 14:05:05 -0400
Subject: [PATCH 86/89] Backed out changesets 4b6aa5c0a1bf and fdf38a41d92b
 (bug 1150549) for Mulet crashes. CLOSED TREE

---
 gfx/layers/Effects.h                      |   3 +-
 gfx/layers/TiledLayerBuffer.h             |  81 +--
 gfx/layers/client/TiledContentClient.cpp  |  29 +-
 gfx/layers/client/TiledContentClient.h    |  12 +-
 gfx/layers/composite/TextureHost.h        |   1 -
 gfx/layers/composite/TiledContentHost.cpp | 604 ++++++++++++----------
 gfx/layers/composite/TiledContentHost.h   |  71 +--
 gfx/layers/ipc/LayersMessages.ipdlh       |   3 -
 8 files changed, 400 insertions(+), 404 deletions(-)

diff --git a/gfx/layers/Effects.h b/gfx/layers/Effects.h
index d5cae98a29e..f32098d9a3d 100644
--- a/gfx/layers/Effects.h
+++ b/gfx/layers/Effects.h
@@ -286,8 +286,7 @@ CreateTexturedEffect(TextureSource* aSource,
   MOZ_ASSERT(aSource);
   if (aSourceOnWhite) {
     MOZ_ASSERT(aSource->GetFormat() == gfx::SurfaceFormat::R8G8B8X8 ||
-               aSource->GetFormat() == gfx::SurfaceFormat::B8G8R8X8);
-    MOZ_ASSERT(aSource->GetFormat() == aSourceOnWhite->GetFormat());
+               aSourceOnWhite->GetFormat() == gfx::SurfaceFormat::B8G8R8X8);
     return MakeAndAddRef(aSource, aSourceOnWhite, aFilter);
   }
 
diff --git a/gfx/layers/TiledLayerBuffer.h b/gfx/layers/TiledLayerBuffer.h
index c38a4bffcd9..717676b92ef 100644
--- a/gfx/layers/TiledLayerBuffer.h
+++ b/gfx/layers/TiledLayerBuffer.h
@@ -94,9 +94,7 @@ class TiledLayerBuffer
 {
 public:
   TiledLayerBuffer()
-    : mFirstTileX(0)
-    , mFirstTileY(0)
-    , mRetainedWidth(0)
+    : mRetainedWidth(0)
     , mRetainedHeight(0)
     , mResolution(1)
     , mTileSize(gfxPlatform::GetPlatform()->GetTileWidth(), gfxPlatform::GetPlatform()->GetTileHeight())
@@ -110,21 +108,13 @@ public:
   //       (aTileOrigin.x, aTileOrigin.y,
   //        GetScaledTileSize().width, GetScaledTileSize().height)
   //       and GetValidRegion() to get the area of the tile that is valid.
-  Tile& GetTile(const gfx::IntPoint& aTileOrigin);
+  Tile GetTile(const nsIntPoint& aTileOrigin) const;
+
   // Given a tile x, y relative to the top left of the layer, this function
   // will return the tile for
   // (x*GetScaledTileSize().width, y*GetScaledTileSize().height,
   //  GetScaledTileSize().width, GetScaledTileSize().height)
-  Tile& GetTile(int x, int y);
-
-  int TileIndex(const gfx::IntPoint& aTileOrigin) const;
-  int TileIndex(int x, int y) const { return x * mRetainedHeight + y; }
-
-  bool HasTile(int index) const { return index >= 0 && index < (int)mRetainedTiles.Length(); }
-  bool HasTile(const gfx::IntPoint& aTileOrigin) const;
-  bool HasTile(int x, int y) const {
-    return x >= 0 && x < mRetainedWidth && y >= 0 && y < mRetainedHeight;
-  }
+  Tile GetTile(int x, int y) const;
 
   const gfx::IntSize& GetTileSize() const { return mTileSize; }
 
@@ -165,6 +155,14 @@ public:
   // individual tile's rect in relation to the valid region.
   // Setting the resolution will invalidate the buffer.
   float GetResolution() const { return mResolution; }
+  void SetResolution(float aResolution) {
+    if (mResolution == aResolution) {
+      return;
+    }
+
+    Update(nsIntRegion(), nsIntRegion());
+    mResolution = aResolution;
+  }
   bool IsLowPrecision() const { return mResolution < 1; }
 
   typedef Tile* Iterator;
@@ -180,10 +178,6 @@ protected:
   // to the implementor.
   void Update(const nsIntRegion& aNewValidRegion, const nsIntRegion& aPaintRegion);
 
-  // Return a reference to this tile in GetTile when the requested tile offset
-  // does not exist.
-  Tile mPlaceHolderTile;
-
   nsIntRegion     mValidRegion;
   nsIntRegion     mPaintedRegion;
 
@@ -196,8 +190,6 @@ protected:
    * tiles is scaled by mResolution.
    */
   nsTArray  mRetainedTiles;
-  int             mFirstTileX;
-  int             mFirstTileY;
   int             mRetainedWidth;  // in tiles
   int             mRetainedHeight; // in tiles
   float           mResolution;
@@ -257,39 +249,24 @@ static inline int floor_div(int a, int b)
   }
 }
 
-template bool
-TiledLayerBuffer::HasTile(const gfx::IntPoint& aTileOrigin) const {
-  gfx::IntSize scaledTileSize = GetScaledTileSize();
-  return HasTile(floor_div(aTileOrigin.x, scaledTileSize.width) - mFirstTileX,
-                 floor_div(aTileOrigin.y, scaledTileSize.height) - mFirstTileY);
-}
-
-template Tile&
-TiledLayerBuffer::GetTile(const nsIntPoint& aTileOrigin)
-{
-  if (HasTile(aTileOrigin)) {
-    return mRetainedTiles[TileIndex(aTileOrigin)];
-  }
-  return mPlaceHolderTile;
-}
-
-template int
-TiledLayerBuffer::TileIndex(const gfx::IntPoint& aTileOrigin) const
+template Tile
+TiledLayerBuffer::GetTile(const nsIntPoint& aTileOrigin) const
 {
+  // TODO Cache firstTileOriginX/firstTileOriginY
   // Find the tile x/y of the first tile and the target tile relative to the (0, 0)
   // origin, the difference is the tile x/y relative to the start of the tile buffer.
   gfx::IntSize scaledTileSize = GetScaledTileSize();
-  return TileIndex(floor_div(aTileOrigin.x, scaledTileSize.width) - mFirstTileX,
-                   floor_div(aTileOrigin.y, scaledTileSize.height) - mFirstTileY);
+  int firstTileX = floor_div(mValidRegion.GetBounds().x, scaledTileSize.width);
+  int firstTileY = floor_div(mValidRegion.GetBounds().y, scaledTileSize.height);
+  return GetTile(floor_div(aTileOrigin.x, scaledTileSize.width) - firstTileX,
+                 floor_div(aTileOrigin.y, scaledTileSize.height) - firstTileY);
 }
 
-template Tile&
-TiledLayerBuffer::GetTile(int x, int y)
+template Tile
+TiledLayerBuffer::GetTile(int x, int y) const
 {
-  if (HasTile(x, y)) {
-    return mRetainedTiles[TileIndex(x, y)];
-  }
-  return mPlaceHolderTile;
+  int index = x * mRetainedHeight + y;
+  return mRetainedTiles.SafeElementAt(index, AsDerived().GetPlaceholderTile());
 }
 
 template void
@@ -305,15 +282,15 @@ TiledLayerBuffer::Dump(std::stringstream& aStream,
 
     for (int32_t y = visibleRect.y; y < visibleRect.y + visibleRect.height;) {
       int32_t tileStartY = GetTileStart(y, scaledTileSize.height);
-      nsIntPoint tileOrigin = nsIntPoint(RoundDownToTileEdge(x, scaledTileSize.width),
-                                         RoundDownToTileEdge(y, scaledTileSize.height));
-      Tile& tileTexture = GetTile(tileOrigin);
+      Tile tileTexture =
+        GetTile(nsIntPoint(RoundDownToTileEdge(x, scaledTileSize.width),
+                           RoundDownToTileEdge(y, scaledTileSize.height)));
       int32_t h = scaledTileSize.height - tileStartY;
 
       aStream << "\n" << aPrefix << "Tile (x=" <<
         RoundDownToTileEdge(x, scaledTileSize.width) << ", y=" <<
         RoundDownToTileEdge(y, scaledTileSize.height) << "): ";
-      if (!tileTexture.IsPlaceholderTile()) {
+      if (tileTexture != AsDerived().GetPlaceholderTile()) {
         tileTexture.DumpTexture(aStream);
       } else {
         aStream << "empty tile";
@@ -620,10 +597,6 @@ TiledLayerBuffer::Update(const nsIntRegion& newValidRegion,
 
   mRetainedTiles = newRetainedTiles;
   mValidRegion = newValidRegion;
-
-  mFirstTileX = floor_div(mValidRegion.GetBounds().x, scaledTileSize.width);
-  mFirstTileY = floor_div(mValidRegion.GetBounds().y, scaledTileSize.height);
-
   mPaintedRegion.Or(mPaintedRegion, aPaintRegion);
 }
 
diff --git a/gfx/layers/client/TiledContentClient.cpp b/gfx/layers/client/TiledContentClient.cpp
index 26314d9663f..0c07cd3865d 100644
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -374,7 +374,7 @@ int32_t
 gfxMemorySharedReadLock::ReadUnlock()
 {
   int32_t readCount = PR_ATOMIC_DECREMENT(&mReadCount);
-  MOZ_ASSERT(readCount >= 0);
+  NS_ASSERTION(readCount >= 0, "ReadUnlock called without ReadLock.");
 
   return readCount;
 }
@@ -424,7 +424,7 @@ gfxShmSharedReadLock::ReadUnlock() {
   }
   ShmReadLockInfo* info = GetShmReadLockInfoPtr();
   int32_t readCount = PR_ATOMIC_DECREMENT(&info->readCount);
-  MOZ_ASSERT(readCount >= 0);
+  NS_ASSERTION(readCount >= 0, "ReadUnlock called without a ReadLock.");
   if (readCount <= 0) {
     mAllocator->FreeShmemSection(mShmemSection);
   }
@@ -520,7 +520,6 @@ TileClient::TileClient(const TileClient& o)
   mBackLock = o.mBackLock;
   mFrontLock = o.mFrontLock;
   mCompositableClient = o.mCompositableClient;
-  mUpdateRect = o.mUpdateRect;
 #ifdef GFX_TILEDLAYER_DEBUG_OVERLAY
   mLastUpdate = o.mLastUpdate;
 #endif
@@ -540,7 +539,6 @@ TileClient::operator=(const TileClient& o)
   mBackLock = o.mBackLock;
   mFrontLock = o.mFrontLock;
   mCompositableClient = o.mCompositableClient;
-  mUpdateRect = o.mUpdateRect;
 #ifdef GFX_TILEDLAYER_DEBUG_OVERLAY
   mLastUpdate = o.mLastUpdate;
 #endif
@@ -611,8 +609,6 @@ CopyFrontToBack(TextureClient* aFront,
 
   gfx::IntPoint rectToCopyTopLeft = aRectToCopy.TopLeft();
   aFront->CopyToTextureClient(aBack, &aRectToCopy, &rectToCopyTopLeft);
-
-  aFront->Unlock();
   return true;
 }
 
@@ -709,9 +705,9 @@ TileClient::DiscardBackBuffer()
        mManager->ReportClientLost(*mBackBufferOnWhite);
      }
     } else {
-      mManager->ReturnTextureClientDeferred(*mBackBuffer);
+      mManager->ReturnTextureClient(*mBackBuffer);
       if (mBackBufferOnWhite) {
-        mManager->ReturnTextureClientDeferred(*mBackBufferOnWhite);
+        mManager->ReturnTextureClient(*mBackBufferOnWhite);
       }
     }
     mBackLock->ReadUnlock();
@@ -820,17 +816,23 @@ TileClient::GetTileDescriptor()
   if (mFrontLock->GetType() == gfxSharedReadLock::TYPE_MEMORY) {
     return TexturedTileDescriptor(nullptr, mFrontBuffer->GetIPDLActor(),
                                   mFrontBufferOnWhite ? MaybeTexture(mFrontBufferOnWhite->GetIPDLActor()) : MaybeTexture(null_t()),
-                                  mUpdateRect,
                                   TileLock(uintptr_t(mFrontLock.get())));
   } else {
     gfxShmSharedReadLock *lock = static_cast(mFrontLock.get());
     return TexturedTileDescriptor(nullptr, mFrontBuffer->GetIPDLActor(),
                                   mFrontBufferOnWhite ? MaybeTexture(mFrontBufferOnWhite->GetIPDLActor()) : MaybeTexture(null_t()),
-                                  mUpdateRect,
                                   TileLock(lock->GetShmemSection()));
   }
 }
 
+void
+ClientTiledLayerBuffer::ReadUnlock() {
+  for (size_t i = 0; i < mRetainedTiles.Length(); i++) {
+    if (mRetainedTiles[i].IsPlaceholderTile()) continue;
+    mRetainedTiles[i].ReadUnlock();
+  }
+}
+
 void
 ClientTiledLayerBuffer::ReadLock() {
   for (size_t i = 0; i < mRetainedTiles.Length(); i++) {
@@ -871,12 +873,9 @@ ClientTiledLayerBuffer::GetSurfaceDescriptorTiles()
       tileDesc = mRetainedTiles[i].GetTileDescriptor();
     }
     tiles.AppendElement(tileDesc);
-    mRetainedTiles[i].mUpdateRect = IntRect();
   }
   return SurfaceDescriptorTiles(mValidRegion, mPaintedRegion,
-                                tiles,
-                                mFirstTileX, mFirstTileY,
-                                mRetainedWidth, mRetainedHeight,
+                                tiles, mRetainedWidth, mRetainedHeight,
                                 mResolution, mFrameResolution.xScale,
                                 mFrameResolution.yScale);
 }
@@ -1137,8 +1136,6 @@ ClientTiledLayerBuffer::ValidateTile(TileClient aTile,
                         &createdTextureClient, extraPainted,
                         &backBufferOnWhite);
 
-  aTile.mUpdateRect = offsetScaledDirtyRegion.GetBounds().Union(extraPainted.GetBounds());
-
   extraPainted.MoveBy(aTileOrigin);
   extraPainted.And(extraPainted, mNewValidRegion);
   mPaintedRegion.Or(mPaintedRegion, extraPainted);
diff --git a/gfx/layers/client/TiledContentClient.h b/gfx/layers/client/TiledContentClient.h
index 6dfb20e1c0a..80a1a4b2f57 100644
--- a/gfx/layers/client/TiledContentClient.h
+++ b/gfx/layers/client/TiledContentClient.h
@@ -270,7 +270,6 @@ struct TileClient
   RefPtr mBackLock;
   RefPtr mFrontLock;
   RefPtr mManager;
-  gfx::IntRect mUpdateRect;
   CompositableClient* mCompositableClient;
 #ifdef GFX_TILEDLAYER_DEBUG_OVERLAY
   TimeStamp        mLastUpdate;
@@ -419,6 +418,8 @@ public:
                    LayerManager::DrawPaintedLayerCallback aCallback,
                    void* aCallbackData);
 
+  void ReadUnlock();
+
   void ReadLock();
 
   void Release();
@@ -444,15 +445,6 @@ public:
 
   SurfaceDescriptorTiles GetSurfaceDescriptorTiles();
 
-  void SetResolution(float aResolution) {
-    if (mResolution == aResolution) {
-      return;
-    }
-
-    Update(nsIntRegion(), nsIntRegion());
-    mResolution = aResolution;
-  }
-
 protected:
   TileClient ValidateTile(TileClient aTile,
                           const nsIntPoint& aTileRect,
diff --git a/gfx/layers/composite/TextureHost.h b/gfx/layers/composite/TextureHost.h
index d842808c23a..68e8093385d 100644
--- a/gfx/layers/composite/TextureHost.h
+++ b/gfx/layers/composite/TextureHost.h
@@ -326,7 +326,6 @@ protected:
   virtual ~TextureHost();
 
 public:
-
   /**
    * Factory method.
    */
diff --git a/gfx/layers/composite/TiledContentHost.cpp b/gfx/layers/composite/TiledContentHost.cpp
index bf28b7d029e..c21e94c7fe7 100644
--- a/gfx/layers/composite/TiledContentHost.cpp
+++ b/gfx/layers/composite/TiledContentHost.cpp
@@ -29,28 +29,198 @@ class Layer;
 
 TiledLayerBufferComposite::TiledLayerBufferComposite()
   : mFrameResolution()
+  , mHasDoubleBufferedTiles(false)
+  , mIsValid(false)
 {}
 
-TiledLayerBufferComposite::~TiledLayerBufferComposite()
-{
-  Clear();
-}
-
 /* static */ void
 TiledLayerBufferComposite::RecycleCallback(TextureHost* textureHost, void* aClosure)
 {
   textureHost->CompositorRecycle();
 }
 
+TiledLayerBufferComposite::TiledLayerBufferComposite(ISurfaceAllocator* aAllocator,
+                                                     const SurfaceDescriptorTiles& aDescriptor,
+                                                     const nsIntRegion& aOldPaintedRegion,
+                                                     Compositor* aCompositor)
+{
+  mIsValid = true;
+  mHasDoubleBufferedTiles = false;
+  mValidRegion = aDescriptor.validRegion();
+  mPaintedRegion = aDescriptor.paintedRegion();
+  mRetainedWidth = aDescriptor.retainedWidth();
+  mRetainedHeight = aDescriptor.retainedHeight();
+  mResolution = aDescriptor.resolution();
+  mFrameResolution = CSSToParentLayerScale2D(aDescriptor.frameXResolution(),
+                                             aDescriptor.frameYResolution());
+  if (mResolution == 0 || IsNaN(mResolution)) {
+    // There are divisions by mResolution so this protects the compositor process
+    // against malicious content processes and fuzzing.
+    mIsValid = false;
+    return;
+  }
+
+  // Combine any valid content that wasn't already uploaded
+  nsIntRegion oldPaintedRegion(aOldPaintedRegion);
+  oldPaintedRegion.And(oldPaintedRegion, mValidRegion);
+  mPaintedRegion.Or(mPaintedRegion, oldPaintedRegion);
+
+  bool isSameProcess = aAllocator->IsSameProcess();
+
+  const InfallibleTArray& tiles = aDescriptor.tiles();
+  for(size_t i = 0; i < tiles.Length(); i++) {
+    CompositableTextureHostRef texture;
+    CompositableTextureHostRef textureOnWhite;
+    const TileDescriptor& tileDesc = tiles[i];
+    switch (tileDesc.type()) {
+      case TileDescriptor::TTexturedTileDescriptor : {
+        texture = TextureHost::AsTextureHost(tileDesc.get_TexturedTileDescriptor().textureParent());
+        MaybeTexture onWhite = tileDesc.get_TexturedTileDescriptor().textureOnWhite();
+        if (onWhite.type() == MaybeTexture::TPTextureParent) {
+          textureOnWhite = TextureHost::AsTextureHost(onWhite.get_PTextureParent());
+        }
+        const TileLock& ipcLock = tileDesc.get_TexturedTileDescriptor().sharedLock();
+        nsRefPtr sharedLock;
+        if (ipcLock.type() == TileLock::TShmemSection) {
+          sharedLock = gfxShmSharedReadLock::Open(aAllocator, ipcLock.get_ShmemSection());
+        } else {
+          if (!isSameProcess) {
+            // Trying to use a memory based lock instead of a shmem based one in
+            // the cross-process case is a bad security violation.
+            NS_ERROR("A client process may be trying to peek at the host's address space!");
+            // This tells the TiledContentHost that deserialization failed so that
+            // it can propagate the error.
+            mIsValid = false;
+
+            mRetainedTiles.Clear();
+            return;
+          }
+          sharedLock = reinterpret_cast(ipcLock.get_uintptr_t());
+          if (sharedLock) {
+            // The corresponding AddRef is in TiledClient::GetTileDescriptor
+            sharedLock.get()->Release();
+          }
+        }
+
+        CompositableTextureSourceRef textureSource;
+        CompositableTextureSourceRef textureSourceOnWhite;
+        if (texture) {
+          texture->SetCompositor(aCompositor);
+          texture->PrepareTextureSource(textureSource);
+        }
+        if (textureOnWhite) {
+          textureOnWhite->SetCompositor(aCompositor);
+          textureOnWhite->PrepareTextureSource(textureSourceOnWhite);
+        }
+        mRetainedTiles.AppendElement(TileHost(sharedLock,
+                                              texture.get(),
+                                              textureOnWhite.get(),
+                                              textureSource.get(),
+                                              textureSourceOnWhite.get()));
+        break;
+      }
+      default:
+        NS_WARNING("Unrecognised tile descriptor type");
+        // Fall through
+      case TileDescriptor::TPlaceholderTileDescriptor :
+        mRetainedTiles.AppendElement(GetPlaceholderTile());
+        break;
+    }
+    if (texture && !texture->HasInternalBuffer()) {
+      mHasDoubleBufferedTiles = true;
+    }
+  }
+}
+
+void
+TiledLayerBufferComposite::ReadUnlock()
+{
+  if (!IsValid()) {
+    return;
+  }
+  for (size_t i = 0; i < mRetainedTiles.Length(); i++) {
+    mRetainedTiles[i].ReadUnlock();
+  }
+}
+
+void
+TiledLayerBufferComposite::ReleaseTextureHosts()
+{
+  if (!IsValid()) {
+    return;
+  }
+  for (size_t i = 0; i < mRetainedTiles.Length(); i++) {
+    mRetainedTiles[i].mTextureHost = nullptr;
+    mRetainedTiles[i].mTextureHostOnWhite = nullptr;
+    mRetainedTiles[i].mTextureSource = nullptr;
+    mRetainedTiles[i].mTextureSourceOnWhite = nullptr;
+  }
+}
+
+void
+TiledLayerBufferComposite::Upload()
+{
+  if(!IsValid()) {
+    return;
+  }
+  // The TextureClients were created with the TextureFlags::IMMEDIATE_UPLOAD flag,
+  // so calling Update on all the texture hosts will perform the texture upload.
+  Update(mValidRegion, mPaintedRegion);
+  ClearPaintedRegion();
+}
+
+TileHost
+TiledLayerBufferComposite::ValidateTile(TileHost aTile,
+                                        const IntPoint& aTileOrigin,
+                                        const nsIntRegion& aDirtyRect)
+{
+  if (aTile.IsPlaceholderTile()) {
+    NS_WARNING("Placeholder tile encountered in painted region");
+    return aTile;
+  }
+
+#ifdef GFX_TILEDLAYER_PREF_WARNINGS
+  printf_stderr("Upload tile %i, %i\n", aTileOrigin.x, aTileOrigin.y);
+  long start = PR_IntervalNow();
+#endif
+
+  MOZ_ASSERT(aTile.mTextureHost->GetFlags() & TextureFlags::IMMEDIATE_UPLOAD);
+
+#ifdef MOZ_GFX_OPTIMIZE_MOBILE
+  MOZ_ASSERT(!aTile.mTextureHostOnWhite);
+  // We possibly upload the entire texture contents here. This is a purposeful
+  // decision, as sub-image upload can often be slow and/or unreliable, but
+  // we may want to reevaluate this in the future.
+  // For !HasInternalBuffer() textures, this is likely a no-op.
+  aTile.mTextureHost->Updated(nullptr);
+#else
+  nsIntRegion tileUpdated = aDirtyRect.MovedBy(-aTileOrigin);
+  aTile.mTextureHost->Updated(&tileUpdated);
+  if (aTile.mTextureHostOnWhite) {
+    aTile.mTextureHostOnWhite->Updated(&tileUpdated);
+  }
+#endif
+
+#ifdef GFX_TILEDLAYER_PREF_WARNINGS
+  if (PR_IntervalNow() - start > 1) {
+    printf_stderr("Tile Time to upload %i\n", PR_IntervalNow() - start);
+  }
+#endif
+  return aTile;
+}
+
 void
 TiledLayerBufferComposite::SetCompositor(Compositor* aCompositor)
 {
   MOZ_ASSERT(aCompositor);
-  for (TileHost& tile : mRetainedTiles) {
-    if (tile.IsPlaceholderTile()) continue;
-    tile.mTextureHost->SetCompositor(aCompositor);
-    if (tile.mTextureHostOnWhite) {
-      tile.mTextureHostOnWhite->SetCompositor(aCompositor);
+  if (!IsValid()) {
+    return;
+  }
+  for (size_t i = 0; i < mRetainedTiles.Length(); i++) {
+    if (mRetainedTiles[i].IsPlaceholderTile()) continue;
+    mRetainedTiles[i].mTextureHost->SetCompositor(aCompositor);
+    if (mRetainedTiles[i].mTextureHostOnWhite) {
+      mRetainedTiles[i].mTextureHostOnWhite->SetCompositor(aCompositor);
     }
   }
 }
@@ -59,6 +229,10 @@ TiledContentHost::TiledContentHost(const TextureInfo& aTextureInfo)
   : ContentHost(aTextureInfo)
   , mTiledBuffer(TiledLayerBufferComposite())
   , mLowPrecisionTiledBuffer(TiledLayerBufferComposite())
+  , mOldTiledBuffer(TiledLayerBufferComposite())
+  , mOldLowPrecisionTiledBuffer(TiledLayerBufferComposite())
+  , mPendingUpload(false)
+  , mPendingLowPrecisionUpload(false)
 {
   MOZ_COUNT_CTOR(TiledContentHost);
 }
@@ -66,6 +240,28 @@ TiledContentHost::TiledContentHost(const TextureInfo& aTextureInfo)
 TiledContentHost::~TiledContentHost()
 {
   MOZ_COUNT_DTOR(TiledContentHost);
+
+  // Unlock any buffers that may still be locked. If we have a pending upload,
+  // we will need to unlock the buffer that was about to be uploaded.
+  // If a buffer that was being composited had double-buffered tiles, we will
+  // need to unlock that buffer too.
+  if (mPendingUpload) {
+    mTiledBuffer.ReadUnlock();
+    if (mOldTiledBuffer.HasDoubleBufferedTiles()) {
+      mOldTiledBuffer.ReadUnlock();
+    }
+  } else if (mTiledBuffer.HasDoubleBufferedTiles()) {
+    mTiledBuffer.ReadUnlock();
+  }
+
+  if (mPendingLowPrecisionUpload) {
+    mLowPrecisionTiledBuffer.ReadUnlock();
+    if (mOldLowPrecisionTiledBuffer.HasDoubleBufferedTiles()) {
+      mOldLowPrecisionTiledBuffer.ReadUnlock();
+    }
+  } else if (mLowPrecisionTiledBuffer.HasDoubleBufferedTiles()) {
+    mLowPrecisionTiledBuffer.ReadUnlock();
+  }
 }
 
 void
@@ -81,10 +277,33 @@ TiledContentHost::Detach(Layer* aLayer,
                          AttachFlags aFlags /* = NO_FLAGS */)
 {
   if (!mKeepAttached || aLayer == mLayer || aFlags & FORCE_DETACH) {
-    // Clear the TiledLayerBuffers, which will take care of releasing the
-    // copy-on-write locks.
-    mTiledBuffer.Clear();
-    mLowPrecisionTiledBuffer.Clear();
+
+    // Unlock any buffers that may still be locked. If we have a pending upload,
+    // we will need to unlock the buffer that was about to be uploaded.
+    // If a buffer that was being composited had double-buffered tiles, we will
+    // need to unlock that buffer too.
+    if (mPendingUpload) {
+      mTiledBuffer.ReadUnlock();
+      if (mOldTiledBuffer.HasDoubleBufferedTiles()) {
+        mOldTiledBuffer.ReadUnlock();
+      }
+    } else if (mTiledBuffer.HasDoubleBufferedTiles()) {
+      mTiledBuffer.ReadUnlock();
+    }
+
+    if (mPendingLowPrecisionUpload) {
+      mLowPrecisionTiledBuffer.ReadUnlock();
+      if (mOldLowPrecisionTiledBuffer.HasDoubleBufferedTiles()) {
+        mOldLowPrecisionTiledBuffer.ReadUnlock();
+      }
+    } else if (mLowPrecisionTiledBuffer.HasDoubleBufferedTiles()) {
+      mLowPrecisionTiledBuffer.ReadUnlock();
+    }
+
+    mTiledBuffer = TiledLayerBufferComposite();
+    mLowPrecisionTiledBuffer = TiledLayerBufferComposite();
+    mOldTiledBuffer = TiledLayerBufferComposite();
+    mOldLowPrecisionTiledBuffer = TiledLayerBufferComposite();
   }
   CompositableHost::Detach(aLayer,aFlags);
 }
@@ -94,280 +313,60 @@ TiledContentHost::UseTiledLayerBuffer(ISurfaceAllocator* aAllocator,
                                       const SurfaceDescriptorTiles& aTiledDescriptor)
 {
   if (aTiledDescriptor.resolution() < 1) {
-    if (!mLowPrecisionTiledBuffer.UseTiles(aTiledDescriptor, mCompositor, aAllocator)) {
-      return false;
-    }
-  } else {
-    if (!mTiledBuffer.UseTiles(aTiledDescriptor, mCompositor, aAllocator)) {
-      return false;
-    }
-  }
-  return true;
-}
-
-void
-UseTileTexture(CompositableTextureHostRef& aTexture,
-               CompositableTextureSourceRef& aTextureSource,
-               const IntRect& aUpdateRect,
-               TextureHost* aNewTexture,
-               Compositor* aCompositor)
-{
-  if (aTexture && aTexture->GetFormat() != aNewTexture->GetFormat()) {
-    // Only reuse textures if their format match the new texture's.
-    aTextureSource = nullptr;
-    aTexture = nullptr;
-  }
-  aTexture = aNewTexture;
-  if (aTexture) {
-    if (aCompositor) {
-      aTexture->SetCompositor(aCompositor);
-    }
-
-    if (!aUpdateRect.IsEmpty()) {
-#ifdef MOZ_GFX_OPTIMIZE_MOBILE
-      aTexture->Updated(nullptr);
-#else
-      // We possibly upload the entire texture contents here. This is a purposeful
-      // decision, as sub-image upload can often be slow and/or unreliable, but
-      // we may want to reevaluate this in the future.
-      // For !HasInternalBuffer() textures, this is likely a no-op.
-      nsIntRegion region = aUpdateRect;
-      aTexture->Updated(®ion);
-#endif
-    }
-    aTexture->PrepareTextureSource(aTextureSource);
-  }
-}
-
-bool
-GetCopyOnWriteLock(const TileLock& ipcLock, TileHost& aTile, ISurfaceAllocator* aAllocator) {
-  MOZ_ASSERT(aAllocator);
-
-  nsRefPtr sharedLock;
-  if (ipcLock.type() == TileLock::TShmemSection) {
-    sharedLock = gfxShmSharedReadLock::Open(aAllocator, ipcLock.get_ShmemSection());
-  } else {
-    if (!aAllocator->IsSameProcess()) {
-      // Trying to use a memory based lock instead of a shmem based one in
-      // the cross-process case is a bad security violation.
-      NS_ERROR("A client process may be trying to peek at the host's address space!");
-      return false;
-    }
-    sharedLock = reinterpret_cast(ipcLock.get_uintptr_t());
-    if (sharedLock) {
-      // The corresponding AddRef is in TiledClient::GetTileDescriptor
-      sharedLock.get()->Release();
-    }
-  }
-  aTile.mSharedLock = sharedLock;
-  return true;
-}
-
-bool
-TiledLayerBufferComposite::UseTiles(const SurfaceDescriptorTiles& aTiles,
-                                    Compositor* aCompositor,
-                                    ISurfaceAllocator* aAllocator)
-{
-  if (mResolution != aTiles.resolution()) {
-    Clear();
-  }
-  MOZ_ASSERT(aAllocator);
-  MOZ_ASSERT(aCompositor);
-  if (!aAllocator || !aCompositor) {
-    return false;
-  }
-
-  if (aTiles.resolution() == 0 || IsNaN(aTiles.resolution())) {
-    // There are divisions by mResolution so this protects the compositor process
-    // against malicious content processes and fuzzing.
-    return false;
-  }
-
-  int newFirstTileX = aTiles.firstTileX();
-  int newFirstTileY = aTiles.firstTileY();
-  int oldFirstTileX = mFirstTileX;
-  int oldFirstTileY = mFirstTileY;
-  int newRetainedWidth = aTiles.retainedWidth();
-  int newRetainedHeight = aTiles.retainedHeight();
-  int oldRetainedWidth = mRetainedWidth;
-  int oldRetainedHeight = mRetainedHeight;
-
-  const InfallibleTArray& tileDescriptors = aTiles.tiles();
-
-  nsTArray oldTiles;
-  mRetainedTiles.SwapElements(oldTiles);
-  mRetainedTiles.SetLength(tileDescriptors.Length());
-
-  // Step 1, we need to unlock tiles that don't have an internal buffer after the
-  // next frame where they are replaced.
-  // Since we are about to replace the tiles' textures, we need to keep their locks
-  // somewhere (in mPreviousSharedLock) until we composite the layer.
-  for (size_t i = 0; i < oldTiles.Length(); ++i) {
-    TileHost& tile = oldTiles[i];
-    // It can happen that we still have a previous lock at this point,
-    // if we changed a tile's front buffer (causing mSharedLock to
-    // go into mPreviousSharedLock, and then did not composite that tile until
-    // the next transaction, either because the tile is offscreen or because the
-    // two transactions happened with no composition in between (over-production).
-    tile.ReadUnlockPrevious();
-
-    if (tile.mTextureHost && !tile.mTextureHost->HasInternalBuffer()) {
-      MOZ_ASSERT(tile.mSharedLock);
-      int tileX = i % oldRetainedWidth + oldFirstTileX;
-      int tileY = i / oldRetainedWidth + oldFirstTileY;
-
-      if (tileX >= newFirstTileX && tileY >= newFirstTileY &&
-          tileX < (newFirstTileX + newRetainedWidth) &&
-          tileY < (newFirstTileY + newRetainedHeight)) {
-        // This tile still exist in the new buffer
-        tile.mPreviousSharedLock = tile.mSharedLock;
-        tile.mSharedLock = nullptr;
-      } else {
-        // This tile does not exist anymore in the new buffer because the size
-        // changed.
-        tile.ReadUnlock();
-      }
-    }
-
-    // By now we should not have anything in mSharedLock.
-    MOZ_ASSERT(!tile.mSharedLock);
-  }
-
-  // Step 2, move the tiles in mRetainedTiles at places that correspond to where
-  // they should be with the new retained with and height rather than the
-  // old one.
-  for (size_t i = 0; i < tileDescriptors.Length(); i++) {
-    int tileX = i % newRetainedWidth + newFirstTileX;
-    int tileY = i / newRetainedWidth + newFirstTileY;
-
-    // First, get the already existing tiles to the right place in the array,
-    // and use placeholders where there was no tiles.
-    if (tileX < oldFirstTileX || tileY < oldFirstTileY ||
-        tileX >= (oldFirstTileX + oldRetainedWidth) ||
-        tileY >= (oldFirstTileY + oldRetainedHeight)) {
-      mRetainedTiles[i] = GetPlaceholderTile();
+    if (mPendingLowPrecisionUpload) {
+      mLowPrecisionTiledBuffer.ReadUnlock();
     } else {
-      mRetainedTiles[i] = oldTiles[(tileY - oldFirstTileY) * oldRetainedWidth +
-                                   (tileX - oldFirstTileX)];
-      // If we hit this assertion it means we probably mixed something up in the
-      // logic that tries to reuse tiles on the compositor side. It is most likely
-      // benign, but we are missing some fast paths so let's try to make it not happen.
-      MOZ_ASSERT(tileX == mRetainedTiles[i].x && tileY == mRetainedTiles[i].y);
-    }
-  }
-
-  // It is important to remove the duplicated reference to tiles before calling
-  // TextureHost::PrepareTextureSource, etc. because depending on the textures
-  // ref counts we may or may not get some of the fast paths.
-  oldTiles.Clear();
-
-  // Step 3, handle the texture updates and release the copy-on-write locks.
-  for (size_t i = 0; i < mRetainedTiles.Length(); i++) {
-    const TileDescriptor& tileDesc = tileDescriptors[i];
-
-    TileHost& tile = mRetainedTiles[i];
-
-    switch (tileDesc.type()) {
-      case TileDescriptor::TTexturedTileDescriptor: {
-        const TexturedTileDescriptor& texturedDesc = tileDesc.get_TexturedTileDescriptor();
-
-        const TileLock& ipcLock = texturedDesc.sharedLock();
-        if (!GetCopyOnWriteLock(ipcLock, tile, aAllocator)) {
-          return false;
-        }
-
-        RefPtr textureHost = TextureHost::AsTextureHost(
-          texturedDesc.textureParent()
-        );
-
-        RefPtr textureOnWhite = nullptr;
-        if (texturedDesc.textureOnWhite().type() == MaybeTexture::TPTextureParent) {
-          textureOnWhite = TextureHost::AsTextureHost(
-            texturedDesc.textureOnWhite().get_PTextureParent()
-          );
-        }
-
-        UseTileTexture(tile.mTextureHost,
-                       tile.mTextureSource,
-                       texturedDesc.updateRect(),
-                       textureHost,
-                       aCompositor);
-
-        if (textureOnWhite) {
-          UseTileTexture(tile.mTextureHostOnWhite,
-                         tile.mTextureSourceOnWhite,
-                         texturedDesc.updateRect(),
-                         textureOnWhite,
-                         aCompositor);
-        } else {
-          // We could still have component alpha textures from a previous frame.
-          tile.mTextureSourceOnWhite = nullptr;
-          tile.mTextureHostOnWhite = nullptr;
-        }
-
-        if (textureHost->HasInternalBuffer()) {
-          // Now that we did the texture upload (in UseTileTexture), we can release
-          // the lock.
-          tile.ReadUnlock();
-        }
-
-        break;
-      }
-      default:
-        NS_WARNING("Unrecognised tile descriptor type");
-      case TileDescriptor::TPlaceholderTileDescriptor: {
-
-        if (tile.mTextureHost) {
-          tile.mTextureHost->UnbindTextureSource();
-          tile.mTextureSource = nullptr;
-        }
-        if (tile.mTextureHostOnWhite) {
-          tile.mTextureHostOnWhite->UnbindTextureSource();
-          tile.mTextureSourceOnWhite = nullptr;
-        }
-        // we may have a previous lock, and are about to loose our reference to it.
-        // It is okay to unlock it because we just destroyed the texture source.
-        tile.ReadUnlockPrevious();
-        tile = GetPlaceholderTile();
-
-        break;
+      mPendingLowPrecisionUpload = true;
+      // If the old buffer has double-buffered tiles, hang onto it so we can
+      // unlock it after we've composited the new buffer.
+      // We only need to hang onto the locks, but not the textures.
+      // Releasing the textures here can help prevent a memory spike in the
+      // situation that the client starts rendering new content before we get
+      // to composite the new buffer.
+      if (mLowPrecisionTiledBuffer.HasDoubleBufferedTiles()) {
+        mOldLowPrecisionTiledBuffer = mLowPrecisionTiledBuffer;
+        mOldLowPrecisionTiledBuffer.ReleaseTextureHosts();
       }
     }
-
-    tile.x = i % newRetainedWidth + newFirstTileX;
-    tile.y = i / newRetainedWidth + newFirstTileY;
+    mLowPrecisionTiledBuffer =
+      TiledLayerBufferComposite(aAllocator,
+                                aTiledDescriptor,
+                                mLowPrecisionTiledBuffer.GetPaintedRegion(),
+                                mCompositor);
+    if (!mLowPrecisionTiledBuffer.IsValid()) {
+      // Something bad happened. Stop here, return false (kills the child process),
+      // and do as little work as possible on the received data as it appears
+      // to be corrupted.
+      mPendingLowPrecisionUpload = false;
+      mPendingUpload = false;
+      return false;
+    }
+  } else {
+    if (mPendingUpload) {
+      mTiledBuffer.ReadUnlock();
+    } else {
+      mPendingUpload = true;
+      if (mTiledBuffer.HasDoubleBufferedTiles()) {
+        mOldTiledBuffer = mTiledBuffer;
+        mOldTiledBuffer.ReleaseTextureHosts();
+      }
+    }
+    mTiledBuffer = TiledLayerBufferComposite(aAllocator,
+                                             aTiledDescriptor,
+                                             mTiledBuffer.GetPaintedRegion(),
+                                             mCompositor);
+    if (!mTiledBuffer.IsValid()) {
+      // Something bad happened. Stop here, return false (kills the child process),
+      // and do as little work as possible on the received data as it appears
+      // to be corrupted.
+      mPendingLowPrecisionUpload = false;
+      mPendingUpload = false;
+      return false;
+    }
   }
-
-  mFirstTileX = newFirstTileX;
-  mFirstTileY = newFirstTileY;
-  mRetainedWidth = newRetainedWidth;
-  mRetainedHeight = newRetainedHeight;
-  mValidRegion = aTiles.validRegion();
-
-  mResolution = aTiles.resolution();
-  mFrameResolution = CSSToParentLayerScale2D(aTiles.frameXResolution(),
-                                             aTiles.frameYResolution());
-
   return true;
 }
 
-void
-TiledLayerBufferComposite::Clear()
-{
-  for (TileHost& tile : mRetainedTiles) {
-    tile.ReadUnlock();
-    tile.ReadUnlockPrevious();
-  }
-  mRetainedTiles.Clear();
-  mFirstTileX = 0;
-  mFirstTileY = 0;
-  mRetainedWidth = 0;
-  mRetainedHeight = 0;
-  mValidRegion = nsIntRegion();
-  mPaintedRegion = nsIntRegion();
-  mResolution = 1.0;
-}
-
 void
 TiledContentHost::Composite(EffectChain& aEffectChain,
                             float aOpacity,
@@ -377,6 +376,25 @@ TiledContentHost::Composite(EffectChain& aEffectChain,
                             const nsIntRegion* aVisibleRegion /* = nullptr */)
 {
   MOZ_ASSERT(mCompositor);
+  if (mPendingUpload) {
+    mTiledBuffer.SetCompositor(mCompositor);
+    mTiledBuffer.Upload();
+
+    // For a single-buffered tiled buffer, Upload will upload the shared memory
+    // surface to texture memory and we no longer need to read from them.
+    if (!mTiledBuffer.HasDoubleBufferedTiles()) {
+      mTiledBuffer.ReadUnlock();
+    }
+  }
+  if (mPendingLowPrecisionUpload) {
+    mLowPrecisionTiledBuffer.SetCompositor(mCompositor);
+    mLowPrecisionTiledBuffer.Upload();
+
+    if (!mLowPrecisionTiledBuffer.HasDoubleBufferedTiles()) {
+      mLowPrecisionTiledBuffer.ReadUnlock();
+    }
+  }
+
   // Reduce the opacity of the low-precision buffer to make it a
   // little more subtle and less jarring. In particular, text
   // rendered at low-resolution and scaled tends to look pretty
@@ -422,11 +440,24 @@ TiledContentHost::Composite(EffectChain& aEffectChain,
                     aFilter, aClipRect, *renderRegion, aTransform);
   RenderLayerBuffer(mTiledBuffer, nullptr, aEffectChain, aOpacity, aFilter,
                     aClipRect, *renderRegion, aTransform);
+
+  // Now release the old buffer if it had double-buffered tiles, as we can
+  // guarantee that they're no longer on the screen (and so any locks that may
+  // have been held have been released).
+  if (mPendingUpload && mOldTiledBuffer.HasDoubleBufferedTiles()) {
+    mOldTiledBuffer.ReadUnlock();
+    mOldTiledBuffer = TiledLayerBufferComposite();
+  }
+  if (mPendingLowPrecisionUpload && mOldLowPrecisionTiledBuffer.HasDoubleBufferedTiles()) {
+    mOldLowPrecisionTiledBuffer.ReadUnlock();
+    mOldLowPrecisionTiledBuffer = TiledLayerBufferComposite();
+  }
+  mPendingUpload = mPendingLowPrecisionUpload = false;
 }
 
 
 void
-TiledContentHost::RenderTile(TileHost& aTile,
+TiledContentHost::RenderTile(const TileHost& aTile,
                              const gfxRGBA* aBackgroundColor,
                              EffectChain& aEffectChain,
                              float aOpacity,
@@ -493,7 +524,6 @@ TiledContentHost::RenderTile(TileHost& aTile,
   }
   mCompositor->DrawDiagnostics(flags,
                                aScreenRegion, aClipRect, aTransform, mFlashCounter);
-  aTile.ReadUnlockPrevious();
 }
 
 void
@@ -561,10 +591,10 @@ TiledContentHost::RenderLayerBuffer(TiledLayerBufferComposite& aLayerBuffer,
         h = visibleRect.y + visibleRect.height - y;
       }
 
-      nsIntPoint tileOrigin = nsIntPoint(aLayerBuffer.RoundDownToTileEdge(x, scaledTileSize.width),
-                                         aLayerBuffer.RoundDownToTileEdge(y, scaledTileSize.height));
-      TileHost& tileTexture = aLayerBuffer.GetTile(tileOrigin);
-      if (!tileTexture.IsPlaceholderTile()) {
+      TileHost tileTexture = aLayerBuffer.
+        GetTile(IntPoint(aLayerBuffer.RoundDownToTileEdge(x, scaledTileSize.width),
+                         aLayerBuffer.RoundDownToTileEdge(y, scaledTileSize.height)));
+      if (tileTexture != aLayerBuffer.GetPlaceholderTile()) {
         nsIntRegion tileDrawRegion;
         tileDrawRegion.And(IntRect(x, y, w, h), aLayerBuffer.GetValidRegion());
         tileDrawRegion.And(tileDrawRegion, aVisibleRegion);
diff --git a/gfx/layers/composite/TiledContentHost.h b/gfx/layers/composite/TiledContentHost.h
index 2647c6f1557..13a89a20797 100644
--- a/gfx/layers/composite/TiledContentHost.h
+++ b/gfx/layers/composite/TiledContentHost.h
@@ -52,8 +52,6 @@ public:
   // essentially, this is a sentinel used to represent an invalid or blank
   // tile.
   TileHost()
-  : x(-1)
-  , y(-1)
   {}
 
   // Constructs a TileHost from a gfxSharedReadLock and TextureHost.
@@ -67,8 +65,6 @@ public:
     , mTextureHostOnWhite(aTextureHostOnWhite)
     , mTextureSource(aSource)
     , mTextureSourceOnWhite(aSourceOnWhite)
-    , x(-1)
-    , y(-1)
   {}
 
   TileHost(const TileHost& o) {
@@ -77,9 +73,6 @@ public:
     mTextureSource = o.mTextureSource;
     mTextureSourceOnWhite = o.mTextureSourceOnWhite;
     mSharedLock = o.mSharedLock;
-    mPreviousSharedLock = o.mPreviousSharedLock;
-    x = o.x;
-    y = o.y;
   }
   TileHost& operator=(const TileHost& o) {
     if (this == &o) {
@@ -90,9 +83,6 @@ public:
     mTextureSource = o.mTextureSource;
     mTextureSourceOnWhite = o.mTextureSourceOnWhite;
     mSharedLock = o.mSharedLock;
-    mPreviousSharedLock = o.mPreviousSharedLock;
-    x = o.x;
-    y = o.y;
     return *this;
   }
 
@@ -108,14 +98,6 @@ public:
   void ReadUnlock() {
     if (mSharedLock) {
       mSharedLock->ReadUnlock();
-      mSharedLock = nullptr;
-    }
-  }
-
-  void ReadUnlockPrevious() {
-    if (mPreviousSharedLock) {
-      mPreviousSharedLock->ReadUnlock();
-      mPreviousSharedLock = nullptr;
     }
   }
 
@@ -129,14 +111,10 @@ public:
   }
 
   RefPtr mSharedLock;
-  RefPtr mPreviousSharedLock;
   CompositableTextureHostRef mTextureHost;
   CompositableTextureHostRef mTextureHostOnWhite;
   mutable CompositableTextureSourceRef mTextureSource;
   mutable CompositableTextureSourceRef mTextureSourceOnWhite;
-  // This is not strictly necessary but makes debugging whole lot easier.
-  int x;
-  int y;
 };
 
 class TiledLayerBufferComposite
@@ -145,14 +123,13 @@ class TiledLayerBufferComposite
   friend class TiledLayerBuffer;
 
 public:
+  typedef TiledLayerBuffer::Iterator Iterator;
+
   TiledLayerBufferComposite();
-  ~TiledLayerBufferComposite();
-
-  bool UseTiles(const SurfaceDescriptorTiles& aTileDescriptors,
-                Compositor* aCompositor,
-                ISurfaceAllocator* aAllocator);
-
-  void Clear();
+  TiledLayerBufferComposite(ISurfaceAllocator* aAllocator,
+                            const SurfaceDescriptorTiles& aDescriptor,
+                            const nsIntRegion& aOldPaintedRegion,
+                            Compositor* aCompositor);
 
   TileHost GetPlaceholderTile() const { return TileHost(); }
 
@@ -160,16 +137,43 @@ public:
   // by the sum of the resolutions of all parent layers' FrameMetrics.
   const CSSToParentLayerScale2D& GetFrameResolution() { return mFrameResolution; }
 
+  void ReadUnlock();
+
+  void ReleaseTextureHosts();
+
+  /**
+   * This will synchronously upload any necessary texture contents, making the
+   * sources immediately available for compositing. For texture hosts that
+   * don't have an internal buffer, this is unlikely to actually do anything.
+   */
+  void Upload();
+
   void SetCompositor(Compositor* aCompositor);
 
+  bool HasDoubleBufferedTiles() { return mHasDoubleBufferedTiles; }
+
+  bool IsValid() const { return mIsValid; }
+
   // Recycle callback for TextureHost.
   // Used when TiledContentClient is present in client side.
   static void RecycleCallback(TextureHost* textureHost, void* aClosure);
 
 protected:
+  TileHost ValidateTile(TileHost aTile,
+                        const gfx::IntPoint& aTileRect,
+                        const nsIntRegion& dirtyRect);
+
+  // do nothing, the desctructor in the texture host takes care of releasing resources
+  void ReleaseTile(TileHost aTile) {}
+
   void SwapTiles(TileHost& aTileA, TileHost& aTileB) { std::swap(aTileA, aTileB); }
 
+  void UnlockTile(TileHost aTile) {}
+  void PostValidate(const nsIntRegion& aPaintRegion) {}
+private:
   CSSToParentLayerScale2D mFrameResolution;
+  bool mHasDoubleBufferedTiles;
+  bool mIsValid;
 };
 
 /**
@@ -229,10 +233,11 @@ public:
 
   virtual void SetCompositor(Compositor* aCompositor) override
   {
-    MOZ_ASSERT(aCompositor);
     CompositableHost::SetCompositor(aCompositor);
     mTiledBuffer.SetCompositor(aCompositor);
     mLowPrecisionTiledBuffer.SetCompositor(aCompositor);
+    mOldTiledBuffer.SetCompositor(aCompositor);
+    mOldLowPrecisionTiledBuffer.SetCompositor(aCompositor);
   }
 
   virtual bool UseTiledLayerBuffer(ISurfaceAllocator* aAllocator,
@@ -274,7 +279,7 @@ private:
                          gfx::Matrix4x4 aTransform);
 
   // Renders a single given tile.
-  void RenderTile(TileHost& aTile,
+  void RenderTile(const TileHost& aTile,
                   const gfxRGBA* aBackgroundColor,
                   EffectChain& aEffectChain,
                   float aOpacity,
@@ -289,6 +294,10 @@ private:
 
   TiledLayerBufferComposite    mTiledBuffer;
   TiledLayerBufferComposite    mLowPrecisionTiledBuffer;
+  TiledLayerBufferComposite    mOldTiledBuffer;
+  TiledLayerBufferComposite    mOldLowPrecisionTiledBuffer;
+  bool                         mPendingUpload;
+  bool                         mPendingLowPrecisionUpload;
 };
 
 }
diff --git a/gfx/layers/ipc/LayersMessages.ipdlh b/gfx/layers/ipc/LayersMessages.ipdlh
index 22fdd827bd3..c1a0a12e078 100644
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -315,7 +315,6 @@ union MaybeTexture {
 struct TexturedTileDescriptor {
   PTexture texture;
   MaybeTexture textureOnWhite;
-  IntRect updateRect;
   TileLock sharedLock;
 };
 
@@ -331,8 +330,6 @@ struct SurfaceDescriptorTiles {
   nsIntRegion validRegion;
   nsIntRegion paintedRegion;
   TileDescriptor[] tiles;
-  int         firstTileX;
-  int         firstTileY;
   int         retainedWidth;
   int         retainedHeight;
   float       resolution;

From e65454c0f6a02861824c268aa61dfb9eb2bbb21d Mon Sep 17 00:00:00 2001
From: Kim Moir 
Date: Mon, 25 May 2015 14:24:19 -0400
Subject: [PATCH 87/89] No bug update mozharness.json to 12ce04f3ec51

---
 testing/mozharness/mozharness.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/testing/mozharness/mozharness.json b/testing/mozharness/mozharness.json
index 91b01f887c5..0f5534b8f63 100644
--- a/testing/mozharness/mozharness.json
+++ b/testing/mozharness/mozharness.json
@@ -1,4 +1,4 @@
 {
     "repo": "https://hg.mozilla.org/build/mozharness",
-    "revision": "66c655a58460"
+    "revision": "12ce04f3ec51"
 }

From 12892845fd0f91de0a3bc9f60a4c9bb07d63bab0 Mon Sep 17 00:00:00 2001
From: Kartikaya Gupta 
Date: Mon, 25 May 2015 15:32:09 -0400
Subject: [PATCH 88/89] Bug 1117958 - Allow any debugging options to the run or
 gtest mach subcommands to automatically enable debugging. r=gps

---
 python/mozbuild/mozbuild/mach_commands.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/python/mozbuild/mozbuild/mach_commands.py b/python/mozbuild/mozbuild/mach_commands.py
index 549501708b7..d2cd24dc5a1 100644
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -645,7 +645,7 @@ class GTestCommands(MachCommandBase):
 
     @CommandArgumentGroup('debugging')
     @CommandArgument('--debug', action='store_true', group='debugging',
-        help='Enable the debugger. Not specifying a --debugger option will result in the default debugger being used. The following arguments have no effect without this.')
+        help='Enable the debugger. Not specifying a --debugger option will result in the default debugger being used.')
     @CommandArgument('--debugger', default=None, type=str, group='debugging',
         help='Name of debugger to use.')
     @CommandArgument('--debugger-args', default=None, metavar='params', type=str,
@@ -661,7 +661,7 @@ class GTestCommands(MachCommandBase):
         app_path = self.get_binary_path('app')
         args = [app_path, '-unittest'];
 
-        if debug:
+        if debug or debugger or debugger_args:
             args = self.prepend_debugger_args(args, debugger, debugger_args)
 
         cwd = os.path.join(self.topobjdir, '_tests', 'gtest')
@@ -862,7 +862,7 @@ class RunProgram(MachCommandBase):
 
     @CommandArgumentGroup('debugging')
     @CommandArgument('--debug', action='store_true', group='debugging',
-        help='Enable the debugger. Not specifying a --debugger option will result in the default debugger being used. The following arguments have no effect without this.')
+        help='Enable the debugger. Not specifying a --debugger option will result in the default debugger being used.')
     @CommandArgument('--debugger', default=None, type=str, group='debugging',
         help='Name of debugger to use.')
     @CommandArgument('--debugparams', default=None, metavar='params', type=str,
@@ -921,7 +921,7 @@ class RunProgram(MachCommandBase):
 
         extra_env = {}
 
-        if debug:
+        if debug or debugger or debugparams:
             import mozdebug
             if not debugger:
                 # No debugger name was provided. Look for the default ones on

From 4463c794e3b96541554e39d4362cae2a8a848f78 Mon Sep 17 00:00:00 2001
From: Kartikaya Gupta 
Date: Mon, 25 May 2015 15:32:20 -0400
Subject: [PATCH 89/89] Bug 1167721 - Ensure we trigger a repaint when dropping
 velocity to zero in CancelAnimation. r=botond

---
 gfx/layers/apz/src/AsyncPanZoomController.cpp | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp
index 34f17f63439..e238dda682c 100644
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -2275,7 +2275,11 @@ void AsyncPanZoomController::CancelAnimation(CancelAnimationFlags aFlags) {
   SetState(NOTHING);
   mAnimation = nullptr;
   // Since there is no animation in progress now the axes should
-  // have no velocity either.
+  // have no velocity either. If we are dropping the velocity from a non-zero
+  // value we should trigger a repaint as the displayport margins are dependent
+  // on the velocity and the last repaint request might not have good margins
+  // any more.
+  bool repaint = !IsZero(GetVelocityVector());
   mX.SetVelocity(0);
   mY.SetVelocity(0);
   // Setting the state to nothing and cancelling the animation can
@@ -2283,6 +2287,9 @@ void AsyncPanZoomController::CancelAnimation(CancelAnimationFlags aFlags) {
   // overscroll here.
   if (!(aFlags & ExcludeOverscroll) && IsOverscrolled()) {
     ClearOverscroll();
+    repaint = true;
+  }
+  if (repaint) {
     RequestContentRepaint();
     ScheduleComposite();
     UpdateSharedCompositorFrameMetrics();