Merge central to inbound

This commit is contained in:
Marco Bonardo 2012-01-30 11:47:01 +01:00
commit 63f2e7c2e5
18 changed files with 779 additions and 351 deletions

View File

@ -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.
mBlobs.Clear();
if (aClearBuffer) {
mBlobs.Clear();
}
return NS_OK;
}

View 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

View File

@ -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) {
*writeCount = count;
return NS_OK;
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 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,34 +2186,31 @@ 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();
}
NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
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;
mChannelEventSink = nsnull;

View File

@ -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;

View File

@ -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() {

View File

@ -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");
}

View File

@ -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 \

View 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>

View File

@ -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;
}

View File

@ -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()) {

View File

@ -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

View File

@ -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.

View File

@ -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;
}

View File

@ -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, '&amp;').replace(/'/g, '&#39;').
replace(/\</g, '&lt;').replace(/>/g, '&gt;').
replace(/\"/g, '&quot;');
}
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,77 +980,57 @@ function kindToString(aKind)
}
}
// For user-controlled strings.
function escapeAll(aStr)
{
return aStr.replace(/\&/g, '&amp;').replace(/'/g, '&#39;').
replace(/\</g, '&lt;').replace(/>/g, '&gt;').
replace(/\"/g, '&quot;');
}
// 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) {
if (aShowSubtrees) {
text += "<span class='mrSep hidden'>++ </span>";
text += "<span class='mrSep'>-- </span>";
text += "<span class='mrSep hidden'> ++ </span>";
text += "<span class='mrSep'> -- </span>";
} else {
text += "<span class='mrSep'>++ </span>";
text += "<span class='mrSep hidden'>-- </span>";
text += "<span class='mrSep'> ++ </span>";
text += "<span class='mrSep hidden'> -- </span>";
}
} else {
text += "<span class='mrSep'>" + kHorizontal + kHorizontal + " </span>";
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,28 +1196,27 @@ 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._units = aUnits;
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 = {
toString: function() {
switch(this._units) {
switch (this._units) {
case UNITS_BYTES: return formatBytes(this._amount);
case UNITS_COUNT:
case UNITS_COUNT_CUMULATIVE: return formatInt(this._amount);
@ -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);

View File

@ -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\
";
@ -332,8 +413,64 @@ Explicit Allocations\n\
└────────────0 B (00.00%) ── heap-unclassified [*]\n\
\n\
Other Measurements\n\
0 B ── heap-allocated [*]\n\
0 B ── other1 [*]\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\
"

View File

@ -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
@ -382,17 +385,27 @@ TelemetryPing.prototype = {
this.addValue(mr.path, id, val);
}
},
/**
* Return true if we're interested in having a STARTUP_* histogram for
* the given histogram name.
*/
isInterestingStartupHistogram: function isInterestingStartupHistogram(name) {
return this._startupHistogramRegex.test(name);
},
/**
* Make a copy of sqlite histograms on startup
* Make a copy of interesting histograms at startup.
*/
gatherStartupSqlite: function gatherStartupSqlite() {
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

View File

@ -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 {

View File

@ -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 {