Bug 786108 - Cache UTF-16 version of URI to prevent repeated conversions in the CSS scanner, and free the cache after a short time. r=bz

This commit is contained in:
Seth Fowler 2012-10-03 15:28:57 -07:00
parent 803b082135
commit ed479625af
5 changed files with 130 additions and 15 deletions

View File

@ -0,0 +1,22 @@
<html>
<head></head>
<body></body>
<script type="text/javascript">
// Detect severe performance and memory issues when large amounts of errors
// are reported from CSS embedded in a file with a long data URI. Addressed
// by 786108; should finish quickly with that patch and run for a very long
// time otherwise.
var img = new Array;
img.push('<img src="data:image/svg+xml,');
img.push(encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300px" height="300px">'));
for (var i = 0 ; i < 10000 ; i++)
img.push(encodeURIComponent('<circle cx="0" cy="0" r="1" style="xxx-invalid-property: 0;"/>'));
img.push(encodeURIComponent('</svg>'));
img.push('">');
document.getElementsByTagName('body')[0].innerHTML = img.join('');
</script>
</html>

View File

@ -0,0 +1,23 @@
<html>
<head></head>
<body></body>
<script type="text/javascript">
// Detect severe performance and memory issues when large amounts of errors
// are reported from CSS embedded in a file with a long data URI. Addressed
// by 786108; should finish quickly with that patch and run for a very long
// time otherwise. This version is designed for slow / memory constrained
// platforms like Android.
var img = new Array;
img.push('<img src="data:image/svg+xml,');
img.push(encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300px" height="300px">'));
for (var i = 0 ; i < 2500 ; i++)
img.push(encodeURIComponent('<circle cx="0" cy="0" r="1" style="xxx-invalid-property: 0;"/>'));
img.push(encodeURIComponent('</svg>'));
img.push('">');
document.getElementsByTagName('body')[0].innerHTML = img.join('');
</script>
</html>

View File

@ -77,4 +77,6 @@ load 696188-1.html
load 700116.html load 700116.html
load 729126-1.html load 729126-1.html
load 729126-2.html load 729126-2.html
skip-if(Android||browserIsRemote) load 786108-1.html # Bug 795534
skip-if(browserIsRemote) load 786108-2.html # Bug 795534
load 788836.html load 788836.html

View File

@ -249,6 +249,29 @@ nsCSSToken::AppendToString(nsString& aBuffer)
} }
} }
class DeferredCleanupRunnable : public nsRunnable
{
public:
DeferredCleanupRunnable(nsCSSScanner* aToClean)
: mToClean(aToClean)
{}
NS_IMETHOD Run() {
if (mToClean) {
mToClean->PerformDeferredCleanup();
}
return NS_OK;
}
void Revoke() {
mToClean = nullptr;
}
private:
nsCSSScanner* mToClean;
};
nsCSSScanner::nsCSSScanner() nsCSSScanner::nsCSSScanner()
: mReadPointer(nullptr) : mReadPointer(nullptr)
, mSVGMode(false) , mSVGMode(false)
@ -271,13 +294,24 @@ nsCSSScanner::nsCSSScanner()
nsCSSScanner::~nsCSSScanner() nsCSSScanner::~nsCSSScanner()
{ {
MOZ_COUNT_DTOR(nsCSSScanner); MOZ_COUNT_DTOR(nsCSSScanner);
Close(); Reset();
if (mLocalPushback != mPushback) { if (mLocalPushback != mPushback) {
delete [] mPushback; delete [] mPushback;
} }
} }
#ifdef CSS_REPORT_PARSE_ERRORS #ifdef CSS_REPORT_PARSE_ERRORS
void
nsCSSScanner::PerformDeferredCleanup()
{
// Clean up all short term caches.
mCachedURI = nullptr;
mCachedFileName.Truncate();
// Release our DeferredCleanupRunnable.
mDeferredCleaner.Forget();
}
#define CSS_ERRORS_PREF "layout.css.report_errors" #define CSS_ERRORS_PREF "layout.css.report_errors"
static int static int
@ -331,17 +365,13 @@ nsCSSScanner::Init(const nsAString& aBuffer,
mCount = aBuffer.Length(); mCount = aBuffer.Length();
#ifdef CSS_REPORT_PARSE_ERRORS #ifdef CSS_REPORT_PARSE_ERRORS
// If aURI is the same as mURI, no need to reget mFileName -- it // If aURI is different from mCachedURI, invalidate the filename cache.
// shouldn't have changed. if (aURI != mCachedURI) {
if (aURI != mURI) { mCachedURI = aURI;
mURI = aURI; mCachedFileName.Truncate();
if (aURI) {
aURI->GetSpec(mFileName);
} else {
mFileName.Adopt(NS_strdup("from DOM"));
}
} }
#endif // CSS_REPORT_PARSE_ERRORS #endif // CSS_REPORT_PARSE_ERRORS
mLineNumber = aLineNumber; mLineNumber = aLineNumber;
// Reset variables that we use to keep track of our progress through the input // Reset variables that we use to keep track of our progress through the input
@ -406,8 +436,19 @@ nsCSSScanner::OutputError()
do_CreateInstance(gScriptErrorFactory, &rv); do_CreateInstance(gScriptErrorFactory, &rv);
if (NS_SUCCEEDED(rv)) { if (NS_SUCCEEDED(rv)) {
// Update the cached filename if needed.
if (mCachedFileName.IsEmpty()) {
if (mCachedURI) {
nsAutoCString cFileName;
mCachedURI->GetSpec(cFileName);
CopyUTF8toUTF16(cFileName, mCachedFileName);
} else {
mCachedFileName.AssignLiteral("from DOM");
}
}
rv = errorObject->InitWithWindowID(mError, rv = errorObject->InitWithWindowID(mError,
NS_ConvertUTF8toUTF16(mFileName), mCachedFileName,
EmptyString(), EmptyString(),
mErrorLineNumber, mErrorLineNumber,
mErrorColNumber, mErrorColNumber,
@ -554,19 +595,37 @@ nsCSSScanner::ReportUnexpectedTokenParams(nsCSSToken& tok,
void void
nsCSSScanner::Close() nsCSSScanner::Close()
{
Reset();
// Schedule deferred cleanup for cached data. We want to strike a balance
// between performance and memory usage, so we only allow short-term caching.
#ifdef CSS_REPORT_PARSE_ERRORS
if (!mDeferredCleaner.IsPending()) {
mDeferredCleaner = new DeferredCleanupRunnable(this);
if (NS_FAILED(NS_DispatchToCurrentThread(mDeferredCleaner.get()))) {
// Peform the "deferred" cleanup immediately if the dispatch fails.
// This will also have the effect of clearing mDeferredCleaner.
nsCSSScanner::PerformDeferredCleanup();
}
}
#endif
}
void
nsCSSScanner::Reset()
{ {
mReadPointer = nullptr; mReadPointer = nullptr;
// Clean things up so we don't hold on to memory if our parser gets recycled. // Clean things up so we don't hold on to memory if our parser gets recycled.
#ifdef CSS_REPORT_PARSE_ERRORS #ifdef CSS_REPORT_PARSE_ERRORS
mFileName.Truncate();
mURI = nullptr;
mError.Truncate(); mError.Truncate();
mInnerWindowID = 0; mInnerWindowID = 0;
mWindowIDCached = false; mWindowIDCached = false;
mSheet = nullptr; mSheet = nullptr;
mLoader = nullptr; mLoader = nullptr;
#endif #endif
if (mPushback != mLocalPushback) { if (mPushback != mLocalPushback) {
delete [] mPushback; delete [] mPushback;
mPushback = mLocalPushback; mPushback = mLocalPushback;

View File

@ -18,6 +18,7 @@
// for #ifdef CSS_REPORT_PARSE_ERRORS // for #ifdef CSS_REPORT_PARSE_ERRORS
#include "nsXPIDLString.h" #include "nsXPIDLString.h"
#include "nsThreadUtils.h"
class nsIURI; class nsIURI;
// Token types // Token types
@ -91,6 +92,8 @@ struct nsCSSToken {
void AppendToString(nsString& aBuffer); void AppendToString(nsString& aBuffer);
}; };
class DeferredCleanupRunnable;
// CSS Scanner API. Used to tokenize an input stream using the CSS // CSS Scanner API. Used to tokenize an input stream using the CSS
// forward compatible tokenization rules. This implementation is // forward compatible tokenization rules. This implementation is
// private to this package and is only used internally by the css // private to this package and is only used internally by the css
@ -120,6 +123,9 @@ class nsCSSScanner {
} }
#ifdef CSS_REPORT_PARSE_ERRORS #ifdef CSS_REPORT_PARSE_ERRORS
// Clean up any reclaimable cached resources.
void PerformDeferredCleanup();
void AddToError(const nsSubstring& aErrorText); void AddToError(const nsSubstring& aErrorText);
void OutputError(); void OutputError();
void ClearError(); void ClearError();
@ -128,6 +134,8 @@ class nsCSSScanner {
void ReportUnexpected(const char* aMessage); void ReportUnexpected(const char* aMessage);
private: private:
void Reset();
void ReportUnexpectedParams(const char* aMessage, void ReportUnexpectedParams(const char* aMessage,
const PRUnichar** aParams, const PRUnichar** aParams,
uint32_t aParamsLength); uint32_t aParamsLength);
@ -211,8 +219,9 @@ protected:
uint32_t mRecordStartOffset; uint32_t mRecordStartOffset;
#ifdef CSS_REPORT_PARSE_ERRORS #ifdef CSS_REPORT_PARSE_ERRORS
nsXPIDLCString mFileName; nsRevocableEventPtr<DeferredCleanupRunnable> mDeferredCleaner;
nsCOMPtr<nsIURI> mURI; // Cached so we know to not refetch mFileName nsCOMPtr<nsIURI> mCachedURI; // Used to invalidate the cached filename.
nsString mCachedFileName;
uint32_t mErrorLineNumber, mColNumber, mErrorColNumber; uint32_t mErrorLineNumber, mColNumber, mErrorColNumber;
nsFixedString mError; nsFixedString mError;
PRUnichar mErrorBuf[200]; PRUnichar mErrorBuf[200];