mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge central to inbound
This commit is contained in:
commit
63f2e7c2e5
@ -35,51 +35,18 @@
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "nsDOMBlobBuilder.h"
|
||||
#include "jstypedarray.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsDOMClassInfoID.h"
|
||||
#include "nsDOMFile.h"
|
||||
#include "nsIMultiplexInputStream.h"
|
||||
#include "nsStringStream.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsJSUtils.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "CheckedInt.h"
|
||||
|
||||
#include "mozilla/StdInt.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
class nsDOMMultipartFile : public nsDOMFileBase
|
||||
{
|
||||
public:
|
||||
// Create as a file
|
||||
nsDOMMultipartFile(nsTArray<nsCOMPtr<nsIDOMBlob> > aBlobs,
|
||||
const nsAString& aName,
|
||||
const nsAString& aContentType)
|
||||
: nsDOMFileBase(aName, aContentType, UINT64_MAX),
|
||||
mBlobs(aBlobs)
|
||||
{
|
||||
}
|
||||
|
||||
// Create as a blob
|
||||
nsDOMMultipartFile(nsTArray<nsCOMPtr<nsIDOMBlob> > aBlobs,
|
||||
const nsAString& aContentType)
|
||||
: nsDOMFileBase(aContentType, UINT64_MAX),
|
||||
mBlobs(aBlobs)
|
||||
{
|
||||
}
|
||||
|
||||
already_AddRefed<nsIDOMBlob>
|
||||
CreateSlice(PRUint64 aStart, PRUint64 aLength, const nsAString& aContentType);
|
||||
|
||||
NS_IMETHOD GetSize(PRUint64*);
|
||||
NS_IMETHOD GetInternalStream(nsIInputStream**);
|
||||
|
||||
protected:
|
||||
nsTArray<nsCOMPtr<nsIDOMBlob> > mBlobs;
|
||||
};
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMMultipartFile::GetSize(PRUint64* aLength)
|
||||
{
|
||||
@ -199,67 +166,6 @@ nsDOMMultipartFile::CreateSlice(PRUint64 aStart, PRUint64 aLength,
|
||||
return blob.forget();
|
||||
}
|
||||
|
||||
class nsDOMBlobBuilder : public nsIDOMMozBlobBuilder
|
||||
{
|
||||
public:
|
||||
nsDOMBlobBuilder()
|
||||
: mData(nsnull), mDataLen(0), mDataBufferLen(0)
|
||||
{}
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIDOMMOZBLOBBUILDER
|
||||
protected:
|
||||
nsresult AppendVoidPtr(void* aData, PRUint32 aLength);
|
||||
nsresult AppendString(JSString* aString, JSContext* aCx);
|
||||
nsresult AppendBlob(nsIDOMBlob* aBlob);
|
||||
nsresult AppendArrayBuffer(JSObject* aBuffer);
|
||||
|
||||
bool ExpandBufferSize(PRUint64 aSize)
|
||||
{
|
||||
if (mDataBufferLen >= mDataLen + aSize) {
|
||||
mDataLen += aSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Start at 1 or we'll loop forever.
|
||||
CheckedUint32 bufferLen = NS_MAX<PRUint32>(mDataBufferLen, 1);
|
||||
while (bufferLen.valid() && bufferLen.value() < mDataLen + aSize)
|
||||
bufferLen *= 2;
|
||||
|
||||
if (!bufferLen.valid())
|
||||
return false;
|
||||
|
||||
// PR_ memory functions are still fallible
|
||||
void* data = PR_Realloc(mData, bufferLen.value());
|
||||
if (!data)
|
||||
return false;
|
||||
|
||||
mData = data;
|
||||
mDataBufferLen = bufferLen.value();
|
||||
mDataLen += aSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Flush() {
|
||||
if (mData) {
|
||||
// If we have some data, create a blob for it
|
||||
// and put it on the stack
|
||||
|
||||
nsCOMPtr<nsIDOMBlob> blob =
|
||||
new nsDOMMemoryFile(mData, mDataLen, EmptyString(), EmptyString());
|
||||
mBlobs.AppendElement(blob);
|
||||
mData = nsnull; // The nsDOMMemoryFile takes ownership of the buffer
|
||||
mDataLen = 0;
|
||||
mDataBufferLen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
nsTArray<nsCOMPtr<nsIDOMBlob> > mBlobs;
|
||||
void* mData;
|
||||
PRUint64 mDataLen;
|
||||
PRUint64 mDataBufferLen;
|
||||
};
|
||||
|
||||
DOMCI_DATA(MozBlobBuilder, nsDOMBlobBuilder)
|
||||
|
||||
NS_IMPL_ADDREF(nsDOMBlobBuilder)
|
||||
@ -271,7 +177,7 @@ NS_INTERFACE_MAP_BEGIN(nsDOMBlobBuilder)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
nsresult
|
||||
nsDOMBlobBuilder::AppendVoidPtr(void* aData, PRUint32 aLength)
|
||||
nsDOMBlobBuilder::AppendVoidPtr(const void* aData, PRUint32 aLength)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aData);
|
||||
|
||||
@ -319,6 +225,14 @@ nsDOMBlobBuilder::AppendArrayBuffer(JSObject* aBuffer)
|
||||
NS_IMETHODIMP
|
||||
nsDOMBlobBuilder::GetBlob(const nsAString& aContentType,
|
||||
nsIDOMBlob** aBlob)
|
||||
{
|
||||
return GetBlobInternal(aContentType, true, aBlob);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsDOMBlobBuilder::GetBlobInternal(const nsAString& aContentType,
|
||||
bool aClearBuffer,
|
||||
nsIDOMBlob** aBlob)
|
||||
{
|
||||
NS_ENSURE_ARG(aBlob);
|
||||
|
||||
@ -332,7 +246,9 @@ nsDOMBlobBuilder::GetBlob(const nsAString& aContentType,
|
||||
// the existing contents of the BlobBuilder should be included
|
||||
// in the next blob produced. This seems silly and has been raised
|
||||
// on the WHATWG listserv.
|
||||
if (aClearBuffer) {
|
||||
mBlobs.Clear();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
143
content/base/src/nsDOMBlobBuilder.h
Normal file
143
content/base/src/nsDOMBlobBuilder.h
Normal file
@ -0,0 +1,143 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla File API.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Kyle Huey <me@kylehuey.com>
|
||||
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#ifndef nsDOMBlobBuilder_h
|
||||
#define nsDOMBlobBuilder_h
|
||||
|
||||
#include "nsDOMFile.h"
|
||||
#include "CheckedInt.h"
|
||||
|
||||
#include "mozilla/StdInt.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
class nsDOMMultipartFile : public nsDOMFileBase
|
||||
{
|
||||
public:
|
||||
// Create as a file
|
||||
nsDOMMultipartFile(nsTArray<nsCOMPtr<nsIDOMBlob> > aBlobs,
|
||||
const nsAString& aName,
|
||||
const nsAString& aContentType)
|
||||
: nsDOMFileBase(aName, aContentType, UINT64_MAX),
|
||||
mBlobs(aBlobs)
|
||||
{
|
||||
}
|
||||
|
||||
// Create as a blob
|
||||
nsDOMMultipartFile(nsTArray<nsCOMPtr<nsIDOMBlob> > aBlobs,
|
||||
const nsAString& aContentType)
|
||||
: nsDOMFileBase(aContentType, UINT64_MAX),
|
||||
mBlobs(aBlobs)
|
||||
{
|
||||
}
|
||||
|
||||
already_AddRefed<nsIDOMBlob>
|
||||
CreateSlice(PRUint64 aStart, PRUint64 aLength, const nsAString& aContentType);
|
||||
|
||||
NS_IMETHOD GetSize(PRUint64*);
|
||||
NS_IMETHOD GetInternalStream(nsIInputStream**);
|
||||
|
||||
protected:
|
||||
nsTArray<nsCOMPtr<nsIDOMBlob> > mBlobs;
|
||||
};
|
||||
|
||||
class nsDOMBlobBuilder : public nsIDOMMozBlobBuilder
|
||||
{
|
||||
public:
|
||||
nsDOMBlobBuilder()
|
||||
: mData(nsnull), mDataLen(0), mDataBufferLen(0)
|
||||
{}
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIDOMMOZBLOBBUILDER
|
||||
|
||||
nsresult GetBlobInternal(const nsAString& aContentType,
|
||||
bool aClearBuffer, nsIDOMBlob** aBlob);
|
||||
nsresult AppendVoidPtr(const void* aData, PRUint32 aLength);
|
||||
|
||||
protected:
|
||||
nsresult AppendString(JSString* aString, JSContext* aCx);
|
||||
nsresult AppendBlob(nsIDOMBlob* aBlob);
|
||||
nsresult AppendArrayBuffer(JSObject* aBuffer);
|
||||
|
||||
bool ExpandBufferSize(PRUint64 aSize)
|
||||
{
|
||||
if (mDataBufferLen >= mDataLen + aSize) {
|
||||
mDataLen += aSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Start at 1 or we'll loop forever.
|
||||
CheckedUint32 bufferLen = NS_MAX<PRUint32>(mDataBufferLen, 1);
|
||||
while (bufferLen.valid() && bufferLen.value() < mDataLen + aSize)
|
||||
bufferLen *= 2;
|
||||
|
||||
if (!bufferLen.valid())
|
||||
return false;
|
||||
|
||||
// PR_ memory functions are still fallible
|
||||
void* data = PR_Realloc(mData, bufferLen.value());
|
||||
if (!data)
|
||||
return false;
|
||||
|
||||
mData = data;
|
||||
mDataBufferLen = bufferLen.value();
|
||||
mDataLen += aSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Flush() {
|
||||
if (mData) {
|
||||
// If we have some data, create a blob for it
|
||||
// and put it on the stack
|
||||
|
||||
nsCOMPtr<nsIDOMBlob> blob =
|
||||
new nsDOMMemoryFile(mData, mDataLen, EmptyString(), EmptyString());
|
||||
mBlobs.AppendElement(blob);
|
||||
mData = nsnull; // The nsDOMMemoryFile takes ownership of the buffer
|
||||
mDataLen = 0;
|
||||
mDataBufferLen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
nsTArray<nsCOMPtr<nsIDOMBlob> > mBlobs;
|
||||
void* mData;
|
||||
PRUint64 mDataLen;
|
||||
PRUint64 mDataBufferLen;
|
||||
};
|
||||
|
||||
#endif
|
@ -582,6 +582,8 @@ nsXMLHttpRequest::ResetResponse()
|
||||
mResponseBody.Truncate();
|
||||
mResponseText.Truncate();
|
||||
mResponseBlob = nsnull;
|
||||
mDOMFile = nsnull;
|
||||
mBuilder = nsnull;
|
||||
mResultArrayBuffer = nsnull;
|
||||
mResultJSON = JSVAL_VOID;
|
||||
mLoadTransferred = 0;
|
||||
@ -967,6 +969,28 @@ nsXMLHttpRequest::CreateResponseParsedJSON(JSContext* aCx)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsXMLHttpRequest::CreatePartialBlob()
|
||||
{
|
||||
if (mDOMFile) {
|
||||
if (mLoadTotal == mLoadTransferred) {
|
||||
mResponseBlob = mDOMFile;
|
||||
} else {
|
||||
mResponseBlob =
|
||||
mDOMFile->CreateSlice(0, mLoadTransferred, EmptyString());
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCAutoString contentType;
|
||||
if (mLoadTotal == mLoadTransferred) {
|
||||
mChannel->GetContentType(contentType);
|
||||
}
|
||||
|
||||
return mBuilder->GetBlobInternal(NS_ConvertASCIItoUTF16(contentType),
|
||||
false, getter_AddRefs(mResponseBlob));
|
||||
}
|
||||
|
||||
/* attribute AString responseType; */
|
||||
NS_IMETHODIMP nsXMLHttpRequest::GetResponseType(nsAString& aResponseType)
|
||||
{
|
||||
@ -995,6 +1019,9 @@ NS_IMETHODIMP nsXMLHttpRequest::GetResponseType(nsAString& aResponseType)
|
||||
case XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER:
|
||||
aResponseType.AssignLiteral("moz-chunked-arraybuffer");
|
||||
break;
|
||||
case XML_HTTP_RESPONSE_TYPE_MOZ_BLOB:
|
||||
aResponseType.AssignLiteral("moz-blob");
|
||||
break;
|
||||
default:
|
||||
NS_ERROR("Should not happen");
|
||||
}
|
||||
@ -1041,6 +1068,8 @@ NS_IMETHODIMP nsXMLHttpRequest::SetResponseType(const nsAString& aResponseType)
|
||||
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
||||
}
|
||||
mResponseType = XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER;
|
||||
} else if (aResponseType.EqualsLiteral("moz-blob")) {
|
||||
mResponseType = XML_HTTP_RESPONSE_TYPE_MOZ_BLOB;
|
||||
}
|
||||
// If the given value is not the empty string, "arraybuffer",
|
||||
// "blob", "document", or "text" terminate these steps.
|
||||
@ -1053,7 +1082,8 @@ NS_IMETHODIMP nsXMLHttpRequest::SetResponseType(const nsAString& aResponseType)
|
||||
if (mState & XML_HTTP_REQUEST_HEADERS_RECEIVED) {
|
||||
nsCOMPtr<nsICachingChannel> cc(do_QueryInterface(mChannel));
|
||||
if (cc) {
|
||||
cc->SetCacheAsFile(mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB);
|
||||
cc->SetCacheAsFile(mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB ||
|
||||
mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1097,12 +1127,22 @@ NS_IMETHODIMP nsXMLHttpRequest::GetResponse(JSContext *aCx, jsval *aResult)
|
||||
break;
|
||||
|
||||
case XML_HTTP_RESPONSE_TYPE_BLOB:
|
||||
if (mState & XML_HTTP_REQUEST_DONE && mResponseBlob) {
|
||||
case XML_HTTP_RESPONSE_TYPE_MOZ_BLOB:
|
||||
*aResult = JSVAL_NULL;
|
||||
if (mState & XML_HTTP_REQUEST_DONE) {
|
||||
// do nothing here
|
||||
} else if (mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) {
|
||||
if (!mResponseBlob) {
|
||||
rv = CreatePartialBlob();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
} else {
|
||||
return rv;
|
||||
}
|
||||
if (mResponseBlob) {
|
||||
JSObject* scope = JS_GetGlobalForScopeChain(aCx);
|
||||
rv = nsContentUtils::WrapNative(aCx, scope, mResponseBlob, aResult,
|
||||
nsnull, true);
|
||||
} else {
|
||||
*aResult = JSVAL_NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
@ -1712,16 +1752,29 @@ nsXMLHttpRequest::StreamReaderFunc(nsIInputStream* in,
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB &&
|
||||
xmlHttpRequest->mResponseBlob) {
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB ||
|
||||
xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) {
|
||||
if (!xmlHttpRequest->mDOMFile) {
|
||||
if (!xmlHttpRequest->mBuilder) {
|
||||
xmlHttpRequest->mBuilder = new nsDOMBlobBuilder();
|
||||
}
|
||||
rv = xmlHttpRequest->mBuilder->AppendVoidPtr(fromRawSegment, count);
|
||||
}
|
||||
// Clear the cache so that the blob size is updated.
|
||||
if (xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) {
|
||||
xmlHttpRequest->mResponseBlob = nsnull;
|
||||
}
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
*writeCount = count;
|
||||
return NS_OK;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
if ((xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_DEFAULT &&
|
||||
xmlHttpRequest->mResponseXML) ||
|
||||
xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER ||
|
||||
xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB ||
|
||||
xmlHttpRequest->mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER) {
|
||||
// Copy for our own use
|
||||
PRUint32 previousLength = xmlHttpRequest->mResponseBody.Length();
|
||||
@ -1738,8 +1791,6 @@ nsXMLHttpRequest::StreamReaderFunc(nsIInputStream* in,
|
||||
xmlHttpRequest->AppendToResponseText(fromRawSegment, count);
|
||||
}
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
if (xmlHttpRequest->mState & XML_HTTP_REQUEST_PARSEBODY) {
|
||||
// Give the same data to the parser.
|
||||
|
||||
@ -1773,7 +1824,7 @@ nsXMLHttpRequest::StreamReaderFunc(nsIInputStream* in,
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool nsXMLHttpRequest::CreateResponseBlob(nsIRequest *request)
|
||||
bool nsXMLHttpRequest::CreateDOMFile(nsIRequest *request)
|
||||
{
|
||||
nsCOMPtr<nsIFile> file;
|
||||
nsCOMPtr<nsICachingChannel> cc(do_QueryInterface(request));
|
||||
@ -1801,9 +1852,10 @@ bool nsXMLHttpRequest::CreateResponseBlob(nsIRequest *request)
|
||||
fromFile = true;
|
||||
}
|
||||
|
||||
mResponseBlob =
|
||||
mDOMFile =
|
||||
new nsDOMFileFile(file, NS_ConvertASCIItoUTF16(contentType), cacheToken);
|
||||
mResponseBody.Truncate();
|
||||
mBuilder = nsnull;
|
||||
NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
|
||||
}
|
||||
return fromFile;
|
||||
}
|
||||
@ -1822,8 +1874,9 @@ nsXMLHttpRequest::OnDataAvailable(nsIRequest *request,
|
||||
mProgressSinceLastProgressEvent = true;
|
||||
|
||||
bool cancelable = false;
|
||||
if (mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB && !mResponseBlob) {
|
||||
cancelable = CreateResponseBlob(request);
|
||||
if ((mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB ||
|
||||
mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) && !mDOMFile) {
|
||||
cancelable = CreateDOMFile(request);
|
||||
// The nsIStreamListener contract mandates us
|
||||
// to read from the stream before returning.
|
||||
}
|
||||
@ -1835,7 +1888,7 @@ nsXMLHttpRequest::OnDataAvailable(nsIRequest *request,
|
||||
|
||||
if (cancelable) {
|
||||
// We don't have to read from the local file for the blob response
|
||||
mResponseBlob->GetSize(&mLoadTransferred);
|
||||
mDOMFile->GetSize(&mLoadTransferred);
|
||||
ChangeState(XML_HTTP_REQUEST_LOADING);
|
||||
return request->Cancel(NS_OK);
|
||||
}
|
||||
@ -1936,7 +1989,8 @@ nsXMLHttpRequest::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
|
||||
mState &= ~XML_HTTP_REQUEST_MPART_HEADERS;
|
||||
ChangeState(XML_HTTP_REQUEST_HEADERS_RECEIVED);
|
||||
|
||||
if (mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB) {
|
||||
if (mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB ||
|
||||
mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB) {
|
||||
nsCOMPtr<nsICachingChannel> cc(do_QueryInterface(mChannel));
|
||||
if (cc) {
|
||||
cc->SetCacheAsFile(true);
|
||||
@ -2132,33 +2186,30 @@ nsXMLHttpRequest::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult
|
||||
MaybeDispatchProgressEvents(true);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
|
||||
NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
|
||||
|
||||
if (NS_SUCCEEDED(status) && mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB) {
|
||||
if (!mResponseBlob) {
|
||||
CreateResponseBlob(request);
|
||||
if (NS_SUCCEEDED(status) &&
|
||||
(mResponseType == XML_HTTP_RESPONSE_TYPE_BLOB ||
|
||||
mResponseType == XML_HTTP_RESPONSE_TYPE_MOZ_BLOB)) {
|
||||
if (!mDOMFile) {
|
||||
CreateDOMFile(request);
|
||||
}
|
||||
if (!mResponseBlob) {
|
||||
if (mDOMFile) {
|
||||
mResponseBlob = mDOMFile;
|
||||
mDOMFile = nsnull;
|
||||
} else {
|
||||
// Smaller files may be written in cache map instead of separate files.
|
||||
// Also, no-store response cannot be written in persistent cache.
|
||||
nsCAutoString contentType;
|
||||
mChannel->GetContentType(contentType);
|
||||
// XXX We should change mResponseBody to be a raw malloc'ed buffer
|
||||
// to avoid copying the data.
|
||||
PRUint32 blobLen = mResponseBody.Length();
|
||||
void *blobData = PR_Malloc(blobLen);
|
||||
if (blobData) {
|
||||
memcpy(blobData, mResponseBody.BeginReading(), blobLen);
|
||||
|
||||
mResponseBlob =
|
||||
new nsDOMMemoryFile(blobData, blobLen,
|
||||
NS_ConvertASCIItoUTF16(contentType));
|
||||
mResponseBody.Truncate();
|
||||
mBuilder->GetBlobInternal(NS_ConvertASCIItoUTF16(contentType),
|
||||
false, getter_AddRefs(mResponseBlob));
|
||||
mBuilder = nsnull;
|
||||
}
|
||||
NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
|
||||
NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
|
||||
NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
|
||||
|
||||
channel->SetNotificationCallbacks(nsnull);
|
||||
mNotificationCallbacks = nsnull;
|
||||
|
@ -65,6 +65,8 @@
|
||||
#include "nsDOMProgressEvent.h"
|
||||
#include "nsDOMEventTargetWrapperCache.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsDOMFile.h"
|
||||
#include "nsDOMBlobBuilder.h"
|
||||
|
||||
class nsILoadGroup;
|
||||
class AsyncVerifyRedirectCallbackForwarder;
|
||||
@ -217,7 +219,8 @@ protected:
|
||||
PRUint32 count,
|
||||
PRUint32 *writeCount);
|
||||
nsresult CreateResponseParsedJSON(JSContext* aCx);
|
||||
bool CreateResponseBlob(nsIRequest *request);
|
||||
nsresult CreatePartialBlob(void);
|
||||
bool CreateDOMFile(nsIRequest *request);
|
||||
// Change the state of the object with this. The broadcast argument
|
||||
// determines if the onreadystatechange listener should be called.
|
||||
nsresult ChangeState(PRUint32 aState, bool aBroadcast = true);
|
||||
@ -309,10 +312,20 @@ protected:
|
||||
XML_HTTP_RESPONSE_TYPE_TEXT,
|
||||
XML_HTTP_RESPONSE_TYPE_JSON,
|
||||
XML_HTTP_RESPONSE_TYPE_CHUNKED_TEXT,
|
||||
XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER
|
||||
XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER,
|
||||
XML_HTTP_RESPONSE_TYPE_MOZ_BLOB
|
||||
} mResponseType;
|
||||
|
||||
// It is either a cached blob-response from the last call to GetResponse,
|
||||
// but is also explicitly set in OnStopRequest.
|
||||
nsCOMPtr<nsIDOMBlob> mResponseBlob;
|
||||
// Non-null only when we are able to get a os-file representation of the
|
||||
// response, i.e. when loading from a file, or when the http-stream
|
||||
// caches into a file or is reading from a cached file.
|
||||
nsRefPtr<nsDOMFileBase> mDOMFile;
|
||||
// We stream data to mBuilder when response type is "blob" or "moz-blob"
|
||||
// and mDOMFile is null.
|
||||
nsRefPtr<nsDOMBlobBuilder> mBuilder;
|
||||
|
||||
nsCString mOverrideMimeType;
|
||||
|
||||
|
@ -212,13 +212,16 @@ is(xhr.response, null, "Bad JSON should result in null response even 2nd time.")
|
||||
// test response (responseType='blob')
|
||||
var onloadCount = 0;
|
||||
function checkOnloadCount() {
|
||||
if (++onloadCount >= 3) SimpleTest.finish();
|
||||
if (++onloadCount >= 6) SimpleTest.finish();
|
||||
};
|
||||
|
||||
var responseTypes = ['blob', 'moz-blob'];
|
||||
for (var i = 0; i < responseTypes.length; i++) {
|
||||
var t = responseTypes[i];
|
||||
// with a simple text file
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", 'file_XHR_pass2.txt');
|
||||
xhr.responseType = 'blob';
|
||||
xhr.responseType = t;
|
||||
xhr.onloadend = continueTest;
|
||||
xhr.send(null);
|
||||
yield;
|
||||
@ -245,7 +248,7 @@ xhr.onreadystatechange = function() {
|
||||
switch (xhr.readyState) {
|
||||
case 2:
|
||||
is(xhr.status, 200, "wrong status");
|
||||
xhr.responseType = 'blob';
|
||||
xhr.responseType = t;
|
||||
break;
|
||||
case 4:
|
||||
b = xhr.response;
|
||||
@ -293,9 +296,10 @@ xhr.onreadystatechange = function() {
|
||||
}
|
||||
};
|
||||
xhr.open("GET", 'file_XHR_binary2.bin', true);
|
||||
xhr.responseType = 'blob';
|
||||
xhr.responseType = t;
|
||||
xhr.send(null);
|
||||
})();
|
||||
}
|
||||
|
||||
var client = new XMLHttpRequest();
|
||||
client.onreadystatechange = function() {
|
||||
|
@ -39,13 +39,21 @@ function updateProgress(e, data, testName) {
|
||||
is(typeof e.target.response, "string", "response should be a string" + test);
|
||||
response = e.target.response;
|
||||
}
|
||||
else if (data.blob) {
|
||||
ok(e.target.response instanceof Blob, "response should be a Blob" + test);
|
||||
response = e.target.response;
|
||||
}
|
||||
else {
|
||||
ok(e.target.response instanceof ArrayBuffer, "response should be a ArrayBuffer" + test);
|
||||
ok(e.target.response instanceof ArrayBuffer, "response should be an ArrayBuffer" + test);
|
||||
response = bufferToString(e.target.response);
|
||||
}
|
||||
is(e.target.response, e.target.response, "reflexivity should hold" + test);
|
||||
|
||||
if (!data.nodata && !data.encoded) {
|
||||
if (!data.chunked) {
|
||||
if (data.blob) {
|
||||
is(e.loaded, response.size, "event.loaded matches response size" + test);
|
||||
}
|
||||
else if (!data.chunked) {
|
||||
is(e.loaded, response.length, "event.loaded matches response size" + test);
|
||||
}
|
||||
else {
|
||||
@ -57,7 +65,7 @@ function updateProgress(e, data, testName) {
|
||||
ok(e.loaded - data.receivedBytes <= data.pendingBytes,
|
||||
"event.loaded didn't increase too much" + test);
|
||||
|
||||
if (!data.nodata) {
|
||||
if (!data.nodata && !data.blob) {
|
||||
var newData;
|
||||
ok(startsWith(response, data.receivedResult),
|
||||
"response strictly grew" + test);
|
||||
@ -74,7 +82,7 @@ function updateProgress(e, data, testName) {
|
||||
is(e.total, data.total, "total" + test);
|
||||
}
|
||||
|
||||
if (!data.nodata) {
|
||||
if (!data.nodata && !data.blob) {
|
||||
data.pendingResult = data.pendingResult.substr(newData.length);
|
||||
}
|
||||
data.pendingBytes -= e.loaded - data.receivedBytes;
|
||||
@ -113,7 +121,8 @@ function runTests() {
|
||||
|
||||
var responseTypes = [{ type: "text", text: true },
|
||||
{ type: "arraybuffer", text: false, nodata: true },
|
||||
{ type: "blob", text: false, nodata: true },
|
||||
{ type: "blob", text: false, nodata: true, blob: true },
|
||||
{ type: "moz-blob", text: false, nodata: false, blob: true },
|
||||
{ type: "document", text: true, nodata: true },
|
||||
{ type: "json", text: true, nodata: true },
|
||||
{ type: "", text: true },
|
||||
@ -153,7 +162,7 @@ function runTests() {
|
||||
{ data: utf8encode("a\u867Eb").substr(4), utf16: "b" },
|
||||
{ close: true },
|
||||
];
|
||||
if (responseType.type === "blob") {
|
||||
if (responseType.blob) {
|
||||
tests.push({ file: "file_XHR_binary2.bin", name: "cacheable data", total: 65536 },
|
||||
{ close: true },
|
||||
{ file: "file_XHR_binary2.bin", name: "cached data", total: 65536 },
|
||||
@ -177,6 +186,7 @@ function runTests() {
|
||||
nodata: responseType.nodata,
|
||||
chunked: responseType.chunked,
|
||||
text: responseType.text,
|
||||
blob: responseType.blob,
|
||||
file: test.file };
|
||||
|
||||
xhr.onreadystatechange = null;
|
||||
@ -235,7 +245,7 @@ function runTests() {
|
||||
is(xhr.response, null, "chunked data has null response for " + testState.name);
|
||||
}
|
||||
|
||||
if (!testState.nodata || responseType.chunked) {
|
||||
if (!testState.nodata && !responseType.blob || responseType.chunked) {
|
||||
// This branch intentionally left blank
|
||||
// Under these conditions we check the response during updateProgress
|
||||
}
|
||||
@ -243,7 +253,7 @@ function runTests() {
|
||||
is(bufferToString(xhr.response), testState.pendingResult,
|
||||
"full response for " + testState.name);
|
||||
}
|
||||
else if (responseType.type === "blob") {
|
||||
else if (responseType.blob) {
|
||||
let reader = new FileReader;
|
||||
reader.readAsBinaryString(xhr.response);
|
||||
reader.onloadend = getEvent;
|
||||
@ -280,7 +290,7 @@ function runTests() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!testState.nodata) {
|
||||
if (!testState.nodata && !testState.blob) {
|
||||
is(testState.pendingResult, "",
|
||||
"should have consumed the expected result");
|
||||
}
|
||||
|
@ -127,6 +127,7 @@ _TEST_FILES = \
|
||||
test_loop.html \
|
||||
test_media_selection.html \
|
||||
test_mozLoadFrom.html \
|
||||
test_no_load_event.html \
|
||||
test_networkState.html \
|
||||
test_new_audio.html \
|
||||
test_paused.html \
|
||||
|
59
content/media/test/test_no_load_event.html
Normal file
59
content/media/test/test_no_load_event.html
Normal file
@ -0,0 +1,59 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=715469
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 715469</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="text/javascript" src="manifest.js"></script>
|
||||
</head>
|
||||
<body onload="start();">
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=715469">Mozilla Bug 715469</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
|
||||
<script type="application/javascript">
|
||||
|
||||
/** Test for Bug 715469 **/
|
||||
|
||||
var gotLoadEvent = false;
|
||||
|
||||
function start() {
|
||||
var resource = getPlayableVideo(gSmallTests);
|
||||
if (resource == null) {
|
||||
todo(false, "No types supported");
|
||||
} else {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
var v = document.createElement("video");
|
||||
v.src = resource.name;
|
||||
v.addEventListener("loadeddata", function(){v.play();}, false);
|
||||
v.controls = "true";
|
||||
|
||||
v.addEventListener("load",
|
||||
function(){
|
||||
gotLoadEvent = true;
|
||||
},
|
||||
false);
|
||||
|
||||
v.addEventListener("ended", finished, false);
|
||||
|
||||
document.body.appendChild(v);
|
||||
}
|
||||
}
|
||||
|
||||
function finished() {
|
||||
is(gotLoadEvent, false, "Should not receive a load on the video element");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -953,6 +953,7 @@ RasterImage::GetImageContainer(LayerManager* aManager,
|
||||
(!mImageContainer->Manager() &&
|
||||
(mImageContainer->GetBackendType() == aManager->GetBackendType())))) {
|
||||
*_retval = mImageContainer;
|
||||
NS_ADDREF(*_retval);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -978,6 +979,7 @@ RasterImage::GetImageContainer(LayerManager* aManager,
|
||||
mImageContainer->SetCurrentImage(image);
|
||||
|
||||
*_retval = mImageContainer;
|
||||
NS_ADDREF(*_retval);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -219,7 +219,7 @@ protected:
|
||||
* supports being optimized to an ImageLayer (TYPE_RASTER only) returns
|
||||
* an ImageContainer for the image.
|
||||
*/
|
||||
nsRefPtr<ImageContainer> CanOptimizeImageLayer(LayerManager* aManager);
|
||||
already_AddRefed<ImageContainer> CanOptimizeImageLayer(LayerManager* aManager);
|
||||
|
||||
/**
|
||||
* The region of visible content in the layer, relative to the
|
||||
@ -968,7 +968,7 @@ ContainerState::FindOpaqueBackgroundColorFor(PRInt32 aThebesLayerIndex)
|
||||
return NS_RGBA(0,0,0,0);
|
||||
}
|
||||
|
||||
nsRefPtr<ImageContainer>
|
||||
already_AddRefed<ImageContainer>
|
||||
ContainerState::ThebesLayerData::CanOptimizeImageLayer(LayerManager* aManager)
|
||||
{
|
||||
if (!mImage || !mImageClip.mRoundedClipRects.IsEmpty()) {
|
||||
|
@ -1210,19 +1210,13 @@ nsDisplayImage::Paint(nsDisplayListBuilder* aBuilder,
|
||||
: (PRUint32) imgIContainer::FLAG_NONE);
|
||||
}
|
||||
|
||||
nsCOMPtr<imgIContainer>
|
||||
nsDisplayImage::GetImage()
|
||||
{
|
||||
return mImage;
|
||||
}
|
||||
|
||||
nsRefPtr<ImageContainer>
|
||||
already_AddRefed<ImageContainer>
|
||||
nsDisplayImage::GetContainer(LayerManager* aManager)
|
||||
{
|
||||
ImageContainer* container;
|
||||
nsresult rv = mImage->GetImageContainer(aManager, &container);
|
||||
NS_ENSURE_SUCCESS(rv, NULL);
|
||||
return container;
|
||||
nsRefPtr<ImageContainer> container;
|
||||
nsresult rv = mImage->GetImageContainer(aManager, getter_AddRefs(container));
|
||||
NS_ENSURE_SUCCESS(rv, nsnull);
|
||||
return container.forget();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -386,13 +386,12 @@ public:
|
||||
}
|
||||
virtual void Paint(nsDisplayListBuilder* aBuilder,
|
||||
nsRenderingContext* aCtx);
|
||||
nsCOMPtr<imgIContainer> GetImage();
|
||||
|
||||
/**
|
||||
* Returns an ImageContainer for this image if the image type
|
||||
* supports it (TYPE_RASTER only).
|
||||
*/
|
||||
nsRefPtr<ImageContainer> GetContainer(LayerManager* aManager);
|
||||
already_AddRefed<ImageContainer> GetContainer(LayerManager* aManager);
|
||||
|
||||
/**
|
||||
* Configure an ImageLayer for this display item.
|
||||
|
@ -73,7 +73,7 @@ body.non-verbose pre.tree {
|
||||
color: #004;
|
||||
}
|
||||
|
||||
.mrStar {
|
||||
.mrNote {
|
||||
color: #604;
|
||||
}
|
||||
|
||||
@ -98,3 +98,9 @@ body.non-verbose pre.tree {
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
color: #fff;
|
||||
background-color: #f00;
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,30 @@ const UNITS_PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;
|
||||
|
||||
const kUnknown = -1; // used for _amount if a memory reporter failed
|
||||
|
||||
const kTreeDescriptions = {
|
||||
// Paths, names and descriptions all need to be sanitized before being
|
||||
// displayed, because the user has some control over them via names of
|
||||
// compartments, windows, etc. Also, forward slashes in URLs in paths are
|
||||
// represented with backslashes to avoid being mistaken for path separators, so
|
||||
// we need to undo that as well.
|
||||
|
||||
function escapeAll(aStr)
|
||||
{
|
||||
return aStr.replace(/\&/g, '&').replace(/'/g, ''').
|
||||
replace(/\</g, '<').replace(/>/g, '>').
|
||||
replace(/\"/g, '"');
|
||||
}
|
||||
|
||||
function flipBackslashes(aStr)
|
||||
{
|
||||
return aStr.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
function makeSafe(aUnsafeStr)
|
||||
{
|
||||
return escapeAll(flipBackslashes(aUnsafeStr));
|
||||
}
|
||||
|
||||
const kTreeUnsafeDescriptions = {
|
||||
'explicit' :
|
||||
"This tree covers explicit memory allocations by the application, " +
|
||||
"both at the operating system level (via calls to functions such as " +
|
||||
@ -199,13 +222,13 @@ function sendHeapMinNotifications()
|
||||
sendHeapMinNotificationsInner();
|
||||
}
|
||||
|
||||
function Reporter(aPath, aKind, aUnits, aAmount, aDescription)
|
||||
function Reporter(aUnsafePath, aKind, aUnits, aAmount, aUnsafeDesc)
|
||||
{
|
||||
this._path = aPath;
|
||||
this._unsafePath = aUnsafePath;
|
||||
this._kind = aKind;
|
||||
this._units = aUnits;
|
||||
this._amount = aAmount;
|
||||
this._description = aDescription;
|
||||
this._unsafeDescription = aUnsafeDesc;
|
||||
// this._nMerged is only defined if > 1
|
||||
// this._done is defined when getBytes is called
|
||||
}
|
||||
@ -228,7 +251,7 @@ Reporter.prototype = {
|
||||
// Nb: the '/' must be present, because we have a KIND_OTHER reporter
|
||||
// called "explicit" which is not part of the "explicit" tree.
|
||||
aTreeName += "/";
|
||||
return this._path.slice(0, aTreeName.length) === aTreeName;
|
||||
return this._unsafePath.slice(0, aTreeName.length) === aTreeName;
|
||||
}
|
||||
};
|
||||
|
||||
@ -246,21 +269,22 @@ function getReportersByProcess(aMgr)
|
||||
// allocated.
|
||||
var reportersByProcess = {};
|
||||
|
||||
function addReporter(aProcess, aPath, aKind, aUnits, aAmount, aDescription)
|
||||
function addReporter(aProcess, aUnsafePath, aKind, aUnits, aAmount,
|
||||
aUnsafeDesc)
|
||||
{
|
||||
var process = aProcess === "" ? "Main" : aProcess;
|
||||
var r = new Reporter(aPath, aKind, aUnits, aAmount, aDescription);
|
||||
var r = new Reporter(aUnsafePath, aKind, aUnits, aAmount, aUnsafeDesc);
|
||||
if (!reportersByProcess[process]) {
|
||||
reportersByProcess[process] = {};
|
||||
}
|
||||
var reporters = reportersByProcess[process];
|
||||
var reporter = reporters[r._path];
|
||||
var reporter = reporters[r._unsafePath];
|
||||
if (reporter) {
|
||||
// Already an entry; must be a duplicated reporter. This can happen
|
||||
// legitimately. Merge them.
|
||||
reporter.merge(r);
|
||||
} else {
|
||||
reporters[r._path] = r;
|
||||
reporters[r._unsafePath] = r;
|
||||
}
|
||||
}
|
||||
|
||||
@ -371,28 +395,28 @@ function update()
|
||||
// - Leaf TreeNodes correspond to Reporters and have more properties.
|
||||
// - Non-leaf TreeNodes are just scaffolding nodes for the tree; their values
|
||||
// are derived from their children.
|
||||
function TreeNode(aName)
|
||||
function TreeNode(aUnsafeName)
|
||||
{
|
||||
// Nb: _units is not needed, it's always UNITS_BYTES.
|
||||
this._name = aName;
|
||||
this._unsafeName = aUnsafeName;
|
||||
this._kids = [];
|
||||
// All TreeNodes have these properties added later:
|
||||
// - _amount (which is never |kUnknown|)
|
||||
// - _description
|
||||
// - _unsafeDescription
|
||||
//
|
||||
// Leaf TreeNodes have these properties added later:
|
||||
// - _kind
|
||||
// - _nMerged (if > 1)
|
||||
// - _hasProblem (only defined if true)
|
||||
// - _isUnknown (only defined if true)
|
||||
//
|
||||
// Non-leaf TreeNodes have these properties added later:
|
||||
// - _hideKids (only defined if true)
|
||||
}
|
||||
|
||||
TreeNode.prototype = {
|
||||
findKid: function(aName) {
|
||||
findKid: function(aUnsafeName) {
|
||||
for (var i = 0; i < this._kids.length; i++) {
|
||||
if (this._kids[i]._name === aName) {
|
||||
if (this._kids[i]._unsafeName === aUnsafeName) {
|
||||
return this._kids[i];
|
||||
}
|
||||
}
|
||||
@ -413,7 +437,7 @@ TreeNode.compare = function(a, b) {
|
||||
* structure that will be shown as output.
|
||||
*
|
||||
* @param aReporters
|
||||
* The table of Reporters, indexed by path.
|
||||
* The table of Reporters, indexed by _unsafePath.
|
||||
* @param aTreeName
|
||||
* The name of the tree being built.
|
||||
* @return The built tree.
|
||||
@ -428,8 +452,8 @@ function buildTree(aReporters, aTreeName)
|
||||
// "explicit". But there may be zero for "map" trees; if that happens,
|
||||
// bail.
|
||||
var foundReporter = false;
|
||||
for (var path in aReporters) {
|
||||
if (aReporters[path].treeNameMatches(aTreeName)) {
|
||||
for (var unsafePath in aReporters) {
|
||||
if (aReporters[unsafePath].treeNameMatches(aTreeName)) {
|
||||
foundReporter = true;
|
||||
break;
|
||||
}
|
||||
@ -440,22 +464,22 @@ function buildTree(aReporters, aTreeName)
|
||||
}
|
||||
|
||||
var t = new TreeNode("falseRoot");
|
||||
for (var path in aReporters) {
|
||||
// Add any missing nodes in the tree implied by the path.
|
||||
var r = aReporters[path];
|
||||
for (var unsafePath in aReporters) {
|
||||
// Add any missing nodes in the tree implied by the unsafePath.
|
||||
var r = aReporters[unsafePath];
|
||||
if (r.treeNameMatches(aTreeName)) {
|
||||
assert(r._kind === KIND_HEAP || r._kind === KIND_NONHEAP,
|
||||
"reporters in the tree must have KIND_HEAP or KIND_NONHEAP");
|
||||
assert(r._units === UNITS_BYTES, "r._units === UNITS_BYTES");
|
||||
var names = r._path.split('/');
|
||||
var unsafeNames = r._unsafePath.split('/');
|
||||
var u = t;
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
var name = names[i];
|
||||
var uMatch = u.findKid(name);
|
||||
for (var i = 0; i < unsafeNames.length; i++) {
|
||||
var unsafeName = unsafeNames[i];
|
||||
var uMatch = u.findKid(unsafeName);
|
||||
if (uMatch) {
|
||||
u = uMatch;
|
||||
} else {
|
||||
var v = new TreeNode(name);
|
||||
var v = new TreeNode(unsafeName);
|
||||
u._kids.push(v);
|
||||
u = v;
|
||||
}
|
||||
@ -474,19 +498,20 @@ function buildTree(aReporters, aTreeName)
|
||||
|
||||
// Next, fill in the remaining properties bottom-up.
|
||||
// Note that this function never returns kUnknown.
|
||||
function fillInTree(aT, aPrepath)
|
||||
function fillInTree(aT, aUnsafePrePath)
|
||||
{
|
||||
var path = aPrepath ? aPrepath + '/' + aT._name : aT._name;
|
||||
var unsafePath =
|
||||
aUnsafePrePath ? aUnsafePrePath + '/' + aT._unsafeName : aT._unsafeName;
|
||||
if (aT._kids.length === 0) {
|
||||
// Leaf node. Must have a reporter.
|
||||
assert(aT._kind !== undefined, "aT._kind is undefined for leaf node");
|
||||
aT._description = getDescription(aReporters, path);
|
||||
var amount = getBytes(aReporters, path);
|
||||
aT._unsafeDescription = getUnsafeDescription(aReporters, unsafePath);
|
||||
var amount = getBytes(aReporters, unsafePath);
|
||||
if (amount !== kUnknown) {
|
||||
aT._amount = amount;
|
||||
} else {
|
||||
aT._amount = 0;
|
||||
aT._hasProblem = true;
|
||||
aT._isUnknown = true;
|
||||
}
|
||||
} else {
|
||||
// Non-leaf node. Derive its size and description entirely from its
|
||||
@ -494,10 +519,11 @@ function buildTree(aReporters, aTreeName)
|
||||
assert(aT._kind === undefined, "aT._kind is defined for non-leaf node");
|
||||
var childrenBytes = 0;
|
||||
for (var i = 0; i < aT._kids.length; i++) {
|
||||
childrenBytes += fillInTree(aT._kids[i], path);
|
||||
childrenBytes += fillInTree(aT._kids[i], unsafePath);
|
||||
}
|
||||
aT._amount = childrenBytes;
|
||||
aT._description = "The sum of all entries below '" + aT._name + "'.";
|
||||
aT._unsafeDescription =
|
||||
"The sum of all entries below '" + aT._unsafeName + "'.";
|
||||
}
|
||||
assert(aT._amount !== kUnknown, "aT._amount !== kUnknown");
|
||||
return aT._amount;
|
||||
@ -515,8 +541,8 @@ function buildTree(aReporters, aTreeName)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the description on the root node.
|
||||
t._description = kTreeDescriptions[t._name];
|
||||
// Set the (unsafe) description on the root node.
|
||||
t._unsafeDescription = kTreeUnsafeDescriptions[t._unsafeName];
|
||||
|
||||
return t;
|
||||
}
|
||||
@ -526,16 +552,16 @@ function buildTree(aReporters, aTreeName)
|
||||
* explicitly marking them as done.
|
||||
*
|
||||
* @param aReporters
|
||||
* The table of Reporters, indexed by path.
|
||||
* The table of Reporters, indexed by _unsafePath.
|
||||
* @param aTreeName
|
||||
* The name of the tree being built.
|
||||
*/
|
||||
function ignoreTree(aReporters, aTreeName)
|
||||
{
|
||||
for (var path in aReporters) {
|
||||
var r = aReporters[path];
|
||||
for (var unsafePath in aReporters) {
|
||||
var r = aReporters[unsafePath];
|
||||
if (r.treeNameMatches(aTreeName)) {
|
||||
var dummy = getBytes(aReporters, path);
|
||||
var dummy = getBytes(aReporters, unsafePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -546,7 +572,7 @@ function ignoreTree(aReporters, aTreeName)
|
||||
* @param aT
|
||||
* The tree.
|
||||
* @param aReporters
|
||||
* Table of Reporters for this process, indexed by _path.
|
||||
* Table of Reporters for this process, indexed by _unsafePath.
|
||||
* @return A boolean indicating if "heap-allocated" is known for the process.
|
||||
*/
|
||||
function fixUpExplicitTree(aT, aReporters)
|
||||
@ -579,15 +605,14 @@ function fixUpExplicitTree(aT, aReporters)
|
||||
heapAllocatedBytes - getKnownHeapUsedBytes(aT);
|
||||
} else {
|
||||
heapUnclassifiedT._amount = 0;
|
||||
heapUnclassifiedT._hasProblem = true;
|
||||
heapUnclassifiedT._isUnknown = true;
|
||||
}
|
||||
// This kindToString() ensures the "(Heap)" prefix is set without having to
|
||||
// set the _kind property, which would mean that there is a corresponding
|
||||
// Reporter for this TreeNode (which isn't true).
|
||||
heapUnclassifiedT._description =
|
||||
kindToString(KIND_HEAP) +
|
||||
// Reporter for this TreeNode (which isn't true)
|
||||
heapUnclassifiedT._unsafeDescription = kindToString(KIND_HEAP) +
|
||||
"Memory not classified by a more specific reporter. This includes " +
|
||||
"slop bytes due to internal fragmentation in the heap allocator "
|
||||
"slop bytes due to internal fragmentation in the heap allocator " +
|
||||
"(caused when the allocator rounds up request sizes).";
|
||||
|
||||
aT._kids.push(heapUnclassifiedT);
|
||||
@ -649,7 +674,7 @@ function sortTreeAndInsertAggregateNodes(aTotalBytes, aT)
|
||||
}
|
||||
aggT._hideKids = true;
|
||||
aggT._amount = aggBytes;
|
||||
aggT._description =
|
||||
aggT._unsafeDescription =
|
||||
nAgg + " sub-trees that are below the " + kSignificanceThresholdPerc +
|
||||
"% significance threshold.";
|
||||
aT._kids.splice(i0, nAgg, aggT);
|
||||
@ -671,6 +696,11 @@ function sortTreeAndInsertAggregateNodes(aTotalBytes, aT)
|
||||
sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
|
||||
}
|
||||
|
||||
// Global variable indicating if we've seen any invalid values for this
|
||||
// process; it holds the unsafePaths of any such reporters. It is reset for
|
||||
// each new process.
|
||||
var gUnsafePathsWithInvalidValuesForThisProcess = [];
|
||||
|
||||
function genWarningText(aHasKnownHeapAllocated, aHasMozMallocUsableSize)
|
||||
{
|
||||
var warningText = "";
|
||||
@ -698,6 +728,31 @@ function genWarningText(aHasKnownHeapAllocated, aHasMozMallocUsableSize)
|
||||
"by individual memory reporters and so will fall under " +
|
||||
"'heap-unclassified'.</p>\n\n";
|
||||
}
|
||||
|
||||
if (gUnsafePathsWithInvalidValuesForThisProcess.length > 0) {
|
||||
warningText +=
|
||||
"<div class='accuracyWarning'>" +
|
||||
"<p>WARNING: the following values are negative or unreasonably " +
|
||||
"large.</p>\n" +
|
||||
"<ul>";
|
||||
for (var i = 0;
|
||||
i < gUnsafePathsWithInvalidValuesForThisProcess.length;
|
||||
i++)
|
||||
{
|
||||
warningText +=
|
||||
" <li>" +
|
||||
makeSafe(gUnsafePathsWithInvalidValuesForThisProcess[i]) +
|
||||
"</li>\n";
|
||||
}
|
||||
warningText +=
|
||||
"</ul>" +
|
||||
"<p>This indicates a defect in one or more memory reporters. The " +
|
||||
"invalid values are highlighted, but you may need to expand one " +
|
||||
"or more sub-trees to see them.</p>\n\n" +
|
||||
"</div>";
|
||||
gUnsafePathsWithInvalidValuesForThisProcess = []; // reset for the next process
|
||||
}
|
||||
|
||||
return warningText;
|
||||
}
|
||||
|
||||
@ -707,7 +762,7 @@ function genWarningText(aHasKnownHeapAllocated, aHasMozMallocUsableSize)
|
||||
* @param aProcess
|
||||
* The name of the process.
|
||||
* @param aReporters
|
||||
* Table of Reporters for this process, indexed by _path.
|
||||
* Table of Reporters for this process, indexed by _unsafePath.
|
||||
* @param aHasMozMallocUsableSize
|
||||
* Boolean indicating if moz_malloc_usable_size works.
|
||||
* @return The generated text.
|
||||
@ -719,20 +774,14 @@ function genProcessText(aProcess, aReporters, aHasMozMallocUsableSize)
|
||||
sortTreeAndInsertAggregateNodes(explicitTree._amount, explicitTree);
|
||||
var explicitText = genTreeText(explicitTree, aProcess);
|
||||
|
||||
// Generate any warnings about inaccuracies due to platform limitations.
|
||||
// The newlines give nice spacing if we cut+paste into a text buffer.
|
||||
var warningText = "";
|
||||
var accuracyTagText = "<p class='accuracyWarning'>";
|
||||
var warningText =
|
||||
genWarningText(hasKnownHeapAllocated, aHasMozMallocUsableSize);
|
||||
|
||||
// We only show these breakdown trees in verbose mode.
|
||||
var mapTreeText = "";
|
||||
kMapTreePaths.forEach(function(t) {
|
||||
if (gVerbose) {
|
||||
var tree = buildTree(aReporters, t);
|
||||
|
||||
// |tree| will be null if we don't have any reporters for the given path.
|
||||
// |tree| will be null if we don't have any reporters for the given
|
||||
// unsafePath.
|
||||
if (tree) {
|
||||
sortTreeAndInsertAggregateNodes(tree._amount, tree);
|
||||
tree._hideKids = true; // map trees are always initially collapsed
|
||||
@ -747,12 +796,35 @@ function genProcessText(aProcess, aReporters, aHasMozMallocUsableSize)
|
||||
// looks at all the reporters which aren't part of a tree.
|
||||
var otherText = genOtherText(aReporters, aProcess);
|
||||
|
||||
// Generate any warnings about inaccuracies due to platform limitations.
|
||||
// This must come after generating all the text. The newlines give nice
|
||||
// spacing if we cut+paste into a text buffer.
|
||||
var warningText = "";
|
||||
var warningText =
|
||||
genWarningText(hasKnownHeapAllocated, aHasMozMallocUsableSize);
|
||||
|
||||
// The newlines give nice spacing if we cut+paste into a text buffer.
|
||||
return "<h1>" + aProcess + " Process</h1>\n\n" +
|
||||
warningText + explicitText + mapTreeText + otherText +
|
||||
"<hr></hr>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a number has a negative sign when converted to a string.
|
||||
* Works even for -0.
|
||||
*
|
||||
* @param aN
|
||||
* The number.
|
||||
* @return A boolean.
|
||||
*/
|
||||
function hasNegativeSign(aN)
|
||||
{
|
||||
if (aN === 0) { // this succeeds for 0 and -0
|
||||
return 1 / aN === -Infinity; // this succeeds for -0
|
||||
}
|
||||
return aN < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an int as a human-readable string.
|
||||
*
|
||||
@ -763,7 +835,7 @@ function genProcessText(aProcess, aReporters, aHasMozMallocUsableSize)
|
||||
function formatInt(aN)
|
||||
{
|
||||
var neg = false;
|
||||
if (aN < 0) {
|
||||
if (hasNegativeSign(aN)) {
|
||||
neg = true;
|
||||
aN = -aN;
|
||||
}
|
||||
@ -804,7 +876,8 @@ function formatBytes(aBytes)
|
||||
} else {
|
||||
var mbytes = (aBytes / (1024 * 1024)).toFixed(2);
|
||||
var a = String(mbytes).split(".");
|
||||
s = formatInt(a[0]) + "." + a[1] + " " + unit;
|
||||
// If the argument to formatInt() is -0, it will print the negative sign.
|
||||
s = formatInt(Number(a[0])) + "." + a[1] + " " + unit;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@ -847,17 +920,17 @@ function pad(aS, aN, aC)
|
||||
* property.
|
||||
*
|
||||
* @param aReporters
|
||||
* Table of Reporters for this process, indexed by _path.
|
||||
* @param aPath
|
||||
* The path of the R.
|
||||
* Table of Reporters for this process, indexed by _unsafePath.
|
||||
* @param aUnsafePath
|
||||
* The unsafePath of the R.
|
||||
* @param aDoNotMark
|
||||
* If true, the _done property is not set.
|
||||
* @return The byte count.
|
||||
*/
|
||||
function getBytes(aReporters, aPath, aDoNotMark)
|
||||
function getBytes(aReporters, aUnsafePath, aDoNotMark)
|
||||
{
|
||||
var r = aReporters[aPath];
|
||||
assert(r, "getBytes: no such Reporter: " + aPath);
|
||||
var r = aReporters[aUnsafePath];
|
||||
assert(r, "getBytes: no such Reporter: " + makeSafe(aUnsafePath));
|
||||
if (!aDoNotMark) {
|
||||
r._done = true;
|
||||
}
|
||||
@ -865,19 +938,19 @@ function getBytes(aReporters, aPath, aDoNotMark)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the description for a particular Reporter.
|
||||
* Gets the (unsafe) description for a particular Reporter.
|
||||
*
|
||||
* @param aReporters
|
||||
* Table of Reporters for this process, indexed by _path.
|
||||
* @param aPath
|
||||
* The path of the Reporter.
|
||||
* Table of Reporters for this process, indexed by _unsafePath.
|
||||
* @param aUnsafePath
|
||||
* The unsafePath of the Reporter.
|
||||
* @return The description.
|
||||
*/
|
||||
function getDescription(aReporters, aPath)
|
||||
function getUnsafeDescription(aReporters, aUnsafePath)
|
||||
{
|
||||
var r = aReporters[aPath];
|
||||
assert(r, "getDescription: no such Reporter: " + aPath);
|
||||
return r._description;
|
||||
var r = aReporters[aUnsafePath];
|
||||
assert(r, "getUnsafeDescription: no such Reporter: " + makeSafe(aUnsafePath));
|
||||
return r._unsafeDescription;
|
||||
}
|
||||
|
||||
// There's a subset of the Unicode "light" box-drawing chars that are widely
|
||||
@ -889,9 +962,11 @@ const kHorizontal = "\u2500",
|
||||
kUpAndRight = "\u2514",
|
||||
kVerticalAndRight = "\u251c";
|
||||
|
||||
function genMrValueText(aValue)
|
||||
function genMrValueText(aValue, aIsInvalid)
|
||||
{
|
||||
return "<span class='mrValue'>" + aValue + " </span>";
|
||||
return aIsInvalid ?
|
||||
"<span class='mrValue invalid'>" + aValue + "</span>" :
|
||||
"<span class='mrValue'>" + aValue + "</span>";
|
||||
}
|
||||
|
||||
function kindToString(aKind)
|
||||
@ -905,34 +980,8 @@ function kindToString(aKind)
|
||||
}
|
||||
}
|
||||
|
||||
// For user-controlled strings.
|
||||
function escapeAll(aStr)
|
||||
{
|
||||
return aStr.replace(/\&/g, '&').replace(/'/g, ''').
|
||||
replace(/\</g, '<').replace(/>/g, '>').
|
||||
replace(/\"/g, '"');
|
||||
}
|
||||
|
||||
// Compartment reporter names are URLs and so can include forward slashes. But
|
||||
// forward slash is the memory reporter path separator. So the memory
|
||||
// reporters change them to backslashes. Undo that here.
|
||||
function flipBackslashes(aStr)
|
||||
{
|
||||
return aStr.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
function prepName(aStr)
|
||||
{
|
||||
return escapeAll(flipBackslashes(aStr));
|
||||
}
|
||||
|
||||
function prepDesc(aStr)
|
||||
{
|
||||
return escapeAll(flipBackslashes(aStr));
|
||||
}
|
||||
|
||||
function genMrNameText(aKind, aShowSubtrees, aHasKids, aDesc, aName,
|
||||
aHasProblem, aNMerged)
|
||||
function genMrNameText(aKind, aShowSubtrees, aHasKids, aUnsafeDesc,
|
||||
aUnsafeName, aIsUnknown, aIsInvalid, aNMerged)
|
||||
{
|
||||
var text = "";
|
||||
if (aHasKids) {
|
||||
@ -947,35 +996,41 @@ function genMrNameText(aKind, aShowSubtrees, aHasKids, aDesc, aName,
|
||||
text += "<span class='mrSep'> " + kHorizontal + kHorizontal + " </span>";
|
||||
}
|
||||
text += "<span class='mrName' title='" +
|
||||
kindToString(aKind) + prepDesc(aDesc) + "'>" +
|
||||
prepName(aName) + "</span>";
|
||||
if (aHasProblem) {
|
||||
kindToString(aKind) + makeSafe(aUnsafeDesc) + "'>" +
|
||||
makeSafe(aUnsafeName) + "</span>";
|
||||
if (aIsUnknown) {
|
||||
const problemDesc =
|
||||
"Warning: this memory reporter was unable to compute a useful value. ";
|
||||
text += "<span class='mrStar' title=\"" + problemDesc + "\"> [*]</span>";
|
||||
text += "<span class='mrNote' title=\"" + problemDesc + "\"> [*]</span>";
|
||||
}
|
||||
if (aIsInvalid) {
|
||||
const invalidDesc =
|
||||
"Warning: this value is invalid and indicates a bug in one or more " +
|
||||
"memory reporters. ";
|
||||
text += "<span class='mrNote' title=\"" + invalidDesc + "\"> [?!]</span>";
|
||||
}
|
||||
if (aNMerged) {
|
||||
const dupDesc = "This value is the sum of " + aNMerged +
|
||||
" memory reporters that all have the same path.";
|
||||
text += "<span class='mrStar' title=\"" + dupDesc + "\"> [" +
|
||||
text += "<span class='mrNote' title=\"" + dupDesc + "\"> [" +
|
||||
aNMerged + "]</span>";
|
||||
}
|
||||
return text + '\n';
|
||||
}
|
||||
|
||||
// This is used to record which sub-trees have been toggled, so the
|
||||
// collapsed/expanded state can be replicated when the page is regenerated.
|
||||
// It can end up holding IDs of nodes that no longer exist, e.g. for
|
||||
// compartments that have been closed. This doesn't seem like a big deal,
|
||||
// This is used to record the (safe) IDs of which sub-trees have been toggled,
|
||||
// so the collapsed/expanded state can be replicated when the page is
|
||||
// regenerated. It can end up holding IDs of nodes that no longer exist, e.g.
|
||||
// for compartments that have been closed. This doesn't seem like a big deal,
|
||||
// because the number is limited by the number of entries the user has changed
|
||||
// from their original state.
|
||||
var gToggles = {};
|
||||
var gTogglesBySafeTreeId = {};
|
||||
|
||||
function toggle(aEvent)
|
||||
{
|
||||
// This relies on each line being a span that contains at least five spans:
|
||||
// mrValue, mrPerc, mrSep ('++'), mrSep ('--'), mrName, and then zero or more
|
||||
// mrStars. All whitespace must be within one of these spans for this
|
||||
// mrNotes. All whitespace must be within one of these spans for this
|
||||
// function to find the right nodes. And the span containing the children of
|
||||
// this line must immediately follow. Assertions check this.
|
||||
|
||||
@ -1003,11 +1058,11 @@ function toggle(aEvent)
|
||||
subTreeSpan.classList.toggle("hidden");
|
||||
|
||||
// Record/unrecord that this sub-tree was toggled.
|
||||
var treeId = outerSpan.id;
|
||||
if (gToggles[treeId]) {
|
||||
delete gToggles[treeId];
|
||||
var safeTreeId = outerSpan.id;
|
||||
if (gTogglesBySafeTreeId[safeTreeId]) {
|
||||
delete gTogglesBySafeTreeId[safeTreeId];
|
||||
} else {
|
||||
gToggles[treeId] = true;
|
||||
gTogglesBySafeTreeId[safeTreeId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1024,13 +1079,13 @@ function genTreeText(aT, aProcess)
|
||||
{
|
||||
var treeBytes = aT._amount;
|
||||
var rootStringLength = aT.toString().length;
|
||||
var isExplicitTree = aT._name == 'explicit';
|
||||
var isExplicitTree = aT._unsafeName == 'explicit';
|
||||
|
||||
/**
|
||||
* Generates the text for a particular tree, without a heading.
|
||||
*
|
||||
* @param aPrePath
|
||||
* The partial path leading up to this node.
|
||||
* @param aUnsafePrePath
|
||||
* The partial unsafePath leading up to this node.
|
||||
* @param aT
|
||||
* The tree.
|
||||
* @param aIndentGuide
|
||||
@ -1042,7 +1097,7 @@ function genTreeText(aT, aProcess)
|
||||
* The length of the formatted byte count of the top node in the tree.
|
||||
* @return The generated text.
|
||||
*/
|
||||
function genTreeText2(aPrePath, aT, aIndentGuide, aParentStringLength)
|
||||
function genTreeText2(aUnsafePrePath, aT, aIndentGuide, aParentStringLength)
|
||||
{
|
||||
function repeatStr(aC, aN)
|
||||
{
|
||||
@ -1053,6 +1108,15 @@ function genTreeText(aT, aProcess)
|
||||
return s;
|
||||
}
|
||||
|
||||
// Determine if we should show the sub-tree below this entry; this
|
||||
// involves reinstating any previous toggling of the sub-tree.
|
||||
var unsafePath = aUnsafePrePath + aT._unsafeName;
|
||||
var safeTreeId = escapeAll(aProcess + ":" + unsafePath);
|
||||
var showSubtrees = !aT._hideKids;
|
||||
if (gTogglesBySafeTreeId[safeTreeId]) {
|
||||
showSubtrees = !showSubtrees;
|
||||
}
|
||||
|
||||
// Generate the indent.
|
||||
var indent = "<span class='treeLine'>";
|
||||
if (aIndentGuide.length > 0) {
|
||||
@ -1075,24 +1139,24 @@ function genTreeText(aT, aProcess)
|
||||
}
|
||||
indent += "</span>";
|
||||
|
||||
// Generate the percentage, and determine if we should show subtrees.
|
||||
// Generate the percentage; detect and record invalid values at the same
|
||||
// time.
|
||||
var percText = "";
|
||||
var showSubtrees = !aT._hideKids;
|
||||
var tIsInvalid = false;
|
||||
if (aT._amount === treeBytes) {
|
||||
percText = "100.0";
|
||||
} else {
|
||||
var perc = (100 * aT._amount / treeBytes);
|
||||
if (!(0 <= perc && perc <= 100)) {
|
||||
tIsInvalid = true;
|
||||
gUnsafePathsWithInvalidValuesForThisProcess.push(unsafePath);
|
||||
}
|
||||
percText = (100 * aT._amount / treeBytes).toFixed(2);
|
||||
percText = pad(percText, 5, '0');
|
||||
}
|
||||
percText = "<span class='mrPerc'>(" + percText + "%) </span>";
|
||||
|
||||
// Reinstate any previous toggling of this sub-tree.
|
||||
var path = aPrePath + aT._name;
|
||||
var treeId = escapeAll(aProcess + ":" + path);
|
||||
if (gToggles[treeId]) {
|
||||
showSubtrees = !showSubtrees;
|
||||
}
|
||||
percText = tIsInvalid ?
|
||||
"<span class='mrPerc invalid'> (" + percText + "%)</span>" :
|
||||
"<span class='mrPerc'> (" + percText + "%)</span>";
|
||||
|
||||
// We don't want to show '(nonheap)' on a tree like 'map/vsize', since the
|
||||
// whole tree is non-heap.
|
||||
@ -1106,12 +1170,13 @@ function genTreeText(aT, aProcess)
|
||||
}
|
||||
var text = indent;
|
||||
if (hasKids) {
|
||||
text +=
|
||||
"<span onclick='toggle(event)' class='hasKids' id='" + treeId + "'>";
|
||||
text += "<span onclick='toggle(event)' class='hasKids' id='" +
|
||||
safeTreeId + "'>";
|
||||
}
|
||||
text += genMrValueText(tString) + percText;
|
||||
text += genMrNameText(kind, showSubtrees, hasKids, aT._description,
|
||||
aT._name, aT._hasProblem, aT._nMerged);
|
||||
text += genMrValueText(tString, tIsInvalid) + percText;
|
||||
text += genMrNameText(kind, showSubtrees, hasKids, aT._unsafeDescription,
|
||||
aT._unsafeName, aT._isUnknown, tIsInvalid,
|
||||
aT._nMerged);
|
||||
if (hasKids) {
|
||||
var hiddenText = showSubtrees ? "" : " hidden";
|
||||
// The 'kids' class is just used for sanity checking in toggle().
|
||||
@ -1121,7 +1186,7 @@ function genTreeText(aT, aProcess)
|
||||
for (var i = 0; i < aT._kids.length; i++) {
|
||||
// 3 is the standard depth, the callee adjusts it if necessary.
|
||||
aIndentGuide.push({ _isLastKid: (i === aT._kids.length - 1), _depth: 3 });
|
||||
text += genTreeText2(path + "/", aT._kids[i], aIndentGuide,
|
||||
text += genTreeText2(unsafePath + "/", aT._kids[i], aIndentGuide,
|
||||
tString.length);
|
||||
aIndentGuide.pop();
|
||||
}
|
||||
@ -1131,23 +1196,22 @@ function genTreeText(aT, aProcess)
|
||||
|
||||
var text = genTreeText2(/* prePath = */"", aT, [], rootStringLength);
|
||||
|
||||
return genSectionMarkup(aT._name, text);
|
||||
return genSectionMarkup(aT._unsafeName, text);
|
||||
}
|
||||
|
||||
function OtherReporter(aPath, aUnits, aAmount, aDescription,
|
||||
aNMerged)
|
||||
function OtherReporter(aUnsafePath, aUnits, aAmount, aUnsafeDesc, aNMerged)
|
||||
{
|
||||
// Nb: _kind is not needed, it's always KIND_OTHER.
|
||||
this._path = aPath;
|
||||
this._unsafePath = aUnsafePath;
|
||||
this._units = aUnits;
|
||||
if (aAmount === kUnknown) {
|
||||
this._amount = 0;
|
||||
this._hasProblem = true;
|
||||
this._isUnknown = true;
|
||||
} else {
|
||||
this._amount = aAmount;
|
||||
}
|
||||
this._description = aDescription;
|
||||
this.asString = this.toString();
|
||||
this._unsafeDescription = aUnsafeDesc;
|
||||
this._asString = this.toString();
|
||||
}
|
||||
|
||||
OtherReporter.prototype = {
|
||||
@ -1160,12 +1224,25 @@ OtherReporter.prototype = {
|
||||
default:
|
||||
assert(false, "bad units in OtherReporter.toString");
|
||||
}
|
||||
},
|
||||
|
||||
isInvalid: function() {
|
||||
var n = this._amount;
|
||||
switch (this._units) {
|
||||
case UNITS_BYTES:
|
||||
case UNITS_COUNT:
|
||||
case UNITS_COUNT_CUMULATIVE: return (n !== kUnknown && n < 0);
|
||||
case UNITS_PERCENTAGE: return (n !== kUnknown &&
|
||||
!(0 <= n && n <= 10000));
|
||||
default:
|
||||
assert(false, "bad units in OtherReporter.isInvalid");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
OtherReporter.compare = function(a, b) {
|
||||
return a._path < b._path ? -1 :
|
||||
a._path > b._path ? 1 :
|
||||
return a._unsafePath < b._unsafePath ? -1 :
|
||||
a._unsafePath > b._unsafePath ? 1 :
|
||||
0;
|
||||
};
|
||||
|
||||
@ -1173,7 +1250,7 @@ OtherReporter.compare = function(a, b) {
|
||||
* Generates the text for the "Other Measurements" section.
|
||||
*
|
||||
* @param aReportersByProcess
|
||||
* Table of Reporters for this process, indexed by _path.
|
||||
* Table of Reporters for this process, indexed by _unsafePath.
|
||||
* @param aProcess
|
||||
* The process these reporters correspond to.
|
||||
* @return The generated text.
|
||||
@ -1185,19 +1262,17 @@ function genOtherText(aReportersByProcess, aProcess)
|
||||
// widest element, so we can format things nicely.
|
||||
var maxStringLength = 0;
|
||||
var otherReporters = [];
|
||||
for (var path in aReportersByProcess) {
|
||||
var r = aReportersByProcess[path];
|
||||
for (var unsafePath in aReportersByProcess) {
|
||||
var r = aReportersByProcess[unsafePath];
|
||||
if (!r._done) {
|
||||
assert(r._kind === KIND_OTHER, "_kind !== KIND_OTHER for " + r._path);
|
||||
assert(r.nMerged === undefined); // we don't allow dup'd OTHER reporters
|
||||
var hasProblem = false;
|
||||
if (r._amount === kUnknown) {
|
||||
hasProblem = true;
|
||||
}
|
||||
var o = new OtherReporter(r._path, r._units, r._amount, r._description);
|
||||
assert(r._kind === KIND_OTHER,
|
||||
"_kind !== KIND_OTHER for " + makeSafe(r._unsafePath));
|
||||
assert(r._nMerged === undefined); // we don't allow dup'd OTHER reporters
|
||||
var o = new OtherReporter(r._unsafePath, r._units, r._amount,
|
||||
r._unsafeDescription);
|
||||
otherReporters.push(o);
|
||||
if (o.asString.length > maxStringLength) {
|
||||
maxStringLength = o.asString.length;
|
||||
if (o._asString.length > maxStringLength) {
|
||||
maxStringLength = o._asString.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1207,10 +1282,14 @@ function genOtherText(aReportersByProcess, aProcess)
|
||||
var text = "";
|
||||
for (var i = 0; i < otherReporters.length; i++) {
|
||||
var o = otherReporters[i];
|
||||
text += genMrValueText(pad(o.asString, maxStringLength, ' '));
|
||||
var oIsInvalid = o.isInvalid();
|
||||
if (oIsInvalid) {
|
||||
gUnsafePathsWithInvalidValuesForThisProcess.push(o._unsafePath);
|
||||
}
|
||||
text += genMrValueText(pad(o._asString, maxStringLength, ' '), oIsInvalid);
|
||||
text += genMrNameText(KIND_OTHER, /* showSubtrees = */true,
|
||||
/* hasKids = */false, o._description, o._path,
|
||||
o._hasProblem);
|
||||
/* hasKids = */false, o._unsafeDescription,
|
||||
o._unsafePath, o._isUnknown, oIsInvalid);
|
||||
}
|
||||
|
||||
return genSectionMarkup('other', text);
|
||||
|
@ -166,7 +166,36 @@
|
||||
f("3rd", "explicit/a/d", HEAP, kUnknown),
|
||||
f("3rd", "explicit/a/d", HEAP, kUnknown), // dup: merge
|
||||
f("3rd", "explicit/b", NONHEAP, kUnknown),
|
||||
f("3rd", "other1", OTHER, kUnknown)
|
||||
f2("3rd", "other1", OTHER, BYTES, kUnknown),
|
||||
f2("3rd", "other2", OTHER, COUNT, kUnknown),
|
||||
f2("3rd", "other3", OTHER, COUNT_CUMULATIVE, kUnknown),
|
||||
f2("3rd", "other4", OTHER, PERCENTAGE, kUnknown),
|
||||
|
||||
// Invalid values (negative, too-big) should be identified.
|
||||
f("4th", "heap-allocated", OTHER, 100 * MB),
|
||||
f("4th", "explicit/js/compartment(http:\\\\too-big.com\\)/stuff",
|
||||
HEAP, 150 * MB),
|
||||
f("4th", "explicit/ok", HEAP, 5 * MB),
|
||||
f("4th", "explicit/neg1", NONHEAP, -2 * MB),
|
||||
// -111 becomes "-0.00MB" in non-verbose mode, and getting the negative
|
||||
// sign in there correctly is non-trivial.
|
||||
f2("4th", "other1", OTHER, BYTES, -111),
|
||||
f2("4th", "other2", OTHER, BYTES, -222 * MB),
|
||||
f2("4th", "other3", OTHER, COUNT, -333),
|
||||
f2("4th", "other4", OTHER, COUNT_CUMULATIVE, -444),
|
||||
f2("4th", "other5", OTHER, PERCENTAGE, -555),
|
||||
// Escaping should again prevent this script from running when the name
|
||||
// is printed in the warning.
|
||||
f2("4th", "other6-danger<script>window.alert(1)</script>",
|
||||
OTHER, PERCENTAGE, 66666),
|
||||
|
||||
// If a negative value is within a collapsed sub-tree in non-verbose mode,
|
||||
// we should still get the warning at the top.
|
||||
f("5th", "heap-allocated", OTHER, 100 * MB),
|
||||
f("5th", "explicit/big", HEAP, 99 * MB),
|
||||
f("5th", "explicit/a/pos", HEAP, 40 * KB),
|
||||
f("5th", "explicit/a/neg1", NONHEAP, -20 * KB),
|
||||
f("5th", "explicit/a/neg2", NONHEAP, -10 * KB)
|
||||
];
|
||||
for (var i = 0; i < fakeReporters2.length; i++) {
|
||||
mgr.registerReporter(fakeReporters2[i]);
|
||||
@ -175,8 +204,8 @@
|
||||
]]>
|
||||
</script>
|
||||
|
||||
<iframe id="amFrame" height="300" src="about:memory"></iframe>
|
||||
<iframe id="amvFrame" height="300" src="about:memory?verbose"></iframe>
|
||||
<iframe id="amFrame" height="400" src="about:memory"></iframe>
|
||||
<iframe id="amvFrame" height="400" src="about:memory?verbose"></iframe>
|
||||
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
@ -247,6 +276,58 @@ Explicit Allocations\n\
|
||||
Other Measurements\n\
|
||||
0.00 MB ── heap-allocated [*]\n\
|
||||
0.00 MB ── other1 [*]\n\
|
||||
0 ── other2 [*]\n\
|
||||
0 ── other3 [*]\n\
|
||||
0.00% ── other4 [*]\n\
|
||||
\n\
|
||||
4th Process\n\
|
||||
\n\
|
||||
WARNING: the following values are negative or unreasonably large.\n\
|
||||
explicit/js\n\
|
||||
explicit/js/compartment(http://too-big.com/)\n\
|
||||
explicit/js/compartment(http://too-big.com/)/stuff\n\
|
||||
explicit/(2 tiny)\n\
|
||||
explicit/(2 tiny)/neg1\n\
|
||||
explicit/(2 tiny)/heap-unclassified\n\
|
||||
other1\n\
|
||||
other2\n\
|
||||
other3\n\
|
||||
other4\n\
|
||||
other5\n\
|
||||
other6-danger<script>window.alert(1)</script>\n\
|
||||
This indicates a defect in one or more memory reporters. The invalid values are highlighted, but you may need to expand one or more sub-trees to see them.\n\
|
||||
\n\
|
||||
Explicit Allocations\n\
|
||||
98.00 MB (100.0%) -- explicit\n\
|
||||
├──150.00 MB (153.06%) -- js [?!]\n\
|
||||
│ └──150.00 MB (153.06%) -- compartment(http://too-big.com/) [?!]\n\
|
||||
│ └──150.00 MB (153.06%) ── stuff [?!]\n\
|
||||
├───5.00 MB (05.10%) ── ok\n\
|
||||
└──-57.00 MB (-58.16%) ++ (2 tiny) [?!]\n\
|
||||
\n\
|
||||
Other Measurements\n\
|
||||
100.00 MB ── heap-allocated\n\
|
||||
-0.00 MB ── other1 [?!]\n\
|
||||
-222.00 MB ── other2 [?!]\n\
|
||||
-333 ── other3 [?!]\n\
|
||||
-444 ── other4 [?!]\n\
|
||||
-5.55% ── other5 [?!]\n\
|
||||
666.66% ── other6-danger<script>window.alert(1)</script> [?!]\n\
|
||||
\n\
|
||||
5th Process\n\
|
||||
\n\
|
||||
WARNING: the following values are negative or unreasonably large.\n\
|
||||
explicit/(2 tiny)/a/neg2\n\
|
||||
explicit/(2 tiny)/a/neg1\n\
|
||||
This indicates a defect in one or more memory reporters. The invalid values are highlighted, but you may need to expand one or more sub-trees to see them.\n\
|
||||
\n\
|
||||
Explicit Allocations\n\
|
||||
99.97 MB (100.0%) -- explicit\n\
|
||||
├──99.00 MB (99.03%) ── big\n\
|
||||
└───0.97 MB (00.97%) ++ (2 tiny)\n\
|
||||
\n\
|
||||
Other Measurements\n\
|
||||
100.00 MB ── heap-allocated\n\
|
||||
\n\
|
||||
";
|
||||
|
||||
@ -334,6 +415,62 @@ Explicit Allocations\n\
|
||||
Other Measurements\n\
|
||||
0 B ── heap-allocated [*]\n\
|
||||
0 B ── other1 [*]\n\
|
||||
0 ── other2 [*]\n\
|
||||
0 ── other3 [*]\n\
|
||||
0.00% ── other4 [*]\n\
|
||||
\n\
|
||||
4th Process\n\
|
||||
\n\
|
||||
WARNING: the following values are negative or unreasonably large.\n\
|
||||
explicit/js\n\
|
||||
explicit/js/compartment(http://too-big.com/)\n\
|
||||
explicit/js/compartment(http://too-big.com/)/stuff\n\
|
||||
explicit/neg1\n\
|
||||
explicit/heap-unclassified\n\
|
||||
other1\n\
|
||||
other2\n\
|
||||
other3\n\
|
||||
other4\n\
|
||||
other5\n\
|
||||
other6-danger<script>window.alert(1)</script>\n\
|
||||
This indicates a defect in one or more memory reporters. The invalid values are highlighted, but you may need to expand one or more sub-trees to see them.\n\
|
||||
\n\
|
||||
Explicit Allocations\n\
|
||||
102,760,448 B (100.0%) -- explicit\n\
|
||||
├──157,286,400 B (153.06%) -- js [?!]\n\
|
||||
│ └──157,286,400 B (153.06%) -- compartment(http://too-big.com/) [?!]\n\
|
||||
│ └──157,286,400 B (153.06%) ── stuff [?!]\n\
|
||||
├────5,242,880 B (05.10%) ── ok\n\
|
||||
├───-2,097,152 B (-2.04%) ── neg1 [?!]\n\
|
||||
└──-57,671,680 B (-56.12%) ── heap-unclassified [?!]\n\
|
||||
\n\
|
||||
Other Measurements\n\
|
||||
104,857,600 B ── heap-allocated\n\
|
||||
-111 B ── other1 [?!]\n\
|
||||
-232,783,872 B ── other2 [?!]\n\
|
||||
-333 ── other3 [?!]\n\
|
||||
-444 ── other4 [?!]\n\
|
||||
-5.55% ── other5 [?!]\n\
|
||||
666.66% ── other6-danger<script>window.alert(1)</script> [?!]\n\
|
||||
\n\
|
||||
5th Process\n\
|
||||
\n\
|
||||
WARNING: the following values are negative or unreasonably large.\n\
|
||||
explicit/a/neg2\n\
|
||||
explicit/a/neg1\n\
|
||||
This indicates a defect in one or more memory reporters. The invalid values are highlighted, but you may need to expand one or more sub-trees to see them.\n\
|
||||
\n\
|
||||
Explicit Allocations\n\
|
||||
104,826,880 B (100.0%) -- explicit\n\
|
||||
├──103,809,024 B (99.03%) ── big\n\
|
||||
├────1,007,616 B (00.96%) ── heap-unclassified\n\
|
||||
└───────10,240 B (00.01%) -- a\n\
|
||||
├──40,960 B (00.04%) ── pos\n\
|
||||
├──-10,240 B (-0.01%) ── neg2 [?!]\n\
|
||||
└──-20,480 B (-0.02%) ── neg1 [?!]\n\
|
||||
\n\
|
||||
Other Measurements\n\
|
||||
104,857,600 B ── heap-allocated\n\
|
||||
\n\
|
||||
"
|
||||
|
||||
|
@ -184,6 +184,9 @@ TelemetryPing.prototype = {
|
||||
// duplicate submissions.
|
||||
_uuid: generateUUID(),
|
||||
_prevSession: null,
|
||||
// Regex that matches histograms we care about during startup.
|
||||
_startupHistogramRegex: /SQLITE|HTTP|SPDY|CACHE|DNS/,
|
||||
_slowSQLStartup: {},
|
||||
|
||||
/**
|
||||
* Returns a set of histograms that can be converted into JSON
|
||||
@ -384,15 +387,25 @@ TelemetryPing.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Make a copy of sqlite histograms on startup
|
||||
* Return true if we're interested in having a STARTUP_* histogram for
|
||||
* the given histogram name.
|
||||
*/
|
||||
gatherStartupSqlite: function gatherStartupSqlite() {
|
||||
isInterestingStartupHistogram: function isInterestingStartupHistogram(name) {
|
||||
return this._startupHistogramRegex.test(name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Make a copy of interesting histograms at startup.
|
||||
*/
|
||||
gatherStartupInformation: function gatherStartupInformation() {
|
||||
let info = Telemetry.registeredHistograms;
|
||||
let sqlite_re = /SQLITE/;
|
||||
let snapshots = Telemetry.histogramSnapshots;
|
||||
for (let name in info) {
|
||||
if (sqlite_re.test(name))
|
||||
// Only duplicate interesting histograms with actual data.
|
||||
if (this.isInterestingStartupHistogram(name) && name in snapshots)
|
||||
Telemetry.histogramFrom("STARTUP_" + name, name);
|
||||
}
|
||||
this._slowSQLStartup = Telemetry.slowSQL;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -436,6 +449,10 @@ TelemetryPing.prototype = {
|
||||
payloadObj.simpleMeasurements = getSimpleMeasurements();
|
||||
payloadObj.histograms = this.getHistograms(Telemetry.histogramSnapshots);
|
||||
payloadObj.slowSQL = Telemetry.slowSQL;
|
||||
if (Object.keys(this._slowSQLStartup.mainThread).length
|
||||
|| Object.keys(this._slowSQLStartup.otherThreads).length) {
|
||||
payloadObj.slowSQLStartup = this._slowSQLStartup;
|
||||
}
|
||||
}
|
||||
return { previous: !!havePreviousSession, slug: slug, payload: JSON.stringify(payloadObj) };
|
||||
},
|
||||
@ -602,7 +619,7 @@ TelemetryPing.prototype = {
|
||||
}
|
||||
break;
|
||||
case "sessionstore-windows-restored":
|
||||
this.gatherStartupSqlite();
|
||||
this.gatherStartupInformation();
|
||||
break;
|
||||
case "idle-daily":
|
||||
// Enqueue to main-thread, otherwise components may be inited by the
|
||||
|
@ -35,18 +35,17 @@
|
||||
}
|
||||
|
||||
.fullscreenButton {
|
||||
background-color: transparent;
|
||||
list-style-image: url("chrome://global/skin/media/fullscreenButton.png");
|
||||
-moz-image-region: rect(0px, 16px, 16px, 0px);
|
||||
background: -moz-image-rect(url("chrome://global/skin/media/fullscreenButton.png"), 0, 16, 16, 0) no-repeat center;
|
||||
-moz-appearance: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 28px;
|
||||
min-width: 28px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.fullscreenButton[fullscreened] {
|
||||
-moz-image-region: rect(0px, 32px, 16px, 16px);
|
||||
background-image: -moz-image-rect(url("chrome://global/skin/media/fullscreenButton.png"), 0, 32, 16, 16);
|
||||
}
|
||||
|
||||
.volumeStack {
|
||||
|
@ -37,9 +37,7 @@
|
||||
}
|
||||
|
||||
.fullscreenButton {
|
||||
background-color: transparent;
|
||||
list-style-image: url("chrome://global/skin/media/fullscreenButton.png");
|
||||
-moz-image-region: rect(0px, 16px, 16px, 0px);
|
||||
background: -moz-image-rect(url("chrome://global/skin/media/fullscreenButton.png"), 0, 16, 16, 0) no-repeat center;
|
||||
-moz-appearance: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@ -49,7 +47,7 @@
|
||||
}
|
||||
|
||||
.fullscreenButton[fullscreened] {
|
||||
-moz-image-region: rect(0px, 32px, 16px, 16px);
|
||||
background-image: -moz-image-rect(url("chrome://global/skin/media/fullscreenButton.png"), 0, 32, 16, 16);
|
||||
}
|
||||
|
||||
.volumeStack {
|
||||
|
Loading…
Reference in New Issue
Block a user