mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:18:48 +00:00
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:
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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().
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
Vendored
+9
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]: "
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -15,6 +15,7 @@ enum IDBTransactionMode {
|
||||
// |IndexedDatabaseManager::ExperimentalFeaturesEnabled()| function returns
|
||||
// true. This mode is not yet part of the standard.
|
||||
"readwriteflush",
|
||||
"cleanup",
|
||||
"versionchange"
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user