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

- Bug 1182987 - Part 1: Remove unreferenced files immediately; r=baku (3ced56ca25)
- Bug 1182987 - Part 2: Add getQuotaObjects() to mozIStorageConnection; r=mak (64b4dc418c)
- Bug 1182987 - Part 3: Add "cleanup" transaction with disabled quota checks and vacuuming/checkpointing after commit; r=baku (7c19f28ae2)
- Bug 1182987 - Part 4: Add a test for QuotaExceededError recovery and the new "cleanup" transaction type; r=baku (f91935e737)
- Bug 1182987 - Part 5: Change mode of "readwrite" transaction to "cleanup" after QuotaExceeded is fired; r=baku (79d709970d)
- Bug 1257725 part 3. Get rid of ThreadsafeAutoJSContext usage in Promise code. r=bholley (405d3c03d4)
- Bug 1257725 part 1. Get rid of ThreadsafeAutoJSContext usage in JSEventHandler::HandleEvent. r=smaug (3222a73565)
- Bug 1257725 part 4. Get rid of ThreadsafeAutoJSContext usage in IndexedDB code, except for IDBRequest::CaptureCaller. r=khuey (8ad88560f0)
- Bug 1257725 part 5. Get rid of ThreadsafeAutoJSContext usage in IDBRequest::CaptureCaller. r=khuey (50a1f05ec9)
- Bug 1257725 part 6. Get rid of ThreadsafeAutoJSContext. r=bholley (8968c69fcc)
- Bug 1247420 - part1: removeContentState. r=smaug (6c7a54b58e)
- Bug 1247420 - part2: IPC hover state management for select. r=Felipc (c7809aec7c)
- Bug 1223533 - Don't hide the select popup on irrelevant pagehide events. r=mconley (0cf218515a)
- Bug 1253486, [e10s only] hide select popups when the select element is removed, r=mconley (8a7049b6f1)
- Bug 1180827 - Fix reuse of previous search results. r=MattN (6b4e65d318)
- Bug 1242208 - Fix cached form history results with a datalist present. r=MattN (10078ada31)
- Bug 1252074 - test_form_autocomplete.html and test_form_autocomplete_with_list.html should pass on e10s. r=paolo (8a9cf4a5f1)
- Bug 1260441 - Never pass a null js context to OpenCursor() r=bz (8d818b0257)
- Bug 1170045 - part 2 - use SegmentedVector in the DeferredFinalize implementation; r=mccr8 (3954a5e390)
- Bug 1265770. Don't try to get a prototype for the interface object for an interface that's [NoInterfaceObject], since it's just unnecessary work that can't even be done at all in some cases (e.g. when the parent interface is also [NoInterfaceObject]). r=peterv (53d3077e31)
- Bug 1264187 - check for a ProtoAndIfaceCache before blindly destroying it; r=bz (97536e815b)
- Bug 1104955 part 3. Pass our unscopable names to CreateInterfaceObjects and have it define the right thing on the prototype. r=khuey (48386ab6b5)
- Bug 934889: Use JS_InitStandardClasses everywhere now that it works. r=bz (01d545259a)
- Bug 1258585. Remove some remaining vestiges of WebIDL quickstubs. r=peterv (3fa02388f1)
This commit is contained in:
2024-05-03 10:52:27 +08:00
parent 5f6d4971d0
commit 59e31c9007
58 changed files with 1704 additions and 393 deletions
-22
View File
@@ -838,28 +838,6 @@ AutoJSContext::operator JSContext*() const
return mCx;
}
ThreadsafeAutoJSContext::ThreadsafeAutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (NS_IsMainThread()) {
mCx = nullptr;
mAutoJSContext.emplace();
} else {
mCx = mozilla::dom::workers::GetCurrentThreadJSContext();
mRequest.emplace(mCx);
}
}
ThreadsafeAutoJSContext::operator JSContext*() const
{
if (mCx) {
return mCx;
} else {
return *mAutoJSContext;
}
}
AutoSafeJSContext::AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
: AutoJSAPI()
{
-16
View File
@@ -441,22 +441,6 @@ protected:
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
/**
* Use ThreadsafeAutoJSContext when you want an AutoJSContext but might be
* running on a worker thread.
*/
class MOZ_RAII ThreadsafeAutoJSContext {
public:
explicit ThreadsafeAutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
operator JSContext*() const;
private:
JSContext* mCx; // Used on workers. Null means mainthread.
Maybe<JSAutoRequest> mRequest; // Used on workers.
Maybe<AutoJSContext> mAutoJSContext; // Used on main thread.
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
/**
* AutoSafeJSContext is similar to AutoJSContext but will only return the safe
* JS context. That means it will never call nsContentUtils::GetCurrentJSContext().
+31 -34
View File
@@ -761,43 +761,13 @@ CreateInterfaceObject(JSContext* cx, JS::Handle<JSObject*> global,
return constructor;
}
bool
DefineWebIDLBindingUnforgeablePropertiesOnXPCObject(JSContext* cx,
JS::Handle<JSObject*> obj,
const NativeProperties* properties)
{
if (properties->unforgeableAttributes &&
!DefinePrefable(cx, obj, properties->unforgeableAttributes)) {
return false;
}
return true;
}
bool
DefineWebIDLBindingPropertiesOnXPCObject(JSContext* cx,
JS::Handle<JSObject*> obj,
const NativeProperties* properties)
{
if (properties->methods &&
!DefinePrefable(cx, obj, properties->methods)) {
return false;
}
if (properties->attributes &&
!DefinePrefable(cx, obj, properties->attributes)) {
return false;
}
return true;
}
static JSObject*
CreateInterfacePrototypeObject(JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<JSObject*> parentProto,
const js::Class* protoClass,
const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties)
const NativeProperties* chromeOnlyProperties,
const char* const* unscopableNames)
{
JS::Rooted<JSObject*> ourProto(cx,
JS_NewObjectWithUniqueType(cx, Jsvalify(protoClass), parentProto));
@@ -806,6 +776,28 @@ CreateInterfacePrototypeObject(JSContext* cx, JS::Handle<JSObject*> global,
return nullptr;
}
if (unscopableNames) {
JS::Rooted<JSObject*> unscopableObj(cx, JS_NewPlainObject(cx));
if (!unscopableObj) {
return nullptr;
}
for (; *unscopableNames; ++unscopableNames) {
if (!JS_DefineProperty(cx, unscopableObj, *unscopableNames,
JS::TrueHandleValue, JSPROP_ENUMERATE)) {
return nullptr;
}
}
JS::Rooted<jsid> unscopableId(cx,
SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, JS::SymbolCode::unscopables)));
// Readonly and non-enumerable to match Array.prototype.
if (!JS_DefinePropertyById(cx, ourProto, unscopableId, unscopableObj,
JSPROP_READONLY)) {
return nullptr;
}
}
return ourProto;
}
@@ -861,7 +853,8 @@ CreateInterfaceObjects(JSContext* cx, JS::Handle<JSObject*> global,
JS::Heap<JSObject*>* constructorCache,
const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties,
const char* name, bool defineOnGlobal)
const char* name, bool defineOnGlobal,
const char* const* unscopableNames)
{
MOZ_ASSERT(protoClass || constructorClass || constructor,
"Need at least one class or a constructor!");
@@ -887,12 +880,16 @@ CreateInterfaceObjects(JSContext* cx, JS::Handle<JSObject*> global,
MOZ_ASSERT(!(constructorClass || constructor) == !constructorCache,
"If, and only if, there is an interface object we need to cache "
"it");
MOZ_ASSERT(constructorProto || (!constructorClass && !constructor),
"Must have a constructor proto if we plan to create a constructor "
"object");
JS::Rooted<JSObject*> proto(cx);
if (protoClass) {
proto =
CreateInterfacePrototypeObject(cx, global, protoProto, protoClass,
properties, chromeOnlyProperties);
properties, chromeOnlyProperties,
unscopableNames);
if (!proto) {
return;
}
+20 -25
View File
@@ -24,6 +24,7 @@
#include "mozilla/dom/NonRefcountedDOMObject.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/SegmentedVector.h"
#include "mozilla/dom/workers/Workers.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Likely.h"
@@ -522,6 +523,10 @@ DestroyProtoAndIfaceCache(JSObject* obj)
{
MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL);
if (!HasProtoAndIfaceCache(obj)) {
return;
}
ProtoAndIfaceCache* protoAndIfaceCache = GetProtoAndIfaceCache(obj);
delete protoAndIfaceCache;
@@ -554,7 +559,9 @@ struct NamedConstructor
* global is used as the parent of the interface object and the interface
* prototype object
* protoProto is the prototype to use for the interface prototype object.
* interfaceProto is the prototype to use for the interface object.
* interfaceProto is the prototype to use for the interface object. This can be
* null if both constructorClass and constructor are null (as in,
* if we're not creating an interface object at all).
* protoClass is the JSClass to use for the interface prototype object.
* This is null if we should not create an interface prototype
* object.
@@ -584,6 +591,8 @@ struct NamedConstructor
* false in situations where we want the properties to only
* appear on privileged Xrays but not on the unprivileged
* underlying global.
* unscopableNames if not null it points to a null-terminated list of const
* char* names of the unscopable properties for this interface.
*
* At least one of protoClass, constructorClass or constructor should be
* non-null. If constructorClass or constructor are non-null, the resulting
@@ -600,7 +609,8 @@ CreateInterfaceObjects(JSContext* cx, JS::Handle<JSObject*> global,
JS::Heap<JSObject*>* constructorCache,
const NativeProperties* regularProperties,
const NativeProperties* chromeOnlyProperties,
const char* name, bool defineOnGlobal);
const char* name, bool defineOnGlobal,
const char* const* unscopableNames);
/**
* Define the properties (regular and chrome-only) on obj.
@@ -634,16 +644,6 @@ bool
DefineUnforgeableAttributes(JSContext* cx, JS::Handle<JSObject*> obj,
const Prefable<const JSPropertySpec>* props);
bool
DefineWebIDLBindingUnforgeablePropertiesOnXPCObject(JSContext* cx,
JS::Handle<JSObject*> obj,
const NativeProperties* properties);
bool
DefineWebIDLBindingPropertiesOnXPCObject(JSContext* cx,
JS::Handle<JSObject*> obj,
const NativeProperties* properties);
#define HAS_MEMBER_TYPEDEFS \
private: \
typedef char yes[1]; \
@@ -2847,27 +2847,27 @@ struct DeferredFinalizerImpl
typename Conditional<IsRefcounted<T>::value,
RefPtr<T>,
nsAutoPtr<T>>::Type>::Type SmartPtr;
typedef nsTArray<SmartPtr> SmartPtrArray;
typedef SegmentedVector<SmartPtr> SmartPtrArray;
static_assert(IsSame<T, nsISupports>::value || !IsBaseOf<nsISupports, T>::value,
"nsISupports classes should all use the nsISupports instantiation");
static inline void
AppendAndTake(nsTArray<nsCOMPtr<nsISupports>>& smartPtrArray, nsISupports* ptr)
AppendAndTake(SegmentedVector<nsCOMPtr<nsISupports>>& smartPtrArray, nsISupports* ptr)
{
smartPtrArray.AppendElement(dont_AddRef(ptr));
smartPtrArray.InfallibleAppend(dont_AddRef(ptr));
}
template<class U>
static inline void
AppendAndTake(nsTArray<RefPtr<U>>& smartPtrArray, U* ptr)
AppendAndTake(SegmentedVector<RefPtr<U>>& smartPtrArray, U* ptr)
{
smartPtrArray.AppendElement(dont_AddRef(ptr));
smartPtrArray.InfallibleAppend(dont_AddRef(ptr));
}
template<class U>
static inline void
AppendAndTake(nsTArray<nsAutoPtr<U>>& smartPtrArray, U* ptr)
AppendAndTake(SegmentedVector<nsAutoPtr<U>>& smartPtrArray, U* ptr)
{
smartPtrArray.AppendElement(ptr);
smartPtrArray.InfallibleAppend(ptr);
}
static void*
@@ -2890,7 +2890,7 @@ struct DeferredFinalizerImpl
aSlice = oldLen;
}
uint32_t newLen = oldLen - aSlice;
pointers->RemoveElementsAt(newLen, aSlice);
pointers->PopLastN(aSlice);
if (newLen == 0) {
delete pointers;
return true;
@@ -3005,9 +3005,6 @@ struct CreateGlobalOptions
{
static MOZ_CONSTEXPR_VAR ProtoAndIfaceCache::Kind ProtoAndIfaceCacheKind =
ProtoAndIfaceCache::NonWindowLike;
// Intl API is broken and makes JS_InitStandardClasses fail intermittently,
// see bug 934889.
static MOZ_CONSTEXPR_VAR bool ForceInitStandardClassesToFalse = true;
static void TraceGlobal(JSTracer* aTrc, JSObject* aObj)
{
mozilla::dom::TraceProtoAndIfaceCache(aTrc, aObj);
@@ -3025,7 +3022,6 @@ struct CreateGlobalOptions<nsGlobalWindow>
{
static MOZ_CONSTEXPR_VAR ProtoAndIfaceCache::Kind ProtoAndIfaceCacheKind =
ProtoAndIfaceCache::WindowLike;
static MOZ_CONSTEXPR_VAR bool ForceInitStandardClassesToFalse = false;
static void TraceGlobal(JSTracer* aTrc, JSObject* aObj);
static bool PostCreateGlobal(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
};
@@ -3076,7 +3072,6 @@ CreateGlobal(JSContext* aCx, T* aNative, nsWrapperCache* aCache,
}
if (aInitStandardClasses &&
!CreateGlobalOptions<T>::ForceInitStandardClassesToFalse &&
!JS_InitStandardClasses(aCx, aGlobal)) {
NS_WARNING("Failed to init standard classes");
return nullptr;
+12 -1
View File
@@ -619,6 +619,16 @@ DOMInterfaces = {
'nativeType': 'mozilla::dom::IDBCursor',
},
'IDBDatabase': {
'implicitJSContext': [ 'transaction', 'createMutableFile',
'mozCreateFileHandle' ],
},
'IDBFactory': {
'implicitJSContext': [ 'open', 'deleteDatabase', 'openForPrincipal',
'deleteForPrincipal' ],
},
'IDBIndex': {
'binaryNames': {
'mozGetAll': 'getAll',
@@ -638,7 +648,8 @@ DOMInterfaces = {
'IDBObjectStore': {
'binaryNames': {
'mozGetAll': 'getAll'
}
},
'implicitJSContext': [ 'clear' ],
},
'IDBOpenDBRequest': {
+39 -11
View File
@@ -2720,13 +2720,14 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
properties should be a PropertyArrays instance.
"""
def __init__(self, descriptor, properties):
def __init__(self, descriptor, properties, haveUnscopables):
args = [Argument('JSContext*', 'aCx'),
Argument('JS::Handle<JSObject*>', 'aGlobal'),
Argument('ProtoAndIfaceCache&', 'aProtoAndIfaceCache'),
Argument('bool', 'aDefineOnGlobal')]
CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', 'void', args)
self.properties = properties
self.haveUnscopables = haveUnscopables
def definition_body(self):
(protoGetter, protoHandleGetter) = InterfacePrototypeObjectProtoGetter(self.descriptor)
@@ -2840,11 +2841,15 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
if needInterfaceObject:
interfaceClass = "&sInterfaceObjectClass.mBase"
interfaceCache = "&aProtoAndIfaceCache.EntrySlotOrCreate(constructors::id::%s)" % self.descriptor.name
getConstructorProto = CGGeneric(getConstructorProto)
constructorProto = "constructorProto"
else:
# We don't have slots to store the named constructors.
assert len(self.descriptor.interface.namedConstructors) == 0
interfaceClass = "nullptr"
interfaceCache = "nullptr"
getConstructorProto = None
constructorProto = "nullptr"
isGlobal = self.descriptor.isGlobal() is not None
if not isGlobal and self.properties.hasNonChromeOnly():
@@ -2862,15 +2867,17 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
JS::Heap<JSObject*>* interfaceCache = ${interfaceCache};
dom::CreateInterfaceObjects(aCx, aGlobal, ${parentProto},
${protoClass}, protoCache,
constructorProto, ${interfaceClass}, ${constructHookHolder}, ${constructArgs}, ${namedConstructors},
${constructorProto}, ${interfaceClass}, ${constructHookHolder}, ${constructArgs}, ${namedConstructors},
interfaceCache,
${properties},
${chromeProperties},
${name}, aDefineOnGlobal);
${name}, aDefineOnGlobal,
${unscopableNames});
""",
protoClass=protoClass,
parentProto=parentProto,
protoCache=protoCache,
constructorProto=constructorProto,
interfaceClass=interfaceClass,
constructHookHolder=constructHookHolder,
constructArgs=constructArgs,
@@ -2878,7 +2885,8 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
interfaceCache=interfaceCache,
properties=properties,
chromeProperties=chromeProperties,
name='"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "nullptr")
name='"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "nullptr",
unscopableNames="unscopableNames" if self.haveUnscopables else "nullptr")
# If we fail after here, we must clear interface and prototype caches
# using this code: intermediate failure must not expose the interface in
@@ -3046,7 +3054,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
makeProtoPrototypeImmutable = None
return CGList(
[getParentProto, CGGeneric(getConstructorProto), initIds,
[getParentProto, getConstructorProto, initIds,
prefCache, CGGeneric(call), defineAliases, unforgeableHolderSetup,
speciesSetup, makeProtoPrototypeImmutable],
"\n").define()
@@ -12053,7 +12061,8 @@ class CGDescriptor(CGThing):
# CGCreateInterfaceObjectsMethod needs to come after our
# CGDOMJSClass and unscopables, if any.
cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties))
cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties,
haveUnscopables))
# CGGetProtoObjectMethod and CGGetConstructorObjectMethod need
# to come after CGCreateInterfaceObjectsMethod.
@@ -16358,11 +16367,9 @@ class CGEventGetter(CGNativeMember):
if type.isAny():
return fill(
"""
JS::ExposeValueToActiveJS(${memberName});
aRetVal.set(${memberName});
return;
${selfName}(aRetVal);
""",
memberName=memberName)
selfName=self.name)
if type.isUnion():
return "aRetVal = " + memberName + ";\n"
if type.isSequence():
@@ -16579,8 +16586,28 @@ class CGEventClass(CGBindingImplClass):
def __init__(self, descriptor):
CGBindingImplClass.__init__(self, descriptor, CGEventMethod, CGEventGetter, CGEventSetter, False, "WrapObjectInternal")
members = []
extraMethods = []
for m in descriptor.interface.members:
if m.isAttr():
if m.type.isAny():
# Add a getter that doesn't need a JSContext. Note that we
# don't need to do this if our originating interface is not
# the descriptor's interface, because in that case we
# wouldn't generate the getter that _does_ need a JSContext
# either.
extraMethods.append(
ClassMethod(
CGSpecializedGetter.makeNativeName(descriptor, m),
"void",
[Argument("JS::MutableHandle<JS::Value>",
"aRetVal")],
const=True,
body=fill(
"""
JS::ExposeValueToActiveJS(${memberName});
aRetVal.set(${memberName});
""",
memberName=CGDictionary.makeMemberName(m.identifier.name))))
if getattr(m, "originatingInterface",
descriptor.interface) != descriptor.interface:
continue
@@ -16613,10 +16640,11 @@ class CGEventClass(CGBindingImplClass):
body="return this;\n",
breakAfterReturnDecl=" ",
override=True)
extraMethods.append(asConcreteTypeMethod)
CGClass.__init__(self, className,
bases=[ClassBase(self.parentType)],
methods=[asConcreteTypeMethod]+self.methodDecls,
methods=extraMethods+self.methodDecls,
members=members,
extradeclarations=baseDeclarations)
+9
View File
@@ -13,6 +13,8 @@ namespace mozilla {
namespace dom {
namespace cache {
using mozilla::dom::quota::QuotaObject;
NS_IMPL_ISUPPORTS(cache::Connection, mozIStorageAsyncConnection,
mozIStorageConnection);
@@ -272,6 +274,13 @@ Connection::EnableModule(const nsACString& aModule)
return mBase->EnableModule(aModule);
}
NS_IMETHODIMP
Connection::GetQuotaObjects(QuotaObject** aDatabaseQuotaObject,
QuotaObject** aJournalQuotaObject)
{
return mBase->GetQuotaObjects(aDatabaseQuotaObject, aJournalQuotaObject);
}
} // namespace cache
} // namespace dom
} // namespace mozilla
+19 -3
View File
@@ -147,8 +147,13 @@ DataStoreDB::Open(IDBTransactionMode aMode, const Sequence<nsString>& aDbs,
return rv;
}
// We only need a JSContext here to get a stack from, so just init our
// AutoJSAPI without a global.
AutoJSAPI jsapi;
jsapi.Init();
ErrorResult error;
mRequest = mFactory->Open(mDatabaseName, DATASTOREDB_VERSION, error);
mRequest = mFactory->Open(jsapi.cx(), mDatabaseName, DATASTOREDB_VERSION,
error);
if (NS_WARN_IF(error.Failed())) {
return error.StealNSResult();
}
@@ -313,8 +318,14 @@ DataStoreDB::DatabaseOpened()
return NS_ERROR_OUT_OF_MEMORY;
}
// We init with the global of our result, just for consistency.
AutoJSAPI jsapi;
if (!jsapi.Init(&result.toObject())) {
return NS_ERROR_UNEXPECTED;
}
RefPtr<IDBTransaction> txn;
error = mDatabase->Transaction(objectStores,
error = mDatabase->Transaction(jsapi.cx(),
objectStores,
mTransactionMode,
getter_AddRefs(txn));
if (NS_WARN_IF(error.Failed())) {
@@ -342,9 +353,14 @@ DataStoreDB::Delete()
mDatabase = nullptr;
}
// We only need a JSContext here to get a stack from, so just init our
// AutoJSAPI without a global.
AutoJSAPI jsapi;
jsapi.Init();
ErrorResult error;
RefPtr<IDBOpenDBRequest> request =
mFactory->DeleteDatabase(mDatabaseName, IDBOpenDBOptions(), error);
mFactory->DeleteDatabase(jsapi.cx(), mDatabaseName, IDBOpenDBOptions(),
error);
if (NS_WARN_IF(error.Failed())) {
return error.StealNSResult();
}
+4 -1
View File
@@ -27,6 +27,7 @@
#include "mozilla/dom/IDBTransaction.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/unused.h"
#include "mozIApplication.h"
@@ -410,7 +411,9 @@ public:
return;
}
mRequest = store->OpenCursor(IDBCursorDirection::Prev, error);
AutoJSAPI jsapi;
jsapi.Init();
mRequest = store->OpenCursor(jsapi.cx(), IDBCursorDirection::Prev, error);
if (NS_WARN_IF(error.Failed())) {
return;
}
+2 -3
View File
@@ -154,9 +154,8 @@ JSEventHandler::HandleEvent(nsIDOMEvent* aEvent)
columnNumber.Construct();
columnNumber.Value() = scriptEvent->Colno();
ThreadsafeAutoJSContext cx;
error.Construct(cx);
scriptEvent->GetError(cx, &error.Value());
error.Construct(nsContentUtils::RootingCxForThread());
scriptEvent->GetError(&error.Value());
} else {
msgOrEvent.SetAsEvent() = aEvent->InternalDOMEvent();
}
+227 -4
View File
@@ -4923,6 +4923,8 @@ private:
nsInterfaceHashtable<nsCStringHashKey, mozIStorageStatement>
mCachedStatements;
RefPtr<UpdateRefcountFunction> mUpdateRefcountFunction;
RefPtr<QuotaObject> mQuotaObject;
RefPtr<QuotaObject> mJournalQuotaObject;
bool mInReadTransaction;
bool mInWriteTransaction;
@@ -4999,6 +5001,12 @@ public:
void
Close();
nsresult
DisableQuotaChecks();
void
EnableQuotaChecks();
private:
DatabaseConnection(mozIStorageConnection* aStorageConnection,
FileManager* aFileManager);
@@ -5020,6 +5028,9 @@ private:
uint32_t aFreelistCount,
bool aNeedsCheckpoint,
bool* aFreedSomePages);
nsresult
GetFileSize(const nsAString& aPath, int64_t* aResult);
};
class MOZ_STACK_CLASS DatabaseConnection::AutoSavepoint final
@@ -10241,6 +10252,106 @@ DatabaseConnection::Close()
mFileManager = nullptr;
}
nsresult
DatabaseConnection::DisableQuotaChecks()
{
AssertIsOnConnectionThread();
MOZ_ASSERT(mStorageConnection);
if (!mQuotaObject) {
MOZ_ASSERT(!mJournalQuotaObject);
nsresult rv = mStorageConnection->GetQuotaObjects(
getter_AddRefs(mQuotaObject),
getter_AddRefs(mJournalQuotaObject));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(mQuotaObject);
MOZ_ASSERT(mJournalQuotaObject);
}
mQuotaObject->DisableQuotaCheck();
mJournalQuotaObject->DisableQuotaCheck();
return NS_OK;
}
void
DatabaseConnection::EnableQuotaChecks()
{
AssertIsOnConnectionThread();
MOZ_ASSERT(mQuotaObject);
MOZ_ASSERT(mJournalQuotaObject);
RefPtr<QuotaObject> quotaObject;
RefPtr<QuotaObject> journalQuotaObject;
mQuotaObject.swap(quotaObject);
mJournalQuotaObject.swap(journalQuotaObject);
quotaObject->EnableQuotaCheck();
journalQuotaObject->EnableQuotaCheck();
int64_t fileSize;
nsresult rv = GetFileSize(quotaObject->Path(), &fileSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
int64_t journalFileSize;
rv = GetFileSize(journalQuotaObject->Path(), &journalFileSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
DebugOnly<bool> result =
journalQuotaObject->MaybeUpdateSize(journalFileSize, /* aTruncate */ true);
MOZ_ASSERT(result);
result = quotaObject->MaybeUpdateSize(fileSize, /* aTruncate */ true);
MOZ_ASSERT(result);
}
nsresult
DatabaseConnection::GetFileSize(const nsAString& aPath, int64_t* aResult)
{
MOZ_ASSERT(!aPath.IsEmpty());
MOZ_ASSERT(aResult);
nsresult rv;
nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = file->InitWithPath(aPath);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
int64_t fileSize;
bool exists;
rv = file->Exists(&exists);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (exists) {
rv = file->GetFileSize(&fileSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
} else {
fileSize = 0;
}
*aResult = fileSize;
return NS_OK;
}
DatabaseConnection::
CachedStatement::CachedStatement()
#ifdef DEBUG
@@ -10335,6 +10446,7 @@ AutoSavepoint::~AutoSavepoint()
MOZ_ASSERT(mDEBUGTransaction->GetMode() == IDBTransaction::READ_WRITE ||
mDEBUGTransaction->GetMode() ==
IDBTransaction::READ_WRITE_FLUSH ||
mDEBUGTransaction->GetMode() == IDBTransaction::CLEANUP ||
mDEBUGTransaction->GetMode() == IDBTransaction::VERSION_CHANGE);
if (NS_FAILED(mConnection->RollbackSavepoint())) {
@@ -10350,6 +10462,7 @@ AutoSavepoint::Start(const TransactionBase* aTransaction)
MOZ_ASSERT(aTransaction);
MOZ_ASSERT(aTransaction->GetMode() == IDBTransaction::READ_WRITE ||
aTransaction->GetMode() == IDBTransaction::READ_WRITE_FLUSH ||
aTransaction->GetMode() == IDBTransaction::CLEANUP ||
aTransaction->GetMode() == IDBTransaction::VERSION_CHANGE);
DatabaseConnection* connection = aTransaction->GetDatabase()->GetConnection();
@@ -10452,7 +10565,7 @@ UpdateRefcountFunction::DidCommit()
js::ProfileEntry::Category::STORAGE);
for (auto iter = mFileInfoEntries.ConstIter(); !iter.Done(); iter.Next()) {
auto value = iter.Data();
FileInfoEntry* value = iter.Data();
MOZ_ASSERT(value);
@@ -10534,9 +10647,94 @@ UpdateRefcountFunction::Reset()
MOZ_ASSERT(!mSavepointEntriesIndex.Count());
MOZ_ASSERT(!mInSavepoint);
class MOZ_STACK_CLASS CustomCleanupCallback final
: public FileInfo::CustomCleanupCallback
{
nsCOMPtr<nsIFile> mDirectory;
nsCOMPtr<nsIFile> mJournalDirectory;
public:
virtual nsresult
Cleanup(FileManager* aFileManager, int64_t aId)
{
if (!mDirectory) {
MOZ_ASSERT(!mJournalDirectory);
mDirectory = aFileManager->GetDirectory();
if (NS_WARN_IF(!mDirectory)) {
return NS_ERROR_FAILURE;
}
mJournalDirectory = aFileManager->GetJournalDirectory();
if (NS_WARN_IF(!mJournalDirectory)) {
return NS_ERROR_FAILURE;
}
}
nsCOMPtr<nsIFile> file = aFileManager->GetFileForId(mDirectory, aId);
if (NS_WARN_IF(!file)) {
return NS_ERROR_FAILURE;
}
nsresult rv;
int64_t fileSize;
if (aFileManager->EnforcingQuota()) {
rv = file->GetFileSize(&fileSize);
if (NS_WARN_IF(!file)) {
return NS_ERROR_FAILURE;
}
}
rv = file->Remove(false);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (aFileManager->EnforcingQuota()) {
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
quotaManager->DecreaseUsageForOrigin(aFileManager->Type(),
aFileManager->Group(),
aFileManager->Origin(),
fileSize);
}
file = aFileManager->GetFileForId(mJournalDirectory, aId);
if (NS_WARN_IF(!file)) {
return NS_ERROR_FAILURE;
}
rv = file->Remove(false);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
};
mJournalsToCreateBeforeCommit.Clear();
mJournalsToRemoveAfterCommit.Clear();
mJournalsToRemoveAfterAbort.Clear();
// FileInfo implementation automatically removes unreferenced files, but it's
// done asynchronously and with a delay. We want to remove them (and decrease
// quota usage) before we fire the commit event.
for (auto iter = mFileInfoEntries.ConstIter(); !iter.Done(); iter.Next()) {
FileInfoEntry* value = iter.Data();
MOZ_ASSERT(value);
FileInfo* fileInfo = value->mFileInfo.forget().take();
MOZ_ASSERT(fileInfo);
CustomCleanupCallback customCleanupCallback;
fileInfo->Release(&customCleanupCallback);
}
mFileInfoEntries.Clear();
}
@@ -13477,7 +13675,8 @@ Database::AllocPBackgroundIDBTransactionParent(
if (NS_WARN_IF(aMode != IDBTransaction::READ_ONLY &&
aMode != IDBTransaction::READ_WRITE &&
aMode != IDBTransaction::READ_WRITE_FLUSH)) {
aMode != IDBTransaction::READ_WRITE_FLUSH &&
aMode != IDBTransaction::CLEANUP)) {
ASSERT_UNLESS_FUZZING();
return nullptr;
}
@@ -13485,7 +13684,8 @@ Database::AllocPBackgroundIDBTransactionParent(
// If this is a readwrite transaction to a chrome database make sure the child
// has write access.
if (NS_WARN_IF((aMode == IDBTransaction::READ_WRITE ||
aMode == IDBTransaction::READ_WRITE_FLUSH) &&
aMode == IDBTransaction::READ_WRITE_FLUSH ||
aMode == IDBTransaction::CLEANUP) &&
mPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo &&
!mChromeWriteAccessAllowed)) {
return nullptr;
@@ -13550,7 +13750,8 @@ Database::RecvPBackgroundIDBTransactionConstructor(
MOZ_ASSERT(!aObjectStoreNames.IsEmpty());
MOZ_ASSERT(aMode == IDBTransaction::READ_ONLY ||
aMode == IDBTransaction::READ_WRITE ||
aMode == IDBTransaction::READ_WRITE_FLUSH);
aMode == IDBTransaction::READ_WRITE_FLUSH ||
aMode == IDBTransaction::CLEANUP);
MOZ_ASSERT(!mClosed);
if (IsInvalidated()) {
@@ -13710,6 +13911,13 @@ StartTransactionOp::DoDatabaseWork(DatabaseConnection* aConnection)
Transaction()->SetActiveOnConnectionThread();
if (Transaction()->GetMode() == IDBTransaction::CLEANUP) {
nsresult rv = aConnection->DisableQuotaChecks();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
if (Transaction()->GetMode() != IDBTransaction::READ_ONLY) {
nsresult rv = aConnection->BeginWriteTransaction();
if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -14006,6 +14214,7 @@ TransactionBase::VerifyRequestParams(const RequestParams& aParams) const
case RequestParams::TObjectStoreDeleteParams: {
if (NS_WARN_IF(mMode != IDBTransaction::READ_WRITE &&
mMode != IDBTransaction::READ_WRITE_FLUSH &&
mMode != IDBTransaction::CLEANUP &&
mMode != IDBTransaction::VERSION_CHANGE)) {
ASSERT_UNLESS_FUZZING();
return false;
@@ -14029,6 +14238,7 @@ TransactionBase::VerifyRequestParams(const RequestParams& aParams) const
case RequestParams::TObjectStoreClearParams: {
if (NS_WARN_IF(mMode != IDBTransaction::READ_WRITE &&
mMode != IDBTransaction::READ_WRITE_FLUSH &&
mMode != IDBTransaction::CLEANUP &&
mMode != IDBTransaction::VERSION_CHANGE)) {
ASSERT_UNLESS_FUZZING();
return false;
@@ -22095,6 +22305,7 @@ CommitOp::WriteAutoIncrementCounts()
mTransaction->AssertIsOnConnectionThread();
MOZ_ASSERT(mTransaction->GetMode() == IDBTransaction::READ_WRITE ||
mTransaction->GetMode() == IDBTransaction::READ_WRITE_FLUSH ||
mTransaction->GetMode() == IDBTransaction::CLEANUP ||
mTransaction->GetMode() == IDBTransaction::VERSION_CHANGE);
const nsTArray<RefPtr<FullObjectStoreMetadata>>& metadataArray =
@@ -22162,6 +22373,7 @@ CommitOp::CommitOrRollbackAutoIncrementCounts()
mTransaction->AssertIsOnConnectionThread();
MOZ_ASSERT(mTransaction->GetMode() == IDBTransaction::READ_WRITE ||
mTransaction->GetMode() == IDBTransaction::READ_WRITE_FLUSH ||
mTransaction->GetMode() == IDBTransaction::CLEANUP ||
mTransaction->GetMode() == IDBTransaction::VERSION_CHANGE);
nsTArray<RefPtr<FullObjectStoreMetadata>>& metadataArray =
@@ -22293,6 +22505,12 @@ CommitOp::Run()
CommitOrRollbackAutoIncrementCounts();
connection->FinishWriteTransaction();
if (mTransaction->GetMode() == IDBTransaction::CLEANUP) {
connection->DoIdleProcessing(/* aNeedsCheckpoint */ true);
connection->EnableQuotaChecks();
}
}
}
@@ -24627,6 +24845,11 @@ ObjectStoreAddOrPutRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
return rv;
}
rv2 = journalFile->Remove(false);
if (NS_WARN_IF(NS_FAILED(rv2))) {
return rv;
}
if (mFileManager->EnforcingQuota()) {
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
+10 -2
View File
@@ -130,7 +130,8 @@ FileInfo::GetReferences(int32_t* aRefCnt,
void
FileInfo::UpdateReferences(ThreadSafeAutoRefCnt& aRefCount,
int32_t aDelta)
int32_t aDelta,
CustomCleanupCallback* aCustomCleanupCallback)
{
// XXX This can go away once DOM objects no longer hold FileInfo objects...
// Looking at you, BlobImplBase...
@@ -169,7 +170,14 @@ FileInfo::UpdateReferences(ThreadSafeAutoRefCnt& aRefCount,
}
if (needsCleanup) {
Cleanup();
if (aCustomCleanupCallback) {
nsresult rv = aCustomCleanupCallback->Cleanup(mFileManager, Id());
if (NS_FAILED(rv)) {
NS_WARNING("Custom cleanup failed!");
}
} else {
Cleanup();
}
}
delete this;
+17 -3
View File
@@ -27,6 +27,8 @@ class FileInfo
RefPtr<FileManager> mFileManager;
public:
class CustomCleanupCallback;
static
FileInfo* Create(FileManager* aFileManager, int64_t aId);
@@ -39,9 +41,9 @@ public:
}
void
Release()
Release(CustomCleanupCallback* aCustomCleanupCallback = nullptr)
{
UpdateReferences(mRefCnt, -1);
UpdateReferences(mRefCnt, -1, aCustomCleanupCallback);
}
void
@@ -74,7 +76,8 @@ protected:
private:
void
UpdateReferences(ThreadSafeAutoRefCnt& aRefCount,
int32_t aDelta);
int32_t aDelta,
CustomCleanupCallback* aCustomCleanupCallback = nullptr);
bool
LockedClearDBRefs();
@@ -83,6 +86,17 @@ private:
Cleanup();
};
class NS_NO_VTABLE FileInfo::CustomCleanupCallback
{
public:
virtual nsresult
Cleanup(FileManager* aFileManager, int64_t aId) = 0;
protected:
CustomCleanupCallback()
{ }
};
} // namespace indexedDB
} // namespace dom
} // namespace mozilla
+2 -1
View File
@@ -589,7 +589,8 @@ IDBCursor::Update(JSContext* aCx, JS::Handle<JS::Value> aValue,
return nullptr;
}
if (IsSourceDeleted() ||
if (mTransaction->GetMode() == IDBTransaction::CLEANUP ||
IsSourceDeleted() ||
!mHaveValue ||
mType == Type_ObjectStoreKey ||
mType == Type_IndexKey) {
+33 -29
View File
@@ -175,6 +175,7 @@ IDBDatabase::IDBDatabase(IDBOpenDBRequest* aRequest,
, mFileHandleDisabled(aRequest->IsFileHandleDisabled())
, mClosed(false)
, mInvalidated(false)
, mQuotaExceeded(false)
{
MOZ_ASSERT(aRequest);
MOZ_ASSERT(aFactory);
@@ -556,42 +557,28 @@ IDBDatabase::DeleteObjectStore(const nsAString& aName, ErrorResult& aRv)
}
already_AddRefed<IDBTransaction>
IDBDatabase::Transaction(const StringOrStringSequence& aStoreNames,
IDBDatabase::Transaction(JSContext* aCx,
const StringOrStringSequence& aStoreNames,
IDBTransactionMode aMode,
ErrorResult& aRv)
{
AssertIsOnOwningThread();
aRv.MightThrowJSException();
if (aMode == IDBTransactionMode::Readwriteflush &&
if ((aMode == IDBTransactionMode::Readwriteflush ||
aMode == IDBTransactionMode::Cleanup) &&
!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);
aRv.ThrowTypeError<MSG_INVALID_ENUM_VALUE>(
NS_LITERAL_STRING("Argument 2 of IDBDatabase.transaction"),
NS_LITERAL_STRING("readwriteflush"),
NS_LITERAL_STRING("IDBTransactionMode"));
return nullptr;
}
RefPtr<IDBTransaction> transaction;
aRv = Transaction(aStoreNames, aMode, getter_AddRefs(transaction));
aRv = Transaction(aCx, aStoreNames, aMode, getter_AddRefs(transaction));
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
@@ -600,13 +587,15 @@ IDBDatabase::Transaction(const StringOrStringSequence& aStoreNames,
}
nsresult
IDBDatabase::Transaction(const StringOrStringSequence& aStoreNames,
IDBDatabase::Transaction(JSContext* aCx,
const StringOrStringSequence& aStoreNames,
IDBTransactionMode aMode,
IDBTransaction** aTransaction)
{
AssertIsOnOwningThread();
if (NS_WARN_IF(aMode == IDBTransactionMode::Readwriteflush &&
if (NS_WARN_IF((aMode == IDBTransactionMode::Readwriteflush ||
aMode == IDBTransactionMode::Cleanup) &&
!IndexedDatabaseManager::ExperimentalFeaturesEnabled())) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
@@ -679,11 +668,20 @@ IDBDatabase::Transaction(const StringOrStringSequence& aStoreNames,
mode = IDBTransaction::READ_ONLY;
break;
case IDBTransactionMode::Readwrite:
mode = IDBTransaction::READ_WRITE;
if (mQuotaExceeded) {
mode = IDBTransaction::CLEANUP;
mQuotaExceeded = false;
} else {
mode = IDBTransaction::READ_WRITE;
}
break;
case IDBTransactionMode::Readwriteflush:
mode = IDBTransaction::READ_WRITE_FLUSH;
break;
case IDBTransactionMode::Cleanup:
mode = IDBTransaction::CLEANUP;
mQuotaExceeded = false;
break;
case IDBTransactionMode::Versionchange:
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
@@ -692,7 +690,7 @@ IDBDatabase::Transaction(const StringOrStringSequence& aStoreNames,
}
RefPtr<IDBTransaction> transaction =
IDBTransaction::Create(this, sortedStoreNames, mode);
IDBTransaction::Create(aCx, this, sortedStoreNames, mode);
if (NS_WARN_IF(!transaction)) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
@@ -716,6 +714,10 @@ IDBDatabase::Transaction(const StringOrStringSequence& aStoreNames,
transaction->SetBackgroundActor(actor);
if (mode == IDBTransaction::CLEANUP) {
ExpireFileActors(/* aExpireAll */ true);
}
transaction.forget(aTransaction);
return NS_OK;
}
@@ -730,7 +732,8 @@ IDBDatabase::Storage() const
}
already_AddRefed<IDBRequest>
IDBDatabase::CreateMutableFile(const nsAString& aName,
IDBDatabase::CreateMutableFile(JSContext* aCx,
const nsAString& aName,
const Optional<nsAString>& aType,
ErrorResult& aRv)
{
@@ -754,7 +757,7 @@ IDBDatabase::CreateMutableFile(const nsAString& aName,
CreateFileParams params(nsString(aName), type);
RefPtr<IDBRequest> request = IDBRequest::Create(this, nullptr);
RefPtr<IDBRequest> request = IDBRequest::Create(aCx, this, nullptr);
MOZ_ASSERT(request);
BackgroundDatabaseRequestChild* actor =
@@ -861,6 +864,7 @@ IDBDatabase::AbortTransactions(bool aShouldWarn)
// We warn for any transactions that could have written data.
case IDBTransaction::READ_WRITE:
case IDBTransaction::READ_WRITE_FLUSH:
case IDBTransaction::CLEANUP:
case IDBTransaction::VERSION_CHANGE:
transactionsThatNeedWarning.AppendElement(transaction);
break;
+16 -5
View File
@@ -83,6 +83,7 @@ class IDBDatabase final
const bool mFileHandleDisabled;
bool mClosed;
bool mInvalidated;
bool mQuotaExceeded;
public:
static already_AddRefed<IDBDatabase>
@@ -149,6 +150,12 @@ public:
return mInvalidated;
}
void
SetQuotaExceeded()
{
mQuotaExceeded = true;
}
void
EnterSetVersionTransaction(uint64_t aNewVersion);
@@ -222,13 +229,15 @@ public:
// This will be called from the DOM.
already_AddRefed<IDBTransaction>
Transaction(const StringOrStringSequence& aStoreNames,
Transaction(JSContext* aCx,
const StringOrStringSequence& aStoreNames,
IDBTransactionMode aMode,
ErrorResult& aRv);
// This can be called from C++ to avoid JS exception.
nsresult
Transaction(const StringOrStringSequence& aStoreNames,
Transaction(JSContext* aCx,
const StringOrStringSequence& aStoreNames,
IDBTransactionMode aMode,
IDBTransaction** aTransaction);
@@ -240,16 +249,18 @@ public:
IMPL_EVENT_HANDLER(versionchange)
already_AddRefed<IDBRequest>
CreateMutableFile(const nsAString& aName,
CreateMutableFile(JSContext* aCx,
const nsAString& aName,
const Optional<nsAString>& aType,
ErrorResult& aRv);
already_AddRefed<IDBRequest>
MozCreateFileHandle(const nsAString& aName,
MozCreateFileHandle(JSContext* aCx,
const nsAString& aName,
const Optional<nsAString>& aType,
ErrorResult& aRv)
{
return CreateMutableFile(aName, aType, aRv);
return CreateMutableFile(aCx, aName, aType, aRv);
}
void
+30 -18
View File
@@ -481,11 +481,13 @@ IDBFactory::IncrementParentLoggingRequestSerialNumber()
}
already_AddRefed<IDBOpenDBRequest>
IDBFactory::Open(const nsAString& aName,
IDBFactory::Open(JSContext* aCx,
const nsAString& aName,
uint64_t aVersion,
ErrorResult& aRv)
{
return OpenInternal(/* aPrincipal */ nullptr,
return OpenInternal(aCx,
/* aPrincipal */ nullptr,
aName,
Optional<uint64_t>(aVersion),
Optional<StorageType>(),
@@ -494,11 +496,13 @@ IDBFactory::Open(const nsAString& aName,
}
already_AddRefed<IDBOpenDBRequest>
IDBFactory::Open(const nsAString& aName,
IDBFactory::Open(JSContext* aCx,
const nsAString& aName,
const IDBOpenDBOptions& aOptions,
ErrorResult& aRv)
{
return OpenInternal(/* aPrincipal */ nullptr,
return OpenInternal(aCx,
/* aPrincipal */ nullptr,
aName,
aOptions.mVersion,
aOptions.mStorage,
@@ -507,11 +511,13 @@ IDBFactory::Open(const nsAString& aName,
}
already_AddRefed<IDBOpenDBRequest>
IDBFactory::DeleteDatabase(const nsAString& aName,
IDBFactory::DeleteDatabase(JSContext* aCx,
const nsAString& aName,
const IDBOpenDBOptions& aOptions,
ErrorResult& aRv)
{
return OpenInternal(/* aPrincipal */ nullptr,
return OpenInternal(aCx,
/* aPrincipal */ nullptr,
aName,
Optional<uint64_t>(),
aOptions.mStorage,
@@ -545,7 +551,8 @@ IDBFactory::Cmp(JSContext* aCx, JS::Handle<JS::Value> aFirst,
}
already_AddRefed<IDBOpenDBRequest>
IDBFactory::OpenForPrincipal(nsIPrincipal* aPrincipal,
IDBFactory::OpenForPrincipal(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aName,
uint64_t aVersion,
ErrorResult& aRv)
@@ -556,7 +563,8 @@ IDBFactory::OpenForPrincipal(nsIPrincipal* aPrincipal,
}
MOZ_ASSERT(nsContentUtils::IsCallerChrome());
return OpenInternal(aPrincipal,
return OpenInternal(aCx,
aPrincipal,
aName,
Optional<uint64_t>(aVersion),
Optional<StorageType>(),
@@ -565,7 +573,8 @@ IDBFactory::OpenForPrincipal(nsIPrincipal* aPrincipal,
}
already_AddRefed<IDBOpenDBRequest>
IDBFactory::OpenForPrincipal(nsIPrincipal* aPrincipal,
IDBFactory::OpenForPrincipal(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aName,
const IDBOpenDBOptions& aOptions,
ErrorResult& aRv)
@@ -576,7 +585,8 @@ IDBFactory::OpenForPrincipal(nsIPrincipal* aPrincipal,
}
MOZ_ASSERT(nsContentUtils::IsCallerChrome());
return OpenInternal(aPrincipal,
return OpenInternal(aCx,
aPrincipal,
aName,
aOptions.mVersion,
aOptions.mStorage,
@@ -585,7 +595,8 @@ IDBFactory::OpenForPrincipal(nsIPrincipal* aPrincipal,
}
already_AddRefed<IDBOpenDBRequest>
IDBFactory::DeleteForPrincipal(nsIPrincipal* aPrincipal,
IDBFactory::DeleteForPrincipal(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aName,
const IDBOpenDBOptions& aOptions,
ErrorResult& aRv)
@@ -596,7 +607,8 @@ IDBFactory::DeleteForPrincipal(nsIPrincipal* aPrincipal,
}
MOZ_ASSERT(nsContentUtils::IsCallerChrome());
return OpenInternal(aPrincipal,
return OpenInternal(aCx,
aPrincipal,
aName,
Optional<uint64_t>(),
aOptions.mStorage,
@@ -605,7 +617,8 @@ IDBFactory::DeleteForPrincipal(nsIPrincipal* aPrincipal,
}
already_AddRefed<IDBOpenDBRequest>
IDBFactory::OpenInternal(nsIPrincipal* aPrincipal,
IDBFactory::OpenInternal(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aName,
const Optional<uint64_t>& aVersion,
const Optional<StorageType>& aStorageType,
@@ -730,16 +743,15 @@ IDBFactory::OpenInternal(nsIPrincipal* aPrincipal,
RefPtr<IDBOpenDBRequest> request;
if (mWindow) {
JS::Rooted<JSObject*> scriptOwner(nsContentUtils::RootingCxForThread(),
JS::Rooted<JSObject*> scriptOwner(aCx,
nsGlobalWindow::Cast(mWindow.get())->FastGetGlobalJSObject());
MOZ_ASSERT(scriptOwner);
request = IDBOpenDBRequest::CreateForWindow(this, mWindow, scriptOwner);
request = IDBOpenDBRequest::CreateForWindow(aCx, this, mWindow, scriptOwner);
} else {
JS::Rooted<JSObject*> scriptOwner(nsContentUtils::RootingCxForThread(),
mOwningObject);
JS::Rooted<JSObject*> scriptOwner(aCx, mOwningObject);
request = IDBOpenDBRequest::CreateForJS(this, scriptOwner);
request = IDBOpenDBRequest::CreateForJS(aCx, this, scriptOwner);
if (!request) {
MOZ_ASSERT(!NS_IsMainThread());
aRv.ThrowUncatchableException();
+14 -7
View File
@@ -164,17 +164,20 @@ public:
IsChrome() const;
already_AddRefed<IDBOpenDBRequest>
Open(const nsAString& aName,
Open(JSContext* aCx,
const nsAString& aName,
uint64_t aVersion,
ErrorResult& aRv);
already_AddRefed<IDBOpenDBRequest>
Open(const nsAString& aName,
Open(JSContext* aCx,
const nsAString& aName,
const IDBOpenDBOptions& aOptions,
ErrorResult& aRv);
already_AddRefed<IDBOpenDBRequest>
DeleteDatabase(const nsAString& aName,
DeleteDatabase(JSContext* aCx,
const nsAString& aName,
const IDBOpenDBOptions& aOptions,
ErrorResult& aRv);
@@ -185,19 +188,22 @@ public:
ErrorResult& aRv);
already_AddRefed<IDBOpenDBRequest>
OpenForPrincipal(nsIPrincipal* aPrincipal,
OpenForPrincipal(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aName,
uint64_t aVersion,
ErrorResult& aRv);
already_AddRefed<IDBOpenDBRequest>
OpenForPrincipal(nsIPrincipal* aPrincipal,
OpenForPrincipal(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aName,
const IDBOpenDBOptions& aOptions,
ErrorResult& aRv);
already_AddRefed<IDBOpenDBRequest>
DeleteForPrincipal(nsIPrincipal* aPrincipal,
DeleteForPrincipal(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aName,
const IDBOpenDBOptions& aOptions,
ErrorResult& aRv);
@@ -231,7 +237,8 @@ private:
nsIPrincipal** aPrincipal);
already_AddRefed<IDBOpenDBRequest>
OpenInternal(nsIPrincipal* aPrincipal,
OpenInternal(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aName,
const Optional<uint64_t>& aVersion,
const Optional<StorageType>& aStorageType,
+6 -6
View File
@@ -29,7 +29,7 @@ namespace dom {
namespace {
already_AddRefed<IDBRequest>
GenerateRequest(IDBIndex* aIndex)
GenerateRequest(JSContext* aCx, IDBIndex* aIndex)
{
MOZ_ASSERT(aIndex);
aIndex->AssertIsOnOwningThread();
@@ -37,7 +37,7 @@ GenerateRequest(IDBIndex* aIndex)
IDBTransaction* transaction = aIndex->ObjectStore()->Transaction();
RefPtr<IDBRequest> request =
IDBRequest::Create(aIndex, transaction->Database(), transaction);
IDBRequest::Create(aCx, aIndex, transaction->Database(), transaction);
MOZ_ASSERT(request);
return request.forget();
@@ -299,7 +299,7 @@ IDBIndex::GetInternal(bool aKeyOnly,
params = IndexGetParams(objectStoreId, indexId, serializedKeyRange);
}
RefPtr<IDBRequest> request = GenerateRequest(this);
RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
MOZ_ASSERT(request);
if (aKeyOnly) {
@@ -383,7 +383,7 @@ IDBIndex::GetAllInternal(bool aKeysOnly,
params = IndexGetAllParams(objectStoreId, indexId, optionalKeyRange, limit);
}
RefPtr<IDBRequest> request = GenerateRequest(this);
RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
MOZ_ASSERT(request);
if (aKeysOnly) {
@@ -482,7 +482,7 @@ IDBIndex::OpenCursorInternal(bool aKeysOnly,
params = Move(openParams);
}
RefPtr<IDBRequest> request = GenerateRequest(this);
RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
MOZ_ASSERT(request);
if (aKeysOnly) {
@@ -560,7 +560,7 @@ IDBIndex::Count(JSContext* aCx,
params.optionalKeyRange() = void_t();
}
RefPtr<IDBRequest> request = GenerateRequest(this);
RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
MOZ_ASSERT(request);
IDB_LOG_MARK("IndexedDB %s: Child Transaction[%lld] Request[%llu]: "
+13 -12
View File
@@ -197,7 +197,7 @@ struct MOZ_STACK_CLASS GetAddInfoClosure final
};
already_AddRefed<IDBRequest>
GenerateRequest(IDBObjectStore* aObjectStore)
GenerateRequest(JSContext* aCx, IDBObjectStore* aObjectStore)
{
MOZ_ASSERT(aObjectStore);
aObjectStore->AssertIsOnOwningThread();
@@ -205,7 +205,7 @@ GenerateRequest(IDBObjectStore* aObjectStore)
IDBTransaction* transaction = aObjectStore->Transaction();
RefPtr<IDBRequest> request =
IDBRequest::Create(aObjectStore, transaction->Database(), transaction);
IDBRequest::Create(aCx, aObjectStore, transaction->Database(), transaction);
MOZ_ASSERT(request);
return request.forget();
@@ -1295,7 +1295,8 @@ IDBObjectStore::AddOrPut(JSContext* aCx,
MOZ_ASSERT(aCx);
MOZ_ASSERT_IF(aFromCursor, aOverwrite);
if (mDeletedSpec) {
if (mTransaction->GetMode() == IDBTransaction::CLEANUP ||
mDeletedSpec) {
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
return nullptr;
}
@@ -1401,7 +1402,7 @@ IDBObjectStore::AddOrPut(JSContext* aCx,
params = ObjectStoreAddParams(commonParams);
}
RefPtr<IDBRequest> request = GenerateRequest(this);
RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
MOZ_ASSERT(request);
if (!aFromCursor) {
@@ -1480,7 +1481,7 @@ IDBObjectStore::GetAllInternal(bool aKeysOnly,
params = ObjectStoreGetAllParams(id, optionalKeyRange, limit);
}
RefPtr<IDBRequest> request = GenerateRequest(this);
RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
MOZ_ASSERT(request);
if (aKeysOnly) {
@@ -1517,7 +1518,7 @@ IDBObjectStore::GetAllInternal(bool aKeysOnly,
}
already_AddRefed<IDBRequest>
IDBObjectStore::Clear(ErrorResult& aRv)
IDBObjectStore::Clear(JSContext* aCx, ErrorResult& aRv)
{
AssertIsOnOwningThread();
@@ -1539,7 +1540,7 @@ IDBObjectStore::Clear(ErrorResult& aRv)
ObjectStoreClearParams params;
params.objectStoreId() = Id();
RefPtr<IDBRequest> request = GenerateRequest(this);
RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
MOZ_ASSERT(request);
IDB_LOG_MARK("IndexedDB %s: Child Transaction[%lld] Request[%llu]: "
@@ -1738,7 +1739,7 @@ IDBObjectStore::Get(JSContext* aCx,
params.objectStoreId() = Id();
keyRange->ToSerialized(params.keyRange());
RefPtr<IDBRequest> request = GenerateRequest(this);
RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
MOZ_ASSERT(request);
IDB_LOG_MARK("IndexedDB %s: Child Transaction[%lld] Request[%llu]: "
@@ -1796,7 +1797,7 @@ IDBObjectStore::DeleteInternal(JSContext* aCx,
params.objectStoreId() = Id();
keyRange->ToSerialized(params.keyRange());
RefPtr<IDBRequest> request = GenerateRequest(this);
RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
MOZ_ASSERT(request);
if (!aFromCursor) {
@@ -2065,7 +2066,7 @@ IDBObjectStore::Count(JSContext* aCx,
params.optionalKeyRange() = void_t();
}
RefPtr<IDBRequest> request = GenerateRequest(this);
RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
MOZ_ASSERT(request);
IDB_LOG_MARK("IndexedDB %s: Child Transaction[%lld] Request[%llu]: "
@@ -2092,7 +2093,7 @@ IDBObjectStore::OpenCursorInternal(bool aKeysOnly,
ErrorResult& aRv)
{
AssertIsOnOwningThread();
MOZ_ASSERT_IF(!aCx, aRange.isUndefined());
MOZ_ASSERT(aCx);
if (mDeletedSpec) {
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
@@ -2142,7 +2143,7 @@ IDBObjectStore::OpenCursorInternal(bool aKeysOnly,
params = Move(openParams);
}
RefPtr<IDBRequest> request = GenerateRequest(this);
RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
MOZ_ASSERT(request);
if (aKeysOnly) {
+4 -5
View File
@@ -202,7 +202,7 @@ public:
Get(JSContext* aCx, JS::Handle<JS::Value> aKey, ErrorResult& aRv);
already_AddRefed<IDBRequest>
Clear(ErrorResult& aRv);
Clear(JSContext* aCx, ErrorResult& aRv);
already_AddRefed<IDBIndex>
CreateIndex(const nsAString& aName,
@@ -262,12 +262,13 @@ public:
}
already_AddRefed<IDBRequest>
OpenCursor(IDBCursorDirection aDirection,
OpenCursor(JSContext* aCx,
IDBCursorDirection aDirection,
ErrorResult& aRv)
{
AssertIsOnOwningThread();
return OpenCursorInternal(/* aKeysOnly */ false, nullptr,
return OpenCursorInternal(/* aKeysOnly */ false, aCx,
JS::UndefinedHandleValue, aDirection, aRv);
}
@@ -347,8 +348,6 @@ private:
const IDBIndexParameters& aOptionalParameters,
ErrorResult& aRv);
// aCx is allowed to be null but only if aRange.isUndefined(). In that case,
// we don't actually use aCx for anything, so it's OK.
already_AddRefed<IDBRequest>
OpenCursorInternal(bool aKeysOnly,
JSContext* aCx,
+19 -14
View File
@@ -112,14 +112,16 @@ IDBRequest::InitMembers()
// static
already_AddRefed<IDBRequest>
IDBRequest::Create(IDBDatabase* aDatabase,
IDBRequest::Create(JSContext* aCx,
IDBDatabase* aDatabase,
IDBTransaction* aTransaction)
{
MOZ_ASSERT(aCx);
MOZ_ASSERT(aDatabase);
aDatabase->AssertIsOnOwningThread();
RefPtr<IDBRequest> request = new IDBRequest(aDatabase);
CaptureCaller(request->mFilename, &request->mLineNo, &request->mColumn);
CaptureCaller(aCx, request->mFilename, &request->mLineNo, &request->mColumn);
request->mTransaction = aTransaction;
request->SetScriptOwner(aDatabase->GetScriptOwner());
@@ -129,14 +131,15 @@ IDBRequest::Create(IDBDatabase* aDatabase,
// static
already_AddRefed<IDBRequest>
IDBRequest::Create(IDBObjectStore* aSourceAsObjectStore,
IDBRequest::Create(JSContext* aCx,
IDBObjectStore* aSourceAsObjectStore,
IDBDatabase* aDatabase,
IDBTransaction* aTransaction)
{
MOZ_ASSERT(aSourceAsObjectStore);
aSourceAsObjectStore->AssertIsOnOwningThread();
RefPtr<IDBRequest> request = Create(aDatabase, aTransaction);
RefPtr<IDBRequest> request = Create(aCx, aDatabase, aTransaction);
request->mSourceAsObjectStore = aSourceAsObjectStore;
@@ -145,14 +148,15 @@ IDBRequest::Create(IDBObjectStore* aSourceAsObjectStore,
// static
already_AddRefed<IDBRequest>
IDBRequest::Create(IDBIndex* aSourceAsIndex,
IDBRequest::Create(JSContext* aCx,
IDBIndex* aSourceAsIndex,
IDBDatabase* aDatabase,
IDBTransaction* aTransaction)
{
MOZ_ASSERT(aSourceAsIndex);
aSourceAsIndex->AssertIsOnOwningThread();
RefPtr<IDBRequest> request = Create(aDatabase, aTransaction);
RefPtr<IDBRequest> request = Create(aCx, aDatabase, aTransaction);
request->mSourceAsIndex = aSourceAsIndex;
@@ -183,15 +187,14 @@ IDBRequest::SetLoggingSerialNumber(uint64_t aLoggingSerialNumber)
}
void
IDBRequest::CaptureCaller(nsAString& aFilename, uint32_t* aLineNo,
uint32_t* aColumn)
IDBRequest::CaptureCaller(JSContext* aCx, nsAString& aFilename,
uint32_t* aLineNo, uint32_t* aColumn)
{
MOZ_ASSERT(aFilename.IsEmpty());
MOZ_ASSERT(aLineNo);
MOZ_ASSERT(aColumn);
ThreadsafeAutoJSContext cx;
nsJSUtils::GetCallingLocation(cx, aFilename, aLineNo, aColumn);
nsJSUtils::GetCallingLocation(aCx, aFilename, aLineNo, aColumn);
}
void
@@ -529,7 +532,8 @@ IDBOpenDBRequest::~IDBOpenDBRequest()
// static
already_AddRefed<IDBOpenDBRequest>
IDBOpenDBRequest::CreateForWindow(IDBFactory* aFactory,
IDBOpenDBRequest::CreateForWindow(JSContext* aCx,
IDBFactory* aFactory,
nsPIDOMWindow* aOwner,
JS::Handle<JSObject*> aScriptOwner)
{
@@ -542,7 +546,7 @@ IDBOpenDBRequest::CreateForWindow(IDBFactory* aFactory,
RefPtr<IDBOpenDBRequest> request =
new IDBOpenDBRequest(aFactory, aOwner, fileHandleDisabled);
CaptureCaller(request->mFilename, &request->mLineNo, &request->mColumn);
CaptureCaller(aCx, request->mFilename, &request->mLineNo, &request->mColumn);
request->SetScriptOwner(aScriptOwner);
@@ -551,7 +555,8 @@ IDBOpenDBRequest::CreateForWindow(IDBFactory* aFactory,
// static
already_AddRefed<IDBOpenDBRequest>
IDBOpenDBRequest::CreateForJS(IDBFactory* aFactory,
IDBOpenDBRequest::CreateForJS(JSContext* aCx,
IDBFactory* aFactory,
JS::Handle<JSObject*> aScriptOwner)
{
MOZ_ASSERT(aFactory);
@@ -562,7 +567,7 @@ IDBOpenDBRequest::CreateForJS(IDBFactory* aFactory,
RefPtr<IDBOpenDBRequest> request =
new IDBOpenDBRequest(aFactory, nullptr, fileHandleDisabled);
CaptureCaller(request->mFilename, &request->mLineNo, &request->mColumn);
CaptureCaller(aCx, request->mFilename, &request->mLineNo, &request->mColumn);
request->SetScriptOwner(aScriptOwner);
+11 -6
View File
@@ -67,20 +67,23 @@ public:
class ResultCallback;
static already_AddRefed<IDBRequest>
Create(IDBDatabase* aDatabase, IDBTransaction* aTransaction);
Create(JSContext* aCx, IDBDatabase* aDatabase, IDBTransaction* aTransaction);
static already_AddRefed<IDBRequest>
Create(IDBObjectStore* aSource,
Create(JSContext* aCx,
IDBObjectStore* aSource,
IDBDatabase* aDatabase,
IDBTransaction* aTransaction);
static already_AddRefed<IDBRequest>
Create(IDBIndex* aSource,
Create(JSContext* aCx,
IDBIndex* aSource,
IDBDatabase* aDatabase,
IDBTransaction* aTransaction);
static void
CaptureCaller(nsAString& aFilename, uint32_t* aLineNo, uint32_t* aColumn);
CaptureCaller(JSContext* aCx, nsAString& aFilename, uint32_t* aLineNo,
uint32_t* aColumn);
static uint64_t
NextSerialNumber();
@@ -234,12 +237,14 @@ class IDBOpenDBRequest final
public:
static already_AddRefed<IDBOpenDBRequest>
CreateForWindow(IDBFactory* aFactory,
CreateForWindow(JSContext* aCx,
IDBFactory* aFactory,
nsPIDOMWindow* aOwner,
JS::Handle<JSObject*> aScriptOwner);
static already_AddRefed<IDBOpenDBRequest>
CreateForJS(IDBFactory* aFactory,
CreateForJS(JSContext* aCx,
IDBFactory* aFactory,
JS::Handle<JSObject*> aScriptOwner);
bool
+11 -3
View File
@@ -207,7 +207,7 @@ IDBTransaction::CreateVersionChange(
// static
already_AddRefed<IDBTransaction>
IDBTransaction::Create(IDBDatabase* aDatabase,
IDBTransaction::Create(JSContext* aCx, IDBDatabase* aDatabase,
const nsTArray<nsString>& aObjectStoreNames,
Mode aMode)
{
@@ -216,11 +216,12 @@ IDBTransaction::Create(IDBDatabase* aDatabase,
MOZ_ASSERT(!aObjectStoreNames.IsEmpty());
MOZ_ASSERT(aMode == READ_ONLY ||
aMode == READ_WRITE ||
aMode == READ_WRITE_FLUSH);
aMode == READ_WRITE_FLUSH ||
aMode == CLEANUP);
RefPtr<IDBTransaction> transaction =
new IDBTransaction(aDatabase, aObjectStoreNames, aMode);
IDBRequest::CaptureCaller(transaction->mFilename, &transaction->mLineNo,
IDBRequest::CaptureCaller(aCx, transaction->mFilename, &transaction->mLineNo,
&transaction->mColumn);
transaction->SetScriptOwner(aDatabase->GetScriptOwner());
@@ -763,6 +764,10 @@ IDBTransaction::FireCompleteOrAbortEvents(nsresult aResult)
eNotCancelable);
MOZ_ASSERT(event);
} else {
if (aResult == NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR) {
mDatabase->SetQuotaExceeded();
}
if (!mError && !mAbortedByScript) {
mError = new DOMError(GetOwner(), aResult);
}
@@ -838,6 +843,9 @@ IDBTransaction::GetMode(ErrorResult& aRv) const
case READ_WRITE_FLUSH:
return IDBTransactionMode::Readwriteflush;
case CLEANUP:
return IDBTransactionMode::Cleanup;
case VERSION_CHANGE:
return IDBTransactionMode::Versionchange;
+3 -1
View File
@@ -59,6 +59,7 @@ public:
READ_ONLY = 0,
READ_WRITE,
READ_WRITE_FLUSH,
CLEANUP,
VERSION_CHANGE,
// Only needed for IPC serialization helper, should never be used in code.
@@ -123,7 +124,7 @@ public:
int64_t aNextIndexId);
static already_AddRefed<IDBTransaction>
Create(IDBDatabase* aDatabase,
Create(JSContext* aCx, IDBDatabase* aDatabase,
const nsTArray<nsString>& aObjectStoreNames,
Mode aMode);
@@ -188,6 +189,7 @@ public:
AssertIsOnOwningThread();
return mMode == READ_WRITE ||
mMode == READ_WRITE_FLUSH ||
mMode == CLEANUP ||
mMode == VERSION_CHANGE;
}
+1 -2
View File
@@ -515,8 +515,7 @@ IndexedDatabaseManager::CommonPostHandleEvent(EventChainPostVisitor& aVisitor,
error->GetName(errorName);
}
ThreadsafeAutoJSContext cx;
RootedDictionary<ErrorEventInit> init(cx);
RootedDictionary<ErrorEventInit> init(nsContentUtils::RootingCxForThread());
request->GetCallerLocation(init.mFilename, &init.mLineno, &init.mColno);
init.mMessage = errorName;
+3
View File
@@ -129,6 +129,9 @@ public:
case IDBTransaction::READ_WRITE_FLUSH:
AppendLiteral("\"readwriteflush\"");
break;
case IDBTransaction::CLEANUP:
AppendLiteral("\"cleanup\"");
break;
case IDBTransaction::VERSION_CHANGE:
AppendLiteral("\"versionchange\"");
break;
@@ -0,0 +1,155 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
var disableWorkerTest = "Need a way to set temporary prefs from a worker";
var testGenerator = testSteps();
function testSteps()
{
const spec = "http://foo.com";
const name =
this.window ? window.location.pathname : "test_quotaExceeded_recovery";
const objectStoreName = "foo";
// We want 32 MB database, but there's the group limit so we need to
// multiply by 5.
const tempStorageLimitKB = 32 * 1024 * 5;
// Store in 1 MB chunks.
const dataSize = 1024 * 1024;
for (let blobs of [false, true]) {
setTemporaryStorageLimit(tempStorageLimitKB);
clearAllDatabases(continueToNextStepSync);
yield undefined;
info("Opening database");
let request = indexedDB.openForPrincipal(getPrincipal(spec), name);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;;
request.onsuccess = unexpectedSuccessHandler;
yield undefined;
// upgradeneeded
request.onupgradeneeded = unexpectedSuccessHandler;
request.onsuccess = grabEventAndContinueHandler;
info("Creating objectStore");
request.result.createObjectStore(objectStoreName);
yield undefined;
// success
let db = request.result;
db.onerror = errorHandler;
ok(true, "Adding data until quota is reached");
let obj = {
name: "foo"
}
if (!blobs) {
obj.data = getRandomView(dataSize);
}
let i = 1;
let j = 1;
while (true) {
if (blobs) {
obj.data = getBlob(getView(dataSize));
}
let trans = db.transaction(objectStoreName, "readwrite");
request = trans.objectStore(objectStoreName).add(obj, i);
request.onerror = function(event)
{
event.stopPropagation();
}
trans.oncomplete = function(event) {
i++;
j++;
testGenerator.send(true);
}
trans.onabort = function(event) {
is(trans.error.name, "QuotaExceededError", "Reached quota limit");
testGenerator.send(false);
}
let completeFired = yield undefined;
if (completeFired) {
ok(true, "Got complete event");
} else {
ok(true, "Got abort event");
if (j == 1) {
// Plain cleanup transaction (just vacuuming and checkpointing)
// couldn't shrink database any further.
break;
}
j = 1;
trans = db.transaction(objectStoreName, "cleanup");
trans.onabort = unexpectedSuccessHandler;;
trans.oncomplete = grabEventAndContinueHandler;
yield undefined;
}
}
info("Reopening database");
db.close();
request = indexedDB.openForPrincipal(getPrincipal(spec), name);
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
yield undefined;
db = request.result;
db.onerror = errorHandler;
info("Deleting some data")
let trans = db.transaction(objectStoreName, "cleanup");
trans.objectStore(objectStoreName).delete(1);
trans.onabort = unexpectedSuccessHandler;;
trans.oncomplete = grabEventAndContinueHandler;
yield undefined;
info("Adding data again")
trans = db.transaction(objectStoreName, "readwrite");
trans.objectStore(objectStoreName).add(obj, 1);
trans.onabort = unexpectedSuccessHandler;
trans.oncomplete = grabEventAndContinueHandler;
yield undefined;
info("Deleting database");
db.close();
request = indexedDB.deleteForPrincipal(getPrincipal(spec), name);
request.onerror = errorHandler;
request.onsuccess = grabEventAndContinueHandler;
yield undefined;
}
finishTest();
yield undefined;
}
@@ -0,0 +1,138 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
var disableWorkerTest = "Need a way to set temporary prefs from a worker";
var testGenerator = testSteps();
function testSteps()
{
const spec = "http://foo.com";
const name =
this.window ? window.location.pathname : "test_quotaExceeded_recovery";
const objectStoreName = "foo";
// We want 8 MB database on Android and 32 MB database on other platforms.
const groupLimitMB = mozinfo.os == "android" ? 8 : 32;
// The group limit is calculated as 20% of the global temporary storage limit.
const tempStorageLimitKB = groupLimitMB * 5 * 1024;
// Store in 1 MB chunks.
const dataSize = 1024 * 1024;
const maxIter = 10;
for (let blobs of [false, true]) {
setTemporaryStorageLimit(tempStorageLimitKB);
clearAllDatabases(continueToNextStepSync);
yield undefined;
info("Opening database");
let request = indexedDB.openForPrincipal(getPrincipal(spec), name);
request.onerror = errorHandler;
request.onupgradeneeded = grabEventAndContinueHandler;;
request.onsuccess = unexpectedSuccessHandler;
yield undefined;
// upgradeneeded
request.onupgradeneeded = unexpectedSuccessHandler;
request.onsuccess = grabEventAndContinueHandler;
info("Creating objectStore");
request.result.createObjectStore(objectStoreName, { autoIncrement: true });
yield undefined;
// success
let db = request.result;
db.onerror = errorHandler;
ok(true, "Filling database");
let obj = {
name: "foo"
}
if (!blobs) {
obj.data = getRandomView(dataSize);
}
let iter = 1;
let i = 1;
let j = 1;
while (true) {
if (blobs) {
obj.data = getBlob(getView(dataSize));
}
let trans = db.transaction(objectStoreName, "readwrite");
request = trans.objectStore(objectStoreName).add(obj);
request.onerror = function(event)
{
event.stopPropagation();
}
trans.oncomplete = function(event) {
if (iter == 1) {
i++;
}
j++;
testGenerator.send(true);
}
trans.onabort = function(event) {
is(trans.error.name, "QuotaExceededError", "Reached quota limit");
testGenerator.send(false);
}
let completeFired = yield undefined;
if (completeFired) {
ok(true, "Got complete event");
continue;
}
ok(true, "Got abort event");
if (iter++ == maxIter) {
break;
}
if (iter > 1) {
ok(i == j, "Recycled entire database");
j = 1;
}
trans = db.transaction(objectStoreName, "readwrite");
// Don't use a cursor for deleting stored blobs (Cursors prolong live
// of stored files since each record must be fetched from the database
// first which creates a memory reference to the stored blob.)
if (blobs) {
request = trans.objectStore(objectStoreName).clear();
} else {
request = trans.objectStore(objectStoreName).openCursor();
request.onsuccess = function(event) {
let cursor = event.target.result;
if (cursor) {
cursor.delete();
cursor.continue();
}
}
}
trans.onabort = unexpectedSuccessHandler;;
trans.oncomplete = grabEventAndContinueHandler;
yield undefined;
}
}
finishTest();
yield undefined;
}
@@ -15,34 +15,14 @@ function testSteps()
const tempStorageLimitKB = 1024;
const checkpointSleepTimeSec = 5;
function setLimit(limit) {
const pref = "dom.quotaManager.temporaryStorage.fixedLimit";
if (limit) {
info("Setting temporary storage limit to " + limit);
SpecialPowers.setIntPref(pref, limit);
} else {
info("Removing temporary storage limit");
SpecialPowers.clearUserPref(pref);
}
}
function getSpec(index) {
return "http://foo" + index + ".com";
}
function getPrincipal(url) {
let uri = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService)
.newURI(url, null, null);
return Cc["@mozilla.org/scriptsecuritymanager;1"]
.getService(Ci.nsIScriptSecurityManager)
.getNoAppCodebasePrincipal(uri);
}
for (let temporary of [true, false]) {
info("Testing '" + (temporary ? "temporary" : "default") + "' storage");
setLimit(tempStorageLimitKB);
setTemporaryStorageLimit(tempStorageLimitKB);
clearAllDatabases(continueToNextStepSync);
yield undefined;
@@ -165,7 +145,7 @@ function testSteps()
db.close();
db = null;
setLimit(tempStorageLimitKB * 2);
setTemporaryStorageLimit(tempStorageLimitKB * 2);
resetAllDatabases(continueToNextStepSync);
yield undefined;
@@ -197,7 +177,7 @@ function testSteps()
info("Stage 3 - " +
"Cutting storage limit in half to force deletion of some databases");
setLimit(tempStorageLimitKB / 2);
setTemporaryStorageLimit(tempStorageLimitKB / 2);
resetAllDatabases(continueToNextStepSync);
yield undefined;
@@ -220,7 +200,7 @@ function testSteps()
db.close();
db = null;
setLimit(tempStorageLimitKB * 2);
setTemporaryStorageLimit(tempStorageLimitKB * 2);
resetAllDatabases(continueToNextStepSync);
yield undefined;
@@ -332,6 +332,23 @@ function installPackagedProfile(packageName)
zipReader.close();
}
function getView(size)
{
let buffer = new ArrayBuffer(size);
let view = new Uint8Array(buffer);
is(buffer.byteLength, size, "Correct byte length");
return view;
}
function getRandomView(size)
{
let view = getView(size);
for (let i = 0; i < size; i++) {
view[i] = parseInt(Math.random() * 255)
}
return view;
}
function getBlob(str)
{
return new Blob([str], {type: "type/text"});
@@ -419,6 +436,28 @@ function verifyMutableFile(mutableFile1, file2)
});
}
function setTemporaryStorageLimit(limit)
{
const pref = "dom.quotaManager.temporaryStorage.fixedLimit";
if (limit) {
info("Setting temporary storage limit to " + limit);
SpecialPowers.setIntPref(pref, limit);
} else {
info("Removing temporary storage limit");
SpecialPowers.clearUserPref(pref);
}
}
function getPrincipal(url)
{
let uri = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService)
.newURI(url, null, null);
let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
.getService(Ci.nsIScriptSecurityManager);
return ssm.createCodebasePrincipal(uri, {});
}
var SpecialPowers = {
isMainProcess: function() {
return Components.classes["@mozilla.org/xre/app-info;1"]
@@ -25,6 +25,7 @@ support-files =
[test_blob_file_backed.js]
[test_bug1056939.js]
[test_cleanup_transaction.js]
[test_defaultStorageUpgrade.js]
[test_globalObjects_ipc.js]
skip-if = toolkit == 'android'
@@ -35,6 +36,7 @@ skip-if = true
[test_lowDiskSpace.js]
[test_metadataRestore.js]
[test_mutableFileUpgrade.js]
[test_quotaExceeded_recovery.js]
[test_readwriteflush_disabled.js]
[test_schema18upgrade.js]
[test_schema21upgrade.js]
@@ -34,8 +34,12 @@ interface nsIContentPrefObserver : nsISupports
* @param aGroup the group to which the pref belongs, or null
* if it's a global pref (applies to all sites)
* @param aName the name of the pref that was removed
* @param aIsPrivate an optional flag determining whether the
* original context is private or not
*/
void onContentPrefRemoved(in AString aGroup, in AString aName);
void onContentPrefRemoved(in AString aGroup,
in AString aName,
[optional] in boolean aIsPrivate);
};
[scriptable, function, uuid(c1b3d6df-5373-4606-8494-8bcf14a7fc62)]
+16 -8
View File
@@ -79,10 +79,14 @@ protected:
Run() override
{
NS_ASSERT_OWNINGTHREAD(PromiseReactionJob);
ThreadsafeAutoJSContext cx;
JS::Rooted<JSObject*> wrapper(cx, mPromise->GetWrapper());
MOZ_ASSERT(wrapper); // It was preserved!
JSAutoCompartment ac(cx, wrapper);
MOZ_ASSERT(mPromise->GetWrapper()); // It was preserved!
AutoJSAPI jsapi;
if (!jsapi.Init(mPromise->GetWrapper())) {
return NS_ERROR_FAILURE;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JS::Value> value(cx, mValue);
if (!MaybeWrapValue(cx, &value)) {
@@ -200,12 +204,16 @@ protected:
Run() override
{
NS_ASSERT_OWNINGTHREAD(PromiseResolveThenableJob);
ThreadsafeAutoJSContext cx;
JS::Rooted<JSObject*> wrapper(cx, mPromise->GetWrapper());
MOZ_ASSERT(wrapper); // It was preserved!
MOZ_ASSERT(mPromise->GetWrapper()); // It was preserved!
AutoJSAPI jsapi;
// If we ever change which compartment we're working in here, make sure to
// fix the fast-path for resolved-with-a-Promise in ResolveInternal.
JSAutoCompartment ac(cx, wrapper);
if (!jsapi.Init(mPromise->GetWrapper())) {
return NS_ERROR_FAILURE;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JSObject*> resolveFunc(cx,
mPromise->CreateThenableFunction(cx, mPromise, PromiseCallback::Resolve));
+7 -4
View File
@@ -422,11 +422,14 @@ private:
template <typename T>
void MaybeSomething(T& aArgument, MaybeFunc aFunc) {
ThreadsafeAutoJSContext cx;
JSObject* wrapper = PromiseObj();
MOZ_ASSERT(wrapper); // We preserved it!
MOZ_ASSERT(PromiseObj()); // It was preserved!
AutoJSAPI jsapi;
if (!jsapi.Init(mGlobal)) {
return;
}
JSContext* cx = jsapi.cx();
JSAutoCompartment ac(cx, wrapper);
JS::Rooted<JS::Value> val(cx);
if (!ToJSValue(cx, aArgument, &val)) {
HandleException(cx);
+26
View File
@@ -1808,6 +1808,10 @@ QuotaObject::MaybeUpdateSize(int64_t aSize, bool aTruncate)
MutexAutoLock lock(quotaManager->mQuotaMutex);
if (mQuotaCheckDisabled) {
return true;
}
if (mSize == aSize) {
return true;
}
@@ -1983,6 +1987,28 @@ QuotaObject::MaybeUpdateSize(int64_t aSize, bool aTruncate)
return true;
}
void
QuotaObject::DisableQuotaCheck()
{
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
MutexAutoLock lock(quotaManager->mQuotaMutex);
mQuotaCheckDisabled = true;
}
void
QuotaObject::EnableQuotaCheck()
{
QuotaManager* quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
MutexAutoLock lock(quotaManager->mQuotaMutex);
mQuotaCheckDisabled = false;
}
/*******************************************************************************
* Quota manager
******************************************************************************/
+18 -1
View File
@@ -30,12 +30,27 @@ public:
void
Release();
const nsAString&
Path() const
{
return mPath;
}
bool
MaybeUpdateSize(int64_t aSize, bool aTruncate);
void
DisableQuotaCheck();
void
EnableQuotaCheck();
private:
QuotaObject(OriginInfo* aOriginInfo, const nsAString& aPath, int64_t aSize)
: mOriginInfo(aOriginInfo), mPath(aPath), mSize(aSize)
: mOriginInfo(aOriginInfo)
, mPath(aPath)
, mSize(aSize)
, mQuotaCheckDisabled(false)
{
MOZ_COUNT_CTOR(QuotaObject);
}
@@ -61,6 +76,8 @@ private:
OriginInfo* mOriginInfo;
nsString mPath;
int64_t mSize;
bool mQuotaCheckDisabled;
};
END_QUOTA_NAMESPACE
+1
View File
@@ -15,6 +15,7 @@ enum IDBTransactionMode {
// |IndexedDatabaseManager::ExperimentalFeaturesEnabled()| function returns
// true. This mode is not yet part of the standard.
"readwriteflush",
"cleanup",
"versionchange"
};
+8
View File
@@ -141,6 +141,14 @@ nsListControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
mContent->RemoveSystemEventListener(NS_LITERAL_STRING("mousemove"),
mEventListener, false);
if (XRE_IsContentProcess() &&
Preferences::GetBool("browser.tabs.remote.desktopbehavior", false)) {
nsContentUtils::AddScriptRunner(
new AsyncEventDispatcher(mContent,
NS_LITERAL_STRING("mozhidedropdown"), true,
true));
}
nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
nsHTMLScrollFrame::DestroyFrom(aDestructRoot);
}
+23 -8
View File
@@ -1095,21 +1095,36 @@ inDOMUtils::GetBindingURLs(nsIDOMElement *aElement, nsIArray **_retval)
NS_IMETHODIMP
inDOMUtils::SetContentState(nsIDOMElement* aElement,
EventStates::InternalType aState)
EventStates::InternalType aState,
bool* aRetVal)
{
NS_ENSURE_ARG_POINTER(aElement);
RefPtr<EventStateManager> esm =
inLayoutUtils::GetEventStateManagerFor(aElement);
if (esm) {
nsCOMPtr<nsIContent> content;
content = do_QueryInterface(aElement);
NS_ENSURE_TRUE(esm, NS_ERROR_INVALID_ARG);
// XXX Invalid cast of bool to nsresult (bug 778108)
return (nsresult)esm->SetContentState(content, EventStates(aState));
}
nsCOMPtr<nsIContent> content;
content = do_QueryInterface(aElement);
NS_ENSURE_TRUE(content, NS_ERROR_INVALID_ARG);
return NS_ERROR_FAILURE;
*aRetVal = esm->SetContentState(content, EventStates(aState));
return NS_OK;
}
NS_IMETHODIMP
inDOMUtils::RemoveContentState(nsIDOMElement* aElement,
EventStates::InternalType aState,
bool* aRetVal)
{
NS_ENSURE_ARG_POINTER(aElement);
RefPtr<EventStateManager> esm =
inLayoutUtils::GetEventStateManagerFor(aElement);
NS_ENSURE_TRUE(esm, NS_ERROR_INVALID_ARG);
*aRetVal = esm->SetContentState(nullptr, EventStates(aState));
return NS_OK;
}
NS_IMETHODIMP
+12 -2
View File
@@ -17,7 +17,7 @@ interface nsIDOMFontFaceList;
interface nsIDOMRange;
interface nsIDOMCSSStyleSheet;
[scriptable, uuid(ec3dc3d5-41d1-4d08-ace5-7e944de6614d)]
[scriptable, uuid(362e98c3-82c2-4ad8-8dcb-00e8e4eab497)]
interface inIDOMUtils : nsISupports
{
// CSS utilities
@@ -158,7 +158,17 @@ interface inIDOMUtils : nsISupports
// content state utilities
unsigned long long getContentState(in nsIDOMElement aElement);
void setContentState(in nsIDOMElement aElement, in unsigned long long aState);
/**
* Setting and removing content state on an element. Both these functions
* calling EventStateManager::SetContentState internally, the difference is
* that for the remove case we simply pass in nullptr for the element.
* Use them accordingly.
*
* @return Returns true if the state was set successfully. See more details
* in EventStateManager.h SetContentState.
*/
bool setContentState(in nsIDOMElement aElement, in unsigned long long aState);
bool removeContentState(in nsIDOMElement aElement, in unsigned long long aState);
nsIDOMFontFaceList getUsedFontFaces(in nsIDOMRange aRange);
+2 -2
View File
@@ -20,7 +20,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=462789
/** Test for Bug 462789 **/
function do_test() {
const ERROR_FAILURE = 0x80004005;
const ERROR_INVALID_ARG = 0x80070057;
const DOCUMENT_NODE_TYPE = 9;
var utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]
@@ -79,7 +79,7 @@ function do_test() {
}
catch(e) {
e = SpecialPowers.wrap(e);
is(e.result, ERROR_FAILURE, "got the expected exception");
is(e.result, ERROR_INVALID_ARG, "got the expected exception");
}
SimpleTest.finish();
+10
View File
@@ -888,5 +888,15 @@ sqlite3_vfs* ConstructTelemetryVFS()
return tvfs;
}
already_AddRefed<QuotaObject>
GetQuotaObjectForFile(sqlite3_file *pFile)
{
MOZ_ASSERT(pFile);
telemetry_file *p = (telemetry_file *)pFile;
RefPtr<QuotaObject> result = p->quotaObject;
return result.forget();
}
} // namespace storage
} // namespace mozilla
+26
View File
@@ -6,6 +6,19 @@
#include "nsISupports.idl"
#include "mozIStorageAsyncConnection.idl"
%{C++
namespace mozilla {
namespace dom {
namespace quota {
class QuotaObject;
}
}
}
%}
[ptr] native QuotaObject(mozilla::dom::quota::QuotaObject);
interface mozIStorageAggregateFunction;
interface mozIStorageCompletionCallback;
interface mozIStorageFunction;
@@ -238,4 +251,17 @@ interface mozIStorageConnection : mozIStorageAsyncConnection {
* For unknown module names.
*/
[noscript] void enableModule(in ACString aModuleName);
/**
* Get quota objects.
*
* @param[out] aDatabaseQuotaObject
* The QuotaObject associated with the database file.
* @param[out] aJournalQuotaObject
* The QuotaObject associated with the journal file.
*
* @throws NS_ERROR_NOT_INITIALIZED.
*/
[noscript] void getQuotaObjects(out QuotaObject aDatabaseQuotaObject,
out QuotaObject aJournalQuotaObject);
};
+44
View File
@@ -19,6 +19,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/unused.h"
#include "mozilla/dom/quota/QuotaObject.h"
#include "mozIStorageAggregateFunction.h"
#include "mozIStorageCompletionCallback.h"
@@ -67,6 +68,8 @@ mozilla::LazyLogModule gStorageLog("mozStorage");
namespace mozilla {
namespace storage {
using mozilla::dom::quota::QuotaObject;
namespace {
int
@@ -1915,5 +1918,46 @@ Connection::EnableModule(const nsACString& aModuleName)
return NS_ERROR_FAILURE;
}
// Implemented in TelemetryVFS.cpp
already_AddRefed<QuotaObject>
GetQuotaObjectForFile(sqlite3_file *pFile);
NS_IMETHODIMP
Connection::GetQuotaObjects(QuotaObject** aDatabaseQuotaObject,
QuotaObject** aJournalQuotaObject)
{
MOZ_ASSERT(aDatabaseQuotaObject);
MOZ_ASSERT(aJournalQuotaObject);
if (!mDBConn) {
return NS_ERROR_NOT_INITIALIZED;
}
sqlite3_file* file;
int srv = ::sqlite3_file_control(mDBConn,
nullptr,
SQLITE_FCNTL_FILE_POINTER,
&file);
if (srv != SQLITE_OK) {
return convertResultCode(srv);
}
RefPtr<QuotaObject> databaseQuotaObject = GetQuotaObjectForFile(file);
srv = ::sqlite3_file_control(mDBConn,
nullptr,
SQLITE_FCNTL_JOURNAL_POINTER,
&file);
if (srv != SQLITE_OK) {
return convertResultCode(srv);
}
RefPtr<QuotaObject> journalQuotaObject = GetQuotaObjectForFile(file);
databaseQuotaObject.forget(aDatabaseQuotaObject);
journalQuotaObject.forget(aJournalQuotaObject);
return NS_OK;
}
} // namespace storage
} // namespace mozilla
@@ -74,6 +74,7 @@ this.AutoCompleteE10S = {
messageManager.addMessageListener("FormAutoComplete:SelectBy", this);
messageManager.addMessageListener("FormAutoComplete:GetSelectedIndex", this);
messageManager.addMessageListener("FormAutoComplete:ClosePopup", this);
messageManager.addMessageListener("FormAutoComplete:Disconnect", this);
},
_initPopup: function(browserWindow, rect, direction) {
@@ -154,10 +155,17 @@ this.AutoCompleteE10S = {
message.data.datalistResult = null;
}
let previousResult = null;
let previousSearchString = message.data.previousSearchString;
let searchString = message.data.untrimmedSearchString.toLowerCase();
if (previousSearchString && previousSearchString.length > 1 &&
searchString.includes(previousSearchString)) {
previousResult = this._resultCache;
}
formAutoComplete.autoCompleteSearchAsync(message.data.inputName,
message.data.untrimmedSearchString,
message.data.mockField,
null,
previousResult,
message.data.datalistResult,
{ onSearchCompletion:
this.onSearchComplete.bind(this) });
@@ -186,6 +194,14 @@ this.AutoCompleteE10S = {
case "FormAutoComplete:ClosePopup":
this.popup.closePopup();
break;
case "FormAutoComplete:Disconnect":
// The controller stopped controlling the current input, so clear
// any cached data. This is necessary cause otherwise we'd clear data
// only when starting a new search, but the next input could not support
// autocomplete and it would end up inheriting the existing data.
AutoCompleteE10SView.clearResults();
break;
}
},
@@ -213,23 +213,26 @@ FormAutoComplete.prototype = {
// If there were datalist results result is a FormAutoCompleteResult
// as defined in nsFormAutoCompleteResult.jsm with the entire list
// of results in wrappedResult._values and only the results from
// form history in wrappedResults.entries.
// form history in wrappedResult.entries.
// First, grab the entire list of old results.
let allResults = wrappedResult._values;
let allResults = wrappedResult._labels;
let datalistResults, datalistLabels;
if (allResults) {
// We have datalist results, extract them from the values array.
datalistResults = allResults.slice(wrappedResult.entries.length);
let filtered = [];
// Both allResults and values arrays are in the form of:
// |--wR.entries--|
// <history entries><datalist entries>
let oldLabels = allResults.slice(wrappedResult.entries.length);
let oldValues = wrappedResult._values.slice(wrappedResult.entries.length);
datalistLabels = [];
for (let i = datalistResults.length; i > 0; --i) {
if (datalistResults[i - 1].contains(searchString)) {
filtered.push(datalistResults[i - 1]);
datalistLabels.push(wrappedResult._labels[i - 1]);
datalistResults = [];
for (let i = 0; i < oldLabels.length; ++i) {
if (oldLabels[i].toLowerCase().includes(searchString)) {
datalistLabels.push(oldLabels[i]);
datalistResults.push(oldValues[i]);
}
}
datalistResults = filtered;
}
let searchTokens = searchString.split(/\s+/);
@@ -259,6 +262,9 @@ FormAutoComplete.prototype = {
let comments = new Array(filteredEntries.length + datalistResults.length).fill("");
comments[filteredEntries.length] = "separator";
// History entries don't have labels (their labels would be read
// from their values). Pad out the labels array so the datalist
// results (which do have separate values and labels) line up.
datalistLabels = new Array(filteredEntries.length).fill("").concat(datalistLabels);
wrappedResult._values = filteredEntries.concat(datalistResults);
wrappedResult._labels = datalistLabels;
@@ -300,19 +306,18 @@ FormAutoComplete.prototype = {
mergeResults(historyResult, datalistResult) {
let values = datalistResult.wrappedJSObject._values;
let labels = datalistResult.wrappedJSObject._labels;
let comments = [];
let comments = new Array(values.length).fill("");
// formHistoryResult will be null if form autocomplete is disabled. We
// historyResult will be null if form autocomplete is disabled. We
// still want the list values to display.
let entries = historyResult.wrappedJSObject.entries;
let historyResults = entries.map(function(entry) { return entry.text });
let historyResults = entries.map(entry => entry.text);
let historyComments = new Array(entries.length).fill("");
// fill out the comment column for the suggestions
// if we have any suggestions, put a label at the top
if (values.length) {
comments[0] = "separator";
comments.fill(1, "");
}
// now put the history results above the datalist suggestions
@@ -496,6 +501,7 @@ FormAutoCompleteChild.prototype = {
untrimmedSearchString: aUntrimmedSearchString,
mockField: mockField,
datalistResult: datalistResult,
previousSearchString: aPreviousResult && aPreviousResult.searchString.trim().toLowerCase(),
left: rect.left,
top: rect.top,
width: rect.width,
@@ -518,7 +524,7 @@ FormAutoCompleteChild.prototype = {
null,
Array.from(message.data.results, res => ({ text: res })),
null,
null
aUntrimmedSearchString
);
if (aListener) {
aListener.onSearchCompletion(result);
@@ -533,6 +539,17 @@ FormAutoCompleteChild.prototype = {
this.log("stopAutoCompleteSearch");
this._pendingSearch = null;
},
stopControllingInput(aField) {
let window = aField.ownerDocument.defaultView;
let topLevelDocshell = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.sameTypeRootTreeItem
.QueryInterface(Ci.nsIDocShell);
let mm = topLevelDocshell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIContentFrameMessageManager);
mm.sendAsyncMessage("FormAutoComplete:Disconnect");
}
}; // end of FormAutoCompleteChild implementation
// nsIAutoCompleteResult implementation
@@ -564,7 +581,7 @@ FormAutoCompleteResult.prototype = {
},
// Interfaces from idl...
searchString : null,
searchString : "",
errorDescription : "",
get defaultIndex() {
if (this.entries.length == 0)
@@ -1206,6 +1206,14 @@ nsFormFillController::StopControllingInput()
if (mFocusedInputNode) {
MaybeRemoveMutationObserver(mFocusedInputNode);
nsresult rv;
nsCOMPtr <nsIFormAutoComplete> formAutoComplete =
do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
if (formAutoComplete) {
formAutoComplete->StopControllingInput(mFocusedInput);
}
mFocusedInputNode = nullptr;
mFocusedInput = nullptr;
}
@@ -26,6 +26,11 @@ interface nsIFormAutoComplete: nsISupports {
* to cancel an existing search, for example, in preparation for a new search.
*/
void stopAutoCompleteSearch();
/**
* Since the controller is disconnecting, any related data must be cleared.
*/
void stopControllingInput(in nsIDOMHTMLInputElement aField);
};
[scriptable, function, uuid(604419ab-55a0-4831-9eca-1b9e67cc4751)]
@@ -1,14 +1,15 @@
[DEFAULT]
skip-if = toolkit == 'android' || buildapp == 'b2g' || os == 'linux' || e10s # linux - bug 947531
skip-if = toolkit == 'android' || buildapp == 'b2g' || os == 'linux' # linux - bug 1022386
support-files =
satchel_common.js
subtst_form_submission_1.html
subtst_privbrowsing.html
parent_utils.js
[test_bug_511615.html]
[test_bug_787624.html]
[test_datalist_with_caching.html]
[test_form_autocomplete.html]
skip-if = true # Test disabled for too many intermittent failures (bug 874429)
[test_form_autocomplete_with_list.html]
[test_form_submission.html]
[test_form_submission_cap.html]
@@ -0,0 +1,120 @@
var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/FormHistory.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://testing-common/ContentTaskUtils.jsm");
var gAutocompletePopup = Services.ww.activeWindow.
document.
getElementById("PopupAutoComplete");
assert.ok(gAutocompletePopup, "Got autocomplete popup");
var ParentUtils = {
getMenuEntries() {
let entries = [];
let column = gAutocompletePopup.tree.columns[0];
let numRows = gAutocompletePopup.tree.view.rowCount;
for (let i = 0; i < numRows; i++) {
entries.push(gAutocompletePopup.tree.view.getCellText(i, column));
}
return entries;
},
cleanUpFormHist() {
FormHistory.update({ op: "remove" });
},
updateFormHistory(changes) {
let handler = {
handleError: function (error) {
assert.ok(false, error);
sendAsyncMessage("formHistoryUpdated", { ok: false });
},
handleCompletion: function (reason) {
if (!reason)
sendAsyncMessage("formHistoryUpdated", { ok: true });
},
};
FormHistory.update(changes, handler);
},
popupshownListener() {
let results = this.getMenuEntries();
sendAsyncMessage("onpopupshown", { results });
},
countEntries(name, value) {
let obj = {};
if (name)
obj.fieldname = name;
if (value)
obj.value = value;
let count = 0;
let listener = {
handleResult(result) { count = result },
handleError(error) {
assert.ok(false, error);
sendAsyncMessage("entriesCounted", { ok: false });
},
handleCompletion(reason) {
if (!reason) {
sendAsyncMessage("entriesCounted", { ok: true, count });
}
}
};
FormHistory.count(obj, listener);
},
checkRowCount(expectedCount, expectedFirstValue = null) {
ContentTaskUtils.waitForCondition(() => {
return gAutocompletePopup.tree.view.rowCount === expectedCount &&
(!expectedFirstValue ||
expectedCount <= 1 ||
gAutocompletePopup.tree.view.getCellText(0, gAutocompletePopup.tree.columns[0]) ===
expectedFirstValue);
}).then(() => {
let results = this.getMenuEntries();
sendAsyncMessage("gotMenuChange", { results });
});
},
observe(subject, topic, data) {
assert.ok(topic === "satchel-storage-changed");
sendAsyncMessage("satchel-storage-changed", { subject: null, topic, data });
},
cleanup() {
gAutocompletePopup.removeEventListener("popupshown", this._popupshownListener);
this.cleanUpFormHist();
}
};
ParentUtils._popupshownListener =
ParentUtils.popupshownListener.bind(ParentUtils);
gAutocompletePopup.addEventListener("popupshown", ParentUtils._popupshownListener);
ParentUtils.cleanUpFormHist();
addMessageListener("updateFormHistory", (msg) => {
ParentUtils.updateFormHistory(msg.changes);
});
addMessageListener("countEntries", ({ name, value }) => {
ParentUtils.countEntries(name, value);
});
addMessageListener("waitForMenuChange", ({ expectedCount, expectedFirstValue }) => {
ParentUtils.checkRowCount(expectedCount, expectedFirstValue);
});
addMessageListener("addObserver", () => {
Services.obs.addObserver(ParentUtils, "satchel-storage-changed", false);
});
addMessageListener("removeObserver", () => {
Services.obs.removeObserver(ParentUtils, "satchel-storage-changed");
});
addMessageListener("cleanup", () => {
ParentUtils.cleanup();
});
@@ -0,0 +1,139 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Form History Autocomplete</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
<script type="text/javascript" src="satchel_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
Form History test: form field autocomplete
<p id="display"></p>
<!-- we presumably can't hide the content for this test. -->
<div id="content">
<!-- normal, basic form -->
<form id="form1" onsubmit="return false;">
<input list="suggest" type="text" name="field1">
<button type="submit">Submit</button>
</form>
<datalist id="suggest">
<option value="First"></option>
<option value="Second"></option>
<option value="Secomundo"></option>
</datalist>
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
var input = $_(1, "field1");
function setupFormHistory(aCallback) {
updateFormHistory([
{ op : "remove" },
{ op : "add", fieldname : "field1", value : "Sec" },
], () => {
spawn_task(aCallback);
});
}
function setForm(value) {
input.value = value;
input.focus();
}
// Restore the form to the default state.
function restoreForm() {
setForm("");
}
// Check for expected form data.
function checkForm(expectedValue) {
var formID = input.parentNode.id;
is(input.value, expectedValue, "Checking " + formID + " input");
}
SimpleTest.waitForExplicitFinish();
var expectingPopup = null;
function expectPopup() {
info("expecting a popup");
return new Promise(resolve => {
expectingPopup = resolve;
});
}
var testNum = 0;
function popupShownListener() {
info("popup shown for test " + testNum);
if (expectingPopup) {
expectingPopup();
expectingPopup = null;
}
else {
ok(false, "Autocomplete popup not expected during test " + testNum);
}
}
function waitForMenuChange(expectedCount) {
return new Promise(resolve => {
notifyMenuChanged(expectedCount, null, resolve);
});
}
registerPopupShownListener(popupShownListener);
function checkMenuEntries(expectedValues) {
var actualValues = getMenuEntries();
is(actualValues.length, expectedValues.length, testNum + " Checking length of expected menu");
for (var i = 0; i < expectedValues.length; i++)
is(actualValues[i], expectedValues[i], testNum + " Checking menu entry #"+i);
}
function* runTests() {
testNum++;
restoreForm();
doKey("down");
yield expectPopup();
checkMenuEntries(["Sec", "First", "Second", "Secomundo"]);
doKey("down");
doKey("return");
checkForm("Sec");
testNum++;
restoreForm();
sendString("Sec");
doKey("down");
yield expectPopup();
testNum++;
checkMenuEntries(["Sec", "Second", "Secomundo"]);
sendString("o");
yield waitForMenuChange(2);
testNum++;
checkMenuEntries(["Second", "Secomundo"]);
doKey("down");
doKey("return");
checkForm("Second");
SimpleTest.finish();
}
function startTest() {
setupFormHistory(runTests);
}
window.onload = startTest;
</script>
</pre>
</body>
</html>
@@ -653,7 +653,8 @@ function runTest() {
input.focus();
doKey("left");
expectPopup();
sendChar("a");
// Check case-insensitivity.
sendChar("A");
break;
case 206:
@@ -662,6 +663,7 @@ function runTest() {
break;
case 207:
// check that results were cached
input.focus();
doKey("right");
sendChar("q");
@@ -974,7 +976,7 @@ function runTest() {
case 601:
checkMenuEntries([], testNum);
SpecialPowers.removeAutoCompletePopupEventListener(window, "popupshown", popupShownListener);
input.blur();
SimpleTest.finish();
return;
@@ -60,8 +60,6 @@ function setForm(value) {
input.focus();
}
var autocompleteMenu = getAutocompletePopup();
// Restore the form to the default state.
function restoreForm() {
setForm("");
@@ -77,14 +75,12 @@ var testNum = 0;
var prevValue;
var expectingPopup = false;
function expectPopup()
{
function expectPopup() {
info("expecting popup for test " + testNum);
expectingPopup = true;
}
function popupShownListener()
{
function popupShownListener() {
info("popup shown for test " + testNum);
if (expectingPopup) {
expectingPopup = false;
@@ -95,7 +91,7 @@ function popupShownListener()
}
}
SpecialPowers.addAutoCompletePopupEventListener(window, "popupshown", popupShownListener);
registerPopupShownListener(popupShownListener);
/*
* Main section of test...
@@ -207,6 +203,28 @@ function runTest() {
doKey("return");
checkForm("Google");
expectPopup();
restoreForm();
doKey("down");
break;
case 10:
// Test autocompletion of datalists with cached results.
sendString("PAS");
waitForMenuChange(2);
break;
case 11:
// Continuation of test 10
sendString("S1");
waitForMenuChange(1);
break;
case 12:
doKey("down");
doKey("return");
checkForm("Google");
// Trigger autocomplete popup
// Look at form 3, try to trigger autocomplete popup
input.value = "";
@@ -332,45 +350,43 @@ function runTest() {
datalist = document.getElementById('suggest');
var added = new Option("Foo");
datalist.insertBefore(added, datalist.children[1]);
SimpleTest.executeSoon(function() {
doKey("down");
doKey("down");
doKey("return");
checkForm("Foo");
// Remove the element.
datalist.removeChild(added);
expectPopup();
restoreForm();
doKey("down");
});
waitForMenuChange(4);
break;
case 202:
// Change the first element value attribute.
doKey("down");
doKey("down");
doKey("return");
checkForm("Foo");
// Remove the element.
datalist = document.getElementById('suggest');
datalist.removeChild(datalist.children[1]);
waitForMenuChange(0);
break;
case 203:
// Change the first element value attribute.
restoreForm();
datalist = document.getElementById('suggest');
prevValue = datalist.children[0].value;
datalist.children[0].value = "foo";
expectPopup();
break;
case 203:
case 204:
doKey("down");
doKey("return");
checkForm("foo");
datalist = document.getElementById('suggest');
datalist.children[0].value = prevValue;
expectPopup();
restoreForm();
doKey("down");
waitForMenuChange(0);
break;
case 204:
case 205:
// Change the textContent to update the value attribute.
doKey("down");
restoreForm();
datalist = document.getElementById('suggest');
prevValue = datalist.children[0].getAttribute('value');
datalist.children[0].removeAttribute('value');
@@ -378,7 +394,7 @@ function runTest() {
expectPopup();
break;
case 205:
case 206:
doKey("down");
doKey("return");
checkForm("foobar");
@@ -386,14 +402,13 @@ function runTest() {
datalist = document.getElementById('suggest');
datalist.children[0].setAttribute('value', prevValue);
testNum = 299;
expectPopup();
restoreForm();
doKey("down");
waitForMenuChange(0);
break;
// Tests for filtering (or not).
case 300:
// Filters with first letter of the word.
restoreForm();
synthesizeKey("f", {});
expectPopup();
break;
@@ -411,30 +426,36 @@ function runTest() {
// Filter with a letter in the middle of the word.
synthesizeKey("i", {});
synthesizeKey("n", {});
setTimeout(function() {
doKey("down");
doKey("return");
checkForm("final");
expectPopup();
restoreForm();
doKey("down");
}, 500);
waitForMenuChange(1);
break;
case 303:
// Continuation of test 302.
doKey("down");
doKey("return");
checkForm("final");
expectPopup();
restoreForm();
doKey("down");
break;
case 304:
// Filter is disabled with mozNoFilter.
input.setAttribute('mozNoFilter', 'true');
synthesizeKey("f", {});
setTimeout(function() {
doKey("down");
doKey("return");
checkForm("Google");
input.removeAttribute('mozNoFilter');
testNum = 399;
expectPopup();
restoreForm();
doKey("down");
}, 500);
waitForMenuChange(3); // no change
break;
case 305:
// Continuation of test 304.
doKey("down");
doKey("return");
checkForm("Google");
input.removeAttribute('mozNoFilter');
testNum = 399;
expectPopup();
restoreForm();
doKey("down");
break;
case 400:
@@ -445,7 +466,7 @@ function runTest() {
ok(event.bubbles, "input event should bubble");
ok(event.cancelable, "input event should be cancelable");
checkForm("Google");
SpecialPowers.removeAutoCompletePopupEventListener(window, "popupshown", popupShownListener);
input.blur();
SimpleTest.finish();
}, false);
@@ -456,20 +477,13 @@ function runTest() {
default:
ok(false, "Unexpected invocation of test #" + testNum);
SpecialPowers.removeAutoCompletePopupEventListener(window, "popupshown", popupShownListener);
SimpleTest.finish();
return;
}
}
function waitForMenuChange(expectedCount)
{
if (autocompleteMenu.tree.view.rowCount != expectedCount) {
SimpleTest.executeSoon(function () waitForMenuChange(expectedCount));
}
else {
runTest();
}
function waitForMenuChange(expectedCount) {
notifyMenuChanged(expectedCount, null, runTest);
}
function checkMenuEntries(expectedValues, testNum) {
@@ -479,19 +493,6 @@ function checkMenuEntries(expectedValues, testNum) {
is(actualValues[i], expectedValues[i], testNum + " Checking menu entry #"+i);
}
function getMenuEntries() {
var entries = [];
// Could perhaps pull values directly from the controller, but it seems
// more reliable to test the values that are actually in the tree?
var column = autocompleteMenu.tree.columns[0];
var numRows = autocompleteMenu.tree.view.rowCount;
for (var i = 0; i < numRows; i++) {
entries.push(autocompleteMenu.tree.view.getValueAt(i, column));
}
return entries;
}
function startTest() {
setupFormHistory(runTest);
}
@@ -499,7 +500,6 @@ function startTest() {
window.onload = startTest;
SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout("untriaged");
</script>
</pre>
</body>
@@ -0,0 +1,139 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Form History Autocomplete</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
<script type="text/javascript" src="satchel_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
Form History test: form field autocomplete
<p id="display"></p>
<!-- we presumably can't hide the content for this test. -->
<div id="content">
<!-- normal, basic form -->
<form id="form1" onsubmit="return false;">
<input list="suggest" type="text" name="field1">
<button type="submit">Submit</button>
</form>
<datalist id="suggest">
<option value="First"></option>
<option value="Second"></option>
<option value="Secomundo"></option>
</datalist>
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
var input = $_(1, "field1");
function setupFormHistory(aCallback) {
updateFormHistory([
{ op : "remove" },
{ op : "add", fieldname : "field1", value : "Sec" },
], () => {
spawn_task(aCallback);
});
}
function setForm(value) {
input.value = value;
input.focus();
}
// Restore the form to the default state.
function restoreForm() {
setForm("");
}
// Check for expected form data.
function checkForm(expectedValue) {
var formID = input.parentNode.id;
is(input.value, expectedValue, "Checking " + formID + " input");
}
SimpleTest.waitForExplicitFinish();
var expectingPopup = null;
function expectPopup() {
info("expecting a popup");
return new Promise(resolve => {
expectingPopup = resolve;
});
}
var testNum = 0;
function popupShownListener() {
info("popup shown for test " + testNum);
if (expectingPopup) {
expectingPopup();
expectingPopup = null;
}
else {
ok(false, "Autocomplete popup not expected during test " + testNum);
}
}
function waitForMenuChange(expectedCount) {
return new Promise(resolve => {
notifyMenuChanged(expectedCount, null, resolve);
});
}
registerPopupShownListener(popupShownListener);
function checkMenuEntries(expectedValues) {
var actualValues = getMenuEntries();
is(actualValues.length, expectedValues.length, testNum + " Checking length of expected menu");
for (var i = 0; i < expectedValues.length; i++)
is(actualValues[i], expectedValues[i], testNum + " Checking menu entry #"+i);
}
function* runTests() {
testNum++;
restoreForm();
doKey("down");
yield expectPopup();
checkMenuEntries(["Sec", "First", "Second", "Secomundo"]);
doKey("down");
doKey("return");
checkForm("Sec");
testNum++;
restoreForm();
sendString("Sec");
doKey("down");
yield expectPopup();
testNum++;
checkMenuEntries(["Sec", "Second", "Secomundo"]);
sendString("o");
yield waitForMenuChange(2);
testNum++;
checkMenuEntries(["Second", "Secomundo"]);
doKey("down");
doKey("return");
checkForm("Second");
SimpleTest.finish();
}
function startTest() {
setupFormHistory(runTests);
}
window.onload = startTest;
</script>
</pre>
</body>
</html>
+26 -11
View File
@@ -242,6 +242,17 @@ function attachToWindow(provider, targetWindow) {
}
function hookWindowCloseForPanelClose(targetWindow) {
let _mozSocialDOMWindowClose;
if ("messageManager" in targetWindow) {
let mm = targetWindow.messageManager;
mm.sendAsyncMessage("Social:HookWindowCloseForPanelClose");
mm.addMessageListener("DOMWindowClose", _mozSocialDOMWindowClose = function() {
closePanel(targetWindow);
});
return;
}
// We allow window.close() to close the panel, so add an event handler for
// this, then cancel the event (so the window itself doesn't die) and
// close the panel instead.
@@ -251,21 +262,12 @@ function hookWindowCloseForPanelClose(targetWindow) {
.getInterface(Ci.nsIDOMWindowUtils);
dwu.allowScriptsToClose();
targetWindow.addEventListener("DOMWindowClose", function _mozSocialDOMWindowClose(evt) {
targetWindow.addEventListener("DOMWindowClose", _mozSocialDOMWindowClose = function(evt) {
let elt = targetWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler;
while (elt) {
if (elt.localName == "panel") {
elt.hidePopup();
break;
} else if (elt.localName == "chatbox") {
elt.close();
break;
}
elt = elt.parentNode;
}
closePanel(elt);
// preventDefault stops the default window.close() function being called,
// which doesn't actually close anything but causes things to get into
// a bad state (an internal 'closed' flag is set and debug builds start
@@ -277,6 +279,19 @@ function hookWindowCloseForPanelClose(targetWindow) {
}, true);
}
function closePanel(elt) {
while (elt) {
if (elt.localName == "panel") {
elt.hidePopup();
break;
} else if (elt.localName == "chatbox") {
elt.close();
break;
}
elt = elt.parentNode;
}
}
function schedule(callback) {
Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
}
+29 -2
View File
@@ -12,6 +12,10 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils",
"@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
const kStateHover = 0x00000004; // NS_EVENT_STATE_HOVER
this.EXPORTED_SYMBOLS = [
"SelectContentHelper"
@@ -29,13 +33,19 @@ this.SelectContentHelper.prototype = {
init: function() {
this.global.addMessageListener("Forms:SelectDropDownItem", this);
this.global.addMessageListener("Forms:DismissedDropDown", this);
this.global.addMessageListener("Forms:MouseOver", this);
this.global.addMessageListener("Forms:MouseOut", this);
this.global.addEventListener("pagehide", this);
this.global.addEventListener("mozhidedropdown", this);
},
uninit: function() {
this.global.removeMessageListener("Forms:SelectDropDownItem", this);
this.global.removeMessageListener("Forms:DismissedDropDown", this);
this.global.removeMessageListener("Forms:MouseOver", this);
this.global.removeMessageListener("Forms:MouseOut", this);
this.global.removeEventListener("pagehide", this);
this.global.removeEventListener("mozhidedropdown", this);
this.element = null;
this.global = null;
},
@@ -73,14 +83,31 @@ this.SelectContentHelper.prototype = {
this.uninit();
break;
case "Forms:MouseOver":
DOMUtils.setContentState(this.element, kStateHover);
break;
case "Forms:MouseOut":
DOMUtils.removeContentState(this.element, kStateHover);
break;
}
},
handleEvent: function(event) {
switch (event.type) {
case "pagehide":
this.global.sendAsyncMessage("Forms:HideDropDown", {});
this.uninit();
if (this.element.ownerDocument === event.target) {
this.global.sendAsyncMessage("Forms:HideDropDown", {});
this.uninit();
}
break;
case "mozhidedropdown":
if (this.element === event.target) {
this.global.sendAsyncMessage("Forms:HideDropDown", {});
this.uninit();
}
break;
}
}
+16 -2
View File
@@ -32,12 +32,22 @@ this.SelectParentHelper = {
menulist.selectedItem.scrollIntoView();
},
hide: function(menulist) {
menulist.menupopup.hidePopup();
hide: function(menulist, browser) {
if (currentBrowser == browser) {
menulist.menupopup.hidePopup();
}
},
handleEvent: function(event) {
switch (event.type) {
case "mouseover":
currentBrowser.messageManager.sendAsyncMessage("Forms:MouseOver", {});
break;
case "mouseout":
currentBrowser.messageManager.sendAsyncMessage("Forms:MouseOut", {});
break;
case "command":
if (event.target.hasAttribute("value")) {
currentBrowser.messageManager.sendAsyncMessage("Forms:SelectDropDownItem", {
@@ -59,11 +69,15 @@ this.SelectParentHelper = {
_registerListeners: function(popup) {
popup.addEventListener("command", this);
popup.addEventListener("popuphidden", this);
popup.addEventListener("mouseover", this);
popup.addEventListener("mouseout", this);
},
_unregisterListeners: function(popup) {
popup.removeEventListener("command", this);
popup.removeEventListener("popuphidden", this);
popup.removeEventListener("mouseover", this);
popup.removeEventListener("mouseout", this);
},
};