import changes from `dev' branch of rmottola/Arctic-Fox:

- Bug 1160628. Speed up the URL constructor in the common case of no base URI. r=baku (ac5b2fe4e)
- Bug 1139005 - Need to check the return value of AutoJSAPI.init in canvas EncodeCallback (r=ayang) (df623ad1b)
- Bug 866846 - Use WAL journal mode for IndexedDB databases, r=janv. (bdcb676ac)
- Apply  at Bug 866846 - Use WAL journal mode for IndexedDB databases (breaks build!) (cf90bf698)
- Bug 1131766 - Use a simpler thread model for IndexedDB transactions, r=khuey. (broken build) (12c8980b8)
- Replay of: Bug 1138293 - Use malloc/free/realloc/calloc instead of moz_malloc Bug 1134923 - Remove NS_Alloc/NS_Realloc/NS_Free. r=nfroyd Bug 1182996 - Fix and add missing namespace comments. rs=ehsan Bug 1149420 - Make the IndexedDB permissions prompt work in e10s. r=b (f6818b630)
- Bug 1112702 - Make IndexedDB transactions non-durable by default, r=khuey. (4d76a07c9)
- Bug 1131776 - Use WITHOUT ROWID tables for IndexedDB, r=janv. (941399fa2)
This commit is contained in:
2020-07-10 23:59:28 +08:00
parent 1bada3664b
commit 515941185e
39 changed files with 9416 additions and 3508 deletions
+5 -3
View File
@@ -833,13 +833,15 @@ MainProcessRunnable::OpenCacheFileForWrite()
mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
NS_ENSURE_STATE(mQuotaObject);
if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize,
/* aTruncate */ false)) {
// If the request fails, it might be because mOrigin is using too much
// space (MaybeAllocateMoreSpace will not evict our own origin since it is
// space (MaybeUpdateSize will not evict our own origin since it is
// active). Try to make some space by evicting LRU entries until there is
// enough space.
EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata);
if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize,
/* aTruncate */ false)) {
mResult = JS::AsmJSCache_QuotaExceeded;
return NS_ERROR_FAILURE;
}
+36 -36
View File
@@ -18,6 +18,7 @@
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsIURL.h"
#include "nsContentUtils.h"
namespace mozilla {
namespace dom {
@@ -42,7 +43,7 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(URL)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
URL::URL(nsIURI* aURI)
URL::URL(already_AddRefed<nsIURI> aURI)
: mURI(aURI)
{
}
@@ -57,56 +58,55 @@ URL::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHa
URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
URL& aBase, ErrorResult& aRv)
{
nsresult rv;
nsCOMPtr<nsIIOService> ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv));
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return nullptr;
}
nsCOMPtr<nsIURI> uri;
rv = ioService->NewURI(NS_ConvertUTF16toUTF8(aUrl), nullptr, aBase.GetURI(),
getter_AddRefs(uri));
if (NS_FAILED(rv)) {
nsAutoString label(aUrl);
aRv.ThrowTypeError(MSG_INVALID_URL, &label);
return nullptr;
}
nsRefPtr<URL> url = new URL(uri);
return url.forget();
return Constructor(aUrl, aBase.GetURI(), aRv);
}
/* static */ already_AddRefed<URL>
URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
const nsAString& aBase, ErrorResult& aRv)
const Optional<nsAString>& aBase, ErrorResult& aRv)
{
nsresult rv;
nsCOMPtr<nsIIOService> ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv));
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return nullptr;
if (aBase.WasPassed()) {
return Constructor(aUrl, aBase.Value(), aRv);
}
return Constructor(aUrl, nullptr, aRv);
}
/* static */ already_AddRefed<URL>
URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
const nsAString& aBase, ErrorResult& aRv)
{
return Constructor(aUrl, aBase, aRv);
}
/* static */ already_AddRefed<URL>
URL::Constructor(const nsAString& aUrl, const nsAString& aBase,
ErrorResult& aRv)
{
nsCOMPtr<nsIURI> baseUri;
rv = ioService->NewURI(NS_ConvertUTF16toUTF8(aBase), nullptr, nullptr,
getter_AddRefs(baseUri));
if (NS_FAILED(rv)) {
nsAutoString label(aBase);
aRv.ThrowTypeError(MSG_INVALID_URL, &label);
nsresult rv = NS_NewURI(getter_AddRefs(baseUri), aBase, nullptr, nullptr,
nsContentUtils::GetIOService());
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.ThrowTypeError(MSG_INVALID_URL, &aBase);
return nullptr;
}
return Constructor(aUrl, baseUri, aRv);
}
/* static */
already_AddRefed<URL>
URL::Constructor(const nsAString& aUrl, nsIURI* aBase, ErrorResult& aRv)
{
nsCOMPtr<nsIURI> uri;
rv = ioService->NewURI(NS_ConvertUTF16toUTF8(aUrl), nullptr, baseUri,
getter_AddRefs(uri));
if (NS_FAILED(rv)) {
nsAutoString label(aUrl);
aRv.ThrowTypeError(MSG_INVALID_URL, &label);
nsresult rv = NS_NewURI(getter_AddRefs(uri), aUrl, nullptr, aBase,
nsContentUtils::GetIOService());
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.ThrowTypeError(MSG_INVALID_URL, &aUrl);
return nullptr;
}
nsRefPtr<URL> url = new URL(uri);
nsRefPtr<URL> url = new URL(uri.forget());
return url.forget();
}
+9 -1
View File
@@ -39,7 +39,7 @@ public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(URL)
explicit URL(nsIURI* aURI);
explicit URL(already_AddRefed<nsIURI> aURI);
// WebIDL methods
bool
@@ -49,8 +49,16 @@ public:
Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
URL& aBase, ErrorResult& aRv);
static already_AddRefed<URL>
Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
const Optional<nsAString>& aBase, ErrorResult& aRv);
// Versions of Constructor that we can share with workers and other code.
static already_AddRefed<URL>
Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
const nsAString& aBase, ErrorResult& aRv);
static already_AddRefed<URL>
Constructor(const nsAString& aUrl, const nsAString& aBase, ErrorResult& aRv);
static already_AddRefed<URL>
Constructor(const nsAString& aUrl, nsIURI* aBase, ErrorResult& aRv);
static void CreateObjectURL(const GlobalObject& aGlobal,
File& aBlob,
+1 -1
View File
@@ -145,7 +145,7 @@ ErrorResult::ThrowErrorWithMessage(va_list ap, const dom::ErrNum errorNumber,
MOZ_ASSERT(argCount <= 10);
argCount = std::min<uint16_t>(argCount, 10);
while (argCount--) {
message->mArgs.AppendElement(*va_arg(ap, nsString*));
message->mArgs.AppendElement(*va_arg(ap, const nsAString*));
}
mMessage = message;
#ifdef DEBUG
+2 -2
View File
@@ -1042,7 +1042,7 @@ WrapNewBindingNonWrapperCachedObject(JSContext* cx,
// We can end up here in all sorts of compartments, per above. Make
// sure to JS_WrapValue!
rval.set(JS::ObjectValue(*obj));
return JS_WrapValue(cx, rval);
return MaybeWrapObjectValue(cx, rval);
}
// Create a JSObject wrapping "value", for cases when "value" is a
@@ -1090,7 +1090,7 @@ WrapNewBindingNonWrapperCachedObject(JSContext* cx,
// We can end up here in all sorts of compartments, per above. Make
// sure to JS_WrapValue!
rval.set(JS::ObjectValue(*obj));
return JS_WrapValue(cx, rval);
return MaybeWrapObjectValue(cx, rval);
}
// Helper for smart pointers (nsRefPtr/nsCOMPtr).
+7 -3
View File
@@ -315,9 +315,13 @@ DataStoreDB::DatabaseOpened()
return rv;
}
nsRefPtr<IDBTransaction> txn = mDatabase->Transaction(mObjectStores,
mTransactionMode,
error);
StringOrStringSequence objectStores;
objectStores.RawSetAsStringSequence().AppendElements(mObjectStores);
nsRefPtr<IDBTransaction> txn;
error = mDatabase->Transaction(objectStores,
mTransactionMode,
getter_AddRefs(txn));
if (NS_WARN_IF(error.Failed())) {
return error.StealNSResult();
}
+3 -2
View File
@@ -563,8 +563,9 @@ HTMLCanvasElement::ToBlob(JSContext* aCx,
nsresult rv = blob->GetSize(&size);
if (NS_SUCCEEDED(rv)) {
AutoJSAPI jsapi;
jsapi.Init(mGlobal);
JS_updateMallocCounter(jsapi.cx(), size);
if (jsapi.Init(mGlobal)) {
JS_updateMallocCounter(jsapi.cx(), size);
}
}
nsRefPtr<File> newBlob = new File(mGlobal, blob->Impl());
File diff suppressed because it is too large Load Diff
+84 -42
View File
@@ -25,6 +25,7 @@
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/DOMStringList.h"
#include "mozilla/dom/DOMStringListBinding.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/IDBDatabaseBinding.h"
#include "mozilla/dom/IDBObjectStoreBinding.h"
@@ -650,73 +651,97 @@ IDBDatabase::DeleteObjectStore(const nsAString& aName, ErrorResult& aRv)
}
already_AddRefed<IDBTransaction>
IDBDatabase::Transaction(const nsAString& aStoreName,
IDBDatabase::Transaction(const StringOrStringSequence& aStoreNames,
IDBTransactionMode aMode,
ErrorResult& aRv)
{
AssertIsOnOwningThread();
Sequence<nsString> storeNames;
if (!storeNames.AppendElement(aStoreName)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
aRv.MightThrowJSException();
if (aMode == IDBTransactionMode::Readwriteflush &&
!IndexedDatabaseManager::ExperimentalFeaturesEnabled()) {
// Pretend that this mode doesn't exist. We don't have a way to annotate
// certain enum values as depending on preferences so we just duplicate the
// normal exception generation here.
ThreadsafeAutoJSContext cx;
// Disable any automatic error reporting that might be set up so that we
// can grab the exception object.
AutoForceSetExceptionOnContext forceExn(cx);
MOZ_ALWAYS_FALSE(
ThrowErrorMessage(cx,
MSG_INVALID_ENUM_VALUE,
"Argument 2 of IDBDatabase.transaction",
"readwriteflush",
"IDBTransactionMode"));
MOZ_ASSERT(JS_IsExceptionPending(cx));
JS::Rooted<JS::Value> exception(cx);
MOZ_ALWAYS_TRUE(JS_GetPendingException(cx, &exception));
aRv.ThrowJSException(cx, exception);
return nullptr;
}
return Transaction(storeNames, aMode, aRv);
nsRefPtr<IDBTransaction> transaction;
aRv = Transaction(aStoreNames, aMode, getter_AddRefs(transaction));
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return transaction.forget();
}
already_AddRefed<IDBTransaction>
IDBDatabase::Transaction(const Sequence<nsString>& aStoreNames,
nsresult
IDBDatabase::Transaction(const StringOrStringSequence& aStoreNames,
IDBTransactionMode aMode,
ErrorResult& aRv)
IDBTransaction** aTransaction)
{
AssertIsOnOwningThread();
if (NS_WARN_IF(aMode == IDBTransactionMode::Readwriteflush &&
!IndexedDatabaseManager::ExperimentalFeaturesEnabled())) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
if (QuotaManager::IsShuttingDown()) {
IDB_REPORT_INTERNAL_ERR();
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
return nullptr;
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
if (mClosed) {
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
return nullptr;
if (mClosed || RunningVersionChangeTransaction()) {
return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
}
if (RunningVersionChangeTransaction()) {
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
return nullptr;
nsAutoTArray<nsString, 1> stackSequence;
if (aStoreNames.IsString()) {
stackSequence.AppendElement(aStoreNames.GetAsString());
} else {
MOZ_ASSERT(aStoreNames.IsStringSequence());
if (aStoreNames.GetAsStringSequence().IsEmpty()) {
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
}
}
if (aStoreNames.IsEmpty()) {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return nullptr;
}
IDBTransaction::Mode mode;
switch (aMode) {
case IDBTransactionMode::Readonly:
mode = IDBTransaction::READ_ONLY;
break;
case IDBTransactionMode::Readwrite:
mode = IDBTransaction::READ_WRITE;
break;
case IDBTransactionMode::Versionchange:
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return nullptr;
default:
MOZ_CRASH("Unknown mode!");
}
const nsTArray<nsString>& storeNames =
aStoreNames.IsString() ?
stackSequence :
static_cast<const nsTArray<nsString>&>(aStoreNames.GetAsStringSequence());
MOZ_ASSERT(!storeNames.IsEmpty());
const nsTArray<ObjectStoreSpec>& objectStores = mSpec->objectStores();
const uint32_t nameCount = aStoreNames.Length();
const uint32_t nameCount = storeNames.Length();
nsTArray<nsString> sortedStoreNames;
sortedStoreNames.SetCapacity(nameCount);
// Check to make sure the object store names we collected actually exist.
for (uint32_t nameIndex = 0; nameIndex < nameCount; nameIndex++) {
const nsString& name = aStoreNames[nameIndex];
const nsString& name = storeNames[nameIndex];
bool found = false;
@@ -730,8 +755,7 @@ IDBDatabase::Transaction(const Sequence<nsString>& aStoreNames,
}
if (!found) {
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
return nullptr;
return NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR;
}
sortedStoreNames.InsertElementSorted(name);
@@ -744,12 +768,29 @@ IDBDatabase::Transaction(const Sequence<nsString>& aStoreNames,
}
}
IDBTransaction::Mode mode;
switch (aMode) {
case IDBTransactionMode::Readonly:
mode = IDBTransaction::READ_ONLY;
break;
case IDBTransactionMode::Readwrite:
mode = IDBTransaction::READ_WRITE;
break;
case IDBTransactionMode::Readwriteflush:
mode = IDBTransaction::READ_WRITE_FLUSH;
break;
case IDBTransactionMode::Versionchange:
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
default:
MOZ_CRASH("Unknown mode!");
}
nsRefPtr<IDBTransaction> transaction =
IDBTransaction::Create(this, sortedStoreNames, mode);
if (NS_WARN_IF(!transaction)) {
IDB_REPORT_INTERNAL_ERR();
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
return nullptr;
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
BackgroundTransactionChild* actor =
@@ -770,7 +811,8 @@ IDBDatabase::Transaction(const Sequence<nsString>& aStoreNames,
transaction->SetBackgroundActor(actor);
return transaction.forget();
transaction.forget(aTransaction);
return NS_OK;
}
StorageType
+8 -5
View File
@@ -31,7 +31,8 @@ namespace dom {
class File;
class DOMStringList;
struct IDBObjectStoreParameters;
template <typename> class Sequence;
template <class> class Optional;
class StringOrStringSequence;
namespace indexedDB {
@@ -212,15 +213,17 @@ public:
void
DeleteObjectStore(const nsAString& name, ErrorResult& aRv);
// This will be called from the DOM.
already_AddRefed<IDBTransaction>
Transaction(const nsAString& aStoreName,
Transaction(const StringOrStringSequence& aStoreNames,
IDBTransactionMode aMode,
ErrorResult& aRv);
already_AddRefed<IDBTransaction>
Transaction(const Sequence<nsString>& aStoreNames,
// This can be called from C++ to avoid JS exception.
nsresult
Transaction(const StringOrStringSequence& aStoreNames,
IDBTransactionMode aMode,
ErrorResult& aRv);
IDBTransaction** aTransaction);
StorageType
Storage() const;
+9 -2
View File
@@ -215,7 +215,9 @@ IDBTransaction::CreateVersionChange(
nsTArray<nsString> emptyObjectStoreNames;
nsRefPtr<IDBTransaction> transaction =
new IDBTransaction(aDatabase, emptyObjectStoreNames, VERSION_CHANGE);
new IDBTransaction(aDatabase,
emptyObjectStoreNames,
VERSION_CHANGE);
aOpenRequest->GetCallerLocation(transaction->mFilename,
&transaction->mLineNo);
@@ -251,7 +253,9 @@ IDBTransaction::Create(IDBDatabase* aDatabase,
MOZ_ASSERT(aDatabase);
aDatabase->AssertIsOnOwningThread();
MOZ_ASSERT(!aObjectStoreNames.IsEmpty());
MOZ_ASSERT(aMode == READ_ONLY || aMode == READ_WRITE);
MOZ_ASSERT(aMode == READ_ONLY ||
aMode == READ_WRITE ||
aMode == READ_WRITE_FLUSH);
nsRefPtr<IDBTransaction> transaction =
new IDBTransaction(aDatabase, aObjectStoreNames, aMode);
@@ -863,6 +867,9 @@ IDBTransaction::GetMode(ErrorResult& aRv) const
case READ_WRITE:
return IDBTransactionMode::Readwrite;
case READ_WRITE_FLUSH:
return IDBTransactionMode::Readwriteflush;
case VERSION_CHANGE:
return IDBTransactionMode::Versionchange;
+4 -1
View File
@@ -58,6 +58,7 @@ public:
{
READ_ONLY = 0,
READ_WRITE,
READ_WRITE_FLUSH,
VERSION_CHANGE,
// Only needed for IPC serialization helper, should never be used in code.
@@ -175,7 +176,9 @@ public:
IsWriteAllowed() const
{
AssertIsOnOwningThread();
return mMode == READ_WRITE || mMode == VERSION_CHANGE;
return mMode == READ_WRITE ||
mMode == READ_WRITE_FLUSH ||
mMode == VERSION_CHANGE;
}
bool
+1 -2
View File
@@ -646,8 +646,7 @@ IndexedDatabaseManager::FullSynchronous()
// static
bool
IndexedDatabaseManager::ExperimentalFeaturesEnabled(JSContext* aCx,
JSObject* aGlobal)
IndexedDatabaseManager::ExperimentalFeaturesEnabled()
{
if (NS_IsMainThread()) {
if (NS_WARN_IF(!GetOrCreate())) {
+7 -1
View File
@@ -107,7 +107,13 @@ public:
#endif
static bool
ExperimentalFeaturesEnabled(JSContext* aCx, JSObject* aGlobal);
ExperimentalFeaturesEnabled();
static bool
ExperimentalFeaturesEnabled(JSContext* /* aCx */, JSObject* /* aGlobal */)
{
return ExperimentalFeaturesEnabled();
}
already_AddRefed<FileManager>
GetFileManager(PersistenceType aPersistenceType,
+58 -48
View File
@@ -13,6 +13,7 @@
#include "mozilla/Endian.h"
#include "mozilla/FloatingPoint.h"
#include "mozIStorageStatement.h"
#include "mozIStorageValueArray.h"
#include "nsAlgorithm.h"
#include "nsJSUtils.h"
#include "ReportInternalError.h"
@@ -27,10 +28,10 @@ namespace indexedDB {
Basic strategy is the following
Numbers: 1 n n n n n n n n ("n"s are encoded 64bit float)
Dates: 2 n n n n n n n n ("n"s are encoded 64bit float)
Strings: 3 s s s ... 0 ("s"s are encoded unicode bytes)
Arrays: 4 i i i ... 0 ("i"s are encoded array items)
Numbers: 0x10 n n n n n n n n ("n"s are encoded 64bit float)
Dates: 0x20 n n n n n n n n ("n"s are encoded 64bit float)
Strings: 0x30 s s s ... 0 ("s"s are encoded unicode bytes)
Arrays: 0x50 i i i ... 0 ("i"s are encoded array items)
When encoding floats, 64bit IEEE 754 are almost sortable, except that
@@ -55,66 +56,59 @@ namespace indexedDB {
When encoding Arrays, we use an additional trick. Rather than adding a byte
containing the value '4' to indicate type, we instead add 4 to the next byte.
containing the value 0x50 to indicate type, we instead add 0x50 to the next byte.
This is usually the byte containing the type of the first item in the array.
So simple examples are
["foo"] 7 s s s 0 0 // 7 is 3 + 4
[1, 2] 5 n n n n n n n n 1 n n n n n n n n 0 // 5 is 1 + 4
["foo"] 0x80 s s s 0 0 // 0x80 is 0x30 + 0x50
[1, 2] 0x60 n n n n n n n n 1 n n n n n n n n 0 // 0x60 is 0x10 + 0x50
Whe do this iteratively if the first item in the array is also an array
[["foo"]] 11 s s s 0 0 0
[["foo"]] 0xA0 s s s 0 0 0
However, to avoid overflow in the byte, we only do this 3 times. If the first
item in an array is an array, and that array also has an array as first item,
we simply write out the total value accumulated so far and then follow the
"normal" rules.
[[["foo"]]] 12 3 s s s 0 0 0 0
[[["foo"]]] 0xF0 0x30 s s s 0 0 0 0
There is another edge case that can happen though, which is that the array
doesn't have a first item to which we can add 4 to the type. Instead the
doesn't have a first item to which we can add 0x50 to the type. Instead the
next byte would normally be the array terminator (per basic-strategy table)
so we simply add the 4 there.
so we simply add the 0x50 there.
[[]] 8 0 // 8 is 4 + 4 + 0
[] 4 // 4 is 4 + 0
[[], "foo"] 8 3 s s s 0 0 // 8 is 4 + 4 + 0
[[]] 0xA0 0 // 0xA0 is 0x50 + 0x50 + 0
[] 0x50 // 0x50 is 0x50 + 0
[[], "foo"] 0xA0 0x30 s s s 0 0 // 0xA0 is 0x50 + 0x50 + 0
Note that the max-3-times rule kicks in before we get a chance to add to the
array terminator
[[[]]] 12 0 0 0 // 12 is 4 + 4 + 4
We could use a much higher number than 3 at no complexity or performance cost,
however it seems unlikely that it'll make a practical difference, and the low
limit makes testing eaiser.
[[[]]] 0xF0 0 0 0 // 0xF0 is 0x50 + 0x50 + 0x50
As a final optimization we do a post-encoding step which drops all 0s at the
end of the encoded buffer.
"foo" // 3 s s s
1 // 1 bf f0
["a", "b"] // 7 s 3 s
[1, 2] // 5 bf f0 0 0 0 0 0 0 1 c0
[[]] // 8
"foo" // 0x30 s s s
1 // 0x10 bf f0
["a", "b"] // 0x80 s 0 0x30 s
[1, 2] // 0x60 bf f0 0 0 0 0 0 0 0x10 c0
[[]] // 0x80
*/
const int MaxArrayCollapse = 3;
const int MaxRecursionDepth = 256;
nsresult
Key::EncodeJSValInternal(JSContext* aCx, JS::Handle<JS::Value> aVal,
uint8_t aTypeOffset, uint16_t aRecursionDepth)
{
NS_ENSURE_TRUE(aRecursionDepth < MaxRecursionDepth, NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
static_assert(eMaxType * MaxArrayCollapse < 256,
static_assert(eMaxType * kMaxArrayCollapse < 256,
"Unable to encode jsvals.");
if (NS_WARN_IF(aRecursionDepth == kMaxRecursionDepth)) {
return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
}
if (aVal.isString()) {
nsAutoJSString str;
if (!str.init(aCx, aVal)) {
@@ -139,12 +133,12 @@ Key::EncodeJSValInternal(JSContext* aCx, JS::Handle<JS::Value> aVal,
if (JS_IsArrayObject(aCx, obj)) {
aTypeOffset += eMaxType;
if (aTypeOffset == eMaxType * MaxArrayCollapse) {
if (aTypeOffset == eMaxType * kMaxArrayCollapse) {
mBuffer.Append(aTypeOffset);
aTypeOffset = 0;
}
NS_ASSERTION((aTypeOffset % eMaxType) == 0 &&
aTypeOffset < (eMaxType * MaxArrayCollapse),
aTypeOffset < (eMaxType * kMaxArrayCollapse),
"Wrong typeoffset");
uint32_t length;
@@ -192,7 +186,9 @@ Key::DecodeJSValInternal(const unsigned char*& aPos, const unsigned char* aEnd,
JSContext* aCx, uint8_t aTypeOffset, JS::MutableHandle<JS::Value> aVal,
uint16_t aRecursionDepth)
{
NS_ENSURE_TRUE(aRecursionDepth < MaxRecursionDepth, NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
if (NS_WARN_IF(aRecursionDepth == kMaxRecursionDepth)) {
return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
}
if (*aPos - aTypeOffset >= eArray) {
JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, 0));
@@ -204,7 +200,7 @@ Key::DecodeJSValInternal(const unsigned char*& aPos, const unsigned char* aEnd,
aTypeOffset += eMaxType;
if (aTypeOffset == eMaxType * MaxArrayCollapse) {
if (aTypeOffset == eMaxType * kMaxArrayCollapse) {
++aPos;
aTypeOffset = 0;
}
@@ -331,10 +327,9 @@ nsresult
Key::DecodeJSVal(const unsigned char*& aPos,
const unsigned char* aEnd,
JSContext* aCx,
uint8_t aTypeOffset,
JS::MutableHandle<JS::Value> aVal)
{
return DecodeJSValInternal(aPos, aEnd, aCx, aTypeOffset, aVal, 0);
return DecodeJSValInternal(aPos, aEnd, aCx, 0, aVal, 0);
}
// static
@@ -466,16 +461,14 @@ nsresult
Key::SetFromStatement(mozIStorageStatement* aStatement,
uint32_t aIndex)
{
uint8_t* data;
uint32_t dataLength = 0;
return SetFromSource(aStatement, aIndex);
}
nsresult rv = aStatement->GetBlob(aIndex, &dataLength, &data);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
mBuffer.Adopt(
reinterpret_cast<char*>(const_cast<uint8_t*>(data)), dataLength);
return NS_OK;
nsresult
Key::SetFromValueArray(mozIStorageValueArray* aValues,
uint32_t aIndex)
{
return SetFromSource(aValues, aIndex);
}
nsresult
@@ -509,7 +502,7 @@ Key::ToJSVal(JSContext* aCx,
}
const unsigned char* pos = BufferStart();
nsresult rv = DecodeJSVal(pos, BufferEnd(), aCx, 0, aVal);
nsresult rv = DecodeJSVal(pos, BufferEnd(), aCx, aVal);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@@ -543,6 +536,23 @@ Key::AppendItem(JSContext* aCx, bool aFirstOfArray, JS::Handle<JS::Value> aVal)
return NS_OK;
}
template <typename T>
nsresult
Key::SetFromSource(T* aSource, uint32_t aIndex)
{
const uint8_t* data;
uint32_t dataLength = 0;
nsresult rv = aSource->GetSharedBlob(aIndex, &dataLength, &data);
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
mBuffer.Assign(reinterpret_cast<const char*>(data), dataLength);
return NS_OK;
}
#ifdef DEBUG
void
+29 -14
View File
@@ -11,6 +11,7 @@
#include "nsString.h"
class mozIStorageStatement;
class mozIStorageValueArray;
namespace IPC {
@@ -29,11 +30,28 @@ class Key
nsCString mBuffer;
public:
enum {
eTerminator = 0,
eFloat = 0x10,
eDate = 0x20,
eString = 0x30,
eArray = 0x50,
eMaxType = eArray
};
static const uint8_t kMaxArrayCollapse = uint8_t(3);
static const uint8_t kMaxRecursionDepth = uint8_t(64);
Key()
{
Unset();
}
explicit
Key(const nsACString& aBuffer)
: mBuffer(aBuffer)
{ }
Key&
operator=(const nsAString& aString)
{
@@ -111,25 +129,25 @@ public:
bool
IsFloat() const
{
return !IsUnset() && mBuffer.First() == eFloat;
return !IsUnset() && *BufferStart() == eFloat;
}
bool
IsDate() const
{
return !IsUnset() && mBuffer.First() == eDate;
return !IsUnset() && *BufferStart() == eDate;
}
bool
IsString() const
{
return !IsUnset() && mBuffer.First() == eString;
return !IsUnset() && *BufferStart() == eString;
}
bool
IsArray() const
{
return !IsUnset() && mBuffer.First() >= eArray;
return !IsUnset() && *BufferStart() >= eArray;
}
double
@@ -208,6 +226,9 @@ public:
nsresult
SetFromStatement(mozIStorageStatement* aStatement, uint32_t aIndex);
nsresult
SetFromValueArray(mozIStorageValueArray* aValues, uint32_t aIndex);
static int16_t
CompareKeys(Key& aFirst, Key& aSecond)
{
@@ -237,15 +258,6 @@ private:
return reinterpret_cast<const unsigned char*>(mBuffer.EndReading());
}
enum {
eTerminator = 0,
eFloat = 1,
eDate = 2,
eString = 3,
eArray = 4,
eMaxType = eArray
};
// Encoding helper. Trims trailing zeros off of mBuffer as a post-processing
// step.
void
@@ -275,7 +287,6 @@ private:
DecodeJSVal(const unsigned char*& aPos,
const unsigned char* aEnd,
JSContext* aCx,
uint8_t aTypeOffset,
JS::MutableHandle<JS::Value> aVal);
static void
@@ -300,6 +311,10 @@ private:
JS::MutableHandle<JS::Value> aVal,
uint16_t aRecursionDepth);
template <typename T>
nsresult
SetFromSource(T* aSource, uint32_t aIndex);
void
Assert(bool aCondition) const
#ifdef DEBUG
+4 -1
View File
@@ -126,6 +126,9 @@ public:
case IDBTransaction::READ_WRITE:
AppendLiteral("\"readwrite\"");
break;
case IDBTransaction::READ_WRITE_FLUSH:
AppendLiteral("\"readwriteflush\"");
break;
case IDBTransaction::VERSION_CHANGE:
AppendLiteral("\"versionchange\"");
break;
@@ -279,7 +282,7 @@ LoggingHelper(bool aUseProfiler, const char* aFmt, ...)
PRLogModuleInfo* logModule = IndexedDatabaseManager::GetLoggingModule();
MOZ_ASSERT(logModule);
static const PRLogModuleLevel logLevel = PR_LOG_DEBUG;
static const PRLogModuleLevel logLevel = PR_LOG_WARNING;
if (PR_LOG_TEST(logModule, logLevel) ||
(aUseProfiler && profiler_is_active())) {
File diff suppressed because it is too large Load Diff
-159
View File
@@ -1,159 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_indexeddb_transactionthreadpool_h__
#define mozilla_dom_indexeddb_transactionthreadpool_h__
#include "mozilla/Attributes.h"
#include "nsAutoPtr.h"
#include "nsClassHashtable.h"
#include "nsCOMPtr.h"
#include "nsHashKeys.h"
#include "nsISupportsImpl.h"
#include "nsTArray.h"
struct nsID;
class nsIEventTarget;
class nsIRunnable;
class nsIThreadPool;
namespace mozilla {
namespace dom {
namespace indexedDB {
class TransactionThreadPool final
{
class FinishTransactionRunnable;
friend class FinishTransactionRunnable;
class TransactionQueue;
friend class TransactionQueue;
struct DatabaseTransactionInfo;
struct DatabasesCompleteCallback;
struct TransactionInfo;
struct TransactionInfoPair;
nsCOMPtr<nsIThreadPool> mThreadPool;
nsCOMPtr<nsIEventTarget> mOwningThread;
nsClassHashtable<nsCStringHashKey, DatabaseTransactionInfo>
mTransactionsInProgress;
nsTArray<nsAutoPtr<DatabasesCompleteCallback>> mCompleteCallbacks;
uint64_t mNextTransactionId;
bool mShutdownRequested;
bool mShutdownComplete;
public:
class FinishCallback;
static already_AddRefed<TransactionThreadPool> Create();
uint64_t NextTransactionId();
void Start(uint64_t aTransactionId,
const nsACString& aDatabaseId,
const nsTArray<nsString>& aObjectStoreNames,
uint16_t aMode,
const nsID& aBackgroundChildLoggingId,
int64_t aLoggingSerialNumber,
nsIRunnable* aRunnable);
void Dispatch(uint64_t aTransactionId,
const nsACString& aDatabaseId,
nsIRunnable* aRunnable,
bool aFinish,
FinishCallback* aFinishCallback);
void WaitForDatabasesToComplete(nsTArray<nsCString>& aDatabaseIds,
nsIRunnable* aCallback);
NS_INLINE_DECL_REFCOUNTING(TransactionThreadPool)
void Shutdown();
void AssertIsOnOwningThread() const
#ifdef DEBUG
;
#else
{ }
#endif
private:
static PLDHashOperator
CollectTransactions(const uint64_t& aTransactionId,
TransactionInfo* aValue,
void* aUserArg);
static PLDHashOperator
FindTransaction(const uint64_t& aTransactionId,
TransactionInfo* aValue,
void* aUserArg);
static PLDHashOperator
MaybeUnblockTransaction(nsPtrHashKey<TransactionInfo>* aKey,
void* aUserArg);
TransactionThreadPool();
// Reference counted.
~TransactionThreadPool();
nsresult Init();
void Cleanup();
void FinishTransaction(uint64_t aTransactionId,
const nsACString& aDatabaseId,
const nsTArray<nsString>& aObjectStoreNames,
uint16_t aMode);
TransactionQueue* GetQueueForTransaction(uint64_t aTransactionId,
const nsACString& aDatabaseId);
TransactionQueue& CreateQueueForTransaction(
uint64_t aTransactionId,
const nsACString& aDatabaseId,
const nsTArray<nsString>& aObjectStoreNames,
uint16_t aMode,
const nsID& aBackgroundChildLoggingId,
int64_t aLoggingSerialNumber);
bool MaybeFireCallback(DatabasesCompleteCallback* aCallback);
void CleanupAsync();
};
class NS_NO_VTABLE TransactionThreadPool::FinishCallback
{
public:
NS_IMETHOD_(MozExternalRefCountType)
AddRef() = 0;
NS_IMETHOD_(MozExternalRefCountType)
Release() = 0;
// Called on the owning thread before any additional transactions are
// unblocked.
virtual void
TransactionFinishedBeforeUnblock() = 0;
// Called on the owning thread after additional transactions may have been
// unblocked.
virtual void
TransactionFinishedAfterUnblock() = 0;
protected:
FinishCallback()
{ }
};
} // namespace indexedDB
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_indexeddb_transactionthreadpool_h__
-1
View File
@@ -64,7 +64,6 @@ UNIFIED_SOURCES += [
'KeyPath.cpp',
'PermissionRequestBase.cpp',
'ReportInternalError.cpp',
'TransactionThreadPool.cpp',
]
SOURCES += [
+3
View File
@@ -69,6 +69,7 @@ support-files =
unit/test_put_get_values.js
unit/test_put_get_values_autoIncrement.js
unit/test_readonly_transactions.js
unit/test_readwriteflush_disabled.js
unit/test_remove_index.js
unit/test_remove_objectStore.js
unit/test_request_readyState.js
@@ -318,6 +319,8 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
[test_readonly_transactions.html]
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
[test_readwriteflush_disabled.html]
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
[test_remove_index.html]
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
[test_remove_objectStore.html]
@@ -0,0 +1,19 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html>
<head>
<title>Indexed Database Property Test</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="text/javascript;version=1.7" src="unit/test_readwriteflush_disabled.js"></script>
<script type="text/javascript;version=1.7" src="helpers.js"></script>
</head>
<body onload="runTest();"></body>
</html>
Binary file not shown.
+6 -2
View File
@@ -61,7 +61,9 @@ function testSteps()
ok(true, "clear should throw on READ_ONLY transactions");
}
request = db.transaction("foo", "readwrite").objectStore("foo").clear();
request = db.transaction("foo", "readwriteflush")
.objectStore("foo")
.clear();
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
event = yield undefined;
@@ -81,7 +83,9 @@ function testSteps()
}
yield undefined;
request = db.transaction("foo", "readwrite").objectStore("foo").add({});
request = db.transaction("foo", "readwrite")
.objectStore("foo")
.add({});
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
event = yield undefined;
@@ -0,0 +1,72 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
let disableWorkerTest = "Need a way to set temporary prefs from a worker";
let testGenerator = testSteps();
function testSteps()
{
const name =
this.window ? window.location.pathname : "test_readwriteflush_disabled.js";
info("Resetting experimental pref");
if (this.window) {
SpecialPowers.pushPrefEnv(
{
"set": [
["dom.indexedDB.experimental", false]
]
},
continueToNextStep
);
yield undefined;
} else {
resetExperimental();
}
info("Opening database");
let request = indexedDB.open(name);
request.onerror = errorHandler;
request.onupgradeneeded = continueToNextStepSync;
request.onsuccess = unexpectedSuccessHandler;
yield undefined;
// upgradeneeded
request.onupgradeneeded = unexpectedSuccessHandler;
request.onsuccess = continueToNextStepSync;
info("Creating objectStore");
request.result.createObjectStore(name);
yield undefined;
// success
let db = request.result;
info("Attempting to create a 'readwriteflush' transaction");
let exception;
try {
let transaction = db.transaction(name, "readwriteflush");
} catch (e) {
exception = e;
}
ok(exception, "'readwriteflush' transaction threw");
ok(exception instanceof Error, "exception is an Error object");
is(exception.message,
"Argument 2 of IDBDatabase.transaction 'readwriteflush' is not a valid " +
"value for enumeration IDBTransactionMode.",
"exception has the correct message");
finishTest();
yield undefined;
}
@@ -0,0 +1,336 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
var testGenerator = testSteps();
function testSteps()
{
const testName = "schema18upgrade";
const testKeys = [
-1/0,
-1.7e308,
-10000,
-2,
-1.5,
-1,
-1.00001e-200,
-1e-200,
0,
1e-200,
1.00001e-200,
1,
2,
10000,
1.7e308,
1/0,
new Date("1750-01-02"),
new Date("1800-12-31T12:34:56.001Z"),
new Date(-1000),
new Date(-10),
new Date(-1),
new Date(0),
new Date(1),
new Date(2),
new Date(1000),
new Date("1971-01-01"),
new Date("1971-01-01T01:01:01Z"),
new Date("1971-01-01T01:01:01.001Z"),
new Date("1971-01-01T01:01:01.01Z"),
new Date("1971-01-01T01:01:01.1Z"),
new Date("1980-02-02"),
new Date("3333-03-19T03:33:33.333Z"),
"",
"\x00",
"\x00\x00",
"\x00\x01",
"\x01",
"\x02",
"\x03",
"\x04",
"\x07",
"\x08",
"\x0F",
"\x10",
"\x1F",
"\x20",
"01234",
"\x3F",
"\x40",
"A",
"A\x00",
"A1",
"ZZZZ",
"a",
"a\x00",
"aa",
"azz",
"}",
"\x7E",
"\x7F",
"\x80",
"\xFF",
"\u0100",
"\u01FF",
"\u0200",
"\u03FF",
"\u0400",
"\u07FF",
"\u0800",
"\u0FFF",
"\u1000",
"\u1FFF",
"\u2000",
"\u3FFF",
"\u4000",
"\u7FFF",
"\u8000",
"\uD800",
"\uD800a",
"\uD800\uDC01",
"\uDBFF",
"\uDC00",
"\uDFFF\uD800",
"\uFFFE",
"\uFFFF",
"\uFFFF\x00",
"\uFFFFZZZ",
[],
[-1/0],
[-1],
[0],
[1],
[1, "a"],
[1, []],
[1, [""]],
[2, 3],
[2, 3.0000000000001],
[12, [[]]],
[12, [[[]]]],
[12, [[[""]]]],
[12, [[["foo"]]]],
[12, [[[[[3]]]]]],
[12, [[[[[[3]]]]]]],
[12, [[[[[[3],[[[[[4.2]]]]]]]]]]],
[new Date(-1)],
[new Date(1)],
[""],
["", [[]]],
["", [[[]]]],
["abc"],
["abc", "def"],
["abc\x00"],
["abc\x00", "\x00\x01"],
["abc\x00", "\x00def"],
["abc\x00\x00def"],
["x", [[]]],
["x", [[[]]]],
[[]],
[[],"foo"],
[[],[]],
[[[]]],
[[[]], []],
[[[]], [[]]],
[[[]], [[1]]],
[[[]], [[[]]]],
[[[1]]],
[[[[]], []]],
];
const testString =
"abcdefghijklmnopqrstuvwxyz0123456789`~!@#$%^&*()-_+=,<.>/?\\|";
clearAllDatabases(continueToNextStepSync);
yield undefined;
info("Installing profile");
installPackagedProfile(testName + "_profile");
info("Opening database with no version");
let request = indexedDB.open(testName);
request.onerror = errorHandler;
request.onupgradeneeded = unexpectedSuccessHandler;
request.onsuccess = grabEventAndContinueHandler;
let event = yield undefined;
let db = event.target.result;
is(db.version, 1, "Correct db version");
let transaction = db.transaction(testName);
transaction.oncomplete = grabEventAndContinueHandler;
let objectStore = transaction.objectStore(testName);
let index = objectStore.index("uniqueIndex");
info("Starting 'uniqueIndex' cursor");
let keyIndex = 0;
index.openCursor().onsuccess = event => {
let cursor = event.target.result;
if (cursor) {
info("Comparing " + JSON.stringify(cursor.primaryKey) + " to " +
JSON.stringify(testKeys[cursor.key]) +
" [" + cursor.key + "]");
is(indexedDB.cmp(cursor.primaryKey, testKeys[cursor.key]), 0,
"Keys compare equally via 'indexedDB.cmp'");
is(compareKeys(cursor.primaryKey, testKeys[cursor.key]), true,
"Keys compare equally via 'compareKeys'");
let indexProperty = cursor.value.index;
is(Array.isArray(indexProperty), true, "index property is Array");
is(indexProperty[0], cursor.key, "index property first item correct");
is(indexProperty[1], cursor.key + 1, "index property second item correct");
is(cursor.key, keyIndex, "Cursor key property is correct");
is(cursor.value.testString, testString, "Test string compared equally");
keyIndex++;
cursor.continue();
}
};
yield undefined;
is(keyIndex, testKeys.length, "Saw all keys");
transaction = db.transaction(testName, "readwrite");
transaction.oncomplete = grabEventAndContinueHandler;
objectStore = transaction.objectStore(testName);
index = objectStore.index("index");
info("Getting all 'index' keys");
index.getAllKeys().onsuccess = grabEventAndContinueHandler;
event = yield undefined;
is(event.target.result.length, testKeys.length * 2, "Got all keys");
info("Starting objectStore cursor");
objectStore.openCursor().onsuccess = event => {
let cursor = event.target.result;
if (cursor) {
let value = cursor.value;
is(value.testString, testString, "Test string compared equally");
delete value.index;
cursor.update(value);
cursor.continue();
} else {
continueToNextStepSync();
}
};
yield undefined;
info("Getting all 'index' keys");
index.getAllKeys().onsuccess = grabEventAndContinueHandler;
event = yield undefined;
is(event.target.result.length, 0, "Removed all keys");
yield undefined;
db.close();
info("Opening database with new version");
request = indexedDB.open(testName, 2);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
request.onsuccess = grabEventAndContinueHandler;
event = yield undefined;
info("Deleting indexes");
objectStore = event.target.transaction.objectStore(testName);
objectStore.deleteIndex("index");
objectStore.deleteIndex("uniqueIndex");
event = yield undefined;
db = event.target.result;
transaction = db.transaction(testName, "readwrite");
transaction.oncomplete = grabEventAndContinueHandler;
info("Starting objectStore cursor");
objectStore = transaction.objectStore(testName);
objectStore.openCursor().onsuccess = event => {
let cursor = event.target.result;
if (cursor) {
let value = cursor.value;
is(value.testString, testString, "Test string compared equally");
value.index = value.keyPath;
cursor.update(value);
cursor.continue();
}
};
event = yield undefined;
db.close();
info("Opening database with new version");
request = indexedDB.open(testName, 3);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;
request.onsuccess = grabEventAndContinueHandler;
event = yield undefined;
info("Creating indexes");
objectStore = event.target.transaction.objectStore(testName);
objectStore.createIndex("index", "index");
event = yield undefined;
db = event.target.result;
transaction = db.transaction(testName);
transaction.oncomplete = grabEventAndContinueHandler;
objectStore = transaction.objectStore(testName);
index = objectStore.index("index");
info("Starting 'index' cursor");
keyIndex = 0;
index.openCursor().onsuccess = event => {
let cursor = event.target.result;
if (cursor) {
is(indexedDB.cmp(cursor.primaryKey, testKeys[keyIndex]), 0,
"Keys compare equally via 'indexedDB.cmp'");
is(compareKeys(cursor.primaryKey, testKeys[keyIndex]), true,
"Keys compare equally via 'compareKeys'");
is(indexedDB.cmp(cursor.key, testKeys[keyIndex]), 0,
"Keys compare equally via 'indexedDB.cmp'");
is(compareKeys(cursor.key, testKeys[keyIndex]), true,
"Keys compare equally via 'compareKeys'");
let indexProperty = cursor.value.index;
is(indexedDB.cmp(indexProperty, testKeys[keyIndex]), 0,
"Keys compare equally via 'indexedDB.cmp'");
is(compareKeys(indexProperty, testKeys[keyIndex]), true,
"Keys compare equally via 'compareKeys'");
is(cursor.value.testString, testString, "Test string compared equally");
keyIndex++;
cursor.continue();
}
};
yield undefined;
is(keyIndex, testKeys.length, "Added all keys again");
finishTest();
yield undefined;
}
@@ -3,6 +3,14 @@
* http://creativecommons.org/publicdomain/zero/1.0/
*/
var disableWorkerTest =
"This test requires a precise 'executeSoon()' to complete reliably. On a " +
"worker 'executeSoon()' currently uses 'setTimeout()', and that switches " +
"to the timer thread and back before completing. That gives the IndexedDB " +
"transaction thread time to fully complete transactions and to place " +
"'complete' events in the worker thread's queue before the timer event, " +
"causing ordering problems in the spot marked 'Worker Fails Here' below.";
var testGenerator = testSteps();
var abortFired = false;
@@ -330,6 +338,8 @@ function testSteps()
// During COMMITTING
transaction = db.transaction("foo", "readwrite");
transaction.objectStore("foo").put({hello: "world"}, 1).onsuccess = function(event) {
// Worker Fails Here! Due to the thread switching of 'executeSoon()' the
// transaction can commit and fire a 'complete' event before we continue.
continueToNextStep();
};
yield undefined;
@@ -15,6 +15,7 @@ support-files =
GlobalObjectsComponent.manifest
GlobalObjectsModule.jsm
GlobalObjectsSandbox.js
schema18upgrade_profile.zip
xpcshell-shared.ini
[include:xpcshell-shared.ini]
@@ -28,6 +29,8 @@ skip-if = toolkit == 'android'
# disabled for the moment.
skip-if = true
[test_lowDiskSpace.js]
[test_readwriteflush_disabled.js]
[test_schema18upgrade.js]
[test_temporary_storage.js]
# bug 951017: intermittent failure on Android x86 emulator
skip-if = os == "android" && processor == "x86"
+6 -3
View File
@@ -23,7 +23,7 @@ FileQuotaStream<FileStreamBase>::SetEOF()
nsresult rv = FileStreamBase::Tell(&offset);
NS_ENSURE_SUCCESS(rv, rv);
mQuotaObject->UpdateSize(offset);
mQuotaObject->MaybeUpdateSize(offset, /* aTruncate */ true);
}
return NS_OK;
@@ -56,7 +56,7 @@ FileQuotaStream<FileStreamBase>::DoOpen()
NS_ENSURE_SUCCESS(rv, rv);
if (mQuotaObject && (FileStreamBase::mOpenParams.ioFlags & PR_TRUNCATE)) {
mQuotaObject->UpdateSize(0);
mQuotaObject->MaybeUpdateSize(0, /* aTruncate */ true);
}
return NS_OK;
@@ -75,8 +75,11 @@ FileQuotaStreamWithWrite<FileStreamBase>::Write(const char* aBuf,
rv = FileStreamBase::Tell(&offset);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(INT64_MAX - offset >= int64_t(aCount));
if (!FileQuotaStreamWithWrite::
mQuotaObject->MaybeAllocateMoreSpace(offset, aCount)) {
mQuotaObject->MaybeUpdateSize(offset + int64_t(aCount),
/* aTruncate */ false)) {
return NS_ERROR_FILE_NO_DEVICE_SPACE;
}
}
+98 -54
View File
@@ -6,11 +6,55 @@
#include "QuotaObject.h"
#include "mozilla/TypeTraits.h"
#include "QuotaManager.h"
#include "Utilities.h"
USING_QUOTA_NAMESPACE
namespace {
template <typename T, bool = mozilla::IsUnsigned<T>::value>
struct IntChecker
{
static void
Assert(T aInt)
{
static_assert(mozilla::IsIntegral<T>::value, "Not an integer!");
MOZ_ASSERT(aInt >= 0);
}
};
template <typename T>
struct IntChecker<T, true>
{
static void
Assert(T aInt)
{
static_assert(mozilla::IsIntegral<T>::value, "Not an integer!");
}
};
template <typename T>
void
AssertNoOverflow(uint64_t aDest, T aArg)
{
IntChecker<T>::Assert(aDest);
IntChecker<T>::Assert(aArg);
MOZ_ASSERT(UINT64_MAX - aDest >= uint64_t(aArg));
}
template <typename T, typename U>
void
AssertNoUnderflow(T aDest, U aArg)
{
IntChecker<T>::Assert(aDest);
IntChecker<T>::Assert(aArg);
MOZ_ASSERT(uint64_t(aDest) >= uint64_t(aArg));
}
} // anonymous namespace
void
QuotaObject::AddRef()
{
@@ -61,71 +105,75 @@ QuotaObject::Release()
delete this;
}
void
QuotaObject::UpdateSize(int64_t aSize)
bool
QuotaObject::MaybeUpdateSize(int64_t aSize, bool aTruncate)
{
QuotaManager* quotaManager = QuotaManager::Get();
NS_ASSERTION(quotaManager, "Shouldn't be null!");
MOZ_ASSERT(quotaManager);
MutexAutoLock lock(quotaManager->mQuotaMutex);
if (!mOriginInfo) {
return;
if (mSize == aSize) {
return true;
}
GroupInfo* groupInfo = mOriginInfo->mGroupInfo;
quotaManager->mTemporaryStorageUsage -= mSize;
groupInfo->mUsage -= mSize;
mOriginInfo->mUsage -= mSize;
mSize = aSize;
mOriginInfo->mUsage += mSize;
groupInfo->mUsage += mSize;
quotaManager->mTemporaryStorageUsage += mSize;
}
bool
QuotaObject::MaybeAllocateMoreSpace(int64_t aOffset, int32_t aCount)
{
int64_t end = aOffset + aCount;
QuotaManager* quotaManager = QuotaManager::Get();
NS_ASSERTION(quotaManager, "Shouldn't be null!");
MutexAutoLock lock(quotaManager->mQuotaMutex);
if (mSize >= end || !mOriginInfo) {
if (!mOriginInfo) {
mSize = aSize;
return true;
}
GroupInfo* groupInfo = mOriginInfo->mGroupInfo;
MOZ_ASSERT(groupInfo);
if (mSize > aSize) {
if (aTruncate) {
const int64_t delta = mSize - aSize;
AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, delta);
quotaManager->mTemporaryStorageUsage -= delta;
AssertNoUnderflow(groupInfo->mUsage, delta);
groupInfo->mUsage -= delta;
AssertNoUnderflow(mOriginInfo->mUsage, delta);
mOriginInfo->mUsage -= delta;
mSize = aSize;
}
return true;
}
MOZ_ASSERT(mSize < aSize);
nsRefPtr<GroupInfo> complementaryGroupInfo =
groupInfo->mGroupInfoPair->LockedGetGroupInfo(
ComplementaryPersistenceType(groupInfo->mPersistenceType));
uint64_t delta = end - mSize;
uint64_t delta = aSize - mSize;
AssertNoOverflow(mOriginInfo->mUsage, delta);
uint64_t newUsage = mOriginInfo->mUsage + delta;
// Temporary storage has no limit for origin usage (there's a group and the
// global limit though).
AssertNoOverflow(groupInfo->mUsage, delta);
uint64_t newGroupUsage = groupInfo->mUsage + delta;
uint64_t groupUsage = groupInfo->mUsage;
if (complementaryGroupInfo) {
AssertNoOverflow(groupUsage, complementaryGroupInfo->mUsage);
groupUsage += complementaryGroupInfo->mUsage;
}
// Temporary storage has a hard limit for group usage (20 % of the global
// limit).
AssertNoOverflow(groupUsage, delta);
if (groupUsage + delta > quotaManager->GetGroupLimit()) {
return false;
}
AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta);
uint64_t newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage +
delta;
@@ -181,17 +229,22 @@ QuotaObject::MaybeAllocateMoreSpace(int64_t aOffset, int32_t aCount)
// We unlocked and relocked several times so we need to recompute all the
// essential variables and recheck the group limit.
delta = end - mSize;
AssertNoUnderflow(aSize, mSize);
delta = aSize - mSize;
AssertNoOverflow(mOriginInfo->mUsage, delta);
newUsage = mOriginInfo->mUsage + delta;
AssertNoOverflow(groupInfo->mUsage, delta);
newGroupUsage = groupInfo->mUsage + delta;
groupUsage = groupInfo->mUsage;
if (complementaryGroupInfo) {
AssertNoOverflow(groupUsage, complementaryGroupInfo->mUsage);
groupUsage += complementaryGroupInfo->mUsage;
}
AssertNoOverflow(groupUsage, delta);
if (groupUsage + delta > quotaManager->GetGroupLimit()) {
// Unfortunately some other thread increased the group usage in the
// meantime and we are not below the group limit anymore.
@@ -204,6 +257,7 @@ QuotaObject::MaybeAllocateMoreSpace(int64_t aOffset, int32_t aCount)
return false;
}
AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta);
newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage + delta;
NS_ASSERTION(newTemporaryStorageUsage <=
@@ -211,15 +265,14 @@ QuotaObject::MaybeAllocateMoreSpace(int64_t aOffset, int32_t aCount)
// Ok, we successfully freed enough space and the operation can continue
// without throwing the quota error.
mOriginInfo->mUsage = newUsage;
groupInfo->mUsage = newGroupUsage;
quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;;
// Some other thread could increase the size in the meantime, but no more
// than this one.
NS_ASSERTION(mSize < end, "This shouldn't happen!");
mSize = end;
MOZ_ASSERT(mSize < aSize);
mSize = aSize;
// Finally, release IO thread only objects and allow next synchronized
// ops for the evicted origins.
@@ -234,7 +287,7 @@ QuotaObject::MaybeAllocateMoreSpace(int64_t aOffset, int32_t aCount)
groupInfo->mUsage = newGroupUsage;
quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;
mSize = end;
mSize = aSize;
return true;
}
@@ -244,30 +297,19 @@ OriginInfo::LockedDecreaseUsage(int64_t aSize)
{
AssertCurrentThreadOwnsQuotaMutex();
AssertNoUnderflow(mUsage, aSize);
mUsage -= aSize;
AssertNoUnderflow(mGroupInfo->mUsage, aSize);
mGroupInfo->mUsage -= aSize;
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, aSize);
quotaManager->mTemporaryStorageUsage -= aSize;
}
// static
PLDHashOperator
OriginInfo::ClearOriginInfoCallback(const nsAString& aKey,
QuotaObject* aValue,
void* aUserArg)
{
NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
NS_ASSERTION(aValue, "Null pointer!");
aValue->mOriginInfo = nullptr;
return PL_DHASH_NEXT;
}
already_AddRefed<OriginInfo>
GroupInfo::LockedGetOriginInfo(const nsACString& aOrigin)
{
@@ -294,11 +336,13 @@ GroupInfo::LockedAddOriginInfo(OriginInfo* aOriginInfo)
"Replacing an existing entry!");
mOriginInfos.AppendElement(aOriginInfo);
AssertNoOverflow(mUsage, aOriginInfo->mUsage);
mUsage += aOriginInfo->mUsage;
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
AssertNoOverflow(quotaManager->mTemporaryStorageUsage, aOriginInfo->mUsage);
quotaManager->mTemporaryStorageUsage += aOriginInfo->mUsage;
}
@@ -309,14 +353,14 @@ GroupInfo::LockedRemoveOriginInfo(const nsACString& aOrigin)
for (uint32_t index = 0; index < mOriginInfos.Length(); index++) {
if (mOriginInfos[index]->mOrigin == aOrigin) {
MOZ_ASSERT(mUsage >= mOriginInfos[index]->mUsage);
AssertNoUnderflow(mUsage, mOriginInfos[index]->mUsage);
mUsage -= mOriginInfos[index]->mUsage;
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
MOZ_ASSERT(quotaManager->mTemporaryStorageUsage >=
mOriginInfos[index]->mUsage);
AssertNoUnderflow(quotaManager->mTemporaryStorageUsage,
mOriginInfos[index]->mUsage);
quotaManager->mTemporaryStorageUsage -= mOriginInfos[index]->mUsage;
mOriginInfos.RemoveElementAt(index);
@@ -337,10 +381,10 @@ GroupInfo::LockedRemoveOriginInfos()
for (uint32_t index = mOriginInfos.Length(); index > 0; index--) {
OriginInfo* originInfo = mOriginInfos[index - 1];
MOZ_ASSERT(mUsage >= originInfo->mUsage);
AssertNoUnderflow(mUsage, originInfo->mUsage);
mUsage -= originInfo->mUsage;
MOZ_ASSERT(quotaManager->mTemporaryStorageUsage >= originInfo->mUsage);
AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, originInfo->mUsage);
quotaManager->mTemporaryStorageUsage -= originInfo->mUsage;
mOriginInfos.RemoveElementAt(index - 1);
+3 -16
View File
@@ -32,11 +32,8 @@ public:
void
Release();
void
UpdateSize(int64_t aSize);
bool
MaybeAllocateMoreSpace(int64_t aOffset, int32_t aCount);
MaybeUpdateSize(int64_t aSize, bool aTruncate);
private:
QuotaObject(OriginInfo* aOriginInfo, const nsAString& aPath, int64_t aSize)
@@ -96,6 +93,8 @@ private:
~OriginInfo()
{
MOZ_COUNT_DTOR(OriginInfo);
MOZ_ASSERT(!mQuotaObjects.Count());
}
void
@@ -109,18 +108,6 @@ private:
mAccessTime = aAccessTime;
}
void
LockedClearOriginInfos()
{
AssertCurrentThreadOwnsQuotaMutex();
mQuotaObjects.EnumerateRead(ClearOriginInfoCallback, nullptr);
}
static PLDHashOperator
ClearOriginInfoCallback(const nsAString& aKey,
QuotaObject* aValue, void* aUserArg);
nsDataHashtable<nsStringHashKey, QuotaObject*> mQuotaObjects;
GroupInfo* mGroupInfo;
+2 -8
View File
@@ -23,15 +23,9 @@ interface IDBDatabase : EventTarget {
[Throws]
void deleteObjectStore (DOMString name);
// This should be:
// IDBTransaction transaction ((DOMString or sequence<DOMString>) storeNames, optional IDBTransactionMode mode = "readonly");
// but unions are not currently supported.
[Throws]
IDBTransaction transaction (DOMString storeName, optional IDBTransactionMode mode = "readonly");
[Throws]
IDBTransaction transaction (sequence<DOMString> storeNames, optional IDBTransactionMode mode = "readonly");
IDBTransaction transaction ((DOMString or sequence<DOMString>) storeNames,
optional IDBTransactionMode mode = "readonly");
void close ();
+4
View File
@@ -11,6 +11,10 @@
enum IDBTransactionMode {
"readonly",
"readwrite",
// The "readwriteflush" mode is only available when the
// |IndexedDatabaseManager::ExperimentalFeaturesEnabled()| function returns
// true. This mode is not yet part of the standard.
"readwriteflush",
"versionchange"
};
+1 -1
View File
@@ -14,7 +14,7 @@
// [Constructor(DOMString url, optional (URL or DOMString) base = "about:blank")]
[Constructor(DOMString url, URL base),
Constructor(DOMString url, optional DOMString base = "about:blank"),
Constructor(DOMString url, optional DOMString base),
Exposed=(Window,Worker)]
interface URL {
[Throws]
+49 -42
View File
@@ -34,7 +34,7 @@ class URLProxy final
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(URLProxy)
explicit URLProxy(mozilla::dom::URL* aURL)
explicit URLProxy(already_AddRefed<mozilla::dom::URL> aURL)
: mURL(aURL)
{
AssertIsOnMainThread();
@@ -228,7 +228,7 @@ class ConstructorRunnable : public WorkerMainThreadRunnable
private:
const nsString mURL;
const nsString mBase;
nsString mBase; // IsVoid() if we have no base URI string.
nsRefPtr<URLProxy> mBaseProxy;
mozilla::ErrorResult& mRv;
@@ -236,13 +236,17 @@ private:
public:
ConstructorRunnable(WorkerPrivate* aWorkerPrivate,
const nsAString& aURL, const nsAString& aBase,
const nsAString& aURL, const Optional<nsAString>& aBase,
mozilla::ErrorResult& aRv)
: WorkerMainThreadRunnable(aWorkerPrivate)
, mURL(aURL)
, mBase(aBase)
, mRv(aRv)
{
if (aBase.WasPassed()) {
mBase = aBase.Value();
} else {
mBase.SetIsVoid(true);
}
mWorkerPrivate->AssertIsOnWorkerThread();
}
@@ -254,6 +258,7 @@ public:
, mBaseProxy(aBaseProxy)
, mRv(aRv)
{
mBase.SetIsVoid(true);
mWorkerPrivate->AssertIsOnWorkerThread();
}
@@ -262,35 +267,20 @@ public:
{
AssertIsOnMainThread();
nsresult rv;
nsCOMPtr<nsIIOService> ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv));
if (NS_FAILED(rv)) {
mRv.Throw(rv);
return true;
}
nsCOMPtr<nsIURI> baseURL;
if (!mBaseProxy) {
rv = ioService->NewURI(NS_ConvertUTF16toUTF8(mBase), nullptr, nullptr,
getter_AddRefs(baseURL));
if (NS_FAILED(rv)) {
mRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return true;
}
nsRefPtr<mozilla::dom::URL> url;
if (mBaseProxy) {
url = mozilla::dom::URL::Constructor(mURL, mBaseProxy->URI(), mRv);
} else if (!mBase.IsVoid()) {
url = mozilla::dom::URL::Constructor(mURL, mBase, mRv);
} else {
baseURL = mBaseProxy->URI();
url = mozilla::dom::URL::Constructor(mURL, nullptr, mRv);
}
nsCOMPtr<nsIURI> url;
rv = ioService->NewURI(NS_ConvertUTF16toUTF8(mURL), nullptr, baseURL,
getter_AddRefs(url));
if (NS_FAILED(rv)) {
mRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
if (mRv.Failed()) {
return true;
}
mRetval = new URLProxy(new mozilla::dom::URL(url));
mRetval = new URLProxy(url.forget());
return true;
}
@@ -522,18 +512,21 @@ URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
nsRefPtr<ConstructorRunnable> runnable =
new ConstructorRunnable(workerPrivate, aUrl, aBase.GetURLProxy(), aRv);
if (!runnable->Dispatch(cx)) {
JS_ReportPendingException(cx);
}
return FinishConstructor(cx, workerPrivate, runnable, aRv);
}
nsRefPtr<URLProxy> proxy = runnable->GetURLProxy();
if (!proxy) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return nullptr;
}
// static
already_AddRefed<URL>
URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
const Optional<nsAString>& aBase, ErrorResult& aRv)
{
JSContext* cx = aGlobal.Context();
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
nsRefPtr<URL> url = new URL(workerPrivate, proxy);
return url.forget();
nsRefPtr<ConstructorRunnable> runnable =
new ConstructorRunnable(workerPrivate, aUrl, aBase, aRv);
return FinishConstructor(cx, workerPrivate, runnable, aRv);
}
// static
@@ -544,20 +537,34 @@ URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
JSContext* cx = aGlobal.Context();
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
Optional<nsAString> base;
base = &aBase;
nsRefPtr<ConstructorRunnable> runnable =
new ConstructorRunnable(workerPrivate, aUrl, aBase, aRv);
new ConstructorRunnable(workerPrivate, aUrl, base, aRv);
if (!runnable->Dispatch(cx)) {
JS_ReportPendingException(cx);
return FinishConstructor(cx, workerPrivate, runnable, aRv);
}
// static
already_AddRefed<URL>
URL::FinishConstructor(JSContext* aCx, WorkerPrivate* aPrivate,
ConstructorRunnable* aRunnable, ErrorResult& aRv)
{
if (!aRunnable->Dispatch(aCx)) {
JS_ReportPendingException(aCx);
}
nsRefPtr<URLProxy> proxy = runnable->GetURLProxy();
if (aRv.Failed()) {
return nullptr;
}
nsRefPtr<URLProxy> proxy = aRunnable->GetURLProxy();
if (!proxy) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return nullptr;
}
nsRefPtr<URL> url = new URL(workerPrivate, proxy);
nsRefPtr<URL> url = new URL(aPrivate, proxy);
return url.forget();
}
+8
View File
@@ -23,6 +23,7 @@ struct objectURLOptions;
BEGIN_WORKERS_NAMESPACE
class URLProxy;
class ConstructorRunnable;
class URL final : public mozilla::dom::URLSearchParamsObserver
{
@@ -52,6 +53,9 @@ public:
Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
URL& aBase, ErrorResult& aRv);
static already_AddRefed<URL>
Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
const Optional<nsAString>& aBase, ErrorResult& aRv);
static already_AddRefed<URL>
Constructor(const GlobalObject& aGlobal, const nsAString& aUrl,
const nsAString& aBase, ErrorResult& aRv);
@@ -129,6 +133,10 @@ private:
return mURLProxy;
}
static already_AddRefed<URL>
FinishConstructor(JSContext* aCx, WorkerPrivate* aPrivate,
ConstructorRunnable* aRunnable, ErrorResult& aRv);
void CreateSearchParamsIfNeeded();
void SetSearchInternal(const nsAString& aSearch);
+303 -34
View File
@@ -125,10 +125,205 @@ struct telemetry_file {
// quota object for this file
nsRefPtr<QuotaObject> quotaObject;
// The chunk size for this file. See the documentation for
// sqlite3_file_control() and FCNTL_CHUNK_SIZE.
int fileChunkSize;
// This contains the vfs that actually does work
sqlite3_file pReal[1];
};
const char*
DatabasePathFromWALPath(const char *zWALName)
{
/**
* Do some sketchy pointer arithmetic to find the parameter key. The WAL
* filename is in the middle of a big allocated block that contains:
*
* - Random Values
* - Main Database Path
* - \0
* - Multiple URI components consisting of:
* - Key
* - \0
* - Value
* - \0
* - \0
* - Journal Path
* - \0
* - WAL Path (zWALName)
* - \0
*
* Because the main database path is preceded by a random value we have to be
* careful when trying to figure out when we should terminate this loop.
*/
MOZ_ASSERT(zWALName);
nsDependentCSubstring dbPath(zWALName, strlen(zWALName));
// Chop off the "-wal" suffix.
NS_NAMED_LITERAL_CSTRING(kWALSuffix, "-wal");
MOZ_ASSERT(StringEndsWith(dbPath, kWALSuffix));
dbPath.Rebind(zWALName, dbPath.Length() - kWALSuffix.Length());
MOZ_ASSERT(!dbPath.IsEmpty());
// We want to scan to the end of the key/value URI pairs. Skip the preceding
// null and go to the last char of the journal path.
const char* cursor = zWALName - 2;
// Make sure we just skipped a null.
MOZ_ASSERT(!*(cursor + 1));
// Walk backwards over the journal path.
while (*cursor) {
cursor--;
}
// There should be another null here.
cursor--;
MOZ_ASSERT(!*cursor);
// Back up one more char to the last char of the previous string. It may be
// the database path or it may be a key/value URI pair.
cursor--;
#ifdef DEBUG
{
// Verify that we just walked over the journal path. Account for the two
// nulls we just skipped.
const char *journalStart = cursor + 3;
nsDependentCSubstring journalPath(journalStart,
strlen(journalStart));
// Chop off the "-journal" suffix.
NS_NAMED_LITERAL_CSTRING(kJournalSuffix, "-journal");
MOZ_ASSERT(StringEndsWith(journalPath, kJournalSuffix));
journalPath.Rebind(journalStart,
journalPath.Length() - kJournalSuffix.Length());
MOZ_ASSERT(!journalPath.IsEmpty());
// Make sure that the database name is a substring of the journal name.
MOZ_ASSERT(journalPath == dbPath);
}
#endif
// Now we're either at the end of the key/value URI pairs or we're at the
// end of the database path. Carefully walk backwards one character at a
// time to do this safely without running past the beginning of the database
// path.
const char *const dbPathStart = dbPath.BeginReading();
const char *dbPathCursor = dbPath.EndReading() - 1;
bool isDBPath = true;
while (true) {
MOZ_ASSERT(*dbPathCursor, "dbPathCursor should never see a null char!");
if (isDBPath) {
isDBPath = dbPathStart <= dbPathCursor &&
*dbPathCursor == *cursor &&
*cursor;
}
if (!isDBPath) {
// This isn't the database path so it must be a value. Scan past it and
// the key also.
for (size_t stringCount = 0; stringCount < 2; stringCount++) {
// Scan past the string to the preceding null character.
while (*cursor) {
cursor--;
}
// Back up one more char to the last char of preceding string.
cursor--;
}
// Reset and start again.
dbPathCursor = dbPath.EndReading() - 1;
isDBPath = true;
continue;
}
MOZ_ASSERT(isDBPath);
MOZ_ASSERT(*cursor);
if (dbPathStart == dbPathCursor) {
// Found the full database path, we're all done.
MOZ_ASSERT(nsDependentCString(cursor) == dbPath);
return cursor;
}
// Change the cursors and go through the loop again.
cursor--;
dbPathCursor--;
}
MOZ_CRASH("Should never get here!");
}
already_AddRefed<QuotaObject>
GetQuotaObjectFromNameAndParameters(const char *zName,
const char *zURIParameterKey)
{
MOZ_ASSERT(zName);
MOZ_ASSERT(zURIParameterKey);
const char *persistenceType =
sqlite3_uri_parameter(zURIParameterKey, "persistenceType");
if (!persistenceType) {
return nullptr;
}
const char *group = sqlite3_uri_parameter(zURIParameterKey, "group");
if (!group) {
NS_WARNING("SQLite URI had 'persistenceType' but not 'group'?!");
return nullptr;
}
const char *origin = sqlite3_uri_parameter(zURIParameterKey, "origin");
if (!origin) {
NS_WARNING("SQLite URI had 'persistenceType' and 'group' but not "
"'origin'?!");
return nullptr;
}
QuotaManager *quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
return quotaManager->GetQuotaObject(
PersistenceTypeFromText(nsDependentCString(persistenceType)),
nsDependentCString(group),
nsDependentCString(origin),
NS_ConvertUTF8toUTF16(zName));
}
void
MaybeEstablishQuotaControl(const char *zName,
telemetry_file *pFile,
int flags)
{
MOZ_ASSERT(pFile);
MOZ_ASSERT(!pFile->quotaObject);
if (!(flags & (SQLITE_OPEN_URI | SQLITE_OPEN_WAL))) {
return;
}
MOZ_ASSERT(zName);
const char *zURIParameterKey = (flags & SQLITE_OPEN_WAL) ?
DatabasePathFromWALPath(zName) :
zName;
MOZ_ASSERT(zURIParameterKey);
pFile->quotaObject =
GetQuotaObjectFromNameAndParameters(zName, zURIParameterKey);
}
/*
** Close a telemetry_file.
*/
@@ -145,6 +340,9 @@ xClose(sqlite3_file *pFile)
delete p->base.pMethods;
p->base.pMethods = nullptr;
p->quotaObject = nullptr;
#ifdef DEBUG
p->fileChunkSize = 0;
#endif
}
return rc;
}
@@ -165,6 +363,19 @@ xRead(sqlite3_file *pFile, void *zBuf, int iAmt, sqlite_int64 iOfst)
return rc;
}
/*
** Return the current file-size of a telemetry_file.
*/
int
xFileSize(sqlite3_file *pFile, sqlite_int64 *pSize)
{
IOThreadAutoTimer ioTimer(IOInterposeObserver::OpStat);
telemetry_file *p = (telemetry_file *)pFile;
int rc;
rc = p->pReal->pMethods->xFileSize(p->pReal, pSize);
return rc;
}
/*
** Write data to a telemetry_file.
*/
@@ -172,13 +383,24 @@ int
xWrite(sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst)
{
telemetry_file *p = (telemetry_file *)pFile;
if (p->quotaObject && !p->quotaObject->MaybeAllocateMoreSpace(iOfst, iAmt)) {
return SQLITE_FULL;
}
IOThreadAutoTimer ioTimer(p->histograms->writeMS, IOInterposeObserver::OpWrite);
int rc;
if (p->quotaObject) {
MOZ_ASSERT(INT64_MAX - iOfst >= iAmt);
if (!p->quotaObject->MaybeUpdateSize(iOfst + iAmt, /* aTruncate */ false)) {
return SQLITE_FULL;
}
}
rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst);
Telemetry::Accumulate(p->histograms->writeB, rc == SQLITE_OK ? iAmt : 0);
if (p->quotaObject && rc != SQLITE_OK) {
NS_WARNING("xWrite failed on a quota-controlled file, attempting to "
"update its current size...");
sqlite_int64 currentSize;
if (xFileSize(pFile, &currentSize) == SQLITE_OK) {
p->quotaObject->MaybeUpdateSize(currentSize, /* aTruncate */ true);
}
}
return rc;
}
@@ -192,9 +414,33 @@ xTruncate(sqlite3_file *pFile, sqlite_int64 size)
telemetry_file *p = (telemetry_file *)pFile;
int rc;
Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_TRUNCATE_MS> timer;
if (p->quotaObject) {
if (p->fileChunkSize > 0) {
// Round up to the smallest multiple of the chunk size that will hold all
// the data.
size =
((size + p->fileChunkSize - 1) / p->fileChunkSize) * p->fileChunkSize;
}
if (!p->quotaObject->MaybeUpdateSize(size, /* aTruncate */ true)) {
return SQLITE_FULL;
}
}
rc = p->pReal->pMethods->xTruncate(p->pReal, size);
if (rc == SQLITE_OK && p->quotaObject) {
p->quotaObject->UpdateSize(size);
if (p->quotaObject) {
if (rc == SQLITE_OK) {
#ifdef DEBUG
// Make sure xTruncate set the size exactly as we calculated above.
sqlite_int64 newSize;
MOZ_ASSERT(xFileSize(pFile, &newSize) == SQLITE_OK);
MOZ_ASSERT(newSize == size);
#endif
} else {
NS_WARNING("xTruncate failed on a quota-controlled file, attempting to "
"update its current size...");
if (xFileSize(pFile, &size) == SQLITE_OK) {
p->quotaObject->MaybeUpdateSize(size, /* aTruncate */ true);
}
}
}
return rc;
}
@@ -210,19 +456,6 @@ xSync(sqlite3_file *pFile, int flags)
return p->pReal->pMethods->xSync(p->pReal, flags);
}
/*
** Return the current file-size of a telemetry_file.
*/
int
xFileSize(sqlite3_file *pFile, sqlite_int64 *pSize)
{
IOThreadAutoTimer ioTimer(IOInterposeObserver::OpStat);
telemetry_file *p = (telemetry_file *)pFile;
int rc;
rc = p->pReal->pMethods->xFileSize(p->pReal, pSize);
return rc;
}
/*
** Lock a telemetry_file.
*/
@@ -265,7 +498,41 @@ int
xFileControl(sqlite3_file *pFile, int op, void *pArg)
{
telemetry_file *p = (telemetry_file *)pFile;
int rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg);
int rc;
// Hook SQLITE_FCNTL_SIZE_HINT for quota-controlled files and do the necessary
// work before passing to the SQLite VFS.
if (op == SQLITE_FCNTL_SIZE_HINT && p->quotaObject) {
sqlite3_int64 hintSize = *static_cast<sqlite3_int64*>(pArg);
sqlite3_int64 currentSize;
rc = xFileSize(pFile, &currentSize);
if (rc != SQLITE_OK) {
return rc;
}
if (hintSize > currentSize) {
rc = xTruncate(pFile, hintSize);
if (rc != SQLITE_OK) {
return rc;
}
}
}
rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg);
// Grab the file chunk size after the SQLite VFS has approved.
if (op == SQLITE_FCNTL_CHUNK_SIZE && rc == SQLITE_OK) {
p->fileChunkSize = *static_cast<int*>(pArg);
}
#ifdef DEBUG
if (op == SQLITE_FCNTL_SIZE_HINT && p->quotaObject && rc == SQLITE_OK) {
sqlite3_int64 hintSize = *static_cast<sqlite3_int64*>(pArg);
if (p->fileChunkSize > 0) {
hintSize =
((hintSize + p->fileChunkSize - 1) / p->fileChunkSize) *
p->fileChunkSize;
}
sqlite3_int64 currentSize;
MOZ_ASSERT(xFileSize(pFile, &currentSize) == SQLITE_OK);
MOZ_ASSERT(currentSize >= hintSize);
}
#endif
return rc;
}
@@ -371,20 +638,7 @@ xOpen(sqlite3_vfs* vfs, const char *zName, sqlite3_file* pFile,
}
p->histograms = h;
const char* persistenceType;
const char* group;
const char* origin;
if ((flags & SQLITE_OPEN_URI) &&
(persistenceType = sqlite3_uri_parameter(zName, "persistenceType")) &&
(group = sqlite3_uri_parameter(zName, "group")) &&
(origin = sqlite3_uri_parameter(zName, "origin"))) {
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
p->quotaObject = quotaManager->GetQuotaObject(PersistenceTypeFromText(
nsDependentCString(persistenceType)), nsDependentCString(group),
nsDependentCString(origin), NS_ConvertUTF8toUTF16(zName));
}
MaybeEstablishQuotaControl(zName, p, flags);
rc = orig_vfs->xOpen(orig_vfs, zName, p->pReal, flags, pOutFlags);
if( rc != SQLITE_OK )
@@ -436,7 +690,22 @@ int
xDelete(sqlite3_vfs* vfs, const char *zName, int syncDir)
{
sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
return orig_vfs->xDelete(orig_vfs, zName, syncDir);
int rc;
nsRefPtr<QuotaObject> quotaObject;
if (StringEndsWith(nsDependentCString(zName), NS_LITERAL_CSTRING("-wal"))) {
const char *zURIParameterKey = DatabasePathFromWALPath(zName);
MOZ_ASSERT(zURIParameterKey);
quotaObject = GetQuotaObjectFromNameAndParameters(zName, zURIParameterKey);
}
rc = orig_vfs->xDelete(orig_vfs, zName, syncDir);
if (rc == SQLITE_OK && quotaObject) {
MOZ_ALWAYS_TRUE(quotaObject->MaybeUpdateSize(0, /* aTruncate */ true));
}
return rc;
}
int
+2
View File
@@ -10191,6 +10191,8 @@
"url": "/IndexedDB/key_invalid.htm"
},
{
"path": "IndexedDB/key_valid.html",
"timeout": "long",
"url": "/IndexedDB/key_valid.html"
},
{
@@ -1,6 +1,7 @@
<!DOCTYPE html>
<!-- Submitted from TestTWF Paris -->
<meta charset=utf-8">
<meta name="timeout" content="long">
<title>Valid key</title>
<link rel=help href="http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#key-construct">
<link rel=assert title="A value is said to be a valid key if it is one of the following types: Array JavaScript objects [ECMA-262], DOMString [WEBIDL], Date [ECMA-262] or float [WEBIDL]. However Arrays are only valid keys if every item in the array is defined and is a valid key (i.e. sparse arrays can not be valid keys) and if the Array doesn't directly or indirectly contain itself. Any non-numeric properties are ignored, and thus does not affect whether the Array is a valid key. Additionally, if the value is of type float, it is only a valid key if it is not NaN, and if the value is of type Date it is only a valid key if its [[PrimitiveValue]] internal property, as defined by [ECMA-262], is not NaN. Conforming user agents must support all valid keys as keys.">